3

Before everything,

  • I have gone through several other regex related questions and also successfully came up with a solution but it looks like there might be a simpler solution
  • I am a beginner with regex
  • I am using java to execute this server side

Version should be of the format "< major >.< minor >.< beta >" with either of them having a range <0-999>

My desired behaviour is as follows

0             -   0.0.0
1             -   1.0.0
000.0.0       -   0.0.0
..9           -   0.0.9
00007         -   7.0.0
090876        -   validate as false
9.00098000.00 -   validate as false
9.0900000.00  -   9.09.0
-13.0.4       -   validate as false
1a.0.4b       -   validate as false

My solution is as follows

if (StringUtils.isBlank(version)) {
 //set error
} else {
    if (!this.version.trim().matches("\\d+") && !(this.version.trim().matches("^-+"))
 && !(this.version.trim().matches("^+"))) {
        String[] versionsplit = this.version.split("\\.");
        // in the format <major>.<minor>.<beta> for major version, only
        // leading zeroes should be removed
        versionsplit[0] = versionsplit[0].trim().replaceFirst("^0+(?!$)", "");
        if (versionsplit[0].length() == 0) {
            // if major version is left blank add 0
            versionsplit[0] = "0";
        }
        for (int i = 1; i < versionsplit.length; i++) {
            // for minor and beta versions, trailing zeroes should be
            // removed
            versionsplit[i] = versionsplit[i].trim().replaceAll("0*$", "");
            if (versionsplit[i].length() == 0) {
                versionsplit[i] = "0";
            }
        }
        this.version = StringUtils.join(versionsplit, ".");
        if (versionsplit.length < 3) {
            // to comply with the standard format <major>.<minor>.<beta>
            for (int i = versionsplit.length; i < 3; i++) {
                version += ".0";
            }
        }
    } else {
//set error
}

if(< no error check > && !(version.matches("\\d(\\d(\\d)?)?(\\.\\d(\\d(\\d)?)?(\\.\\d(\\d(\\d)?)?)?)?"))) {
// set error    
}
}

Tell me if this isn't the most complicated solution, i will be glad to let it be. I just want the code to be readable for the next person.

Please ask if requirement is unclear. Also please don't be frustrated if i do not respond immediately as i am not always online

Thanks in advance.

Edit

I understand the format checker at the end is more accurate in the below way.

if(< no error check > && !(version.matches("(?:[0-9]{1,3}\\.){2}[0-9]{1,3}$"))) {
// set error    
}
das
  • 33
  • 5
  • Looks like you can use `\\b(?<!\\-)(?:[0-9]{1,3}\\.){2}[0-9]{1,3}\\b` regex. Or, in case you test isolated strings, `^(?<!\\-)(?:[0-9]{1,3}\\.){2}[0-9]{1,3}$` – Wiktor Stribiżew Apr 01 '15 at 11:47
  • In general [0-9] is better to use that \d. http://stackoverflow.com/questions/6479423/does-d-in-regex-mean-a-digit – Srb1313711 Apr 01 '15 at 11:49
  • Thanks for replying. @Srb1313711 I thought \d is shortform of [0-9]. good to know that some arabic jibberish also passes . – das Apr 02 '15 at 04:16
  • @stribizhev Now that i think about it, this looks more elegant. Thanks. But is there any way i can also put the _removing leading zeroes in the first group_ and _removing trailing zeroes in the second and third group_ along with what you gave in a single expression. I mean a one or two line code for the whole thing. – das Apr 02 '15 at 04:16

2 Answers2

0

Not sure it is better ), but here is what I have:

public static Pattern pattern = Pattern.compile("([\\d]*)[\\.]?([\\d]*)[\\.]?([\\d]*)");
public static boolean check(String input) {
    Matcher matcher = pattern.matcher(input);
    if(!matcher.matches()){
        // NOT MATCH
        return false;
    }

    String major = handleMajor(matcher.group(1));
    String minor = handleMinor(matcher.group(2));
    String rev   = handleMinor(matcher.group(3));
    if (major.length() > 3 || minor.length() > 3 || rev.length() > 3 ){
        // NOT MATCH: over the range 0-999
        return false;
    }

    // MATCH !!!
    System.out.println(major + "." + minor + "." + rev);
    return true;
}

private static String handleMajor(String in){
    if (in.matches("(0*)")) { // replace "" or "00000" with "0"
        return "0";
    } else {                  // remove leading zeros
        return in.replaceFirst("(0+)", "");
    }
}

