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 den String
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:
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}
Dies ist ein echtes Chaos, weil es bedeutet , dass Dinge wie Alpha
, Lower
und Space
tun nicht in Java Karte , um die Unicode Alphabetic
, Lowercase
oder Whitespace
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:
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 -
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:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
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:
\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*)
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:
- Sie suchen immer nur für
\w
Wortzeichen, nie für Nicht-Wort - Zeichen.
- Sie suchen nicht speziell nach der Kante der Zeichenfolge.
Eine \b
Grenze bedeutet:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
Und diese sind alle ganz einfach definiert als:
- folgt Wort ist
(?<=\w)
.
- vorausgeht Wort ist
(?=\w)
.
- folgt nicht Wort ist
(?<!\w)
.
- nicht precede Wort ist
(?!\w)
.
Da dies in Regexen IF-THEN
als and
Ed- AB
Together codiert ist, ist ein or
is X|Y
, und weil das and
eine höhere Priorität hat als or
, ist das einfach AB|CD
. Alles \b
, was bedeutet, dass eine Grenze sicher ersetzt werden kann durch:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
mit dem \w
in geeigneter Weise definierten.
(Man könnte denken , es seltsam , dass die A
und C
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:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Zulassen, dass alle Instanzen von \B
ersetzt werden durch:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
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:
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)
Menschen wollen jedoch oft eine andere Art von Grenze. Sie wollen etwas, das Whitespace- und String-Edge-fähig ist:
- linker Rand als
(?:(?<=^)|(?<=\s))
- rechter Rand als
(?=$|\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:
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;
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:
(?:(?:\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:.))
welche in Java würden Sie schreiben als:
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üß!