Mã nguồn
Mã nguồn cho các chức năng viết lại mà tôi thảo luận dưới đây có sẵn ở đây .
Cập nhật trong Java 7
Pattern
Lớp cập nhật của Sun cho JDK7 có một lá cờ mới tuyệt vời UNICODE_CHARACTER_CLASS
, khiến mọi thứ hoạt động trở lại. Nó có sẵn dưới dạng nhúng (?U)
cho bên trong mẫu, vì vậy bạn cũng có thể sử dụng nó với các String
hàm bao của lớp. Nó cũng thể thao sửa các định nghĩa cho các tính chất khác nhau, quá. Hiện tại, nó theo dõi Tiêu chuẩn Unicode, trong cả RL1.2 và RL1.2a từ UTS # 18: Biểu thức chính quy Unicode . Đây là một cải tiến thú vị và ấn tượng, và nhóm phát triển sẽ được khen ngợi cho nỗ lực quan trọng này.
Các vấn đề về Regex Unicode của Java
Vấn đề với Java regexes là Perl 1.0 charclass thoát - có nghĩa là \w
, \b
, \s
, \d
và bổ sung của họ - không phải là trong Java mở rộng để làm việc với Unicode. Một mình trong số này, \b
thích một số ngữ nghĩa mở rộng nhất định, nhưng những bản đồ này không \w
, cũng không phải là định danh Unicode , cũng như các thuộc tính ngắt dòng Unicode .
Ngoài ra, các thuộc tính POSIX trong Java được truy cập theo cách này:
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}
Đây là một mớ hỗn độn thật, bởi vì nó có nghĩa là điều thích Alpha
, Lower
và Space
làm không trong bản đồ Java với Unicode Alphabetic
, Lowercase
hoặc Whitespace
tài sản. Điều này là cực kỳ khó chịu. Hỗ trợ thuộc tính Unicode của Java hoàn toàn không có niên đại , theo tôi, nó có nghĩa là nó hỗ trợ không có thuộc tính Unicode nào xuất hiện trong thập kỷ qua.
Không thể nói về khoảng trắng đúng cách là siêu khó chịu. Hãy xem xét bảng sau. Đối với mỗi điểm mã đó, có cả cột kết quả J cho Java và cột kết quả P cho Perl hoặc bất kỳ công cụ regex dựa trên PCRE nào khác:
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 -
Thấy chưa
Hầu như mọi kết quả trong khoảng trắng của Java là ̲w̲r̲o̲n̲g̲ theo Unicode. Đó là một vấn đề thực sự lớn. Java chỉ bị rối tung, đưa ra các câu trả lời là Sai sai theo thực tiễn hiện có và cũng theo Unicode. Plus Java thậm chí không cung cấp cho bạn quyền truy cập vào các thuộc tính Unicode thực sự! Trong thực tế, Java không hỗ trợ bất kỳ thuộc tính nào tương ứng với khoảng trắng Unicode.
Giải pháp cho tất cả những vấn đề đó, và nhiều hơn nữa
Để giải quyết vấn đề này và nhiều vấn đề liên quan khác, hôm qua tôi đã viết một hàm Java để viết lại một chuỗi mẫu viết lại 14 lần thoát này.
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
bằng cách thay thế chúng bằng những thứ thực sự hoạt động để phù hợp với Unicode theo cách có thể dự đoán và nhất quán. Nó chỉ là một nguyên mẫu alpha từ một phiên hack duy nhất, nhưng nó hoàn toàn hoạt động.
Câu chuyện ngắn là mã của tôi viết lại 14 cái đó như sau:
\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*)
Một số điều cần xem xét ...
Điều đó sử dụng cho \X
định nghĩa của nó, cái mà Unicode bây giờ gọi là cụm grapheme kế thừa , không phải là cụm grapheme mở rộng , vì cái sau này khá phức tạp. Bản thân Perl hiện sử dụng phiên bản fancier, nhưng phiên bản cũ vẫn hoàn toàn khả thi cho các tình huống phổ biến nhất. EDIT: Xem phụ lục ở phía dưới.
Phải làm gì \d
phụ thuộc vào ý định của bạn, nhưng mặc định là định nghĩa Uniode. Tôi có thể thấy mọi người không phải lúc nào cũng muốn \p{Nd}
, nhưng đôi khi [0-9]
hoặc \pN
.
Hai định nghĩa ranh giới, \b
và \B
, được viết riêng để sử dụng \w
định nghĩa.
\w
Định nghĩa đó là quá rộng, bởi vì nó lấy các chữ cái không chỉ là những chữ được khoanh tròn. Other_Alphabetic
Thuộc tính Unicode không có sẵn cho đến JDK7, vì vậy đó là cách tốt nhất bạn có thể làm.
Khám phá ranh giới
Ranh giới đã trở thành một vấn đề kể từ khi Larry Wall lần đầu tiên đưa ra \b
và \B
cú pháp để nói về chúng cho Perl 1.0 vào năm 1987. Chìa khóa để hiểu cách thức \b
và \B
cả hai hoạt động là xua tan hai huyền thoại phổ biến về chúng:
- Họ chỉ tìm kiếm các
\w
ký tự từ, không bao giờ tìm các ký tự không phải từ.
- Họ không đặc biệt tìm kiếm các cạnh của chuỗi.
Một \b
ranh giới có nghĩa là:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
Và tất cả đều được định nghĩa hoàn toàn đơn giản là:
- theo từ là
(?<=\w)
.
- đi trước từ là
(?=\w)
.
- không theo từ là
(?<!\w)
.
- không có từ trước là
(?!\w)
.
Do đó, vì IF-THEN
được mã hóa dưới dạng and
ed-together AB
trong regexes, an or
là X|Y
và bởi vì and
mức độ ưu tiên cao hơn or
, điều đó chỉ đơn giản AB|CD
. Vì vậy, mọi \b
điều đó có nghĩa là một ranh giới có thể được thay thế một cách an toàn bằng:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
với \w
định nghĩa theo cách thích hợp.
(Bạn có thể nghĩ nó lạ mà A
và C
linh kiện là đối lập Trong một thế giới hoàn hảo, bạn sẽ có thể viết đó. AB|D
, Nhưng trong một thời gian tôi đã đuổi theo xuống mâu thuẫn loại trừ lẫn nhau trong các thuộc tính Unicode - mà tôi nghĩ rằng tôi đã đưa về chăm sóc , nhưng tôi đã để điều kiện kép trong ranh giới chỉ trong trường hợp. Thêm vào đó, điều này làm cho nó mở rộng hơn nếu bạn có thêm ý tưởng sau này.)
Đối với các \B
phi giới hạn, logic là:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Cho phép tất cả các trường hợp \B
được thay thế bằng:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
Đây thực sự là cách \b
và \B
hành xử. Các mẫu tương đương cho chúng là
\b
sử dụng ((IF)THEN|ELSE)
cấu trúc là(?(?<=\w)(?!\w)|(?=\w))
\B
sử dụng ((IF)THEN|ELSE)
cấu trúc là(?(?=\w)(?<=\w)|(?<!\w))
Nhưng các phiên bản chỉ AB|CD
ổn, đặc biệt nếu bạn thiếu các mẫu có điều kiện trong ngôn ngữ regex của bạn - như Java. ☹
Tôi đã xác minh hành vi của các ranh giới bằng cách sử dụng cả ba định nghĩa tương đương với bộ kiểm tra kiểm tra 110.385.408 trận đấu mỗi lần chạy và tôi đã chạy trên hàng tá cấu hình dữ liệu khác nhau theo:
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)
Tuy nhiên, mọi người thường muốn một loại ranh giới khác. Họ muốn một cái gì đó là khoảng trắng và nhận biết cạnh của chuỗi:
- cạnh trái như
(?:(?<=^)|(?<=\s))
- cạnh phải như
(?=$|\s)
Sửa lỗi Java với Java
Mã tôi đã đăng trong câu trả lời khác của tôi cung cấp điều này và khá nhiều tiện ích khác. Điều này bao gồm các định nghĩa cho các từ ngôn ngữ tự nhiên, dấu gạch ngang, dấu gạch ngang và dấu nháy đơn, cộng với một chút nữa.
Nó cũng cho phép bạn chỉ định các ký tự Unicode trong các điểm mã logic, không phải trong các đại diện UTF-16 ngu ngốc. Thật khó để nhấn mạnh tầm quan trọng của nó! Và đó chỉ là để mở rộng chuỗi.
Để thay thế charex regex làm cho lớp char trong regex Java của bạn cuối cùng hoạt động trên Unicode và hoạt động chính xác, hãy lấy toàn bộ nguồn từ đây . Bạn có thể làm với nó như bạn muốn, tất nhiên. Nếu bạn sửa nó, tôi rất muốn nghe về nó, nhưng bạn không phải làm thế. Nó khá ngắn. Các can đảm của chức năng viết lại biểu thức chính là đơn giản:
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;
Dù sao, mã đó chỉ là một bản phát hành alpha, thứ tôi đã hack vào cuối tuần qua. Nó sẽ không ở lại như vậy.
Đối với bản beta tôi dự định:
cùng nhau nhân đôi mã
cung cấp một giao diện rõ ràng hơn về thoát thoát chuỗi không định hình so với thoát thoát regex
cung cấp một số linh hoạt trong việc \d
mở rộng và có thể\b
cung cấp các phương thức tiện lợi để xử lý việc quay vòng và gọi Pattern.compile hoặc String.matches hoặc whatnot cho bạn
Để phát hành sản xuất, cần có javadoc và bộ kiểm tra JUnit. Tôi có thể bao gồm gigatester của tôi, nhưng nó không được viết dưới dạng thử nghiệm JUnit.
Phụ lục
Tôi có tin tốt và tin xấu.
Tin vui là giờ đây tôi đã có một xấp xỉ rất gần với cụm grapheme mở rộng để sử dụng cho một cải tiến \X
.
Tin xấu là mô hình đó là:
(?:(?:\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:.))
mà trong Java bạn sẽ viết là:
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üß!