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 biasa UNICODE_CHARACTER_CLASS
, yang membuat semuanya bekerja dengan baik kembali. Ini tersedia sebagai embeddable (?U)
untuk di dalam pola, jadi Anda juga bisa menggunakannya dengan String
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:
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}
Ini adalah berantakan, karena itu berarti bahwa hal-hal seperti Alpha
, Lower
, dan Space
lakukan tidak di peta Jawa ke Unicode Alphabetic
, Lowercase
atau Whitespace
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:
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 -
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:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
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:
\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*)
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:
- Mereka hanya pernah mencari untuk
\w
karakter kata, tidak pernah untuk karakter non-kata.
- Mereka tidak secara khusus mencari tepi benang.
Sebuah \b
batas berarti:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
Dan itu semua didefinisikan dengan sangat lugas sebagai:
- mengikuti kata adalah
(?<=\w)
.
- mendahului kata adalah
(?=\w)
.
- tidak mengikuti kata adalah
(?<!\w)
.
- tidak mendahului kata adalah
(?!\w)
.
Oleh karena itu, karena IF-THEN
dikodekan sebagai and
ed-bersama AB
dalam ekspresi reguler, an or
adalah X|Y
, dan karena and
lebih diutamakan daripada or
, itu sederhana AB|CD
. Jadi setiap \b
itu berarti batas dapat diganti dengan aman dengan:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
dengan yang \w
ditentukan dengan cara yang sesuai.
(Anda mungkin berpikir aneh bahwa A
dan C
komponen berlawanan. Di dunia yang sempurna, Anda seharusnya dapat menulis itu AB|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:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Mengizinkan semua contoh \B
untuk diganti dengan:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
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:
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)
Namun, orang sering menginginkan jenis batasan yang berbeda. Mereka menginginkan sesuatu yang whitespace dan edge-of-string aware:
- tepi kiri sebagai
(?:(?<=^)|(?<=\s))
- tepi kanan sebagai
(?=$|\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:
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;
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:
(?:(?:\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:.))
yang di Java Anda akan menulis sebagai:
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üß!