Banyak penerapan ekspresi reguler modern menafsirkan \w
singkatan kelas karakter sebagai "huruf, angka, atau tanda baca apa pun" (biasanya: garis bawah). Dengan cara itu, regex seperti \w+
pertandingan kata seperti hello
, élève
, GOÄ_432
atau gefräßig
.
Sayangnya, Java tidak. Di Jawa, \w
dibatasi hingga [A-Za-z0-9_]
. Ini membuat kata-kata yang cocok seperti yang disebutkan di atas menjadi sulit, di antara masalah lainnya.
Tampaknya \b
pemisah kata juga cocok di tempat yang tidak semestinya.
Apa yang akan menjadi padanan yang benar dari .NET-like, Unicode-aware \w
atau \b
di Java? Pintasan lain mana yang perlu "ditulis ulang" untuk membuatnya peka terhadap Unicode?
java
regex
unicode
character-properties
Tim Pietzcker
sumber
sumber
Jawaban:
Kode sumber
Kode sumber untuk fungsi penulisan ulang yang saya diskusikan di bawah tersedia di sini .
Update di Java 7
Pattern
Kelas Sun yang diperbarui untuk JDK7 memiliki tanda baru yang luar biasaUNICODE_CHARACTER_CLASS
, yang membuat semuanya bekerja dengan baik kembali. Ini tersedia sebagai embeddable(?U)
untuk di dalam pola, jadi Anda juga bisa menggunakannya denganString
pembungkus kelas. Ini juga menampilkan definisi yang dikoreksi untuk berbagai properti lainnya juga. Sekarang melacak The Unicode Standard, di RL1.2 dan RL1.2a dari UTS # 18: Unicode Regular Expressions . Ini adalah peningkatan yang menarik dan dramatis, dan tim pengembangan patut dipuji atas upaya penting ini.Masalah Unicode Regex Java
Masalah dengan Java regexes adalah bahwa Perl 1.0 charclass lolos - yang berarti
\w
,\b
,\s
,\d
dan melengkapi mereka - tidak di Jawa diperpanjang untuk bekerja dengan Unicode. Sendiri di antara ini,\b
menikmati semantik diperpanjang tertentu, tetapi ini tidak memetakan ke\w
, atau ke pengidentifikasi Unicode , atau ke properti pemisah baris Unicode .Selain itu, properti POSIX di Java diakses dengan cara ini:
Ini adalah berantakan, karena itu berarti bahwa hal-hal seperti
Alpha
,Lower
, danSpace
lakukan tidak di peta Jawa ke UnicodeAlphabetic
,Lowercase
atauWhitespace
properti. Ini sangat menjengkelkan. Dukungan properti Unicode Java sangat antemilenial , yang saya maksudkan adalah tidak mendukung properti Unicode yang telah dirilis dalam dekade terakhir.Tidak dapat berbicara tentang whitespace dengan benar sangat mengganggu. Perhatikan tabel berikut. Untuk setiap poin kode tersebut, ada kolom J-results untuk Java dan kolom P-results untuk Perl atau mesin regex berbasis PCRE lainnya:
Lihat itu?
Hampir setiap hasil spasi putih Java tersebut adalah ̲w̲r̲o̲n̲g̲ menurut Unicode. Ini masalah yang sangat besar. Java hanya kacau, memberikan jawaban yang “salah” menurut praktik yang ada dan juga menurut Unicode. Plus Java bahkan tidak memberi Anda akses ke properti Unicode yang sebenarnya! Faktanya, Java tidak mendukung properti apa pun yang sesuai dengan spasi kosong Unicode.
Solusi untuk Semua Masalah Itu, dan Lainnya
Untuk mengatasi ini dan banyak masalah terkait lainnya, kemarin saya menulis fungsi Java untuk menulis ulang string pola yang menulis ulang 14 pelarian charclass ini:
dengan menggantinya dengan hal-hal yang benar-benar berfungsi untuk mencocokkan Unicode dengan cara yang dapat diprediksi dan konsisten. Ini hanya prototipe alfa dari satu sesi peretasan, tetapi sepenuhnya berfungsi.
Singkatnya, kode saya menulis ulang 14 itu sebagai berikut:
Beberapa hal yang perlu dipertimbangkan ...
Itu menggunakan
\X
definisi yang Unicode sekarang sebut sebagai cluster grafem warisan , bukan cluster grafem yang diperluas , karena yang terakhir agak lebih rumit. Perl sendiri sekarang menggunakan versi yang lebih bagus, tetapi versi lama masih bisa diterapkan dengan sempurna untuk situasi yang paling umum. EDIT: Lihat addendum di bagian bawah.Apa yang harus dilakukan
\d
tergantung pada niat Anda, tetapi defaultnya adalah definisi Uniode. Saya dapat melihat orang tidak selalu menginginkan\p{Nd}
, tetapi terkadang salah satu[0-9]
atau\pN
.Dua definisi batas,
\b
dan\B
, secara khusus ditulis untuk menggunakan\w
definisi tersebut.Itu
\w
definisi terlalu luas, karena meraih huruf parenned bukan hanya yang dilingkari.Other_Alphabetic
Properti Unicode tidak tersedia hingga JDK7, jadi itulah yang terbaik yang dapat Anda lakukan.Menjelajahi Batasan
Batasan telah menjadi masalah sejak Larry Wall pertama kali menciptakan sintaks
\b
dan\B
untuk membicarakannya untuk Perl 1.0 pada tahun 1987. Kunci untuk memahami bagaimana\b
dan\B
keduanya bekerja adalah untuk menghilangkan dua mitos yang tersebar luas tentang mereka:\w
karakter kata, tidak pernah untuk karakter non-kata.Sebuah
\b
batas berarti:Dan itu semua didefinisikan dengan sangat lugas sebagai:
(?<=\w)
.(?=\w)
.(?<!\w)
.(?!\w)
.Oleh karena itu, karena
IF-THEN
dikodekan sebagaiand
ed-bersamaAB
dalam ekspresi reguler, anor
adalahX|Y
, dan karenaand
lebih diutamakan daripadaor
, itu sederhanaAB|CD
. Jadi setiap\b
itu berarti batas dapat diganti dengan aman dengan:dengan yang
\w
ditentukan dengan cara yang sesuai.(Anda mungkin berpikir aneh bahwa
A
danC
komponen berlawanan. Di dunia yang sempurna, Anda seharusnya dapat menulis ituAB|D
, tetapi untuk sementara saya mengejar kontradiksi saling pengecualian dalam properti Unicode - yang menurut saya sudah saya tangani , tetapi saya meninggalkan kondisi ganda di perbatasan untuk berjaga-jaga. Ditambah ini membuatnya lebih dapat diperluas jika Anda mendapatkan ide tambahan nanti.)Untuk
\B
non-batasan, logikanya adalah:Mengizinkan semua contoh
\B
untuk diganti dengan:Ini benar-benar bagaimana
\b
dan\B
berperilaku. Pola yang setara untuk mereka adalah\b
menggunakan((IF)THEN|ELSE)
konstruk tersebut(?(?<=\w)(?!\w)|(?=\w))
\B
menggunakan((IF)THEN|ELSE)
konstruk tersebut(?(?=\w)(?<=\w)|(?<!\w))
Tetapi versi dengan hanya
AB|CD
baik-baik saja, terutama jika Anda tidak memiliki pola bersyarat dalam bahasa regex Anda - seperti Java. ☹Saya telah memverifikasi perilaku batas menggunakan ketiga definisi yang setara dengan rangkaian pengujian yang memeriksa 110.385.408 kecocokan per proses, dan yang telah saya jalankan pada selusin konfigurasi data berbeda menurut:
Namun, orang sering menginginkan jenis batasan yang berbeda. Mereka menginginkan sesuatu yang whitespace dan edge-of-string aware:
(?:(?<=^)|(?<=\s))
(?=$|\s)
Memperbaiki Java dengan Java
Kode yang saya posting di jawaban saya yang lain menyediakan ini dan beberapa kemudahan lainnya. Ini termasuk definisi untuk kata-kata bahasa alami, tanda hubung, tanda hubung, dan apostrof, ditambah sedikit lagi.
Ini juga memungkinkan Anda untuk menentukan karakter Unicode dalam poin kode logis, bukan dalam pengganti UTF-16 idiot. Sulit untuk menekankan betapa pentingnya hal itu! Dan itu hanya untuk ekspansi string.
Untuk substitusi regex charclass yang membuat charclass di regex Java Anda akhirnya berfungsi di Unicode, dan berfungsi dengan benar, ambil sumber lengkapnya dari sini . Anda dapat melakukannya sesuka Anda, tentu saja. Jika Anda memperbaikinya, saya ingin sekali mendengarnya, tetapi Anda tidak perlu melakukannya. Ini sangat singkat. Inti dari fungsi penulisan ulang regex utama sederhana:
Bagaimanapun, kode itu hanyalah rilis alfa, hal-hal yang saya retas selama akhir pekan. Tidak akan seperti itu.
Untuk beta saya bermaksud untuk:
lipat bersama duplikasi kode
menyediakan antarmuka yang lebih jelas mengenai pelarian string yang tidak lolos versus menambah pelarian ekspresi reguler
memberikan beberapa fleksibilitas dalam
\d
perluasan, dan mungkin\b
menyediakan metode praktis yang menangani pembalikan dan memanggil Pattern.compile atau String.matches atau yang lainnya untuk Anda
Untuk rilis produksi, harus memiliki javadoc dan rangkaian pengujian JUnit. Saya mungkin menyertakan gigatester saya, tetapi tidak ditulis sebagai tes JUnit.
Tambahan
Saya punya kabar baik dan kabar buruk.
Kabar baiknya adalah bahwa saya sekarang memiliki pendekatan yang sangat dekat dengan cluster grafem yang diperluas untuk digunakan untuk peningkatan
\X
.Kabar buruknya ☺ adalah bahwa polanya adalah:
yang di Java Anda akan menulis sebagai:
¡Tschüß!
sumber
t
di @tchrist. Mungkin sampai ke kepalaku. :)Sangat disayangkan itu
\w
tidak berhasil. Solusi yang diusulkan\p{Alpha}
juga tidak berhasil untuk saya.Tampaknya
[\p{L}]
menangkap semua huruf Unicode. Jadi padanan Unicode\w
seharusnya[\p{L}\p{Digit}_]
.sumber
\w
juga cocok dengan angka dan banyak lagi. Saya pikir hanya untuk surat,\p{L}
akan berhasil.\p{L}
cukup. Saya juga berpikir bahwa masalahnya hanya huruf.[\p{L}\p{Digit}_]
harus menangkap semua karakter alfanumerik termasuk garis bawah.\w
Unicode didefinisikan sebagai lebih luas dari sekedar\pL
dan digit ASCII, dari semua hal konyol. Anda harus menulis[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
jika Anda menginginkan Unicode-aware\w
untuk Java - atau Anda bisa menggunakanunicode_charclass
fungsi saya dari sini . Maaf!\pL
memang berfungsi (Anda tidak perlu menggunakan alat peraga satu huruf). Namun, Anda jarang menginginkannya, karena Anda harus lebih berhati-hati agar pasangan Anda tidak mendapatkan jawaban yang berbeda hanya karena data Anda dalam Unicode Normalization Form D (alias NFD, yang berarti dekomposisi kanonik ) versus berada di NFC (NFD diikuti oleh kanonik komposisi ). Contohnya adalah titik kode U + E9 ("é"
) adalah\pL
dalam bentuk NFC, tetapi bentuk NFD-nya menjadi U + 65,301, jadi cocok\pL\pM
. Anda bisa agak mendapatkan sekitar ini dengan\X
:(?:(?=\pL)\X)
, tapi Anda harus versi saya bahwa untuk Java. :(Di Jawa,
\w
dan\d
tidak sadar-Unicode; mereka hanya cocok dengan karakter ASCII,[A-Za-z0-9_]
dan[0-9]
. Hal yang sama berlaku untuk\p{Alpha}
dan teman-teman ("kelas karakter" POSIX yang menjadi dasar mereka seharusnya peka terhadap lokal, tetapi di Jawa mereka hanya cocok dengan karakter ASCII). Jika Anda ingin mencocokkan "karakter kata" Unicode Anda harus mengejanya, misalnya[\pL\p{Mn}\p{Nd}\p{Pc}]
, untuk huruf, pengubah non-spasi (aksen), angka desimal, dan tanda baca penghubung.Namun, Jawa
\b
adalah Unicode-savvy; ia menggunakanCharacter.isLetterOrDigit(ch)
dan memeriksa huruf beraksen juga, tetapi satu-satunya karakter "penghubung tanda baca" yang dikenali adalah garis bawah. EDIT: ketika saya mencoba kode sampel Anda, itu dicetak""
danélève"
sebagaimana mestinya ( lihat di ideone.com ).sumber
\b
mengerti Unicode. Itu membuat banyak sekali kesalahan."\u2163="
,,"\u24e7="
dan"\u0301="
semua gagal mencocokkan pola"\\b="
di Jawa, tetapi seharusnya - seperti yangperl -le 'print /\b=/ || 0 for "\x{2163}=", "\x{24e7}=", "\x{301}="'
diungkapkan. Namun, jika (dan hanya jika) Anda menukar batas kata versi saya alih-alih yang asli\b
di Java, maka semua itu juga berfungsi di Java.\b
kebenarannya, hanya menunjukkan bahwa ini beroperasi pada karakter Unicode (seperti yang diterapkan di Java), tidak hanya seperti ASCII\w
dan teman-teman. Namun, ini berfungsi dengan benar sehubungan dengan\u0301
kapan karakter itu dipasangkan dengan karakter dasar, seperti padae\u0301=
. Dan saya tidak yakin bahwa Java salah dalam hal ini. Bagaimana tanda kombinasi dianggap sebagai karakter kata kecuali itu adalah bagian dari cluster grafem dengan sebuah huruf?\X
singkatan dari non-mark diikuti dengan sejumlah tanda, bermasalah, karena Anda harus dapat menggambarkan semua file sebagai cocok/^(\X*\R)*\R?$/
, tetapi Anda tidak bisa jika Anda memiliki\pM
di awal file, atau bahkan baris. Jadi mereka telah mengembangkannya agar selalu cocok dengan setidaknya satu karakter. Itu selalu terjadi, tapi sekarang pola di atas berhasil. [… Lanjutan…]\b
sebagian peka terhadap Unicode. Pertimbangkan untuk mencocokkan benang"élève"
dengan pola\b(\w+)\b
. Lihat masalahnya?\w+
menemukan dua kecocokan:l
danve
, yang sudah cukup buruk. Tetapi dengan batasan kata itu tidak menemukan apa-apa, karena\b
mengenalié
danè
sebagai karakter kata. Minimal,\b
dan\w
harus setuju tentang apa itu karakter kata dan apa yang bukan.