JavaScript 正則表達式匹配漢字

2021-03-02 前端外刊評論

感謝 @黃俊亮 的投稿,原文:JavaScript 正則表達式匹配漢字。轉載請註明作者和出處。

一個可能有 20 年歷史的正則表達式

在谷歌搜索「JavaScript 正則表達式匹配漢字」的時候,前幾條結果全都是 /[\u4e00-\u9fa5]/。沒有人懷疑這個正則表達式有什麼問題,那麼在 2018 年的今天,讓我們站在 Chrome 64 的肩膀上,放飛一下自我。

漢文(Han Script)是漢語、日本語、朝鮮語、韓國語的書寫系統中的一種文字(Script),越南語在早期也曾在書寫系統中使用漢文[1]。漢字(CJK Ideograph)是漢文的基本單元。各國都對漢字提出了自己的編碼標準,Unicode 將這些標準加總在一起進行統一編碼,力求實現原標準與 Unicode 編碼之間的無損轉換。Unicode 從語義(semantic)、抽象字形(abstract shape),具體字形(typeface)三個維度[2]出發,把不同編碼標準裡「起源相同、本義相同、形狀一樣或稍異」的漢字賦予相同編碼,這些被編碼的字符稱為中日韓統一表意文字(下文我們提到的「漢字」,如果不加說明,均指代中日韓統一表意文字)。如果把它們全部列舉出來寫成正則表達式,那麼就是技術上完整的匹配漢字的正則表達式了。

正則表達式 /[\u4e00-\u9fa5]/的意思是匹配所有從 U+4E00, cjk unified ideograph-4e00 到 U+9FA5, cjk unified ideograph-9fa5 的字符。這一段區域對應的是 Unicode 1.0.1 就收錄進來的中日韓統一表意文字(CJK Unified Ideographs)區塊,在 Unicode 3.0 加入擴展 A 區以前,這個正則表達式確實給出了所有漢字的編碼。換言之,從1992年到1999年,這個正則表達式確實是正確的,想必這個表達式已經有20年歷史了。

匹配所有統一表意文字

然而時光飛逝,Unicode 在2017年6月發布了10.0.0版本。在這20年間,Unicode 添加了許多漢字。比如 Unicode 8.0 添加的 109 號化學元素「䥑(⿰⻐麥)」,其碼點是 9FCF,不在這個正則表達式範圍中。而如果我們期望程序裡的 /[\u4e00-\u9fa5]/可以與時俱進匹配最新的 Unicode 標準,顯然是不現實的事情。因此,我們需要換一個思路,寫一個無需維護的正則表達式:

/\p{Unified_Ideograph}/u

其中 \u是 ECMAScript 2015 定義的正則表達式標誌,意味著將表達式作為 Unicode 碼點序列。 \p是正在提案階段的正則表達式 Unicode 屬性轉義,它賦予了我們根據 Unicode 字符的屬性數據[3]構造表達式的能力。 Unified_Ideograph是 Unicode字符的一個二值屬性,對於漢字,其取值為 Yes,否則為 No。因此 \p{Unified_Ideograph}匹配所有滿足 Unified_Ideograph=yes的 Unicode 字符,而它的底層實現由運行時所依賴的 Unicode 版本決定,開發者不需要知道漢字的具體 Unicode 碼點範圍。

容易混淆的其他 Unicode 屬性轉義表達式

/\p{Ideographic}/u

這個表達式匹配所有滿足 Ideographic=yes的 Unicode 字符。我們先看一下 UAX #44 對這個屬性的解釋[4] :

Characters considered to be CJKV (Chinese, Japanese, Korean, and Vietnamese) or other siniform (Chinese writing-related) ideographs. This property roughly defines the class of "Chinese characters" and does not include characters of other logographic scripts such as Cuneiform or Egyptian Hieroglyphs.

這個屬性表明該字符屬於 CJKV 表意文字或者與漢語書寫相關的其他表意文字(如西夏文、女書),這個屬性粗略地定義了「中文字符」的分類。我們查看Unicode 10.0.0 字符屬性列表可以知道,在 Unicode 10.0.0 中,Ideographic 屬性為 yes 的字符有

3006 ; Ideographic # Lo IDEOGRAPHIC CLOSING MARK

3007 ; Ideographic # Nl IDEOGRAPHIC NUMBER ZERO

3021..3029 ; Ideographic # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE

3038..303A ; Ideographic # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY

3400..4DB5 ; Ideographic # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5

4E00..9FEA ; Ideographic # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEA

