Unicode正则表达式预组合/分解字符匹配问题及方案问询
Unicode正则表达式等价字符匹配问题
人类视觉和语义上会认为以下两个字符串完全相同:
>>> [b'a\xc3\xa9b'.decode('utf-8'), b'ae\xcc\x81b'.decode('utf-8')] ['aéb', 'aéb']
其中一个使用预组合字符(é对应U+00E9),另一个使用分解字符序列(e+U+0301重音符),但ICU和PCRE默认都不支持这类等价匹配:
ICU代码示例(匹配失败)
UParseError pe; UErrorCode status; URegularExpression *regexp = uregex_open(L"a\u0065\u0301b", 4, URegexpFlag::UREGEX_CASE_INSENSITIVE, &pe, &status); uregex_setText(regexp, L"a\u00E9b", 3, &status); UBool success = uregex_matches(regexp, 0, &status); // 匹配失败 uregex_close(regexp);
PCRE测试示例(匹配失败)
$ LC_ALL=en_US.UTF-8 pcretest <(printf '%s\n' $'/ae\xcc\x81b/i' $'a\xc3\xa9b') PCRE version 8.45 2021-06-15 /aéb/ aéb No match
待解决问题
- 如何让任意正则表达式的匹配结果符合人类认知(即支持Unicode等价字符匹配)?
- 能否改造现有库(如ICU、PCRE),使其正确处理任意Unicode正则表达式与语料?
- 是否存在可完整处理Unicode特性(含全大小写折叠等)的正则库?优先C++方案,其他语言方案也可。
解决方案
一、统一Unicode规范化形式(适配现有库)
核心思路:将正则表达式模板和待匹配文本统一转换为相同的Unicode规范化形式,再执行匹配。常用规范化形式有两种:
- NFC(预组合规范形式):把分解字符序列合并为单个预组合字符,适合大多数场景
- NFD(分解规范形式):把预组合字符拆分为基础字符+重音符号
针对现有库的具体操作
ICU:
- 新版本ICU支持
UREGEX_UNICODE_NORMALIZATION标志,开启后会自动对正则和文本做规范化匹配,无需手动转换 - 旧版本可使用
u_strToNFC()或u_strToNFD()函数,手动转换正则和文本后再执行匹配
- 新版本ICU支持
PCRE:
- PCRE2(PCRE的新版本)支持
PCRE2_UCP(Unicode属性支持)和PCRE2_UNICODE_NORMALIZATION标志,开启后自动处理规范化匹配 - 旧版PCRE(如示例中的8.45)无原生支持,需借助外部库(如libicu)手动规范化正则和文本后再匹配
- PCRE2(PCRE的新版本)支持
二、支持完整Unicode特性的正则库
C++方案
ICU正则表达式:
- 开启
UREGEX_UNICODE_NORMALIZATION+UREGEX_CASE_INSENSITIVE+UREGEX_FOLD_CASE标志后,可完整处理Unicode等价匹配、全大小写折叠等特性,是C++生态中最成熟的选择 - 改造后匹配成功的示例:
UParseError pe; UErrorCode status = U_ZERO_ERROR; URegularExpression *regexp = uregex_open(L"a\u0065\u0301b", 4, UREGEX_CASE_INSENSITIVE | UREGEX_UNICODE_NORMALIZATION, &pe, &status); uregex_setText(regexp, L"a\u00E9b", 3, &status); UBool success = uregex_matches(regexp, 0, &status); // 匹配成功 uregex_close(regexp);
- 开启
Boost.Regex(带Unicode支持):
- 使用
boost::u32regex配合Boost.Locale的normalize()函数,先将正则和文本统一转为NFC/NFD形式,再开启boost::regex::icase和boost::regex::unicode标志执行匹配
- 使用
其他语言方案
Python:
- Python 3.10+的
re模块支持re.UNICODE_NORMALIZATION标志,无需手动转换;旧版本可使用unicodedata.normalize('NFC', s)统一处理正则和文本后再匹配
- Python 3.10+的
JavaScript:
- 原生正则不直接支持,可借助第三方库如
regexpu处理Unicode规范化匹配,或结合Intl.Collator的等价性判断逻辑辅助实现
- 原生正则不直接支持,可借助第三方库如
关键注意事项
- 必须保证正则表达式和待匹配文本使用完全相同的规范化形式,否则仍会匹配失败
- 全大小写折叠需单独开启对应标志(如ICU的
UREGEX_FOLD_CASE),普通的CASE_INSENSITIVE无法覆盖复杂Unicode字符的大小写转换(如德语ß转为SS)
内容的提问来源于stack exchange,提问作者user541686
相关产品推荐
相关产品推荐

