本网站的其他内容 |
简介 |
正则表达式快速入门 |
正则表达式教程 |
替换字符串教程 |
应用程序和语言 |
正则表达式范例 |
正则表达式参考 |
替换字符串参考 |
Unicode 正则表达式
Unicode 是一个字符集,旨在定义所有语言(无论是活的还是死的)的所有字符和字形。由于越来越多软件需要支持多种语言,甚至只是「任何」语言,因此 Unicode 近年来已大幅普及。对不同语言使用不同的字符集对编程人员和用户来说实在太过繁琐。
很不幸的是,Unicode 在正则表达式方面带来了自己的需求和陷阱。在本教学中讨论的正则表达式风格中,Java、XML 和 .NET 使用基于 Unicode 的正则表达式引擎。Perl 从版本 5.6 开始支持 Unicode。PCRE 可以选择编译时支持 Unicode。请注意,尽管 PCRE 名称为「Perl 兼容」,但对于 \p
权杖允许的内容,它的弹性远低于 Perl。基于 PCRE 的 PHP preg 函数 在正则表达式附加 /u 选项时支持 Unicode。Ruby 从版本 1.9 开始在正则表达式中支持 Unicode 逸出和属性。XRegExp 为 JavaScript 带来 Unicode 属性支持。
字符、码点和字比特或 Unicode 如何搞乱事情
大多数人会认为 à
是单一字符。很不幸的是,这取决于「字符」一词的意思,因此不一定会是这样。
本教程讨论的所有 Unicode regex 引擎将任何单一 Unicode 码点视为单一字符。当本教程告诉您 点号符合任何单一字符 时,这在 Unicode 术语中转换为「点号符合任何单一 Unicode 码点」。在 Unicode 中,à
可以编码为两个码点:U+0061 (a) 后接 U+0300(重音符号)。在这种情况下,套用于 à
的 .
将符合没有重音符号的 a
。 ^.$
将无法符合,因为字符串包含两个码点。 ^..$
符合 à
。
Unicode 码点 U+0300(重音符号)是组合标记。任何不是组合标记的码点都可以后接任意数量的组合标记。这个串行(例如上述的 U+0061 U+0300)在屏幕上显示为单一字比特。
很不幸的是,à
也可以使用单一 Unicode 码点 U+00E0(带重音符号的 a)编码。这种二元性的原因是,许多历史字符集将「带重音符号的 a」编码为单一字符。Unicode 的设计者认为,除了 Unicode 分隔标记和基本字母的方式(这使得传统字符集不支持的任意组合成为可能)之外,与流行的传统字符集进行一对一对应会很有用。
如何符合单一 Unicode 字比特
在 Perl、PCRE、PHP、Boost、Ruby 2.0 和 Java 9 中,很容易比对单一音节,无论是编码为单一码点,或使用组合符号编码为多个码点:只需使用 \X
。您可以将 \X
视为 点号 的 Unicode 版本。不过,有一个差异:\X
始终会比对换行字符,而点号则不会比对换行字符,除非您激活 点号比对换行字符比对模式。
.NET、Java 8 及更早版本,以及 Ruby 1.9 中,您可以使用 \P{M}\p{M}*+
或 (?>\P{M}\p{M}*)
作为相当接近的替代方案。若要比对任意数量的音节,请使用 (?>\P{M}\p{M}*)+
作为 \X+
的替代方案。
比对特定码点
若要比对特定 Unicode 码点,请使用 \uFFFF
,其中 FFFF 是您要比对的码点的十六进位数字。您必须始终指定 4 个十六进位数字,例如 \u00E0
比对 à
,但仅在编码为单一码点 U+00E0 时。
Perl、PCRE、Boost 和 std::regex 不支持 \uFFFF
语法。它们改用 \x{FFFF}
。您可以在大括弧中的十六进位数字中省略前导零。由于 \x
本身不是有效的正则表达式标记,因此 \x{1234}
绝不会被误认为比对 \x
1234 次。它始终比对 Unicode 码点 U+1234。 \x{1234}{5678}
将尝试比对码点 U+1234 正好 5678 次。
在 Java 中,正则表达式标记 \uFFFF
仅比对指定的码点,即使您已打开规范等价性。不过,相同的语法 \uFFFF
也用于在 Java 原代码中将 Unicode 字符插入字符串常数。 Pattern.compile("\u00E0")
将比对 à
的单一码点和双码点编码,而 Pattern.compile("\\u00E0")
仅比对单一码点版本。请记住,在将正则表达式写为 Java 字符串常数时,反斜线必须加上转义字符。前一个 Java 代码编译正则表达式 à
,而后一个则编译 \u00E0
。根据您的操作,差异可能很显著。
JavaScript 通过其 RegExp 类别不提供任何 Unicode 支持,但支持 \uFFFF
,作为其字符串语法的一部分,用于比对单一 Unicode 码点。
XML Schema 和 XPath 没有用于比对 Unicode 编码点的正则表达式记号。不过,您可以轻松地使用 XML 实体,例如 
,将文本编码点插入正则表达式中。
Unicode 类别
除了复杂性之外,Unicode 也带来了新的可能性。其中之一是每个 Unicode 字符都属于某个类别。您可以使用 \p{L}
比对属于「字母」类别的单一字符。您可以使用 \P{L}
比对不属于该类别的单一字符。
同样地,「字符」实际上是指「Unicode 编码点」。\p{L}
比对「字母」类别中的单一编码点。如果您的输入字符串是编码为 U+0061 U+0300 的 à
,它会比对没有重音符号的 a
。如果输入字符串是编码为 U+00E0 的 à
,它会比对带有重音符号的 à
。原因是编码点 U+0061 (a) 和 U+00E0 (à) 都属于「字母」类别,而 U+0300 属于「标记」类别。
现在您应该了解为什么 \P{M}\p{M}*+
等于 \X
。 \P{M}
比对不是组合标记的编码点,而 \p{M}*+
比对 零个或多个 是组合标记的编码点。若要比对包含任何变音符号的字母,请使用 \p{L}\p{M}*+
。这个最后的正则表达式将永远比对 à
,无论它是如何编码的。所有格量词 可确保回溯不会导致 \P{M}\p{M}*+
比对没有后接组合标记的非标记,而 \X
永远不会这样做。
PCRE、PHP 和 .NET 在检查 \p
记号的大括号之间的部分时,会区分大小写。\p{Zs}
会比对任何类型的空白字符,而 \p{zs}
会掷回错误。本教程中描述的所有其他正则表达式引擎都会在这两种情况下比对空白,忽略大括号之间类别的大小写。尽管如此,我建议您养成使用与我在以下属性清单中相同的大小写组合的习惯。这将使您的正则表达式适用于所有 Unicode 正则表达式引擎。
除了标准符号 \p{L}
之外,Java、Perl、PCRE 和 XRegExp 3 允许您使用简写 \pL
。简写仅适用於单一字母的 Unicode 属性。\pLl
并非 等同于 \p{Ll}
。它等同于 \p{L}l
,后者会比对 Al
或 àl
或任何 Unicode 字母后接一个字面 l
。
Perl 和 XRegExp 也支持长写 \p{Letter}
。您可以在下方找到所有 Unicode 属性的完整清单。您可以省略底线,或改用连字号或空白。
\p{L}
或\p{Letter}
:任何语言的任何种类字母。\p{Ll}
或\p{Lowercase_Letter}
:小写字母,有对应的大写变体。\p{Lu}
或\p{Uppercase_Letter}
:大写字母,有对应的小写变体。\p{Lt}
或\p{Titlecase_Letter}
:出现在单字开头的字母,而该单字只有第一个字母大写。\p{L&}
或\p{Cased_Letter}
:存在小写和大写变体的字母(Ll、Lu 和 Lt 的组合)。\p{Lm}
或\p{Modifier_Letter}
:用作字母的特殊字符。\p{Lo}
或\p{Other_Letter}
:没有小写和大写变体的字母或表意文本。
\p{M}
或\p{Mark}
:用于与另一个字符组合的字符(例如重音、变音符号、包围框等)。\p{Mn}
或\p{Non_Spacing_Mark}
:用来与其他字符结合,且不占额外空间的字符(例如重音符号、变音符号等)。\p{Mc}
或\p{Spacing_Combining_Mark}
:用来与其他字符结合,且会占额外空间的字符(许多东方语言中的元音符号)。\p{Me}
或\p{Enclosing_Mark}
:将其所结合的字符包围起来的字符(圆圈、方块、键帽等)。
\p{Z}
或\p{Separator}
:任何类型的空白或隐藏分隔符号。\p{Zs}
或\p{Space_Separator}
:不可见的空白字符,但会占用空间。\p{Zl}
或\p{Line_Separator}
:行分隔字符 U+2028。\p{Zp}
或\p{Paragraph_Separator}
:段落分隔字符 U+2029。
\p{S}
或\p{Symbol}
:数学符号、货币符号、装饰符号、方框绘制字符等。\p{Sm}
或\p{Math_Symbol}
:任何数学符号。\p{Sc}
或\p{Currency_Symbol}
:任何货币符号。\p{Sk}
或\p{Modifier_Symbol}
:作为独立全角字符的组合字符(符号)。\p{So}
或\p{Other_Symbol}
:各种非数学符号、货币符号或组合字符的符号。
\p{N}
或\p{Number}
:任何文本系统中的任何类型的数字字符。\p{Nd}
或\p{Decimal_Digit_Number}
:任何文本系统中(除表意文本系统外)的数字 0 到 9。\p{Nl}
或\p{Letter_Number}
:看起来像字母的数字,例如罗马数字。\p{No}
或\p{Other_Number}
:上标或下标数字,或不是数字 0–9 的数字(不包括来自表意文本脚本的数字)。
\p{P}
或\p{Punctuation}
:任何类型的标点符号字符。\p{Pd}
或\p{Dash_Punctuation}
:任何类型的连字号或破折号。\p{Ps}
或\p{Open_Punctuation}
:任何类型的打开括号。\p{Pe}
或\p{Close_Punctuation}
:任何类型的关闭括号。\p{Pi}
或\p{Initial_Punctuation}
:任何类型的打开引号。\p{Pf}
或\p{Final_Punctuation}
:任何类型的关闭引号。\p{Pc}
或\p{Connector_Punctuation}
:连接字词的标点符号字符,例如底线。\p{Po}
或\p{Other_Punctuation}
:任何不是破折号、括号、引号或连接字符的标点符号字符。
\p{C}
或\p{Other}
:不可见的控制字符和未使用的码点。\p{Cc}
或\p{Control}
:ASCII 或 Latin-1 控制字符:0x00–0x1F 和 0x7F–0x9F。\p{Cf}
或\p{Format}
:不可见的格式化指示符。\p{Co}
或\p{Private_Use}
:任何保留供私人使用的码点。\p{Cs}
或\p{Surrogate}
:UTF-16 编码中的代理对的一半。\p{Cn}
或\p{Unassigned}
:尚未指派任何字符的任何码点。
Unicode 文本系统
Unicode 标准将每个已分配的码点(字符)放入一个文本系统中。文本系统是一组由特定人类书写系统使用的码点。有些文本系统(如 泰文
)对应到单一的人类语言。其他文本系统(如 拉丁文
)则横跨多种语言。
有些语言由多个文本系统组成。没有日文 Unicode 文本系统。相反地,Unicode 提供日文文档通常由其组成的 平假名
、片假名
、汉字
和 拉丁文
文本系统。
一个特殊的文本系统是 通用
文本系统。此文本系统包含各种字符,这些字符是广泛文本系统中常见的。它包含各种标点符号、空白和杂项符号。
所有已分配的 Unicode 码点(由 \P{Cn}
匹配的)都是一个 Unicode 文本系统的一部分。所有未分配的 Unicode 码点(由 \p{Cn}
匹配的)都不属于任何 Unicode 文本系统。
Perl、PCRE、PHP、Ruby 1.9、Delphi 和 XRegExp 可以匹配 Unicode 文本系统。以下是清单
\p{Common}
\p{Arabic}
\p{Armenian}
\p{Bengali}
\p{Bopomofo}
\p{Braille}
\p{Buhid}
\p{Canadian_Aboriginal}
\p{Cherokee}
\p{Cyrillic}
\p{Devanagari}
\p{Ethiopic}
\p{Georgian}
\p{Greek}
\p{Gujarati}
\p{Gurmukhi}
\p{Han}
\p{Hangul}
\p{Hanunoo}
\p{Hebrew}
\p{Hiragana}
\p{Inherited}
\p{Kannada}
\p{Katakana}
\p{Khmer}
\p{Lao}
\p{Latin}
\p{Limbu}
\p{Malayalam}
\p{Mongolian}
\p{Myanmar}
\p{Ogham}
\p{Oriya}
\p{Runic}
\p{Sinhala}
\p{Syriac}
\p{Tagalog}
\p{Tagbanwa}
\p{TaiLe}
\p{Tamil}
\p{Telugu}
\p{Thaana}
\p{Thai}
\p{Tibetan}
\p{Yi}
Perl 允许您使用 \p{IsLatin}
取代 \p{Latin}
。如下一节所述,「Is」语法对于区分文本系统和区块很有用。PCRE、PHP 和 XRegExp 不支持「Is」前缀。
Java 7 支持 Unicode 码。与其他版本不同,Java 7 需要「Is」前缀。
Unicode 区块
Unicode 标准将 Unicode 字符对应表分成不同的区块或码点范围。每个区块用于定义特定码表的字符,例如「藏文」,或属于特定群组,例如「点字模式」。大多数区块包含未指派码点,保留供未来扩充 Unicode 标准使用。
请注意,Unicode 区块与码表并非 100% 对应。区块与码表之间的本质区别在于区块是连续的单一码点范围,如下所列。码表由取自整个 Unicode 字符对应表的字符组成。区块可能包含未指派的码点(即与 \p{Cn}
匹配的码点)。码表绝不包含未指派的码点。一般来说,如果您不确定要使用 Unicode 码表或 Unicode 区块,请使用码表。
例如,货币区块不包含美元和日圆符号。这些符号出现在 Basic_Latin 和 Latin-1_Supplement 区块中,即使它们都是货币符号,而且日圆符号不是拉丁字符。这是出于历史原因,因为 ASCII 标准包含美元符号,而 ISO-8859 标准包含日圆符号。您不应根据以下列出的任何区块的名称盲目使用它们。相反地,请查看它们实际匹配的字符范围。当您尝试寻找所有货币符号时,Unicode 属性 \p{Sc}
或 \p{Currency_Symbol}
会比 Unicode 区块 \p{InCurrency_Symbols}
更好。
\p{InBasic_Latin}
: U+0000–U+007F\p{InLatin-1_Supplement}
: U+0080–U+00FF\p{InLatin_Extended-A}
: U+0100–U+017F\p{InLatin_Extended-B}
: U+0180–U+024F\p{InIPA_Extensions}
: U+0250–U+02AF\p{InSpacing_Modifier_Letters}
: U+02B0–U+02FF\p{InCombining_Diacritical_Marks}
: U+0300–U+036F\p{InGreek_and_Coptic}
: U+0370–U+03FF\p{InCyrillic}
: U+0400–U+04FF\p{InCyrillic_Supplementary}
: U+0500–U+052F\p{InArmenian}
: U+0530–U+058F\p{InHebrew}
: U+0590–U+05FF\p{InArabic}
: U+0600–U+06FF\p{InSyriac}
: U+0700–U+074F\p{InThaana}
: U+0780–U+07BF\p{InDevanagari}
: U+0900–U+097F\p{InBengali}
: U+0980–U+09FF\p{InGurmukhi}
: U+0A00–U+0A7F\p{InGujarati}
: U+0A80–U+0AFF\p{InOriya}
: U+0B00–U+0B7F\p{InTamil}
: U+0B80–U+0BFF\p{InTelugu}
: U+0C00–U+0C7F\p{InKannada}
: U+0C80–U+0CFF\p{InMalayalam}
: U+0D00–U+0D7F\p{InSinhala}
: U+0D80–U+0DFF\p{InThai}
: U+0E00–U+0E7F\p{InLao}
: U+0E80–U+0EFF\p{InTibetan}
: U+0F00–U+0FFF\p{InMyanmar}
: U+1000–U+109F\p{InGeorgian}
: U+10A0–U+10FF\p{InHangul_Jamo}
: U+1100–U+11FF\p{InEthiopic}
: U+1200–U+137F\p{InCherokee}
: U+13A0–U+13FF\p{InUnified_Canadian_Aboriginal_Syllabics}
: U+1400–U+167F\p{InOgham}
: U+1680–U+169F\p{InRunic}
: U+16A0–U+16FF\p{InTagalog}
: U+1700–U+171F\p{InHanunoo}
: U+1720–U+173F\p{InBuhid}
: U+1740–U+175F\p{InTagbanwa}
: U+1760–U+177F\p{InKhmer}
: U+1780–U+17FF\p{InMongolian}
: U+1800–U+18AF\p{InLimbu}
: U+1900–U+194F\p{InTai_Le}
: U+1950–U+197F\p{InKhmer_Symbols}
: U+19E0–U+19FF\p{InPhonetic_Extensions}
: U+1D00–U+1D7F\p{InLatin_Extended_Additional}
: U+1E00–U+1EFF\p{InGreek_Extended}
: U+1F00–U+1FFF\p{InGeneral_Punctuation}
: U+2000–U+206F\p{InSuperscripts_and_Subscripts}
: U+2070–U+209F\p{InCurrency_Symbols}
: U+20A0–U+20CF\p{InCombining_Diacritical_Marks_for_Symbols}
: U+20D0–U+20FF\p{InLetterlike_Symbols}
: U+2100–U+214F\p{InNumber_Forms}
: U+2150–U+218F\p{InArrows}
: U+2190–U+21FF\p{InMathematical_Operators}
: U+2200–U+22FF\p{InMiscellaneous_Technical}
: U+2300–U+23FF\p{InControl_Pictures}
: U+2400–U+243F\p{InOptical_Character_Recognition}
: U+2440–U+245F\p{InEnclosed_Alphanumerics}
: U+2460–U+24FF\p{InBox_Drawing}
: U+2500–U+257F\p{InBlock_Elements}
: U+2580–U+259F\p{InGeometric_Shapes}
: U+25A0–U+25FF\p{InMiscellaneous_Symbols}
: U+2600–U+26FF\p{InDingbats}
: U+2700–U+27BF\p{InMiscellaneous_Mathematical_Symbols-A}
: U+27C0–U+27EF\p{InSupplemental_Arrows-A}
: U+27F0–U+27FF\p{InBraille_Patterns}
: U+2800–U+28FF\p{InSupplemental_Arrows-B}
: U+2900–U+297F\p{InMiscellaneous_Mathematical_Symbols-B}
: U+2980–U+29FF\p{InSupplemental_Mathematical_Operators}
: U+2A00–U+2AFF\p{InMiscellaneous_Symbols_and_Arrows}
: U+2B00–U+2BFF\p{InCJK_Radicals_Supplement}
: U+2E80–U+2EFF\p{InKangxi_Radicals}
: U+2F00–U+2FDF\p{InIdeographic_Description_Characters}
: U+2FF0–U+2FFF\p{InCJK_Symbols_and_Punctuation}
: U+3000–U+303F\p{InHiragana}
: U+3040–U+309F\p{InKatakana}
: U+30A0–U+30FF\p{InBopomofo}
: U+3100–U+312F\p{InHangul_Compatibility_Jamo}
: U+3130–U+318F\p{InKanbun}
: U+3190–U+319F\p{InBopomofo_Extended}
: U+31A0–U+31BF\p{InKatakana_Phonetic_Extensions}
: U+31F0–U+31FF\p{InEnclosed_CJK_Letters_and_Months}
: U+3200–U+32FF\p{InCJK_Compatibility}
: U+3300–U+33FF\p{InCJK_Unified_Ideographs_Extension_A}
: U+3400–U+4DBF\p{InYijing_Hexagram_Symbols}
: U+4DC0–U+4DFF\p{InCJK_Unified_Ideographs}
: U+4E00–U+9FFF\p{InYi_Syllables}
: U+A000–U+A48F\p{InYi_Radicals}
: U+A490–U+A4CF\p{InHangul_Syllables}
: U+AC00–U+D7AF\p{InHigh_Surrogates}
: U+D800–U+DB7F\p{InHigh_Private_Use_Surrogates}
: U+DB80–U+DBFF\p{InLow_Surrogates}
: U+DC00–U+DFFF\p{InPrivate_Use_Area}
: U+E000–U+F8FF\p{InCJK_Compatibility_Ideographs}
: U+F900–U+FAFF\p{InAlphabetic_Presentation_Forms}
: U+FB00–U+FB4F\p{InArabic_Presentation_Forms-A}
: U+FB50–U+FDFF\p{InVariation_Selectors}
: U+FE00–U+FE0F\p{InCombining_Half_Marks}
: U+FE20–U+FE2F\p{InCJK_Compatibility_Forms}
: U+FE30–U+FE4F\p{InSmall_Form_Variants}
: U+FE50–U+FE6F\p{InArabic_Presentation_Forms-B}
: U+FE70–U+FEFF\p{InHalfwidth_and_Fullwidth_Forms}
: U+FF00–U+FFEF\p{InSpecials}
: U+FFF0–U+FFFF
并非所有 Unicode 正则表达式引擎都使用相同的语法来比对 Unicode 区块。 Java、Ruby 2.0 和 XRegExp 使用如上所列的 \p{InBlock}
语法。 .NET 和 XML 则使用 \p{IsBlock}
。 Perl 支持这两种表示法。如果你使用的正则表达式引擎支持,我建议你使用「In」表示法。「In」只能用于 Unicode 区块,而「Is」则可以根据你使用的正则表达式风格用于 Unicode 属性和脚本。通过使用「In」,很明显你比对的是区块,而不是名称相似的属性或脚本。
在 .NET 和 XML 中,您必须省略底线,但保留区块名称中的连字号。例如,使用 \p{IsLatinExtended-A}
取代 \p{InLatin_Extended-A}
。在 Java 中,您必须省略连字号。.NET 和 XML 也会区分名称大小写,而 Perl 和 Ruby 则不区分大小写。Java 4 区分大小写。Java 5 和更新版本对「Is」前缀区分大小写,但对区块名称本身不区分大小写。
所有正则表达式引擎中区块的实际名称都相同。区块名称在 Unicode 标准中定义。PCRE 和 PHP 不支持 Unicode 区块,即使它们支持 Unicode 脚本。
您需要担心不同的编码吗?
虽然您应该永远记住重音字符可以用不同方式编码所造成的陷阱,但您不必总是担心它们。如果您知道您的输入字符串和正则表达式使用相同的样式,那么您根本不必担心。此进程称为 Unicode 范式。所有具有原生 Unicode 支持的编程语言,例如 Java、C# 和 VB.NET,都有用于范式字符串的函数库常式。如果您在尝试比对之前范式主旨和正则表达式,就不会有任何不一致的情况。
如果您使用 Java,您可以将 CANON_EQ 旗标传递为 Pattern.compile() 的第二个参数。这会告诉 Java 正则表达式引擎将正规等价字符视为相同。正则表达式 à
编码为 U+00E0 与编码为 U+0061 U+0300 的 à
相符,反之亦然。目前没有其他正则表达式引擎在比对时支持正规等价。
如果您在键盘上输入 à 键,我所知道的文本处理器都会将码点 U+00E0 插入文件中。因此,如果您使用自己输入的文本,您自己输入的任何正则表达式都会以相同的方式相符。
由于所有 Windows 或 ISO-8859 码页都将重音字符编码为单一码点,因此在将文件转换为 Unicode 时,几乎所有软件都对每个字符使用单一 Unicode 码点。