Equivalents Unicode pour w et b dans les expressions régulières Java?

plusieurs implémentations modernes de regex interprètent le raccourci de la classe de caractères w comme "n'importe quelle lettre, chiffre, ou ponctuation de connexion" (habituellement: soulignement). De cette façon, un regex comme w+ correspond à des mots comme hello , élève , GOÄ_432 ou gefräßig .

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

il apparaît également que le séparateur de mots b correspond à des endroits où il ne devrait pas.

quel serait l'équivalent correct d'un .net-like, Unicode-aware w ou b en Java? Quels autres raccourcis ont besoin d'être" réécrits " pour les rendre Unicode-conscients?

118
demandé sur tchrist 2010-11-29 18:00:11

3 réponses

code Source

le code source pour les fonctions de réécriture que je discute ci-dessous est disponible ici .

mise à jour en Java 7

Soleil du jour Pattern classe pour JDK7 a un merveilleux nouveau drapeau, UNICODE_CHARACTER_CLASS , ce qui rend tout travail de nouveau à droite. Il est disponible comme intégrable (?U) pour l'intérieur du modèle, de sorte que vous pouvez l'utiliser avec l' Les emballages de la classe String aussi. Il arbore également corrigé les définitions des différentes autres propriétés, trop. Il suit maintenant la norme Unicode, dans les deux RL1.2 et RL1.2a from UTS#18: Unicode Regular Expressions . Il s'agit d'une amélioration stimulante et spectaculaire, et l'équipe de développement doit être félicitée pour cet effort important.


regex de Java Unicode Problèmes

le problème avec les java regexes est que le Perl 1.0 charclass échappe-signification \w , \b , \s , \d et leurs compléments - ne sont pas en Java étendu pour travailler avec Unicode. Seul parmi ceux - ci, \b jouit de certaines sémantiques étendues, mais ces \w , ni à Unicode identifiers , ni à Unicode line-break propriétés de .

de plus, les propriétés POSIX en Java sont accessibles de cette façon:

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 gâchis, parce que cela signifie que des choses comme Alpha , Lower , et Space do pas dans la carte Java aux propriétés Unicode Alphabetic , Lowercase , ou Whitespace . C'est exceeedingly ennuyeux. Le support de la propriété Unicode de Java est strictement antemillenial , par lequel je veux dire qu'il ne supporte aucune propriété Unicode qui est sorti au cours de la dernière décennie.

ne pas pouvoir parler de l'espace correctement est super-ennuyeux. Considérons le tableau suivant. Pour chacun de ces points de code, il y a à la fois une colonne j-results pour Java et une colonne de résultats pour Perl ou tout autre moteur de 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  -

vous voyez ça?

pratiquement tous ces Java white les résultats de l'espace sont erronés selon Unicode. C'est un très gros problème. Java est tout simplement foiré, donnant des réponses qui sont "faux" selon la pratique existante et aussi selon Unicode. De plus, Java ne vous donne même pas accès aux propriétés D'Unicode! En fait, Java ne supporte pas les propriétés any qui correspondent à Unicode whitespace.


la Solution à tous ces Problèmes, et plus

pour résoudre ce problème et bien d'autres, j'ai écrit hier une fonction Java pour réécrire une chaîne de caractères qui réécrit ces 14 échappées de charclass:

\w \W \s \S \v \V \h \H \d \D \b \B \X \R

en les remplaçant par des choses qui fonctionnent réellement pour correspondre à Unicode d'une 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 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*)

Certaines choses à considérer...

  • qui utilise pour son \X définition ce que Unicode se réfère maintenant à comme un legacy grapheme cluster , pas un étendu grapheme cluster , car ce dernier est un peu plus compliqué. Perl utilise maintenant l'amateur version, mais l'ancienne version est encore parfaitement utilisable pour les situations les plus courantes. EDIT: Voir l'addendum au fond.

  • Que faire à propos de \d dépend de votre intention, mais la définition par défaut est la définition Uniode. Je vois que les gens ne veulent pas toujours \p{Nd} , mais parfois [0-9] ou \pN .

  • les deux définitions des limites, \b et \B , sont spécifiquement écrits pour utiliser la définition \w .

  • cette définition de \w est trop large, parce qu'elle saisit les lettres parennées et pas seulement les lettres encerclées. La propriété Unicode Other_Alphabetic n'est pas disponible avant JDK7, donc c'est le mieux que vous pouvez faire.


L'Exploration Des Limites

