算法与数据结构 / regex · Unicode 字符模型与一个自研引擎 / \p{S} 符号四子类:竖线是符号、脱字符不是重音、emoji 归哪一类 待审核 7 / 36
基础 · \p{S} 符号四子类

\p{S} 符号四子类:竖线是符号、脱字符不是重音、emoji 归哪一类

接着 \p{P} 标点七子类 那页——码点档案 的 gc 表里,和 \p{P} 并排的还有一行 Symbol(符号)\p{S},它切成 4 个子类 Sm Sc Sk So(数学 / 货币 / 修饰 / 其他)。反直觉的归类集中在此:+ | ~ 是数学符号 Sm(不是标点)、$ 是货币 Sc^ ´ ¨ 是修饰符号 Sk(看着像重音,实为独立符号),而 😀 © 全挤在 catch-all 子类 So 里。

注 · gc 子类是「身份」,不是「住址」。一个码点的 General_Category 在 UCD 里是单值的——| 的 gc 永远是 Sm,无论它在代码里当「或」、当管道还是当绝对值竖线。这和 Block(住址)不同:住址只说它住哪段码位。本页所有「是不是某子类」都用 /^\p{…}$/u 当场测,不是查表。

1 · 看着像符号却不是符号的字符

挑选符号最常见的误区是凭字形归类。下面这些天天见、像极了符号的字符,gc 落在标点 P 或字母 L 里。

图 1-1 · 七个「看着像符号」的字符及其真实 gc,每行的判定当场实测。

警示 · 两点结论。其一,百分号 % 、角分秒 看着是「记号」,gc 却是标点 Po(和脚注符号同桶,见 标点七子类);§ 在 Unicode 6.1 之前曾是 So,之后并入 Po。其二,微符号 µU+00B5)、欧姆常用的 Ω(实为希腊大写 Omega U+03A9)、抑扬符 ˆU+02C6)看着是符号,gc 却是字母(Ll / Lu / Lm)——「长得像」从来推不出「是哪类」。

2 · 四个子类的实测基数

每个码点恰好属于其中一个。So 是 catch-all 子类,独占近九成。

