Как сравнить строки юникода, содержащие неанглийские символы, для сортировки по алфавиту? - PullRequest
18 голосов
/ 13 августа 2011

Я пытаюсь отсортировать массив / списки / что угодно из данных на основе строковых значений Юникода, которые содержат неанглийские символы, я хочу, чтобы они сортировались правильно по алфавиту.

Я написал много кода (D2010, win XP), который, как мне показалось, был довольно солидным для будущей интернационализации, но это не так.Все это с использованием типа данных unicodestring (string), который до сих пор я только что помещал английские символы в строки unicode.

Кажется, я должен признать очень серьезную ошибку Unicode.Я поговорил с моим немецким другом и попробовал некоторые немецкие ß (ß - это 'ss' и должно следовать после S и перед T в алфавите), а также ö и т.д. (обратите внимание на умляут), и ни один из моих алгоритмов сортировки больше не работает.Результаты очень запутаны.Мусор.

С тех пор я много читал и узнал много неприятных вещей, касающихся сопоставления юникода.Все выглядит мрачно, гораздо мрачнее, чем я когда-либо ожидал, я серьезно все испортил.Я надеюсь, что я что-то упускаю, и вещи на самом деле не так мрачны, как кажутся в настоящее время.Я возился с просмотром вызовов Windows API (RtlCompareUnicodeString) безуспешно (ошибки защиты), я не мог заставить его работать.Проблема с вызовами API, которую я узнал, заключается в том, что они меняются на различных новых платформах Windows, а также в связи с тем, что Delphi скоро станет кроссплатформенным, с Linux позже, мое приложение - клиент-сервер, поэтому я должен быть обеспокоен этой ситуацией, но сейчасэто (плохо), я был бы благодарен за любой прогресс вперед, то есть, специфичный для win api.

Является ли использование функции win api RtlCompareUnicodeString очевидным решением?Если это так, я действительно должен повторить попытку с этим, но я был озадачен всеми проблемами, связанными с сопоставлением юникода, и я не совсем понял, что мне следует делать, чтобы сравнивать эти строки таким образом.

Я узнал о проекте с открытым исходным кодом IBM ICU c ++, для него есть обертка delphi, хотя и для более старой версии ICU.Кажется, это очень всеобъемлющее решение, которое не зависит от платформы.Конечно, я не могу смотреть на создание оболочки Delphi для этого (или обновлять существующую), чтобы получить хорошее решение для сопоставления Unicode?

Я был бы очень рад услышать советы на двух уровнях: -

A) Специально для переносимого решения для Windows, я был бы рад, что на данный момент, забудьте о последствиях клиент-сервер!Б) Более портативное решение, которое защищено от различных вариаций функций API Unicode XP / Vista / Win7, поэтому я могу найти хорошую поддержку для поддержки Mac XE2 и будущей поддержки Linux, не говоря уже о сложностях клиент-сервер.

Между прочим, я действительно не хочу делать «готовые» решения, сканировать строки перед сравнением и заменять некоторые хитрые символы и т. Д., О которых я читал.Я привел немецкий пример выше, это всего лишь пример, я хочу, чтобы он работал на всех (или, по крайней мере, на большинстве, дальневосточном, русском) языках, я не хочу делать обходные пути для конкретного языка или двух.Мне также не нужны никакие советы по алгоритмам сортировки, они в порядке, просто бит сравнения строк неправильный.

Надеюсь, я упускаю / делаю что-то глупое, все это выглядит как головная боль.

Спасибо.


РЕДАКТИРОВАТЬ, Руди, вот как я пытался вызвать RtlCompareUnicodeString.Извините за задержку, с которой у меня было ужасное время.

program Project26

{$APPTYPE CONSOLE}

uses
  SysUtils;


var
  a,b:ansistring;

  k,l:string;
  x,y:widestring;
  r:integer;

procedure RtlInitUnicodeString(
  DestinationString:pstring;
  SourceString:pwidechar) stdcall; external 'NTDLL';

function RtlCompareUnicodeString(
  String1:pstring;
  String2:pstring;
  CaseInSensitive:boolean
  ):integer stdcall; external 'NTDLL';


