快速入門
教學
工具和語言
範例
參考
書籍評論
正規表示式教學
簡介
目錄
特殊字元
不可列印字元
正規表示式引擎內部
字元類別
字元類別減法
字元類別交集
簡寫字元類別
錨點
字詞邊界
交替
選用項目
重複
群組和擷取
反向參照
反向參照,第 2 部分
命名群組
相對反向參照
分支重設群組
自由間距和註解
Unicode
模式修改器
原子群組
佔有量詞
前瞻和後顧
環顧,第 2 部分
將文字保持在比對之外
條件式
平衡群組
遞迴
子常式
無限遞迴
遞迴和量詞
遞迴和擷取
遞迴和反向參照
遞迴和回溯
POSIX 方括號表示式
零長度比對
繼續比對
本網站的其他內容
簡介
正規表示式快速入門
正規表示式教學
替換字串教學
應用程式和語言
正規表示式範例
正規表示式參考
替換字串參考
書籍評論
可列印 PDF
關於本網站
RSS 饋送和部落格
RegexBuddy—Better than a regular expression tutorial!

Unicode 正規表示式

Unicode 是一個字元集,旨在定義所有語言(無論是活的還是死的)的所有字元和字形。由於越來越多軟體需要支援多種語言,甚至只是「任何」語言,因此 Unicode 近年來已大幅普及。對不同語言使用不同的字元集對程式設計人員和使用者來說實在太過繁瑣。

很不幸的是,Unicode 在正規表示式方面帶來了自己的需求和陷阱。在本教學中討論的正規表示式風味中,JavaXML.NET 使用基於 Unicode 的正規表示式引擎。Perl 從版本 5.6 開始支援 Unicode。PCRE 可以選擇編譯時支援 Unicode。請注意,儘管 PCRE 名稱為「Perl 相容」,但對於 \p 權杖允許的內容,它的彈性遠低於 Perl。基於 PCRE 的 PHP preg 函式 在正規表示式附加 /u 選項時支援 Unicode。Ruby 從版本 1.9 開始在正規表示式中支援 Unicode 逸出和屬性。XRegExp 為 JavaScript 帶來 Unicode 屬性支援。

RegexBuddy 的 regex 引擎 從 2.0.0 版開始完全基於 Unicode。RegexBuddy 1.x.x 完全不支援 Unicode。 PowerGREP 從 3.0.0 版開始使用相同的 Unicode regex 引擎。較早的版本會在使用 8 位元(即非 Unicode)regex 引擎進行搜尋之前,將 Unicode 檔案轉換為 ANSI。 EditPad Pro 從 6.0.0 版開始支援 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 和 Just Great Software 應用程式中,很容易比對單一音節,無論是編碼為單一碼點,或使用組合符號編碼為多個碼點:只需使用 \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 時。

PerlPCREBooststd::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 SchemaXPath 沒有用於比對 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、JGsoft 引擎 和 XRegExp 3 允許您使用簡寫 \pL。簡寫僅適用於單一字母的 Unicode 屬性。\pLl 並非 等同於 \p{Ll}。它等同於 \p{L}l,後者會比對 Alàl 或任何 Unicode 字母後接一個字面 l

Perl、XRegExp 和 JGsoft 引擎也支援長寫 \p{Letter}。您可以在下方找到所有 Unicode 屬性的完整清單。您可以省略底線,或改用連字號或空白。

Unicode 文字系統

Unicode 標準將每個已分配的碼點(字元)放入一個文字系統中。文字系統是一組由特定人類書寫系統使用的碼點。有些文字系統(如 泰文)對應到單一的人類語言。其他文字系統(如 拉丁文)則橫跨多種語言。

有些語言由多個文字系統組成。沒有日文 Unicode 文字系統。相反地,Unicode 提供日文文件通常由其組成的 平假名片假名漢字拉丁文 文字系統。

