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

7
我正在寻找一种方法来比较和排序UTF-8字符串,以不区分大小写的方式在C++中使用它,在SQLite自定义排序函数中使用。
  1. 这种方法最好是独立于语言环境的。然而,据我所知,排序非常依赖于语言,因此任何适用于英语以外语言的方法都可以,即使这意味着切换语言环境。
  2. 选项包括使用标准C或C++库或一个小型(适用于嵌入式系统)且非GPL(适用于专有系统)的第三方库。

目前的进展:

  1. strcoll with C locales and std::collate/std::collate_byname are case-sensitive. (Are there case-insensitive versions of these?)
  2. I tried to use a POSIX strcasecmp, but it seems to be not defined for locales other than "POSIX"

    In the POSIX locale, strcasecmp() and strncasecmp() do upper to lower conversions, then a byte comparison. The results are unspecified in other locales.

    And, indeed, the result of strcasecmp does not change between locales on Linux with GLIBC.

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

    This is printed:

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

P. S.

是的,我知道ICU,但由于它的巨大体积,我们无法在嵌入式平台上使用它。

6个回答

7
您真正想要的是逻辑上不可能的。没有一种与语言环境无关、大小写不敏感的字符串排序方法。简单的反例是 "i" <> "I"?朴素的答案是否定的,但在土耳其语中,这些字符串是不相等的。"i" 被大写为 "İ"(U+130 拉丁大写字母 I 上面有点)。
UTF-8 字符串给问题增加了额外的复杂性。它们是完全有效的多字节 char* 字符串,如果您有适当的语言环境。但是,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"?它们甚至长度都不相等。然而,对于德国人来说,您必须将它们视为相等。您能接受这种情况吗?

2
关于您的例子,涉及德语 "ß" 字符(以及所有这类普遍情况):无论是 UTF-8 还是其他编码方式,这些字符必须已经被“解决”或以其他方式处理了数千次。MS Word 一直有一个“切换大小写”的功能——在 Unicode 之前的版本中,它如何处理该字符?WordPerfect 又如何处理?我遇到了与 OP 相同的问题,只不过我使用的是 Delphi。我看到了许多基于 Windows 的 SQLite 应用程序,在英语、德语或(我所使用的)波兰语环境下执行不区分大小写的 SELECT(和我猜测的 ORDER BY)。试试 Firefox :) 它们是如何做到的? - Marek Jedliński
通常不正确 :) 据我所知,波兰语没有硬性格;在波兰语中使用的所有非ASCII字符都是基于ASCII字符的。 - MSalters
除了土耳其语问题外,Unicode大小写折叠算法(http://www.unicode.org/reports/tr44/)表现出色。 - dalle
UTF-8每个码点最多可以有4个字节,一个字形可以由多个(确切的最大值未指定,如果我没记错的话)码点组成。 - MarcusJ

0

我认为没有标准的C/C++库函数可供使用。您将不得不自己编写或使用第三方库。区域设置特定排序的完整Unicode规范可以在此找到: http://www.unicode.org/reports/tr10/警告:这是一份长篇文档)。


0

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


0

我相信你需要编写自己的代码或使用一个第三方库。我建议使用第三方库,因为要得到真正的国际支持需要遵循许多规则 - 最好让专家来处理这些规则。


0

我没有例程的明确答案,但是我应该指出一个UTF-8字节流实际上包含Unicode字符,你必须使用C/C++运行库的wchar_t版本。

不过,你首先必须将这些UTF-8字节转换为wchar_t字符串。这并不太难,因为UTF-8编码标准已经非常好地记录了下来。我知道这一点,因为我做过,但我不能与你分享那段代码。


0
如果您只是在本地使用它进行搜索和排序,我建议您的函数调用一个简单的替换函数,使用类似以下表格的方式将多字节字符串转换为每个字符一个字节的字符串:

A -> a
à -> a
á -> a
ß -> ss
Ç -> c
等等

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接