web-dev-qa-db-fra.com

Équivalents Unicode pour \ w et \ b dans Java expressions régulières?

De nombreuses implémentations de regex modernes interprètent le \w Caractère abrégé de la classe de caractères comme "lettre, chiffre ou signe de ponctuation" (généralement: trait de soulignement). De cette façon, un regex comme \w+ correspond aux mots comme hello, élève, GOÄ_432 ou gefräßig.

Malheureusement, Java ne l'est pas. En Java, \w est limité à [A-Za-z0-9_]. Cela rend difficile la concordance de mots comme ceux mentionnés ci-dessus, entre autres problèmes.

Il semble également que le \b Le séparateur de mots correspond aux endroits où il ne devrait pas.

Quel serait l'équivalent correct d'un fichier de type .NET, Unicode-aware \w ou \b en Java? Quels autres raccourcis nécessitent une "réécriture" pour les rendre compatibles avec Unicode?

123
Tim Pietzcker

Code source

Le code source pour les fonctions de réécriture dont je parle ci-dessous est disponible ici .

Mise à jour dans Java 7

La classe Pattern mise à jour de Sun pour JDK7 possède un merveilleux nouvel indicateur, UNICODE_CHARACTER_CLASS, Qui permet à tout de fonctionner à nouveau correctement. Il est disponible sous forme de (?U) À intégrer dans le motif, vous pouvez donc l’utiliser avec les wrappers de la classe String. Il affiche également des définitions corrigées pour diverses autres propriétés. Il suit maintenant le standard Unicode, à la fois RL1.2 et RL1.2a de TS # 18: expressions régulières Unicode. Il s’agit là d’une amélioration passionnante et spectaculaire, et nous devons féliciter l’équipe de développement pour cet effort important.


Problèmes Unicode de Java avec Regex

Le problème avec les expressions rationnelles Java est que le caractère de la classe Perl 1.0 s'échappe - ce qui signifie \w, \b, \s, \d Et leurs compléments - sont pas dans Java étendu pour fonctionner avec Unicode. Seul parmi ceux-ci, \b Bénéficie de certaines sémantiques étendues, mais celles-ci ne mappent ni en \w , ni en identificateurs Unicode , ni en - propriétés de saut de ligne Unicode .

De plus, les propriétés POSIX dans 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}

C'est un vrai désastre car cela signifie que des choses comme Alpha, Lower et Space do not dans Java mappe sur les propriétés Unicode Alphabetic, Lowercase ou Whitespace. C'est extrêmement ennuyant. La propriété Unicode de Java est strictement antemillennial , ce qui veut dire qu’elle ne prend en charge aucune propriété Unicode publiée au cours de la dernière décennie.

Ne pas être capable de parler correctement des espaces est une gêne. Considérons le tableau suivant. Pour chacun de ces points de code, il existe une colonne de résultats J pour Java et une colonne de résultats P 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 Java est "né" selon "Unicode". C’est un vraiment un gros problème. Java est tout simplement foiré, donnant des réponses "fausses" selon la pratique existante et également selon Unicode. De plus, Java ne vous donne même pas accès aux propriétés Unicode réelles! En fait, Java ne prend pas en charge la propriété any qui correspond aux espaces Unicode.


La solution à tous ces problèmes, et plus encore

Pour faire face à cela et à bien d’autres problèmes connexes, j’ai écrit hier une fonction Java permettant de réécrire une chaîne de modèle qui réécrit ces 14 échappements de classes de classes:

\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 issu d’une seule session de hack, mais il est complètement fonctionnel.

La nouvelle 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 ...

  • Qui utilise pour sa définition \X Quoi nicode désigne maintenant comme un cluster de graphèmes hérité, pas comme un cluster de graphèmes étend, comme ce dernier est un peu plus compliqué. Perl lui-même utilise maintenant la version la plus sophistiquée, mais l'ancienne version reste parfaitement utilisable pour les situations les plus courantes. EDIT: Voir addenda en bas.

  • Que faire avec \d Dépend de votre intention, mais la valeur par défaut est la définition de Uniode. Je peux voir que les gens ne veulent pas toujours \p{Nd}, Mais parfois soit [0-9] Ou \pN.

  • Les deux définitions de limites, \b Et \B, Sont spécifiquement écrites pour utiliser la définition \w.

  • Cette définition de \w Est trop large, car elle saisit les lettres minuscules et pas seulement les lettres encerclées. La propriété Unicode Other_Alphabetic N’est disponible qu’à JDK7, c’est ce que vous pouvez faire de mieux.


Explorer les limites

Les limites sont un problème depuis que Larry Wall a inventé la syntaxe \b Et \B Pour en parler avec Perl 1.0 en 1987. La clé pour comprendre comment \b Et \B Ces deux travaux ont pour but de dissiper deux mythes répandus à leur sujet:

  1. Ce sont ne cherche que pour les caractères \w, never pour les caractères autres que Word.
  2. Ils ne recherchent pas spécifiquement le bord de la chaîne.