一個特殊的文字系統是 通用 文字系統。此文字系統包含各種字元,這些字元是廣泛文字系統中常見的。它包含各種標點符號、空白和雜項符號。

所有已分配的 Unicode 碼點(由 \P{Cn} 匹配的)都是一個 Unicode 文字系統的一部分。所有未分配的 Unicode 碼點(由 \p{Cn} 匹配的)都不屬於任何 Unicode 文字系統。

JGsoft 引擎PerlPCREPHPRuby 1.9DelphiXRegExp 可以匹配 Unicode 文字系統。以下是清單

  1. \p{Common}
  2. \p{Arabic}
  3. \p{Armenian}
  4. \p{Bengali}
  5. \p{Bopomofo}
  6. \p{Braille}
  7. \p{Buhid}
  8. \p{Canadian_Aboriginal}
  9. \p{Cherokee}
  10. \p{Cyrillic}
  11. \p{Devanagari}
  12. \p{Ethiopic}
  13. \p{Georgian}
  14. \p{Greek}
  15. \p{Gujarati}
  16. \p{Gurmukhi}
  17. \p{Han}
  18. \p{Hangul}
  19. \p{Hanunoo}
  20. \p{Hebrew}
  21. \p{Hiragana}
  22. \p{Inherited}
  23. \p{Kannada}
  24. \p{Katakana}
  25. \p{Khmer}
  26. \p{Lao}
  27. \p{Latin}
  28. \p{Limbu}
  29. \p{Malayalam}
  30. \p{Mongolian}
  31. \p{Myanmar}
  32. \p{Ogham}
  33. \p{Oriya}
  34. \p{Runic}
  35. \p{Sinhala}
  36. \p{Syriac}
  37. \p{Tagalog}
  38. \p{Tagbanwa}
  39. \p{TaiLe}
  40. \p{Tamil}
  41. \p{Telugu}
  42. \p{Thaana}
  43. \p{Thai}
  44. \p{Tibetan}
  45. \p{Yi}

