【问题标题】:How to emulate word boundary when using unicode character properties?使用 unicode 字符属性时如何模拟单词边界?
【发布时间】:2013-02-03 06:12:13
【问题描述】:

从我之前的问题Why under locale-pragma word characters do not match? 和How to change nested quotes 我了解到,在处理 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;
}

【问题讨论】:

  • 首先,您误会了:\w 和 \p{word} 在定义上是相同的。但第二,拜托,拜托,拜托不要使用use locale pragma。它是破碎的、不可靠的、不可预测的,并且是一种令人难以忍受的痛苦——正如你似乎已经发现的那样。您应该使用Unicode::Collate::Locale 模块。你可能也不应该使用use utf8:all,而是做你想做的特定事情。
  • @tchrist: \w 和 \p{Word} 可能定义相同,但在 use locale 下它们的行为不同。当然,当我现在有其他方式时,我不会使用语言环境。 use utf8::all 很好地满足了我的需求,这是表达我意图的干净方式。如果 utf8::all 有一些不足,也许你可以指出作者?
  • 你不知道 utf8:all 做什么或不做什么,这正是问题所在——我注意到,这个问题不能通过添加东西来解决。你得到什么级别的utf8 警告?没有或警告或致命的?那么这三个子类型,nonchar 和 surrogates 以及 non_unicode 呢?这些东西应该在代码中明确显示,以便人们可以看到它们是什么。然后是在输入端渲染成 NFD,在输出端渲染成 NFC 的问题;这样做吗?
  • @tchrist:我对utf8::all 内部结构的理解比DBI、Unicode::Collate::Locale 或Perl 核心内部结构要好得多,但这并不能阻止我在有限的范围内尽可能地使用它们。例如,我从不担心标准化。当我有一天,我会更深入地挖掘。这对我来说是正常的抽象级别 - 当它们没有足够的意义但按预期工作时,不要担心底层细节。在utf8::all 出现的时候,我自己也已经把大部分图片放在一起了。但是使用它我说:你们都可以使用一些“轮子”。我想念它在核心

标签: regex perl unicode utf-8 locale


【解决方案1】:

您应该使用负面的环视:

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

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

【讨论】:

  • 这不就像写\b(\w+)\b吗?
  • 他把事情搞砸了 icky/broken use locale;请参阅this answer 以了解如何以正确的方式在 Perl 中执行语言环境。这样你也可以使用普通的正则表达式。
【解决方案2】:

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

(?<=\p{Word})

我不熟悉 Perl,但从 what I tested here 看来,\w(和 \b)在编码设置为 UTF-8 时也能很好地工作。

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

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


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

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

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

(?<!C)C

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

C(?!C)

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


对于\B\w 仿真:

(?<=C)C

同样\w\B:

C(?=C)

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


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

  • \b\W: (?&lt;=C)c
  • \W\b: c(?=C)
  • \B\W: (?&lt;!C)c
  • \W\B: c(?!C)

对于独立边界的模拟(相当于\b):

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

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

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

【讨论】:

  • 将\b 更改为(?!\p{Word}) 并没有改变结果。使用测试用例'"äöõ "ä õ ü" ï"' 我被捕获而不是äöõ "ä õ ü 仍然是äöõ ,就像我的积极环顾一样。你能指出,出了什么问题吗?
  • @w.k:我不确定您要做什么(括号匹配?)。问题不在于单词边界(及其仿真),而在于您当前拥有的正则表达式。
  • 我的目标是将双引号"äöõ" 变成花哨的引号«äöõ»。在嵌套引号中,它应该替换不匹配的对,而是替换第一个和第三个引号,然后是第二个和第四个。当我不启用语言环境时,我的第一个正则表达式完全按照我的预期工作。但我也需要语言环境。因此,在第二个正则表达式中,我所做的唯一更改是将\b 更改为(?=\P{Word}),并在您的建议之后更改为否定前瞻(?!\p{Word})。这些前瞻不像\b 那样工作,我不明白为什么?
  • @w.k:我不确定您如何将 2 个引号识别为一对并将它们更改为花哨的引号。我不确定您是否知道您在原始正则表达式中在做什么:pastebin.com/DVUqzSYb
  • 嗯,但第 46 行的匹配正是我要寻找的。我也想获得与模拟边界相同的匹配,但到目前为止我还做不到。 $1 变成了我想用花哨的引号括起来的字符串。你在哪里看到问题?重复次数过多?
猜你喜欢
  • 2012-05-22
  • 2016-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-24
  • 2016-03-31
相关资源
最近更新 更多