Эквиваленты Unicode для w и b в регулярных выражениях Java?
многие современные реализации регулярных выражений интерпретируют w
сокращение класса символов как "любая буква, цифра или соединительная пунктуация" (обычно: подчеркивание). Таким образом, выражения типа w+
соответствует таким словам, как hello
, élève
, GOÄ_432
или gefräßig
.
к сожалению, Java этого не делает. На Java,w
ограничен [A-Za-z0-9_]
. Это делает совпадающие слова, подобные упомянутым выше, трудными среди других проблем.
также кажется, что b
слово разделитель совпадает там, где не должен.
что было бы правильным эквивалентом .NET-подобного, Unicode-aware w
или b
в Java? Какие другие ярлыки нужно "переписать", чтобы сделать их Unicode-aware?
3 ответов
исходный код
исходный код для функций перезаписи, которые я обсуждаю ниже здесь.
обновление в Java 7
Солнце обновляется!--12--> класс для JDK7 имеет чудесный новый флаг,UNICODE_CHARACTER_CLASS
, что заставляет все снова работать правильно. Он доступен как встраиваемый (?U)
внутри шаблона, так что вы можете использовать его с String
обертки класса тоже. Он также sports исправил определения для различные другие свойства тоже. Теперь он отслеживает стандарт Unicode в обоих RL1.2 и RL1.2а с UTS#18: регулярные выражения Unicode. Это захватывающее и драматическое улучшение, и команда разработчиков заслуживает похвалы за эти важные усилия.
проблемы Юникода регулярных выражений Java
проблема с Java regexes заключается в том, что Perl 1.0 charclass escapes - meaning \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 не в Java карта в Unicode Alphabetic
, Lowercase
или Whitespace
свойства. Это exceeedingly раздражает. Поддержка свойств Юникода Java -строго antemillennial, под которым я подразумеваю, что он не поддерживает свойство 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
определение является чрезмерно широким, так как его хватает parenned буквы не просто обвел. ЮникодOther_Alphabetic
свойство не доступно до JDK7, так что это лучшее, что вы можете сделать.
Исследуя Границы
границы были проблемой с тех пор, как Ларри Уолл впервые появился \b
и \B
синтаксис для разговора о них для Perl 1.0 еще в 1987 году. Ключ к понимание как \b
и \B
обе работы должны развеять два всепроникающих мифа о них:
- они только просмотр for
\w
слово символов никогда для символов без слов. - они специально не ищут край строки.
A \b
граница означает:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
и все они определены совершенно прямолинейно as:
-
следующее слово и
(?<=\w)
. -
предшествует слово и
(?=\w)
. -
не следует за словом и
(?<!\w)
. -
не предшествует слово и
(?!\w)
.
IF-THEN
кодируется как and
Эд-вместе AB
в regexes, an or
is 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)
тем не менее, люди часто хотят другого рода границы. Они хотят что-то, что является пробелом и краем строки:
-
левый край as
(?:(?<=^)|(?<=\s))
-
правого края as
(?=$|\s)
исправление Java с помощью Java
код, который я разместил в мой другой ответ обеспечивает это и довольно много других удобств. Это включает определения для естественно-языковых слов, тире, дефисов и апостроф, а также немного больше.
он также позволяет указывать символы Юникода в логических кодовых точках, а не в идиотских суррогатах UTF-16. трудно переоценить, насколько это важно! и это только для расширения строки.
для замены charclass regex, которая делает charclass в ваших Java regexes наконец-то работа над Unicode,и работать правильно, схватила исходный код здесь. вы можете делать с ним, как вам угодно, конечно. Если вы исправите это, я бы с удовольствием послушал, но вы не обязаны. Он довольно короткий. Кишки основной функции перезаписи regex просты:
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 Test suite. Я могу включить свой gigatester, но он не написан как тесты 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}]
ловит все буквы в Юникоде. Таким образом, эквивалент Unicode \w
должно быть [\p{L}\p{Digit}_]
.
В Java, \w
и \d
не Unicode-aware; они соответствуют только символам ASCII,[A-Za-z0-9_]
и [0-9]
. То же самое касается \p{Alpha}
и друзья ("классы символов" POSIX, на которых они основаны, должны быть чувствительны к локали, но в Java они только когда-либо соответствовали символам ASCII). Если вы хотите, чтобы соответствовать Unicode "символы слова" вы должны по буквам, например [\pL\p{Mn}\p{Nd}\p{Pc}]
, для букв, модификаторов без интервалов (акцентов), десятичных цифр и соединительной пунктуации.
однако, Java \b
is Unicode-savvy; он использует Character.isLetterOrDigit(ch)
и проверяет наличие акцентированных букв, но единственный символ "соединительной пунктуации", который он распознает, - это подчеркивание. EDIT: когда я пробую ваш образец кода, он печатает ""
и élève"
как следует (смотрите дальше ideone.com).