begin

  x:='wef';
  y:='fsd';

  RtlInitUnicodeString(@k, pwidechar(x));
  RtlInitUnicodeString(@l, pwidechar(y));

  r:=RtlCompareUnicodeString(@k,@l,false);

  writeln(r);
  readln;

end.

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

О вашей функции API StringCompareEx.Это выглядело действительно хорошо, но доступно только на Vista +, я использую XP.StringCompare работает на XP, но это не Unicode!

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

Кто-нибудь может сказать наверняка, должен ли ansicomparetext сделать это или нет?Это не работает для меня, но другие сказали, что это должно, и другие вещи, которые я прочитал, предполагают, что это должно.

Это то, что я получаю с 31 тестовой строкой, когда использую AnsiCompareText в немецком языке (с разделителями пробелами - ни одна строка не содержит пробелов): -

  • arß Asß asß aßs no nöööönoo öö oöo öoö pö ss SS ßaß ßbß sß Sßa Sßb ßß ssss SSSS ßßß sssßß SSßß ßz ßzß z zzz

EDIT 2.

до сих пор слышуесли бы я ожидал, что AnsiCompareText будет работать с использованием информации о локали, как сказал lkessler, и lkessler также уже публиковал информацию по этим вопросам раньше, и, похоже, уже проходил через это раньше.

Однако, следуя совету Руди, я имеютакже проверял CompareStringW - который разделяет ту же документацию с CompareString , так что это НЕ не-Unicode, как я уже говорил ранее.

Даже если AnsiCompareText не будет работать, хотя ядумаю, что это должно, функция Win32api CompareStringW действительно должна работать.Теперь я определил свою функцию API, и я могу ее вызвать, и я получаю результат, и никаких ошибок ... но я получаю один и тот же результат каждый раз, независимо от входных строк!Он возвращает 1 каждый раз - что означает меньше, чем.Вот мой код

var
  k,l:string;

function CompareStringW(
  Locale:integer;
  dwCmpFlags:longword;
  lpString1:pstring;
  cchCount1:integer;
  lpString2:pstring;
  cchCount2:integer
  ):integer stdcall; external 'Kernel32.dll';

begin;

  k:='zzz';
  l:='xxx';

  writeln(length(k));
  r:=comparestringw(LOCALE_USER_DEFAULT,0,@k,3,@l,3);

  writeln(r); // result is 1=less than, 2=equal, 3=greater than
  readln;

end;

Я чувствую, что сейчас куда-то добираюсь после сильной боли.Был бы рад узнать об AnsiCompareText и о том, что я делаю не так с приведенным выше вызовом API CompareStringW.Спасибо.


EDIT 3

Во-первых, я сам исправил вызов API для CompareStringW, я передавал @mystring, когда должен был выполнить PString (mystring).Теперь все работает правильно.

r:=comparestringw(LOCALE_USER_DEFAULT,0,pstring(k),-1,pstring(l),-1);

Теперь вы можете себе представить мое смятение, когда я все еще получил тот же результат сортировки, что и в самом начале ...

  • arßasß aßs Asß no nö ön oo öö oöo öoö öp pö ss SS ßaßßbß sß Sßa Sßb ßß ssss SSSS ßßß sssßs SSßß ßzß z zzz
*1065* Вы также можете упомянуть, что вы не можете упоминать менярадость, когда я понял, что порядок сортировки является ПРАВИЛЬНЫМ, И ЭТО БЫЛО ПРАВИЛЬНО ВЕРНУТЬСЯ В НАЧАЛЕ!Мне больно говорить это, но с самого начала не было никаких проблем - все это связано с моим отсутствием знания немецкого языка.Я поверил, что сортировка была неправильной, так как вы можете видеть выше строки, начинающиеся с S, затем позже они начинаются с ß, затем снова s и возвращаются к ß и так далее.Ну, я не могу говорить по-немецки, но я все еще мог ясно видеть, что они не были отсортированы правильно - мой немецкий друг сказал мне, что идет после S и до T ... Я НЕПРАВИЛЬНО!Что происходит, так это то, что строковые функции (и AnsiCompareText, и winapi CompareTextW) заменяют каждое 'ß' на 'ss', а каждое 'ö' - на нормальное 'o' ... так что если я возьму результаты выше и для поискаи заменить, как описано, я получаю ...
  • arss asss asss Asss нет нет на oo oo ooo ooo op po ss SS ssass ssbss sss Sssa Sssb ssss ssss SSSS ssssss ssssss SSssss ssz sszss z zzz

