Copyright © 2004-2021 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and permissive document license rules apply.
本文档基于 万维网字符模型 1.0:基础[CHARMOD],为规范作者、软件开发者和内容开发者提供关于万维网上字符串同一性 匹配的共同参考,从而提高互操作性。
本节描述本文档在发布时的状态。其他文档可能会取代 本文档。当前 W3C 出版物列表以及本技术报告的 最新修订版可在 W3C 技术报告 索引中找到,网址为 https://www.w3.org/TR/。
为了更容易跟踪评论,请为每条评论分别提出单独的议题或电子邮件,并使用 URL 指向 你所评论的章节。
本文档由 国际化 工作组作为 工作组说明发布。
首选使用 GitHub Issues 来 讨论本规范。
作为工作组说明发布并不意味着得到 W3C 会员的认可。
这是一份草案文档,可能随时由其他文档更新、替换 或废弃。除作为进行中的工作外,不应引用此 文档。
本文档由一个根据 2017 年 8 月 1 日 W3C 专利 政策运作的小组制作。 该小组不预期本文档成为 W3C 推荐标准。
本文档受 2020 年 9 月 15 日 W3C 流程文档管辖。
万维网字符模型的目标是促进所有人使用 Web, 无论其语言、文字、书写系统或文化惯例如何,并符合 W3C 普遍访问目标。实现这一目标的一个基本 前提是能够以定义良好且易于理解的方式传输和处理世界各地使用的 字符。
本文档基于 万维网字符模型:基础 [CHARMOD]。 理解该文档中的概念,对于成功理解和应用本 文档非常重要。
万维网字符模型的这一部分涵盖字符串 匹配——即规范或实现用来定义两个字符串值是否彼此相同或不同的过程。它 描述了语义等价的文本可以如何以不同方式 编码,以及这对形式语言中重要的匹配操作所产生的影响 (例如构成 Web 的格式和协议中所使用的那些语言)。
本规范的主要目标受众是 W3C 规范 开发者。本规范及其部分内容可以被其他 W3C 规范引用, 并且它为 W3C 规范以及其他规范定义了一致性标准。
本规范的其他受众包括软件开发者、 内容开发者,以及 W3C 之外的规范作者。 软件开发者和内容开发者实现并使用 W3C 规范。本规范为实现和使用 W3C 规范的实现(软件)和内容定义了一些一致性标准。它还帮助软件开发者和内容 开发者理解 W3C 规范中与字符相关的规定。
本规范中描述的字符模型为规范 作者、软件开发者和内容开发者提供了一个共同参考, 以便在万维网上进行一致且可互操作的文本处理。 这三个群体协同工作,可以构建一个全球可访问的 Web。
本文档通过为文档格式中的字符串 同一性匹配定义规则和流程,定义了与该问题相关的 Web 基础 构建块之一。这些规则是为文档格式中使用的 标识符和结构化标记(句法内容) 设计的,以确保对每一项进行一致处理,并且 面向规范编写者。本 节面向实现者。
本文档分为两个主要部分。
第一部分阐述了 字符串匹配涉及的问题;Unicode 和大小写 折叠对这些问题的影响;并概述了可用于解决这些问题的各种议题和 规范化机制。
第二部分提供了 用于形式语言的字符串同一性匹配的 要求和建议,例如许多由 W3C 规范定义的 文档格式。这一部分主要 关注使 Web 具备功能性,并为文档 作者提供一致的结果。
本节提供了本规范所述主题的一些历史背景。
字符模型的核心是通用字符集 (UCS),它由 Unicode 标准 [Unicode] 和 ISO/IEC 10646 [ISO10646] 共同定义。 在本文档中,Unicode 被用作通用字符集的同义词。成功的 字符模型允许以世界各种书写 系统、文字和语言编写的 Web 文档(并且位于不同平台上)被全球的 Web 用户交换、阅读和搜索。
Unicode 标准 [Unicode] 的前几章提供了有用的背景阅读。
关于影响本规范重要部分制定的 要求的信息,请参见 字符串同一性匹配和字符串索引的要求 [CHARREQ]。
本节包含本文档特有的术语和记法。
Web 建立在基于文本的格式和协议之上。为了 有效描述字符串匹配或搜索,有必要 建立一套术语,使我们能够讨论给定格式或协议中文本的不同种类, 因为相关要求和细节差异很大。
Unicode 码点(或“码点”)指分配给每个
Unicode 字符的数值。Unicode 码点范围从 0 到
0x10FFFF。(有关字符编码术语的
更深入讨论,请参见 [CHARMOD] 第 4.1 节。)
Unicode 码点表示为 U+hhhh,其中 hhhh 是至少四位、最多六位的
十六进制数字序列。例如,字符 € [U+20AC EURO
SIGN] 的码点是 U+20AC,而字符 😺 [U+1F63A SMILING CAT FACE WITH OPEN MOUTH]
的码点是 U+1F63A。
本文档示例中使用的一些字符可能无法按预期显示在你的特定设备或 显示器上。这通常是因为本地未安装特定文字所需的字体,或者是由于 你的特定渲染系统存在其他限制。本文档使用 Webfont 为许多非拉丁字符提供后备字形, 但你的设备可能不支持显示该字体。在 可能的范围内,编辑者已尽力确保这些示例仍然易于理解。
传统字符编码是一种 字符编码形式,它不能编码 Unicode 字符集中完整的字符集合。
转码器是一个在两种字符编码之间转换 文本的过程。在本文档中,它最常见地 指从传统字符编码 转换为 Unicode 编码 形式的过程, 例如 UTF-8。
自然语言是人类使用的口语、书面或手语 交流方式(另请参见此处 [LTLI])
句法内容是文档格式 或协议中属于该格式或协议结构的任何文本。该定义包括 通常被认为是“标记”的值,但也可以包括其他值,例如 HTTP 头中字段的名称。 句法内容由构成格式或协议结构的所有字符组成。例如,< 和 >(以及它们所包围的元素名和各种属性) 是 HTML 文档中句法内容的一部分。
句法内容通常由一个或多个规范定义,并且既包括给定协议或格式中定义的、 保留的关键字,也包括由文档作者定义、用于形成文档结构(而不是文档“内容”)的 字符串标记和标识符。
词汇表是保留关键字和/或用于在格式或 协议中分配用户提供值(例如标识符)的规则列表。 它可以包括对可出现在不同位置的字符的范围、顺序或类型的限制。
例如,HTML 定义其元素和属性的名称,以及枚举属性 值,这定义了 HTML 句法内容的“词汇表”。另一个示例 是 ECMAScript,它限制可出现在标识符或变量名开头或主体中的字符范围。 它还对其他情况应用不同规则,例如字符串字面量的值。
词汇表中的值分为两大类:一类旨在被 人类看到、阅读或交互(因此可能期望包含自然语言文本); 另一类是应用或协议内部的,不打算供人类交互。
面向用户的标识符是由用户在 词汇表中定义或分配的标识符,它至少可能对 最终用户可见(因此是可本地化内容)。
应用内部标识符是由用户在 词汇表中定义或分配的标识符, 它是文档格式或协议内部的,并且不打算供人类交互。此类值通常不是 可本地化内容。
用户提供值是在
句法内容中未保留的 词汇表内容,
由用户分配,不同于给定格式或协议中的保留关键字。用户通常
期望其用户提供值可以是其首选自然语言中的词语或短语。这就是为什么 [CHARMOD] 建议
“规范不应任意排除从 U+0000 到 U+10FFFF(含)完整
Unicode 码点范围中的码点。”
可本地化
内容指预期作为人类可读文本的文档内容,而不是构成文档结构一部分的任何
周围或嵌入的句法内容。注意,句法内容中可以嵌入可本地化内容,例如当 [HTML] img 元素具有包含图像描述的 alt 属性时。
资源,在本文档的上下文中,是指给定的文档、文件或
协议“消息”,其中既包括可本地化内容,也包括
句法内容,例如包围或
包含该内容的标识符。例如,在一个还包含一些 CSS 和几个带有嵌入式 JavaScript 的
script 标签的 HTML 文档中,整个 HTML 文档作为
一个文件来看就是一个资源。此术语有意类似于 [RFC3986] 中使用的“resource”一词,尽管在这里该
术语是宽泛适用的。
字素是某些文本的视觉表示中由
一个或多个字符组成的序列,
典型用户会将其感知为一个单一单元(字符
)。
字素对于许多文本操作很重要,例如
排序或文本选择,因此需要能够计算
每个用户感知字符之间的边界。Unicode 在 Unicode
标准附件 #29:文本分段 [UAX29] 中定义了计算字素的
默认机制,并将这种近似称为字素簇。默认字素簇定义了两种类型。
除非另有说明,本文档中的字素
簇指扩展默认字素
簇。(Unicode 标准第 2 节中也讨论了字素簇,
[Unicode]。另参见
第 2.11 节
靠近末尾处,版本 8.0 的 Unicode 标准)
由于不同自然语言有不同需求,字素簇 有时也可能需要定制。例如,斯洛伐克用户可能 希望将默认的一对字素簇 “ch” 视为单个 字素簇。注意,字符串内容的语言与最终用户偏好之间的 相互作用可能很复杂。
本节说明上面定义的一些术语。为便于说明,我们将使用 以下小型 HTML 文件作为示例(添加行号以供参考):
1 <html lang="en" dir="ltr">
2 <head>
3 <meta charset="UTF-8">
4 <title>莎士比亚</title>
5 </head>
6 <body>
7 <img src="shakespeare.jpg" alt="William Shakespeare" id="shakespeare_image">
8 <p>名字有什么关系?我们所称的玫瑰,即便换个 名字,也依然芬芳如故。</p>
9 </body>
10 </html>
’是句法内容的一部分。)
William Shakespeare) 也是可本地化内容。
除了标记为非规范性的章节之外,本规范中的所有编写指南、图表、示例和注释均为 非规范性内容。本规范中的其他所有内容均为规范性内容。
本文档中的关键词 MAY、MUST、MUST NOT、OPTIONAL、RECOMMENDED、SHOULD 和 SHOULD NOT 应按 BCP 14 [RFC2119] [RFC8174] 中的描述解释,并且仅当它们如这里所示以全大写形式出现时才如此解释。
本文档描述了其他规范作者的最佳实践,以及 面向实现和内容作者的建议。这些最佳实践也可以在 国际化工作组的文档 规范开发者国际化最佳实践 [INTERNATIONAL-SPECS] 中找到, 该文档旨在作为 W3C 规范中所有国际化最佳实践的一般参考。
本文档中的最佳实践使用 [RFC2119] 关键词, 以阐明国际化工作组对特定 建议的意图。遵循本文档中的建议,有助于避免在 W3C 的“广泛审查”流程中、实现期间或 作者生成的内容中出现问题。本文档本身不是规范性文档,并且可以不时修订。
如果规范满足以下条件,则可以声明符合本文档:
施加于规范的要求可能会间接导致要求施加于 声称符合这些规范的实现或内容。
如果本规范包含过程性描述,应理解为一种用于指定 期望外部行为的方式。实现可以使用其他方式来达到 相同结果,只要可观察行为不受影响。
Web 主要由基于 字符数据的文档格式和协议构成。这些格式或协议可以被视为一组资源,主要由 文本文件组成,其中包含某种形式的结构化标记或句法内容。 处理此类句法内容或文档数据需要 基于字符串的操作,例如匹配(包括正则表达式)、索引、搜索、排序 等等。
用户,特别是实现者,有时会对相似字符串的匹配或不匹配, 或他们可能应用于文本的不同变换的有效性抱有天真的预期,尤其是对 句法内容如此,但也包括 Web 上许多类型的文本处理。
由于从根本上说,Web 对文本在 文档中可能表示的不同方式是敏感的,如果不考虑同一文本可以用不同方式表示, 就可能使用户困惑,或导致意外且令人沮丧的结果。在下面各节中,本文档考察了 影响用户对 Web 上文本感知以及 Web 所依赖的字符串处理的 不同 文本变体类型。
一些文字和书写系统会区分大写、小写和标题式大小写字符。大多数 文字,包括印度的婆罗米系文字、阿拉伯文字,以及用于书写 中文、日文或韩文的文字,都没有大小写区分,但有一些重要文字具有这种区分。 此类文字的示例包括本文档大部分内容所使用的拉丁文字,以及 希腊文、亚美尼亚文和西里尔文等文字。
大小写映射是 将字符转换为特定大小写形式的过程,例如大写、小写或标题式大小写。对于那些 具有大小写区分的文字,Unicode 为每个 Unicode 码点定义了默认的大写、小写和标题式大小写 字符映射。大小写映射起初看起来很简单。然而,在多样化语言中处理完整 Unicode 范围时, 需要考虑一些变体。
大小写折叠是 为比较目的而使仅在大小写上不同的两个文本变得相同的过程,也就是说,它 用于字符串匹配。这不同于大小写映射,后者 主要用于显示目的。与默认大小写映射一样,Unicode 为每个 Unicode 码点定义了默认的大小写 折叠映射(“大小写折叠”)。Unicode 定义了两种大小写折叠形式, 我们将在下文考察。
由于大多数文字没有大小写区分,因此与大小写映射一样,大多数 Unicode 码点并不
需要大小写折叠。对于那些
具有大小写折叠的码点,大多数都有一种简单、直接的映射,映射到另一个单个匹配的
(通常是小写)码点。Unicode
将这组折叠称为 common,因为 Unicode 定义的两种大小写折叠都包括这些折叠。
少数字符具有将一个 Unicode 码点映射到两个或更多码点的大小写折叠。这组
大小写折叠称为 full 大小写折叠。full 和 common
大小写折叠结合使用,为
整个 Unicode 提供默认大小写折叠。本文档将这种大小写折叠形式称为 完整
大小写折叠或 Unicode full。
由于某些应用在执行大小写折叠操作时无法分配额外存储空间,
Unicode 提供了 simple 大小写折叠,它将通常会折叠为更多或更少码点的
码点改为使用单个码点进行比较。与完整折叠不同,这种折叠总是会改变内容
(并且可能改变文本的含义)。与完整大小写折叠一样,简单大小写折叠或 Unicode simple
大小写折叠是 simple 和 common 映射的组合,
以覆盖 Unicode 的完整范围。Unicode simple 不适合在
Web 上使用。
注意,大小写折叠会从字符串中移除一些之后无法恢复的信息。例如, 德语中的两个 s 字母在未折叠文本中不一定表示 ß。
大小写映射和大小写折叠的另一个方面是,它可能具有语言敏感性。 Unicode 为每个已编码字符定义了默认大小写映射和大小写折叠,但 这些只是默认值,并不适用于所有情况。某些 语言需要对大小写映射进行定制,以满足特定的语言学 需求。一个示例是使用 拉丁文字书写的突厥语族语言:
虽然上面的示例(以及本文档总体上)聚焦于用于
匹配目的的大小写折叠,但请注意,大小写映射也具有语言特定性。土耳其第二大城市的名称是
“Diyarbakır”,其中
同时包含有点和无点的字母 i。
一些文档格式或协议试图通过忽略其定义的词汇表中 或该格式或协议所允许的用户提供值中的大小写变体,来促进互操作性或 为内容作者提供帮助。
有时,大小写会以一种在语义上没有意义 或并不完全受用户控制的方式变化。这在 搜索文档时尤其如此,但有时也可能适用于 为匹配用户或内容生成的值(例如标识符)定义规则的情况。 在这些情形中,改用大小写不敏感 匹配可能是可取的。
定义词汇表时,一个重要考虑因素是这些值 是否被限制为 Unicode 的 ASCII [ASCII] 子集,或者词汇表是否允许使用可能具有更复杂 大小写折叠要求的字符(例如拉丁字母上的重音符号,或包括非拉丁文字在内的广泛 Unicode 字符)。 为满足这些不同要求,本文档为文档格式 或协议中的字符串同一性匹配定义了四种大小写折叠 匹配类型:
区分大小写的匹配:直接比较 码点,不进行大小写折叠。
ASCII 不区分大小写的匹配在 [INFRA] 中定义。该定义比较两个码点序列, 就好像 0x41 到 0x5A(A 到 Z)范围内的所有 ASCII 码点都被映射到 0x61 到 0x7A(a 到 z)范围内的相应码点。若词汇表本身受限于 ASCII,则可能需要 ASCII 不区分大小写的匹配。
Unicode 不区分大小写的匹配比较两个码点 序列,就好像已经对两个输入序列都应用了Unicode full 大小写折叠(见上文)。
语言敏感的区分大小写匹配在少见情况下很有用: 当文档格式或协议包含关于句法内容语言的信息, 并且可以合理应用语言敏感大小写折叠时。这些大小写折叠由 Unicode 联盟的 公共区域设置数据仓库 [UAX35] 项目定义。
关于如何处理大小写折叠的建议,请参见 § 3.2.6 大小写 折叠的其他考虑事项。
Unicode 文本中可能出现另一种变体:有时可以使用多个不同的Unicode 码点序列来表示 同一个抽象字符。当通过比较码点来搜索或匹配文本时,这些编码上的变体会导致用户期望相同的 文本值无法匹配。
由于应用需要在使用不同码点序列的文本中找到语义等价, Unicode 定义了一种使两个语义等价文本变得相同的方法: Unicode 规范化形式 [UAX15]。
资源通常容易受到这些变体的影响, 因为 Web 上的规范和实现既不要求文本进行 Unicode 规范化, 也没有考虑稍后处理句法内容(包括用户提供值)和可本地化内容时所使用的字符串匹配算法。因此,内容 开发者需要确保其提供了一致的表示,以避免 后续出现问题。
然而,用户可能很难确保给定资源 或一组资源使用一致的文本表示,因为这些差异在以文本形式查看时通常不可见。 因此,工具和实现需要考虑用户在面对视觉上或逻辑上等价、且在用户心中“应该” 匹配的字符串却被视为不同值时所遇到的困难。 提供一种让用户看到这些差异和/或按需对其进行规范化的方式, 可以使最终用户避免由源文档中不可见差异引发的失败。 例如,当 HTML 文档并非完全采用 Unicode 规范化形式 C 时,W3C Validator 会发出警告。
Unicode 定义了字符之间的两种等价类型:规范等价和 兼容等价。
规范 等价是 Unicode 码点或 Unicode 码点序列之间的一种基本等价关系, 这些码点或序列表示同一个抽象字符。规范等价的序列理想情况下应具有相同的视觉外观 (尽管有许多因素可能导致它们看起来略有不同),并且应被视为相同来处理和加工。 Unicode 定义了一个称为规范分解的过程,用来移除两个编码不同但规范等价的文本之间的 这些主要差异。
Unicode 定义的规范等价示例包括:
兼容等价是 Unicode 字符或 Unicode 字符序列之间一种较弱的等价关系, 它们表示同一个抽象字符,但可能具有不同的视觉外观 或行为。一般来说,称为兼容分解的过程会移除 格式变体,例如上标、下标、旋转、 带圈等,但也会出现其他变体。在许多 情况下,具有兼容分解的字符表示一种语义性质的 区别;因此,用其兼容分解替换这些独立字符的使用, 可能会改变文本的含义。经过兼容分解后等价的文本, 在分解之前通常并不会被认为 是相同的,并且形式语言不应将其视为等价。
在上表中,重要的是要注意,所示字符是实际的 Unicode 码点, 而不只是由于上下文或样式造成的呈现 变体。每个字符都被编码进 Unicode,以兼容各种传统字符 编码。它们不应与用于其非兼容 对应字符的正常呈现处理相混淆。
例如,大多数阿拉伯文字文本使用 Unicode 的阿拉伯文字区块中的字符 (从 U+0600 开始)。用于显示 文本的实际字形由字体和文本处理逻辑根据其在词中的位置 (词首、词中、词尾或独立)来选择,这个过程称为“塑形”。在上表中,展示了阿拉伯字母 ه [U+0647 ARABIC LETTER HEH] 的四种 呈现形式。所示字符是 U+FE00 区块中的兼容字符, 每个字符都表示一种特定的“位置”形状,并且所示的四个码点都具有到普通阿拉伯字母 ه [U+0647 ARABIC LETTER HEH] 的兼容分解。这些呈现形式仅用于支持与包含等价呈现形式的传统字符编码 进行往返编码转换。否则,包含一系列字母 “heh”的字符串只会被编码为一系列 U+0647 码点, 并由渲染系统和字体提供适当的形状。
类似地,半角和全角形式以及旋转 字符(用于垂直文本)中的变体被编码为单独的码点, 主要是为了兼容传统字符编码。在 许多情况下,这些变体与 东亚宽度 [UAX11] 中描述的 Unicode 属性相关。 另请参见 Unicode 垂直文本布局 [UTR50], 其中讨论了垂直文本 呈现形式。
对于具有兼容分解的字符,例如上文所示的那些字符,K Unicode 规范化形式会将文本转换为“正常”或“预期”的 Unicode 码点。但不能因为这些兼容 字符的存在,就认为正常文本布局和 呈现过程中产生的相似外观 变体会受到 Unicode 规范化的影响。它们不会。
这两种 Unicode 定义的等价随后又按另一对变体进行分组: “分解”和“组合”。在“分解”中,一个视觉字符中可分离的逻辑部分 被拆分为基字符和组合标记的序列,并将得到的码点 放入固定的规范顺序。在“组合”中,会先执行分解,然后 按照某些规则将组合标记与其基字符重新组合。
粗略地说,NFC 的定义方式是: 每个组合字符序列(一个基字符后跟一个或多个组合字符) 都会在可能范围内被替换为规范等价的预组合字符。
非常重要的是要注意这并不意味着什么。得到的 字符序列仍然可能包含组合标记,因为并非所有字符序列都有 预组合等价形式。事实上,正如我们已经看到的,许多文字除了 使用组合标记外别无选择,例如此 示例中的天城文元音。在其他情况下,给定的基字符和组合标记 不会被预组合字符替换,因为该组合被规范化规则阻止。 例如,某些印度文字不会组合某些基字符加附加符号的序列, 即使存在匹配的预组合字符,也是由于组合排除规则。 组合也可能被两个本来会组合的字符之间的另一个组合标记阻止。
Unicode 规范化形式有四种。每种形式都使用一个字母代码命名:
Unicode 规范化将这些(以及表示同一字符的其他潜在转义序列) 减少为仅三种可能 变体。然而,Unicode 规范化并不会移除所有 文本区别,并且有时应用 Unicode 规范化会移除在给定上下文中具有区别性或有意义的 含义。例如:
NFKD 或 NFKC)进行规范化时,
会变成 ASCII 字符序列:
81/2。
许多用户会惊讶地发现,两个看起来相同的字符串——包括
已应用特定 Unicode 规范化形式的字符串——事实上可能并不
使用相同的底层Unicode 码点。这包括
已应用破坏性更强的 NFKC 和 NFKD 兼容规范化
形式的字符串。即使字符串、标记或标识符在视觉上看起来相同,
它们也可能以不同方式编码。
Unicode 规范化形式关注的是将可用于给定抽象字符或字素簇的多个不同码点
序列折叠为相同的码点
序列。然而,逻辑上不同的字符或字素簇仍然可能看起来相同或非常
相似。当一对字素看起来相同(或非常相似)时,它们称为同形字。当一对字素看起来相似或是同形字,但实际上表示逻辑上不同的字符或
字符序列时,它们被称为易混淆字符
。
即使在单一文字系统内部,也可能出现外观相同或看似相同的示例。这可以 表现为形状相似的字符,例如“0”和“O”或“l”和“1”。但其他文字系统或 不同兼容字符的使用可能呈现出更不易区分的变体。在 某些情况下,Unicode 规范化会将这些变体合并,但在许多其他情况下则不会。
外观相同或易混淆
的字符可能带来欺骗和
其他安全风险。这在单一文字系统内部可能成立,对于
不同文字系统中的相似字符也可能成立。关于同形字和易混淆性的进一步讨论和示例,
一个有用的参考是 [UTS39]。
除了外观相同或相似的字符之外,还存在相反的问题:
Unicode 规范化,即使是 NFKC 和 NFKD 兼容形式,
也不会合并那些具有相同内在含义或功能、
但在外观或用法上不同的字符。例如,U+002E(.)
和 U+3002(。)都用作句末标点,
但规范化不会移除这种区别,因为这些字符具有不同的身份。
当以不区分大小写的方式匹配字符串时,一个复杂因素是,大小写折叠过程可能 产生未规范化的字符串,即使原始字符串已经规范化。由于字符串 比较依赖于匹配码点序列,如果希望 匹配过程可靠,则每个经过大小写折叠的字符串都必须进行规范化。
Unicode 规范化形式(NFC 或 NFD)和大小写 折叠在一起使用时是闭合的:一旦字符串经过大小写折叠并随后应用了 NFD 或 NFC,进一步应用相同的大小写折叠 或 Unicode 规范化形式都不会得到不同的字符串。
当比较字符串中字符之间的兼容等价 (换言之,NFKC/NFKD 形式)时,必须执行两次大小写折叠并规范化操作, 因为兼容分解步骤可能产生需要进行 大小写折叠的字符,而随后的大小写折叠又可能产生一个随后必须规范化的序列。
Unicode 对规范大小写折叠匹配(规则 [D145])和
兼容大小写折叠匹配(规则 [D146])的定义包含多个
规范化步骤。这会增加执行无大小写匹配的复杂度和成本。
在执行大小写折叠之前进行的初始规范化步骤处理了一个特定的边界情况,
本节将对此进行详细说明。
Unicode 大小写折叠匹配流程中的最后规范化步骤,是为了确保 得到的字符串处于特定的 Unicode 规范化形式。如果得到的大小写折叠字符串 要被存储或显示给用户,确保大小写折叠操作产生的非规范化 序列重新规范化以便显示,是一种良好实践。然而,此步骤在Unicode 规范 大小写折叠规范化步骤和Unicode 兼容大小写折叠规范化步骤中是 可选的,因为执行额外的规范化 不会改变字符串比较的结果。
六十三个希腊文预组合字符具有分解映射(即规范化为 NFD 形式),
其中包含字符 ͅ [U+0345 COMBINING GREEK YPOGEGRAMMENI],
这是一个表示下标 iota 的附加符号(称为 prosgegrammei 或
ypogegrammeni)。该标记表示古希腊语或古典
希腊语中存在的一种正字形式,对应的发音在更现代的语言形式中已经不存在。
这些字符的大写和标题式大小写映射会将该组合标记分离为单独的基字母
iota。为与标题式/大写映射保持一致,这些
字符的大小写折叠映射因此包含 ι [U+03B9 GREEK SMALL LETTER IOTA](回想一下,Unicode 大小写折叠通常折叠为小写)。
当且仅当这 63 个字符之一后跟一个组合标记时,若未在大小写折叠之前应用规范 分解,就可能导致潜在的比较不匹配。这样的字符 序列并不处于规范化形式中,并且很难“自然地”产生(通过键盘 和其他输入过程)。
例如,如果从预组合(NFC) 字符 ᾌ [U+1F8C GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI] 开始(这是表示这组 基字符和附加符号组合的最常见方式),并且只执行大小写折叠转换, 最终会得到:ἄι [U+1F04 GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA + U+03B9 GREEK SMALL LETTER IOTA]。
如果改为从表示同一字母的完全分解(NFD)序列开始,ᾌ [U+0391 GREEK CAPITAL LETTER ALPHA + U+0313 COMBINING COMMA ABOVE + U+0301 COMBINING ACUTE ACCENT + U+0345 COMBINING GREEK YPOGEGRAMMENI],最终会得到 ἄι [U+03B1 GREEK SMALL LETTER ALPHA + U+0313 COMBINING COMMA ABOVE + U+0301 COMBINING ACUTE ACCENT + U+03B9 GREEK SMALL LETTER IOTA]。将该字符串规范化为 NFC 会产生与上面第一个 示例相同的字符序列:
在这两种情况下,锐音符号都与 alpha 基字符相关联,而不是与 末尾的 iota 相关联。
然而,如果从半预组合序列 ᾌ [U+1F88 GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI + U+0301 COMBINING ACUTE ACCENT] 开始,最终会得到 ἀί [U+1F00 GREEK SMALL LETTER ALPHA WITH PSILI + U+03B9 GREEK SMALL LETTER IOTA + U+0301 COMBINING ACUTE ACCENT],其中锐音符号与 iota 相关联。 这会产生一个无法规范化为与其他序列匹配的序列(而且实际上是不正确的,因为 它与原始的用户感知字符具有不同含义)。
如上所述,Unicode 通过在执行大小写折叠操作之前将文本规范化为 NFD, 来解决这个匹配问题。然后 ᾌ [U+1F8C GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI] 和 ᾌ [U+1F88 GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI + U+0301 COMBINING ACUTE ACCENT] 都会得到与分解版本相同的结果,即 ᾌ [U+0391 GREEK CAPITAL LETTER ALPHA + U+0313 COMBINING COMMA ABOVE + U+0301 COMBINING ACUTE ACCENT + U+0345 COMBINING GREEK YPOGEGRAMMENI]。如果现在对该序列进行大小写折叠并规范化, 就会在所有情况下产生匹配:
大多数文档格式或协议都提供转义机制, 以允许包含那些在其他情况下难以 输入、处理或编码的字符。这些转义机制提供了一种 在给定资源中表示字符的额外等价方式。它们还允许编码文档所用字符编码方案中 未表示的 Unicode 字符。
字符转义和包含的展开取决于上下文,也就是说,取决于在执行字符串匹配操作时
被认为适用的是哪种句法内容或编程语言。考虑在包含
suçon 但不包含 suçon 的 XML 文档中搜索字符串
suçon。如果在纯文本编辑器中执行搜索,
上下文是纯文本(没有句法内容或编程语言适用),
ç 字符转义不会被识别,因此不会被
展开,搜索失败。如果在 XML 浏览器中执行搜索,上下文是
XML,字符转义(由 XML 定义)会被展开,搜索成功。
中间情况可能是一个 XML 编辑器,它有意 提供一种保留实体引用而不展开的 XML 文档视图。 在这种情况下,对该伪 XML 视图进行搜索时 会有意不展开实体:在该 特定上下文中,实体引用不被视为 包含项,也无需展开
例如,€ U+20AC
EURO SIGN 在 HTML 中也可以编码为十六进制
实体 € 或十进制实体 €。
在 JavaScript 或 JSON 文件中,它可以表现为 \u20ac 或 \u{20AC},
而在 CSS 样式表中,它可以表现为 \20ac。所有这些
表示都编码同一个字面字符值:€。
字符转义通常在文档被 处理以及格式或协议中的字符串被匹配之前解释。 回到上面使用过的一个示例:
你会期望该文本显示如下:Hello world!
为了使其正常工作,用户代理(浏览器)必须匹配两个表示类名
héllo 的字符串,尽管 CSS 和 HTML 各自使用了不同的转义机制。上面的
片段展示了文本可以发生变化但仍按规范被视为“相同”的一种方式:
类名 h\e9llo 匹配 HTML 标记中的类名
héllo(并且也会匹配使用码点 é [U+00E9 LATIN SMALL LETTER E
WITH ACUTE] 的字面值 héllo)。
形式语言和文档格式通常提供将一段文本从一个 资源包含到另一个资源中的设施。包含是一种将内容插入 资源正文中的机制。包含机制在 处理时将内容导入资源。这会影响文档结构,并可能影响与文档 词汇表的匹配。包含的示例包括 XML 中的实体引用、XInclude [XInclude] 规范,以及 CSS 中的 @import 规则。
如果一个包含不以组合标记开头(无论是以字符转义形式, 还是作为被包含资源中的字符字面量),则称该包含是包含规范化的。
Unicode 提供了许多专用字符, 用于帮助文档作者控制文本的外观或表现。 由于其中许多字符不可见或没有键盘等价项,用户并不总是 知道它们存在或不存在。因此,当这些字符是已编码 字符序列的一部分,而预期匹配的文本并不包含它们时,它们可能干扰字符串匹配。 这些字符的一些示例包括:
Unicode 控制字符 U+200D Zero Width Joiner(也 称为 ZWJ)和 U+200C Zero Width Non-Joiner(也 称为 ZWNJ)。 虽然这些字符可用于控制连字形成——无论是防止形成 不期望的连字,还是促成期望的连字——但它们的主要用途是控制 复杂文字(如阿拉伯文或各种印度文字)中的连接和字形选择。 一些印度文字使用 ZWJ 和 ZWNJ 字符,使作者能够控制某些 连写体采用的形状。请参见 [Unicode] 第 12 章中的 讨论。
Zero Width Non-Joiner 在波斯语中用于 防止某些“正常”的阿拉伯文字连接。在这些情况下,该 字符的存在与否确实会影响含义。例如,单词 تنها(“alone”)和单词 تنها (“bodies” 或“corpuses”)分别编码为 “U+062A U+0646 U+0647 U+0627” 和 “U+062A U+0646 U+200C U+0647 U+0627”, 唯一区别是后一个词中的 ZWNJ。
ZWJ 字符也用于形成某些表情符号序列,下文将对此进行更 详细讨论。
变体选择符(U+FE00 到 U+FE0F)是 用于选择替代外观或字形的字符 (参见《字符模型:基础》[CHARMOD])。例如, 它们用于在黑白表情符号和彩色表情符号之间进行选择。 它们也用于预定义的表意文字变体序列(IVS)。Unicode 字符数据库(UCD)的 “Standardized Variants” 部分给出了许多示例。
少数文字还提供了编码视觉变体选择的方式:一个显著示例是 蒙古文的自由变体选择符(U+180B 到 U+180D)。
字符 U+034F Combining Grapheme Joiner 的名称具有误导性(因为它并不连接字素),它用于分隔那些可能在排序目的上 原本被视为一个字素的字符,或用于在对文本应用 Unicode 规范化时提供一种保留某些文本区别的方式。
空白变体也可能影响文本的解释和 匹配。例如,各种不换行空格 字符,如 NBSP、NNBSP 等。
U+200B Zero Width Space 是一种用于 在通常不出现空格的文本中指示词边界的字符。 例如,它可能用于泰语文档,以辅助 断词。
U+00AD Soft Hyphen 可用于文本中指示一个 潜在或首选的断字位置。只有当文本重新排版并在该位置换行时, 它才会变得可见。
U+2060 WORD JOINER,有时称为 WJ,是一个 零宽不换行空格字符。其目的是防止两个字符之间发生换行。 除了用于换行目的外,它应被忽略。它作为字符 U+FEFF ZERO WIDTH NO-BREAK SPACE 的替代,因为 U+FEFF 更常被称为“字节顺序标记”(BOM)。字节顺序 标记用于某些纯文本文件的开头,以指示该文件采用 Unicode 字符 编码。
最后,大多数文字在横向书写时从左到右行进。然而,一些文字,如
阿拉伯文和希伯来文,主要从右到左书写。文本可以混合使用
这些文字,或包含与文本其他部分方向相反的字符序列,例如
另一种文字中的数字或引号。这种文本方向的混合称为
双向文本,简称 bidi
。Unicode 双向算法 [UAX9] 描述了
这种混合方向文本如何被处理以供显示。对于大多数文本,方向处理可以
从文本本身推导出来。然而,在许多情况下,该算法需要额外
信息才能正确呈现文本。更多示例见 [html-bidi]。
Unicode 定义的解决文本方向歧义的一种方式,是使用一组不可见的 控制字符来 标记方向文本段的开始和结束。虽然双向控制符可能会影响 文本外观 (因为它们帮助 Unicode 双向算法呈现文本),但如果文本在没有这些控制符的情况下 自然就会形成双向文本段,它们可能对 文本没有影响。由于这些控制符像上文提到的字符一样不可见, 它们可能对匹配产生意外影响。
在几乎所有这些情况下,用户可能不知道或无法 确定给定文档或文本字符串是否包含或省略了这些字符之一。 因为文本匹配依赖于匹配 底层码点,由这些标记导致的文本编码 变体可能会使本应成功的匹配(从用户角度看)神秘地 失败。
Unicode 的一个较新特性是表情符号字符。在 [UTR51] 中,Unicode 将其描述为:
表情符号是象形图(图画符号),通常以彩色 卡通形式呈现,并在文本中内联使用。它们表示诸如面孔、天气、车辆和建筑、 食物和饮料、动物和植物,或表示情绪、感受或活动的图标。
表情符号可以与多种表情符号修饰符一起使用,包括 U+200D ZERO WIDTH JOINER 或 ZWJ,以形成更复杂的表情符号。
例如,表情符号(👪 [U+1F46A FAMILY])也可以通过在表情符号字符之间使用 ZWJ 的序列 U+1F468 U+200D U+1F469 U+200D U+1F466 形成。 改变或添加其他表情符号字符可以改变家庭的组成。例如,序列 👨👩👧👧 U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F467 在支持此类 组合的系统上会生成一个“家庭:男人、女人、女孩、女孩”的组合 表情符号字符。许多常见表情符号只能使用 ZWJ 序列形成。更多 信息见 [UTR51]。
表情符号字符后可以跟随表情符号修饰符字符。这些修饰符允许为表示人物的 表情符号选择肤色。这些字符通常是不可见修饰符, 跟在其所修饰的基础表情符号之后。例如: 👨 👨🏻 👨🏼 👨🏽 👨🏾 👨🏿
表情符号字符后还可以跟随一个变体 选择符,以指示基础表情符号的文本(黑白,由 U+FE0E Variation Selector 15 指示)或彩色 (由 U+FE0F Variation Selector 16 指示)呈现。
表情符号使用中的另一个复杂之处是旗帜。国旗可以使用源自 [BCP47] 注册表的国家代码组合而成,例如序列 🇿 [U+1F1FF REGIONAL INDICATOR SYMBOL LETTER Z] 🇲 [U+1F1F2 REGIONAL INDICATOR SYMBOL LETTER M],它是赞比亚国家的 国家代码(ZM):🇿🇲。其他区域性或特殊用途旗帜可以 使用旗帜表情符号与各种符号,或使用以 cancel tag 结尾的区域指示符代码组合而成。例如,苏格兰旗帜(🏴)可以这样组成:
这些机制中的每一种都可以组合使用,因此可以使用非常复杂的字符序列来 形成单个表情符号字素或图像。即使非常相似的表情符号序列,也可能不使用完全相同的 编码序列。在大多数情况下,上述修饰符和组合由 最终用户的键盘生成(在键盘中它们呈现为单个表情符号“字符”)。这种编码 选项的多样性在一定程度上通过不同厂商仅使用(且精确使用)Unicode “建议用于交换”的序列来解决。这有助于厂商确保字体和键盘 准备好为用户提供其期望的选项。尽管如此,用户通常不会意识到 底层编码的复杂性,而且生成机制并不限于那些被推荐的机制。 表情符号序列正在快速演进,因此近期可能还会出现有助于或妨碍 表情符号匹配的进一步发展。Unicode 规范化不会对这些序列重新排序,也不会插入 或移除任何修饰符。因此,提醒用户和实现者注意:在命名空间和其他匹配上下文中使用 表情符号字符的用户,可能很容易因编码变体而遇到意外的“字符” 不匹配。
资源可以使用不同的字符编码 方案,包括传统字符编码,来序列化 Web 上的文档格式。每种字符编码方案都使用 不同的字节值和序列来表示通用字符集的给定子集。
强烈建议为所有文档、格式和 协议选择 Unicode 字符编码(如 UTF-8),这是一项建议,因为使用传统字符编码 不会带来额外效用,并且本节其余部分中的考虑事项将完全可以避免。
例如,€ [U+20AC EURO
SIGN] 在 UTF-8 字符编码中编码为字节序列 0xE2.82.AC。
同一个
字符在传统字符编码 windows-1252 中编码为字节序列 0x80。
(其他传统字符编码可能不提供任何字节序列来
编码该字符。)
规范主要通过如下方式处理这些产生的变体: 在从文档的字符编码(无论是传统 字符编码还是 UTF-8 等 Unicode 编码)转换之后,将每个文档视为 Unicode 字符序列, 然后在继续处理 文档之前取消转义任何字符转义。
即使在单一传统字符编码内部,也可能存在
实现上的变体。一个著名示例是传统
日文编码 Shift_JIS。不同
转码器实现面临如何将特定
字节序列映射到 Unicode 的选择。因此,字节序列 0x80.60
(JIS X 0208 字符集中的 0x2141)被
一些实现映射到 U+301C
WAVE DASH,而其他实现选择 U+FF5E
FULL WIDTH TILDE。这意味着两个合理、
自洽的转码器可能会从相同输入产生不同的 Unicode 字符
序列。Encoding [Encoding]
规范存在的部分原因,是为了确保 Web 实现使用
可互操作且相同的映射。然而,并不能保证
与 Encoding 规范一致的转码器会被
应用于 Web 上发现的文档,或用于处理
特定文档格式或协议中出现的数据。
转换为 Unicode 时的另一个考虑因素,是存在 使用视觉存储顺序的双向文字(如希伯来文和阿拉伯文)的 传统字符编码。也就是说,与 Unicode 和其他现代编码不同, 字符在内存中按其在屏幕上从左到右打印的顺序存储 (如行式打印机)。在将 这些编码转换为 Unicode 或比较这些编码中的文本时,必须小心 将源文本和目标文本都置于逻辑顺序中。 更多信息请参见 [CHARMOD] 第 3.3.1 节
在执行自然语言搜索或“查找”功能时,还有其他类型的等价或处理是合适的。 这些内容在字符模型系列文档的另一部分中描述([STRING-SEARCH])。 为词汇表制定规范,或为形式语法定义匹配算法的规范 应避免尝试应用该文档中描述的额外自定义折叠、 映射或处理,因为这些会干扰生成 一致、可预测的结果。
在 Web 环境中,字符串可以使用不同的字符编码,在这些编码中使用不同的字符 序列,并具有本文档所述的其他变体(例如大小写),因此 建立一致的字符串同一性评估流程非常重要。
本章定义了在句法 内容中指定和实现字符串匹配的要求。
使字符串匹配更有效且更一致的一种方式,是对 要匹配的内容施加限制。词汇表的定义,尤其是 允许在该词汇表中使用用户提供值的词汇表,必然 包括关于什么构成“有效标识符”的规则。这通常包括长度和内容 限制。定义这些限制的一些最佳实践包括如下内容:
规范不应允许标识符中出现代理码点 (U+D800 到 U+DFFF)或非字符码 点。
规范不应允许标识符中出现 C0
(U+0000 到 U+001F)和 C1(U+0080 到 U+009F)控制字符。
应用内部标识符是文档格式或协议的 词汇表中机器可读且不用于 显示的部分。它们通常会被赋予有意义的名称(一般为英语),以便为 需要处理或调试文档格式或协议内容的开发者 或内容作者提供便利。
定义应用内部标识符(这些标识符 从不显示给用户,并且始终用于应用或 协议内部的匹配或处理)的规范,应将内容限制为 ASCII 的可打印子集。ASCII 不区分大小写匹配是推荐的。
面向用户的标识符是文档 格式或协议的词汇表中由用户分配或编辑,或呈现给 用户供其选择的部分。面向用户的标识符示例包括网络名称(例如 SSID); 设备名称;类名、样式名或属性名;或者用户定义的设置或值。这类标识符 由于本文档所述问题而更难匹配,但能提供最佳的 体验,特别是对于不说英语或不太熟悉拉丁文字的用户。
许多面向用户的标识符也是用户提供值,可以由 文档格式或协议的用户分配。能够使用用户或用户 社群或文化所偏好的自然语言,可以提供更好的用户体验,并使功能更易于被 语言能力有限的受众访问,尤其是在英语方面。
当标识符对用户可见或可能可见时,规范应允许使用非 ASCII Unicode 字符,以确保 所有语言的用户都能平等访问所产生的文档格式或协议。 推荐区分大小写(即不进行大小写折叠)。
虽然应当允许广泛范围的 Unicode 字符,但规范仍然可以对 面向用户的标识符内容施加某些 实际限制。定义这种类型内容规则的规范示例之一可见于 Unicode Identifier and Pattern Syntax [UAX31]。
为给定规范选择要使用的匹配算法时,基本决策是要对 被匹配的字符串应用何种文本规范化级别(包括大小写敏感性和 Unicode 规范化)。 从历史上看,Web 上的大多数规范都选择不进行 Unicode 规范化的区分大小写 匹配,并且这是所有新规范推荐的 匹配形式。然而,在某些情况下,不区分大小写和规范化是有用的。
如果相关格式或协议对用户的收益超过实现的成本和复杂性, 规范可以选择不区分大小写。由于大小写折叠和 规范化都可能影响被比较的值,包括文本的呈现以及在某些情况下的 含义,并且这些操作相对昂贵,因此通常不鼓励选择不区分大小写。
不区分大小写的一种特殊情况是词汇表被限制在 ASCII/基本拉丁范围内(即
码点 U+0000 到 U+007F)。这些
规范可以选择只在该字符范围内不区分大小写。这大大
简化了匹配的实现。然而,这种匹配形式不适合
在标识符或语法中允许更大范围 Unicode 的规范,因为用户难以
理解匹配行为,并且这对使用非 ASCII 文字
和语言的用户不利。也就是说,用户会觉得很奇怪且不可预测:green
能匹配 GREEN,但 grüß 却不能匹配 GRÜẞ 或可能的
GRÜSS(而是匹配 GRüß)。
匹配算法描述了比较两个字符串所需的一系列步骤。
给定规范所需的正确文本规范化取决于格式或 协议词汇表中的要求。文本规范化有四种选择:
在标识符和句法内容中匹配字符串时,推荐不进行任何大小写折叠或 Unicode 规范化。
此规范化步骤对文本没有影响,因此,比较会对 源字符串中的大小写差异和 Unicode 规范化形式差异均敏感。 如果内容作者希望标记能够匹配,就需要了解并确保他们使用 一致的大小写和一致的字符序列来编码受影响文本。
“ASCII 大小写 折叠”方法只应在例外情况下使用,用于本身 限制在 ASCII 范围内的词汇表(或只在 ASCII 标记上进行匹配的情况)。
ASCII 大小写折叠规范化步骤仅对 ASCII 范围执行大小写折叠。不应用 Unicode 规范化形式。此步骤仅适用于词汇表 本身限制在 ASCII 范围内的情况(或只在 ASCII 标记上进行匹配的情况)。
对每个字符串执行以下步骤:
大小写敏感性不建议用于大多数规范,但在 某种例外情况下,如果词汇表允许非 ASCII 字符且不希望 对大小写区别敏感,则应使用“Unicode 规范大小写折叠”方法。
具有允许非 ASCII 字符的词汇表的规范应 包括大多数新词汇表。
Unicode 大小写折叠可能产生非规范化的字符序列,因此,为了使 匹配符合用户预期,任何 Unicode 大小写折叠都需要随后进行 Unicode 规范化。示例见§ 2.4 规范化与大小写折叠的相互作用。
[Unicode] 要求 D145 要求在大小写折叠操作之后执行规范化步骤,以确保得到的 字符串处于规范化形式。包含大小写折叠后的规范化是可选的。如果得到的字符串仅用于比较 而不存储或显示给用户,则在大小写折叠后码点序列将是等价的。 它们只是不能保证处于任何特定的规范化形式。这是对 D145 的有意违反。 Unicode 已确认这是一项有效的 建议。
对每个字符串执行以下步骤:
不应使用“Unicode 兼容大小写折叠”方法。
具有允许非 ASCII 字符且需要匹配 Unicode 兼容等价项的词汇表的规范,可能会使用此规范化步骤。由于兼容 规范化形式(NFKC 和 NFKD)会改变文本的含义、外观和 处理方式,因此此步骤不应用于 Web 上的大多数 应用。
大小写折叠会受输入码点序列影响。它还可能产生非规范化的 码点序列。兼容分解与大小写折叠之间的相互作用需要 多次遍历才能产生一致的匹配。因此,此规范化步骤包括 多次使用 Unicode 规范化。示例见§ 2.4 规范化与大小写 折叠的相互作用。
对每个字符串执行以下步骤:
内容作者应以 Unicode 字符编码(Web 上通常为 UTF-8)输入和存储资源。
比较文本的第一步是确保二者使用相同的数字表示。这 意味着实现需要将传统 字符编码中的任何文本转换为 Unicode 码点序列。通常这是通过应用 转码器将数据转换为一致的 Unicode 编码形式(例如 UTF-8 或 UTF-16)来完成的。这允许对字符串进行按位比较,以 确定字符串相等性。
规范化转码器是一种转码器, 它从传统字符编码 转换为 Unicode,并且确保结果处于 Unicode 规范化形式 C(NFC)。对于大多数传统字符编码,可以 构造规范化转码器(通过使用任意转码器后接规范化器);如果 传统字符 编码的字符库包含 Unicode 中未表示的字符,则无法这样做。虽然规范化转码器只产生处于 NFC 的字符序列,但转换后的字符 序列可能不是包含规范化的(例如,如果它以 组合标记开头)。
由于 Web 上的文档格式经常与额外的外部 资源交互或通过其处理(例如,将 CSS 样式表应用于 HTML 文档),当在使用不同 字符编码的文档之间匹配值时,文本的一致 表示就变得重要。使用规范化转码器有助于通过使传统 编码文档匹配大多数语言通常预期的 Unicode 字符序列来确保互操作性。
Web 上使用的大多数转码器会产生 NFC 作为其 输出,但也有一些不会。这通常是为了让转码器与 源传统字符编码保持往返兼容、保留其他字符区别,或与 用户代理中正在使用的其他转码器保持一致。这意味着 Encoding 规范 [Encoding] 以及各种其他重要转码 实现包含若干非规范化转码器。事实上,Unicode 中的大多数兼容 字符仅用于从传统编码进行往返转换,并且其中许多在 NFC 中具有单例规范映射。 你在本文档前面已经看到过一个示例:Å [U+212B ANGSTROM SIGN]。
请记住,大多数转码器会产生 NFC 输出, 即使那些不会为 所有字符产生 NFC 的转码器,也会为绝大多数字符产生 NFC。 特别是,没有常用转码器会在存在预组合形式的地方产生分解形式, 或产生与规范化序列不同的 组合字符序列(并且这对 [Encoding] 中的所有转码器都成立)。
规范必须允许 Unicode 字符 编码。
规范必须指定默认字符 编码,并且应指定 UTF-8 作为默认编码。
规范应禁止使用除 UTF-8 之外的编码。
传统字符编码在 Web 上通常已经 超出了其有用期限。新规范需要从一开始就支持 Unicode 编码, 默认使用 Unicode 编码(通常为 UTF-8),并且如有可能,禁止使用任何 其他编码。这不仅促进互操作性,还减少字符和数据表示中 无意义变体的范围。
大多数文档格式和协议都提供一种方式,用于将字符编码为转义序列,或 将外部数据(包括文本)包含到资源中。这在 [CHARMOD] 第 4.6 节以及上文中有详细 讨论。
执行匹配时,重要的是知道何时解释字符转义,以便 匹配能够适当地成功(或失败)。通常,转义、引用和包含会在执行匹配 (或匹配敏感处理)之前被处理 或展开,因为这些语法的存在是为了让难以编码的 序列可以方便地放入文档中,同时使这些字符表现得就像它们 在相关文档中 直接编码为码点序列一样。
这可能变得复杂的一个领域,是决定句法 内容和可本地化内容如何交互。 例如,考虑以下 HTML 片段:
虽然从技术上说,组合标记 ̀ [U+0300 COMBINING GRAVE ACCENT] 会与前面的 引号结合,但 HTML 并不认为该字符(无论是否编码为实体)构成 HTML 语法的一部分。
在资源上执行匹配操作时,一般规则是在用户正在交互的同一
“层级”上展开转义。例如,在考虑上述示例时,用于
查看 HTML 源代码的工具会将转义序列 ̀ 显示为以
& 符号开头的一串字符。相比之下,JavaScript 程序则操作浏览器对
文档的解释,并会将字符 U+0300 匹配为
属性 id 的值。
处理文档格式的语法时,转义通常会在语法处理之前转换为其所表示的字符 序列,除非格式的处理规则明确禁止。 这允许资源将各种类型的字符包含到 资源的句法结构中。
在某些情况下,预处理转义会产生问题。例如,在解析 HTML 文档之前展开序列
< 会产生文档错误。
特定的 Unicode 规范化形式并不总是适合内容作者,或 并不总是可供内容作者使用,而且用户的文本编码选择对数据的下游消费者来说可能并不明显。 如本文档所示,内容作者或应用在输入或交换文本时, 可以选择以许多不同方式表示相同的语义值。规范化可能会 移除用户有意应用的区别。因此,匹配算法指定在执行字符串大小写折叠匹配时使用 Unicode 规范化—— 并且仅在算法内部使用。对内容强加 规范化可能成为用户和实现者的障碍。因此:
规范不应为给定词汇表的编码、存储或交换指定 Unicode 规范化形式。
实现不得改变正在交换、 读取、解析或处理的句法内容(包括用户提供值)或可本地化内容的规范化形式, 除非这是文本转换(例如将内容转码为 Unicode 字符编码、 大小写折叠,或其他 用户发起的更改)的副作用而必须这样做,因为消费者或内容本身可能依赖非规范化 表示。
创作工具应提供一种规范化 资源的方法,并在给定资源不处于 Unicode 规范化形式 C 时警告用户。
要求以特定 规范化形式存储和交换文本的规范,需要处理§ 3.2.5.1 在文档格式中指定 规范化时的要求。
通常不鼓励规范要求格式或协议以规范化形式存储或交换 数据,除非有具体、明确的理由说明该额外要求 是必要的。由于 Web 上许多文档格式并不要求规范化,内容作者可能 偶尔会依赖非规范化的字符序列。规范化步骤可能会对 此类内容产生负面影响。
规范规范化形式(形式 NFC 或形式 NFD) 旨在保留其所应用文本的含义和呈现。这 并不总是成立,这也是不推荐规范化的原因之一。NFC 的优势在于,几乎所有传统数据(如果 直接一对一转码为 Unicode 编码),以及当前 软件创建或由大多数(但不是全部)键盘上的用户输入的数据,已经处于这种形式。NFC 还具有轻微的紧凑性优势,并且在 大多数语言中,就字符与字素之间的关系而言,更符合用户预期。
规范不应指定兼容 规范化形式(NFKC、NFKD)。
实现不得应用兼容 规范化形式(NFKC、NFKD),除非最终用户明确请求。
兼容规范化形式(形式 NFKC 和形式 NFKD)会以重要方式改变文本的 结构并丢失文本的 含义。用户有时会有意使用 Unicode 中具有兼容映射的字符,或者在转换为 Unicode 时 使用传统字符编码中具有 兼容映射的字符。这必须被视为内容作者有意为之。 尽管 NFKC/NFKD 有时可用于可本地化内容中的“查找”操作或字符串 搜索,但抹除兼容性差异是有害的。
要求 NFC 需要规范开发者额外谨慎, 因为 Web 上的内容通常并不处于已知的 规范化状态。在这些情况下,需要仔细考虑并明确规定非规范化内容的边界和错误条件。
如果规范等价但不相交的 Unicode 字符序列构成 安全问题,规范必须记录或提供 健康警告。
内容作者在可能的情况下,应对 内容使用 Unicode 规范化形式 C(NFC)。
注意,NFC 并不总是适合内容, 在某些语言中甚至可能不可供内容作者使用。
内容作者应始终使用 一致的 Unicode 字符序列编码文本,以便于匹配,即使格式或实现执行的匹配中 包含 Unicode 规范化 形式。
为了使其内容能够被一致处理,内容作者应尽量使用 一致的码点序列来表示相同文本。虽然内容可以处于任意 规范化形式,或使用非规范化(但有效)的 Unicode 字符序列, 表示不一致会导致实现将不同序列视为 不同内容。确保一致选择、访问、提取、处理或显示的 最佳方式是始终使用 NFC。
内容作者不应在资源中包含 前面没有基字符的组合标记。
这可能存在例外。例如,在制作字符列表(例如
[Unicode] 字符列表)时,作者可能希望使用
没有对应基字符的组合标记。然而,在没有
基字符的情况下使用组合标记可能导致意外的显示或处理问题,例如当一个天真的
实现将组合标记与相邻的句法、用户提供或可本地化
内容组合时。例如,如果你使用一个组合标记,例如字符 ́ [U+0301 COMBINING ACUTE
ACCENT],作为 HTML 中 class 属性值的开头,
类名可能无法在编辑器中正确显示,并且难以编辑。
一些推荐的基字符包括 ◌ [U+25CC DOTTED CIRCLE](当基字符需要 可见时)或 [U+00A0 NO-BREAK SPACE](当基字符应不可见时)。
由于内容作者并不总是遵循这些指南:
词汇表规范必须定义 句法内容与字符数据之间的边界,以及实体边界(如果该 语言具有任何包含机制)。这些需要包括在处理或匹配内容时、当该语言实例被处理时 可能产生冲突的任何边界,同时允许用于表达任意字符的 字符转义。
当规范要求为存储、传输或处理进行 Unicode 规范化时, 规范作者以及该规范的 实现者还需要处理一些额外考虑事项:
当操作可能从规范化文本 输入产生非规范化输出时,规范必须定义所产生的输出是否 要求被规范化。规范可以声明 对某些操作执行规范化是可选的;在这种情况下,默认情况应是执行规范化,并且应使用显式选项来关闭规范化。
要求规范化的规范不得 将规范化的实现设为可选。
如果一些实现进行规范化而其他实现不进行规范化,就无法实现互操作性。
被要求执行规范化的实现需要考虑这些要求:
除非实现已经先通过检查确认 文本处于规范化形式,或者已经自行重新规范化文本,否则不得 执行规范化敏感操作。私有协议可以在不受 这些规则约束的私有系统中创建,但任何外部可观察结果必须与 遵守这些规则时相同。
会修改文本并执行 规范化敏感操作的规范化文本处理组件,必须表现得就像在每次修改后都进行了规范化一样, 以便任何后续的规范化敏感 操作始终表现得像是在处理规范化文本。
创作工具实现应警告用户, 或防止输入或创建以组合标记开头、可能干扰处理、显示或交换的句法内容。
字符串同一性匹配中的一个重要考虑事项是,比较是区分大小写 还是不区分大小写。
内容作者应始终使用 一致的大写、小写和混合大小写格式拼写标识符,以便于匹配,即使格式或实现支持大小写折叠 匹配。
词汇表通常会高度重视内容作者和用户的可预测性。 区分大小写的匹配最容易实现,并且引入混淆的可能性最小, 因为它通常只包括对底层 Unicode 码点 序列的比较。由于它不受语言特定大小写映射等考虑因素影响, 对于在其句法 内容中包含了单词(例如上文土耳其语示例)的文档作者而言,它带来的意外最少。
不区分大小写通常保留用于处理可本地化 内容,例如执行自然语言文本 搜索。然而,有时不区分大小写是可取的。在这些情况下,形式语言需要考虑若干 实现选择。
在包含超出 Unicode 基本拉丁(ASCII)范围的 词汇表中定义不区分大小写匹配的规范,必须 指定Unicode full 大小写折叠匹配。
规范应允许用户定义值使用完整范围的 Unicode。
词汇表通常应允许使用广泛范围的 Unicode 字符,特别是对于用户提供值,以便最大范围的 语言和文化都能不受不利影响地使用。因此,大小写折叠等文本操作 需要处理完整范围的 Unicode,而不只是选定部分。当 需要不区分大小写匹配时,这意味着使用Unicode 大小写折叠:
Unicode simple 大小写折叠形式不适合 Web 上的字符串同一性匹配。
在限制为 Unicode 基本拉丁(ASCII)子集的词汇表中 定义不区分大小写匹配的规范,可以指定ASCII 不区分大小写匹配。
如果一种形式语言的词汇表限制为 ASCII,并且不允许 用户定义名称或标识符,则可以指定ASCII 不区分大小写匹配。HTML 就是一个示例,它定义了对 HTML 规范所定义的元素和属性名称使用 ASCII 不区分大小写比较。
当且仅当所有标记和标识符都由 规范直接定义,并且这些标识符或标记仅使用 Unicode 基本拉丁 子集时,词汇表才被认为是“仅 ASCII”。如果允许用户定义标识符, 则应允许完整范围的 Unicode 字符(出于安全或交换考虑,可适当限制,见 [UTR36]), 并使用 Unicode 不区分大小写进行同一性匹配。
仅 ASCII 的词汇表可以存在于允许 标识符或值使用更大范围 Unicode 的文档格式或协议内部。例如 [CSS-SYNTAX-3] 以允许完整范围 Unicode 用于标识符和 值的方式定义 CSS 样式表格式。然而,CSS 规范始终使用 ASCII 范围子集定义 CSS 关键字。因此,CSS 的词汇表是仅 ASCII 的,尽管许多样式表包含 非 ASCII 的标识符或数据值。
区域设置或语言特定定制最适合用于自然语言 处理操作(这超出了本文档的范围)。由于大小写映射或大小写折叠的语言特定 定制会产生不同于通用大小写 折叠规则的结果,因此在高度重视可预测性的形式语言中应避免使用。
在词汇表中定义不区分大小写匹配的规范,不应指定语言敏感的不区分大小写匹配。
如果指定了语言敏感的区分大小写匹配,则 Unicode 大小写 映射应根据语言进行定制,并且必须指定每项定制所使用语言的 来源。
要匹配的两个字符串可能处于不同语言中,并且可能出现在第三种语言 上下文中。因此,大小写折叠使用哪种语言取决于应用和用户 预期。
不推荐将语言特定定制用于形式语言,因为语言 信息可能难以获取、验证或管理,并且由此产生的操作可能 产生令用户沮丧的结果,或根据用户正在使用的语言配置或执行匹配的系统 配置,对某些用户失败而对另一些用户成功。
特定于语言的操作在适当时应 包含语言特定的大小写折叠。
例如,CSS 操作 text-transform 在用于
对字符串进行大小写映射时具有语言敏感性。
虽然 Unicode 大小写折叠是文档格式和协议首选的不区分大小写匹配, 但其语言具有不同于默认映射的内容作者和用户仍可能对结果感到意外, 因为他们的预期通常与其所说语言一致。
语言敏感的字符串比较通常被称为
区域设置敏感,因为大多数编程语言和操作环境
使用其各自基于区域设置的 API 访问语言特定定制。例如,
参见 Java 编程语言中的 java.text.Collator 类或 JavaScript 中的
Intl.Collator。
规范必须清楚定义作为匹配过程一部分执行的任何附加 定制。
某些规范可能希望包含附加定制,以辅助给定 词汇表中的匹配。示例可能包括移除第 2 节中描述的其他文本差异,将属于 语法的字符映射到一起或移除,或执行空白修剪。
任何附加定制都需要避免干扰不同语言在 Unicode 中的 表示方式。例如,试图通过 分解文本然后移除所有组合字符来从字母中移除重音符号的过程,会破坏 依赖组合标记的语言。一个示例是示例 2中的天城文文本。(这样的过程也无法移除所有 可能的重音符号,并且很可能损害文本的含义和表示。)
虽然在形式语言中匹配字符串和标记是本文档的主要关注点,但有时 规范需要考虑超出纯字符串相等性的其他匹配类型。
正则表达式语法有时可用于定义格式或协议,因为它们允许用户 指定只部分已知或可以按可预测方式变化的值。正如本文档 各节所见,字符在 Unicode 中可以有不同的编码方式,这可能会干扰 表达式中字符串的指定或匹配方式。例如,字符计数可能需要依赖字素边界,而不是 所用 Unicode 码点的数量;无大小写匹配可能需要考虑大小写 折叠的变体;或者可能需要考虑所处理表达式或文本的 Unicode 规范化。
Unicode 正则表达式 Level 1 支持包括在 正则表达式中指定 Unicode 码点的能力,包括通过使用转义指定,以及访问 Unicode 字符属性 和大多数正则表达式语法共有的某些边界类型。
Level 2 在此基础上扩展了若干重要能力,特别是在 某些类型的字素簇边界上选择文本的能力,以及对大小写转换 的支持(这两个主题在上文已有大量提及)。Level 3 提供基于区域设置 [LTLI] 的 正则表达式定制,这在形式语言中不太有用,但在处理可本地化内容时可能有用。
本文档的变更(从 2018-04-20 的工作草案开始)可通过 github 提交日志查看。
此版本更改了 Unicode 规范大小写折叠规范化步骤和 Unicode 兼容大小写折叠规范化步骤中哪个规范化步骤是可选的。此 版本要求将规范化作为第一步,并使输出规范化成为可选步骤。此变更 基于测试以及与 Unicode 的讨论。
W3C 国际化工作组和兴趣组, 以及其他人员,提供了许多评论和建议。工作组谨向以下人员致谢: Mati Allouche、 Ebrahim Byagowi、 John Cowan、 Martin Dürst、 Behdad Esfahbod、 Asmus Freitag、 Richard Ishida、 John Klensin、 Peter Saint-Andre、 Amir Sarabadani、 Najib Tounsi、 Richard Wordingham, 以及本文档二十(!!)年开发过程中所有 CharMod 贡献者。
本文档的上一版本由以下人员编辑:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: