删除音标符号文本

把文本里的重音符号去掉,但一个字母都不少。先按 Unicode NFD 规范把预组合字符拆成「基字母 + 组合附加符号」,再删掉附加符号,所以预组合的 é 和分解的 e+U+0301 都能正确处理。

删除范围

等待处理。

逐词码点对照表(默认 12 行,数值仅作演示)
词原始码点结果码点去掉清洗后
Café541Cafe
jalapeño880jalapeno
piñata660pinata
Ångström880Angstrom
dèjà541deja
naïve651naive
Zoë431Zoe
fiaçé550fiace
ĀāáǎàÀ660AaaaaA
ēéǖ330eeu
Ω fi ㎡ ½ ⑩990Ω fi ㎡ ½ ⑩
ǭ(o+双锐)312o

使用方法

  1. 把要处理的文本粘进「待处理文本」。默认示例含 113 个码点,覆盖法语、西班牙语、越南语、瑞典语、葡萄牙语、荷兰语与拼音里的带调字母,末尾还放了 5 个兼容字符用来演示降级开关。
  2. 三个开关决定删除范围。默认同时勾上「只删拉丁音标」和「删全部非间距标记」;两者都勾时以 \p{Mn} 为准,覆盖泰文、印地语、希伯来语等拉丁音标区间之外的元音符号。
  3. 要连 fi ㎡ ½ ⑩ Ω 这类兼容字符一起降级成 fi m2 1⁄2 10 Ω,就勾上第三个开关,页面会改用 NFKD 起步。
  4. 点「删除音标」,或在文本框里按 Ctrl+Enter(macOS 按 Cmd+Enter)。结果区上方给出原始码点数、NFD 后码点数、删除数量与被删字符的码位清单。
  5. 默认示例的实测结果:113 个码点经 NFD 变成 130 个,删掉 21 个组合标记后剩 109 个;开兼容字符降级则是 114 个。

这个删除音标符号文本工具能解决什么

把带重音的外文姓名、地名、菜名转成纯 ASCII,方便导入只接受 ASCII 的老系统。MySQL 的 latin1 字段、某些物流单的收货人栏位、URL 路径参数,遇到 é 或 Å 会直接报错或乱码,删掉音标是最省事的兼容方案。

做文本去重与比对前的归一化。搜索 Café 时用户可能输入 Cafe、CAFÉ 或分解形式的 cafe 带锐音符,两种形式在数据库里是不同字符串;统一去音标后四者都能命中同一条记录。

做文件名与 URL 规范化。同一个词在不同系统里可能存成预组合或分解形式,导致 resume.pdf 和带重音的 résumé.pdf 变成两个不同文件;清洗后只剩基字母,冲突自然消失。

提取纯字母骨架做索引。jalapeño、piñata、fiance 清洗后分别是 jalapeno、pinata、finance,可以直接塞进搜索索引或拼音对照表。

本页只删 Unicode 定义的组合附加符号,不删任何有独立语义的字符。要删标点请用删除标点工具;要整段清成只剩 ASCII(含删掉中文)请用删除非ASCII字符;去重是另一件事,见删重复字符工具。删除音标符号文本与这三者的能力边界不重叠。

原理与规则说明

