假设Unicode和不区分大小写,模式".."是否匹配"FfIsS"?

maa*_*nus 17 java regex unicode case-insensitive case-folding

这听起来像个笑话,但我可以证明这一点.

假设:

  • Dot匹配任何单个字符.
  • 不区分大小写的模式匹配s且仅当它匹配时才匹配s.toUpperCase().

以下所有内容都非常符合逻辑并且在Java中保留:

  • "?".matches(".") LATIN SMALL LIGATURE FFI(U + FB03)是一个字符,因此它必须匹配
  • "ß".matches(".") LATIN SMALL LETTER SHARP S(U + 00DF)是一个字符,因此它必须匹配
  • "?".toUpperCase().equals("FFI") 按Unicode标准(没有资本连字FFI)
  • "ß".toUpperCase().equals("SS") 按照Unicode标准(有一个大写的S,但它没有被使用)
  • "FfI".toUpperCase().equals("FFI") 明显
  • "sS".toUpperCase.equals("SS") 明显

因此,假设正则表达式中的第一个点代表?第二个点,则正则ß表达式必须匹配"FFISS",并且因为不区分大小写也是"FfIsS".

我真的希望有一些错误,否则正则表达式会变得非常不可用.

问题:

  • 我的"证明"有什么问题?
  • 如果我的第二个假设不成立,那么"不区分大小写"究竟意味着什么?

tch*_*ist 18

案例折叠

答案是否定的,点会不匹配ss不敏感的情况下,虽然原因是稍微深奥.

然而,一些最了解这类事物的人经常提出你的谜题,因为他们也觉得它会导致矛盾.

Unicode中有两种形式的大小写映射.有一个简单的案例映射,其中一个代码点只映射到另一个代码点.所以如果length(s) == 1,那么你也可以保证,Unicode折叠地图length(fc(s)) == 1在哪里fc.但它也适用于uc,tc和lc大小写映射.

问题在于,您没有获得分析某些类型的真实世界文本的好结果,那么您可以制作那些精确的1:1长度保证.

事实上,其中有不少.这些数字表示在四个案例图下,有多少个别BMP代码点映射到指定的长度:

length lc == 2          1
length lc == 3          0

length fc == 2         88
length fc == 3         16

length uc == 2         86
length uc == 3         16

length tc == 2         32
length tc == 3         16
Run Code Online (Sandbox Code Playgroud)

在全外壳,而不是Java的正则表达式使用简单的外壳,你确实可以得到像tschüß和TSCHÜSS匹配,即使它们是不相等的长度.Perl和Ruby在进行不区分大小写的比较时使用完整的大小写映射.如果你不小心,这会在否定的角色类中导致奇怪的悖论.

但这就是问题:不区分大小写的匹配不执行传递操作.换句话说,如果.比赛ß和下不区分大小写的匹配,ß匹配SS,这并不意味着它通过传递.匹配SS不敏感案件.尽管比我更聪明的人已经深入思考过这个问题,但这并不是那样的.

但是,这两个代码点:

  • U +00DFßLATINSMET LETTER SHARP S.
  • U +1E9EẞLATINCAPITAL LETTER SHARP S.

做肯定不区分大小写的匹配,不仅对方也SS,Ss,sS,并ss在全案的映射.他们只是在简单的案例映射下不这样做.

Unicode确实对此做出了一些保证.其一是,如果length(s) == n,这length(fn(s)) <= 3*n哪里fn是任何4箱子映射:lc,fc,uc,和tc.

论规范化

如果您认为这很糟糕,那么当您考虑规范化形式时,它实际上会变得更糟糕.这里的保证是5×而不是3×.所以length(NFx(s)) <= 5 * length(s),正如你所看到的那样变得昂贵.

下面是等效表,显示在四种规范化形式的每一种下,有多少代码点扩展到多个代码点:

length NFC  == 2        70
length NFC  == 3         2
length NFC  == 4         0
length NFC  == 5         0