Perl 和 JGsoft 風格允許您使用 \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 標準包含日圓符號。您不應根據以下列出的任何區塊的名稱盲目使用它們。相反地,請查看它們實際匹配的字元範圍。像 RegexBuddy 這樣的工具在這方面可以提供很大的幫助。當您嘗試尋找所有貨幣符號時,Unicode 屬性 \p{Sc}\p{Currency_Symbol} 會比 Unicode 區塊 \p{InCurrency_Symbols} 更好。

  1. \p{InBasic_Latin}: U+0000–U+007F
  2. \p{InLatin-1_Supplement}: U+0080–U+00FF
  3. \p{InLatin_Extended-A}: U+0100–U+017F
  4. \p{InLatin_Extended-B}: U+0180–U+024F
  5. \p{InIPA_Extensions}: U+0250–U+02AF
  6. \p{InSpacing_Modifier_Letters}: U+02B0–U+02FF
  7. \p{InCombining_Diacritical_Marks}: U+0300–U+036F
  8. \p{InGreek_and_Coptic}: U+0370–U+03FF
  9. \p{InCyrillic}: U+0400–U+04FF
  10. \p{InCyrillic_Supplementary}: U+0500–U+052F
  11. \p{InArmenian}: U+0530–U+058F
  12. \p{InHebrew}: U+0590–U+05FF
  13. \p{InArabic}: U+0600–U+06FF
  14. \p{InSyriac}: U+0700–U+074F
  15. \p{InThaana}: U+0780–U+07BF
  16. \p{InDevanagari}: U+0900–U+097F
  17. \p{InBengali}: U+0980–U+09FF
  18. \p{InGurmukhi}: U+0A00–U+0A7F
  19. \p{InGujarati}: U+0A80–U+0AFF
  20. \p{InOriya}: U+0B00–U+0B7F
  21. \p{InTamil}: U+0B80–U+0BFF
  22. \p{InTelugu}: U+0C00–U+0C7F
  23. \p{InKannada}: U+0C80–U+0CFF
  24. \p{InMalayalam}: U+0D00–U+0D7F
  25. \p{InSinhala}: U+0D80–U+0DFF
  26. \p{InThai}: U+0E00–U+0E7F
  27. \p{InLao}: U+0E80–U+0EFF
  28. \p{InTibetan}: U+0F00–U+0FFF
  29. \p{InMyanmar}: U+1000–U+109F
  30. \p{InGeorgian}: U+10A0–U+10FF
  31. \p{InHangul_Jamo}: U+1100–U+11FF
  32. \p{InEthiopic}: U+1200–U+137F
  33. \p{InCherokee}: U+13A0–U+13FF
  34. \p{InUnified_Canadian_Aboriginal_Syllabics}: U+1400–U+167F
  35. \p{InOgham}: U+1680–U+169F
  36. \p{InRunic}: U+16A0–U+16FF
  37. \p{InTagalog}: U+1700–U+171F
  38. \p{InHanunoo}: U+1720–U+173F
  39. \p{InBuhid}: U+1740–U+175F
  40. \p{InTagbanwa}: U+1760–U+177F
  41. \p{InKhmer}: U+1780–U+17FF
  42. \p{InMongolian}: U+1800–U+18AF
  43. \p{InLimbu}: U+1900–U+194F
  44. \p{InTai_Le}: U+1950–U+197F
  45. \p{InKhmer_Symbols}: U+19E0–U+19FF
  46. \p{InPhonetic_Extensions}: U+1D00–U+1D7F
  47. \p{InLatin_Extended_Additional}: U+1E00–U+1EFF
  48. \p{InGreek_Extended}: U+1F00–U+1FFF
  49. \p{InGeneral_Punctuation}: U+2000–U+206F
  50. \p{InSuperscripts_and_Subscripts}: U+2070–U+209F
  51. \p{InCurrency_Symbols}: U+20A0–U+20CF
  52. \p{InCombining_Diacritical_Marks_for_Symbols}: U+20D0–U+20FF
  53. \p{InLetterlike_Symbols}: U+2100–U+214F
  54. \p{InNumber_Forms}: U+2150–U+218F
  55. \p{InArrows}: U+2190–U+21FF
  56. \p{InMathematical_Operators}: U+2200–U+22FF
  57. \p{InMiscellaneous_Technical}: U+2300–U+23FF
  58. \p{InControl_Pictures}: U+2400–U+243F
  59. \p{InOptical_Character_Recognition}: U+2440–U+245F
  60. \p{InEnclosed_Alphanumerics}: U+2460–U+24FF
  61. \p{InBox_Drawing}: U+2500–U+257F
  62. \p{InBlock_Elements}: U+2580–U+259F
  63. \p{InGeometric_Shapes}: U+25A0–U+25FF
  64. \p{InMiscellaneous_Symbols}: U+2600–U+26FF
  65. \p{InDingbats}: U+2700–U+27BF
  66. \p{InMiscellaneous_Mathematical_Symbols-A}: U+27C0–U+27EF
  67. \p{InSupplemental_Arrows-A}: U+27F0–U+27FF
  68. \p{InBraille_Patterns}: U+2800–U+28FF
  69. \p{InSupplemental_Arrows-B}: U+2900–U+297F
  70. \p{InMiscellaneous_Mathematical_Symbols-B}: U+2980–U+29FF
  71. \p{InSupplemental_Mathematical_Operators}: U+2A00–U+2AFF
  72. \p{InMiscellaneous_Symbols_and_Arrows}: U+2B00–U+2BFF
  73. \p{InCJK_Radicals_Supplement}: U+2E80–U+2EFF
  74. \p{InKangxi_Radicals}: U+2F00–U+2FDF
  75. \p{InIdeographic_Description_Characters}: U+2FF0–U+2FFF
  76. \p{InCJK_Symbols_and_Punctuation}: U+3000–U+303F
  77. \p{InHiragana}: U+3040–U+309F
  78. \p{InKatakana}: U+30A0–U+30FF
  79. \p{InBopomofo}: U+3100–U+312F
  80. \p{InHangul_Compatibility_Jamo}: U+3130–U+318F
  81. \p{InKanbun}: U+3190–U+319F
  82. \p{InBopomofo_Extended}: U+31A0–U+31BF
  83. \p{InKatakana_Phonetic_Extensions}: U+31F0–U+31FF
  84. \p{InEnclosed_CJK_Letters_and_Months}: U+3200–U+32FF
  85. \p{InCJK_Compatibility}: U+3300–U+33FF
  86. \p{InCJK_Unified_Ideographs_Extension_A}: U+3400–U+4DBF
  87. \p{InYijing_Hexagram_Symbols}: U+4DC0–U+4DFF
  88. \p{InCJK_Unified_Ideographs}: U+4E00–U+9FFF
  89. \p{InYi_Syllables}: U+A000–U+A48F
  90. \p{InYi_Radicals}: U+A490–U+A4CF
  91. \p{InHangul_Syllables}: U+AC00–U+D7AF
  92. \p{InHigh_Surrogates}: U+D800–U+DB7F
  93. \p{InHigh_Private_Use_Surrogates}: U+DB80–U+DBFF
  94. \p{InLow_Surrogates}: U+DC00–U+DFFF
  95. \p{InPrivate_Use_Area}: U+E000–U+F8FF
  96. \p{InCJK_Compatibility_Ideographs}: U+F900–U+FAFF
  97. \p{InAlphabetic_Presentation_Forms}: U+FB00–U+FB4F
  98. \p{InArabic_Presentation_Forms-A}: U+FB50–U+FDFF
  99. \p{InVariation_Selectors}: U+FE00–U+FE0F
  100. \p{InCombining_Half_Marks}: U+FE20–U+FE2F
  101. \p{InCJK_Compatibility_Forms}: U+FE30–U+FE4F
  102. \p{InSmall_Form_Variants}: U+FE50–U+FE6F
  103. \p{InArabic_Presentation_Forms-B}: U+FE70–U+FEFF
  104. \p{InHalfwidth_and_Fullwidth_Forms}: U+FF00–U+FFEF
  105. \p{InSpecials}: U+FFF0–U+FFFF

