I'm a strong advocate of programmatic generation of regex patterns from meta-patterns (see also Martin Fowler's argument for using composed regex). The technique is very applicable in this situation.
Here's a solution in Java:
static String orderedOptional(String sep, String... items) {
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(
"(?:item(?:sep(?!$)|$))?"
.replace("item", item)
.replace("sep", sep)
);
}
return sb.toString();
}
static String wholeLineNonEmpty(String pattern) {
return "^(?!$)pattern$".replace("pattern", pattern);
}
Now we have (as seen on ideone.com):
String PATTERN =
wholeLineNonEmpty(
orderedOptional(" ",
"one", "two", "three")
);
String[] tests = {
"", // false
"onetwo", // false
"one two", // true
"one two ", // false
"two three", // true
"three", // true
"one three", // true
"one two three", // true
"three two one" // false
};
for (String test : tests) {
System.out.println(test.matches(PATTERN));
}
One can also easily use the orderedOptional
meta-pattern with other separator and items, and may also use it without the wholeLineNonEmpty
meta-pattern. Here's an example:
String INCREASING_DIGITS =
wholeLineNonEmpty(
orderedOptional("[,;]",
"1", "2", "3", "4", "5", "6", "7", "8", "9")
);
System.out.println(INCREASING_DIGITS);
// ^(?!$)(?:1(?:[,;](?!$)|$))?(?:2(?:[,;](?!$)|$))?(?:3(?:[,;](?!$)|$))?
// (?:4(?:[,;](?!$)|$))?(?:5(?:[,;](?!$)|$))?(?:6(?:[,;](?!$)|$))?
// (?:7(?:[,;](?!$)|$))?(?:8(?:[,;](?!$)|$))?(?:9(?:[,;](?!$)|$))?$
This pattern accepts e.g. "9"
, "1;2,4"
, "2,3"
and rejects e.g. ""
, "4321"
, "4;3;2;1"
, "1;"
(see on rubular.com). Without a doubt the resulting pattern looks ugly and hard to comprehend, but that's why it was generated programmatically in the first place, using simpler patterns that are much easier to understand and a process that is naturally automated.