private static String handleMinor(String in){
    if (in.matches("(0*)")) { // replace "" or "00000" with "000"
        return "000";
    } else {                  // remove trailing zeros
        String res = in + "00";
        while(res.endsWith("0") && res.length() > 3) {
            res = res.substring(0, res.length() - 1);
        }
        return res;
    }
}
Kysil Ivan
  • 917
  • 1
  • 7
  • 15
  • Thanks for the reply. This definitely looks more readable. But your _handleZeros_ function removes leading zeroes which is only needed for major version. For minor and beta versions , trailing zeroes should be removed. – das Apr 08 '15 at 05:31
  • Then 1.10.20 -> 1.1.2? – Kysil Ivan Apr 08 '15 at 06:37
  • i consider a 3 digit format for minor and beta versions. So 1.10.20 = 1.100.200=1.1.2 and 0001.010.2 = 1.010.200=1.01.2 – das Apr 08 '15 at 06:39
  • Thanks for the reply. But i guess just neglecting the zeroes in the pattern itself gives better code as @ingenious pointed out – das Apr 09 '15 at 04:02
0

Basically all you need is a better understanding of regular expressions and how they work in Java.

Here is my code example with the corresponding output, explanations will follow below:

    List<String> versions = Arrays.asList("0", "1", "000.0.0", "100.2.3","1.2.3", "..9", "00007", "090876", "9.00098000.00", "9.0900000.00", "-13.0.4", "1a.0.4b", "1.2");
    Pattern pattern = Pattern.compile("^0*([1-9][0-9]{0,2})?(?:\\.([0-9]{0,3}?)0*(?:\\.([0-9]{0,3}?)0*)?)?$");
    for (String v : versions) {
        Matcher matcher = pattern.matcher(v);
        Boolean matches = matcher.matches();
        Integer groups = matcher.groupCount();
        System.out.print(v + " evaluates to " + matches);
        // groups is always 3, because the empty string also matches
        if (matches) {
            String major = matcher.group(1);
            if (major == null || major.isEmpty()) {
                major = "0";
            }
            String minor = matcher.group(2);
            if (minor == null || minor.isEmpty()) {
                minor = "0";
            }
            String beta = matcher.group(3);
            if (beta == null || beta.isEmpty()) {
                beta = "0";
            }
            System.out.println(" ---> " + major + "." + minor + "." + beta);
        } else {
            System.out.println();
            // error handling
        }
    }

This results in the following output:

0 evaluates to true ---> 0.0.0
1 evaluates to true ---> 1.0.0
000.0.0 evaluates to true ---> 0.0.0
100.2.3 evaluates to true ---> 100.2.3
1.2.3 evaluates to true ---> 1.2.3
..9 evaluates to true ---> 0.0.9
00007 evaluates to true ---> 7.0.0
090876 evaluates to false
9.00098000.00 evaluates to false
9.0900000.00 evaluates to true ---> 9.09.0
-13.0.4 evaluates to false
1a.0.4b evaluates to false
1.2 evaluates to true ---> 1.2.0

Everything revolves around the pattern and the regular expression it is compiled from. https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html?is-external=true is a wonderful resource to get you started (and way beyond started) with regular expressions in Java.

You should look into capturing and not capturing groups and quantifiers, both greedy and reluctant.

In the regex I've provided above ([1-9][0-9]{0,2})? is a capturing group for the major version. It must start with a numeric other than 0 and can have up to 2 following numeric characters (this time with 0 included). All the leading nulls are discarded because of the leading 0* in the regex - observe how it is not inside any capturing group. The greedy quantifier ? at the end means that this group is optional. The next group is also optional, and it is furthermore a non capturing group, i.e. it won't be accessible after the match is complete. Inside it I discard some zeros and capture two more groups, of which in turn one is optional, using the very same principles.

EDIT: modified answer to accept version strings like x.y as the author suggested.

ingenious
  • 966
  • 10
  • 20
  • Thanks for the reply. Your answer is very informative and easy to understand and almost correct. Your code fails for '1.1' input. I modified the pattern that you used into this **^0*([1-9][0-9]{0,2})?(?:\\.([0-9]{0,3}?)0*(?:\\.([0-9]{0,3}?)0*)?)?$** and got correct result. Once check and modify your answer. – das Apr 09 '15 at 03:51
  • I also wanted it in as few lines as possible and so i have used " major = (major == null || major.isEmpty()) ? "0" : major; "instead of the if block. – das Apr 09 '15 at 03:58
  • OK, I didn't think x.y should be accepted as true. But as you have already observed - once you've got the basics you can easily modify the regex to your liking :) I've used if-s for better readability, but you can of course use the ternary operator as long as you're familiar with it. – ingenious Apr 09 '15 at 09:21