Нечувствительный к регистру строковый порядок сортировки UTF-8 для SQLite (C / C ++) - PullRequest
7 голосов
/ 08 октября 2008

Я ищу метод для сравнения и сортировки строк UTF-8 в C ++ без учета регистра, чтобы использовать его в пользовательской функции сортировки в SQLite .

  1. Метод должен в идеале быть независимым от локали. Однако я не буду затаить дыхание, насколько я знаю, сортировка очень зависит от языка, поэтому все, что работает на других языках, кроме английского, подойдет, даже если это означает переключение языков.
  2. Опции включают использование стандартной библиотеки C или C ++ или small (подходит для встроенной системы) и non-GPL (подходит для проприетарной системы) сторонней библиотеки.

Что у меня так далеко:

  1. strcoll с локалями C и std::collate / std::collate_byname чувствительны к регистру. (Существуют ли эти версии без учета регистра?)
  2. Я пытался использовать POSIX strcasecmp, но, похоже, не определено для локалей, отличных от "POSIX"

    В локали POSIX strcasecmp () и strncasecmp () выполняют преобразование сверху вниз, затем сравнение байтов. Результаты не указаны в других регионах.

    И действительно, результат strcasecmp не меняется между локалями в Linux с 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));
    }
    

    Это напечатано:

    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. С.

И да, я знаю о ICU , но мы не можем использовать его на встроенной платформе из-за его огромного размера .

Ответы [ 6 ]

7 голосов
/ 10 октября 2008

То, что вы действительно хотите, логически невозможно. Не существует независимого от локали, нечувствительного к регистру способа сортировки строк. Простой контрпример - «я» <> «я»? Наивный ответ - нет, но по-турецки эти строки неравны. «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"? Они даже не одинаковой длины. Тем не менее, для немца вы должны считать их равными. Вы можете жить с этим?

0 голосов
/ 16 февраля 2009

Если вы используете его для поиска и сортировки только для вашей локали, я предлагаю вашей функции вызывать простую функцию замены, которая преобразует обе многобайтовые строки в один байт на символьные, используя таблицу типа:

A ->
М ->
á ->
ß -> ss
Ç -> с
и так далее

Затем просто вызовите strcmp и верните результаты.

0 голосов
/ 10 октября 2008

У меня нет однозначного ответа в виде примера кода, но я должен отметить, что поток сообщений UTF-8 фактически содержит символы Unicode, и вам необходимо использовать версии wchar_t библиотеки времени выполнения C / C ++.

Вы должны сначала преобразовать эти байты UTF-8 в строки wchar_t. Это не очень сложно, поскольку стандарт кодирования UTF-8 очень хорошо задокументирован . Я знаю это, потому что я сделал это, но я не могу поделиться этим кодом с вами.

0 голосов
/ 09 октября 2008

Полагаю, вам нужно будет свернуть свою собственную или использовать стороннюю библиотеку. Я рекомендую стороннюю библиотеку, потому что существует множество правил, которым необходимо следовать, чтобы получить настоящую международную поддержку - лучше всего, чтобы кто-то, кто является экспертом, имел с ними дело.

0 голосов
/ 09 октября 2008

В Windows вы можете вызвать возврат к функции ОС CompareStringW и использовать флаг NORM_IGNORECASE. Сначала вам придется конвертировать строки UTF-8 в UTF-16. В противном случае взгляните на IBM Международные компоненты для Unicode .

0 голосов
/ 08 октября 2008

Я не думаю, что есть стандартная библиотечная функция C / C ++, которую вы можете использовать. Вам придется свернуть свой собственный или использовать стороннюю библиотеку. Полная спецификация Unicode для сопоставления, зависящего от локали, может быть найдена здесь: http://www.unicode.org/reports/tr10/ ( предупреждение : это документ long ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...