正则表达式以匹配不同Unicode脚本之间的边界

hip*_*ail 8 regex unicode word-boundary word-boundaries character-properties

正则表达式引擎具有"零宽度"匹配的概念,其中一些对于查找单词边缘很有用:

  • \b - 存在于大多数引擎中以匹配单词和非单词字符之间的任何边界
  • \<\>- 并且- 在Vim中仅分别匹配单词开头的边界和单词的结尾处的边界.

一些正则表达式引擎中的新概念是Unicode类.一个这样的类是脚本,可以区分拉丁语,希腊语,西里尔语等.这些例子都是等价的,匹配希腊语书写系统的任何字符:

  • \p{greek}
  • \p{script=greek}
  • \p{script:greek}
  • [:script=greek:]
  • [:script:greek:]

但到目前为止,在我阅读正则表达式和Unicode的来源时,我还无法确定是否有任何标准或非标准方法来实现零宽度匹配,其中一个脚本结束而另一个脚本开始.

在串????会有的之间的匹配??人物,就如同\b\<会只是之前匹配?字符.

现在,对于这个例子,我可以根据寻找\p{Greek}后面的\p{Han}东西一起破解,我甚至可以根据两个Unicode脚本名称的所有可能组合来一起破解.

但这不是一个确定性的解决方案,因为每个版本仍然会在Unicode中添加新脚本.是否有一种面向未来的方式来表达这一点?或者是否有建议添加它?

tch*_*ist 5

编辑:我刚刚注意到你没有\xe2\x80\x99t 实际上指定了哪个模式匹配语言。好吧,我希望 Perl 解决方案适合您,因为所需的机制对于任何其他语言来说可能都非常困难。另外,如果您\xe2\x80\x99 正在使用 Unicode 进行模式匹配,Perl 确实是适合该特定类型工作的最佳选择。

\n\n
\n\n

当。。。的时候$rx下面的变量设置为适当的模式时,Perl 代码的这个小片段:

\n\n
my $data = "foo1 and \xce\xa0\xcf\x80\xcf\x80 \xe8\xaa\x9e\xe8\xaa\x9e\xe8\xaa\x9e done";\n\nwhile ($data =~ /($rx)/g) {\n   print "Got string: \'$1\'\\n"; \n} \n
Run Code Online (Sandbox Code Playgroud)\n\n

生成此输出:

\n\n
my $data = "foo1 and \xce\xa0\xcf\x80\xcf\x80 \xe8\xaa\x9e\xe8\xaa\x9e\xe8\xaa\x9e done";\n\nwhile ($data =~ /($rx)/g) {\n   print "Got string: \'$1\'\\n"; \n} \n
Run Code Online (Sandbox Code Playgroud)\n\n

也就是说,它会拉出一个拉丁字符串、一个希腊字符串、一个汉字字符串和另一个拉丁字符串。这与我认为你真正需要的东西非常接近。

\n\n

我昨天没有\xe2\x80\x99t 发布这个的原因是我得到了奇怪的核心转储。现在我知道为什么了。

\n\n

我的解决方案在构造内部使用词汇变量(??{...})。事实证明,这在 v5.17.1 之前是不稳定的,最多只是偶然起作用。它在 v5.17.0 上失败,但在 v5.18.0 RC0 和 RC2 上成功。因此,我\xe2\x80\x99添加了一个use v5.17.1以确保您\xe2\x80\x99正在运行足够新的东西以信任这种方法。

\n\n

首先,我确定您\xe2\x80\x99实际上并不想要运行所有相同的脚本类型;您想要运行所有相同的脚本类型以及Common 和 Inherited。否则,您将因 Common 的标点符号、空格和数字以及 Inherited 的组合字符而变得混乱。我真的不认为\xe2\x80\x99你希望那些中断\xe2\x80\x9的运行\xe2\x80\x9调用相同的脚本\xe2\x80\x9d,但如果你这样做,\xe2\x80\x99很容易停止考虑那些。

\n\n

因此,我们要做的是向前查找脚本类型不是 Common 或 Inherited 的第一个字符。更重要的是,我们从中提取脚本类型的实际内容,并使用此信息构建一个新模式,该模式是任意数量的字符,其脚本类型可以是“通用”、“继承”或我们刚刚找到并保存的任何脚本类型。然后我们评估新模式并继续。

\n\n

嘿嘿,我毛茸茸的,不是\xe2\x80\x99吗?

\n\n

在我要显示的程序中,我留下了一些注释掉的调试语句,这些语句显示了它正在做什么。如果取消注释它们,您将获得上次运行的输出,这应该有助于理解该方法:

\n\n
Got string: \'foo1 and \'\nGot string: \'\xce\xa0\xcf\x80\xcf\x80 \'\nGot string: \'\xe8\xaa\x9e\xe8\xaa\x9e\xe8\xaa\x9e \'\nGot string: \'done\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后是最棘手的事情:

\n\n
use v5.17.1;\nuse strict;\nuse warnings;\nuse warnings FATAL => "utf8";\nuse open qw(:std :utf8);\nuse utf8;\n\nuse Unicode::UCD qw(charscript);\n\n# regex to match a string that\'s all of the\n# same Script=XXX type\n#\nmy $rx = qr{\n    (?=\n       [\\p{Script=Common}\\p{Script=Inherited}] *\n        (?<CAPTURE>\n            [^\\p{Script=Common}\\p{Script=Inherited}]\n        )\n    )\n    (??{\n        my $capture = $+{CAPTURE};\n   #####printf "DEBUG: Got peekahead character %s, U+%04x\\n", $capture, ord $capture;\n        my $scriptname = charscript(ord $capture);\n   #####print "DEBUG: Scriptname is $scriptname\\n";\n        my $run = q([\\p{Script=Common}\\p{Script=Inherited}\\p{Script=)\n                . $scriptname\n                . q(}]*);\n   #####print "DEBUG: string to re-interpolate as regex is q{$run}\\n";\n        $run;\n    })\n}x;\n\n\nmy $data = "foo1 and \xce\xa0\xcf\x80\xcf\x80 \xe8\xaa\x9e\xe8\xaa\x9e\xe8\xaa\x9e done";\n\n$| = 1;\n\nwhile ($data =~ /($rx)/g) {\n   print "Got string: \'$1\'\\n";\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

是的,应该有更好的方法。我不认为\xe2\x80\x99 存在\xe2\x80\x94。

\n\n

所以现在,享受吧。

\n