Java正则表达式中\w和\b的Unicode等价物?

Java正则表达式中\w和\b的Unicode等价物?,java,regex,unicode,character-properties,Java,Regex,Unicode,Character Properties,许多现代正则表达式实现将\w字符类速记解释为“任何字母、数字或连接标点符号”(通常为下划线)。这样,像\w+这样的正则表达式匹配像hello,élève,GOÄu 432或gefräig这样的词 不幸的是,Java没有。在Java中,\w仅限于[A-Za-z0-9.]。除其他问题外,这使得上述匹配词变得困难 它还显示\b单词分隔符在不应该匹配的地方匹配 Java中类似.NET、支持Unicode的\w或\b的正确等价物是什么?还有哪些快捷方式需要“重写”才能识别Unicode?很不幸,\w不起作

许多现代正则表达式实现将
\w
字符类速记解释为“任何字母、数字或连接标点符号”(通常为下划线)。这样,像
\w+
这样的正则表达式匹配像
hello
élève
GOÄu 432
gefräig
这样的词

不幸的是,Java没有。在Java中,
\w
仅限于
[A-Za-z0-9.]
。除其他问题外,这使得上述匹配词变得困难

它还显示
\b
单词分隔符在不应该匹配的地方匹配


Java中类似.NET、支持Unicode的
\w
\b
的正确等价物是什么?还有哪些快捷方式需要“重写”才能识别Unicode?

很不幸,
\w
不起作用。提议的解决方案
\p{Alpha}
对我也不起作用


似乎
[\p{L}]
捕获了所有Unicode字母。因此,
\w
的Unicode等价物应该是
[\p{L}\p{Digit}\u]

在Java中,
\w
\d
不知道Unicode;它们仅与ASCII字符相匹配,
[A-Za-z0-9\
[0-9]
。这同样适用于
\p{Alpha}
和friends(它们所基于的POSIX“字符类”应该是区域敏感的,但在Java中它们只匹配ASCII字符)。如果要匹配Unicode“单词字符”,则必须将其拼写出来,例如字母、非间隔修饰符(重音符号)、十进制数字和连接标点符号的
[\pL\p{Mn}\p{Nd}\p{Pc}]

然而,Java的
\b
精通Unicode;它使用Character.isleterOrdigit(ch)并检查重音字母,但它识别的唯一“连接标点”字符是下划线编辑:当我尝试您的示例代码时,它会按应该的方式打印
élève”

源代码 下面讨论的重写函数的源代码

Java7中的更新 Sun为JDK7更新的
模式
类有一个奇妙的新标志,
UNICODE\U CHARACTER\U类
,它使一切重新正常工作。它是一个可嵌入的
(?U)
用于模式内部,因此您也可以将其与
字符串
类的包装器一起使用。它还对各种其他属性的定义进行了更正。它现在在UTS#18:Unicode正则表达式和UTS#18:Unicode正则表达式中跟踪Unicode标准。这是一个激动人心的重大改进,开发团队将为此而努力这一重要努力受到赞扬


Java的正则表达式Unicode问题 Java正则表达式的问题在于,Perl 1.0 charclass转义(意思是
\w
\b
\s
\d
及其补充)在Java中没有扩展到Unicode。在这些转义中,
\b
单独享有某些扩展语义,但它们既不映射到,也不映射到,也不映射到

此外,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}
这真是一团糟,因为这意味着像Java中的
Alpha
Lower
Space
这样的东西会映射到Unicode
字母
小写
、或
空格
属性。这是非常恼人的。Java的Unicode属性支持是严格意义上的millennial,我的意思是它不支持过去十年中出现的Unicode属性

不能正确地谈论空白是非常恼人的。考虑下表。对于这些代码点中的每一个,都有J-结果列。 对于Java和Perl或任何其他基于PCRE的正则表达式引擎的P-results列:

             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空白结果都是̲w̲r̲o̲n̲g̲。这是一个非常大的问题。
Java只是一团糟,给出了“错误”的答案根据现有实践和Unicode,Java甚至不允许您访问真正的Unicode属性!事实上,Java不支持任何与Unicode空格对应的属性


所有这些问题的解决方案等等 为了解决这个问题和许多其他相关问题,昨天我编写了一个Java函数来重写一个模式字符串,该模式字符串重写了以下14个charclass转义:

\w \W \s \S \v \V \h \H \d \D \b \B \X \R
通过以一种可预测且一致的方式将它们替换为与Unicode匹配的东西。这只是一个来自单个黑客会话的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*)
所有这些都被非常直接地定义为:

  • 下面的单词是
    (?)?
    
  • 不在单词is
    (?!\w)
    之前
因此,由于
IF-THEN
被编码为
 在正则表达式中将
AB
组合在一起,则
X | Y
,并且由于
的优先级高于
,这就是简单的
AB | CD
。因此,表示边界的每个
\b
都可以安全地替换为:

    (?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
    (?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
允许将
\B
的所有实例替换为:

    (?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
    (?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
然而,人们通常想要一种不同的边界。他们想要一些能够感知空格和字符串边缘的东西:


  • 左边缘为
    (?:(?)但是
    \w
    也匹配数字和更多。我认为对于字母,
    \p{L}
    就可以了。你是对的。
    \p{L}
    就足够了。而且我还认为只有字母才是问题。
    [\p{L}\p{digital}]
    应捕获所有字母数字字符,包括下划线。@MusicKk:有关完整的解决方案,请参阅我的答案。@t
         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)
    
    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;
    
    (?:(?:\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:.))
    
    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:.))";