F900..FA6D ; Ideographic # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D

FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9

17000..187EC ; Ideographic # Lo [6125] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187EC

18800..18AF2 ; Ideographic # Lo [755] TANGUT COMPONENT-001..TANGUT COMPONENT-755

1B170..1B2FB ; Ideographic # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB

20000..2A6D6 ; Ideographic # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D6

2A700..2B734 ; Ideographic # Lo [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B734

2B740..2B81D ; Ideographic # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D

2B820..2CEA1 ; Ideographic # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA12CEB0..2EBE0 ; Ideographic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0

2F800..2FA1D ; Ideographic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D

Total code points: 96174

它們囊括了所有統一表意文字、西夏文及其組件、女書、中日韓兼容性字符、蘇州碼子、「〇」以及日本語中的書信結尾標誌「〆」。使用 /\p{Ideographic}/u來匹配漢字會過於寬泛。一是包含了西夏文、女書,二是只用於編碼轉換用的兼容字符也納入其中。

/\p{Script=Han}/u

Script 屬性[5]用來篩選滿足下麵條件的一組字符:

字符的書寫形式具有共同的圖像特徵與文字流變

該組字符全部用來表達某個書寫系統內的文本信息(textual information)

我們查看Unicode 10.0.0 Scripts可以知道,滿足 Script=Han的字符有

2E80..2E99 ; Han # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP

2E9B..2EF3 ; Han # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE

2F00..2FD5 ; Han # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE

3005 ; Han # Lm IDEOGRAPHIC ITERATION MARK

3007 ; Han # Nl IDEOGRAPHIC NUMBER ZERO

3021..3029 ; Han # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE

3038..303A ; Han # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY

303B ; Han # Lm VERTICAL IDEOGRAPHIC ITERATION MARK

3400..4DB5 ; Han # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5

4E00..9FEA ; Han # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEA

F900..FA6D ; Han # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D

FA70..FAD9 ; Han # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9

20000..2A6D6 ; Han # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D6

2A700..2B734 ; Han # Lo [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B734

2B740..2B81D ; Han # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D

2B820..2CEA1 ; Han # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1

2CEB0..2EBE0 ; Han # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0

2F800..2FA1D ; Han # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D

# Total code points: 89228

它們囊括了所有統一表意文字、中日韓兼容性字符、蘇州碼子、「〇」、「〆」、「々」以及字典常用的部首。從前面漢文(Han Script)與漢字(CJK Ideograph)的關係我們可以知道, /\p{Script=Han}/u匹配的是漢文作為一個字符集裡面的所有字符,因此它包括了部首、「々」等字符,這些字符要麼當它們獨立存在的時候沒有語言意義(部首獨立存在是一個符號),要麼無法獨立存在(「々」依賴於所修飾的漢字)。所以漢字是漢文的一個單元,漢文除了包含漢字以外,還包括這些符號、數字、修飾符。因此使用 /\p{Script=Han}/u來匹配漢字是混淆了漢文與漢字的概念範圍。

瀏覽器兼容性支持JavaScript

截至2018年1月,只有 Chrome 64 支持正則表達式 Unicode 屬性轉義。對於其他瀏覽器,我們需要用 babel轉譯插件@babel/plugin-proposal-unicode-property-regex的底層將帶有屬性轉義的正則表達式轉為 Unicode 碼點正則表達式或者 ES 5 的正則表達式。轉譯結果的在線演示可以在這裡查看,用戶可以自己在上面轉譯其他的 Unicode 屬性轉義正則表達式。我們在這裡列舉 /\p{Unified_Ideograph}/u轉譯成Unicode 碼點正則表達式的結果:

const regex = /\p{Unified_Ideograph}/u;

// transpiled to ES6:

const regex = /[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29\u{20000}-\u{2A6D6}\u{2A700}-\u{2B734}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}]/u;

從上面這個正則表達式可以知道,轉譯的結果嚴格跟 Unicode 10.0.0 中 Unified_Ideograph 屬性為 yes 的字符

3400..4DB5 ; Unified_Ideograph # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5

4E00..9FEA ; Unified_Ideograph # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEA

FA0E..FA0F ; Unified_Ideograph # Lo [2] CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPATIBILITY IDEOGRAPH-FA0F

FA11 ; Unified_Ideograph # Lo CJK COMPATIBILITY IDEOGRAPH-FA11

FA13..FA14 ; Unified_Ideograph # Lo [2] CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPATIBILITY IDEOGRAPH-FA14

FA1F ; Unified_Ideograph # Lo CJK COMPATIBILITY IDEOGRAPH-FA1F

