假设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")
显然
因此,假设正则表达式中的第一个点代表ffi
而第二个代表ß
,则正则表达式必须匹配“FFISS”,并且因为不区分大小写也是“FfIsS”。
我真的希望有一些错误,否则正则表达式会变得非常不可用。
问题:
- 我的“证据”有什么问题?
- 如果我的第二个假设不成立,那么“不区分大小写”究竟意味着什么?
案例折叠
答案是否定的,dot 不会对ss
情况不敏感,尽管原因有些深奥。
然而,一些最了解这类事物的人经常提出你的谜题,因为他们也觉得它会导致矛盾。
Unicode中有两种forms的大小写映射。 有一个简单的案例映射 ,其中一个代码点只映射到另一个代码点。 因此,如果length(s) == 1
,则保证length(fc(s)) == 1
,其中fc
是Unicode折叠图。 但它也适用于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
在完整的shell中,不是Java正则表达式使用的简单shell,你可以确实得到像tschüß
和TSCHÜSS
这样的东西来匹配,即使它们的长度不相等。 Perl和Ruby在进行不区分大小写的比较时使用完整的大小写映射。 如果你不小心,这会在否定的角色类中导致奇怪的悖论。
但这就是问题:不区分大小写的匹配不执行传递操作。 换句话说,如果.
匹配ß
并且在不区分大小写的匹配下, ß
与SS
匹配,这并不意味着通过传递性.
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
是四个案例图中的任何一个: lc
, fc
, uc
和tc
。
论规范化
如果你认为这很糟糕,那么当你考虑规范化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实现,完全支持组,集和通配符。