正則表達式(三):Unicode諸問題(下)
上一篇文章我們介紹了和Unicode有關的匹配問題,這篇文章我們主要講述一下Unicode編碼本身的特性,以便更好地運用正則表達式解決與Unicode相關的問題。
Unicode Code Point
Unicode字符多種多樣,除去ascii中的字母、數字、標點和中文字符,還包括其它多種語言和多種符號,有些符號甚至很難打出來(比如表示商標注冊的?),這時候該如何表示呢?再說遠一點,如果我們想用一個字符組匹配所有中文字符,能不能像『[a-z]』那樣呢?
所幸,每一個Unicode字符都對應自己的Unicode編碼,也就是Unicode編碼表中的一個代碼點(Code Point),所以在正則表達式中的Unicode字符往往采用Unicode代碼點來指定。
一般來說,指定代碼點的形式有3種:『\uxxxx』、『\u{xxxx}』、『\x{xxxx}』(其中的xxxx為編碼的值,\u之后必須有4位16進制數字)。.NET、Java、JavaScript和Python使用第一種方式,而PHP和Ruby使用第二種方式(Ruby 1.9以上版本才支持這種表示法),PHP使用第三種方式。
比如“發表”的“發”字對應的Unicode編碼是53 d1,它在不同語言中的表示法如下:所以我們可以在.NET、Java、JavaScript的正則表達式中這樣表示“發”字:“\u53d1”,Python稍有不同,必須使用u”\u53d1”(之前的u表示這是一個Unicode字符串); Ruby中,“發”則必須寫作”\u{53d1}”。
現在以“發”字為例,介紹不同語言中的Unicode表示法:
編碼 |
語言 |
表示法 |
備注 |
53 d1 |
.NET |
\u53d1 |
|
Java |
\u53d1 |
||
JavaScript |
\u53d1 |
||
Python |
\u53d1 |
必須使用Unicode字符串,Python 2.x中,要在字符串之前加u |
|
Ruby |
\u{53d1} |
限Ruby 1.9以上版本,且必須顯式指定Unicode模式 |
|
PHP |
\x{53d1} |
必須指定Unicode模式 |
既然可以這樣指定Unicode字符,自然也可以在字符組中用范圍表示法指定一個Unicode編碼范圍了。比如,我們查詢Unicode編碼表可知,中文的編碼一般在4e00到9fff之間,所以可以用這樣的字符組匹配中文字符(Unicode編碼4e00-9fff歸類為“CJK 統一表意符號”CJK Unified Ideographs[1],涵蓋了絕大多數中文字符):
語言 |
字符組 |
備注 |
.NET |
[\u4e00-\u9fff] |
|
Java |
[\u4e00-\u9fff] |
|
JavaScript |
[\u4e00-\u9fff] |
|
Python |
[\u4e00-\u9fff] |
必須使用Unicode字符串,Python 2.x中,要在字符串之前加u |
Ruby |
[\x{4e00}-\x{9fff}] |
限Ruby 1.9以上版本,且必須顯式指定Unicode模式 |
PHP |
[\u{4e00}-\u{9fff}] |
必須指定Unicode模式 |
根據Unicode規范,每一個Unicode字符除了有唯一代碼點對應,還具有其它屬性,現在詳細介紹三種屬性,它們是:Unicode Property、Unicode Block、Unicode Script,下面的圖粗略說明了這三者的關系。
Unicode Property
Unicode Property的記法類似『\p{L}』、『\p{Lo}』,它按照字符的功能分類Unicode字符,而不關心字符屬于哪種語言,每個Unicode字符只能屬于唯一Unicode Property。
舉例來說,『\p{Z}』表示任意的空白字符或不可見的分隔符;『\p{P}』表示任何標點字符,等等。遇到中英文混排、全角半角同時出現的情況,我們就可以用『\p{Z}』匹配所有的空白字符(而不用關心空格到底是全角空格還是半角空格),用『\p{P}』匹配所有的標點字符(而不用關心逗號到底是中文逗號還是英文逗號),而不用費心細節。
如果我們把Unicode Property理解為一個“字符組”,那么它一定能對應某個排除型字符組,此排除型字符組的通行記法是將『\p{xx}』中的小寫p改為大寫P,寫作『\P{xx}』。這樣,『\P{Z}』對應『\p{Z}』無法匹配的字符,『\p{P}』對應『\p{P}』無法匹配的字符。Unicode Block和Unicode Script對應的排除型字符組也是這樣標記,下面不再贅述。
支持Unicode Property的語言有.NET、Java、PHP和Ruby(限1.9以上版本),在PHP和Ruby中使用Unicode Property時,必須要開啟Unicode模式,下面以『\p{P}』的匹配為例:
.NET
Regex.IsMatch(‘,’, “\\p{P}”); //true
Java
“,”.matches(“\\p{P}”); //true
PHP
preg_match(‘/\p{P}/u’, ‘,’); //1
Ruby 1.9
‘/\p{P}/u’ =~ ‘,’ # 0
Unicode Property的完整信息,可參考http://www.regular-expressions.info/unicode.html。
Unicode Block
Unicode Block則不同于Unicode Property,它按照編碼區間劃分Unicode字符,每個Unicode Block中的字符編碼都是落在同一個連續區間的。因為Unicode編碼表中,某種語言的字符通常是落在同一區間的,所以它也可以粗略表示某類語言的字符,比如\p{InHebrew}表示希伯萊語字符,『\p{InCJK_Compatibility}』表示兼容CJK(漢語、韓語、日本語)的字符。如果你細心觀察,會發現Unicod Block的名字雖然類似某種語言的名字,但都有“In”(Java風格)或者“Is”(.NET風格)前綴,這表明它其實對應的還是“落在某個區間的Unicode字符”。
本書介紹的語言中,只有Java和.NET支持Unicode Block,它們的寫法不相同:
Java:『\p{ InCJK_Compatibility_Ideographs }』
.NET:『\p{ IsCJK_Compatibility_Ideographs }』
我們可以在Java中用\p{InCJK_Compatibility}或者在.NET中用\p{IsCJK_Compatibility}粗略匹配中文字符,雖然它們可能匹配日文或者韓文字符,尚不夠精確,但許多情況下確實夠用了。
Java
“我”.matches(“\\p{InCJK_Compatibility_Ideographs}”); //true
.NET
Regex.IsMatch(‘我’, “\\p{IsCJK_Compatibility_Ideographs}”); //true
Unicode Block的完整信息,可參考http://www.regular-expressions.info/unicode.html。
Unicode Script
Unicode Script按照字符所屬的書寫系統來劃分Unicode字符,比如\p{Greek}表示希臘語字符,\p{Han}表示漢語(中文字符)。它的寫法類似Unicode Block,只是名字的開頭沒有“Is”或者“In”。
在本書介紹的語言中,只有PHP和Ruby(限1.9以上版本)支持Unicode Script,PHP在使用Unicode Script時,必須開啟Unicode模式(詳見xx頁)。在這兩種語言中,我們可以很方便地用\p{Han}來匹配中文字符。
PHP
preg_match(‘/\p{Han}/u’, ‘我’); //1
Ruby 1.9
/\p{Han}/u =~ '我' #0
Unicode Script的完整信息,請參考http://www.regular-expressions.info/unicode.html。
小結
用正則表達式處理包含多字節字符(比如中文)的字符串時,最好使用Unicode編碼,否則多字節字符很可能被割裂為多個字節,導致匹配錯誤,最好的辦法是使用Unicode編碼。
如果實在不能使用Unicode編碼,使用字符組時要尤其小心,普通字符組可以用多選分支取代,繞過隱患,但這種辦法對排除型字符組行不通。
如果設定了Unicode模式,可以指定Unicode Code Point來表示某個字符,但不同語言中的記法不同,可能是\uxxxx、\u{xxxx}、\x{xxxx}。
Unicode Property描述Unicode字符的屬性,.NET、Java、PHP和Ruby(限1.9以上版本)中可以使用Unicode Property。
Unicode Block描述Unicode字符所在的區間,其特征是名稱以In或Is為前綴,.NET和Java中可以使用Unicode Block。
Unicode Script描述Unicode字符所在的書寫系統,名字類似Unicode Block,但沒有In或Is前綴,PHP和Ruby(限1.9以上版本)中可以使用Unicode Script。
無論Unicode Property、Unicode Block、Unicode Script,其對應的排除型字符組都是將開頭的\p改為\P,其它不變。
無論Unicode Property、Unicode Block、Unicode Script,都可以視為普通字符組,在字符組[…]中進行拼接組合,比如在PHP和Ruby中,[\p{Han}\p{Po}]既可以匹配漢字字符,又可以匹配任何標點字符。