许多现代正则表达式实现将\w
字符类的简写解释为“任何字母,数字或连接标点符号”(通常:下划线)。这样一来,像一个正则表达式\w+
匹配的话像hello
,élève
,GOÄ_432
或gefräßig
。
不幸的是,Java没有。在Java中,\w
仅限于[A-Za-z0-9_]
。除了其他问题之外,这使得匹配上述单词变得困难。
似乎\b
分隔符在不应该匹配的地方匹配。
类似于.NET
的,支持Unicode的\w
或\b
Java 的正确等效方式是什么?还有哪些其他快捷方式需要“重写”以使它们能够识别Unicode?
源代码
我下面讨论的重写功能的源代码可在此处获得。
Java 7中的更新
Sun的PatternJDK7
更新类有一个很棒的新标志UNICODE_CHARACTER_CLASS
,它使一切重新正常运行。它可以作为(?U)模式内部的可嵌入对象使用,因此你也可以将其与String类的包装器一起使用。它还针对其他各种属性修改了定义。现在跟踪Unicode
标准,在这两个RL1.2和RL1.2a从UTS#18:Unicode的正则表达式。这是一个令人兴奋的巨大进步,开发团队的这一重要努力值得称赞。
Java的Regex Unicode问题
使用Java正则表达式的问题是,Perl的1.0 charclass将逃逸-这意味着\w
,\b
,\s
,\d
和它们的补-不是Java扩展工作使用Unicode
。其中\b
只有一个具有某些扩展的语义,但是它们既不映射\w
到Unicode
标识符,也不映射到Unicode
换行属性。
此外,以这种方式访问Java中的POSIX属性:
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}
这是一个真正的混乱,因为这意味着一些事情,如Alpha
,Lower
和Space
做的不是在Java
中映射为Unicode Alphabetic
,Lowercase
或Whitespace
性质。这真是令人讨厌。Java的Unicode
属性严格地是antemillennial
,这意味着我不支持过去十年来出现的Unicode
属性。
无法正确谈论空白是一件令人讨厌的事情。请考虑下表。对于每个代码点,Java都有一个J结果列,Perl或任何其他基于PCRE的正则表达式引擎都有一个P结果列:
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 -
看到那个吗?
实际上,根据Unicode,这些Java空格结果中的每一个都是“ ̲”。这是一个很大的问题。 Java只是一团糟,根据现有惯例以及根据Unicode,给出的答案都是“错误的”。另外,Java甚至都不允许你访问真正的Unicode属性!实际上,Java不支持与Unicode空格相对应的任何属性。
所有这些问题的解决方案,以及更多
为了解决这个问题以及其他许多相关问题,昨天我编写了一个Java函数来重写模式字符串,该模式字符串重写了这14个charclass转义:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
通过以一种可预测且一致的方式将其替换为与Unicode匹配的实际工作来代替它们。它只是单个hack会话中的一个alpha原型,但功能完全正常。
简而言之,我的代码如下重写了这14个代码:
\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*)
一些要考虑的事情…
\X
定义使用了Unicode现在称为传统字素簇而不是扩展字素簇的定义,因为后者更为复杂。Perl本身现在使用的是高级版本,但是对于大多数常见情况,旧版本仍然可以完美使用。编辑:请参阅底部的附录。\d
取决于你的意图,但默认值为Uniode定义。我看到人们并不总是想要\p{Nd}
,但有时[0-9]
还是想要\pN
。\b
和\B
,以使用该\w
定义。\w
定义过于宽泛,因为它抓住了赦免字母,而不仅仅是带圆圈的字母。Unicode Other_Alphabetic
属性直到JDK7才可用,所以这是你可以做的最好的事情。探索边界
边界已自从拉里·沃尔首先创造了一个问题\b
和\B
语法在1987年谈论他们对Perl 1.0后面的关键是了解如何\b
与\B
这两个工作是打消她们两分无孔不入的神话:
\w
字的字符,从来没有对非单词字符。一个\b
边界的机构:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
所有这些都直接定义为:
is (?<=\w)
。(?=\w)
。is (?<!\w)
。(?!\w)
。因此,由于IF-THEN被编码为and ED-一起AB在正则表达式,一个or是X|Y,并且因为and是在优先级高于or,即简单地AB|CD。因此,所有\b这意味着可以用以下方法安全地替换边界:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
用\w
适当的方式定义。
(你可能会觉得A和C组件是对立的,这很奇怪。在一个完美的世界中,你应该能够编写该代码AB|D,但是有一段时间我一直在追踪Unicode属性中的互斥矛盾-我认为我已经注意了,但我把双重条件留在了边界,以防万一。另外,如果以后有更多的想法,它也可以扩展。
对于\B
非边界,逻辑为:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
允许将的所有实例\B
替换为:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
这确实是怎样\b
和\B
行为方式。它们的等效模式是
\b
使用的((IF)THEN|ELSE)
构造是(?(?<=\w)(?!\w)|(?=\w))
\B
使用的((IF)THEN|ELSE)
构造是(?(?=\w)(?<=\w)|(?<!\w))
但是只要使用版本就AB|CD可以了,特别是如果你的正则表达式语言(例如Java)缺少条件模式时。
我已经使用测试套件检查了所有三个等效定义,验证了边界的行为,该套件每次运行检查110,385,408个匹配项,并且根据以下十个不同的数据配置运行了该测试套件:
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)
但是,人们通常希望使用另一种边界。他们想要一些空格和字符串边缘感知的东西:
(?:(?<=^)|(?<=\s))
(?=$|\s)
我在其他答案中发布的代码提供了此功能,并提供了许多其他便利。其中包括自然语言单词,破折号,连字符和撇号的定义,以及更多定义。
它还允许你在逻辑代码点中指定Unicode字符,而不是在惯用的UTF-16替代中指定。很难强调这有多重要!那只是为了字符串扩展。
要进行正则表达式charclass替换,使Java regexes中的charclass 最终可以在Unicode上正常工作,请从此处 获取完整的源代码。 当然,你可以随便使用它。如果你对此进行了修复,我很想听听它,但你不必这样做。很短 正则表达式的主要重写功能很简单:
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;
无论如何,这些代码只是一个alpha版本,这是我在周末破解的内容。不会那样的。
对于Beta版,我打算:
\d
扩展提供了一些灵活性,也许\b
对于生产版本,它应该具有javadoc和一个JUnit测试套件。我可能包括我的gigatester,但它不是JUnit测试编写的。
附录
我有好消息,也有坏消息。
好消息是,我现在已经非常接近扩展的字素簇,可以用来进行改进\X。
坏消息☺是这种模式是:
(?:(?:\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:.))
在Java中,你将其编写为:
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:.))";
问题内容: 我有一个脚本,可以分析电视剧集的文件名(例如show.name.s01e02.avi),获取剧集的名称(从www.thetvdb.com API),然后自动将其重命名为更好的名称(显示名-[01x02 ] .avi) 该脚本可以正常工作,直到您尝试在具有Unicode显示名称的文件上使用该脚本为止(我从未真正考虑过这一点,因为我拥有的所有文件都是英文的,所以大部分都属于) 如何允许正则
问题内容: 谁能解释和正则表达式元字符之间的区别?据我了解,这两个元字符都用于单词边界。除此之外,哪个元字符对于多语言内容有效? 问题答案: 元字符是锚号,例如插入号和美元符号。它在称为“单词边界”的位置匹配。此匹配为零长度。 有三个不同的位置可作为单词边界: 如果字符串中的第一个字符是单词字符,则在字符串中第一个字符之前。 如果字符串中的最后一个字符是单词字符,则在字符串的最后一个字符之后。 字
本文向大家介绍\ w vs \ W在JavaScript正则表达式中?,包括了\ w vs \ W在JavaScript正则表达式中?的使用技巧和注意事项,需要的朋友参考一下 \ w与\ W javascript 中的 ' \ w '和' \ W '之间有很多变体,其中前者照顾字母字符(例如字母数字),而后者照顾非单词字符(例如&,^, %等。让我们简要地讨论一下。 语法1 上面的代码给出了在ja
问题内容: 谁能解释和正则表达式元字符之间的区别?据我了解,这两个元字符都用于单词边界。除此之外,哪个元字符对于多语言内容有效? 问题答案: 元字符是锚号,例如插入号和美元符号。它在称为 “单词边界” 的位置匹配。此匹配为零长度。 有三个不同的位置可作为单词边界: 如果字符串中的第一个字符是单词字符,则在字符串中第一个字符之前。 如果字符串中的最后一个字符是单词字符,则在字符串的最后一个字符之后。
问题内容: 我需要从字符串“ بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ”中删除一些Unicode符号 我知道他们肯定在这里。我试过了: 但这不起作用。字符串保持不变。我究竟做错了什么? 问题答案: 您使用的是python 2.x还是3.0? 如果您使用的是2.x,请尝试使用“ u”使正则表达式字符串成为unicode-escape字符串。由于它是正则表达式,因此
问题内容: 如何在JavaScript中使用支持Unicode的正则表达式? 例如,应该有类似的东西可以匹配Letters或Marks类别中的任何代码点(而不仅仅是ASCII的),并且希望具有这样的过滤器来标点,等等。 问题答案: ES 6的情况 即将发布的ECMAScript语言规范,版本6,包含可识别Unicode的正则表达式。必须使用u正则表达式上的修饰符启用支持。请参阅ES6中支持Unic