65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Windows 中使用 UTF-8,第二部分 - 区分大小写还是不区分大小写

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2020年2月18日

MIT

4分钟阅读

viewsIcon

14641

downloadIcon

206

UTF-8 代码的大小写折叠。

Cyrillic letters in cursive - source: Wikipedia

引言

在那个每个人都讲英语、ANSI 是唯一选择的美好时光 [1],将大写字母转换为小写字母只需翻转一个位。只需将任何 ASCII 字母与 0x20 进行 OR 运算,就可以从“A”(0x41)变为“a”(0x61)。生活更简单,大家都很快乐。

当你开始处理多语言字符集时,大小写之间的转换会更加复杂。举个例子,我的名字应该写成“Neacșu”,全大写就是“NEACȘU”。Unicode 将“ș”(带软音符的小写拉丁字母 s)的代码称为 0x15E,而“Ș”(带软音符的大写拉丁字母 S)的代码为 0x15F。碰巧这只是一个位的区别,但也是一个位。

本文继续了《在 Windows 中使用 UTF-8 系列文章》,并展示了 `tolower` 和 `toupper` 函数的多语言实现。最新的代码版本始终可以在我的 GitHub 站点下载。

背景

要了解世界上所有语言的字母哪些是大写,哪些是小写,这对任何程序员来说都是一项艰巨的任务。幸运的是,Unicode 联盟(负责管理 Unicode 的机构)从创建表情符号的间隙中,创建了一个长长的字符大小写折叠代码列表。你可以从 此链接 下载列表。该文档描述了四种大小写折叠类型:通用、完整、简单和土耳其语。我的函数仅实现了标准和简单情况。

实现

实现的 [基本思想] 非常简单。将 Unicode 文档转换为两个大小相等的表,一个包含大写字母,另一个包含小写字母。大写字母表被排序,以便进行二分查找。如果在“大写字母表”中找到一个代码,则将其替换为“小写字母表”中对应的代码。

以下是 `tolower` 函数的代码

//definition of 'u2l' and 'lc' tables
#include "uppertab.c"

std::string tolower (const std::string& str)
{
  u32string wstr = runes (str);
  for (auto ptr = wstr.begin (); ptr < wstr.end (); ptr++)
  {
    char32_t *f = lower_bound (begin (u2l), end (u2l), *ptr);
    if (f != end (u2l) && *f == *ptr)
      *ptr = lc[f - u2l];
  }
  return narrow (wstr);
}    

uppertab.c”是通过一个简短程序生成的,该程序读取 Unicode 大小写折叠文件并生成两个表 `u2l` 和 `lc`。以下是其中简短的示例

//Upper case table
static char32_t u2l [1411] = { 
  0x00041, //  LATIN CAPITAL LETTER A
  0x00042, //  LATIN CAPITAL LETTER B
  0x00043, //  LATIN CAPITAL LETTER C
  0x00044, //  LATIN CAPITAL LETTER D
....
  0x1e91f, //  ADLAM CAPITAL LETTER ZAL
  0x1e920, //  ADLAM CAPITAL LETTER KPO
  0x1e921};//  ADLAM CAPITAL LETTER SHA

//Lower case equivalents
static char32_t lc [1411] = { 
  0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 
  0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 
... 
  0x1e939, 0x1e93a, 0x1e93b, 0x1e93c, 0x1e93d, 0x1e93e, 0x1e93f, 0x1e940, 
  0x1e941, 0x1e942, 0x1e943};      

输入字符串通过调用 `utf8::runes` 函数转换为 UTF-32。然后,每个字符都在 `u2l` 表中进行搜索,如果找到,则将其替换为 `lc` 表中对应的字符。搜索使用执行二分查找的 `lower_bound` 函数。结果字符串被转换回 UTF-8,就是这样。

`toupper` 函数非常相似,除了它使用“lowertab.c”文件中定义的 `l2c` 和 `uc` 表。

为求完整性,这两个函数还有一个原地修改的版本

void tolower (std::string& str);
void toupper (std::string& str);    

另一个函数是 `utf8::icompare`,它执行不区分大小写的字符串比较

int icompare (const std::string& s1, const std::string& s2);

它的行为与 `std::string::compare` 类似,如果 `s1` 转换为小写后排在 `s2` 转换后的小写字母之前,则返回一个负值;如果 `s1` 在 `s2` 之后,则返回一个正值。如果两个字符串的小写版本相等,则函数返回 0。

表生成程序“gen_casetab.cpp”也很直接。它读取并解析大小写折叠文本文件,首先生成“uppertab.c”文件,然后是“lowertab.c”文件。在此过程中,它需要重新排序按大写字母代码排序的表,使其按小写字母代码排序。

Using the Code

所有函数都在 `utf8` 命名空间中。由于这些函数以及该命名空间中的许多其他函数与标准 C/C++ 函数同名,我建议**不要使用“using”指令**。下面是一个展示如何调用这些函数的简短示例

#include <utf8.h>
...
string all_caps = utf8::toupper (u8"Neacșu"); // all_caps should be "NEACȘU"
string greek {u8"αλφάβητο"};
utf8::toupper (greek);                        //string should be "ΑΛΦΆΒΗΤΟ"    

关注点

一个有趣的点是,存在多个大写代码被折叠成相同的小写代码。在我的实现中,第二个代码被从表中删除。这似乎可以正常工作,但整个想法有些令人担忧:这意味着没有唯一的方法可以将小写字符串转换为对应的大写字符串。我个人认为 Unicode 联盟的表格中存在一些奇怪的选择。例如,大写拉丁字母 K 和摄氏度符号 (K) 都被折叠成小写字母 (k) [2]

另请注意,这些函数并不是非常轻量级的:每对转换表都超过 10k,但我猜如果你需要多语言大小写感知,这就是你必须付出的代价。

历史

  • 2020 年 2 月 17 日 - 初始版本

脚注

[1] 从来没有那么好的时光:碰巧在这个领域工作的许多技术人员都说英语,ANSI 代码被称为 ASCII,除了一个大公司坚持使用 EBCDIC 外,大家都使用它。这太浪费了:EBCDIC 使用 8 位来完成 ASCII 用 7 位就能完成的任务!

[2] 我认为摄氏度符号没有小写形式,我的科学老师可能会同意我的看法。

© . All rights reserved.