字母表汤:Linux 的国际化,第 2 部分
目前,国际化的中心标准是 POSIX 的区域设置模型。不幸的是,以目前的先进水平来看,通过 POSIX 模型进行本地化有点像普罗克拉斯提斯的床。例如,在日语中,有两种常见的日元货币单位表示方法:后缀 ¥ 和前缀 ¥。在同一文档的不同上下文中同时使用这两种约定并不罕见:前者在正文中很常见,后者在表格中很常见。POSIX 没有对此进行规定。通过创建一个日语表格区域设置来补充日语文本区域设置,可以很容易地实现这一点,但这将把设置正确区域设置的负担放在应用程序员身上。虽然规模小得多,但这种负担很像多语言化带来的负担。POSIX 也不是为了支持如此精细的区分而设计的;无论如何,最好留给各个应用程序来处理。
POSIX 风格的国际化为几乎所有用户和应用程序提供了舒适、功能丰富的环境。具体来说,POSIX 区域设置决定了
要使用的字符集和编码
字符的分类(例如,字母、十六进制数字、空格等)
语言中字符串的排序顺序
数字分隔符和小数点约定
日期和时间表示
货币表示
消息格式(特别是 是 和 否 的字符串)
所有这些功能都是通过更改标准库函数的功能或添加新函数来实现的。也就是说,libc 中的 isalpha 函数不再查阅固定的表,而是根据当前区域设置更改表。可以使用新函数 strfmon 来显示货币值。不幸的是,截至 libc-2.0.7t,Linux libc 中的区域设置支持仍然只是部分文档化;strfmon 没有手册页,尽管库中有一个入口点。GNU libc 的作者之一 Ulrich Drepper 的一篇有用的讨论可以在 http://i44s11.info.uni-karlsruhe.de/~drepper/conf96/paper.html 找到。
POSIX 标准定义了许多符合国际化标准的级别。这些级别在一定程度上可以作为衡量国际化工作进展程度的有用指南。当系统是 8 位干净的时,就达到了 1 级合规性。显然,这是一个最低要求,因为某些字符可能会损坏。当实现了一个灵活的系统来生成本地化的时间、日期和货币格式时,就达到了 2 级合规性。如上所述,这些工具由 GNU libc 提供,因此有纪律地使用适当的格式化函数和 setlocale 调用对于大多数应用程序来说足以实现 2 级合规性。当应用程序可以使用本地化的消息目录时,就达到了 3 级合规性。此工具由 GNU gettext 库提供。控制 gettext 几乎与设置区域设置一样简单。不幸的是,优先级规则有些不同。但是,有纪律地使用 gettext 及其支持函数将使本地化变得更加容易。(请参阅 Pancrazio de Mauro 于 1999 年 3 月发表的“Linux 程序中的国际化消息”。)
4 级是指亚洲语言支持。亚洲语言由于支持它们所需的各种复杂子系统而被赋予了特殊的地位。例如,许多 X 的实现都有两个独立的字符串显示函数系列,一个用于以单字节编码的字符串,另一个用于由双字节编码的字符组成的字符串。在日语中,单字节和双字节字符可以自由混合,因此需要处理日语的国际化应用程序必须即时将字符串分析为单字节和双字节子字符串。事实上,仅处理日语本身就迫使程序员处理真正的多语言应用程序带来的许多问题。
如果我们能将字符集直接视为与我们手写使用的脚本相对应,那就太好了,但不幸的是,事情并非如此简单。例如,世界上有近 200 个国家,每个国家都有自己的货币。当然,许多国家使用相同的符号,但很明显,如果您的键盘上每个货币符号都有一个键,那么它将比您今天使用的键盘大两倍左右。其他有用的符号包括律师使用的段落和章节标记以及数学家使用的各种运算符和非拉丁符号。由于新的字符一直在被创建(例如,新的欧洲货币单位欧元的符号),因此不可能将它们全部包含在内。因此,实际上,字符集是某人认为有用的一组字符。
在计算机内部以位字符串形式表示会带来进一步的限制。由于现代计算机都以字节作为最小的有效访问单元进行工作,因此基于 256 个字符集的文本(可以用单个字节编码)和 257 个字符集的文本(无法用单个字节编码)在空间和处理要求方面存在很大差异。人们可能会认为扩展到两个字节,即 65536 个字符,足以满足任何人的需求,但事实证明,即使这也不够。在设计 Unicode 字符集时,选择大约 20,000 个汉字字符的过程引发了许多争论。即使这 20,000 个字符也可能不够;虽然世界上只有少数人关心一些被排除在外的汉字字符,但对他们来说,这可能是世界上最重要的字符,因为这是他们用来写名字的字符。
结果是,已经设计和填充了许多字符集,并且已经编写了标准来编纂它们的使用。
所有标准中最有影响力的标准是美国信息交换标准代码,缩写为 ASCII。这是一个包含 128 个 7 位位字符串的列表,每个字符串都分配给美国英语中常用的字符或控制功能。如今,许多控制功能都没有使用,但是已经编写了如此多的软件,假设十六进制值 0x00 到 0x1F 不是打印字符,以至于没有人考虑为其中一些代码点分配更多字符。
由于几乎所有现有的计算机语言都与 ASCII 字符集兼容,因此某种形式的 ASCII 是大多数电子字符集的子集。但是,有很多变体。例如,大多数日语计算机中使用的 JIS Roman 字符集几乎与 ASCII 相同,只是更改了几个字形,并将日语日元符号替换为反斜杠。为了编纂这种发展,国际标准化组织 (ISO) 在 ISO 646 标准中定义了类似 ASCII 的字符集。美国 ASCII 被指定为 ISO 646 的国际参考版本,有时也称为 ISO 646-IRV(例如,在命名 X Window 系统的字体时)。
ASCII 根本不足以在国际化环境中使用。例如,大多数欧洲语言都使用带重音符号的字符。当然,可以将“带有锐音符的拉丁小写字母 a”(á)表示为两个字符的连字(例如,'a),但这对于排序来说很不方便,并且可能存在歧义。此外,仅使用 ASCII 字符如何表示抑扬符并不明显。为了保持与现有软件的 ASCII 兼容性,并容纳许多密集使用计算机的国家/地区,设计了 ISO 8859 标准。ISO 8859 有三个主要目标:保持 ASCII 兼容性,在 ISO 2022 的约束内实现,并在单字节编码中提供最广泛的语言覆盖范围。不幸的是,这三个目标并不兼容。几种可以用单字节编码的重要脚本,由于它们与 ASCII 或彼此不重叠,因此每个脚本都需要几十个代码点才能表示其字符:希腊语、俄语、希伯来语和阿拉伯语。
最终的解决方案不是定义单个字符集,而是定义一系列字符集。每个 ISO 8859 字符集都包含 ASCII (ISO-646-IRV) 作为子集,并且编码的定义方式是,解释为整数(C 字符),ASCII 字符在 ASCII 和 ISO 8859 中以相同的方式编码。然后定义了一个补充字符集列表,每个字符集最多包含 96 个字符,以符合 ISO 2022(如下所述)。然后将这些补充字符分配给代码点 0xA0 到 0xFF。当补充集源自字母表时,遵循自然的排序顺序,但是对于带重音符号的字符集合,顺序必然是任意的。当前的补充字符集在表 1 中列出。
下一步是统一所有各种字符集。当然,国家标准有两个主要优势。它们节省空间,用一个字节编码日常使用和计算机编程所需的字符,并且它们节省时间,因为它们可以按自然的排序顺序排列。当在 ISO 8859 系列中使用编码时,第二个优势已经被大多数欧洲语言所认可。实际上,ISO 8859-1 取得了巨大的成功,因为它有效地将所有主要的西欧和美洲语言统一在一个多语言编码中。借助系统库对排序的支持(POSIX 区域设置的 LC_COLLATE 部分),很难证明在可以使用它的地方使用其他任何东西是合理的。
在这种背景下,很自然地尝试通过放弃单字节编码的效率,转而支持单一的综合编码来涵盖世界上所有语言使用的所有字符,从而扩展 ISO 8859 的成功。一个商业联盟和 ISO 并行进行了两项互补的工作。毫不奇怪,ISO 的工作组将其工作称为 Universal Multiple-Octet Coded Character Set(缩写为 UCS),而商业联盟则采用了活泼的“Unicode”。同样不足为奇的是,Unicode 联盟(在统一双字节编码的商业优势的驱动下)能够在 1991 年制定一个标准,将世界上几乎所有的脚本都统一在一个双字节编码中,并编纂了每个字符的属性字典,指导连字和双向文本等用法,而 ISO 最终在 1993 年定义了 UCS 的双字节版本和四字节(31 位)版本,但没有其他属性。同样在 1993 年,Unicode 和 UCS-2 字符集和编码得到了统一,尽管每个标准都保留了独特的功能。
为什么要进行单独的努力?当然,65336 个不同的字符对于任何人来说都足够了。谁需要 20 亿个字符?
单独努力的原因很容易解释。Unicode 努力是由单一编码的商业优势驱动的。在互联网协议的标准化方面已经付出了很多努力,首先是克服“8 位不干净”的互联网软件造成的问题,然后是添加对亚洲语言的支持,最后是创建用于协商字符集的协议。如果通过采用一种标准编码可以避免所有这些努力以及必要的实现效率低下,那就太好了。正如我们将看到的,这并非易事,但 Unicode 的标准化可以大大节省成本,包括开发成本、处理器时间和协议开销。
另一方面,ISO 组主要关注的是创建一个真正通用的框架,以避免将来需要进行另一次“通用”标准化工作。它更关注通用性,并避免对了解不深的领域进行标准化,例如双向文本的处理。实际上,UCS-4 目前仅包含 Unicode 标准定义的那些字符,这些字符被整体采用为 UCS-4 的基本多语言平面,并且等同于 UCS-2。
他们担心的原因是,很明显,65536 个字符对于某些用途来说是 不 够的。尽管 Unicode 中还剩下超过 18,000 个未分配的代码位置,但象形文字或汉语的古典学者可以迅速用表意文字填满这些位置。当前的“统一汉字”(中文、日文、韩文和越南文使用的汉字)在经过极具争议的统一过程后,减少到 20,902 个,这表明一些有争议的字符可能会重新分配给代码点。古韩文(组合的韩语音节)将增加数千个。Unicode 还明确排除了标准化的图形符号,例如音乐、舞蹈和电子学中使用的符号。显然,真正通用的字符集将很容易超过双字节编码施加的 65536 个字符的限制。
为什么 ISO 10646 指定 31 位编码?当前的硬件是面向字节的,但是没有特别的理由停留在 24 位,因为只有某些视频硬件可以有效地使用三字节的内存字。大多数当前硬件最有效访问的字大小是四个字节。考虑到可能有数十亿个字符,因此认为在每个字符中保留一位用于任意内部处理目的是明智的;但是,在将字符传递给期望 UCS 字符的实体之前,必须清除该位。
同样,已经保留了大的连续私有空间,其中包含 1/8 的三字节代码,即高位字节为 0 的代码,以及 1/4 的四字节代码。这意味着应用程序可以在此空间中以自然的方式嵌入整个国家标准字符集(特别是保留其排序),而不会与标准、当前或任何未来扩展发生冲突的可能性。ISO 10646 不一定推荐此类技术,但肯定允许它们。这仍然留下了超过 15 亿个代码点用于未来的标准化;似乎可以肯定,大多数代码点将在很长一段时间内保持保留但未使用状态。
但是,Unicode,更不用说 UCS-4,似乎不太可能很快获得 ISO 8859-1 所享有的成功。首先,东方语言的数字字符集标准尚不令人满意,部分原因是这些语言尚未完全标准化。所有汉字语言的标准化工作仍在积极进行中。例如,如果日本人尚未确定国家字符集,他们如何对 Unicode 的统一汉字感到满意?最近一篇题为 日语正处于危险之中! 的文章声称 Unicode 将是日语的末日,许多精通计算机的日本人对它的论点表示不同程度的同情。
其次,在多语言文本中,可能需要搜索一些特定的汉字(而不是其韩语或日语同源字)。在 Unicode 中,这需要维护大量的周围上下文,其中将包含指示语言的标记,并且在 Unicode 编码的纯文本中按定义是不可能的。虽然您可能会指出 ISO 8859-1 文本也存在类似的困难,但情况并非如此。汉字是一个具有特定含义的语义单元,不同于字母字符。实际上,汉字统一过程通常忽略语义。因此,它将形状与给定汉字相同的日文字符与含义不同的汉字混淆。另一方面,ISO 8859-1 字符很少被孤立地搜索;如果是这样,它们就没有语义内容。
第三,亚洲人在亚洲语言之间的多语言程度不如欧洲人在欧洲语言之间的多语言程度,尽管这种情况正在迅速变化。尽管如此,我们不太可能看到一个“亚洲瑞士”,同时使用中文、日语和越南语作为官方语言。因此,Unicode 相对于国家标准的优势并没有那么大。
第四,从西欧的角度来看,ISO 8859-1 已经实现了对支持多语言处理的单个字符集的大部分收益。西欧人对 Unicode 的需求不大。
在不久的将来,Unicode 对计算机和操作系统供应商(包括 Linux)最有用。通过支持 Unicode 作为基本内部代码集,可以提供一种明确的方式来避免语言混乱。添加新语言将只是提供字体、Unicode 到字体编码的映射表和翻译消息的问题。不需要额外的编程工作,并且保证向后兼容性。这并非微不足道。下面给出了一个内核补丁的示例,该补丁用于使使用 MS-DOS 或 VFAT 文件系统挂载的日语 Windows 文件系统的目录列表可读。此内核补丁肯定永远不会集成到内核源代码中,因为无法确保它不会搞乱非日语名称。
除了上面提到的国家标准字符集外,还有许多其他字符集仍在常用。一些更重要的字符集包括俄罗斯的 KOI-8(ISO 8859-5 的替代方案)、用于以梵文字母书写的印度语言的 ISCII 和用于越南语的 VISCII。当然,美国 ASCII 是可用的。
其他重要的字符集是由行业或个别公司定义的字符集。这些私有字符集的一个重要特征是它们的编码通常不符合 ISO 2022 标准,这使得系统之间的互换变得困难。Microsoft 走在前列,定义和注册了无数的 Windows 字符集。这些字符集大多数都是 ASCII 派生的,并且与 ISO 8859 编码密切相关,因此,尽管细微的差异让程序员感到恼火,但对于用户来说,它们通常是微不足道的。但是,在亚洲语言领域,不符合 ISO-2022 标准的编码 Shift JIS(Microsoft 和 Apple 操作系统使用的日语编码)和 Big 5(由五家大型台湾制造商组成的联盟定义的繁体中文编码)非常重要。在这两种情况下,都使用了国际标准字符集未使用的部分代码空间。
在 Shift JIS 的情况下,其想法是包含所谓的半角片假名,即语音转录日语语言所需的 70 个左右的字符。在普通文本中很少使用,但在 DOS 8.3 格式的文件名中有点方便。这要求将它们编码为单个字节。日本标准 JIS X 0201 以其通常的代码点编码 ASCII,并将片假名放在值从 0xA1 到 0xDF 的字节中。Shift JIS 基于此标准,并使用简单的算法将标准 JIS 汉字代码转换为双字节代码,其中第一个字节的范围为 0x81 到 0x9F 和 0xE0 到 0xEF,JIS X 0201 未使用这些范围。
Unicode 和 UCS-4 永久性地解决了字符表示问题。但是,如上所示,Unicode 对于所有用途来说还不够完善,而 UCS-4 对于通用用途来说又太浪费了。此外,大量的硬件和软件都面向单字节字符集。因此,ISO 2022:1994 代码扩展技术标准(特别是,在一个数据流中使用多个字符集),该标准的最新版本最初由欧洲计算机制造商协会于 1971 年以 ECMA-35 的名称发布,仍然具有相关性。
ISO 2022 是一个相当抽象的标准。以下是其规定的简要概述。
将代码分为 7 位和 8 位类型;8 位表中的 256 个代码点分为左半部分(L,0x00 到 0x7F)和右半部分(R,0x80 到 0xFF)。7 位代码被认为仅使用左半部分。
将每个半部分中的 128 个点进一步划分为控制代码(C,0x00 到 0x1F)和图形代码(G,0x20 到 0x7F)。
CL 和 GL 中的代码 0x1B(转义)、0x20(空格)和 0x7F(删除)是固定的。GR 中的代码 0xA0 和 0xFF 通常保持未使用状态。
为处理类似于下面描述的图形字符的控制字符做出了规定,但在国际化讨论中,这些是不感兴趣的。
图形字符集必须以每个字符固定数量的字节进行编码。所有字符的所有字节都在 0x20 到 0x7F 的范围内,或者所有字符的所有字节都在 0xA0 到 0xFF 的范围内。其中字节 0x20 和 0x7F 或 0xA0 和 0xFF 从不 使用的字符集被称为 94n 字符集,其中 n 是字节数。否则,该字符集是 96n 字符集。
一种编码可以同时使用最多四个字符集,分别表示为 G0、G1、G2 和 G3。G0 必须是 94n 字符集;其他三个可以是 96n 字符集。字节的解释取决于移位状态。G0、G1、G2 或 G3 中的任何一个都可以通过 锁定移位 控制代码 LS0、LS1、LS2 和 LS3 分别调用到 GL 中。当字符集 G0 移位到 GL 中时,则 G0 用于解释范围为 0x20 到 0x7F 的字节。类似地,在 8 位编码中,右锁定移位 用于通过控制代码 LS1R、LS2R 和 LS3R 将字符集 G1、G2 或 G3 调用到 GR 中。然后,该字符集用于解释范围为 0xA0 到 0xFF 的字节。
可以通过使用 单次移位 控制代码 SS2 和 SS3 从 G2 和 G3 集中调用单个字符。
提供了转义序列,用于将新的字符集 指定 到 G0、G1、G2 和 G3 元素中。
给定版本的 ISO 2022 不需要提供上述所有移位和指定工具。例如,ASCII 没有提供任何工具。如果衍生标准提供了这些工具,则控制代码必须采用表 2 中所示的值。
可以被认为是 ISO 2022 标准的应用的三个代码示例是 ASCII、ISO 8859-1 和 EUC-JP。ASCII 是美国英语的标准编码。它是一个 7 位代码,其中 ASCII 控制代码指定给 C0,ASCII 图形字符指定给 G0,C1、G1、G2 和 G3 未使用。C0 在 CL 中调用;G0 在 GL 中调用。不使用移位功能。
ISO 8859-1 是一个 8 位代码,其中 C0 未指定(但通常 C0 包含 ASCII 控制字符),ASCII 图形字符指定给 G0,拉丁文-1 字符集指定给 G1。C1、G2 和 G3 未使用。C0 在 CL 中调用,G0 在 GL 中调用。不使用移位功能。
打包格式的 EUC-JP 是一个 8 位代码,其中 C0 未指定,但通常使用 ASCII 控制字符;指定给 G0 的 ISO 646 的 JIS X 0201 Roman 版本;主要日文字符集 JIS X 0208 包含几个字母表、标点符号、日文假名音节、一些装饰字和大约 6000 个最常用的汉字(表意文字),指定给 G1;指定给 G2 的 JIS X 0201 中的半角片假名;以及指定给 G3 的大约 8000 个不太常见的汉字的 JIS X 0212 集。C0 在 CL 中调用,G0 在 GL 中调用,G1 在 GR 中调用。不使用锁定移位功能。半角片假名和 JIS X 0212 汉字必须分别使用单次移位 SS2 和 SS3 访问,并且它们被移位到 GR 中。
最后,ISO 2022 通常用于互联网邮件和多语言文档。使用 7 位版本,并且每个字符集都必须在使用前指定给 G0。
ISO 2022 最重要的方面是,ASCII 控制字符范围内的代码点不得用于图形字符。这意味着使用符合 ISO 2022 的编码的文本文件在显示时将像文本一样(带有换行符,并且不会在您的终端或模拟器中引起奇怪的行为)。如果您没有字体或您的软件不理解指定转义序列,您将看到乱码,但至少您的终端将继续工作。
第二个有用的事实是,在大多数情况下,ASCII 或 ISO 646 的某个版本被指定给 G0。像 EUC-JP 这样的编码,其中 ASCII 指定给 G0 并调用到 GL,所有其他字符集都调用到 GR,在 8 位干净系统中是“文件系统安全的”。这或多或少是 ISO 2022 的 EUC 变体的定义。
一些不符合标准并因此经常在未专门为其准备的软件中引起问题的编码是 Shift JIS、Big 5、VISCII 和 KOI-8。Shift JIS 特别让我恼火,因为我双启动 Linux 和 Windows 95 用于日语 OCR、将 Microsoft Word 文档转换为纯文本和空当接龙,并且带有日语的目录列表总是搞砸了。幸运的是,我还没有理由尝试访问以日语命名的文件。内核补丁可用于帮助处理此问题,但它们是非官方的,并且将保持这种状态,因为它们本质上是危险的。也就是说,它们在大多数时候都适用于日语,但并非所有时候都适用,并且它们无法正确处理非日语 8 位编码。
互联网最早和最重要的应用之一是消息传递,无论是直接发送给接收者(电子邮件)还是广播(Usenet 新闻组)。从国际化的角度来看,这些基本上是相同的;国际化不关心传输机制,只关心如何处理内容。
由于消息传递是一个早期的应用程序,因此它假设一个相当受限的环境。特别是,它假设数据流仅限于 7 位位字符串,甚至不能确定所有 ASCII 字符都能无错误地传输。特别是,如果消息起源于 UNIX 世界,通过 BITNET(即 EBCDIC 编码)并返回到 UNIX,则某些字符可能会损坏。当然,如今这种损坏不太可能发生,但当标准制定时,这很常见。现在,这些限制已在标准中定义并在软件中广泛实施,因此即使互联网数据传输的硬件和软件非常可靠,这些限制也可能会在可预见的未来继续存在。
互联网邮件传输协议 (SMTP) 在 RFC-821 中定义。感兴趣的主要规定是传输通道必须正确传输所有 128 个 ASCII 字符。鼓励使用 8 位干净通道,但隐式地,7 位字符是规范。互联网消息在 RFC-822(用于电子邮件)和 RFC-1036(用于 Usenet)中进行了标准化。RFC-1036 几乎完全采用了 RFC-822,因此我将这三个标准统称为 RFC-822。
RFC-822 首先旨在与 RFC-821 兼容。消息的内容分为与邮件传输系统相关的部分,即 标头,以及与传输消息无关的部分,即 正文。RFC-822 允许用户自行承担风险在正文中发送 8 位内容,但标头必须采用 7 位代码,特别是 ASCII。这对非英语用户来说相当烦人。为了允许在主题标头和注释(特别是与地址关联的全名)中使用非英语文本,并为非 ASCII 正文内容(包括非英语文本和各种类型的二进制数据)提供可靠的传输,定义了多用途互联网邮件扩展协议套件。如今,此标准占据了不少于五个 RFC(2045-2049)。我们将只对与国际化相关的部分感兴趣。
MIME 传输编码类似于上面讨论的 UCS 转换格式。它们允许以一种不会阻塞传输通道或被其损坏的方式表示任意内容。MIME 定义了两种传输编码,可打印字符引用编码 和 BASE64。
可打印字符引用编码非常简单。任何字节都可以用其十六进制代码表示,前面加上等号。因此,空格字符表示为 =20,西班牙语小写字母 enye (ñ) 表示为 =F1。拉丁文大写字母 A 为 =41。但是,通常仅在三种情况下使用这些编码。首先,由于等号是转义字符,因此必须表示为 =3D。其次,某些软件会去除尾随空格,尤其是在使用面向记录的存储且不使用控制字符来表示换行符的系统上。行尾的空格或制表符将分别编码为 =20 或 =09。这对于 Usenet 新闻组上使用的签名约定很重要。最后,包括大多数控制字符在内的非 ASCII 字节将被编码。因此,可打印字符引用编码旨在用于诸如西欧语言之类的应用程序,在这些应用程序中,大多数字符来自基本拉丁文(即 ASCII)集。实际上,人们很快学会了准确地读取可打印字符引用文本,而无需对其进行解码。
请注意,这是一种传输编码。它是一种纯粹的机械转换,不提供有关字符预期含义的任何信息。虽然 ñ 是 =F1 的一种解释,但还有许多其他解释,包括十个 ISO 8859 字符集中的每一个都有不同的解释。可打印字符引用编码未提供任何指示说明预期是哪种解释。
BASE64 编码旨在成为任意二进制数据(包括图像和音频)的可靠编码。但是,它也常用于日语等语言,在这些语言中,如果不解码,则将每个字节单独解释为 ASCII 字符是难以辨认的。它比可打印字符引用编码更有效,仅比原始文本多使用 33% 的空间,而每个引用的字符使用的空间是未编码字节的三倍。BASE64 类似于著名的 uuencode 格式,该格式长期以来在 UNIX 中用于相同的目的,但是用于编码的字符仅限于 52 个拉丁字母、10 个十进制数字、加号和斜杠。
等号也用作填充。选择此字符的原因是,base 64 是面向字节的编码的便捷基数,因为四个 base-64 位可以编码 24 位或 3 个字节。所选字符可以通过所有已知系统完整传递,这对于 uuencode 算法中使用的一些标点符号来说并非如此。编码算法很明显
将数据流分解为三个字节的组。最后一组可能有一个或两个字节,并将进行特殊处理。
对于每组三个字节,将字节连接成一个 24 位字符串,然后将其分解为四个 6 位组。将每个组解释为 6 位二进制整数,并索引到上面的表中。这将生成一组四个 base-64 位。将它们添加到输出中。
如果存在剩余组,则其中包含一个或两个字节。添加一个或两个空字节以完成三个字节的组。现在按照步骤 2 中的方式处理它,但如果组中有一个字节,则将前两个 base-64 位添加到输出中,并在末尾填充两个等号以组成一组四个字节。如果最后一组中有两个字节,则将前三个 base-64 位添加到输出中,并用最后一个等号填充以组成一组四个字节。
请注意,通过使用等号,始终可以精确解码原始文本;末尾甚至不会出现伪造的空字符。此外,考虑到限制,该算法速度非常快且节省空间。
符合 MIME 标准的消息必须具有以下形式的版本标头
MIME-Version: 1.0
有些邮件程序非常挑剔,以至于拒绝处理缺少有效 MIME-Version 标头的邮件。这本应很有趣,但事实是,许多邮件程序要么没有正确实现 MIME 功能,要么生成非法 MIME-Version 标头,要么根本未能插入 MIME-Version 标头。
MIME 标头格式的唯一版本是 1.0。MIME 标准经历了多次修订和扩展,但基本格式在版本 1.0 中保持不变。这些修订和标准为某些参数添加了新值,并为某些模棱两可的领域指定了解释,但语法未更改。在标头标签和值中,大小写均无关紧要。下面使用的大写风格或多或少是传统的,但不是必需的。
保护内容的一种方法,或者至少检查内容是否被截断,是提供 Content-Length 标头。MIME 标准允许这样做。正文的一般编码类型在 content-transfer-encoding 标头中声明。默认值为
Content-Transfer-Encoding: 7-bit
其他允许的值包括“quoted-printable”和“base64”(均隐式为 7 位)以及“8-bit”。
接下来,指定正文的内容类型。在大多数消息中,它将是纯文本,指定为
Content-Type: text/plain
如今在邮件中常见的其他文本类型是 text/rich 和 text/html。转发的消息(没有前言注释)的内容类型可以指定为 message/rfc-822。消息也可以是多部分的。这通常用于添加多媒体附件,但也可以用于将正文分解为不同语言的组件。
MIME 标准规定,除非另有说明,否则字符集为 ASCII。RFC-822 要求所有标头均为 ASCII,因此 MIME 字符集规范仅适用于消息正文。此规范使用内容类型标头的 charset 参数完成。默认值可以显式指定为
Content-Type: text/plain;charset=us-ascii
请注意,可选参数以 keyword=value 形式指定。指定 ASCII 的正确方法是“us-ascii”,因为这是在 IANA 注册的首选形式。MIME 的有效字符集列表位于 http://www.hunnysoft.com/mime/。欧洲人通常会使用
Content-Transfer-Encoding: 8-bit Content-Type: text/plain;charset=iso-8859-1
日本的电子消息标准是 ISO 2022 的一个版本,称为 ISO-2022-JP。事实上,这种编码只需稍微扩展即可。它也可以用于中文和韩文,甚至可以作为多语言编码。扩展版本称为 ISO-2022-JP-2 或 ISO-2022-INT。
MIME 标准还提供了一种将非 ASCII 文本放入标头的机制。RFC-822 使此行为非法,因此,使用此机制将导致不实现 MIME 的邮件程序显示乱码。但是,如今大多数邮件程序都支持 MIME,因此这不应造成任何问题。如果您的收件人抱怨,请告诉他们获取支持 MIME 的邮件程序。
该机制很简单。非 ASCII 文本根据方便性使用 quoted-printable 编码或 BASE64 编码进行编码,并捆绑到 编码字 中。必须捆绑到编码字中的原因是 Content-Type 标头应用于正文,如果正文是多部分的,则不会有 charset 参数。使用特殊标头来控制标头的格式似乎很愚蠢,因此编码字本身将包含必要的字符集信息。
编码字的格式以字符 =? 开头,继续是使用的编码名称,字符 ?,字母 Q(对于“quoted printable”)或字母 B(对于 BASE64),再次是字符 ?,编码文本,最后是字符 ?=。例如,法语单词 “voil<\#226>” 编码为 =?ISO-8859-1?Q?V=F3il=E0?=. 当然,非常低效,但这些每个消息只会使用几次。请注意,在 quoted printable 编码上施加了一个额外的限制,在基本编码中不存在:编码文本中的任何问号都必须进行编码。否则,序列 <question mark><encoded octet> 将被解释为编码字的结尾。
到目前为止,还没有通用标准,但 HTTP 1.1 是一个协议示例,该协议为浏览器和服务器协商要提供的内容类型提供了便利。特别是,浏览器可以自动指定内容的语言和首选编码。如果该语言的内容不可用,服务器可能会忽略此请求。与提供各种语言的翻译链接相比,此方法对用户来说肯定更方便。
MIME multipart/alternative 格式提供了内容协商的另一个示例。此格式允许以多种方式呈现相同的内容。例如,邮件消息可以格式化为纯文本和 HTML。许多 UNIX 邮件用户代理不理解 HTML,但 Netscape 肯定理解。这允许“哑” MUA(或讨厌 HTML 电子邮件的人)以最少的 MIME 理解来阅读纯文本电子邮件,而那些使用 Netscape 阅读邮件的人可以获得 HTML 呈现的(在我看来,可疑的)好处。
这两篇文章概述了国际化原则。它并不简短,但远非完整或全面。Linux 现在在国际化的基本设施方面处于相当好的状态,GNU libc 版本 2(在 Linux 系统上通常称为 glibc 或 libc6)的广泛传播证明了这一点。
一些问题仍然需要解决,尤其是在亚洲语言方面。我们可以预期标准会随着时间的推移变得更加全面。例如,区域设置可以处理换行约定,或者可以扩展区域设置模型以直接支持多语言应用程序。
但是,今天的主要努力必须来自应用程序程序员和多语言志愿者。应用程序程序员需要使用 POSIX 区域设置工具和 GNU gettext 来国际化他们的程序。多语言志愿者应加入 GNU 翻译项目,并帮助翻译他们喜爱程序的 message catalog。
