gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 19:34:11 26 4
gpt4 key购买 nike

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

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

看来 \b单词分隔符在不应该匹配的地方匹配。

什么是类似 .NET、Unicode 感知的正确等价物 \w\b在 java ?哪些其他快捷方式需要“重写”以使其能够识别 Unicode?

最佳答案

源代码

我在下面讨论的重写函数的源代码 is available here .

Java 7 中的更新

Sun 更新 Pattern JDK7 的类有一个了不起的新标志,UNICODE_CHARACTER_CLASS ,这使得一切正常。它可用作嵌入式 (?U)用于模式内部,因此您可以将其与 String 一起使用类的包装器也是如此。它还修正了各种其他属性的定义。它现在在 RL1.2 中跟踪 Unicode 标准。和 RL1.2a来自 UTS#18:Unicode 正则表达式。这是一个令人兴奋和戏剧性的改进,开发团队的这一重要努力值得表扬。

Java 的正则表达式 Unicode 问题

Java 正则表达式的问题在于 Perl 1.0 字符类转义——意思是 \w , \b , \s , \d和它们的补充——在 Java 中没有扩展到与 Unicode 一起使用。其中,只有\b享有某些扩展语义,但这些都没有映射到 \w ,也不至 Unicode identifiers ,也不至 Unicode line-break properties .

此外,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 属性支持是 严格意义上的千禧年 ,我的意思是它不支持过去十年中出现的任何 Unicode 属性。

不能正确地谈论空白是非常烦人的。考虑下表。对于每个代码点,都有一个 J-results 列
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 个字符类转义:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R

通过用可预测和一致的方式实际匹配 Unicode 的东西替换它们。它只是来自单个黑客 session 的 alpha 原型(prototype),但它是完整的功能。

简而言之,我的代码将这 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 now refers to作为传统的字形集群,而不是扩展的字形集群,因为后者更复杂。 Perl 本身现在使用更高级的版本,但旧版本在最常见的情况下仍然完全可用。 编辑:见底部附录。
  • 怎么办\d取决于您的意图,但默认是 Uniode 定义。我可以看到人们并不总是想要 \p{Nd} ,但有时 [0-9]\pN .
  • 两个边界定义,\b\B , 是专门为使用 \w 而编写的定义。
  • 那个\w定义过于宽泛,因为它捕获了括号内的字母,而不仅仅是圈出的字母。 Unicode Other_Alphabetic属性在 JDK7 之前不可用,所以这是你能做的最好的事情。


  • 探索边界

    自从 Larry Wall 首次创造 \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

    这些都非常直接地定义为:
  • 后面的词是 (?<=\w) .
  • 前面的词是 (?=\w) .
  • 不跟字是(?<!\w) .
  • 不先于词是 (?!\w) .

  • 因此,由于 IF-THEN被编码为 and合编 AB在正则表达式中,一个 orX|Y ,并且因为 and优先级高于 or ,就是 AB|CD .所以每 \b这意味着边界可以安全地替换为:
        (?:(?<=\w)(?!\w)|(?<!\w)(?=\w))

    \w以适当的方式定义。

    (你可能会觉得 AC 组件是对立的很奇怪。在一个完美的世界里,你应该可以写出 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)


  • 用 Java 修复 Java

    我在 my other answer 中发布的代码提供了这一点以及许多其他便利。这包括自然语言单词、破折号、连字符和撇号的定义,以及更多。

    它还允许您在逻辑代码点中指定 Unicode 字符,而不是在愚蠢的 UTF-16 代理中。 很难过分强调这有多重要! 这仅适用于字符串扩展。

    对于使 Java 正则表达式中的字符类最终在 Unicode 上工作并正常工作的正则表达式字符类替换,获取 the full source from here . 当然,你可以随心所欲。如果您对其进行修复,我很乐意听到它,但您不必这样做。它很短。主要正则表达式重写函数的内容很简单:
    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 版本,是我在周末修改的内容。它不会一直这样。

    对于测试版,我打算:
  • 将重复的代码折叠在一起
  • 提供关于非转义字符串转义与增加正则表达式转义的更清晰的界面
  • \d 中提供一些灵活性扩展,也许还有 \b
  • 提供方便的方法来处理和调用 Pattern.compile 或 String.matches 或诸如此类的东西

  • 对于生产版本,它应该有 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:.))";

    ¡Tschüß!

    关于java - Java正则表达式中\w和\b的Unicode等价物?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4304928/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com