Эквиваленты Юникода для w и b в регулярных выражениях Java?
многие современные реализации регулярных выражений интерпретируют w
сокращение класса символов как "любая буква, цифра или соединительная пунктуация" (обычно: подчеркивание). Таким образом, регулярное выражение, как w+
соответствует таким словам, как hello
,élève
,GOÄ_432
или gefräßig
.
к сожалению, Java не. В Java, w
ограничен [A-Za-z0-9_]
. Это делает соответствующие слова, подобные упомянутым выше, трудными, среди других проблем.
также кажется, что b
слово разделитель соответствует в тех местах, где он не должен.
что было бы правильным эквивалентом .NET-like, Unicode-aware w
или b
в Java? Какие другие ярлыки нужно "переписать", чтобы сделать их Unicode-aware?
3 ответа:
исходный код
исходный код для функций перезаписи я обсуждаю ниже здесь.
обновление в Java 7
Солнце обновляется!--12--> класс для JDK7 имеет чудесный новый флаг,
UNICODE_CHARACTER_CLASS
, что заставляет все снова работать правильно. Он доступен как встраиваемый(?U)
внутри шаблона, так что вы можете использовать его сString
обертки класса тоже. Это также спортивные исправленные определения для различные другие свойства тоже. Теперь он отслеживает стандарт Unicode, в обоих RL1.2 и RL1.2а с UTS#18: регулярные выражения Юникода. Это захватывающее и значительное улучшение, и команда разработчиков заслуживает похвалы за эти важные усилия.
проблемы Юникода регулярных выражений Java
проблема с Java regexes заключается в том, что charclass Perl 1.0 экранируется - что означает
\w
,\b
,\s
,\d
и их дополнения - не в Java расширены для работы с Unicode. Один среди них,\b
обладает определенной расширенной семантикой, но эти карты ни к\w
или 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 не в Java map в UnicodeAlphabetic
,Lowercase
илиWhitespace
свойства. Это чрезвычайно раздражает. Поддержка свойств Юникода Java-это строго до миллениума, под которым я подразумеваю, что он не поддерживает свойство Unicode, которое появилось в последнее десятилетие.не в состоянии говорить о пробелах правильно супер-раздражает. Рассмотрим следующую таблицу. Для каждой из этих кодовых точек существует столбец J-results для 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 неверен в соответствии с Unicode. Это же действительно большая проблема. Java просто перепутан, давая ответы, которые являются "неправильными" в соответствии с существующей практикой, а также в соответствии с Unicode. Плюс Java даже не дает вам доступ к реальным свойствам Unicode! На самом деле, Java не поддерживает любой свойство, соответствующее пробелам Юникода.
решение всех этих проблем, и многое другое
чтобы справиться с этой и многими другими связанными с этим проблемами, вчера я написал функцию Java, чтобы переписать строку шаблона, которая переписывает эти 14 побегов charclass:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
заменив их вещами, которые на самом деле работают в соответствии с Unicode предсказуемым и последовательным образом. Это всего лишь альфа-прототип из одного hack сессии, но это полностью функционально.
короче говоря, мой код переписывает эти 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 теперь использует более причудливую версию, но старая версия по-прежнему отлично подходит для самых распространенных ситуаций. EDIT: см. Внизу.что делать
\d
зависит от вашего намерения, но по умолчанию используется определение Uniode. Я вижу, что люди не всегда хотят\p{Nd}
, а иногда[0-9]
или\pN
.два определения границы,
\b
и\B
, специально написаны для использования\w
определение.это
\w
определение слишком широкое, потому что оно захватывает буквы с пареннами, а не только обведенные. Юникод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
Эд-вместеAB
в регулярных выражениях, anor
и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))
но версии с just
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;
в любом случае, этот код - просто альфа-релиз, материал, который я взломал в выходные. Так больше не будет.
для бета-версии я намереваюсь к:
сложите вместе дублирование кода
обеспечивает более четкое интерфейсу по поводу снятия строка убегает против увеличения регулярных выражений ускользает
обеспечить некоторую гибкость в
\d
расширения, и, возможно,\b
обеспечивают удобные методы, которые обрабатывают поворот и вызов шаблона.компиляция или строка.спички или еще что-то ты
для выпуска продукции он должен иметь 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üß!
очень жаль, что у
\w
не работает. Предлагаемое решение\p{Alpha}
У меня тоже не работает.кажется
[\p{L}]
ловит все буквы в Юникоде. Так что Юникод эквивалент\w
должно быть[\p{L}\p{Digit}_]
.
В Java,
\w
и\d
не поддерживают Unicode; они соответствуют только символам ASCII,[A-Za-z0-9_]
и[0-9]
. То же самое касается\p{Alpha}
и друзья (классы символов POSIX, на которых они основаны, должны быть чувствительны к локали, но в Java они только когда-либо соответствовали символам ASCII). Если вы хотите, чтобы соответствовать Unicode "word characters" вы должны прописать его, например[\pL\p{Mn}\p{Nd}\p{Pc}]
, для букв, модификаторов без интервалов (акцентов), десятичных цифр и соединительной пунктуации.однако, Java
\b
и Unicode-savvy; он используетCharacter.isLetterOrDigit(ch)
и проверяет наличие акцентированных букв, но единственный" соединительный знак препинания", который он распознает, - это подчеркивание. EDIT: когда я пытаюсь ваш пример кода, он печатает""
иélève"
как следует (смотрите дальше ideone.com).