Viele moderne Regex-Implementierungen interpretieren die \w
Kurzform der Zeichenklasse als "einen beliebigen Buchstaben, eine Ziffer oder eine verbindende Interpunktion" (normalerweise: Unterstrich). Auf diese Weise, ein regulärer Ausdruck wie \w+
Streichhölzer Wörter wie hello
, élève
, GOÄ_432
oder gefräßig
.
Java leider nicht. In Java \w
ist beschränkt auf [A-Za-z0-9_]
. Dies macht es unter anderem schwierig, Wörter wie die oben genannten zu finden.
Es scheint auch, dass das \b
Worttrennzeichen an Stellen übereinstimmt, an denen es nicht sollte.
Was wäre das richtige Äquivalent zu einem .NET-ähnlichen, Unicode-fähigen \w
oder \b
in Java? Welche anderen Verknüpfungen müssen neu geschrieben werden, um Unicode-fähig zu machen?
java
regex
unicode
character-properties
Tim Pietzcker
quelle
quelle
Antworten:
Quellcode
Der Quellcode für die unten beschriebenen Umschreibfunktionen ist hier verfügbar .
Update in Java 7
Die aktualisierte
Pattern
Klasse von Sun für JDK7 hat eine wunderbare neue Flagge,UNICODE_CHARACTER_CLASS
mit der alles wieder richtig funktioniert. Es ist als Einbettung(?U)
für das Muster verfügbar , sodass Sie es auch mit denString
Wrappern der Klasse verwenden können. Es enthält auch korrigierte Definitionen für verschiedene andere Eigenschaften. Es verfolgt jetzt den Unicode-Standard sowohl in RL1.2 als auch in RL1.2a aus UTS # 18: Unicode Regular Expressions . Dies ist eine aufregende und dramatische Verbesserung, und das Entwicklungsteam ist für diese wichtige Anstrengung zu loben.Java Regex Unicode Probleme
Das Problem mit Java Regexes ist , dass die Perl 1.0 charclass entkommt - das heißt
\w
,\b
,\s
,\d
und die Ergänzungen - sind in Java nicht mit Unicode Arbeit erweitert. Allein unter dieser\b
genießt bestimmte erweiterte Semantik, aber diese Karte weder zu\w
, noch auf Unicode - Kennungen , noch auf Unicode Zeilenumbruch Eigenschaften .Darüber hinaus wird auf die POSIX-Eigenschaften in Java folgendermaßen zugegriffen:
Dies ist ein echtes Chaos, weil es bedeutet , dass Dinge wie
Alpha
,Lower
undSpace
tun nicht in Java Karte , um die UnicodeAlphabetic
,Lowercase
oderWhitespace
Eigenschaften. Das ist außerordentlich ärgerlich. Die Unterstützung von Java-Unicode-Eigenschaften ist streng antemillennial , womit ich meine, dass sie keine Unicode-Eigenschaft unterstützt, die im letzten Jahrzehnt herausgekommen ist.Es ist sehr ärgerlich, nicht richtig über Leerzeichen sprechen zu können. Betrachten Sie die folgende Tabelle. Für jeden dieser Codepunkte gibt es sowohl eine J-Ergebnisspalte für Java als auch eine P-Ergebnisspalte für Perl oder eine andere PCRE-basierte Regex-Engine:
Siehst du das?
Praktisch jedes dieser Java-Leerraumergebnisse ist laut Unicode ̲w̲r̲o̲n̲g̲. Es ist ein wirklich großes Problem. Java ist nur durcheinander und gibt Antworten, die gemäß der bestehenden Praxis und auch gemäß Unicode „falsch“ sind. Außerdem gibt Ihnen Java nicht einmal Zugriff auf die echten Unicode-Eigenschaften! Tatsächlich unterstützt Java keine Eigenschaft, die dem Unicode-Leerzeichen entspricht.
Die Lösung für all diese Probleme und mehr
Um dieses und viele andere verwandte Probleme zu lösen, habe ich gestern eine Java-Funktion geschrieben, um eine Musterzeichenfolge neu zu schreiben, die diese 14 Zeichenklassen-Escapezeichen neu schreibt:
indem Sie sie durch Dinge ersetzen, die tatsächlich so funktionieren, dass sie auf vorhersehbare und konsistente Weise mit Unicode übereinstimmen. Es ist nur ein Alpha-Prototyp aus einer einzelnen Hack-Sitzung, aber es ist voll funktionsfähig.
Die Kurzgeschichte ist, dass mein Code diese 14 wie folgt umschreibt:
Einige Dinge zu beachten ...
Dies verwendet für seine
\X
Definition das, was Unicode jetzt als Legacy-Graphemcluster bezeichnet , nicht als erweiterten Graphemcluster , da letzterer etwas komplizierter ist. Perl selbst verwendet jetzt die schickere Version, aber die alte Version ist für die häufigsten Situationen immer noch perfekt funktionsfähig. BEARBEITEN: Siehe Anhang unten.Was zu tun ist,
\d
hängt von Ihrer Absicht ab, aber die Standardeinstellung ist die Uniodendefinition. Ich kann Leute sehen, die nicht immer wollen\p{Nd}
, aber manchmal entweder[0-9]
oder\pN
.Die beiden Grenzdefinitionen
\b
und\B
sind speziell für die Verwendung der\w
Definition geschrieben.Diese
\w
Definition ist zu weit gefasst, weil sie die geschriebenen Buchstaben erfasst, nicht nur die eingekreisten. Die Unicode-Other_Alphabetic
Eigenschaft ist erst in JDK7 verfügbar. Das ist also das Beste, was Sie tun können.Grenzen erkunden
Grenzen sind ein Problem, seit Larry Wall 1987 erstmals die Syntax
\b
und die\B
Syntax für Perl 1.0 geprägt hat. Der Schlüssel zum Verständnis, wie\b
und\B
beide funktionieren, besteht darin, zwei allgegenwärtige Mythen über sie zu zerstreuen:\w
Wortzeichen, nie für Nicht-Wort - Zeichen.Eine
\b
Grenze bedeutet:Und diese sind alle ganz einfach definiert als:
(?<=\w)
.(?=\w)
.(?<!\w)
.(?!\w)
.Da dies in Regexen
IF-THEN
alsand
Ed-AB
Together codiert ist, ist einor
isX|Y
, und weil dasand
eine höhere Priorität hat alsor
, ist das einfachAB|CD
. Alles\b
, was bedeutet, dass eine Grenze sicher ersetzt werden kann durch:mit dem
\w
in geeigneter Weise definierten.(Man könnte denken , es seltsam , dass die
A
undC
Komponenten Gegensätze sind in einer perfekten Welt, sollten Sie in der Lage sein , das zu schreiben.AB|D
, Aber für eine Weile war ich die Jagd nach mutual exclusion Widersprüchen in Unicode - Eigenschaften - was ich denke , ich habe gesorgt , aber ich habe die doppelte Bedingung für alle Fälle in der Grenze belassen. Außerdem ist sie erweiterbarer, wenn Sie später zusätzliche Ideen erhalten.)Für
\B
die Nichtgrenzen lautet die Logik:Zulassen, dass alle Instanzen von
\B
ersetzt werden durch:Das ist wirklich wie
\b
und wie man sich\B
verhält. Äquivalente Muster für sie sind\b
mit dem((IF)THEN|ELSE)
Konstrukt ist(?(?<=\w)(?!\w)|(?=\w))
\B
mit dem((IF)THEN|ELSE)
Konstrukt ist(?(?=\w)(?<=\w)|(?<!\w))
Aber die Versionen mit just
AB|CD
sind in Ordnung, besonders wenn Sie keine bedingten Muster in Ihrer Regex-Sprache haben - wie Java. ☹Ich habe das Verhalten der Grenzen bereits anhand aller drei äquivalenten Definitionen mit einer Testsuite überprüft, die 110.385.408 Übereinstimmungen pro Lauf überprüft und die ich auf einem Dutzend verschiedener Datenkonfigurationen ausgeführt habe:
Menschen wollen jedoch oft eine andere Art von Grenze. Sie wollen etwas, das Whitespace- und String-Edge-fähig ist:
(?:(?<=^)|(?<=\s))
(?=$|\s)
Java mit Java reparieren
Der Code, den ich in meiner anderen Antwort gepostet habe, bietet dies und einige andere Annehmlichkeiten. Dies beinhaltet Definitionen für Wörter in natürlicher Sprache, Bindestriche, Bindestriche und Apostrophe sowie ein bisschen mehr.
Außerdem können Sie Unicode-Zeichen in logischen Codepunkten angeben, nicht in idiotischen UTF-16-Ersatzzeichen. Es ist schwer zu betonen, wie wichtig das ist! Und das ist nur für die String-Erweiterung.
Für regex charclass Substitution, die die charclass in Java macht Regexes schließlich Arbeit auf Unicode, und die Arbeit richtig, greifen hier die vollständige Quelle . Sie können natürlich damit machen, wie Sie möchten. Wenn Sie Korrekturen vornehmen, würde ich gerne davon hören, aber das müssen Sie nicht. Es ist ziemlich kurz. Der Mut der Hauptfunktion zum Umschreiben von Regex ist einfach:
Wie auch immer, dieser Code ist nur eine Alpha-Veröffentlichung, Sachen, die ich über das Wochenende gehackt habe. Das wird nicht so bleiben.
Für die Beta habe ich vor:
Falten Sie die Codeduplizierung zusammen
Bieten Sie eine klarere Oberfläche für das Entweichen von Zeichenfolgen-Escapezeichen im Vergleich zu erweiterten Regex-Escapezeichen
bieten eine gewisse Flexibilität bei der
\d
Erweiterung, und vielleicht die\b
Stellen Sie bequeme Methoden bereit, mit denen Sie sich umdrehen und Pattern.compile oder String.matches oder so weiter aufrufen können
Für die Produktionsversion sollte es über Javadoc und eine JUnit-Testsuite verfügen. Ich kann meinen Gigatester einschließen, aber er ist nicht als JUnit-Test geschrieben.
Nachtrag
Ich habe gute und schlechte Nachrichten.
Die gute Nachricht ist, dass ich jetzt eine sehr enge Annäherung an einen erweiterten Graphemcluster habe , um ihn für eine Verbesserung zu verwenden
\X
.Die schlechte Nachricht ist, dass dieses Muster ist:
welche in Java würden Sie schreiben als:
¡Tschüß!
quelle
t
in @tchrist fallen. Es könnte zu meinem Kopf gehen. :)Es ist wirklich bedauerlich, dass
\w
das nicht funktioniert. Die vorgeschlagene Lösung\p{Alpha}
funktioniert auch bei mir nicht.Es scheint
[\p{L}]
alle Unicode-Buchstaben zu fangen. Das Unicode-Äquivalent von\w
sollte also sein[\p{L}\p{Digit}_]
.quelle
\w
auch mit Ziffern und mehr überein. Ich denke nur für Briefe,\p{L}
würde funktionieren.\p{L}
reicht. Ich dachte auch, dass nur Buchstaben das Problem sind.[\p{L}\p{Digit}_]
sollte alle alphanumerischen Zeichen einschließlich Unterstrich abfangen.\w
wird von Unicode als viel breiter als nur\pL
und die ASCII-Ziffern aller dummen Dinge definiert. Sie müssen schreiben,[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
wenn Sie ein Unicode-fähiges möchten\w
für Java - oder Sie können einfach meineunicode_charclass
Funktion von hier aus verwenden . Es tut uns leid!\pL
funktionieren (Sie müssen keine Ein-Buchstaben-Requisiten annehmen ). Sie möchten dies jedoch selten, da Sie ziemlich vorsichtig sein müssen, dass Ihre Übereinstimmung keine unterschiedlichen Antworten erhält, nur weil Ihre Daten in Unicode-Normalisierungsform D (auch bekannt als NFD, was kanonische Zerlegung bedeutet ) vorliegen und nicht in NFC (NFD, gefolgt von kanonisch) Zusammensetzung ). Ein Beispiel ist, dass der Codepunkt U + E9 ("é"
)\pL
in NFC-Form vorliegt, seine NFD-Form jedoch zu U + 65.301 wird, also übereinstimmt\pL\pM
. Sie können irgendwie umgehen dies mit\X
:(?:(?=\pL)\X)
, aber Sie werden meine Version davon für Java benötigen. :(In Java
\w
und\d
sind nicht Unicode-fähig; Sie stimmen nur mit den ASCII-Zeichen überein,[A-Za-z0-9_]
und[0-9]
. Das Gleiche gilt für\p{Alpha}
und Freunde (die POSIX "Zeichenklassen", auf denen sie basieren, sollen länderspezifisch sein, aber in Java haben sie immer nur ASCII-Zeichen gefunden). Wenn Sie mit Unicode- "Wortzeichen" übereinstimmen möchten, müssen Sie diese buchstabieren, z. B.[\pL\p{Mn}\p{Nd}\p{Pc}]
für Buchstaben, nicht räumliche Modifikatoren (Akzente), Dezimalstellen und Interpunktionszeichen.Java
\b
ist jedoch Unicode-versiert. Es verwendetCharacter.isLetterOrDigit(ch)
und sucht auch nach Buchstaben mit Akzent, aber das einzige Zeichen, das es erkennt, ist der Unterstrich. BEARBEITEN: Wenn ich Ihren Beispielcode ausprobiere, wird er gedruckt""
undélève"
wie er sollte ( siehe ideone.com ).quelle
\b
Unicode-versiert ist. Es macht Tonnen und Tonnen von Fehlern."\u2163="
,"\u24e7="
Und"\u0301="
alle angepassten Muster nicht"\\b="
in Java, sondern sollte zu - wieperl -le 'print /\b=/ || 0 for "\x{2163}=", "\x{24e7}=", "\x{301}="'
offenbart. Wenn Sie jedoch (und nur dann) meine Version einer Wortgrenze anstelle der nativen\b
in Java austauschen, funktionieren diese auch in Java.\b
Richtigkeit nicht kommentiert und nur darauf hingewiesen, dass sie mit Unicode-Zeichen (wie in Java implementiert) funktioniert, nicht nur mit ASCII-ähnlichen\w
und Freunden. Es funktioniert jedoch korrekt in Bezug darauf,\u0301
wann dieses Zeichen mit einem Basiszeichen gepaart ist, wie ine\u0301=
. Und ich bin nicht davon überzeugt, dass Java in diesem Fall falsch ist. Wie kann eine Kombinationsmarke als Wortzeichen betrachtet werden, wenn sie nicht Teil eines Graphemclusters mit einem Buchstaben ist?\X
für eine Nichtmarke gefolgt von einer beliebigen Anzahl von Markierungen steht, ist problematisch, da Sie in der Lage sein sollten, alle Dateien als übereinstimmend zu beschreiben/^(\X*\R)*\R?$/
, dies jedoch nicht, wenn Sie\pM
zu Beginn eine haben die Datei oder sogar einer Zeile. Sie haben es also so erweitert, dass es immer mindestens einem Zeichen entspricht. Das hat es immer getan, aber jetzt funktioniert das obige Muster.[… Fortsetzung…]\b
teilweise Unicode- ist. Ziehen Sie in Betracht, die Zeichenfolge"élève"
mit dem Muster abzugleichen\b(\w+)\b
. Sehen Sie das Problem?\w+
findet zwei Übereinstimmungen:l
undve
, was schlimm genug ist. Aber mit den Wortgrenzen findet es nichts, weil es\b
erkennté
undè
als Wortzeichen. Zumindest\b
und\w
sollte sich darüber einig sein, was ein Wortcharakter ist und was nicht.