Многоязычная сортировка текста в Perl, в Windows, с использованием локали

Я создаю программу для сортировки указателей книг на разных языках. Он использует Perl и отключает локаль. Я разрабатываю его для Unix, но он должен быть переносимым на Windows. Должно ли это работать в принципе, или, полагаясь на локаль, я ошибаюсь? Суть в том, что мне действительно нужно, чтобы это работало под Windows, но мне удобнее разрабатывать в своей среде UNIX.


person egilchri    schedule 21.02.2013    source источник
comment
Я не знаю, почему бы вам просто не использовать Unicode::Collate, возможно, с Unicode::Collate::Locale.   -  person tchrist    schedule 22.02.2013
comment
@tchrist, сделай это ответом. Я просто не знал об этом.   -  person ikegami    schedule 22.02.2013
comment
@ikegami О, очень хорошо. Но простое преобразование комментария в ответ было бы слишком коротким, поэтому мне пришлось его немного дополнить. :)   -  person tchrist    schedule 22.02.2013
comment
Спасибо, что задали этот вопрос!! Это помогло мне делать инди-проекты :)   -  person SibiCoder    schedule 16.04.2016


Ответы (2)


Предполагая, что вашей отправной точкой является Unicode, поскольку вы очень тщательно декодировали все входящие данные, независимо от того, какая может быть их исходная кодировка, тогда легко использовать модуль Unicode::Collate в качестве отправной точки.

Если вы хотите настроить локаль, то вы, вероятно, захотите начать с Unicode::Collate::Locale вместо этого.

Декодирование в Юникод

Если вы работаете в среде, полностью поддерживающей UTF8, это несложно, но если вы подвержены превратностям случайных так называемых «локалей» (или, что еще хуже, уродливых вещей, которые Microsoft называет «кодовыми страницами»), вам может понадобиться чтобы получить модуль CPAN Encode::Locale, чтобы помочь вам. Например:

 use Encode;
 use Encode::Locale;

 # use "locale" as an arg to encode/decode
 @ARGV = map { decode(locale =>  $_) } @ARGV;

 # or as a stream for binmode or open
 binmode $some_fh, ":encoding(locale)";

 binmode STDIN,  ":encoding(console_in)"  if -t STDIN;
 binmode STDOUT, ":encoding(console_out)"  if -t STDOUT;
 binmode STDERR, ":encoding(console_out)"  if -t STDERR;

(Если бы это был я, я бы просто использовал ":utf8" для вывода.)


Стандартная сортировка, а также локали и адаптация

Дело в том, что как только вы все декодируете во внутренний формат Perl, вы можете использовать на нем Unicode::Collate и Unicode::Collate::Locale. Это может быть очень просто:

   use v5.14;
   use utf8;
   use Unicode::Collate;
   my @exes = qw( x⁷ x⁰ x⁸ x³ x⁶ x⁵ x⁴ x² x⁹ x¹ );
   @exes = Unicode::Collate->new->sort(@exes);
   say "@exes";

   # prints: x⁰ x¹ x² x³ x⁴ x⁵ x⁶ x⁷ x⁸ x⁹

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

my $collator = Unicode::Collate->new(
    --upper_before_lower => 1,
    --preprocess => {
        local $_ = shift;
        s/^ (?: The | An? ) \h+ //x;  # strip articles
        s/ ( \d+ ) / sprintf "%020d", $1 /xeg;
        return $_;
    };
);

Теперь просто используйте метод sort этого объекта для сортировки.

Иногда нужно вывернуть сортировку наизнанку. Например:

 my $collator = Unicode::Collate->new();
 for my $rec (@recs) {
     $rec->{NAME_key} = 
        $collator->getSortKey( $rec->{NAME} );
 }
 @srecs = sort {
     $b->{AGE}       <=>  $a->{AGE}
                     ||
     $a->{NAME_key}  cmp  $b->{NAME_key}
 } @recs;

Причина, по которой вы должны это сделать, заключается в том, что вы сортируете запись с различными полями. Ключ двоичной сортировки позволяет вам использовать оператор cmp для данных, которые прошли через выбранный вами/пользовательский объект подборки.

