Unicode 片假名校验:长音符明明在词里,Script 为什么不认它

前天 3阅读

看起来属于一个词,不等于只有一种脚本属性

给片假名输入框写正则时,很容易使用 Script=Katakana,再用加号要求一个或多个字符。单独测试“コ”能通过,换成“コーヒー”却失败。问题出在中间的长音符“ー”,也就是 U+30FC:它的 Script 属性是 Common,而不是 Katakana。字符的书写用途和单一属性值并不是完全相同的概念。

Unicode 另外提供 Script_Extensions,用一个集合描述字符可与哪些脚本共同使用。长音符的这个集合包含 Hiragana 与 Katakana,因此能够通过两者的扩展属性测试。Common 本身并不意味着“放进任何文本都合适”,更不意味着可以把全部公共字符无条件加入输入白名单。

Unicode 片假名校验:长音符明明在词里,Script 为什么不认它

配图为 AI 生成的概念插画:两条色带共享一枚标记,表现字符与多个脚本的使用关系。

分别检查字符和整个词

下面的例子使用 JavaScript Unicode 属性转义,并显式启用 u 标志。第一组逐一检查长音符,第二组验证整个词,最后故意加入数字,检查规则是否意外放宽。这里使用否定的任意字符前瞻保证输入真正结束,避免美元锚点在末尾换行前也可能匹配的细节干扰结果。

const mark = "ー";
console.log("Script Katakana:", /\p{Script=Katakana}/u.test(mark));
console.log("Script Common:", /\p{Script=Common}/u.test(mark));
console.log("Extensions Katakana:", /\p{Script_Extensions=Katakana}/u.test(mark));
console.log("Extensions Hiragana:", /\p{Script_Extensions=Hiragana}/u.test(mark));

const scriptOnly = /^\p{Script=Katakana}+(?![\s\S])/u;
const extensions = /^\p{Script_Extensions=Katakana}+(?![\s\S])/u;
const broadCommon = /^[\p{Script=Katakana}\p{Script=Common}]+(?![\s\S])/u;
for (const value of ["コーヒー", "コーヒー1", "コーヒー\n"]) {
  console.log(JSON.stringify(value), scriptOnly.test(value), extensions.test(value));
}
console.log("Common also accepts digit:", broadCommon.test("コーヒー1"));

第一组依次得到 false、true、true、true,分别对应单一片假名属性、Common、片假名扩展属性和平假名扩展属性。整词测试中,Script 版本拒绝“コーヒー”,扩展属性版本接受它;扩展属性仍会拒绝带数字的版本,以及尾随换行的版本。测试字符和测试整串应分开看,避免局部匹配成功掩盖其余输入。

不要靠把 Common 全部放进来修补

一个直接但过宽的修补办法,是把 Katakana 与 Common 合并。这样确实能包含长音符,却也可能接受许多业务并不想接收的字符,例如示例里的 ASCII 数字。扩展属性的价值在于更贴近脚本共同使用关系,而不是笼统放行所有共享符号。是否允许空格、中点或标点,仍应逐项确认产品要求。

反过来,扩展属性也不是“这是一个有效日语单词”的证明。它只提供字符层面的分类,无法判断拼写、语义或读音。仅含多个长音符的字符串,也可能通过示例规则。如果表单要求真实姓名、合法词条或特定读音,还需要更具体的业务校验,并准备清楚的错误提示。

属性名与输入策略都要写清楚

在 JavaScript 中应明确写 Script 或 Script_Extensions,不能想当然把其他正则引擎的简写搬过来。属性名称、取值和大小写也应按该引擎支持的别名填写。若运行时过旧而不支持 Unicode 属性转义,应先确认部署环境能力,不能简单删掉 u 标志继续使用同一表达式。

实际文本还可能出现半角形式、组合字符和复制粘贴带来的空白。是否做规范化、是否保留原始输入,以及是否允许这些形式,都应在验证前确定。升级运行时后,Unicode 数据版本也可能变化,保留包含长音符、数字、换行和真实业务样本的回归测试,比只测试单个常见片假名更有价值。

把验证失败的字符单独输出其码点,也能减少肉眼判断的误差。看起来接近的横线、减号和长音符并不是同一个字符,不能因为外形相似就认为共享相同属性。这个例子固定使用长音符本身,复制测试时也应保留它的实际字符。

如果错误提示只写“格式不正确”,用户很难发现是末尾空白还是字符类别导致。可以按规则显示具体原因,但不要在未说明的情况下静默删除输入,免得验证通过后内容也被改变。

参考资料

Unicode UAX #24:Script 与 Script_Extensions;Unicode 数据:ScriptExtensions.txt;Unicode 数据:Scripts.txt;ECMAScript:Unicode 属性匹配规则

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。