Code source
Le code source des fonctions de réécriture dont je parle ci - dessous est disponible ici .
Mise à jour dans Java 7
La Pattern
classe mise à jour de Sun pour JDK7 a un nouveau drapeau merveilleux UNICODE_CHARACTER_CLASS
, qui fait que tout fonctionne à nouveau correctement. Il est disponible en tant qu'intégration (?U)
à l'intérieur du motif, vous pouvez donc également l'utiliser avec les String
wrappers de la classe. Il contient également des définitions corrigées pour diverses autres propriétés. Il suit maintenant le standard Unicode, à la fois dans RL1.2 et RL1.2a à partir de UTS # 18: Expressions régulières Unicode . Il s'agit d'une amélioration passionnante et spectaculaire, et l'équipe de développement doit être félicitée pour cet effort important.
Problèmes Regex Unicode de Java
Le problème avec Java Regexes est que les évasions charClass Perl 1.0 - ce qui signifie \w
, \b
, \s
, \d
et leurs compléments - ne sont pas en Java étendues à travailler avec Unicode. Seul parmi ceux-ci, \b
bénéficie de certaines sémantiques étendues, mais celles-ci ne correspondent \w
ni aux identificateurs Unicode , ni aux propriétés de saut de ligne Unicode .
De plus, les propriétés POSIX en Java sont accessibles de cette manière:
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}
Ceci est un vrai gâchis, car cela signifie que des choses comme Alpha
, Lower
et Space
ne pas en Java carte à l'Unicode Alphabetic
, Lowercase
ou Whitespace
propriétés. C'est extrêmement ennuyeux. La prise en charge de la propriété Unicode de Java est strictement antémillénaire , ce qui signifie qu'elle ne prend en charge aucune propriété Unicode qui est sortie au cours de la dernière décennie.
Ne pas pouvoir parler correctement des espaces est très ennuyeux. Considérez le tableau suivant. Pour chacun de ces points de code, il existe à la fois une colonne J-results pour Java et une colonne P-results pour Perl ou tout autre moteur regex basé sur 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 -
Regarde ça?
Pratiquement chacun de ces résultats d'espaces blancs Java est ̲w̲r̲o̲n̲g̲ selon Unicode. C'est un très gros problème. Java est juste foiré, donnant des réponses «fausses» selon la pratique existante et aussi selon Unicode. De plus, Java ne vous donne même pas accès aux véritables propriétés Unicode! En fait, Java ne prend en charge aucune propriété qui correspond à un espace blanc Unicode.
La solution à tous ces problèmes, et plus encore
Pour résoudre ce problème et bien d'autres problèmes connexes, j'ai écrit hier une fonction Java pour réécrire une chaîne de modèle qui réécrit ces 14 échappements charclass:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
en les remplaçant par des éléments qui fonctionnent réellement pour correspondre à Unicode de manière prévisible et cohérente. Ce n'est qu'un prototype alpha d'une seule session de piratage, mais il est complètement fonctionnel.
La petite histoire est que mon code réécrit ces 14 comme suit:
\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*)
Quelques points à considérer ...
Cela utilise pour sa \X
définition ce que Unicode appelle maintenant un cluster de graphèmes hérité , et non un cluster de graphèmes étendu , car ce dernier est un peu plus compliqué. Perl lui-même utilise maintenant la version la plus sophistiquée, mais l'ancienne version est toujours parfaitement utilisable dans les situations les plus courantes. EDIT: voir addendum en bas.
Ce qu'il faut faire \d
dépend de votre intention, mais la définition par défaut est la définition Uniode. Je peux voir des gens qui ne veulent pas toujours \p{Nd}
, mais parfois l'un [0-9]
ou l' autre \pN
.
Les deux définitions de limites, \b
et \B
, sont spécifiquement écrites pour utiliser la \w
définition.
Cette \w
définition est trop large, car elle saisit les lettres rédigées et non seulement celles encerclées. La Other_Alphabetic
propriété Unicode n'est pas disponible avant JDK7, c'est donc le mieux que vous puissiez faire.
Explorer les limites
Les frontières ont été un problème depuis que Larry Wall a inventé la première syntaxe \b
et \B
pour en parler pour Perl 1.0 en 1987. La clé pour comprendre comment \b
et\B
à la fois le travail consiste à dissiper deux mythes envahissants à leur sujet:
- Ils ne recherchent jamais que des
\w
caractères de mots, jamais des caractères autres que des mots.
- Ils ne recherchent pas spécifiquement le bord de la corde.
Une \b
frontière signifie:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
Et ceux-ci sont tous définis de manière parfaitement simple comme:
- suit le mot est
(?<=\w)
.
- précède le mot est
(?=\w)
.
- ne suit pas le mot est
(?<!\w)
.
- ne précède pas le mot est
(?!\w)
.
Par conséquent, puisque IF-THEN
est codé comme un and
ensemble AB
dans les expressions régulières, un or
est X|Y
, et parce que le and
est supérieur à la priorité or
, c'est simplement AB|CD
. Donc, tout \b
cela signifie qu'une frontière peut être remplacée en toute sécurité par:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
avec le \w
défini de la manière appropriée.
(Vous pourriez trouver étrange que les composants A
et C
soient opposés. Dans un monde parfait, vous devriez être capable de l'écrire AB|D
, mais pendant un certain temps, j'ai recherché les contradictions d'exclusion mutuelle dans les propriétés Unicode - dont je pense avoir pris soin , mais j'ai laissé la condition double dans la limite au cas où. De plus, cela la rend plus extensible si vous obtenez des idées supplémentaires plus tard.)
Pour les \B
non-frontières, la logique est:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Permettre à toutes les instances de \B
d'être remplacées par:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
C'est vraiment comment \b
et \B
se comporter. Les modèles équivalents pour eux sont
\b
utiliser la ((IF)THEN|ELSE)
construction est(?(?<=\w)(?!\w)|(?=\w))
\B
utiliser la ((IF)THEN|ELSE)
construction est(?(?=\w)(?<=\w)|(?<!\w))
Mais les versions avec juste AB|CD
sont bien, surtout si vous manquez de modèles conditionnels dans votre langage regex - comme Java. ☹
J'ai déjà vérifié le comportement des limites en utilisant les trois définitions équivalentes avec une suite de tests qui vérifie 110385408 correspondances par exécution, et que j'ai exécutée sur une douzaine de configurations de données différentes selon:
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)
Cependant, les gens veulent souvent une autre sorte de frontière. Ils veulent quelque chose qui tient compte des espaces et des bords de chaîne:
- bord gauche comme
(?:(?<=^)|(?<=\s))
- bord droit comme
(?=$|\s)
Correction de Java avec Java
Le code que j'ai publié dans mon autre réponse fournit cela et bien d'autres commodités. Cela inclut les définitions des mots en langage naturel, des tirets, des traits d'union et des apostrophes, ainsi qu'un peu plus.
Il vous permet également de spécifier des caractères Unicode dans des points de code logiques, et non dans des substituts UTF-16 idiots. Il est difficile de surestimer à quel point c'est important!Et ce n'est que pour l'extension des cordes.
Pour une substitution de classe de caractères regex qui fait que la classe de caractères de vos expressions régulières Java fonctionne enfin sous Unicode et fonctionne correctement, récupérez la source complète à partir d'ici . Vous pouvez en faire ce que vous voulez, bien sûr. Si vous y apportez des correctifs, j'aimerais en entendre parler, mais ce n'est pas obligatoire. C'est assez court. Les tripes de la fonction principale de réécriture de regex sont simples:
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;
Quoi qu'il en soit, ce code est juste une version alpha, des trucs que j'ai piratés ce week-end. Cela ne restera pas ainsi.
Pour la version bêta, j'ai l'intention de:
pliez ensemble la duplication de code
fournir une interface plus claire concernant les échappements de chaîne sans échappement par rapport à l'augmentation des échappements de regex
fournir une certaine flexibilité dans l' \d
expansion, et peut-être\b
fournir des méthodes pratiques qui gèrent le retournement et l'appel de Pattern.compile ou String.matches ou autre chose pour vous
Pour la version de production, il devrait avoir javadoc et une suite de tests JUnit. Je peux inclure mon gigatester, mais ce n'est pas écrit comme des tests JUnit.
Addenda
J'ai de bonnes et de mauvaises nouvelles.
La bonne nouvelle est que j'ai maintenant une approximation très proche d'un cluster de graphèmes étendu à utiliser pour une amélioration \X
.
La mauvaise nouvelle ☺ est que ce modèle est:
(?:(?:\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:.))
qui en Java vous écririez comme:
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üß!