9

来自我之前的问题为什么 locale-pragma 下的单词字符不匹配?以及如何更改嵌套引号我了解到,在处理 UTF-8 数据时,您不能将其信任\w为 word-char,您必须使用 Unicode 字符属性\p{Word}。现在我发现零宽度单词边界\b也不适用于 UTF-8(启用了语言环境),但我在 Unicode 字符属性中没有找到任何等效项。我想我可以自己构造它:(?<=\P{Word})(\p{Word}+)(?=\P{Word}),它应该等同于\b(\w+)\b.

在下面的测试脚本中,我有两个数组来测试两个不同的正则表达式。\b当未启用语言环境时,第一个基于工作正常。为了让它也适用于语言环境,我编写了另一个模拟边界的版本,(?=\P{Word})但它没有按我的预期工作(我也在脚本中显示了预期的结果)。

您是否看到出了什么问题以及如何让模拟的正则表达式首先使用 ASCII(或没有语言环境)工作?

#!/usr/bin/perl

use 5.010;
use utf8::all;
use locale; # et_EE.UTF-8 in my case
$| = 1;

my @test_boundary = (  # EXPECTED RESULT:
  '"abc def"',         # '«abc def»'
  '"abc "d e f" ghi"', # '«abc «d e f» ghi»'
  '"abc "d e f""',     # '«abc «d e f»»'
  '"abc "d e f"',      # '«abc "d e f»'
  '"abc "d" "e" f"',   # '«abc «d» «e» f»'
  # below won't work with \b when locale enabled
  '"100 Естонiï"',     #  '«100 Естонiï»'
  '"äöõ "ä õ ü" ï"',   # '«äöõ «ä õ ü» ï»'
  '"äöõ "ä õ ü""',     # '«äöõ «ä õ ü»»'
  '"äöõ "ä õ ü"',      # '«äöõ «ä õ ü»'
  '"äöõ "ä" "õ" ï"',   # '«äöõ «ä» «õ» ï»'
);

my @test_emulate = (   # EXPECTED RESULT:
  '"100 Естонiï"',     # '«100 Естонiï»'
  '"äöõ "ä õ ü" ï"',   # '«äöõ «ä õ ü» ï»'
  '"äöõ "ä õ ü""',     # '«äöõ «ä õ ü»»'
  '"äöõ "ä õ ü"',      # '«äöõ "ä õ ü»'
  '"äöõ "ä" "õ" ï"',   # '«äöõ «ä» «õ» ï»'
);

say "BOUNDARY";
for my $sentence ( @test_boundary ) {
  my $quote_count = ( $sentence =~ tr/"/"/ );

  for ( my $i = 0 ; $i <= $quote_count ; $i += 2 ) {
    $sentence =~ s/
      "(                          # first qoute, start capture
        [\p{Word}\.]+?            # suva word-char
        .*?\b[\.,?!»]*?           # any char followed boundary + opt. punctuation
      )"                          # stop capture, ending quote
      /«$1»/xg;                   # change to fancy
  }
  say $sentence;
}

say "EMULATE";
for my $sentence ( @test_emulate ) {
  my $quote_count =  ( $sentence =~ tr/"/"/ );

  for ( my $i = 0 ; $i <= $quote_count ; $i += 2 ) {
    $sentence =~ s/
      "(                         # first qoute, start capture
      [\p{Word}\.]+?             # at least one word-char or point
      .*?(?=\P{Word})            # any char followed boundary 
      [\.,?!»]*?                 # optional punctuation
      )"                         # stop capture, ending quote
      /«$1»/gx;                  # change to fancy
  }
  say $sentence;
}
4

2 回答 2

7

由于 the 位置后面的字符\b要么是一些标点符号,要么是"(为安全起见,请仔细检查\p{Word}与它们中的任何一个都不匹配),它属于 case \b\W。因此,我们可以模拟\b

(?<=\p{Word})

我不熟悉 Perl,但从我在这里测试的结果\w来看,当编码设置为 UTF-8 时, (和)似乎\b也能很好地工作。

$sentence =~ s/
  "(
    [\w\.]+?
    .*?\b[\.,?!»]*?
  )"
  /«$1»/xg;

如果您升级到 Perl 5.14 及更高版本,您可以将字符集设置为带有u标志的 Unicode。


您可以使用这种通用策略来构造对应于字符类的边界。(就像\b单词边界定义如何基于 的定义一样\w)。

让我们C成为字符类。我们想定义一个基于字符类 C 的边界。

当您知道当前字符属于C字符类(相当于(\b\w))时,下面的构造将模拟前面的边界:

(?<!C)C

或后面(相当于\w\b):

C(?!C)

为什么是消极的环顾四周?因为正向环视(使用互补字符类)也将断言前面/后面必须有一个字符(断言前面/后面的宽度至少为 1)。负环视将允许字符串开始/结束的情况,而无需编写繁琐的正则表达式。


对于\B\w仿真:

(?<=C)C

同样\w\B

C(?=C)

\B与 直接相反\b,因此,我们可以翻转正/负环视来模拟效果。这也是有道理的——只有在前面/后面有更多字符时才能形成无边界。


其他仿真(假设c为 的补码字符类C):

  • \b\W(?<=C)c
  • \W\bc(?=C)
  • \B\W(?<!C)c
  • \W\Bc(?!C)

对于独立边界的仿真(相当于\b):

(?:(?<!C)(?=C)|(?<=C)(?!C))

和独立的非边界(相当于\B):

(?:(?<!C)(?!C)|(?<=C)(?=C))
于 2013-02-18T18:25:24.650 回答
5

您应该使用负面环视:

(?<!\p{Word})(\p{Word}+)(?!\p{Word})

正向环视在字符串的开头或结尾失败,因为它们需要存在非单词字符。消极的环视在这两种情况下都有效。

于 2013-02-18T18:24:15.847 回答