Unicode Property Escapesについての補説

 ECMAScript 2018よりRegExpで使えるようになったUnicodeプロパティーエスケープ (\p{...}, \P{...}) についての補説です。

メニュー

書式

 ECMAScript (JavaScript) の正規表現では、\p{...}, \P{...} ともに次のどちらかの書式を取ります。

 いずれの書式も{}内では大文字小文字が区別されます。

\p{NAME=VALUE}型

 \p{NAME=VALUE} という表現は、「Unicodeの全コードポイントのうち、NAMEという属性の値がVALUEであるものすべて」にマッチします。例えば \p{sc=Hira} という表記は、「Unicodeに存在するあらゆるひらがな」にマッチします。
 \P{...} は \p{...} とは逆に、「NAMEという属性の値がVALUEではないものすべて」にマッチします。\P{sc=Hira} という表記は「Unicodeに存在するひらがな以外のあらゆる文字」にマッチします。

 Unicode規格には様々な種類の属性が存在しますが、ECMAScriptではNAMEの部分に指定できる属性名を次の3つに限っています。

 前述の通り \p{...}, \P{...} とも ... の部分では大文字小文字が区別されますので、\p{GC=何々} や \P{script=何々} のように書くと認識されずエラーとなります。

General_Categoryの値一覧

 General_Categoryの値として指定できるのは次の文字列です。

General_Categoryの値一覧(括弧内は別名)
Cased_Letter (LC) Close_Punctuation (Pe) Connector_Punctuation (Pc) Control (Cc, cntrl)
Currency_Symbol (Sc) Dash_Punctuation (Pd) Decimal_Number (Nd, digit) Enclosing_Mark (Me)
Final_Punctuation (Pf) Format (Cf) Initial_Punctuation (Pi) Letter (L)
Letter_Number (Nl) Line_Separator (Zl) Lowercase_Letter (Ll) Mark (M, Combining_Mark)
Math_Symbol (Sm) Modifier_Letter (Lm) Modifier_Symbol (Sk) Nonspacing_Mark (Mn)
Number (N) Open_Punctuation (Ps) Other (C) Other_Letter (Lo)
Other_Number (No) Other_Punctuation (Po) Other_Symbol (So) Paragraph_Separator (Zp)
Private_Use (Co) Punctuation (P, punct) Separator (Z) Space_Separator (Zs)
Spacing_Mark (Mc) Surrogate (Cs) Symbol (S) Titlecase_Letter (Lt)
Unassigned (Cn) Uppercase_Letter (Lu)

Script, Script_Extensionsの値一覧

 ScriptおよびScript_Extensionsの値として指定できる文字列もECMAScript version 13/2022年版までは仕様書に一覧表が掲載されていたのですが、version 14/2023年版から表が載らなくなってしまいました。以下はECMAScript 2022時点のものです。

