Исходный код
Исходный код функций перезаписи, которые я обсуждаю ниже , доступен здесь. .
Обновление в Java 7
Обновленный Pattern
класс Sun для JDK7 имеет чудесный новый флаг UNICODE_CHARACTER_CLASS
, который заставляет все снова работать правильно. Он доступен как встраиваемый (?U)
для внутри шаблона, поэтому вы также можете использовать его с String
оболочками класса. Также были исправлены определения для различных других свойств. Теперь он отслеживает стандарт Unicode в RL1.2 и RL1.2a из UTS # 18: Регулярные выражения Unicode . Это захватывающее и значительное улучшение, и следует поблагодарить команду разработчиков за это важное усилие.
Проблемы Unicode в Java Regex
Проблема с Java регулярных выражений является то , что Perl 1.0 charclass ускользает - значение \w
, \b
, \s
, \d
и их дополнений - не в Java распространяется на работу с Unicode. Один из них \b
имеет определенную расширенную семантику, но эти карты ни \w
, ни к идентификаторам Unicode , ни в Unicode свойство разрыва строки .
Кроме того, свойства POSIX в Java доступны следующим образом:
POSIX syntax Java syntax
[[:Lower:]] \p{Lower}
[[:Upper:]] \p{Upper}
[[:ASCII:]] \p{ASCII}
[[:Alpha:]] \p{Alpha}
[[:Digit:]] \p{Digit}
[[:Alnum:]] \p{Alnum}
[[:Punct:]] \p{Punct}
[[:Graph:]] \p{Graph}
[[:Print:]] \p{Print}
[[:Blank:]] \p{Blank}
[[:Cntrl:]] \p{Cntrl}
[[:XDigit:]] \p{XDigit}
[[:Space:]] \p{Space}
Это настоящий бардак, потому что это означает , что вещи , как Alpha
, Lower
и Space
делать не на карте Java в Unicode Alphabetic
, Lowercase
или Whitespace
свойства. Это очень раздражает. Поддержка свойств Unicode в Java строго устаревшая. , я имею в виду, что она не поддерживает никаких свойств Unicode, за последнее десятилетие.
Неспособность правильно говорить о пробелах очень раздражает. Рассмотрим следующую таблицу. Для каждой из этих кодовых точек существует столбец J-результатов для Java и столбец P-результатов для Perl или любого другого механизма регулярных выражений на основе PCRE:
Regex 001A 0085 00A0 2029
J P J P J P J P
\s 1 1 0 1 0 1 0 1
\pZ 0 0 0 0 1 1 1 1
\p{Zs} 0 0 0 0 1 1 0 0
\p{Space} 1 1 0 1 0 1 0 1
\p{Blank} 0 0 0 0 0 1 0 0
\p{Whitespace} - 1 - 1 - 1 - 1
\p{javaWhitespace} 1 - 0 - 0 - 1 -
\p{javaSpaceChar} 0 - 0 - 1 - 1 -
Видеть, что?
Практически каждый из этих результатов пробелов Java - это «wr̲o̲n̲g̲» согласно Unicode. Это действительно большая проблема. Java просто запуталась, давая «неправильные» ответы согласно существующей практике, а также согласно Unicode. Кроме того, Java даже не дает вам доступа к реальным свойствам Unicode! Фактически, Java не поддерживает никаких свойств, соответствующих пробелам Unicode.
Решение всех этих проблем и не только
Чтобы справиться с этой и многими другими связанными проблемами, вчера я написал функцию Java для перезаписи строки шаблона, которая перезаписывает эти 14 экранирований классов символов:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
заменив их на вещи, которые действительно работают в соответствии с Unicode предсказуемым и последовательным образом. Это всего лишь альфа-прототип от одного сеанса взлома, но он полностью функциональный.
Вкратце, мой код переписывает эти 14 следующим образом:
\s => [\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\S => [^\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\v => [\u000A-\u000D\u0085\u2028\u2029]
\V => [^\u000A-\u000D\u0085\u2028\u2029]
\h => [\u0009\u0020\u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]
\H => [^\u0009\u0020\u00A0\u1680\u180E\u2000\u2001-\u200A\u202F\u205F\u3000]
\w => [\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\W => [^\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\b => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\B => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\d => \p{Nd}
\D => \P{Nd}
\R => (?:(?>\u000D\u000A)|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])
\X => (?>\PM\pM*)
Некоторые моменты, которые следует учитывать ...
Которая использует для его \X
определения , что Unicode теперь ссылается как наследие графем кластера , а не как расширенный кластера графемы , так как последний довольно сложнее. Сам Perl теперь использует более изящную версию, но старая версия по-прежнему отлично работает в наиболее распространенных ситуациях. РЕДАКТИРОВАТЬ: см. Приложение внизу.
Что делать, \d
зависит от вашего намерения, но по умолчанию используется определение Uniode. Я могу видеть , что люди не всегда хотят \p{Nd}
, но иногда либо [0-9]
или \pN
.
Два определения границ \b
и \B
специально написаны для использования этого \w
определения.
Это \w
определение слишком широкое, потому что оно охватывает буквы в паренде, а не только обведенные. Свойство Unicode Other_Alphabetic
недоступно до JDK7, так что это лучшее, что вы можете сделать.
Изучение границ
Границы были проблемы с тех пор Ларри Уолл первый придумал \b
и \B
синтаксис говорить о них Perl 1.0 в 1987 году Ключ к пониманию того, как \b
и \B
оба работают, чтобы развеять два широко распространенных мифов о них:
- Они только когда - либо ищут для
\w
символов слова, никогда для символов без слов.
- Они специально не ищут край веревки.
А \b
граничные средства:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
И все это совершенно четко определяется как:
- следует слово есть
(?<=\w)
.
- предшествует слово является
(?=\w)
.
- не следует слово это
(?<!\w)
.
- не предшествует слову есть
(?!\w)
.
Следовательно, поскольку в регулярных выражениях IF-THEN
кодируется как and
ed-together AB
, то or
есть X|Y
, а поскольку and
приоритет выше or
, чем , то это просто AB|CD
. Итак, все, \b
что означает границу, можно безопасно заменить на:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
с \w
определенным соответствующим образом.
(Вы можете подумать , что странно , что A
и C
компоненты противоположны В идеальном мире, вы должны быть в состоянии написать это. AB|D
, Но на некоторое время я гоняться взаимного исключения противоречий в свойствах Unicode - которые я думаю , я позаботилась о , но на всякий случай я оставил двойное условие в границе. Плюс это делает его более расширяемым, если позже у вас появятся дополнительные идеи.)
Для \B
неграниц логика такова:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Разрешить \B
замену всех экземпляров на:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
Вот уж как \b
и \B
ведут себя. Эквивалентные модели для них
\b
использование ((IF)THEN|ELSE)
конструкции(?(?<=\w)(?!\w)|(?=\w))
\B
использование ((IF)THEN|ELSE)
конструкции(?(?=\w)(?<=\w)|(?<!\w))
Но версии с просто AB|CD
прекрасны, особенно если в вашем языке регулярных выражений нет условных шаблонов, таких как Java. ☹
Я уже проверил поведение границ, используя все три эквивалентных определения с помощью набора тестов, который проверяет 110 385 408 совпадений за запуск и который я использовал для десятка различных конфигураций данных в соответствии с:
0 .. 7F the ASCII range
80 .. FF the non-ASCII Latin1 range
100 .. FFFF the non-Latin1 BMP (Basic Multilingual Plane) range
10000 .. 10FFFF the non-BMP portion of Unicode (the "astral" planes)
Однако люди часто хотят границ другого типа. Им нужно что-то, что учитывает пробелы и края строки:
- левый край как
(?:(?<=^)|(?<=\s))
- правый край как
(?=$|\s)
Исправление Java с помощью Java
Код, который я опубликовал в другом ответе, предоставляет это и несколько других удобств. Сюда входят определения слов на естественном языке, дефисов, дефисов и апострофов, а также многое другое.
Он также позволяет указывать символы Юникода в логических кодовых точках, а не в идиотских суррогатах UTF-16. Трудно переоценить, насколько это важно! И это только для расширения строки.
Для замены charclass регулярного выражения, которая заставляет charclass в ваших регулярных выражениях Java, наконец, работать с Unicode и работать правильно, возьмите полный исходный код отсюда . Вы, конечно, можете поступать с ним как хотите. Если вы исправите это, я бы хотел услышать об этом, но вам не обязательно. Это довольно коротко. Суть основной функции перезаписи регулярных выражений проста:
switch (code_point) {
case 'b': newstr.append(boundary);
break; /* switch */
case 'B': newstr.append(not_boundary);
break; /* switch */
case 'd': newstr.append(digits_charclass);
break; /* switch */
case 'D': newstr.append(not_digits_charclass);
break; /* switch */
case 'h': newstr.append(horizontal_whitespace_charclass);
break; /* switch */
case 'H': newstr.append(not_horizontal_whitespace_charclass);
break; /* switch */
case 'v': newstr.append(vertical_whitespace_charclass);
break; /* switch */
case 'V': newstr.append(not_vertical_whitespace_charclass);
break; /* switch */
case 'R': newstr.append(linebreak);
break; /* switch */
case 's': newstr.append(whitespace_charclass);
break; /* switch */
case 'S': newstr.append(not_whitespace_charclass);
break; /* switch */
case 'w': newstr.append(identifier_charclass);
break; /* switch */
case 'W': newstr.append(not_identifier_charclass);
break; /* switch */
case 'X': newstr.append(legacy_grapheme_cluster);
break; /* switch */
default: newstr.append('\\');
newstr.append(Character.toChars(code_point));
break; /* switch */
}
saw_backslash = false;
Во всяком случае, этот код - всего лишь альфа-версия, которую я взломал на выходных. Так не останется.
Для бета-тестирования я намерен:
сложите дублирование кода
обеспечить более понятный интерфейс, касающийся неэкранированных переходов строки по сравнению с расширяющими escape-символами регулярных выражений
обеспечить некоторую гибкость в \d
расширении, и, возможно,\b
предоставить удобные методы, которые обрабатывают поворот и вызывают Pattern.compile или String.matches или еще что-то для вас
Для производственного выпуска он должен содержать javadoc и набор тестов JUnit. Я могу включить свой гигатестер, но он не написан как тесты JUnit.
добавление
У меня есть хорошие новости и плохие новости.
Хорошая новость заключается в том, что теперь у меня есть очень близкое приближение к расширенному кластеру графем, которое можно использовать для улучшения \X
.
Плохая новость заключается в следующем:
(?:(?:\u000D\u000A)|(?:[\u0E40\u0E41\u0E42\u0E43\u0E44\u0EC0\u0EC1\u0EC2\u0EC3\u0EC4\uAAB5\uAAB6\uAAB9\uAABB\uAABC]*(?:[\u1100-\u115F\uA960-\uA97C]+|([\u1100-\u115F\uA960-\uA97C]*((?:[[\u1160-\u11A2\uD7B0-\uD7C6][\uAC00\uAC1C\uAC38]][\u1160-\u11A2\uD7B0-\uD7C6]*|[\uAC01\uAC02\uAC03\uAC04])[\u11A8-\u11F9\uD7CB-\uD7FB]*))|[\u11A8-\u11F9\uD7CB-\uD7FB]+|[^[\p{Zl}\p{Zp}\p{Cc}\p{Cf}&&[^\u000D\u000A\u200C\u200D]]\u000D\u000A])[[\p{Mn}\p{Me}\u200C\u200D\u0488\u0489\u20DD\u20DE\u20DF\u20E0\u20E2\u20E3\u20E4\uA670\uA671\uA672\uFF9E\uFF9F][\p{Mc}\u0E30\u0E32\u0E33\u0E45\u0EB0\u0EB2\u0EB3]]*)|(?s:.))
который в Java вы бы написали как:
String extended_grapheme_cluster = "(?:(?:\\u000D\\u000A)|(?:[\\u0E40\\u0E41\\u0E42\\u0E43\\u0E44\\u0EC0\\u0EC1\\u0EC2\\u0EC3\\u0EC4\\uAAB5\\uAAB6\\uAAB9\\uAABB\\uAABC]*(?:[\\u1100-\\u115F\\uA960-\\uA97C]+|([\\u1100-\\u115F\\uA960-\\uA97C]*((?:[[\\u1160-\\u11A2\\uD7B0-\\uD7C6][\\uAC00\\uAC1C\\uAC38]][\\u1160-\\u11A2\\uD7B0-\\uD7C6]*|[\\uAC01\\uAC02\\uAC03\\uAC04])[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]*))|[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]+|[^[\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cf}&&[^\\u000D\\u000A\\u200C\\u200D]]\\u000D\\u000A])[[\\p{Mn}\\p{Me}\\u200C\\u200D\\u0488\\u0489\\u20DD\\u20DE\\u20DF\\u20E0\\u20E2\\u20E3\\u20E4\\uA670\\uA671\\uA672\\uFF9E\\uFF9F][\\p{Mc}\\u0E30\\u0E32\\u0E33\\u0E45\\u0EB0\\u0EB2\\u0EB3]]*)|(?s:.))";
¡Tschüß!