Ответ 1
Исходный код
Исходный код для функций перезаписи, которые обсуждаются ниже доступен здесь.
Обновление в Java 7
Обновленный Sun Pattern
класс для JDK7 имеет чудесный новый флаг UNICODE_CHARACTER_CLASS
, который заставляет все снова работать снова. Его доступно как вложенный (?U)
для внутри шаблона, поэтому вы можете использовать его с оболочками классов String
. Это также спортивно корректирует определения для других других свойств. Теперь он отслеживает стандарт Unicode в RL1.2 и RL1.2a из UTS # 18: Регулярные выражения Unicode. Это захватывающее и драматическое улучшение, и команда разработчиков заслуживает похвалы за эти важные усилия.
Javas Regex Unicode Problems
Проблема с регулярными выражениями Java заключается в том, что маркер Perl 1.0 escapes - означает \w
, \b
, \s
, \d
и их дополнения - не в Java, расширенном для работы с Unicode. Среди них \b
имеет определенную расширенную семантику, но они не привязаны ни к \w
, ни к Unicode identifier, а также Свойства разрыва 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
. Это чрезмерно раздражает. Поддержка свойств Javas Unicode строго antemillennial, под которым я подразумеваю, что он не поддерживает свойство Unicode, появившееся за последнее десятилетие.
Невозможно говорить о пробелах должным образом, это слишком раздражает. Рассмотрим следующую таблицу. Для каждой из этих кодовых точек имеется как столбец J-results для Java и столбец P-results для 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 не поддерживает какое-либо свойство, которое соответствует пробелу 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 теперь относится к как к клану наследия grapheme, а не к расширенному кластеру графем, поскольку последнее является более сложным. Сам Perl теперь использует версию fancier, но старая версия по-прежнему отлично подходит для наиболее распространенных ситуаций. EDIT: См. дополнение внизу. -
Что делать с
\d
зависит от ваших намерений, но по умолчанию это определение Uniode. Я вижу людей, которые не всегда хотят\p{Nd}
, но иногда либо[0-9]
, либо\pN
. -
Два граничных определения
\b
и\b
специально написаны для использования определения\w
. -
Это определение
\w
слишком велико, поскольку оно захватывает условные буквы, а не только круговые. Свойство UnicodeOther_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)
. - does not follow word is
(?<!\w)
. - не предшествует слову
(?!\w)
.
Поэтому, поскольку IF-THEN
закодирован как and
ed-together 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. ☹
Ive уже проверила поведение границ с использованием всех трех эквивалентных определений с набором тестов, который проверяет 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 суррогатах. Его трудно переоценить, насколько это важно! И это просто для расширения строки.
Для замены regex 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;
В любом случае, этот код - это просто альфа-релиз, материал, который я взломал в выходные. Это не останется таким.
Для бета-версии я намерен:
-
свернуть дублирование кода
-
обеспечивает более четкий интерфейс, связанный с невыполнением строковых escape-последовательностей и увеличением регулярных выражений
-
обеспечивают некоторую гибкость в расширении
\d
и, возможно,\b
-
предоставляют удобные методы, которые обрабатывают поворот и вызывают Pattern.compile или String.matches или еще что-то для вас.
Для выпуска продукции он должен иметь javadoc и набор тестов JUnit. Я могу включить мой гигантайтер, но он не написан как тесты JUnit.
Добавление
У меня хорошие новости и плохие новости.
Хорошей новостью является то, что Ive теперь получил приблизительное приближение очень к расширенному кластеру grapheme для использования в улучшенном \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 youd записывается как:
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üß!