length NFKC == 2       686
length NFKC == 3       404
length NFKC == 4        53
length NFKC == 5        15

length NFD  == 2       762
length NFD  == 3       220
length NFD  == 4        36
length NFD  == 5         0

length NFKD == 2      1345
length NFKD == 3       642
length NFKD == 4       109
length NFKD == 5        16
Run Code Online (Sandbox Code Playgroud)

不是很了不起?有一段时间,Unicode希望尝试在模式匹配中构建规范等价.由于上述原因,他们知道它很昂贵,但是由于在一个字形单元内组合字符的必要规范重新排序,它花了一段时间才弄清楚它根本不可能.

出于这个原因,以及许多其他人,如果你想比较"不区分大小写"或"标准化不敏感"的东西,目前的建议是自己通过双方的转换运行它,然后比较结果.

例如,给定一个合适的==代码点代码点等价运算符

fc(a) == fc(b)
Run Code Online (Sandbox Code Playgroud)

类似地,对于=~模式匹配运算符(当然,它以传统方式工作,而不是像Java match那种不恰当地锚定东西的破碎方法):

fc(a) =~ fc(b)
Run Code Online (Sandbox Code Playgroud)

问题在于,您不能再在模式的特定部分打开或关闭不区分大小写,例如

/aaa(?i:xxx)bbb/
Run Code Online (Sandbox Code Playgroud)

并且只有xxx部分不区分大小写.

完整的外壳很难,但它可以(在大多数情况下)完成,就像Perl和Ruby已经证明的那样.但在你应该理解的地方,这也是非直观的(阅读:令人惊讶).你必须用括号中的角色类做特殊的事情,尤其是他们的否定,或者导致废话.

区域匹配

最后,为了使事情真正复杂化,在现实世界中,您必须做的不仅仅是案例映射和规范化中的一个或两个.在某些国家/地区,事情变得更加复杂.例如,在德语电话簿中,带有变音符号的元音与相同的基本元音后跟字母e完全相同.所以,在某些情况下müß会出现与MUESS大小写不相符的情况.

要做到这一切,你真的需要不只是完整的案例映射和规范化表,DUCET本身,默认Unicode整理元素表,甚至CLDR数据(参见参考书目):

#!/usr/bin/perl
use utf8;
use open qw(:utf8 :std);
use Unicode::Collate::Locale;

my $Collator = Unicode::Collate::Locale->new(
    locale        => "de__phonebook",
    level         => 1,
    normalization => undef,
);

my $full = "Ich müß Perl studieren.";
my $sub  = "MUESS";
if (my ($pos,$len) = $Collator->index($full, $sub)) {
    my $match = substr($full, $pos, $len);
    print "Found match of literal ‹$sub› at position $pos in ‹$full› as ‹$match›\n";
}
Run Code Online (Sandbox Code Playgroud)

如果你运行它,你会发现它确实有效:

在<IchmüßPerlstudieren.中找到第4位文字<MUESS>的匹配作为<müß>


精选参考书目

大多数例子是从4-4 个版编程的Perl其作者的一种许可.:)我在那里写了很多关于这种Unicode问题的东西,这些东西并不是Perl特有的,而是整体上对Unicode的一般性.

该单字符,让我喜欢收集这些统计数据(1)程序:

$ unichars 'length fc == 2' | wc -l
      88

$ unichars 'length NFKD == 4' | wc -l
     109

$ unichars '/ss/i'
U+00DF ? ß  LATIN SMALL LETTER SHARP S
U+1E9E ? ?  LATIN CAPITAL LETTER SHARP S
Run Code Online (Sandbox Code Playgroud)

是Brian Foy一直非常友好地为我维护的Unicode :: Tussle CPAN模块套件的一部分.


进一步阅读

也可以看看:

  • 哇,一如既往的令人印象深刻的答案。 (2认同)