本篇介紹 ES2018 (ES9) 提供的 RegExp 的 Unicode property escapes。
本篇會有很多特殊字元,但 IT 鐵人這裡無法顯示這些字元,所以內文會看到大量的「???」XDD
過去 ECMAScript 的原生 RegExp 無法直接存取有些 Unicode 的字元 property,簡化來說就是原生 RegExp 無法完整支援 Unicode。
因為原生未提供,所以只能透過其他 library 來解決,但這些方法不是很理想:
詳情可參閱此提案。
在 ES2018 (ES9) 提供了 RegExp 的 Unicode property escapes,語法為 \p{...}
和 \P{...}
,而其中的 p
代表的是「property」的意思。Unicode property escapes 是一種新的 escape sequence (跳脫序列),可用於設定 u
flag 的 RegExp,這樣可以更好的表示 \p{...}
內的表達式與 Unicode property 有關。
此提案解決了過去的問題,因為已變為原生支援,無須使用額外的 library。
\P{...}
是 \p{...}
的反向形式 (negated form),就跟 \d
代表數字字元,\D
代表非數字字元一樣,大寫字有「negated」的意思。
非 binary Unicode property 的 Unicode property escapes 語法如下:
\p{UnicodePropertyName=UnicodePropertyValue}
上面的語法中,可用 Unicode 的 PropertyAliases.txt 和 PropertyValueAliases.txt 中定義的 alias 替換 ECMAScript spec 定義的 UnicodePropertyName
和 UnicodePropertyValue
。
若是 binary property 的 Unicode property escapes 語法如下:
\p{LoneUnicodePropertyNameOrValue}
此語法也可作為 General_Category
值的簡寫,例如:是 \p{Letter}
,而不是 \p{General_Category=Letter}
。
若使用未知的 property name 或 value 會觸發 early SyntaxError
。
\d
和 \D
版本若要 match Unicode 中的任何十進位數字,而不只是 match ASCII 的 [0-9]
,請使用 UTS18 中的 \p{Decimal_Number}
,而不是 \d
:
let decimalNumberPattern = /^\p{Decimal_Number}+$/u;
decimalNumberPattern.test('????????????????');
// true
若要不 match,請用 \P{Decimal_Number}
,而不是 \D
。
match Unicode 中的任何 numeric symbol,包括 non-decimal symbol,例如:羅馬數字:
let numericPattern = /^\p{Number}+$/u;
numericPattern.test('²³¹¼½¾????????????????㉛㉜㉝ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ');
// true
\w
和 \W
版本若要 match Unicode 中的任何 word symbol,而不只是 match ASCII 的 [a-zA-Z0-9_]
,請使用 UTS18 中的 [\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
,而不是 \w
:
let wordSymbolPattern = /([\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]+)/gu;
let text = 'Macedonian: летачко';
for (match of text.matchAll(wordSymbolPattern)) {
console.log(match);
}
// ["Macedonian", "Macedonian", index: 0, input: "Macedonian: летачко", groups: undefined]
// ["летачко", "летачко", index: 12, input: "Macedonian: летачко", groups: undefined]
之後會介紹到
String.prototype.matchAll()
。
若要不 match,請用 [^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
,而不是 \W
。
若要 match emoji,可用 UTR51 的 binary property。
例如:下面 pattern 可 match 以下內容:
\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?
\p{Emoji_Presentation}
\p{Emoji}\uFE0F
let emojiPattern = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu;
let text = `
\u{231A}: ⌚ default emoji presentation character (Emoji_Presentation)
\u{2194}\u{FE0F}: ↔️ default text presentation character rendered as emoji
\u{1F466}: ? emoji modifier base (Emoji_Modifier_Base)
\u{1F466}\u{1F3FB}: ?? emoji modifier base followed by a modifier
`;
for (match of text.matchAll(emojiPattern)) {
const emoji = match[0];
console.log(`${emoji} - code points: ${ [...emoji].length }`);
}
// ⌚ - code points: 1
// ⌚ - code points: 1
// ↔️ - code points: 2
// ↔️ - code points: 2
// ? - code points: 1
// ? - code points: 1
// ?? - code points: 2
// ?? - code points: 2
其中的 Emoji Modifiers 是在 Unicode 8.0 發布的,可用來設定人的 emoji 膚色。
只要將原本人的 emoji 加上膚色的 emoji modifier 就能變成不同膚色人的 emoji,例如:\u{1F466}\u{1F3FB}
,\u{1F466}
是人的 emoji,\u{1F3FB}
是 膚色的 emoji modifier。
在 Unicode 的 spec 定義中有支援 loose matching 規則 UAX44-LM3,包括忽略大小寫、空格、_
和 -
等。
例如:
\p{lB=Ba}
等同於 \p{Line_Break=Break_After}
/\p{___lower C-A-S-E___}/u
等同於 /\p{Lowercase}/u
但在此提案中提到,loose matching 會影響程式碼的可讀性和可維護性,所以才不支援 loose matching。