並非所有 Unicode 正規表示式引擎都使用相同的語法來比對 Unicode 區塊。 JavaRuby 2.0XRegExp 使用如上所列的 \p{InBlock} 語法。 .NETXML 則使用 \p{IsBlock}PerlJGsoft 風格 支援這兩種表示法。如果你使用的正規表示式引擎支援,我建議你使用「In」表示法。「In」只能用於 Unicode 區塊,而「Is」則可以根據你使用的正規表示式風格用於 Unicode 屬性和腳本。透過使用「In」,很明顯你比對的是區塊,而不是名稱相似的屬性或腳本。

在 .NET 和 XML 中,您必須省略底線,但保留區塊名稱中的連字號。例如,使用 \p{IsLatinExtended-A} 取代 \p{InLatin_Extended-A}。在 Java 中,您必須省略連字號。.NET 和 XML 也會區分名稱大小寫,而 Perl、Ruby 和 JGsoft 風格則不區分大小寫。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 插入檔案中。因此,如果您使用自己輸入的文字,您自己輸入的任何正規表示式都會以相同的方式相符。

最後,如果您使用 PowerGREP 搜尋使用傳統 Windows(通常稱為「ANSI」)或 ISO-8859 碼頁編碼的文字檔案,PowerGREP 始終使用一對一替換。由於所有 Windows 或 ISO-8859 碼頁都將重音字元編碼為單一碼點,因此在將檔案轉換為 Unicode 時,幾乎所有軟體都對每個字元使用單一 Unicode 碼點。