表 2-1 · 四个符号子类及其码点数,合计 8617 个(Unicode 17.0)。计数由全码空间逐码点枚举所得。emoji 几乎都落在 So 一类。
子类 名称 码点数 典型成员
So Other 其他符号 7468 © ® ™ ° № ℃ ★ ♠ ♥ ☑ ░ █ 😀
Sm Math 数学符号 960 + < = > | ~ ± × ÷ ¬ ∑ ∫ ∞ → ↔
Sk Modifier 修饰符号 125 ^ ` ´ ¨ ˜ ¯ ¸ ˚
Sc Currency 货币符号 64 $ ¢ £ ¥ € ₩ ₹ ₿ ฿ ¤

3 · 探针:任意字符落在哪个子类

图 3-1 · 单个字符的符号子类归属逐项实测,不是符号时报它真实的大类。可试竖线(是 Sm)、脱字符(是 Sk)、百分号(实为标点 Po)与 emoji(是 So)。

4 · 四子类着色器

把一段含各种符号的文本逐码点上色,属于 \p{S} 的按四子类着色、其余灰显。

图 4-1 · 一段文本里的符号按四子类着色。可粘贴任意文本或点预设,观察各子类的分布与计数。

5 · 全量速查:8617 个符号

四个子类的全部码点,先按 gc 子类、再按 Unicode block 分组。搜索支持码位 hex(如 2103U+2103)、Unicode 名(如 degreearrow)、直接粘一个字符,或块名(如 currencyletterlike)。

图 5-1 · 全部 8617 个符号码点按子类与 block 分组的速查表。默认只渲染选中的一类,搜索时跨全部四类查找。

注 · So 占了近九成。7468 个 So 里绝大多数是 emoji 与 CJK 兼容符号——本页只把它们和 Sm/Sc/Sk 并列计数;emoji 的拆解见 Basic_Emoji 等几页,「画图用」的那撮见 形状符号。点子类按钮只渲染选中的那一类(默认 Sm),所以即便 So 有 7468 个也不卡顿。

6 · Sm 与 Math 属性的包含关系

Sm(gc 子类)和 \p{Math}(二元属性)常被当成一回事,实为两套坐标:gc 子类是单值身份,Math 是能跨着 gc 切的标签。

图 6-1 · 四个符号子类各自有多少成员被 Math 属性收进去。Sm 整桶被含住,Sc 一个都不是。

注 · 所以「数学符号」有两种问法:按 gc 问 \p{Sm} 得到 960 个「形状就是算符」的字;按属性问 \p{Math} 得到 2322 个「数学语境会用」的字,含被借用的字母与记号。详见 \p{Math} 全量速查——那页正是把这 2322 个全列了出来。

7 · Sk 间隔修饰符与 Mn 组合记号

^ ´ ¨ 这些看着像「重音 / 声调符」,gc 却是 Symbol, modifier(Sk)——它们是独立、占一格的字符,自己就能打出来。这和真正的组合记号 Mn(Mark, nonspacing)是两回事:后者零宽、必须附在前一个字母上叠加渲染。同一种「帽子」,Unicode 备了两份。

图 7-1 · 六组「帽子」的间隔符号与组合记号并排,gc 当场实测。右列借占位圆显形。

警示 · 同一段「修饰符」区里也分三类。Spacing Modifier Letters 块(U+02B0–02FF)里:发音符号 ˆU+02C6ˉU+02C9)实为字母 Lm(Letter, modifier)、不是 Sk;而 ˜U+02DC˚U+02DA)才是 Sk。判定一律以 gc 为准,别按所在块或长相猜。

8 · 摄氏度是兼容字符

摄氏 U+2103)、华氏 U+2109)的 gc 都是 So——不是标点、不是 Sm、也不是字母,和 emoji 😀 同属「其他符号」。但它们还有一层身份:住在 Letterlike Symbols 区(U+2100–214F)、是为旧编码与 CJK 等宽排版预先组合好的兼容字符。

图 8-1 · 三个度数记号的 gc 与 NFKC 归一化结果,当场调用归一化实测。

警示 · 它们是兼容字符,日常不该用。 的 NFKC 分解就是 °C / °F——规范文本里应写度数符号 °U+00B0,也是 So)加字母 C,而不是这个预组合单字。否则 '℃' !== '°C';比较与查重前先 normalize('NFKC'),细节见 归一化

注 · 同区更隐蔽的例外是:住在 Letterlike Symbols 区不等于 gc 是符号。紧挨着的 Ω OHM SIGN(U+2126)和 K KELVIN SIGN(U+212A)长得像符号,gc 却是 Lu(大写字母),而且已被弃用——canonical 等价于普通的希腊 ΩU+03A9)与拉丁 KU+004B),连 NFC 都会把它们折叠掉(Å ANGSTROM U+212BÅ U+00C5 同理)。所以 ℃ ℉ °SoΩ K Å 反而是字母。

建议 · 三种「看着都像记号」凑齐了:预组合兼容字符 So,NFKC 拆成 °C)、管道符 |Sm)、脚注用的 * Po,见 标点七子类)——同样像符号,gc 分属 symbol 与标点两个大类、So / Sm / Po 三个子类。本页是 码点的档案 里 gc 表中 \p{S} 那一行的展开,和标点七子类是一对姊妹页。

9 · 写成符号的汉字

Kangxi Radicals 块(U+2F00U+2FD5)的 214 个码点画出来与汉字无异: 在多数字体下逐笔相同。它们的 gc 却全是 So,与 同属一类——为往返兼容既有标准而另开码位的兼容字符。台湾的 CNS 11643 早在汉字之外为部首单独编过码(按部首检字需要一份独立的部首表),Unicode 3.0 照此收入,两边才能无损往返。隔壁的 CJK Radicals Supplement(U+2E80U+2EF3)收的是 这类变形偏旁,115 个已分配码点同样全是 So。两块合计 329 个,都算在 §2 表里 So 那 7468 个之内。

图 9-1 · 三对部首与汉字并排,逐格实测 gc、字母类判定、Script 判定与 NFKC 结果。末两行取自 CJK Radicals Supplement,其中 ⺟ 是该块里少见的备有兼容分解者。

gc 是 So 而非 Lo,后果是按字母类筛汉字的写法一个都不命中:\p{L}\p{Unified_Ideograph}[\u4e00-\u9fff] 全判 false,命中的只有 \p{Script=Han}。Script 与 gc 是两套正交的坐标,这与 §6 里 SmMath 的关系同理。两条属性的交集给出一个恰好的判定式:全码空间里 \p{Script=Han}\p{So} 同时成立的码点,就是上述两块的 329 个,一个不多一个不少。

NFKC 能不能救回,两块的答案不同。Kangxi Radicals 的 214 个全部备有兼容分解,normalize('NFKC') 逐个折回对应汉字;CJK Radicals Supplement 的 115 个里只有 U+2E9F)与 U+2EF3)折得回去,其余 113 个原样不动。差别不在实现而在有没有可折的目标: 是只作偏旁的变形写法,本就没有对应的独立汉字; 同形,才有分解可写。

注 · 本节初稿按「部首块都能靠 NFKC 折回汉字」写,逐码点扫过两块之后数字与结论都改了:康熙部首 214 个确实全部可折,补充块 115 个里可折的只有 2 个。「兼容字符总能归一化掉」这句常见说法,落到部首块上只对半边成立。

图 9-2 · 一段文本里的部首块字符逐字标红,并与 NFKC 之后的同一段并排。可粘贴文本或点预设,其中「NFKC 救不回的」一条转换后仍有红格。

警示 · 这批码点在拼音与五笔输入里几乎打不出来,文本里出现多半来自 OCR、PDF 抽取或旧系统转码,而屏幕上与汉字无从分辨。后果落在三处:按字母类写的校验与分词一律漏掉它们;indexOf('小') 找不到 ;字体缺这批 glyph 时会回退成另一副面孔。入库前统一 normalize('NFKC') 挡得住康熙部首那 214 个,拦不住补充块剩下的 113 个,后者只能按码点区间显式拒收。

10 · 参考文献

  1. Unicode. UAX #44 §General_Category Values. gc 全部大类与小类的官方定义表:Sm/Sc/Sk/So 各自收哪些字符、判定依据,以及它是单值属性这件事的出处。unicode.org
  2. Wikipedia. Currency Symbols (Unicode block). Sc 货币符号:专属的 Currency Symbols 块(U+20A0–20CF)逐个码点,加散在别处的 $ ¢ £ ¥ ฿ ₿——本页 64 个 Sc 的来处。en.wikipedia.org
  3. Codepoints. 逐码点数据库. 查任意码点的 gc、所属 block、是否 Math 等全部属性——核对本页 ^ ´ ¨Sk 归类、µ Ω ˆ 为何是字母最直接。codepoints.net
  4. Unicode. Code Charts: Kangxi Radicals (U+2F00–U+2FDF). 214 个康熙部首的官方 glyph 与名称逐个列出,另有 CJK Radicals Supplement 一份——本页 §9 两块码点的出处。unicode.org
  5. Unicode. The Unicode Standard, Chapter 18: East Asia. 两个 radical 块的收录用途,以及它们与 CJK Unified Ideographs 的分工。unicode.org