Codice sorgente
Il codice sorgente per le funzioni di riscrittura che discuterò di seguito è disponibile qui .
Aggiornamento in Java 7
La Pattern
classe aggiornata di Sun per JDK7 ha una nuova meravigliosa bandiera UNICODE_CHARACTER_CLASS
, che fa tornare tutto a posto. È disponibile come embeddable (?U)
per all'interno del modello, quindi puoi usarlo anche con i String
wrapper della classe. Mette in mostra anche definizioni corrette per varie altre proprietà. Traccia ora lo standard Unicode, sia in RL1.2 che in RL1.2a da UTS # 18: Unicode Regular Expressions . Si tratta di un miglioramento entusiasmante e drammatico e il team di sviluppo deve essere lodato per questo importante sforzo.
Problemi Unicode Regex di Java
Il problema con Java regex è che i Perl 1.0 charclass fughe - il che significa \w
, \b
, \s
, \d
e loro complementi - non sono in Java estesi a lavoro con Unicode. Solo tra questi, \b
gode di una semantica estesa, ma questi non associano \w
né a identificatori Unicode né a proprietà di interruzione di riga Unicode .
Inoltre, è possibile accedere alle proprietà POSIX in Java in questo modo:
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}
Si tratta di un vero e proprio pasticcio, perché vuol dire che le cose piace Alpha
, Lower
e Space
lo fanno non in carta Java per l'Unicode Alphabetic
, Lowercase
o Whitespace
proprietà. Questo è estremamente fastidioso. Il supporto delle proprietà Unicode di Java è rigorosamente antemillennale , nel senso che non supporta alcuna proprietà Unicode emersa nell'ultimo decennio.
Non essere in grado di parlare correttamente degli spazi bianchi è super fastidioso. Considera la seguente tabella. Per ciascuno di questi punti di codice, esiste sia una colonna J-results per Java che una colonna P-results per Perl o qualsiasi altro motore regex basato su 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 -
Guarda quello?
Praticamente ognuno di quei risultati dello spazio bianco Java è ̲w̲r̲o̲n̲g̲ secondo Unicode. È davvero un grosso problema. Java è appena incasinato, dando risposte “sbagliate” secondo la pratica esistente e anche secondo Unicode. Inoltre Java non ti dà nemmeno accesso alle proprietà Unicode reali! In effetti, Java non supporta alcuna proprietà che corrisponde allo spazio bianco Unicode.
La soluzione a tutti questi problemi e altro ancora
Per affrontare questo e molti altri problemi correlati, ieri ho scritto una funzione Java per riscrivere una stringa di pattern che riscrive queste 14 escape di classe:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
sostituendoli con elementi che funzionano effettivamente per abbinare Unicode in modo prevedibile e coerente. È solo un prototipo alfa da una singola sessione di hacking, ma è completamente funzionale.
Il racconto è che il mio codice riscrive quei 14 come segue:
\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*)
Alcune cose da considerare ...
Che utilizza per la sua \X
definizione quanto Unicode ora si riferisce ad un gruppo legacy grafema , non un gruppo grafema estesa , in quanto quest'ultimo è alquanto più complicata. Perl stesso ora utilizza la versione più elaborata, ma la vecchia versione è ancora perfettamente realizzabile per le situazioni più comuni. EDIT: vedi addendum in fondo.
Cosa fare \d
dipende dal tuo intento, ma l'impostazione predefinita è la definizione Uniode. Vedo persone che non sempre vogliono \p{Nd}
, ma a volte o [0-9]
o \pN
.
Le due definizioni di confine \b
e \B
, sono specificamente scritte per utilizzare la \w
definizione.
Tale \w
definizione è eccessivamente ampia, perché afferra le lettere con pergamena non solo quelle cerchiate. La Other_Alphabetic
proprietà Unicode non è disponibile fino a JDK7, quindi è il massimo che puoi fare.
Esplorare i confini
I confini sono stati un problema sin da quando Larry Wall ha coniato per la prima volta la \b
e la \B
sintassi per parlarne per Perl 1.0 nel 1987. La chiave per capire come \b
ed \B
entrambi funzionano è dissipare due miti pervasivi su di loro:
- Sono sempre e solo alla ricerca di
\w
caratteri di parole, mai di non parole.
- Non cercano specificamente il bordo della stringa.
Un \b
confine significa:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
E quelli sono tutti definiti perfettamente come:
- segue la parola è
(?<=\w)
.
- la parola precedente è
(?=\w)
.
- non segue la parola è
(?<!\w)
.
- non precede la parola è
(?!\w)
.
Pertanto, poiché IF-THEN
è codificato come un and
insieme AB
in regex, an or
is X|Y
, e poiché and
è maggiore in precedenza rispetto a or
, è semplicemente AB|CD
. Quindi ogni cosa \b
che significhi un confine può essere tranquillamente sostituita con:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
con il \w
definito nel modo appropriato.
(Potresti pensare strano che i componenti A
e C
siano opposti. In un mondo perfetto, dovresti essere in grado di scriverlo AB|D
, ma per un po 'stavo inseguendo contraddizioni di esclusione reciproca nelle proprietà Unicode - di cui penso di essermi preso cura , ma ho lasciato la doppia condizione al limite per ogni evenienza. Inoltre, ciò rende più estensibile se in seguito si ottengono idee extra.)
Per i \B
non-limiti, la logica è:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Consentire la \B
sostituzione di tutte le istanze con:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
Questo è davvero come \b
e \B
comportarsi. Modelli equivalenti per loro sono
\b
usando il ((IF)THEN|ELSE)
costrutto è(?(?<=\w)(?!\w)|(?=\w))
\B
usando il ((IF)THEN|ELSE)
costrutto è(?(?=\w)(?<=\w)|(?<!\w))
Ma le versioni con semplicemente AB|CD
vanno bene, specialmente se mancano schemi condizionali nel tuo linguaggio regex - come Java. ☹
Ho già verificato il comportamento dei limiti utilizzando tutte e tre le definizioni equivalenti con una suite di test che controlla 110.385.408 corrispondenze per esecuzione e che ho eseguito su una dozzina di diverse configurazioni di dati in base a:
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)
Tuttavia, le persone spesso vogliono un diverso tipo di confine. Vogliono qualcosa che sia consapevole degli spazi bianchi e del bordo della stringa:
- bordo sinistro come
(?:(?<=^)|(?<=\s))
- bordo destro come
(?=$|\s)
Riparazione di Java con Java
Il codice che ho postato nell'altra mia risposta offre questo e molte altre comodità. Ciò include le definizioni di parole, trattini, trattini e apostrofi in linguaggio naturale, oltre a un po 'di più.
Consente inoltre di specificare caratteri Unicode in punti di codice logici, non in surrogati UTF-16 idioti. È difficile sopravvalutare quanto sia importante! E questo è solo per l'espansione della stringa.
Per la sostituzione della classe di caratteri regex che fa sì che la classe nei regex Java funzioni finalmente su Unicode e funzioni correttamente, prendi l'intera fonte da qui . Puoi farcela a tuo piacimento, ovviamente. Se lo risolvi, mi piacerebbe saperlo, ma non è necessario. È piuttosto corto. Il coraggio della funzione di riscrittura regex principale è semplice:
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;
Comunque, quel codice è solo una versione alfa, roba che ho hackerato durante il fine settimana. Non rimarrà così.
Per la beta intendo:
piegare insieme la duplicazione del codice
fornisce un'interfaccia più chiara per quanto riguarda gli escape di stringa senza escape rispetto agli escape di regex in aumento
fornire una certa flessibilità \d
nell'espansione, e forse il\b
fornire metodi di convenienza che gestiscono il voltaggio e la chiamata a Pattern.compile o String.matches o quant'altro per te
Per la versione di produzione, dovrebbe avere javadoc e una suite di test JUnit. Potrei includere il mio gigatester, ma non è scritto come test JUnit.
appendice
Ho buone notizie e cattive notizie.
La buona notizia è che ora ho un'approssimazione molto vicina a un cluster grapheme esteso da utilizzare per un miglioramento \X
.
La cattiva notizia ☺ è che quel modello è:
(?:(?:\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:.))
che in Java dovresti scrivere come:
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üß!