Wiele nowoczesnych implementacji wyrażeń regularnych interpretuje \w
skrót klasy znaków jako „dowolną literę, cyfrę lub łączącą interpunkcję” (zwykle: podkreślenie). W ten sposób regex podobne \w+
dopasowania słów podoba hello
, élève
, GOÄ_432
lub gefräßig
.
Niestety Java nie. W Javie \w
jest ograniczony do [A-Za-z0-9_]
. Utrudnia to między innymi dopasowywanie słów, takich jak te wymienione powyżej.
Okazuje się również, że \b
separator słów pasuje tam, gdzie nie powinien.
Jaki byłby właściwy odpowiednik środowiska podobnego do .NET, obsługującego Unicode \w
lub \b
Java? Które inne skróty wymagają „przepisania”, aby obsługiwały Unicode?
java
regex
unicode
character-properties
Tim Pietzcker
źródło
źródło
Odpowiedzi:
Kod źródłowy
Kod źródłowy funkcji przepisywania, które omówię poniżej, jest dostępny tutaj .
Aktualizacja w Javie 7
Zaktualizowana
Pattern
klasa Sun dla JDK7 ma cudowną nową flagęUNICODE_CHARACTER_CLASS
, która sprawia, że wszystko znów działa poprawnie. Jest dostępny jako osadzalny element(?U)
wewnątrz wzorca, więc można go również używać zString
opakowaniami klasy. Zawiera również poprawione definicje różnych innych właściwości. Teraz śledzi standard Unicode, zarówno w RL1.2, jak i RL1.2a z UTS # 18: Wyrażenia regularne Unicode . To ekscytująca i radykalna poprawa, a zespół programistów zasługuje na pochwałę za ten ważny wysiłek.Problemy z Regex Unicode w Javie
Problem z wyrażeniami regularnymi w Javie polega na tym, że klasa znaków Perl 1.0 ucieka - co oznacza
\w
,\b
,\s
,\d
i ich uzupełnienia - nie są w Javie przedłużony do pracy z Unicode. Tylko jeden z nich\b
cieszy się pewną rozszerzoną semantyką, ale nie odwzorowują one ani na\w
, ani na identyfikatory Unicode , ani na właściwości podziału wiersza Unicode .Dodatkowo do właściwości POSIX w Javie można uzyskać dostęp w następujący sposób:
To jest prawdziwy bałagan, bo to oznacza, że wszystko podoba
Alpha
,Lower
iSpace
zrobić nie w mapie Java dla UnicodeAlphabetic
,Lowercase
, lubWhitespace
właściwości. To jest wyjątkowo irytujące. Obsługa właściwości Unicode w Javie jest ściśle sprzed tysiąclecia , co oznacza, że nie obsługuje żadnej właściwości Unicode, która pojawiła się w ciągu ostatniej dekady.Brak możliwości prawidłowego mówienia o białych znakach jest bardzo irytujący. Rozważ poniższą tabelę. Dla każdego z tych punktów kodowych istnieje zarówno kolumna wyników w języku J dla języka Java, jak i kolumna wyników P dla języka Perl lub dowolnego innego silnika wyrażeń regularnych opartego na PCRE:
Zobaczyć, że?
Praktycznie każdy z tych wyników białych znaków Java jest zgodny z Unicode ̲w̲r̲o̲n̲g̲. To jest naprawdę duży problem. Java jest po prostu pomieszana, dając odpowiedzi, które są „błędne” zgodnie z istniejącą praktyką, a także zgodnie z Unicode. Plus Java nawet nie daje Ci dostępu do prawdziwych właściwości Unicode! W rzeczywistości Java nie obsługuje żadnej właściwości, która odpowiada białym znakom Unicode.
Rozwiązanie wszystkich tych problemów i nie tylko
Aby poradzić sobie z tym i wieloma innymi powiązanymi problemami, wczoraj napisałem funkcję Java, aby przepisać ciąg wzorca, który przepisuje te 14 znaków ucieczki klas:
zastępując je rzeczami, które faktycznie działają, aby dopasować Unicode w przewidywalny i spójny sposób. To tylko prototyp alfa z jednej sesji hakerskiej, ale jest w pełni funkcjonalny.
Krótko mówiąc, mój kod przepisuje te 14 w następujący sposób:
Kilka rzeczy do rozważenia ...
Który wykorzystuje do jej
\X
definicji, co Unicode teraz odnosi się do postaci klastra spuścizna grafem , a nie rozszerzonym klastra grafem , jak ten ostatni jest raczej bardziej skomplikowana. Sam Perl używa teraz bardziej wyszukanej wersji, ale stara wersja jest nadal doskonale funkcjonalna w większości typowych sytuacji. EDYCJA: patrz dodatek na dole.Co zrobić,
\d
zależy od Twoich zamiarów, ale domyślną definicją jest Uniode. Widzę, że ludzie nie zawsze chcą\p{Nd}
, ale czasami albo[0-9]
albo\pN
.Dwie definicje granic
\b
i\B
są specjalnie napisane w celu użycia\w
definicji.\w
Definicja ta jest zbyt szeroka, ponieważ obejmuje nie tylko litery zapisane w spreferze. Właściwość UnicodeOther_Alphabetic
jest dostępna dopiero w JDK7, więc to najlepsze, co możesz zrobić.Odkrywanie granic
Granice były problemem odkąd Larry Wall po raz pierwszy ukuł składnię
\b
i\B
do mówienia o nich w Perlu 1.0 w 1987 roku. Klucz do zrozumienia, jak\b
i\B
obie działają, jest rozwianie dwóch wszechobecnych mitów na ich temat:\w
znaki słowne, nigdy dla znaków non-słownych.A
\b
brzegowe oznaczają:A to wszystko jest zdefiniowane w prosty sposób jako:
(?<=\w)
.(?=\w)
.(?<!\w)
.(?!\w)
.Dlatego, skoro
IF-THEN
jest kodowany jakoand
ed-togetherAB
w wyrażeniach regularnych, toor
jestX|Y
, a ponieważ theand
ma wyższy priorytet niżor
, to jest po prostuAB|CD
. Więc każdy\b
, co oznacza granicę można bezpiecznie zastąpić:ze
\w
zdefiniowanym w odpowiedni sposób.(Możesz pomyśleć, że to dziwne, że komponenty
A
iC
są przeciwieństwami. W idealnym świecie powinieneś być w stanie to napisaćAB|D
, ale przez chwilę ścigałem wzajemne wykluczające się sprzeczności we właściwościach Unicode - co ja myślę, że się tym zająłem , ale na wszelki wypadek zostawiłem podwójny warunek w granicy. Dodatkowo, dzięki temu jest on bardziej rozszerzalny, jeśli później pojawią się dodatkowe pomysły.W przypadku
\B
braku granic logika jest następująca:Zezwalanie
\B
na zastąpienie wszystkich wystąpień przez :To naprawdę jest jak
\b
i\B
zachowuj się. Są dla nich równoważne wzory\b
użycie((IF)THEN|ELSE)
konstrukcji to(?(?<=\w)(?!\w)|(?=\w))
\B
użycie((IF)THEN|ELSE)
konstrukcji to(?(?=\w)(?<=\w)|(?<!\w))
Ale wersje z just
AB|CD
są w porządku, zwłaszcza jeśli brakuje wzorców warunkowych w Twoim języku regex - takim jak Java. ☹Sprawdziłem już zachowanie granic przy użyciu wszystkich trzech równoważnych definicji za pomocą zestawu testów, który sprawdza 110 385 408 dopasowań na przebieg i który uruchomiłem na kilkunastu różnych konfiguracjach danych zgodnie z:
Jednak ludzie często chcą innego rodzaju granicy. Chcą czegoś, co jest świadome białych znaków i krawędzi łańcucha:
(?:(?<=^)|(?<=\s))
(?=$|\s)
Naprawianie Java za pomocą Java
Kod, który zamieściłem w mojej drugiej odpowiedzi zapewnia to i kilka innych udogodnień. Obejmuje to definicje słów, myślników, łączników i apostrofów w języku naturalnym, a także trochę więcej.
Pozwala także na określenie znaków Unicode w logicznych punktach kodowych, a nie w idiotycznych surogatach UTF-16. Trudno przecenić, jakie to ważne!A to tylko dla rozwinięcia ciągów.
Dla regex charclass podstawienie sprawia, że charclass w Javie regexes wreszcie pracę na Unicode, i działa prawidłowo, chwycić pełną źródło stąd . Możesz oczywiście zrobić z tym, co chcesz. Jeśli naprawisz to, chciałbym o tym usłyszeć, ale nie musisz. Jest dość krótki. Zalety głównej funkcji przepisywania wyrażeń regularnych są proste:
W każdym razie ten kod to tylko wydanie alfa, coś, co zhakowałem w weekend. Tak nie zostanie.
W przypadku wersji beta zamierzam:
złóż razem powielenie kodu
zapewniają jaśniejszy interfejs dotyczący znaków ucieczki ciągów bez zmiany znaczenia w porównaniu ze znakami ucieczki wyrażenia rozszerzającego
zapewniają pewną elastyczność w
\d
rozszerzaniu, a być może\b
zapewniają wygodne metody, które obsługują odwracanie i wywoływanie Pattern.compile lub String.matches lub co innego
W przypadku wydania produkcyjnego powinien mieć javadoc i zestaw testów JUnit. Mogę dołączyć mój gigatester, ale nie jest to napisane jako testy JUnit.
Uzupełnienie
Mam dobre i złe wieści.
Dobra wiadomość jest taka, że mam teraz bardzo bliskie przybliżenie do rozszerzonego klastra grafemowego, którego można użyć do ulepszenia
\X
.Zła wiadomość ☺ jest taka, że ten wzór jest następujący:
które w Javie napiszesz jako:
¡Tschüß!
źródło
t
@tchrist. To może uderzyć mi do głowy. :)To naprawdę niefortunne, że
\w
nie działa. Proponowane rozwiązanie\p{Alpha}
też u mnie nie działa.Wygląda na to, że
[\p{L}]
łapie wszystkie litery Unicode. Więc odpowiednikiem Unicode\w
powinno być[\p{L}\p{Digit}_]
.źródło
\w
pasuje także do cyfr i nie tylko. Myślę, że dla samych listów\p{L}
będzie działać.\p{L}
wystarczy. Pomyślałem też, że problemem są tylko litery.[\p{L}\p{Digit}_]
powinien przechwytywać wszystkie znaki alfanumeryczne, w tym podkreślenie.\w
jest definiowany przez Unicode jako znacznie szerszy niż tylko\pL
i cyfry ASCII, ze wszystkich głupich rzeczy. Musisz napisać,[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
jeśli chcesz mieć obsługę Unicode\w
dla Java - lub możesz po prostu użyć mojejunicode_charclass
funkcji tutaj . Przepraszam!\pL
działają (nie musisz obejmować rekwizytów jednoliterowych). Jednak rzadko tego chcesz, ponieważ musisz być raczej ostrożny, aby twoje dopasowanie nie dawało różnych odpowiedzi tylko dlatego, że twoje dane są w formacie normalizacji Unicode D (aka NFD, co oznacza rozkład kanoniczny ), a nie będąc w NFC (NFD, po którym następuje kanoniczny skład ). Przykładem jest to, że punkt kodowy U + E9 ("é"
) jest\pL
w formie NFC, ale jego forma NFD staje się U + 65.301, więc pasuje\pL\pM
. Można trochę obejść ten problem z\X
:(?:(?=\pL)\X)
, ale trzeba moją wersję, że dla Javy. :(W Javie
\w
i\d
nie obsługują Unicode; pasują tylko do znaków ASCII[A-Za-z0-9_]
i[0-9]
. To samo dotyczy\p{Alpha}
i przyjaciół ("klasy znaków" POSIX, na których są oparte, mają być wrażliwe na ustawienia regionalne, ale w Javie zawsze dopasowywały tylko znaki ASCII). Jeśli chcesz dopasować „znaki słowne” Unicode, musisz je przeliterować, np.[\pL\p{Mn}\p{Nd}\p{Pc}]
Dla liter, modyfikatorów bez odstępów (akcentów), cyfr dziesiętnych i łączących znaków interpunkcyjnych.Jednak Java
\b
jest Unicode zrozumiały; używaCharacter.isLetterOrDigit(ch)
i sprawdza również litery akcentowane, ale jedynym znakiem „łączącej interpunkcji”, który rozpoznaje, jest podkreślenie. EDYCJA: kiedy próbuję Twój przykładowy kod, drukuje""
iélève"
tak, jak powinien ( zobacz na ideone.com ).źródło
\b
obsługuje Unicode. Robi mnóstwo błędów."\u2163="
,"\u24e7="
i"\u0301="
wszystkie nie pasują do wzorca"\\b="
w Javie, ale powinny - jakperl -le 'print /\b=/ || 0 for "\x{2163}=", "\x{24e7}=", "\x{301}="'
ujawnia. Jednakże, jeśli (i tylko wtedy) zamienisz w mojej wersji granicę słów zamiast natywnej\b
w Javie, to wszystko będzie działać również w Javie.\b
poprawności, po prostu wskazałem , że działa na znakach Unicode (zaimplementowanych w Javie), a nie tylko na ASCII\w
i znajomych. Jednak działa poprawnie w odniesieniu do\u0301
sytuacji, gdy ta postać jest sparowana z postacią podstawową, jak we\u0301=
. I nie jestem przekonany, że w tym przypadku Java jest błędna. Jak łączący się znak można uznać za znak słowny, jeśli nie jest częścią klastra grafemów z literą?\X
oznacza brak znaku, po którym następuje dowolna liczba znaków, jest problematyczna, ponieważ powinieneś być w stanie opisać wszystkie pliki jako pasujące/^(\X*\R)*\R?$/
, ale nie możesz, jeśli masz\pM
na początku plik, a nawet wiersz. Więc rozszerzyli go, aby zawsze pasował do co najmniej jednego znaku. Zawsze tak było, ale teraz sprawia, że powyższy wzór działa. […\b
jest częściowo zgodny z Unicode. Rozważ dopasowanie ciągu"élève"
do wzorca\b(\w+)\b
. Widzisz problem?\w+
znajduje dwa dopasowania:l
ive
, co jest wystarczająco złe. Ale w przypadku granic słów nie znajduje nic, ponieważ\b
rozpoznajeé
iè
jako znaki słowne. Jako minimum\b
i\w
powinien uzgodnić, co jest znakiem słowa, a co nie.