整条链只有两步:text.normalize('NFD') 然后 .replace(/\p{Mn}/gu, '')。第一步是 Unicode 规范等价(UAX #15)里的「规范分解」,把带重音的预组合字符拆成基字母加上组合附加符号;第二步按 Unicode 通用类别 Mn(Nonspacing_Mark,非间距标记)删掉所有附加符号。u 标志不可省,它是让正则走 Unicode 属性转义而不是按 UTF-16 码元处理。

为什么必须先 NFD,这是本页最关键的一步。带锐音符的 é 在 Unicode 里有两种存在形式:预组合的 U+00E9(1 个码点)和分解的 U+0065 U+0301(2 个码点)。两者显示完全相同,但 === 比较返回 false。只写 replace(/[\u0300-\u036f]/g, '') 的话,分解形式能被删掉,预组合形式一个音标都删不掉——因为 U+00E9 根本不在那个区间里。只有先 normalize('NFD') 把预组合形式强制拆开,两条路才汇到同一起点。

为什么用 \p{Mn} 而不是硬写 U+0300–U+036F 区间。后者只覆盖 112 个连续码位,是拉丁语系专用的音标区。实测:泰文的 U+0E31、印地语的 U+093F、希伯来语的 U+05B8、补充区的 U+1AB0 都不在这个区间内,只删区间会全部漏掉;用 \p{Mn} 则一律清除。页面上两个开关分开的意义就在这里——需要「严格只删西欧音标、保留泰文元音」时才用区间那档。

为什么绝不用 NFKC。NFKC 等于「NFKD 之后再做一次规范合成」,这个合成步骤会把音标合回预组合形式。实测 o + U+0301 + U+0308(o 加双锐)走 NFD 是 3 个码点,走 NFKC 变成 2 个码点 U+00F3 U+0308——先合成了 ó,还剩一个分音符。再对它删 \p{Mn},结果是 ó 而不是 o:锐音符被合回来了,删不掉。所以本页模式一固定 NFD,模式二用 NFKD,二者都不与 NFKC 混用。

为什么 normalize() 属 ES6。它是 ECMAScript 2015 加入 String.prototype 的方法,IE11 没有这个方法,老环境调用会直接抛 TypeError。页面在运行前先探测 typeof String.prototype.normalize === 'function',不支持就给出明确提示而不是返回一个看似正常、其实根本没处理过的结果。

完整示例(数值仅作演示)

默认示例 113 个码点,含 8 组带重音的西文词与 9 个拼音带调字母。走默认配置(NFD + 删 \p{Mn})的逐步实测:

勾上兼容字符降级(改用 NFKD 起步)后结果变成 114 个码点,比 NFD 路径多 5 个:末尾的 Ω fi ㎡ ½ ⑩ 被降级成 Ω fi m2 1⁄2 10,其中 fi 变 2 个码点、½ 变 3 个码点、⑩ 变 2 个码点,同时全角冒号 : 也被折成半角 :。看起来「码点变多了」,但这是把不可读的符号换成了可读字符,属于预期的降级结果。

对照表第 2 行值得单独说:jalapeño 原始 8 个码点、结果也是 8 个,「去掉」一栏是 0。这不是没删——ñ(U+00F1) 被拆成 n + U+0303 又删掉 U+0303,一进一出净变化为零。只看「结果码点」判断有没有生效会被这类词误导,所以对照表同时给出原始、结果与差值三列。

输入范围与对照表

设置项可选范围本页默认值实际作用
只删拉丁音标勾选 / 不勾选勾选删 U+0300–U+036F 共 112 个码位
删全部非间距标记勾选 / 不勾选勾选删 \p{Mn},覆盖泰文、印地语、希伯来语等
降级兼容字符勾选 / 不勾选不勾选改用 NFKD 起步,fi→fi、㎡→m2、½→1⁄2、⑩→10
文本长度1 ~ 200 000 码点示例 113 码点超上限直接报错,不截断

两个删除范围开关的关系是「后者覆盖前者」:\p{Mn} 是超集,包含 U+0300–U+036F 全部 112 个码位。所以两个都勾与只勾后者效果相同,只勾前者则会在泰文上失效。页面默认两个都勾,此时以 \p{Mn} 为准。

逐词对照表默认渲染 12 行,点「加载更多」每次加 12 行;「导出 CSV」写出全部 12 行并带 UTF-8 BOM,Excel 打开不乱码。

输入范围、边界与精度说明

长度上限 200 000 码点,超出会提示「当前 X 个码点,上限 200 000 个码点,请切分成多段」并中止,不做静默截断。实测 290 000 码点的样本走完 NFD 加一次 replace 约需 8.8 毫秒(NFKD 路径约 9.5 毫秒),20 万这个上限留足了余量。

normalize() 不做原地修改,每次调用都新建一个等长字符串,所以处理 20 万码点时峰值内存是输入的两倍以上。这也是把上限设在 20 万而不是 100 万的原因。

逐码点读取必须用 Array.from(text) 而不是 text.split('')。实测 Array.from('\u{1F600}'.repeat(5000)) 得到 5000 个码点,而 '\u{1F600}'.repeat(5000).split('') 得到 10000 个——后者按 UTF-16 码元切分,会把增补平面字符劈成两个孤代理项。本页统计一律按码点算,与字符数统计工具的口径一致。

中文、emoji、全角字母都不受影响。实测「中文测试,。」6 个码点做 NFD 前后完全一致,因为汉字没有预组合形式;\u{1F600} 做 NFD 仍是 1 个码点;全角 ABC 在 NFD 模式下保持 3 个码点不变,只有开兼容字符降级(NFKD)时才会折成半角 ABC。所以「删音标」和「全角转半角」是两件独立的事。

常见误用一:以为结果码点变少才算生效。如上所述,jalapeño、piñata、Ångström 这类全部使用预组合形式的词,删除后码点数一个都不少。要验证是否生效,看对照表的「清洗后」列内容对不对,别只看数字。

常见误用二:拿它当中文繁简转换或全角半角工具。这两类都是不同码点之间的映射,与 Mn 类别无关。删除空格文本处理空白、删除空行处理换行结构,都是不同层面的清理。

常见误用三:在 NFKC 模式下做这件事。合成会把音标合回预组合形式,导致删除不彻底——ǭ(o 加双锐)在 NFD 下正确变成 o,在 NFKC 下会残留成 ó。这不是本页的 bug,是 NFKC 的定义决定的。

常见问题

为什么有的词删完码点数一点没少?

因为那个音标是预组合形式,删除过程是「拆成 2 个码点、再删掉 1 个」,净变化为零。jalapeño 原始 8 个码点、结果 8 个码点就是这种情况。判断有没有生效请看对照表的「清洗后」列内容,不要只看数字。真正会减少的是那些本来就用分解形式书写的词,比如示例里 Café 的 e 带 U+0301,5 个码点变 4 个。

U+0300 到 U+036F 到底覆盖哪些符号,为什么不全用这个区间?

这 112 个连续码位是拉丁语系的组合附加符号区,包括尖音符 U+0300、锐音符 U+0301、变音符号 U+0308、抑扬符 U+0304、尾点 U+0327 等。范围之外的组合标记还有很多:泰文 U+0E31、印地语 U+093F、希伯来语 U+05B8、补充区的 U+1AB0 实测都不在区间内。所以要处理非拉丁文本时必须用 \p{Mn},本页默认走这一档。

兼容字符降级到底改变了什么?

改用 NFKD 起步后,fi 变 fi、㎡ 变 m2、½ 变 1⁄2(3 个码点,用的是 U+2044 分数斜线)、⑩ 变 10、Ω(U+2126) 降级成希腊字母 Ω(U+03A9),全角冒号也会折成半角。默认示例下结果从 109 个码点变成 114 个——数字变多但内容更可读,这是降级而非删除的预期结果。

老浏览器打开提示不支持,是怎么回事?

normalize() 是 ECMAScript 2015 才加入 String.prototype 的方法,IE11 及更早版本没有,调用会抛 TypeError。页面在运行前先检测该方法是否存在,不存在就给出明确提示并停止处理,不会返回一个看似正常其实没处理过的结果。现代浏览器(Chrome、Firefox、Safari、Edge 近十年的版本)都没有这个问题。

只勾「只删拉丁音标」会漏掉什么?

会漏掉所有拉丁音标区间之外的组合标记。最实际的场景是泰文、越南语、印地语、希伯来语:它们的元音符号分别是 U+0E31、U+093F、U+05B8,都不在 U+0300–U+036F 里。只在「需要保留这些语言的元音信息、只想去掉西欧语言重音」时才用这一档,默认配置不走它。

能不能顺便把中文也删掉,只留英文?

那不是音标删除,是整类字符剔除,语义完全不同——「中文测试,。」6 个码点在 NFD 前后一个不少,因为汉字没有组合附加符号,本页对它无能为力。只留 ASCII 请用删除非ASCII字符,那个工具会连中文、标点、emoji 一起清掉。

结果里全角冒号变成半角了,是 bug 吗?

不是,那正是兼容字符降级开关的作用。全角冒号 U+FF1A 属于 CJK 兼容区,NFKD 会把它折成半角 U+003A。不勾那个开关时,NFD 不会动全角字符——实测 ABC 在 NFD 下保持 3 个码点。想保留全角标点,就不要勾降级开关。

粘贴的文本会被上传吗?

不会。全部处理由浏览器原生 normalize() 与 String.prototype.replace 完成,模板里没有任何网络请求代码。可以在开发者工具的 Network 面板确认:页面加载后除自身 CSS 外不再发出任何请求,刷新即清空输入内容。

相关工具

删除音标符号文本只做「组合附加符号」这一件事,下面这些页面各自处理不同类别的字符清理,按需要串联使用更省事。