算法与数据结构 / regex · Unicode 字符模型与一个自研引擎 / 空白与换行:点号与 \s 各管什么 待审核 29 / 36
. 与 \s · 看不见的字符

空白与换行:点号与 \s 各管什么

语法速览.\s 一笔带过,这页把两者掰开。JS 正则的 . 在没有 s 标志时默认不匹配四个行终止符;而 \s 匹配的是 ECMAScript 的 WhiteSpace 加 LineTerminator 全集,涵盖 NBSP、各种排版空格、全角空格,甚至 BOM。这些字符看不见却真实存在。

1 · 四个行终止符

JS 里的行终止符恰好四个:\n\rU+2028(行分隔)与 U+2029(段落分隔)。. 默认跳过它们,开 s 才让 . 吃下换行;它们同时也都属于 \s。常被忘记的是后两个。另外 m 改的是 ^$ 的含义,不是 .;让 . 跨行的是 s,两者别混。

表 1-1 · 四个行终止符与两个对照字符在 /./、/./s 与 \s 三种判定下的实测结果。

2 · \s 的完整清单

\s 的成员按宽度排开能看出 thin、en、em 与全角的宽窄差。这张表里每一行的命中都是实时用 /^\s$/u 测出来的。

表 2-1 · ECMAScript \s 的全部成员,Zs 类用绿色标尺夹出真实字形宽度,末列是实测命中。

警示 · 两处反直觉。其一,BOM U+FEFF\s 命中——它在规范里是 ZWNBSP,算 WhiteSpace,所以开头带 BOM 的文本,\s 会把它当空白。其二反过来,零宽空格 U+200B 名字带 space,\s 却不认,它是格式字符 Cf,不在 WhiteSpace 里;U+200CU+200D 同理。

3 · 隐形字符扫描

从别处复制来的文本常混入 NBSP、全角空格与 BOM,肉眼看着是普通空格,却让 ===trim() 与数字解析全部失灵。把文本粘进来,每个空白或不可见字符都会被标出来。

图 3-1 · 一段文本里的空白与不可见字符逐个标注,给出简称与码点。可粘贴文本或点预设。

4 · White_Space 属性与「看不见」的边界

上面那张是 ECMAScript \s 的清单;Unicode 自己另有一个二元属性 White_Space,两份名单高度重合却不相等。而「该被渲染器隐藏」的精确属性又是第三个:Default_Ignorable_Code_Point。下面把常见的隐形字符按用途分组摊开,每行实时测这两个属性。

图 4-1 · 隐形字符按用途分组,逐个给出 gc、Default_Ignorable 与 \s 的实测结果。可搜索码位、名称或直接粘一个字符。

注 · 几个要记的点。DI 列大多打勾,但有一批 gc=Cf 的活动格式符 DI=No,例如 U+0600 框住后续数字、U+FFF9U+FFFB 的行间注释——它们不是隐藏自己而是影响排版。盲文空白 U+2800DI=No,属于伪空白:gc 是 So、有真字形,只是画出来是空的,与 HANGUL FILLER 一类是绕过「禁止空名」校验的主力。还有 U+180E 曾经属于 White_Space,Unicode 6.3 起降级为 Cf,老代码若硬把它当空白就会与今天的属性对不上。没有单一属性能一网打尽「看不见」,实务上多用 \p{Cf} 并上 \p{Default_Ignorable_Code_Point},再补 U+2800 之类,或维护显式清单。

5 · 参考文献

  1. Ecma International. ECMA-262: White Space. \s 前半份名单的规范定义,BOM 属于 WhiteSpace 的出处(表 31 列有 U+FEFF <ZWNBSP>)。tc39.es
  2. Ecma International. ECMA-262: Line Terminators. 四个行终止符的规范定义。tc39.es
  3. Unicode. UAX #44: Unicode Character Database. White_SpaceDefault_Ignorable_Code_Point 两个属性的定义与差别。unicode.org
  4. Unicode. PropList.txt. White_Space=Yes 的全量清单,与 \s 对照即见两份名单的差。unicode.org
  5. MDN. 字符类转义. \s\d\w 各标志位下的行为速查。developer.mozilla.org
  6. MDN. Wildcard. . 单独一页:默认不匹配行终止符,s 标志改变这一点。developer.mozilla.org