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

这听起来像个笑话,但我可以certificate这一点。

假设:

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

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

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

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

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

问题:

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

案例折叠

答案是否定的,dot 不会ss情况不敏感,尽管原因有些深奥。

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

Unicode中有两种forms的大小写映射。 有一个简单的案例映射 ,其中一个代码点只映射到另一个代码点。 因此,如果length(s) == 1 ,则保证length(fc(s)) == 1 ,其中fc是Unicode折叠图。 但它也适用于uctclc案例映射。

问题在于,您没有获得分析某些类型的真实文本的好结果,那么您可以制作这些精确的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 

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

但这就是问题:不区分大小写的匹配不执行传递操作。 换句话说,如果. 匹配ß并且在不区分大小写的匹配下, ßSS匹配,这并不意味着通过传递性. SS不敏感地匹配SS 。 尽管比我更聪明的人已经深入思考过这个问题,但这种方式并没有这样做。

但是,这两个代码点:

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

确实不区分大小写,不仅相互匹配,而且在完整的大小写映射下也匹配SSSssSss 。 他们只是在简单的案例映射下不这样做。

Unicode确实对此做出了一些保证。 一个是如果length(s) == n ,那个length(fn(s)) <= 3*n其中fn是四个案例图中的任何一个: lcfcuctc

论规范化

如果你认为这很糟糕,那么当你考虑规范化forms时,它实际上会变得更糟糕。 这里的保证是5×而不是3×。 所以length(NFx(s)) <= 5 * length(s) ,如你所见,价格昂贵。

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

 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 

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

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

例如,给定一个合适的== code-point-by-code-point等价运算符

 fc(a) == fc(b) 

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

 fc(a) =~ fc(b) 

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

 /aaa(?i:xxx)bbb/ 

只有xxx部分不区分大小写。

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

区域匹配

最后,为了使事情真正复杂化,在现实世界中,您必须做的不仅仅是案例映射和规范化中的一个或两个。 在某些国家/地区,事情变得更加复杂。 例如,在德语电话簿中,带有变音符号的元音与相同的基本元音后跟字母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"; } 

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

匹配


精选参考书目

这些示例中的大部分都是根据作者的许可获得的 4版Programming Perl 。 :)我在那里写了很多关于这种Unicode问题的内容,这些内容并不是Perl特有的,而是整体上对Unicode的一般性。

unichars (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 

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


进一步阅读

也可以看看:

  • Unicode案例折叠表

  • Unicode标准附件#15: Unicode规范化表单

  • Unicode技术标准#18: Unicode正则表达式

  • Unicode技术标准#10: Unicode整理算法

  • CLDR - Unicode公共区域设置数据存储库

正如maaartinus在他的评论中指出的那样,Java提供(至少在理论上)Unicode支持不区分大小写的reg-exp匹配。 Java API文档中的措辞是“以与Unicode标准一致的方式”进行匹配。 但问题是,Unicode标准为案例转换和不区分大小写的匹配定义了不同级别的支持,API文档没有指定Java语言支持的级别。

虽然没有记录,但至少在Oracle的Java VM中,reg-exp实现仅限于所谓的简单不区分大小写的匹配。 与示例数据相关的限制因素是,如果案例折叠(转换)导致相同数量的字符并且集合(例如“。”)限制为仅匹配输入中的一个字符,则匹配算法仅按预期工作串。 第一个限制甚至导致“ß”与“SS”不匹配,正如您可能已经预料到的那样。

要获得对字符串文字之间完全不区分大小写匹配的支持,可以在ICU4J库中使用reg-exp实现,以便至少“ß”和“SS”匹配。 但是,AFAIK没有针对Java的reg-exp实现,完全支持组,集和通配符。

Interesting Posts