Полный конструктор для объекта подборки имеет все это для формального синтаксиса:

      $Collator = Unicode::Collate->new(
         UCA_Version => $UCA_Version,
         alternate => $alternate, # alias for 'variable'
         backwards => $levelNumber, # or \@levelNumbers
         entry => $element,
         hangul_terminator => $term_primary_weight,
         highestFFFF => $bool,
         identical => $bool,
         ignoreName => qr/$ignoreName/,
         ignoreChar => qr/$ignoreChar/,
         ignore_level2 => $bool,
         katakana_before_hiragana => $bool,
         level => $collationLevel,
         minimalFFFE => $bool,
         normalization  => $normalization_form,
         overrideCJK => \&overrideCJK,
         overrideHangul => \&overrideHangul,
         preprocess => \&preprocess,
         rearrange => \@charList,
         rewrite => \&rewrite,
         suppress => \@charList,
         table => $filename,
         undefName => qr/$undefName/,
         undefChar => qr/$undefChar/,
         upper_before_lower => $bool,
         variable => $variable,
      );

Но обычно вам не нужно беспокоиться ни о каком из них. На самом деле, если вы хотите настроить локаль для конкретной страны с использованием данных CLDR, вы должны просто использовать Unicode::Collate::Locale, который добавляет в конструктор еще ровно один параметр: locale => $country_code.

 use Unicode::Collate::Locale;
 $coll = Unicode::Collate::Locale->
           new(locale => "fr");
 @french_text = $coll->sort(@french_text);

Видите, как это легко?

Но вы можете делать и другие интересные вещи.

 use Unicode::Collate::Locale;
 my $Collator = new Unicode::Collate::Locale::
                 locale => "de__phonebook",
                 level  => 1,
                 normalization => undef,
                ;

 my $full = "Ich müß Perl studieren.";
 my $sub = "MUESS";
 if (my ($pos,$len) = $Collator->index($full, $sub)) {
     my $match = substr($full, $pos, $len);
     say "Found match of literal ‹$sub› in ‹$full› as ‹$match›";

 }

При запуске пишет:

 Found match of literal ‹MUESS› in ‹Ich müß Perl studieren.› as ‹müß›

Вот доступные локали для v0.96 модуля Unicode::Collate::Locale, взятые с его справочной страницы:

 locale name       description
--------------------------------------------------------------
 af                Afrikaans
 ar                Arabic
 as                Assamese
 az                Azerbaijani (Azeri)
 be                Belarusian
 bg                Bulgarian
 bn                Bengali
 bs                Bosnian
 bs_Cyrl           Bosnian in Cyrillic (tailored as Serbian)
 ca                Catalan
 cs                Czech
 cy                Welsh
 da                Danish
 de__phonebook     German (umlaut as 'ae', 'oe', 'ue')
 ee                Ewe
 eo                Esperanto
 es                Spanish
 es__traditional   Spanish ('ch' and 'll' as a grapheme)
 et                Estonian
 fa                Persian
 fi                Finnish (v and w are primary equal)
 fi__phonebook     Finnish (v and w as separate characters)
 fil               Filipino
 fo                Faroese
 fr                French
 gu                Gujarati
 ha                Hausa
 haw               Hawaiian
 hi                Hindi
 hr                Croatian
 hu                Hungarian
 hy                Armenian
 ig                Igbo
 is                Icelandic
 ja                Japanese [1]
 kk                Kazakh
 kl                Kalaallisut
 kn                Kannada
 ko                Korean [2]
 kok               Konkani
 ln                Lingala
 lt                Lithuanian
 lv                Latvian
 mk                Macedonian
 ml                Malayalam
 mr                Marathi
 mt                Maltese
 nb                Norwegian Bokmal
 nn                Norwegian Nynorsk
 nso               Northern Sotho
 om                Oromo
 or                Oriya
 pa                Punjabi
 pl                Polish
 ro                Romanian
 ru                Russian
 sa                Sanskrit
 se                Northern Sami
 si                Sinhala
 si__dictionary    Sinhala (U+0DA5 = U+0DA2,0DCA,0DA4)
 sk                Slovak
 sl                Slovenian
 sq                Albanian
 sr                Serbian
 sr_Latn           Serbian in Latin (tailored as Croatian)
 sv                Swedish (v and w are primary equal)
 sv__reformed      Swedish (v and w as separate characters)
 ta                Tamil
 te                Telugu
 th                Thai
 tn                Tswana
 to                Tonga
 tr                Turkish
 uk                Ukrainian
 ur                Urdu
 vi                Vietnamese
 wae               Walser
 wo                Wolof
 yo                Yoruba
 zh                Chinese
 zh__big5han       Chinese (ideographs: big5 order)
 zh__gb2312han     Chinese (ideographs: GB-2312 order)
 zh__pinyin        Chinese (ideographs: pinyin order) [3]
 zh__stroke        Chinese (ideographs: stroke order) [3]
 zh__zhuyin        Chinese (ideographs: zhuyin order) [3]

   Locales according to the default UCA rules include chr (Cherokee), de (German), en (English), ga (Irish), id (Indonesian),
   it (Italian), ka (Georgian), ms (Malay), nl (Dutch), pt (Portuguese), st (Southern Sotho), sw (Swahili), xh (Xhosa), zu
   (Zulu).

   Note

   [1] ja: Ideographs are sorted in JIS X 0208 order.  Fullwidth and halfwidth forms are identical to their regular form.  The
   difference between hiragana and katakana is at the 4th level, the comparison also requires "(variable => 'Non-ignorable')",
   and then "katakana_before_hiragana" has no effect.

   [2] ko: Plenty of ideographs are sorted by their reading. Such an ideograph is primary (level 1) equal to, and secondary
   (level 2) greater than, the corresponding hangul syllable.

   [3] zh__pinyin, zh__stroke and zh__zhuyin: implemented alt='short', where a smaller number of ideographs are tailored.

   Note: 'pinyin' is in latin, 'zhuyin' is in bopomofo.

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


