Исходный код
Исходный код для функций перезаписи, которые я обсуждаю ниже доступен здесь .
Обновление в Java 7
Обновленный класс Sun Pattern
для JDK7 имеет чудесный новый флаг UNICODE_CHARACTER_CLASS
, который заставляет все снова работать правильно. Он доступен как встраиваемый (?U)
внутри шаблона, так что вы можете использовать его и с оболочками класса String
. Это также спортивные исправленные определения для различных других свойств, также. Теперь он отслеживает стандарт Unicode как RL1.2 и RL1.2a из UTS # 18: Регулярные выражения Unicode . Это впечатляющее и впечатляющее улучшение, и команда разработчиков заслуживает похвалы за эту важную работу.
Проблемы с Java в Regex Unicode
Проблема с регулярными выражениями Java заключается в том, что экранирующий код Perl 1.0 - то есть \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
do not в 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 является ̲w̲r̲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
символов слова, никогда для несловесных символов. - Они не выглядят специальнодля края строки.
A \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-вместе AB
в регулярных выражениях or
равно X|Y
, а поскольку and
имеет больший приоритет, чем or
, то есть просто AB|CD
.Таким образом, каждый \b
, означающий, что границу можно безопасно заменить на:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
с \w
, определенным соответствующим образом.
(Вам может показаться странным, что компоненты A
и C
являются противоположностями. В идеальном мире вы могли бы написать это AB|D
, но какое-то время я гонялся за взаимнойисключительные противоречия в свойствах Юникода - о которых я думаю я уже позаботился, но на всякий случай я оставил двойное условие в границе. Плюс это делает его более расширяемым, если позже вы получите дополнительные идеи.)
Для \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.12
Я уже проверил поведение границ, используя все три эквивалентных определения с набором тестов, который проверяет 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 finally работает на Unicode, и работает правильно, grab полный источник изздесь . Вы можете делать это, как вам угодно, конечно.Если вы исправите это, я хотел бы услышать об этом, но вы не обязаны это делать.Это довольно коротко.Суть основной функции переписывания регулярных выражений проста:
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;
В любом случае, этот код - просто альфа-релиз, материал, который я взломал на выходных.Так не будет.
Для бета-тестирования я намерен:
сложить дублирование кода
обеспечить более понятный интерфейс в отношении экранирования неэкранированных строк и экранирования в регулярных выражениях
обеспечивает некоторую гибкость в расширении \d
и, возможно, \b
обеспечить конвенМетоды nce, которые обрабатывают поворот и вызов 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üß!