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

10

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

一些正则表达式引擎中的新概念是Unicode类别。 其中之一是脚本(script),它可以区分拉丁文、希腊文、西里尔文等。 这些示例都是等效的,并匹配希腊文字系统中的任何字符:

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

但是到目前为止,通过阅读关于正则表达式和Unicode的来源,我还没有能确定是否有任何标准或非标准方法来实现一个零宽度匹配,其中一个脚本结束并且另一个开始。

在字符串παν語中,在ν字符之间将有一个匹配,就像\b\<将在π字符之前匹配一样。

现在针对这个示例,我可以根据查找\p{Greek}后跟\p{Han}的方法拼凑出一些东西,甚至可以根据所有可能的两个Unicode脚本名称的组合拼凑出一些东西。

但是,这不是确定性解决方案,因为每次发布时都会向Unicode添加新脚本。是否存在一种未来可靠的表达方式? 或者是否有提议要添加它?


2
接近但不完全相同:https://dev59.com/QG7Xa4cB1Zd3GeqPr50V#14942906。我的答案是单个字符类的边界(这适用于任何字符类)。你的问题是关于任何语言之间的边界。 - nhahtdh
@nhahtdh:谢谢。我很惊讶在搜索中没有找到你的问题。 - hippietrail
1
我认为每个人都应该阅读这个链接中的第二部分:http://www.unicode.org/reports/tr24/ - nhahtdh
1
我有一个非常复杂的解决方案,基本上是有效的。然而,在某些可预测的情况下,它也会核心转储,这意味着我的解决方案在某处触发了解释器错误。我正在调查此问题,因为我不想给您一个可能会导致核心转储的解决方案。 - tchrist
1个回答

5
编辑:我刚刚注意到您实际上没有指定您使用的哪种模式匹配语言。希望Perl解决方案对您有用,因为在任何其他语言中,所需的操作可能非常困难。此外,如果您正在使用Unicode进行模式匹配,则Perl确实是可用于该特定工作的最佳选择。

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

my $data = "foo1 and Πππ 語語語 done";

while ($data =~ /($rx)/g) {
   print "Got string: '$1'\n"; 
} 

生成以下输出:
Got string: 'foo1 and '
Got string: 'Πππ '
Got string: '語語語 '
Got string: 'done'

那就是,它会提取出一个拉丁字符串、一个希腊字符串、一个汉字字符串和另一个拉丁字符串。这与您实际需要的非常相似。
昨天我没有发布这篇文章的原因是我得到了一些奇怪的核心转储。现在我知道原因了。
我的解决方案在(??{...})构造内部使用词法变量。结果发现,在v5.17.1之前,它是不稳定的,最多只能偶然成功。它在v5.17.0上失败了,但在v5.18.0 RC0和RC2上成功了。因此,我添加了一个use v5.17.1,以确保您运行的版本足够新,可以信任此方法。
首先,我决定您实际上并不想要所有相同脚本类型的运行;您想要所有相同脚本类型的运行加上常用字符和继承字符。否则,您会因常见字符的标点符号、空格和数字以及继承字符的组合字符而混乱。我真的不认为您希望它们中断您的“所有相同脚本”的运行,但如果您愿意,很容易停止考虑它们。
所以我们要做的是向前搜索第一个具有除Common或Inherited之外的脚本类型的字符。而且,我们从中提取出该脚本类型实际上是什么,并使用此信息构造一个新的模式,即任意数量的字符,其脚本类型为Common、Inherited或刚刚找到并保存的脚本类型。然后我们评估该新模式并继续。
嘿,我说过它很麻烦,不是吗?
在即将展示的程序中,我保留了一些已注释的调试语句,以显示它正在做什么。如果您取消对它们的注释,您将获得最后一次运行的输出,这应该有助于理解方法:
DEBUG: Got peekahead character f, U+0066
DEBUG: Scriptname is Latin
DEBUG: string to re-interpolate as regex is q{[\p{Script=Common}\p{Script=Inherited}\p{Script=Latin}]*}
Got string: 'foo1 and '
DEBUG: Got peekahead character Π, U+03a0
DEBUG: Scriptname is Greek
DEBUG: string to re-interpolate as regex is q{[\p{Script=Common}\p{Script=Inherited}\p{Script=Greek}]*}
Got string: 'Πππ '
DEBUG: Got peekahead character 語, U+8a9e
DEBUG: Scriptname is Han
DEBUG: string to re-interpolate as regex is q{[\p{Script=Common}\p{Script=Inherited}\p{Script=Han}]*}
Got string: '語語語 '
DEBUG: Got peekahead character d, U+0064
DEBUG: Scriptname is Latin
DEBUG: string to re-interpolate as regex is q{[\p{Script=Common}\p{Script=Inherited}\p{Script=Latin}]*}
Got string: 'done'

最后,这里是重头戏:

use v5.17.1;
use strict;
use warnings;
use warnings FATAL => "utf8";
use open qw(:std :utf8);
use utf8;

use Unicode::UCD qw(charscript);

# regex to match a string that's all of the
# same Script=XXX type
#
my $rx = qr{
    (?=
       [\p{Script=Common}\p{Script=Inherited}] *
        (?<CAPTURE>
            [^\p{Script=Common}\p{Script=Inherited}]
        )
    )
    (??{
        my $capture = $+{CAPTURE};
   #####printf "DEBUG: Got peekahead character %s, U+%04x\n", $capture, ord $capture;
        my $scriptname = charscript(ord $capture);
   #####print "DEBUG: Scriptname is $scriptname\n";
        my $run = q([\p{Script=Common}\p{Script=Inherited}\p{Script=)
                . $scriptname
                . q(}]*);
   #####print "DEBUG: string to re-interpolate as regex is q{$run}\n";
        $run;
    })
}x;


my $data = "foo1 and Πππ 語語語 done";

$| = 1;

while ($data =~ /($rx)/g) {
   print "Got string: '$1'\n";
}

是的,应该有更好的方法。 我认为目前还没有。

所以现在就先享受吧。


我特意没有指定正则表达式方言,而是询问了“标准”、“非标准”和“建议”的情况。我正在使用XRegExp并阅读UTS#18和regular-expressions.info,但我更习惯于Perl和Vim的实现。我想知道我应该能做什么,即使特定的方言尚未实现它。至于解决方法,我认为JavaScript甚至是扩展XRegExp最好。 (顺便说一句,在阅读您的答案正文之前,我就写下了这个问题...) - hippietrail
1
@hippietrail UTS#18至少要到第3级才能涵盖这个,而且目前没有人实施。因此,在此期间我们尽力而为。我最近没有看过它,所以不知道在第3级下是否可能。 - tchrist
除了你自己,当今有谁在积极推动Unicode正则表达式的开发?我知道Perl迄今为止拥有最好的Unicode支持,这也是它成为我主要编程语言多年的主要原因之一,但现在出于其他原因,我转向了一个拥有一些最差的Unicode支持的语言。我肯定可以想出一个非正则表达式的字符串分割器,但它似乎是一个显而易见的特性,应该包含在正则表达式引擎中。也许我应该提交一些建议? - hippietrail
1
@hippietrail 是的,你可能应该这么做。有一份关于安全和标识符的UTS值得一看,因为混合脚本是一个欺骗问题,我记得在那里提到过。在这种情况下,这将非常有用。 - tchrist
1
@hippietrail UTS#18 RL 2.2 扩展字形簇讨论了\b{w}表示单词边界,\b{s}表示句子边界等的可能性。似乎你想要的是类似于假设的\b{script}。但请记住常见和继承问题。还有RL3.3定制单词边界,但我认为那也不太对。 - tchrist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接