Script, Script_Extensionsの値一覧(括弧内は別名)
Adlam (Adlm) Ahom (Ahom) Anatolian_Hieroglyphs (Hluw) Arabic (Arab)
Armenian (Armn) Avestan (Avst) Balinese (Bali) Bamum (Bamu)
Bassa_Vah (Bass) Batak (Batk) Bengali (Beng) Bhaiksuki (Bhks)
Bopomofo (Bopo) Brahmi (Brah) Braille (Brai) Buginese (Bugi)
Buhid (Buhd) Canadian_Aboriginal (Cans) Carian (Cari) Caucasian_Albanian (Aghb)
Chakma (Cakm) Cham (Cham) Cherokee (Cher) Chorasmian (Chrs)
Common (Zyyy) Coptic (Copt, Qaac) Cypro_Minoan (Cpmn) Cuneiform (Xsux)
Cypriot (Cprt) Cyrillic (Cyrl) Deseret (Dsrt) Devanagari (Deva)
Dives_Akuru (Diak) Dogra (Dogr) Duployan (Dupl) Egyptian_Hieroglyphs (Egyp)
Elbasan (Elba) Elymaic (Elym) Ethiopic (Ethi) Georgian (Geor)
Glagolitic (Glag) Gothic (Goth) Grantha (Gran) Greek (Grek)
Gujarati (Gujr) Gunjala_Gondi (Gong) Gurmukhi (Guru) Han (Hani)
Hangul (Hang) Hanifi_Rohingya (Rohg) Hanunoo (Hano) Hatran (Hatr)
Hebrew (Hebr) Hiragana (Hira) Imperial_Aramaic (Armi) Inherited (Zinh, Qaai)
Inscriptional_Pahlavi (Phli) Inscriptional_Parthian (Prti) Javanese (Java) Kaithi (Kthi)
Kannada (Knda) Katakana (Kana) Kawi (Kawi) Kayah_Li (Kali)
Kharoshthi (Khar) Khitan_Small_Script (Kits) Khmer (Khmr) Khojki (Khoj)
Khudawadi (Sind) Lao (Laoo) Latin (Latn) Lepcha (Lepc)
Limbu (Limb) Linear_A (Lina) Linear_B (Linb) Lisu (Lisu)
Lycian (Lyci) Lydian (Lydi) Mahajani (Mahj) Makasar (Maka)
Malayalam (Mlym) Mandaic (Mand) Manichaean (Mani) Marchen (Marc)
Masaram_Gondi (Gonm) Medefaidrin (Medf) Meetei_Mayek (Mtei) Mende_Kikakui (Mend)
Meroitic_Cursive (Merc) Meroitic_Hieroglyphs (Mero) Miao (Plrd) Modi (Modi)
Mongolian (Mong) Mro (Mroo) Multani (Mult) Myanmar (Mymr)
Nabataean (Nbat) Nag_Mundari (Nagm) Nandinagari (Nand) New_Tai_Lue (Talu)
Newa (Newa) Nko (Nkoo) Nushu (Nshu) Nyiakeng_Puachue_Hmong (Hmnp)
Ogham (Ogam) Ol_Chiki (Olck) Old_Hungarian (Hung) Old_Italic (Ital)
Old_North_Arabian (Narb) Old_Permic (Perm) Old_Persian (Xpeo) Old_Sogdian (Sogo)
Old_South_Arabian (Sarb) Old_Turkic (Orkh) Old_Uyghur (Ougr) Oriya (Orya)
Osage (Osge) Osmanya (Osma) Pahawh_Hmong (Hmng) Palmyrene (Palm)
Pau_Cin_Hau (Pauc) Phags_Pa (Phag) Phoenician (Phnx) Psalter_Pahlavi (Phlp)
Rejang (Rjng) Runic (Runr) Samaritan (Samr) Saurashtra (Saur)
Sharada (Shrd) Shavian (Shaw) Siddham (Sidd) SignWriting (Sgnw)
Sinhala (Sinh) Sogdian (Sogd) Sora_Sompeng (Sora) Soyombo (Soyo)
Sundanese (Sund) Syloti_Nagri (Sylo) Syriac (Syrc) Tagalog (Tglg)
Tagbanwa (Tagb) Tai_Le (Tale) Tai_Tham (Lana) Tai_Viet (Tavt)
Takri (Takr) Tamil (Taml) Tangsa (Tnsa) Tangut (Tang)
Telugu (Telu) Thaana (Thaa) Thai (Thai) Tibetan (Tibt)
Tifinagh (Tfng) Tirhuta (Tirh) Toto (Toto) Ugaritic (Ugar)
Vai (Vaii) Vithkuqi (Vith) Wancho (Wcho) Warang_Citi (Wara)
Yezidi (Yezi) Yi (Yiii) Zanabazar_Square (Zanb)

 HiraganaとKatakanaはあるのにKanjiがないと思われるかもしれませんが、Han (Hani) というのが漢字のことです(「漢字」の中国語読みをローマ字化したHanziに由来)。

 Unicodeのデータファイル中には時折Katakana_Or_Hiragana (Hrkt) というスクリプト名が出てくることがありますが、これは初期の頃に使われていたものの現在はどのコードポイントとも結び付けられていません(UAX #44中の記述UAX #31中の記述)。
 ECMAScript 2022までは表にも掲載されていませんでしたので、指定しても不明なスクリプト名扱いされます。

 一方Unknownというスクリプト名は、Scripts.txtにはコメントの形で載ってはいるものの、ECMAScript 2022までの対応すべきスクリプト名一覧表には載っていませんでした。しかしECMAScript仕様書が一覧表を廃止したこと、V8では認識されることなどを踏まえ、「UnknownはScripts.txtにコードポイント値の範囲が明示されていなかったため、誤って表から欠落していた」可能性が高いと考え、SRELLでもversion 4.031より対応することにしました。

ScriptとScript_Extensionsとの違い

 ScriptというのはUnicodeにおけるもっとも基本的な文字の分類単位です。日本語では文字体系、書記体系などと訳されることもありますが、今のところ定訳と呼べるようなものはまだないようです。
 例えばひらがなの「あ」(U+3042) は、Unicodeにおいては "Hiragana" scriptに属する文字のうちの一つであると見なされます。

 Unicodeでは文字を符号化する際、その符号化しようとしている文字がどのscriptに分類されるかをまず考えます。もし既存のどのscriptにも属するものではないと判断されれば、新しいscript名が登録され、その上で文字の符号化が行われます。いわばscriptとはディレクトリのようなもので、例えば先のひらがな「あ」であれば Unicode/Hiragana/あ のようにイメージしていただくと分かりやすいかもしれません。

 同名のファイルでもpathが異なれば別物であるように、見た目や発音が同じでもscriptが異なればUnicodeでは別の文字として扱われます。ラテン文字のA (U+0041)、ギリシア文字のΑ (U+0391)、キリル文字のА (U+0410) がUnicodeで区別されるのも、それぞれ別のscriptに属しているためです。ひらがなの「へ(U+3078)」とカタカナの「ヘ(U+30D8)」に別々の文字コードが割り振られているのも同じ理由です。

「異なるscriptに属する文字同士は、たとえ互いに似ていても別々の文字として扱われる」ということは、言い方を変えれば「1つの文字は複数のscriptに属することはできない」ということでもあります。従いましてUnicodeに存在するすべての文字は、1種類の \p{Script=何々} にしかマッチしません。

 しかし現実には、複数のscriptに跨って現れる文字というものも存在します。身近なところですと長音符・長音記号の「ー (U+30FC)」がそうです。この文字は「KATAKANA-HIRAGANA PROLONGED SOUND MARK」というUnicode名が示す通り、Hiragana script とも Katakana script ともどちらとも組み合わせて使われます。このような場合、どちらかのscriptに属させるわけにも行きませんので Common(汎用)という名前のscriptに分類されています。従って \p{sc=Hiragana} も \p{sc=Katakana} も「ー」にはマッチしません。

 このような文字が \p{何々=Hiragana} とも \p{何々=Katakana} ともどちらともマッチするようにしたのがScript_Extensionsです。例えば \p{scx=Hiragana} とすれば、先述の音引き棒のみならず「、。・゛゜」などにもマッチするようになります。さらにこれら「、。・゛゜ー」は、\p{scx=Katakana} ともマッチします。

 Script_Extensionsにすると具体的にどの文字がどう変わるかについては、Scriptデータに対する差分の形で公開されています。

\p{LoneNameOrValue}型

 この書式が指定された場合、正規表現コンパイラはまず \p{General_Category=...} の "General_Category=" の部分が省略されたものと仮定し、General_Categoryとして指定できる値一覧の中に LoneNameOrValue と一致するものがあるかどうか調べます。
 その結果もしあれば、\p{General_Category=LoneNameOrValue} として処理を続けます。

 もしなかった場合、LoneNameOrValueの部分は binary property であると解釈され、この表現は「Unicodeの全コードポイントのうち、LoneNameOrValueで指定された属性値を持つものすべて」にマッチします。例えば \p{White_Space} という表記は、Unicodeに存在するあらゆる空白文字にマッチします。

Binary Property名一覧

 LoneNameOrValueの部分に指定できるのは次の文字列です(ECMAScript 2021現在)。

Binary Property名一覧(括弧内は別名)
ASCII ASCII_Hex_Digit (AHex)
Alphabetic (Alpha) Any
Assigned Bidi_Control (Bidi_C)
Bidi_Mirrored (Bidi_M) Case_Ignorable (CI)
Cased Changes_When_Casefolded (CWCF)
Changes_When_Casemapped (CWCM) Changes_When_Lowercased (CWL)
Changes_When_NFKC_Casefolded (CWKCF) Changes_When_Titlecased (CWT)
Changes_When_Uppercased (CWU) Dash
Default_Ignorable_Code_Point (DI) Deprecated (Dep)
Diacritic (Dia) Emoji
Emoji_Component (EComp) Emoji_Modifier (EMod)
Emoji_Modifier_Base (EBase) Emoji_Presentation (EPres)
Extender (Ext) Extended_Pictographic (ExtPict)
Grapheme_Base (Gr_Base) Grapheme_Extend (Gr_Ext)
Hex_Digit (Hex) IDS_Binary_Operator (IDSB)
IDS_Trinary_Operator (IDST) ID_Continue (IDC)
ID_Start (IDS) Ideographic (Ideo)
Join_Control (Join_C) Logical_Order_Exception (LOE)
Lowercase (Lower) Math
Noncharacter_Code_Point (NChar) Pattern_Syntax (Pat_Syn)
Pattern_White_Space (Pat_WS) Quotation_Mark (QMark)
Radical Regional_Indicator (RI)
Sentence_Terminal (STerm) Soft_Dotted (SD)
Terminal_Punctuation (Term) Unified_Ideograph (UIdeo)
Uppercase (Upper) Variation_Selector (VS)
White_Space (space) XID_Continue (XIDC)
XID_Start (XIDS)

 ちなみに省略が認められているのは "General_Category=" のみで、"Script=" や "Script_Extensions=" の省略は認められていません。これらは=の右辺値が同じですので、\p{Katakana} のような表現だけでは \p{sc=Katakana} なのか \p{scx=Katakana} なのか区別が付かないためです。

参考資料