FA21 ; Unified_Ideograph # Lo CJK COMPATIBILITY IDEOGRAPH-FA21

FA23..FA24 ; Unified_Ideograph # Lo [2] CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24

FA27..FA29 ; Unified_Ideograph # Lo [3] CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA29

20000..2A6D6 ; Unified_Ideograph # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D6

2A700..2B734 ; Unified_Ideograph # Lo [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B734

2B740..2B81D ; Unified_Ideograph # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D

2B820..2CEA1 ; Unified_Ideograph # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1

2CEB0..2EBE0 ; Unified_Ideograph # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0

# Total code points: 87882

嚴格對應。因此轉譯是正確的。

該插件還可以使用

{

 "plugins": [

   ["@babel/plugin-proposal-unicode-property-regex", { "useUnicodeFlag": false }]

 ]

}

配置將表達式轉成 ES5 的傳統的以字符的 UTF16 表示為序列的字符串,這裡不再贅述。

input 元素的 pattern 屬性

在前端技術中,除了JavaScript會用到正則表達式,HTML 裡 <input>元素的 pattern屬性也會用到正則表達式。與 JavaScript 相比, pattern不支持設置正則表達式的標誌位,因此 HTML 標準中強制規定了 input 元素的 pattern 屬性需要施加 unicode標誌 [6]。目前只有 Chrome 53+, Firefox 遵循了這一標準,其他的瀏覽器暫未支持。

在 React/Angular/Vue.js 三大前端框架中,Angular 提供了近似於 pattern 的指令 ngPattern。目前 ngPattern尚未施加 unicode標誌 [7]。AngularJS 的 ngPattern directive 仍未施加。

在大部分情況,是否施加 unicode標誌不會對正則表達式產生語義區別。主要的差別在於,在使用 \u{10000}表示 Unicode 碼點字符情形,正則表達式 /\u{10000}/代表匹配 u一萬次, /\u{10000}/u匹配字符 \u{10000}一次; /./只匹配 BMP 平面的字符, /./u匹配所有平面的字符。

由於 Unicode 屬性轉義正則表達式依賴於標識位 \u,因此下面的用法目前只能在 Chrome 下使用:

<inputtype="text"pattern="\p{Unified_Ideograph}">

因此,如果需要兼容其他瀏覽器,可以使用轉譯插件的底層庫regexpu-core在 js 層轉換正則表達式,再把轉換結果輸送到 HTML 模版中。

const rewritePattern = require("regexpu-core");

rewritePattern('\\p{Unified_Ideograph}', 'u', {

 'unicodePropertyEscape': true,

 'useUnicodeFlag': false

});

// → '/(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])/'

總結

/[\u4e00-\u9fa5]/是錯的,不要用二十年前的正則表達式了

/\p{Unified_Ideograph}/u是正確的,不需要維護,匹配所有漢字。這裡 \p是 Unicode 屬性轉義正則表達式。

/\p{Ideographic}/u 和 /\p{Script=Han}/u 匹配了除了漢字以外的其他一些字符,在「漢字匹配正則表達式」這個需求下,是錯的。

目前只有 Chrome 支持 Unicode 屬性轉義正則表達式。對其他環境,使用 @babel/plugin-proposal-unicode-property-regex 和 regexpu-core 進行優雅降級。

參考資料

[1] Unicode 10.0.0 第六章第一節,書寫系統

[2] Unicode 10.0.0 第十八章第一節,東亞

[3] Unicode 10.0.0 字符屬性列表

[4] UAX #44 第 20 版的屬性說明

[5] UAX #24 第 27 版

[6] HTML 標準中 input元素的 pattern屬性

[7] 給 ngPattern施加 unicode標誌

