ECMAScript 2018よりRegExpで使えるようになったUnicodeプロパティーエスケープ (\p{...}, \P{...}) についての補説です。
ECMAScript (JavaScript) の正規表現では、\p{...}, \P{...} ともに次のどちらかの書式を取ります。
いずれの書式も{}内では大文字小文字が区別されます。
\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の値として指定できるのは次の文字列です。
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の値として指定できる文字列もECMAScript version 13/2022年版までは仕様書に一覧表が掲載されていたのですが、version 14/2023年版から表が載らなくなってしまいました。以下はECMAScript 2022時点のものです。
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というのは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{General_Category=...} の "General_Category=" の部分が省略されたものと仮定し、General_Categoryとして指定できる値一覧の中に LoneNameOrValue と一致するものがあるかどうか調べます。
その結果もしあれば、\p{General_Category=LoneNameOrValue} として処理を続けます。
LoneNameOrValueの部分に指定できるのは次の文字列です(ECMAScript 2021現在)。
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} なのか区別が付かないためです。