Выглядит довольно правильно для меня!И так было всегда.

Я чрезвычайно благодарен за все приведенные советы и очень сожалею, что потратил впустую ваше время, как это.Эти немецкие ß запутали меня, никогда не было ничего плохого со встроенной функцией delphi или чем-то еще.Это просто выглядело как было.Я допустил ошибку, объединив их с обычными 's' в моих тестовых данных, любое другое письмо не создало бы эту иллюзию несортировки!Из-за волнистой особы я выглядела глупо!ßs!

Руди и lkessler, мы оба особенно полезны, оба, я должен принять ответ lkessler как самый правильный, извините, Руди.

Ответы [ 4 ]

9 голосов
/ 13 августа 2011

Вы сказали, что у вас проблемы с вызовами Windows API. Не могли бы вы опубликовать код, чтобы люди могли понять, почему он не сработал? Это не так сложно, как может показаться, но требует некоторой осторожности. ISTM, что RtlCompareUnicodeStrings() слишком низкий уровень.

Я нашел несколько решений:

Non-портативный

Вы можете использовать функцию Windows API CompareStringEx . Это будет сравнивать с использованием конкретных типов сортировки Unicode. Вы можете указать, как вы хотите это сделать (см. Ссылку). Для этого нужны широкие строки, то есть указатели PWideChar на них. Если у вас возникли проблемы с его вызовом, напишите, и я постараюсь добавить демонстрационный код.

Более или менее портативный

Чтобы сделать это более или менее переносимым, вы можете написать функцию, которая сравнивает две строки и использует условные определения для выбора различных API сравнения для платформы.

7 голосов
/ 13 августа 2011

Попробуйте использовать CompareStr для чувствительного к регистру или CompareText для нечувствительного к регистру, если вы хотите, чтобы ваши сортировки были одинаковыми в любой локали.

И используйте AnsiCompareStr для чувствительного к регистру или AnsiCompareText для нечувствительного к регистру, если вы хотите, чтобы ваши сортировки были специфичны для локали пользователя.

См .: Как я могу заставить TStringList сортировать по-разному в Delphi , чтобы получить гораздо больше информации об этом.

2 голосов
/ 13 августа 2011

В Юникоде числовой порядок символов определенно не является последовательностью сортировки. AnsiCompareText, как упомянуто HeartWare, учитывает специфику локали при сравнении символов, но, как вы выяснили, ничего не делает с порядком сортировки. То, что вы ищете, называется последовательностью сортировки языка, которая определяет алфавитный порядок сортировки для языка с учетом диакритики и т. Д. Они были как бы подразумевались в старых кодовых страницах Ansi, хотя они также не учитывали разницу между языками, использующими один и тот же набор символов.

Я проверил документы D2010. Кроме некоторых компонентов TIB * я не нашел никаких ссылок. В C ++ builder, похоже, есть функция сравнения, которая учитывает параметры сортировки, но в Delphi это не очень полезно. Там вам, вероятно, придется напрямую использовать некоторые функции API Windows.

Docs:

Статья "Сортировка" Разобрать все "принадлежит Майклу Каплану, человеку, обладающему глубокими глубокими знаниями обо всех вещах Unicode и всех тонкостях различных языков. Его блог был неоценим для меня при портировании с D2006 на D2009.

1 голос
/ 13 августа 2011

Вы пробовали AnsiCompareText?Несмотря на то, что она называется «Ansi», я считаю, что она требует специфической для ОС Unicode процедуры сравнения ...

Она также должна обезопасить вас от кроссплатформенных зависимостей (при условии, что Embarcadero поставляетсовместимая версия в различных ОС, на которые они нацелены).

Я не знаю, насколько хорошо работает сравнение с различными странными способами кодирования строк в Юникоде, но попробуйте и дайте нам знать результат ...

...