Почему символы слова (\w) не совпадают прямо под прагмой use locale?

Когда я use locale, некоторые символы из моей локали (et_EE.UTF-8) не совпадают с \w и я не вижу в этом никакой причины.

Помимо ASCII, в эстонском языке используются еще шесть символов:

õäöüšž

В моем тестовом сценарии ниже я использую их в $string с тремя дополнительными специальными символами ðŋц (которые не принадлежат эстонскому алфавиту).

use feature 'say';
use POSIX qw( locale_h );

{
  use utf8;
  my  $string = "õäöüšž ðŋц";
  binmode STDOUT, ":encoding(UTF-8)";
  say "nothing";
  say 'LOCALE: ', setlocale(LC_CTYPE), ' ', setlocale(LC_COLLATE);
  say 'UC: ', uc( $string );
  say 'SORT: ', sort( split(//, $string) );
  say $string =~ m/\w/g;
  say $string =~ m/\p{Word}/g;
  say '';
}

{
  use utf8;
  use locale;
  binmode STDOUT, ":encoding(UTF-8)";
  my  $string = "õäöüšž ðŋц";
  say "locale";
  say 'LOCALE: ', setlocale(LC_CTYPE), ' ', setlocale(LC_COLLATE);
  say 'UC: ', uc( $string );
  say 'SORT: ', sort( split(//, $string) );
  say $string =~ m/\w/g;
  say $string =~ m/\p{Word}/g;
  say '';
}

{
  use utf8::all;
  my  $string = "õäöüšž ðŋц";
  say "utf8::all";
  say 'LOCALE: ', setlocale(LC_CTYPE), ' ', setlocale(LC_COLLATE);
  say 'UC: ', uc( $string );
  say 'SORT: ', sort( split(//, $string) );
  say $string =~ m/\w/g;
  say $string =~ m/\p{Word}/g;
  say '';
}

{
  use utf8::all;
  use locale;
  my  $string = "õäöüšž ðŋц";
  say "utf8::all + locale";
  say 'LOCALE: ', setlocale(LC_CTYPE), ' ', setlocale(LC_COLLATE);
  say 'UC: ', uc( $string );
  say 'SORT: ', sort( split(//, $string) );
  say $string =~ m/\w/g;
  say $string =~ m/\p{Word}/g;
  say '';
}

Я пробовал с Perl 5.10.1 и 5.14.2, и оба дали мне такой результат:

nothing
LOCALE: et_EE.UTF-8 et_EE.UTF-8
UC: ÕÄÖÜŠŽ ÐŊЦ
SORT:  äðõöüŋšžц
õäöüšžðŋц
õäöüšžðŋц

locale
LOCALE: et_EE.UTF-8 et_EE.UTF-8
UC: ÕÄÖÜŠŽ ÐŊЦ
SORT:  ðŋšžõäöüц
šžŋц
õäöüšžðŋц

utf8::all
LOCALE: et_EE.UTF-8 et_EE.UTF-8
UC: ÕÄÖÜŠŽ ÐŊЦ
SORT:  äðõöüŋšžц
õäöüšžðŋц
õäöüšžðŋц

utf8::all + locale
LOCALE: et_EE.UTF-8 et_EE.UTF-8
UC: ÕÄÖÜŠŽ ÐŊЦ
SORT:  ðŋšžõäöüц
šžŋц
õäöüšžðŋц

Что не так, как я ожидал?

  • Основная проблема: под use locale я надеялся, что \w будет соответствовать всем моим шести символам, но результат šžŋц довольно странный. Зачем такие спички? Из perlrecharclass я прочитал:

Для кодовых точек выше 255 ... \w соответствует тому же, что и \p{Word} в этом диапазоне. ... Для кодовых точек ниже 256 ... если действуют правила локали ... \w соответствует собственному символу подчеркивания платформы плюс тому, что локаль считает буквенно-цифровым.

Таким образом, \w соответствует там символам выше 255, но не соответствует «всем, что локаль считает буквенно-цифровым». Почему? В то же время сортировка по локали работает нормально (а без локали - нет), результат ðŋšžõäöüц - правильный порядок, который показывает, что правильные символы представлены правильно. AFAIU, сортировка не могла бы работать нормально, не зная их «независимо от того, что локаль считает буквенно-цифровым». Или?

  • я думал, что setlocale дает результат только под прагмой локали. Как я могу проверить, какая локаль эффективна для области?
  • я не ожидал, что все символы в каждом тестовом примере будут в верхнем регистре. AFAIU uc и lc должны зависеть от локали. В первом случае я думал, что все они будут в нижнем регистре, но, используя локаль, я ждал, что первые шесть символов будут в верхнем регистре, а другие нет. Единственный случай, когда я ждал, чтобы все символы были в верхнем регистре, был третьим. Я вижу, что упускаю здесь что-то важное. К сожалению, теперь я нашел из lc документов: «В противном случае, если в EXPR установлен флаг UTF-8: для изменения регистра используется семантика Unicode». На моем $string всегда установлен флаг UTF-8, поэтому при написании я получил ответ.

Использование locale для сортировки и \p{Word} для сопоставления приемлемо для меня, но я все же воспользуюсь некоторыми подсказками: почему \w не работает так, как я ожидал?


person w.k    schedule 13.02.2013    source источник
comment
У меня были похожие проблемы. См. perlmonks.org/?node_id=935404 .   -  person choroba    schedule 14.02.2013
comment
1. поддержка локалей не так хороша. Это архаичная система, и большинство систем в любом случае имеют плохие файлы локали.   -  person ikegami    schedule 14.02.2013
comment
2. Я думаю, что есть большие проблемы с локалями UTF8 в целом. Я думаю, вам больше повезет с локалью с однобайтовой кодировкой и строками, закодированными с использованием этой кодировки (а не строки кодовых точек Unicode, которые вы используете в настоящее время).   -  person ikegami    schedule 14.02.2013
comment
@ikegami: какие у нас есть альтернативы для сортировки помимо локалей?   -  person w.k    schedule 14.02.2013
comment
Это не та область, о которой я много знаю (поэтому я думаю, что комментарии, а не ответ).   -  person ikegami    schedule 14.02.2013
comment
@ikegami прав. Пожалуйста, не используйте принципиально сломанную (и неисправимую) прагму use locale. Пожалуйста, пожалуйста, используйте Unicode::Collate::Locale для всех сопоставлений и сравнений, специфичных для вашей локали. Смотрите обновленный ответ ниже, чтобы узнать, как вы должны подходить к этому. ПОСЛЕДНЕЕ ПРИМЕЧАНИЕ: Теперь ваш \w тоже будет работать правильно.   -  person tchrist    schedule 23.02.2013


Ответы (1)


Пожалуйста, не используйте неработающую прагму use locale.

Пожалуйста, пожалуйста, используйте Unicode::Collate::Locale для сортировки по локали. Он использует правила CLDR, полностью переносим и не зависит от хитроумных сломанных локалей POSIX, которые просто плохо работают.

Если вы сортируете по кодовой точке, вы получаете ерунду, но если вы сортируете с использованием объекта Unicode::Collate::Locale, созданного с эстонской локалью, вы получаете что-то разумное:

Codepoint sort:  äðõöüŋšžц
Estonian  sort:  ðŋšžõäöüц

Кроме того, когда вы делаете эту необработанную сортировку по кодовым точкам, на вас ужасно влияют вопросы нормализации. Рассмотреть возможность:

NFC/NFD sort by codepoint is DIFFERENT
NFC Codepoint sort:  äðõöüŋšžц
NFD Codepoint sort:  äõöšüžðŋц

NFC/NFD sort in estonian  is SAME
NFC Estonian  sort:  ðŋšžõäöüц
NFD Estonian  sort:  ðŋšžõäöüц

А вот демонстрационная программа, которая все это произвела.

#!/usr/bin/env perl
#
# et-demo - show how to handle Estonian collation correctly
#
# Tom Christinansen <[email protected]>
# Fri Feb 22 19:27:51 MST 2013

use v5.14;
use utf8;
use strict;
use warnings;
use warnings FATAL => "utf8";
use open qw(:std :utf8);

use Unicode::Normalize;
use Unicode::Collate::Locale;

main();
exit();

sub graphemes(_) {
    my($str) = @_;
    my @graphs = $str =~ /\X/g;
    return @graphs;
}

sub same_diff($$) {
    my($s1, $s2) = @_;
    no locale;

    if (NFC($s1) eq NFC($s2)) {
        return "SAME";
    } else {
        return "DIFFERENT";
    }
}

sub stringy {
    return join("" => @_);
}

sub cp_sort {
    no locale;
    return sort @_;
}

sub et_sort {
    state $collator = # we want Estonian here:
        Unicode::Collate::Locale->new(locale => "et");
    return $collator->sort(@_);
}

sub main {
    my $orig = "õäöüšž ðŋц";

    say "    Codepoint sort: ", cp_sort(graphemes($orig));
    say "    Estonian  sort: ", et_sort(graphemes($orig));

    my $nfc = NFC($orig);
    my $nfc_cp_sort = stringy cp_sort(graphemes($nfc));
    my $nfc_et_sort = stringy et_sort(graphemes($nfc));

    my $nfd = NFD($orig);
    my $nfd_cp_sort = stringy cp_sort(graphemes($nfd));
    my $nfd_et_sort = stringy et_sort(graphemes($nfd));

    say "NFC/NFD sort by codepoint is ",
        same_diff($nfc_cp_sort, $nfd_cp_sort);

    say "NFC Codepoint sort: ", $nfc_cp_sort;
    say "NFD Codepoint sort: ", $nfd_cp_sort;

    say "NFC/NFD sort in estonian  is ",
        same_diff($nfc_et_sort, $nfd_et_sort);

    say "NFC Estonian  sort: ", $nfc_et_sort;
    say "NFD Estonian  sort: ", $nfd_et_sort;

}

Это действительно то, как вы должны обрабатывать сопоставление локали. См. также этот ответ для многочисленных примеров.

person tchrist    schedule 23.02.2013
comment
Не ожидал такого развернутого ответа. Спасибо! Действительно ценится. - person w.k; 23.02.2013