算法与数据结构 / regex · Unicode 字符模型与一个自研引擎 / \p{…} 属性类:用码点的 Unicode 档案来匹配 待审核 4 / 36
基础 · 码点的档案 \p{…}

\p{…} 属性类:用码点的 Unicode 档案来匹配

字符的编码 那页讲了字符的编号(code point)与存储(UTF-16 / UTF-8)。这页讲第三样:每个编号在 Unicode Character Database(UCD) 里都有一份档案——名字、General_Category(它是字母 / 数字 / 标点……)、Script(它属于哪套文字),外加几十个二元属性(White_Space?Math?…… 的勾选框)。\p{…} 就是「按档案匹配」:不必手写 [一-鿿] 这种码位区间,直接说「我要 \p{Script=Han}」。\P{…} 是取反;两者都必须开 uv 标志——没有标志时 \p 只是字面量 p

1 · \p{…} 的三种合法写法

表 1-1 · 属性类能查档案的三栏。
写法 查档案的哪一栏 例子
\p{L} / \p{Lu} General_Category(可省 gc= \p{L}\p{gc=L}\p{General_Category=L}
\p{Script=Han} Script / Script_Extensions \p{sc=Han}\p{scx=Han}
\p{White_Space} 二元属性(名字单独写) \p{Math}\p{Alphabetic}\p{Emoji}

注 · 单独写一个名字时(\p{X}),引擎按「二元属性名,或 General_Category 的值」解析——所以 \p{L} 是 gc 缩写、\p{Math} 是二元属性,写法看着一样,查的栏不同。拼写是严格的:大小写、下划线都不能错,\p{whitespace} 直接 SyntaxError。没有第四种——档案里还有一栏 Block(住址),JS 的 \p{} 不支持,见本页第 6 节。

2 · 码点探针

用原生 /\p{…}/u 把一个字符的档案逐栏测出来。几个反直觉的例子:𝑥(数学斜体)是字母、Script 却是 Common;(顿号)不是 sc=Han½ 是数字(N)却不是十进制数字(Nd)。

图 2-1 · 单个字符的 Unicode 档案逐栏实测,末行 Block 由内嵌区间表查出。可输入任意字符或点预设。

3 · 第一栏 General_Category

每个码点恰好属于一个 gc 小类;小类首字母就是大类。

表 3-1 · 七个 gc 大类及其码点数(全量实测的基数,随 Unicode 版本微涨)。
大类 含义 码点数 小类 例子
\p{L} Letter 字母 145 672 Lu Ll Lt Lm Lo A é 中 ٱ 𝑥
\p{M} Mark 标记(组合用) 2 543 Mn Mc Me é 拆开后的 ◌́
\p{N} Number 数字 1 924 Nd Nl No 7 ٧ Ⅻ ① ½
\p{P} Punctuation 标点 856 Pc Pd Ps Pe Pi Pf Po , 、 « » _
\p{S} Symbol 符号 8 617 Sm Sc Sk So + € ^ 😀
\p{Z} Separator 分隔 19 Zs Zl Zp 空格 NBSP 全角空格
\p{C} Other 其他 954 481 Cc Cf Cs Co Cn 控制符 ZWJ 未分配区

注 · \p{C} 是大头,因为 U+0000~U+10FFFF 共 1 114 112 个码位里大部分还没分配(Cn)。另外 emoji 😀 的 gc 是 So(其他符号)——「是不是 emoji」记在二元属性栏,不在 gc 栏。\p{P} 标点的七个子类、脚注符号为什么全是 Po,见 \p{P} 标点七子类

把一段文本逐码点按大类着色,就能看清 gc 怎么切分真实文本。

图 3-1 · 一段文本逐码点按 gc 大类着色。可粘贴任意文本或点预设,观察各大类的分布与计数。

4 · \w \d 的 ASCII 视野

\w \d 是钉死的 ASCII 集合:\w = [A-Za-z0-9_]\d = [0-9],开了 u 也不变。要按「档案」理解的字母 / 数字,得用 \p{L} / \p{Nd}

图 4-1 · 十个字符在 ASCII 视野的 \\w \\d 与按档案匹配的 gc 属性类下的命中对照,每格实时测出。

警示 · 三个易错点。其一,国际化校验「是字母」要用 \p{L}\wé 都不认。其二,\p{Nd}(十进制位)不等于 \p{N}(一切数字)—— ½ 是 N 却非 Nd,解析数值时别把 ½ 当数位收进来。其三,_\w 里却既非字母也非数字,它的 gc 是 Pc 连接标点。

5 · 第二栏 Script 与 sc / scx 之辨

Script(sc) 给每个码点记一个主属文字;但很多字符是几套文字共用的——顿号 中日韩都用,长音符 平假名片假名都用,它们的 sc 只能记 CommonScript_Extensions(scx) 记的才是「哪些文字在用它」这个集合。

图 5-1 · 同一段混排文本在 sc 与 scx 两种查法下的命中差异。可选文字、切换查法,观察 CJK 标点等共用字符的归属。

建议 ·「提取一段汉字 / 假名 / 谚文」用 \p{scx=…} 通常更接近直觉——\p{sc=Han} 会把 这些 CJK 标点漏在外面,它们 sc=Common。顺带一提,𝕏 𝑥 这类数学字母的 Script 也是 Common——「长得像哪国字」不等于「属于哪套文字」。

6 · 第三栏二元属性

gc 和 Script 每栏只有一个值;二元属性则是一排独立的 Yes/No 勾选框,一个码点可以同时勾中好几个。JS 支持几十个,常用的如 \p{White_Space}\p{Math}\p{Alphabetic}\p{Uppercase}\p{ASCII},以及 emoji 六属性 整页在讲的 \p{Emoji} 一家。

\p{White_Space}(25 个)与 \s 两份名单高度重合但不相等:

图 6-1 · 六个空白字符在 \\s 与 White_Space 属性类下的命中对照。差异恰好落在 BOM 与 NEL 两处。

警示 · 差异恰好两处:BOM U+FEFF\s 却不算 \p{White_Space}(ES 把 ZWNBSP 纳入 WhiteSpace,Unicode 档案里它是 Cf 格式字符);NEL U+0085\p{White_Space} 却不算 \s。其余成员一致——空白与换行 的完整清单讲的就是 \s 这份名单。

\p{Math}(2322 个)与 \p{Sm}(960 个)则展示了二元属性可以跨着 gc 切:

图 6-2 · 七个字符在 Sm、Math 与 L 三个属性类下的命中对照。数学斜体与阿列夫 gc 是字母却勾了 Math。

注 · 𝑥(数学斜体)、(阿列夫)gc 都是字母,却勾了 Math;普通的 x 反而 Math=No。这正是二元属性的价值:「数学符号」这个概念横跨 Sm / L / P 好几个 gc 类,只有档案里单独一栏才装得下。同理 \p{Alphabetic}(147 421)也比 \p{L}(145 672)大——多出 Nl 与一批组合标记。2322 个 Math 字符的全名单见 \p{Math} 全量速查

7 · Block 那一栏 JS 不支持

档案里还有一栏 Block:码点住在哪个连续码位区段,比如 住在 Mathematical Operators(U+2200~22FF)、 住在 CJK Unified Ideographs(U+4E00~9FFF)。但 JS 的 \p{} 只支持 gc / Script(_Extensions) / 二元属性三种,Block 写法直接 SyntaxError(Java 的 \p{InGreek}、.NET 的 \p{IsGreek}、Python regex 模块的 \p{Block=…} 才有)。想按 block 匹配,JS 只能手写码位区间。

图 7-1 · Block 写法的报错,以及手写码位区间与 Math 属性类在同一段文本上的命中差异。

警示 · 而且 block 是住址、不是身份,就算能用也常不是想要的。其一,同一个 block 里能住好几套文字——CJK Symbols and Punctuation 区里的 Script 是 Common。其二,同一类字符散落在多个 block——上图实测里 ± × ÷ 住 Latin-1、𝑥 住 Mathematical Alphanumeric Symbols,手写 U+2200~22FF 区间全漏掉,而 \p{Math} 一个不漏。按「身份」查档案,几乎总比按「住址」圈选区间更可靠。

建议 · 页首探针的「住址」栏之所以查得到,是因为本系列 regex.ts 内嵌了一份官方 Blocks.txt(Unicode 17.0.0)的区间表,blockOf(cp) 二分一查就知道任意码点的住址。

8 · 全量速查:346 个 block 的住址簿

Unicode 17.0.0 共 346 个具名 block,其余空隙是 No_Block。按码位平面分段,搜索支持三种查法:块名(忽略大小写 / 空格 / 连字符,如 superscriptlatin1)、码点 hex(如 2070U+2070),或直接粘一个字符。

图 8-1 · 全部 346 个具名 block 按平面分组的住址簿。可按块名、码点 hex 或单个字符检索。

建议 · 和别的页串起来看:空白与换行\s 完整清单对应这页的 White_Space 勾选框;emoji 六属性 讲的 \p{Emoji} 一家就是六个二元属性;自研引擎@vega/parsing/regex 同样支持 gc / sc / scx / 二元属性三种写法、同样不支持 Block——名字解析也按 JS 的严格拼写来。