未记录的Java正则表达式字符类:\p{C}

41

我在一个Java项目中发现了一个有趣的正则表达式:"[\\p{C}&&\\S]"

我知道&&表示“集合交集”,\S表示“非空格字符”,但\p{C}是什么,是否可以使用?

java.util.regex.Pattern文档没有提到它。列表中唯一类似的类是\p{Cntrl},但它们的行为不同:它们都匹配控制字符,但\p{C}会对U+FFFF以上的Unicode字符进行两次匹配,如PILE OF POO

public class StrangePattern {
    public static void main(String[] argv) {

        // As far as I can tell, this is the simplest way to create a String
        // with code points above U+FFFF.
        String poo = new String(Character.toChars(0x1F4A9));

        System.out.println(poo);  // prints ``
        System.out.println(poo.replaceAll("\\p{C}", "?"));  // prints `??`
        System.out.println(poo.replaceAll("\\p{Cntrl}", "?"));  // prints ``
    }
}

我在任何地方找到的唯一提及是这里:

\p{C}或\p{Other}: 不可见控制字符和未使用的代码点。

然而,在Java中似乎不存在\p{Other},并且匹配的代码点也不是未使用的。

我的Java版本信息:

$ java -version
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)

额外问题: 原始模式 "[\\p{C}&&\\S]" 的可能意图是什么? 它出现在一种在将字符串发送到电子邮件之前验证字符串的方法中: 如果匹配该模式,则会引发带有消息“无效字符串”的异常。


3
根据http://www.unicode.org/reports/tr44/的重新表述,`p{C}`可以是控制字符、格式控制字符、代理码位、专用字符、保留未分配的码位或非字符。由于“代理码位”的存在(以及Java对UTF-16的实现稍有瑕疵),所以一堆便便符号会匹配其中的两个。 - Dawood ibn Kareem
你的代码有点误导人。实际上,正则表达式只匹配低代理项(第二个 char),但是你的控制台可能会将不成对的高代理项显示为 ?。使用不同的字符作为替换来查看这一点。 - Marcono1234
4个回答

26

在Unicode支持的Pattern文档中,我们发现以下内容:

该类符合Unicode技术标准#18:Unicode正则表达式的一级规范,以及RL2.1规范等效性。

...

类别可以用可选前缀Is:来指定:\p{L}和\p{IsL}都表示Unicode字母类别。与脚本和块相同,也可以使用关键字general_category(或其简写gc)来指定类别,如general_category=Lu或gc=Lu。

支持的类别是由Character类指定的Unicode标准版本中的类别。类别名称是标准中定义的,包括规范和说明性的名称。

Unicode技术标准#18中,我们发现C被定义为匹配任何其他General_Category值,并且支持此功能是符合Level 1一致性要求的一部分。Java实现了\p{C},因为它声称符合UTS#18的Level 1要求。

这个程序可能应该支持\p{Other},但显然它没有。

更糟糕的是,它违反了RL1.7,这是一级符合性所需的,要求匹配发生在代码点而不是代码单元:

为了满足此要求,实现应处理完整范围的Unicode代码点,包括从U+FFFF到U+10FFFF的值。 特别是,在使用UTF-16时,由前导代理和尾随代理组成的序列应被处理为匹配中的单个代码点。

您的测试字符串中不应该有\p{C}的匹配,因为您的测试字符串应该作为单个带有General_Category=So(其他符号)的表情符号代码点进行匹配,而不是作为两个代理进行匹配。


@Hulk:那个标志是针对不同的字符类别集,特别是在“预定义字符类别”和“POSIX字符类别(仅限US-ASCII)”下列出的那些。\p{C}不属于其中之一。 - user2357112
相关的错误报告:JDK-8179668JDK-8029966 - Marcono1234

7
根据https://regex101.com/,\p{C}匹配不可见的控制字符和未使用的代码点(由于Java字符串,因此必须转义\,因此字符串\\p{C}是regex \p{C})。我猜这是一个“黑客字符串检查”,因为在有效(填充字符的)字符串内通常不应出现\p{C},但作者应该留下注释,因为他们想要检查的内容和实际想要检查的通常是两回事。

2
除有效的两个字母的Unicode类别代码或以Unicode类别代码开头的单个字母外,任何其他内容都是非法的,因为Java仅支持Unicode类别的单个字母和两个字母的缩写。这就是为什么\p {Other}在此处无效。

\p{C}在Unicode字符U+FFFF以上(如POO OF PILE)上匹配两次

正确。Java在内部使用UTF-16编码表示Unicode字符,并将其编码为两个16位代码单元(0xD83D 0xDCA9),称为代理对(高代理)。由于\p{C}将每半个分别匹配,
你会在结果集中看到两个匹配项。
原始模式 [\\p{C}&&\\S] 的意图可能是什么?
我没有看到很充分的理由,但似乎开发人员担心类别为“其他”(例如避免在电子邮件主题中出现垃圾{{link1:goomojies}}),因此简单地尝试阻止它们。

你所标记为引用的前两个语句的来源是什么?这很有趣,因为它似乎与当前得票最高的答案相矛盾。https://dev59.com/C1cP5IYBdhLWcg3wqLzA#44034552 - Hulk

1
关于奖励问题:表达式[\\p{C}&&\\S]在Java中查找控制字符,但排除制表符或换行符等空格字符。这些字符在常规邮件中没有价值,因此过滤它们是个好主意(或者像本例一样,将电子邮件内容声明为有误)。请注意,双反斜杠(\\)仅在Java处理中需要转义表达式。正确的正则表达式应该是:[\p{C}&&\S]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接