Tcl 编程/国际化
“一切皆为字符串”,这是 Tcl 的座右铭。字符串是一个(有限长度的)字符序列。那么,什么是字符?字符不同于字形,字形是我们在屏幕或纸张上看到的书写元素,它代表字符,但同一个字形可以代表不同的字符,或者同一个字符可以用不同的字形来表示(例如,想想字体选择器)。
此外,字符与内存中的字节或字节序列也不同。在离开 ASCII 的安全港后,这些字节序列可能代表一个字符,但不是绝对的。
让我们尝试以下工作定义:“字符是书写单元的抽象概念”。这通常等同于字母、数字或标点符号,但字符可以比这更多或更少。更多:连字,两个或多个字母的组合,有时可以被视为一个字符(甚至排列在多行中,如日语 U+337F ㍿ 或阿拉伯语 U+FDFA ﷺ)。更少:添加到字符上的小标记(变音符号),例如 Nürnberg (U+00FC) 中的 ü 上的两个点,可以将它变成一个新的“预组合”字符,如德语中那样;或者它们可以被视为一个单独的“组合字符”(例如 U+0308),在渲染时添加到前一个字形 (u, U+0075) 上,不移动渲染位置——这对于西班牙语、荷兰语甚至(较旧的)英语正字法中这两种点的功能(“变音符”)更为合理的处理方式:考虑 1950 年之前使用的拼写“coöperation”。这种组合是打字机上“死键”的软件等效项。
虽然是一个抽象的概念,但字符当然可以有属性,最重要的是一个名称:一个字符串,它描述了它的功能、用法、发音等。各种名称集已在 Postscript (/oumlaut) 或 HTML (ö) 中正式化。在技术应用中非常重要,当然是对字符分配一个数字(通常是一个非负整数)来识别它——这就是编码的本质,其中这些数字更正式地称为代码点。其他属性可能是谓词,例如“是否大写”、“是否小写”、“是否数字”。
这三个域之间的关系并不太复杂:编码控制如何将 1..n 个字节序列解释为字符,反之亦然;渲染过程将抽象字符转换为字形(通常通过像素或矢量图案)。反之,从一组像素中理解字符序列,并正确地表示它们,是更为困难的 OCR 技术,这里不再赘述。
将字符映射到数字(代码点)的编码工作,比电子计算的历史更悠久。据说弗朗西斯·培根(1561-1626)在大约 1580 年使用了一种 5 位编码,其中位用“a”或“b”表示,用于英文字母表(没有字母 j 和 u!),远早于莱布尼茨在 1679 年讨论二进制算术。在实际使用中,早期的一种编码是 1932 年标准化的 5 位 Baudot/CCIT-2 电传(穿孔纸带)代码,它可以通过两种模式来表示数字和一些标点符号。我曾在使用六位“Fieldata”字符的 Univac 机器上工作过,因为硬件字长为 36 位。虽然 IBM 在 EBCDIC 代码中使用了 8 位,但更著名的美国信息交换标准代码 (ASCII) 在每个字符中基本上完成了相同的任务,每个字符使用 7 位,这足以用于大小写基本拉丁语(英语)以及数字和一些标点符号以及其他“特殊”字符——由于硬件倾向于以 8 位字节作为最小的内存单元,留下一位用于奇偶校验或其他目的。
在美国以外,最重要的用途当然是容纳更多代表国家书写系统的字母——希腊语、俄语,或者几乎每个欧洲国家都使用的带重音或“变音符”的混合字符集。甚至英国也需要一个代码点来表示英镑符号。一般的解决方案是使用在将 ASCII 实现为 8 位字节时可用的 128 个附加位置,十六进制 80..FF。许多这样的编码被定义和使用
- ISO 标准编码 iso8859-.. (1-15)
- MS/DOS 代码页 cp...
- Macintosh 代码页 mac...
- Windows 代码页 cp1...
东亚国家中国、日本和韩国都使用数以千计的字符集,因此“高 ASCII”方法在那里不可行。相反,ASCII 概念扩展到 2x7 位模式,其中 94 个可打印的 ASCII 字符表示 94x94 矩阵中的行和列。这样,所有字符代码在实践中都是两个字节宽的,可以容纳数千个汉字/日文汉字/韩文,以及数百个其他字符(ASCII、希腊语、俄语字母表、许多图形字符)。这些国家的多字节编码是
- JIS C-6226(日本,1978 年;1983 年、1990 年大幅修订)
- GB 2312-80(中国大陆,1980 年)
- KS C-5601(韩国,1987 年)
如果直接实现 2x7 模式,则此类编码中的文件无法与 ASCII 文件区分开来,除非无法读取。但是,它在日语电子邮件中确实有一些应用(在 8 位干净邮件服务器出现之前形成的主流约定中),使用 ESC 控制字符的 ANSI 转义代码被用于声明文本部分为 ASCII 或 JIS C-6226,这被称为“JIS 编码”(或更准确地说是 ISO-2022-JP)。
在其他地方,为了以更实用的方式透明地处理这两种类型的字符串,扩展了“高 ASCII”方法,以便 00..7F 中的字节以 ASCII 面值进行处理,而高位设置为 1(80..FF)的字节被解释为多字节代码的半个字节。例如,GB2312 中的第一个汉字,第 16 行第 1 列(简称为十进制 1601),给出两个字节
16 + 32 + 128 = 176 = 0xB0 1 + 32 + 128 = 161 = 0xA1
这种实现被称为“扩展 UNIX 代码”(EUC),分别在国家风格的 euc-cn(中国)、-jp(日本)、-kr(韩国)中使用。
为了增加“汉字汤”的混乱,与 euc-cn 和 euc-kr 不同,euc-jp(日本)并没有在 Windows 或 Macintosh 平台上广泛采用,这些平台倾向于使用不兼容的 ShiftJIS,它重新排列代码以腾出空间用于旧的单字节代码,用于音译片假名字符。此外,台湾和香港使用自己的“Big 5”编码,它不使用 94×94 结构。与 EUC 编码不同,ASCII 字节可以出现在这些编码中双字节代码的第二个字节中,这在 EUC 编码的常见扩展中也是如此(GBK 扩展了 euc-cn,统一韩文代码扩展了 euc-kr)。
Unicode 标准试图将所有现代字符编码统一到一个一致的 16 位表示中。考虑一个页面,其中有一个 16x16 的表格,其中填充了西欧语言(ISO 8859-1),下半部分是 ASCII 代码点。将其称为“页面 00”,并想象一本书,其中有 256 页或更多这样的页面(其中包含各种其他字符,大多数是 CJK),那么您就对 Unicode 标准有了相当清楚的认识,其中字符的代码位置是“U+”十六进制(页码*256+单元格号码),例如,U+20A4 是英镑符号的一种版本。Unicode 由计算机行业发起(www.unicode.org),与 ISO 10646 一同发展,ISO 10646 是一种并行标准,提供了一种最多 31 位的编码(留一位用于奇偶校验?),范围相同。软件必须允许 Unicode 字符串适合 i18n。从 Unicode 3.1 版本开始,16 位限制被超越,适用于一些罕见的书写系统,也适用于 CJK 统一表意文字扩展 B——显然,即使 65536 个代码位置也不够。Unicode 3.1 中的总数为 94,140 个编码字符,其中 70,207 个是统一汉字;第二大组是 14000 多个韩文。而且这个数字还在不断增长。
UTF-8 旨在涵盖 7 位 ASCII、Unicode 和 ISO 10646。字符表示为 1..6 个八位字节序列——在字符集业务中称为八位字节——(对于 ASCII:1,对于 Unicode:2..4),如下所示
- ASCII 0x00..0x7F(Unicode 页面 0,左半部分):0x00..0x7F。没有变化。
- Unicode,页面 00..07:2 个字节,
110aaabb 10bbbbbb
,其中 aaa 是页码的最后几位,bb.. 是第二个 Unicode 字节的位。这些页面涵盖了欧洲/扩展拉丁语、希腊语、西里尔语、亚美尼亚语、希伯来语、阿拉伯语。 - Unicode,页面 08..FE:3 个字节,
1110aaaa 10aaaabb 10bbbbbb
。这些涵盖了基本多语言平面的其余部分,包括韩文、日文汉字等等。这意味着东亚文本在 UTF-8 中比纯 16 位 Unicode 长 50%。 - Unicode,补充平面:4 个字节,
11110ppp 10ppaaaa 10aaaabb 10bbbbbb
。这些不是 Unicode 的原始设计的一部分(只有 ISO 10646),但当 Unicode 标准变得清晰时,一个平面不足以实现 Unicode 的目标,它们就被添加到 Unicode 标准中。它们主要涵盖表情符号、古代书写系统、利基书写系统、大量的模糊日文汉字/汉字,以及一些在 Unicode 的原始设计之后才出现的非常新的书写系统。 - 超出 Unicode 的 ISO 10646 代码:4..6 个字节。由于当前的审批流程通过防止在 17 个 Unicode 平面之外分配 ISO 10646 来使这些标准保持同步,因此可以保证在可预见的未来这些代码不存在。
UTF-8 的一般原则是,第一个字节要么是单字节字符(如果小于 0x80),要么通过第一个 0 之前的 1 的数量来指示多字节代码的长度,然后用数据位填充。所有其他字节以位 10 开头,然后用 6 个数据位填充。由此得出,UTF-8 编码中的字节位于不同的范围内。
00..7F - plain ASCII 80..BF - non-initial bytes of multibyte code C2..FD - initial bytes of multibyte code (C0, C1 are not legal!) FE, FF - never used, so can be used to detect a UTF-16 byte-order mark (and thus, a non-UTF-8 file).
初始字节和非初始字节之间的区别有助于进行合理性检查,或与丢失数据重新同步。此外,它独立于字节顺序(与 UCS-16 相反,见下文)。但是,Tcl 将这些 UTF-8 细节屏蔽在我们面前:字符就是字符,无论它是 7 位、16 位还是(将来)更多。
字节序列 EF BB BF 是 \uFEFF 的 UTF-8 等效项,由 Windows 记事本检测,当文件以这三个字节开头时,记事本会切换到 UTF-8 编码,并在以 UTF-8 形式保存文件时写入它们。这并不总是用于其他地方,但通常会覆盖文件以它开头的其他声明的字符编码。
UCS-2 表示(在 Tcl 中称为“unicode”编码)更容易解释:每个字符代码都写为一个 16 位“短”无符号整数。实际的复杂之处在于构成“短”的两个内存字节可以排列成“大端”(Motorola、Sparc)或“小端”(Intel)字节顺序。因此,为 Unicode 定义了以下规则
- 代码点 U+FEFF 被定义为字节顺序标记(BOM),后来改名为“零宽度不换行空格”,尽管实际上将其用于其次要空格角色现在被认为是过时的。
- 代码点 U+FFFE(以及 FFFF)是保证的非字符,永远不会是有效的 Unicode 字符。它们旨在用作哨兵,或用于其他内部用途,以及用于检测字节顺序标记是否被错误读取。
这样,一个 Unicode 阅读应用程序(即使是记事本/W2k)也可以轻松地检测到当它遇到字节序列 FFFE 时出现问题,并交换接下来的字节对——一种处理不同字节顺序的最小且优雅的方法。
虽然 Unicode 最初旨在完全适应 UCS-2,而整个 ISO 10646 需要一个 32 位“长”(称为 UCS-4 或 UTF-32),但这种区别后来被废除了,因为一个十六位平面不再被认为足以实现 Unicode 的目标。因此,十六个“补充”平面被添加到 Unicode 中,原始的 16 位平面被保留为“平面 0”或“基本多语言平面”。为了在预期为 UCS-2 流的接口中使用来自补充平面的字符,范围 U+D800–U+DFFF 保证永远不会用于 Unicode 字符。这允许补充平面中的字符在其他情况下以 UCS-2 流的形式明确表示,这被称为 UTF-16。
补充字符在 big-endian UTF-16 中表示如下,其中 ssss
表示平面号减 1。在 little-endian 中,前两个字节被交换,后两个字节被交换,整个序列不会反转。这是因为它被视为两个 UCS-2 字符的序列。
110110ss ssaaaaaa 110111aa bbbbbbbb
对于 XML,编码自识别通过开头标签中的编码属性定义。但这只对可以被视为 ASCII 的文档有用,直到那时,因此必须预先检测 UTF-16/UCS-2 或以其他方式指示。
从 Tcl 8.1 开始,i18n 支持被引入字符串处理,明智地决定
- 使用 Unicode 作为通用字符集
- 使用 UTF-8 作为标准内部编码
- 提供对许多其他正在使用的编码的转换支持。
但是,由于不等长的字节序列使简单任务(如索引字符串或确定其字符长度)变得更加复杂,因此在这种情况下,内部表示将转换为固定长度的 16 位 UCS-16。(这带来了最近的 Unicode 跨越 16 位障碍的新问题……当实际使用证明这一点时,这将不得不更改为 UCS-32,或每个字符 4 个字节。)
因此,并非所有 i18n 问题都对用户自动解决。仍然需要分析看似简单的任务,如大写转换(土耳其带点/不带点 I 构成异常)或排序(“整理顺序”不一定与 Unicode 的数字顺序相同,如 lsort 默认情况下会应用),如果需要更正确的行为,则编写自定义例程。其他与区域设置相关的 i18n 问题,如数字/货币格式、日期/时间处理,也属于这一组。我建议从 Tcl 提供的默认值开始,如有必要,根据需要自定义外观。如果交换了本地化数字数据,一方使用点,另一方使用逗号作为小数点,那么国际数据交换将受到严重阻碍……
严格来说,Tcl 实现“违反了 UTF-8 规范,该规范明确禁止字符的非规范表示,并要求输入中格式错误的 UTF-8 序列为错误。……我认为这是一个优势。但规范说“必须”,所以至少在技术上我们是不符合规范的。”(Kevin B. Kenny 在 Tcl 聊天中,2003-05-13)
如果文本数据在您的 Tcl 脚本内部,您只需要知道 \uxxxx 表示法,它被替换为 Unicode U+xxxx(十六进制)的字符。这种表示法可以在 Tcl 替换发生的地方使用,甚至在花括号中的正则表达式和字符串映射对列表中使用;否则,您可以通过对相关字符串进行 substing 来强制使用它。
为了证明例如 scan 透明地工作,这里有一个单行代码,将任何 Unicode 字符格式化为 HTML 十六进制实体
proc c2html c {format "&#x%4.4x;" [scan $c %c]}
相反,它需要更多的行
proc html2u string { while {[regexp {&#[xX]([0-9A-Fa-f]+);} $string matched hex]} { regsub -all $matched $string [format %c 0x$hex] string } set string } % html2u "this is a &x20ac; sign" this is a € sign
对于所有其他目的,两个命令基本上提供了所有 i18n 支持
fconfigure $ch -encoding $e
如果与系统编码不同,则为打开的通道(文件或套接字)启用从/到编码 e 的转换;
encoding convertfrom/to $e $string
正如它所说,另一个编码始终是 Unicode。
例如,我可以轻松地使用以下命令从十六进制转储中解码字节 EF BB BF
format %x [encoding convertfrom utf-8 \xef\xbb\xbf]
在一个交互式的 tclsh 中,发现它代表了著名的字节顺序标记 FEFF。在 Tcl 内部,(几乎)所有内容都是 Unicode 字符串。所有与操作系统的通信都使用“系统编码”完成,您可以使用 [encoding system] 命令查询(但最好不要更改)它。典型的值为欧洲 Linux 上的 iso8859-1 或 -15,以及欧洲 Windows 上的 cp1252。
内省:使用以下命令找出您的安装中有哪些编码可用
encoding names
您可以通过生成一个 .enc 文件并将其复制到其他 .enc 文件所在的目录 lib/tcl8.4/encoding(或类似目录)中来添加新的编码。有关编码文件格式(它们是文本文件,主要由十六进制数字组成),请参阅手册页 http://www.tcl.tk/man/tcl8.4/TclLib/Encoding.htm 。您的 .enc 文件的基名(不带 .enc 扩展名)将是它可以被寻址的名称,例如,对于编码 iso4711,将文件命名为 iso4711.enc。
最后,msgcat 包支持应用程序的本地化(“l10n”),它允许消息目录用于将字符串(通常用于 GUI 显示)从基语言(通常为英语)翻译成当前区域设置选择的目标语言。例如,要为法国本地化的应用程序可能包含一个文件 en_fr.msg,为简单起见,它只有一行
msgcat::mcset fr File Fichier
在应用程序本身中,您只需要
package require msgcat namespace import msgcat::mc msgcat::mclocale fr ;#(1) #... pack [button .b -text [mc File]]
让按钮显示“File”的本地化文本,即“Fichier”,该文本是从消息目录中获取的。对于其他区域设置,只需要通过从基语言进行翻译来生成新的消息目录。通常,区域设置信息可能来自环境 (LANG) 或注册表变量,而不是像 (1) 中那样显式设置。
在显示器或打印机上渲染国际字符串可能会带来最大的问题。首先,您需要包含相关字符的字体。幸运的是,越来越多的包含国际字符的字体可用,Bitstream Cyberbit 是先驱者之一,它包含大约 40000 个字形,并且在一段时间内可以在网络上免费下载。Microsoft 的 Tahoma 字体也增加了对大多数字母书写系统的支持,包括阿拉伯语。与 Windows 2000 一起提供的 Arial Unicode MS 包含了 Unicode 中几乎所有的字符,因此即使是简单的记事本也可以使用它变得真正国际化。
但拥有一个好的字体还不够。虽然内存中的字符串按逻辑顺序排列,地址从文本的开头到结尾递增,但它们可能需要以其他方式渲染,将变音符号移到前一个字符的不同位置,或者对于从右到左(“r2l”)书写的语言来说最明显:阿拉伯语、希伯来语。(Tk 仍然缺乏自动的“bidi”方向处理,因此 r2l 字符串必须在内存中“错误地”定向以在渲染时显示正确——请参阅 Wiki 上的简单阿拉伯语渲染器。)
正确的 bidi 处理也会影响光标移动、行对齐和换行。从右到左前进的垂直线在日本和台湾很流行——如果你必须渲染蒙古语,这是必须的。
梵文等印度文字是包含大约 40 个字符的字母,但辅音和元音的顺序在渲染时部分颠倒,辅音群必须渲染为包含两个或多个字符的连字——纯单个字母对印度人来说看起来非常难看。一种印度文字的字体已经包含几百个字形。不幸的是,印度连字不包含在 Unicode 中(而阿拉伯语连字包含在内),因此各种供应商标准适用于对这些连字进行编码。
这里有一个小脚本,它可以显示您的系统可以使用的哪些奇异字符。它创建一个文本窗口,并尝试为指定的语言显示一些示例文本(屏幕截图来自 PocketPC 的 Bitstream Cyberbit 字体)
pack [text .t -font {Helvetica 16}] .t insert end " Arabic \uFE94\uFEF4\uFE91\uFEAE\uFECC\uFEDF\uFE8D\uFE94\uFEE4\uFEE0\uFEDC\uFEDF\uFE8D Trad. Chinese \u4E2D\u570B\u7684\u6F22\u5B57 Simplified Chinese \u6C49\u8BED Greek \u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AE\ \u03B3\u03BB\u03CE\u03C3\u03C3\u03B1 Hebrew \u05DD\u05D9\u05DC\u05E9\u05D5\u05E8\u05D9\ \u05DC\u05D9\u05D0\u05E8\u05E9\u05D9 Japanese \u65E5\u672C\u8A9E\u306E\u3072\u3089\u304C\u306A,\ \u6F22\u5B57\u3068\u30AB\u30BF\u30AB\u30CA Korean \uB300\uD55C\uBBFC\uAD6D\uC758 \uD55C\uAE00 (\u3CA2\u3498) Russian \u0420\u0443\u0441\u0441\u043A\u0438\u0439\ \u044F\u0437\u044B\u043A "
没有指定字体或大小,因此您看到的是纯默认值(并注意 Tk 如何设法找到字符)。然后,您可以为想要查看的字体配置文本窗口小部件。
要将键盘上看不到的奇异字符输入到机器中,它们可以在最低级别上指定为转义序列,例如“\u2345”。但大多数用户输入将来自键盘,在不同的国家/地区存在着许多键盘布局。在 CJK 国家/地区,键盘和字符之间还有一个单独的编码级别:按键,它可能代表字符的发音或几何成分,被收集到一个缓冲区中,并在有足够上下文可用时(通常由屏幕菜单支持以解决歧义)转换为目标代码。
最后,屏幕上的“虚拟键盘”,用户可以通过鼠标点击选择字符,对于不经常使用罕见字符的用户来说尤其有用,因为物理键盘不会提示哪个键映射到哪个代码。这可以通过一组按钮实现,或者最小限度地使用一个画布来容纳提供的字符作为文本项目,以及绑定到<1>,这样点击字符就会将它的代码插入具有键盘焦点的窗口小部件中。参见 iKey:一个微小的多语言键盘。
术语“输入法”通常用于操作系统特定的 i18n 支持,但我没有这方面的经验,因为我是在德语 Windows 安装环境下进行 i18n 的。到目前为止,我对手工制作的纯 Tcl/Tk 解决方案完全满意——参见维基上的 taiku。
Lish 家族是一组音译,旨在将低级的 7 位 ASCII 字符串转换为一些主要非拉丁文字系统中的相应 Unicode 字符串。这个名字来自常见的后缀“lish”,如英语,它实际上是这个家族的中性元素,忠实地返回它的输入;-) 一些经验法则
- 一个 *lish 字符应该在适用时明确地映射到一个目标字符
- 一个目标字母应该在适用时由一个 *lish 字母 (A-Za-z) 表示。特殊字符和数字应该避免用于编码字母
- 映射应该是直观的,或者遵循既定的做法
- 在区分大小写的语言中,对应于大写和小写字母的替代字符也应该在较低的 ASCII 中区分大小写。
Tclers' Wiki http://mini.net/tcl/ 提供了 Lish 家族的成员,可以复制粘贴。我经常使用的是
- Arblish,它执行上下文字形选择和从右到左转换;
- Greeklish;
- Hanglish 用于韩语韩文,它根据首字母-元音-尾字母计算 Unicode;
- Ruslish 用于西里尔字母。
调用示例,返回指定输入的 Unicode
arblish dby w Abw Zby greeklish Aqh'nai hanglish se-qul heblish irwsliM ruslish Moskva i Leningrad
这一切都始于 Greeklish,它不是我的发明,而是希腊人在互联网上用来在没有希腊字体或字符集支持的情况下书写希腊语的。我只是扩展了我发现的惯例,用尾随的撇号来标记重音元音(所以它不再是严格的 1:1 音译)。特别注意将词尾的“s”转换为“c”,这样就可以产生最终的 sigma。以下是代码
proc greeklish str { regsub -all {s([ \t\n.,:;])} $str {c\1} str string map { A' \u386 E' \u388 H' \u389 I' \u38a O' \u38c U' \u38e W' \u38f a' \u3ac e' \u3ad h' \u3ae i' \u3af o' \u3cc u' \u3cd w' \u3ce A \u391 B \u392 G \u393 D \u394 E \u395 Z \u396 H \u397 Q \u398 I \u399 K \u39a L \u39b M \u39c N \u39d J \u39e O \u39f P \u3a0 R \u3a1 S \u3a3 T \u3a4 U \u3a5 F \u3a6 X \u3a7 Y \u3a8 W \u3a9 a \u3b1 b \u3b2 g \u3b3 d \u3b4 e \u3b5 z \u3b6 h \u3b7 q \u3b8 i \u3b9 k \u3ba l \u3bb m \u3bc n \u3bd j \u3be o \u3bf p \u3c0 r \u3c1 c \u3c2 s \u3c3 t \u3c4 u \u3c5 f \u3c6 x \u3c7 y \u3c8 w \u3c9 ";" \u387 ? ";" } $str }
测试
% greeklish Aqh'nai Αθήναι % greeklish "eis thn po'lin" εις την πόλιν
尽管韩语韩文有数千个音节字符,但仍然可以从音节的拼写中计算出 Unicode,反之亦然。以下是方法
proc hangul2hanglish s { set lead {g gg n d dd r m b bb s ss "" j jj c k t p h} set vowel {a ae ya yai e ei ye yei o oa oai oi yo u ue uei ui yu w wi i} set tail {"" g gg gs n nj nh d l lg lm lb ls lt lp lh m b bs s ss ng j c k t p h} set res "" foreach c [split $s ""] { scan $c %c cnum if {$cnum>=0xAC00 && $cnum<0xD7A3} { incr cnum -0xAC00 set l [expr {$cnum / (28*21)}] set v [expr {($cnum/28) % 21}] set t [expr {$cnum % 28}] append res [lindex $lead $l ] append res [lindex $vowel $v] append res "[lindex $tail $t] " } else {append res $c} } set res } proc hanglish2uc hanglish { set L ""; set V "" ;# in case regexp doesn't hit set hanglish [string map { AE R SH S R L NG Q YE X YAI F AI R YA V YO Y YU Z VI F } [string toupper $hanglish]] regexp {^([GNDLMBSQJCKTPH]+)?([ARVFEIXOYUZW]+)([GNDLMBSQJCKTPH]*)$} \ $hanglish -> L V T ;# lead cons.-vowel-trail cons. if {$L==""} {set L Q} if {$V==""} {return $hanglish} set l [lsearch {G GG N D DD L M B BB S SS Q J JJ C K T P H} $L] set v [lsearch {A R V F E EI X XI O OA OR OI Y U UE UEI UI Z W WI I} $V] set t [lsearch {"" G GG GS N NJ NH D L LG LM LB LS LT LP LH \ M B BS S SS Q J C K T P H} $T] ;# trailing consonants if {[min $l $v $t] < 0} {return $hanglish} format %c [expr {$l*21*28 + $v*28 + $t + 0xAC00}] } proc min args {lindex [lsort -real $args] 0} proc hanglish argl { set res "" foreach i $argl { foreach j [split $i -] {append res [hanglish2uc $j]} } append res " " }
排序是指“根据定义的优先级规则对字符或宽字符字符串进行逻辑排序。这些规则在排序元素之间识别排序顺序,以及可用于对由多个排序元素组成的字符串进行排序的其他规则。”
Tcl 的 lsort 按数字 Unicode 值进行排序,这在某些区域设置中可能不正确。例如,在葡萄牙语中,重音字母应该像没有重音一样排序,但在 Unicode 序列中排在“z”之后。
以下简单的代码接收一个映射,其中可以列出排序差异,如 {from to from to ...},对映射后的项目进行排序,然后只检索原始元素
proc collatesort {list map} { set l2 {} foreach e $list { lappend l2 [list $e [string map $map $e]] } set res {} foreach e [lsort -index 1 $l2] {lappend res [lindex $e 0]} set res }
测试,葡萄牙语
% collatesort {ab ãc ãd ae} {ã a} ab ãc ãd ae
西班牙语 (ll 排在 lz 之后)
% collatesort {llano luxación leche} {ll lzz} leche luxación llano
德语 (变音字母排序,就像“ä”是“ae”一样)
% lsort {Bar Bär Bor} Bar Bor Bär % collatesort {Bar Bär Bor} {ä ae} Bär Bar Bor