Unicode 支援


Unicode 字元集為世界大部份文字系統做了整理,規則表示式是為了比對文字,兩者相遇就產生了更多的需求;為了能令規則表示式支援 Unicode,Unicode 組織在〈UNICODE REGULAR EXPRESSIONS〉做了規範;JavaScript 對 Unicode 規則表示式,從 ES6 開始逐步支援,想運用的話,必須開啟 u 旗標採用 Unicode 模式。

Unicode 模式

從 ES6 開始,規則表示式啟用 u 旗標,代表著啟用 Unicode 模式,目的之一是支援 \u{…} 的寫法;例如,ES6 以後 '\uD834\uDD1E' 可以使用 '\u{1D11E}' 來表示;然而,若要在規則表示式使用 \u{…},必須開啟 u 旗標:

> 'Treble clef: \uD834\uDD1E'.search(/\u{1D11E}/)
-1
> 'Treble clef: \uD834\uDD1E'.search(/\u{1D11E}/u)
13
>                                                                                                                        

在啟用 Unicode 模式的情況下,既有的 \uhhhh 寫法就是指定「碼點」(而不是碼元),也就是說,若 \uhhhh 指定的碼點處,實際上沒有定義 Unicode 字元,就不會比對成功。例如:

> let trebleClef = '\u{1D11E}'
undefined
> /\uD834/.test(trebleClef)
true
> /\uD834/u.test(trebleClef)
false
>

在沒有開啟 u 旗標的情況下,test 方法會在字串索引 0 處找到相符的碼元;然而,在開啟 Unicode 模式後,\uD834 就是指 Unicode 碼點 U+D834,然而該碼點處未定義字元,test 就因搜尋失敗而傳回 false

啟用 Unicode 模式後,對於 0xFFFF 以外的字元,才會進行正確的辨識,這會影響預定義字元類、量詞等的判斷。

例如,未啟用 Unicode 模式前,預定義字元類 \S 表示非空白字元,然而,對 0xFFFF 以外的字元會誤判,只有在加上 u 旗標的情況下才會正確比對;類似地,\W、「.」也只有在啟用 Unicode 模式下,才能正確比對 0xFFFF 以外的字元:

> /^\S$/.test('\u{1D11E}')
false
> /^\S$/u.test('\u{1D11E}')
true
> /^\W$/.test('\u{1D11E}')
false
> /^\W$/u.test('\u{1D11E}')
true
> /^.$/.test('\u{1D11E}')
false
> /^.$/u.test('\u{1D11E}')
true
>

Unicode 特性轉譯

ES9 以後支援規則表示式的 Unicode 特性轉譯(Unicode property escapes),為了能運用這個新功能,必須認識 Unicode 規範中的分類(Category)、文字(Script)。

在 Unicode 的規範中,每個 Unicode 字元會隸屬於某個分類,在〈General Category Property〉中可以看到 Letter、Uppercase Letter 等一般分類,每個分類也給予了 L、Lu 等縮寫名稱。

舉例來說,隸屬於 Letter 分類的字元都是字母,a 到 z、A 到 Z、全形的 a 到 z、A 到 Z 都在 Letter 分類中,除了英文字母之外,其他如希臘字母 α、β、γ 等,也都隸屬於 Letter 分類。

ES9 以後若使用 u 旗標開啟 Unicode 模式,可以使用 \p{General_Category=Letter}\p{gc=Letter}\p{Letter}\p{L} 等方式來指定分類,若分類名稱有兩個字,要使用 _ 相連,這樣在使用規則表示式判斷字母、數字等,就非常方便了。例如:

> /\p{General_Category=Letter}/u.test('α')
true
> /\p{gc=Letter}/u.test('α')
true
> /\p{Uppercase_Letter}/u.test('α')
false
> /\p{Ll}/u.test('α')
true
> /\p{Number}/u.test('1')
true
> /\p{Number}/u.test('1')
true
>

\p{…} 的相反就是 \P{…}


> /\p{Number}/u.test('1')
true
> /\P{Number}/u.test('1')
false
>

來個有趣的測試吧!𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼 都是十進位數字:

> /^\p{Decimal_Number}+$/u.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼')
true
>

數字呢?²³¹¼½¾𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼㉛㉜㉝ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ都是:

> /^\p{Number}+$/u.test('²³¹¼½¾𝟏𝟐𝟑𝟜𝟏𝟐𝟑𝟜���𝟪𝟫𝟬𝟭𝟮㉛㉛㉜㉜㉝㉝ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ')
true
>

Unicode 將希臘文、漢字等組織為文字(Script)特性,可參考〈UNICODE SCRIPT PROPERTY〉,例如,如果想在規則表示式中以文字特性比對,可以使用 \p{Script=Greek}\p{Script_Extensions=Greek}\p{sc=Han}\p{scx=Han} 的寫法(Han 包含了正體中文、簡體中文,以及日、韓、越南文的全部漢字)。例如:

> /\p{Script=Greek}/u.test('a')
false
> /\p{Script_Extensions=Greek}/u.test('α')
true
> /\p{sc=Greek}/u.test('α')
true
> /\p{sc=Han}/u.test('林')
true
>

另外還有一些二元特性,像是 ASCII、Alphabetic、Lowercase、Emoji 等,直接寫在 \p{..} 裏就可以了。例如:

> /\p{Emoji}/u.test('😃')
true
>

如果想取得 \p{..} 中可以使用的特性名稱等清單,可以查閱 ECMAScript 規格書〈UnicodeMatchProperty〉與〈UnicodeMatchPropertyValue〉內容。