SQLite 的不区分大小写的 UTF-8 字符串排序规则 (C/C++)

Case-insensitive UTF-8 string collation for SQLite (C/C++)

本文关键字:C++ 规则 字符串 不区 大小写 UTF-8 SQLite 排序      更新时间:2023-10-16

我正在寻找一种方法,以不区分大小写的方式比较和排序C++中的 UTF-8 字符串,以便在 SQLite 中的自定义排序规则函数中使用它。

  1. 理想情况下,该方法应与区域设置无关。但是,我不会屏住呼吸,据我所知,排序规则非常依赖于语言,因此任何适用于英语以外的语言都可以,即使这意味着切换区域设置。
  2. 选项包括使用标准的C或C++库或小型(适用于嵌入式系统)和非GPL(适用于专有系统)第三方库。

到目前为止,我拥有的:

  1. 具有 C 语言环境和 std::collate/std::collate_bynamestrcoll区分大小写。(这些版本是否不区分大小写?
  2. 我尝试使用 POSIX strcasecmp,但它似乎没有为"POSIX"以外的语言环境定义

    在 POSIX 语言环境中,strcasecmp()

    和 strncasecmp() 进行从上到下的转换,然后进行字节比较。结果在其他区域设置中未指定。

    事实上,strcasecmp的结果在带有GLIBC的Linux上的语言环境之间不会改变。

    #include <clocale>
    #include <cstdio>
    #include <cassert>
    #include <cstring>
    const static char *s1 = "Äaa";
    const static char *s2 = "äaa";
    int main() {
        printf("strcasecmp('%s', '%s') == %dn", s1, s2, strcasecmp(s1, s2));
        printf("strcoll('%s', '%s') == %dn", s1, s2, strcoll(s1, s2));
        assert(setlocale(LC_ALL, "en_AU.UTF-8"));
        printf("strcasecmp('%s', '%s') == %dn", s1, s2, strcasecmp(s1, s2));
        printf("strcoll('%s', '%s') == %dn", s1, s2, strcoll(s1, s2));
        assert(setlocale(LC_ALL, "fi_FI.UTF-8"));
        printf("strcasecmp('%s', '%s') == %dn", s1, s2, strcasecmp(s1, s2));
        printf("strcoll('%s', '%s') == %dn", s1, s2, strcoll(s1, s2));
    }
    

    这是打印的:

    strcasecmp('Äaa', 'äaa') == -32
    strcoll('Äaa', 'äaa') == -32
    strcasecmp('Äaa', 'äaa') == -32
    strcoll('Äaa', 'äaa') == 7
    strcasecmp('Äaa', 'äaa') == -32
    strcoll('Äaa', 'äaa') == 7
    

附言

是的,我知道ICU,但由于其巨大的规模,我们不能在嵌入式平台上使用它。

你真正想要的在逻辑上是不可能的。没有独立于区域设置、不区分大小写的字符串排序方法。简单的反例是"i"<>"I"?天真的答案是否定的,但在土耳其语中,这些字符串是不相等的。"i"大写为"İ"(U+130 拉丁语大写字母 I,上面有点)

UTF-8 字符串增加了问题的复杂性。如果您有合适的区域设置,它们是完全有效的多字节字符* 字符串。但是 C 和 C++ 标准都没有定义这样的语言环境;请与您的供应商联系(嵌入式供应商太多,抱歉,这里没有通用答案)。因此,您必须选择一个多字节编码为 UTF-8 的区域设置,以便 mbscmp 函数正常工作。这当然会影响排序顺序,排序顺序取决于区域设置。如果您没有 const char* 为 UTF-8 的区域设置,则根本无法使用此技巧。(据我了解,Microsoft的CRT受此影响。他们的多字节代码最多只能处理 2 个字节的字符;UTF-8 需要 3)

wchar_t也不是标准解决方案。据说它是如此之宽,以至于您不必处理多字节编码,但您的排序规则仍然取决于区域设置 (LC_COLLATE)。但是,使用 wchar_t 意味着您现在选择不使用 UTF-8 作为 const char* 的区域设置。

完成此操作后,您基本上可以通过将字符串转换为小写并进行比较来编写自己的排序。这并不完美。你期望 L"ß" == L"ss" 吗?它们甚至不是相同的长度。然而,对于一个德国人来说,你必须认为他们是平等的。你能忍受吗?

我认为没有可以使用标准的C/C++库函数。 您必须自己滚动或使用第 3 方库。 特定于区域设置的排序规则的完整 Unicode 规范可以在这里找到:http://www.unicode.org/reports/tr10/(警告:这是一个很长的文档)。

在Windows上,您可以调用操作系统函数CompareStringW并使用NORM_IGNORECASE标志。您必须先将 UTF-8 字符串转换为 UTF-16。否则,请查看 IBM 的 Unicode 国际组件。

我相信你需要自己动手或使用第三方库。 我推荐第三方图书馆,因为要获得真正的国际支持,需要遵循很多规则 - 最好让专家来处理它们。

我没有示例代码形式的明确答案,但我应该指出,UTF-8 字节流实际上包含 Unicode 字符,您必须使用 C/C++ 运行时库的wchar_t版本。

不过,您必须先将这些 UTF-8 字节转换为wchar_t字符串。这不是很难,因为 UTF-8 编码标准有很好的文档记录。我知道这一点,因为我已经做到了,但我不能与你分享这段代码。

如果您仅使用它来搜索和排序您的语言环境,我建议您的函数调用一个简单的替换函数,该函数使用类似:
A -> a
à -> a
á -> a
ß -> ss
Ç -> c
等等

然后只需调用 strcmp 并返回结果。