\p{P} 标点七子类:脚注符号 * † ‡ § ¶ ‖ 到底归哪一类
码点的档案页的 gc 表里,\p{P}(Punctuation 标点)只占一行——但它
其实切成 7 个子类 Pc Pd Ps Pe Pi Pf Po。传统脚注符号 * †
‡ § ¶ ‖ 全落在成员最多的 catch-all 子类 Po 里;而看着
«成对»的开括号 / 闭括号(Ps / Pe)、前引号 / 后引号(Pi / Pf)
数量却配不齐。这页把这 7 个子类全用原生 /\p{…}/u 实测着看清楚:每个码点恰好属于一个
gc 子类,首字母 P 就是它的大类。
* 的 gc 永远是 Po,无论它出现在脚注、乘法还是脚标里。这和 Block(住址)
不同:住址只说它住哪段码位。本页所有「是不是某子类」都用 /^\p{…}$/u 当场测,不是查表。
先回答那个问题:六个脚注符号全是 Po
排版里第一个脚注用 *、第二个 †、第三个 ‡……(传统排版有固定次序 * † ‡ § ‖ ¶——这是
排版约定的次序,Unicode 的码位顺序根本不照它走:§ ¶ 早早躺在 U+00A7/00B6、‖
在 U+2016、† ‡ 才到 U+2020/2021,被彻底打散)。它们在 gc 里没有专属子类,
一律归入「其他标点 Po」——下表每行的 gc 都是当场 /^\p{Po}$/u 测出来的:
‖(U+2016)是 Po,而长得像它的管道符 |(U+007C)却是
Sm(数学符号,根本不在 \p{P} 里);§ ¶ 在
Unicode 6.1(2012)之前曾是 So(其他符号),之后才并入 Po——老数据可能仍标成 Symbol。
\p{Terminal_Punctuation} / \p{Sentence_Terminal} / \p{Dash} /
\p{Quotation_Mark} 一律 No。唯一「全 Yes」的是 \p{Pattern_Syntax}——但它是
干扰项(red herring):它的含义是「保留给编程语法、不可作标识符」,把 ! ? % &
和上百个符号一并圈入,同样挑不出脚注。下面这两行是当场 /\p{…}/u 实测的:
category 查不出「脚注用途」—— 但可按字符 Name 筛选候选
既然六个脚注符号在 gc 里全是 Po、和 ! ? % & 同属 641 个成员的 catch-all 子类,那
「哪些字符能当脚注 / 引用记号」这件事 category 根本不编码 —— 它是排版约定(editorial convention,
Chicago / Oxford 等 style manual 沿袭的活字传统),不是字符属性。Unicode 唯一沾边的可机读线索是
字符的 Name(及 NamesList.txt 注解),比如 U+203B 干脆就叫 REFERENCE MARK。
所以只能反过来按名字筛选候选,而非用 category 过滤。下面按 Name 关键词筛(数据取自官方 UnicodeData.txt;
\p{} 查不到 Name,和 Block 一样,故走内嵌名单):
Po 里九成以上跟脚注无关,「属于 Po」推不出「能当脚注」;
其二 即便改用 Name 关键词,命中也横跨 Po / Sm / So / Mn / Lo / Cf ——
「名字里有 ASTERISK」既不等于「是标点」(∗ ASTERISK OPERATOR 是 Sm、❃ 是 So 装饰、
◌͙ COMBINING ASTERISK BELOW 是 Mn),更不等于「能当脚注」。Name 是线索不是标签;
「footnote-ness」这种语义 Unicode 不编码 —— 真要判定,得查 style manual,不是查码点属性。
U+070D SYRIAC HARKLEAN ASTERISCUS
的注解明写 “marks the beginning of a phrase, word, or morpheme that has a marginal note”——语义上就是旁注记号、名字也含
ASTERIS、gc 还是 Po,却没被收进上面的候选(它是 Syriac 专用);反过来 ⁂ ASTERISM(U+2042)
在 NamesList 里一条注解都没有,却作为正统排版记号被收了。可见「是不是脚注符号」最终由 style manual 的排版约定裁定——
UnicodeData / NamesList 只能帮你缩小候选,最后一步还得人工按约定取舍。
\p{P} 的七个子类(Unicode 17.0 实测基数)
每个码点恰好属于其中一个。Po 是 catch-all 子类,独占四分之三:
| 子类 | 名称 | 码点数 | 典型成员 |
|---|---|---|---|
Po | Other 其他标点 | 641 | . , ! ? ; : … · * † ‡ § ¶ ‖ 、 。 |
Ps | Open 开标点 | 79 | ( [ { 「 『 ( |
Pe | Close 闭标点 | 77 | ) ] } 」 』 ) |
Pd | Dash 横线标点 | 27 | - – — ~ |
Pi | Initial 前引号 | 12 | « ‹ “ ‘ ‛ ‟ |
Pf | Final 后引号 | 10 | » › ” ’ |
Pc | Connector 连接标点 | 10 | _ ‿ ⁀ _ |
探针:任意字符落在哪个子类
输入一个字符,实时测它是不是 \p{P}、是 7 子类里的哪一个;末行还报它的「配对角色」(开 / 闭 / 引号始 / 引号终)。
试试 _(下划线竟是标点 Pc)、|(不是标点,是 Sm)、、(顿号 Po)。
逐项原生 /^\p{…}$/u 实测
七子类着色器:把标点从文本里挑出来上色
粘一段中英混排文本,逐码点把属于 \p{P} 的字符按 7 子类上色,其余字符灰显——看 gc 怎么在真实标点上切分:
开闭、引号为什么配不齐
直觉上「有开就有闭」,可实测数量对不上:Ps 79 ≠ Pe 77、Pi 12 ≠ Pf 10,
两组各差 2。根源是同一件事——引号天生塞不进「开 / 闭」二分法。gc 标的是字符自身的形状 / 角色,
不是「配对关系」,于是配对就破了。落单的两组都是引号(下面每个 gc 当场实测):
Ps 比 Pe 多 2:德式低位引号 ‚(U+201A)„(U+201E)被归成开标点 Ps
(德语 „…“ 在基线起头),但它们的「闭合形」是抬高的 ‘ “,而那俩被归进了
Pi 而非 Pe —— 于是这两个低引号在 Ps 有身位、Pe 找不到对应。Pi 比 Pf 多 2:反 9 字形引号 ‛(U+201B)‟(U+201F)是「翻转当开引号用」的变体,
没有专属闭合形(要闭合直接用普通的 ’ ”),于是 Pi 这边凭空多两个。
Bidi_Paired_Bracket / Bidi_Paired_Bracket_Type(open / close / none)和 Bidi_Mirrored。
引号因为跨语言方向不固定,根本没被纳入 bracket 配对。所以「成对」靠这套属性表达,而不是指望
Ps 数量等于 Pe。可惜 JS 正则的 \p{…} 不支持 Bidi 系列属性(和
Block一样查不到),本页只能用 gc 把现象呈现出来。
\p{…} 里 gc「七大类 30 小类」表中
\p{P} 那一行的展开,和 \p{Math} 全量速查 是「按身份深挖一类」的两个样本
(一个是 gc 子类、一个是二元属性)。
相关链接
- UAX #44 · General_Category Values · unicode.org gc 全部大类 / 小类的官方定义表:Pc/Pd/Ps/Pe/Pi/Pf/Po 各自收哪些字符、判定依据,以及它是单值属性这件事的出处。
-
UAX #9 · Unicode Bidirectional Algorithm · unicode.org
Bidi_Paired_Bracket/Bidi_Mirrored的来处:括号「成对」与镜像怎么定义——为什么配对信息在这里、不在 gc 的 Ps/Pe 数量上。 - Codepoints · 逐码点数据库 · codepoints.net 查任意码点的 gc、Bidi_Paired_Bracket、所属 block 等全部属性——验证本页 ‚ „ ‛ ‟ 的 Ps/Pi 归类与配对关系最直接。