相關焦點

  • Python正則表達式:特殊符號和字符
    正表達式為高級的文本模式匹配,抽取,與/或文本形式的搜索和替換功能提供了基礎。簡而言之,正則表達式(簡稱regex)是由一些字符和特殊符號組成的字符串,它描述了模式的重複或者表達多個字符。python通過標準庫中的re模塊來支持正則表達式。
  • 學習Python正則表達式
    Python中的正則表達式(re)就可以解決這個問題!正則表達式正則表達式是一個具有特殊字符的序列。它有助於檢查字符串中的每個字符,看它是否與某個模式匹配:哪些字符在什麼位置出現了多少次。\d-所有數字,但只有一個數字如果文本包含數字,考慮匹配「\d」模式。『我想得到每條評論中完整的號碼,而非單個數字。』
  • PostgreSQL Tips:如何在PostgreSQL中使用正則表達式-LIKE和〜Tilde
    介紹正則表達式是一組按特定順序排列的字符,可幫助識別所需的正確輸入。我們將研究正則表達式,以及如何使用除~,~*,!~,!~*之外的與常規匹配的代字運算符系列以外的其他方法或運算符(如 LIKE,NOT LIKE 和 SIMILAR TO)使用它們。區分大小寫和不區分大小寫的情況下的表達式。
  • 通過正則表達式快速獲取電影的下載地址!正則-永遠滴神!
    本文將實現通過正則表達式快速獲取電影的下載地址。如果對正則表達式不熟悉的讀者,點擊 此處 學習正則表達式。在爬取網頁信息的任務中,可以使用正則表達式的方式快速的提取某個標籤內的指定信息,以爬取電影的下載地址為例。
  • 正則表達式帶來的幸福生活
    《正則指引》是我寫的第一本書,對我是煎熬,也是考驗。好不容易寫完了,當然很高興。希望廣大讀者能認可,同時內心裡也會有那麼一點點「狂妄」的期盼:儘管關於正則表達式已經有很多書籍了,但這本書濃縮了我的長期思考,尤其是處理中文字符的經驗,這是任何其它書籍都不具備的。
  • 使用requests+正則表達式爬取貓眼電影排行
    本節中,我們利用requests庫和正則表達式來抓取貓眼電影TOP100的相關內容。
  • Python3爬蟲之豆瓣電影TOP250(requests、Xpath、正則表達式、CSV、二進位數據儲存)
    提取相關信息def parse_pages(url):    movie_pages = requests.get(url=url, headers=headers)    parse_movie = etree.HTML(movie_pages.text)【2x01】Xpath 解析排名、電影名、評分信息其中排名、電影名和評分信息是最容易匹配到的
  • 第一篇:JavaScript基本語法
    html><head><title>這是登錄頁面</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><script type="text/javascript
  • AE表達式教程
    再者就是如何調出表達式?正式開始本次主要是對AE表達式裡time、javascript.math(math.sin、math.floor)property(valueattime)進行不同參數的運動軌跡的對比。
  • JS版漢字與拼音互轉終極方案,附簡單的JS拼音輸入法
    ,或者說是可以讀的漢字,本文用到的幾個字典文件的漢字範圍均是 /^[\u4E00-\u9FA5]+$/,也就是(19968-40869),另外還有一個單獨的漢字〇,其Unicode位置是12295。其中,對於沒有或者找不到讀音的漢字,統一標註為none0,我統計了一下,這樣的漢字一共有525個。
  • 第三篇:JavaScript語句流程控制
    >if和else是JS關鍵字,條件表達式必須括在一對圓括號之內,表達式返回布爾值。for循環結構語法如下:for(表達式1; 表達式2; 表達式3;){代碼段;}表達式1:通常用來給循環變量賦初值,一般是賦值表達式,也允許在for語句外給循環變量賦初值
  • JavaScript模擬輪播圖效果
    setInterval() 方法可按照指定的周期(以毫秒計)來調用函數或計算表達式。div{       border: 1px solid white;       width:600px ;       height:400px;       margin: auto;       text-align: center;     }   </style>   <script type="text/javascript
  • JavaScript運算「程式設計師培養之路第八天」
    *= /= %=//編程思想var a = 3;a = a + 1;//第一步:讀取a的值//第二步:運算a + 1//第三步:把運算結果寫回avar x = 10;x = x + 2; //讀取x的值,在運算x+2,運算結果寫回xvar x - = 2; //x = x - 2; 注意順序x+=2; //x = x+2x-=2;x*=2;x/=2;x%=2;表達式
  • 我的文檔中有多少個純漢字?字數統計中沒有哦
    如何統計Word中純漢字的數量及相關注意事項編輯文章一般都需要統計漢字字數。Word自帶有統計字數的工具,但它並不能統計純漢字的數量。統計字數的工具的界面如下,「中文字符和朝鮮語單詞」是最接近漢字數量的。
  • AE表達式:回彈
    AE具備一個強大的功能,那就是表達式,通過表達式,建立圖層屬性與關鍵幀的相關關係,無需手動K幀,便可以製作出動畫效果。本文將講解表達式基礎,表達式的含義,並傳授一些調整表達式的方法,以滿足你的工作需求。那麼,什麼是表達式呢?對於經常閱讀Thougtbot的讀者來說,他們可能會對表達式非常熟悉。