Эквиваленты Юникода для 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 118

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 в Unicode Alphabetic,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 обе работы направлены на то, чтобы развеять два распространенных мифа о них:

  1. они только просмотр на \w слово символов никогда для символов без слов.
  2. они специально не ищут край строки.

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 в регулярных выражениях, an 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))

но версии с 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).