Я убежденный сторонник программной генерации шаблонов регулярных выражений из мета-шаблонов (см. Также Аргумент Мартина Фаулера об использовании составного регулярного выражения ). Техника очень применима в этой ситуации.
Вот решение на 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);
}
Теперь у нас есть ( как видно на 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));
}
Можно также легко использовать мета-шаблон orderedOptional
с другими разделителями и элементами, а также использовать его без мета-шаблона wholeLineNonEmpty
. Вот пример:
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(?:[,;](?!$)|$))?$
Этот шаблон принимает, например, "9"
, "1;2,4"
, "2,3"
и отклоняет, например, ""
, "4321"
, "4;3;2;1"
, "1;"
( см. На rubular.com ). Без сомнения, полученный шаблон выглядит уродливым и трудным для понимания, но именно поэтому он был сгенерирован в первую очередь программно, с использованием более простых шаблонов, которые гораздо легче понять, и процесса, который естественным образом автоматизирован.