Вихідний код
Вихідний код функцій переписування, про які я обговорюю нижче , доступний тут .
Оновлення в Java 7
Оновлений Pattern
клас Sun для JDK7 має чудовий новий прапор UNICODE_CHARACTER_CLASS
, завдяки якому все працює знову правильно. Він доступний як вбудоване (?U)
всередині візерунка, тому ви можете використовувати його також із String
обгортками класу. Він також спортивно виправляв визначення для різних інших властивостей. Тепер він відстежує Unicode Standard, як 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 - це "̲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 використовує більш шалену версію, але стара версія все ще ідеально підходить для найпоширеніших ситуацій. EDIT: Див. Додаток внизу.
Що робити, \d
залежить від вашого наміру, але типовим є визначення Uniode. Я бачу людей, які не завжди хочуть \p{Nd}
, але іноді [0-9]
або \pN
.
Два граничні визначення, \b
і \B
, спеціально написані для використання цього \w
визначення.
Це \w
визначення надто широке, тому що воно захоплює батьківські букви не лише обведені. Other_Alphabetic
Властивість Unicode доступна лише до 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
вид-разом 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
Код, який я розмістив у своїй іншій відповіді, надає це та ще ряд інших зручностей. Сюди входять визначення слів на природній мові, тире, дефіси та апострофи плюс ще трохи.
Це також дозволяє вказувати символи Unicode в логічних кодових точках, а не в ідіотичних сурогатах UTF-16. Важко перенапружити, як це важливо! І це лише для розширення рядків.
Для заміни шарж-класу з регулярними виразами, яка змушує шарклас у ваших 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
надайте зручні методи, які допомагають обертатись та викликати 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üß!