Une limite \b 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 parfaitement comme suit:

  • suit Word est (?<=\w).
  • précède Word est (?=\w).
  • Word ne suit pas est (?<!\w).
  • Word ne précède pas est (?!\w).

Par conséquent, puisque IF-THEN Est codé comme un and ed-together AB dans les expressions rationnelles, un or est X|Y, Et parce que and est plus prioritaire que or, c'est simplement AB|CD. Ainsi, chaque \b Qui signifie qu’une limite peut être remplacée en toute sécurité par:

    (?:(?<=\w)(?!\w)|(?<!\w)(?=\w))

avec le \w défini de manière appropriée.

(Vous pourriez penser qu’il est étrange que les composants A et C soient opposés. Dans un monde parfait, vous devriez pouvoir écrire ce AB|D, Mais pendant un moment, j’ai traquer les contradictions d'exclusion mutuelles dans les propriétés Unicode - ce que je pense je me suis occupé, mais j'ai laissé la double condition dans la limite juste au cas où. De plus, cela le rend plus extensible si vous avez des idées supplémentaires plus tard.)

Pour les non-limites \B, La logique est la suivante:

    IF does follow Word
        THEN does precede Word
    ELSIF doesn't follow Word
        THEN doesn't precede Word

Autoriser le remplacement de toutes les instances de \B Par:

    (?:(?<=\w)(?=\w)|(?<!\w)(?!\w))

C'est vraiment ainsi que se comportent \b Et \B. Les modèles équivalents pour eux sont

  • \b En utilisant la construction ((IF)THEN|ELSE) Est (?(?<=\w)(?!\w)|(?=\w))
  • \B En utilisant la construction ((IF)THEN|ELSE) Est (?(?=\w)(?<=\w)|(?<!\w))

Mais les versions avec seulement AB|CD Conviennent, surtout si vous ne disposez pas 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 vérifiant 110 385 408 correspondances par exécution, et sur laquelle j'ai exécuté 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 un type de limite différent. Ils veulent quelque chose qui respecte les espaces et les limites de chaîne:

  • Bord gauche comme (?:(?<=^)|(?<=\s))
  • bord droit ​​comme (?=$|\s)

Correction de Java avec Java

Le code que j'ai posté dans mon autre réponse fournit ceci et quelques autres commodités. Cela inclut les définitions de mots, tirets, traits d'union et apostrophes en langage naturel, 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 idiots de UTF-16. Il est difficile de trop insister sur son importance! Et cela ne concerne que l’extension de chaîne.

Pour la substitution de classe de caractères regex, qui fait en sorte que charclass de vos regex Java finally fonctionne sous Unicode, et fonctionne correctement, grab la source complète à partir d'ici . Vous pouvez en faire ce que vous voulez, bien sûr. Si vous apportez des solutions, j'aimerais en entendre parler, mais vous n’êtes pas obligé de le faire. C’est assez court. Le courage de la fonction principale de réécriture de regex est simple:

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 au cours du week-end. Cela ne restera pas comme ça.

Pour la bêta je compte:

  • plier la duplication de code

  • fournit une interface plus claire entre les échappements de chaîne non évidents et l'augmentation des échappements de regex

  • fournir une certaine flexibilité dans le développement \d, et peut-être le \b

  • fournir des méthodes pratiques qui gèrent le retournement et l'appelant Pattern.compile ou String.matches ou tout le reste pour vous

Pour la version de production, javadoc et une suite de tests JUnit sont nécessaires. 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 étroite de very à un cluster de graphèmes étendus à utiliser pour améliorer \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:.))

dans Java, vous écrivez:

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üß!

237
tchrist

C'est vraiment dommage que \w ne fonctionne pas. La solution proposée \p{Alpha} ne fonctionne pas pour moi non plus.

Il semble [\p{L}] intercepte toutes les lettres Unicode. Donc l'équivalent Unicode de \w devrait être [\p{L}\p{Digit}_].

15
musiKk

En Java, \w Et \d Ne sont pas compatibles avec Unicode; ils ne correspondent qu'aux caractères ASCII, [A-Za-z0-9_] et [0-9]. Il en va de même pour \p{Alpha} Et ses amis (les "classes de caractères" POSIX sur lesquelles ils sont basés sont supposés être sensibles à la localisation, mais dans Java, ils n'ont jamais fait correspondre que ASCII caractères). Si vous souhaitez faire correspondre les "caractères Word" Unicode, vous devez l'épeler, par exemple. [\pL\p{Mn}\p{Nd}\p{Pc}], Pour les lettres, les modificateurs de non-espacement (accents), les chiffres décimaux et la ponctuation de connexion.

Cependant, le code \b De Java correspond à Unicode-savvy; il utilise Character.isLetterOrDigit(ch) et vérifie également les lettres accentuées, mais le seul caractère de "ponctuation de connexion" qu'il reconnaît est le trait de soulignement. EDIT: Lorsque j'essaie votre code d'échantillon, il affiche "" Et élève" Comme il se doit ( voyez-le sur ideone.com ).

7
Alan Moore