Примечание: все эти примеры, за исключением ссылки на справочную страницу, любезно взяты из 4го издания Programming Perl с любезного разрешения его автора. :)

person tchrist    schedule 22.02.2013
comment
Вы, ребята, и Stack Overflow, потрясающие. Я думаю, что мои входные данные ведут себя хорошо. Это html с атрибутом lang, установленным для языка, и кодировкой UTF-8 или US-ASCII. К тому времени, когда я закончу, я буду иметь дело с очень широким набором языков. Но меня воодушевил ответ @tchrist, что я могу продолжить разработку в своей среде UNIX и заставить все работать в Windows. - person egilchri; 22.02.2013

Win32::OLE::NLS предоставляет вам доступ к этой части системы. Он предоставляет вам CompareString и необходимые инструменты для получения необходимого идентификатора локали.

Если вы хотите/нужно найти системную документацию, базовый системный вызов называется CompareStringEx.

person ikegami    schedule 22.02.2013
comment
Спасибо. Так что похоже, что это решение для Windows, и это нормально, если это то, что нужно. - person egilchri; 22.02.2013
comment
Нет встроенной поддержки. Perl во многих отношениях ориентирован на unix. Он поддерживает только локали Unix. - person ikegami; 22.02.2013
comment
@egilchri Я не совсем понимаю, что он имеет в виду под «поддержкой только локалей Unix». Я думаю, это может зависеть от того, что подразумевается под «локалями». Лучше всего не зависеть от локали, если под локалью вы просто подразумеваете кодировку. Я бы предпочел, чтобы все это было в Unicode, и тогда я могу, если захочу, выбрать, какой cmp я получу таким образом, чтобы он не действовал по-разному в разных системах. Смотрите мой ответ, как это сделать. - person tchrist; 22.02.2013
comment
@tchrist, Любой аспект, но здесь мы говорим о сопоставлении. Нет встроенной поддержки за пределами Unix, поэтому вам нужно смотреть на модули. Я не говорю, что это проблема. - person ikegami; 22.02.2013
comment
@ikegami Perl поддерживает локали CLDR через Unicode::Collate::Locale, которые почти бесконечно лучше, чем локали LC_blah. - person tchrist; 22.02.2013
comment
@tchrist, я никогда не говорил иначе. Это модуль, нет? Это не встроено, верно? - person ikegami; 22.02.2013
comment
Да, я знаю. Вы добавили бит модуля после того, как я начал. Или мне так показалось. - person tchrist; 22.02.2013