limites ont été un problème depuis que Larry Wall a inventé la syntaxe \b et \B pour parler d'eux pour Perl 1.0 en 1987. La clé pour comprendre comment \b et \B travaillent tous deux est de dissiper deux mythes omniprésents à leur sujet:

  1. ils sont ils ne regardent jamais pour \w mots caractères, jamais pour les caractères non-mots.
  2. Ils ne sont pas spécifiquement recherchez le bord de la chaîne.

a \b limite signifie:

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

et ceux-là sont tous définis parfaitement directement 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) .

donc, puisque IF-THEN est codé comme un and ed-together AB dans regexes, un or est X|Y , et parce que le and est plus élevé en priorité que or , c'est-à-dire simplement AB|CD . Ainsi, chaque \b qui signifie une limite peut être remplacé 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 en mesure d'écrire que AB|D , mais pendant un certain temps je chassais les contradictions d'exclusion mutuelle dans les propriétés Unicode - que je pense j'ai pris soin de, mais j'ai laissé la double condition dans la limite juste au cas. De plus, cela le rend plus extensible si vous obtenez des idées supplémentaires plus tard.)

pour les \B non-limites, la logique est:

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

permettant de remplacer toutes les instances de \B par:

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

C'est vraiment comme ça que se comportent \b et \B . Les modèles équivalents pour eux sont

  • \b utilisant la construction ((IF)THEN|ELSE) est (?(?<=\w)(?!\w)|(?=\w))
  • \B utilisant le ((IF)THEN|ELSE) construit est (?(?=\w)(?<=\w)|(?<!\w))

mais les versions avec juste AB|CD sont très 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 test qui vérifie 110.385.408 correspondances par course, et que j'ai effectuée sur un 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 sorte de limite différente. Ils veulent quelque chose qui est espace blanc et bord-de-corde conscient:

  • côté gauche comme (?:(?<=^)|(?<=\s))
  • côté droit comme (?=$|\s)

corriger Java avec Java

le code que j'ai posté dans mon autre réponse fournit ceci et bien d'autres commodités. Ceci inclut des définitions pour les mots en langage naturel, les tirets, les traits d'Union, et les apostrophes, plus un peu plus.

il vous permet également de spécifier des caractères Unicode dans les points de code logique, pas dans les substituts idiotes UTF-16. il est difficile de surestimer à quel point c'est important! et c'est juste pour la corde expansion.

Pour les regex charclass de substitution qui rend le charclass dans votre Java regexes enfin travail sur l'Unicode, et de travailler correctement, saisir la source à partir d'ici . vous pouvez en faire ce que vous voulez, bien sûr. Si vous y remédiez, j'aimerais l'entendre, mais vous n'êtes pas obligé. C'est assez court. Les tripes 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 n'est qu'une version alpha, que j'ai piraté ce week-end. Il ne reste pas de cette façon.

Pour la bêta, j'ai l'intention de:

  • regroupez la duplication du code

  • fournir une meilleure interface concernant unescaping chaîne échappe rapport à augmenter la regex s'échappe

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

  • fournissent des méthodes de commodité qui gèrent les tournants et les motifs d'appel.de la compilation ou de la Ficelle.les matchs ou autres joyeusetés pour vous

pour la version de production, il devrait y avoir javadoc et une suite de test JUnit. Je peux inclure mon gigatester, mais ce n'est pas écrit comme des tests JUnit.


Addendum

j'ai de bonnes et de mauvaises nouvelles.

la bonne nouvelle est que j'ai maintenant un très proche approximation d'un groupe de graphème é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:.))

que vous écririez en 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üß!

228
répondu tchrist 2017-05-23 12:10:31

c'est vraiment dommage que \w ne fonctionne pas. La solution proposée \p{Alpha} ne me convient pas non plus.

il semble que [\p{L}] capte toutes les lettres Unicode. Ainsi, L'équivalent Unicode de \w devrait être [\p{L}\p{Digit}_] .

14
répondu musiKk 2010-11-29 15:40:54

en Java, \w et \d ne sont pas Unicode-aware; ils correspondent seulement les 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 en Java ils n'ont jamais fait correspondre que des caractères ASCII). Si vous voulez faire correspondre Unicode "word characters" vous devez l'épeler, par exemple [\pL\p{Mn}\p{Nd}\p{Pc}] , pour les lettres, non-modificateurs d'espacement (accents), décimaux, et la connexion de la ponctuation.

cependant, \b de Java est Unicode-savvy; il utilise Character.isLetterOrDigit(ch) et vérifie les lettres accentuées ainsi, mais le seul caractère "connexion ponctuation" qu'il reconnaît est le underscore. EDIT: quand j'essaie votre exemple de code, il imprime "" et élève" comme il se doit ( le voir sur ideone.com ).

7
répondu Alan Moore 2010-11-29 16:54:07