Почему кодирование, а затем расшифровка строк приводит к тому, что арабские символы теряют свой контекст?

Я (с опозданием) тестировал воды Unicode в первый раз и не понимаю, почему процесс кодирования, а затем декодирует арабскую строку, имеет эффект разделения отдельных символов, из которых состоит слово.

В приведенном ниже примере слово "للبيع" состоит из 5 отдельных букв: "ع", "ي", "ب", "ل", "ل", написанных справа налево. В зависимости от окружающего контекста (смежные буквы) буквы меняют форму


use strict;
use warnings;
use utf8;

binmode( STDOUT, ':utf8' );

use Encode qw< encode decode >;

my $str = 'ﻟﻠﺒﻴﻊ';                 # "For sale" 
my $enc = encode( 'UTF-8', $str );
my $dec = decode( 'UTF-8', $enc );

my $decoded = pack 'U0W*', map +ord, split //, $enc;

print "Original string : $str\n";     #  ل ل ب ي ع   
print "Decoded string 1: $dec\n"      #  ل ل ب ي ع
print "Decoded string 2: $decoded\n"; #  ل ل ب ي ع

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ

  • При вставке строки в этот пост рендеринг отменяется, поэтому он выглядит как "عيبلل". Я меняю его вручную, чтобы заставить его выглядеть "правильно". Правильный hexdump приведен ниже:

    $ echo "ﻟﻠﺒﻴﻊ" | hexdump
    0000000 bbef ef8a b4bb baef ef92 a0bb bbef 0a9f
    0000010
    
  • Вывод Perl script (по запросу iikegami):

    $ perl unicode.pl | od -t x1
    0000000 4f 72 69 67 69 6e 61 6c 20 73 74 72 69 6e 67 20
    0000020 3a 20 d8 b9 d9 8a d8 a8 d9 84 d9 84 0a 44 65 63
    0000040 6f 64 65 64 20 73 74 72 69 6e 67 20 31 3a 20 d8
    0000060 b9 d9 8a d8 a8 d9 84 d9 84 0a 44 65 63 6f 64 65
    0000100 64 20 73 74 72 69 6e 67 20 32 3a 20 d8 b9 d9 8a
    0000120 d8 a8 d9 84 d9 84 0a
    0000127
    

    И если я просто напечатаю $str:

    $ perl unicode.pl | od -t x1
    0000000 4f 72 69 67 69 6e 61 6c 20 73 74 72 69 6e 67 20
    0000020 3a 20 d8 b9 d9 8a d8 a8 d9 84 d9 84 0a
    0000035
    

    Наконец (по запросу икегами):

    $ grep 'For sale' unicode.pl | od -t x1
    0000000 6d 79 20 24 73 74 72 20 3d 20 27 d8 b9 d9 8a d8
    0000020 a8 d9 84 d9 84 27 3b 20 20 23 20 22 46 6f 72 20
    0000040 73 61 6c 65 22 20 0a
    0000047
    
  • Детали Perl

    $ perl -v
    
    This is perl, v5.10.1 (*) built for x86_64-linux-gnu-thread-multi
    (with 53 registered patches, see perl -V for more detail)
    
  • Вывод в файл отменяет строку: "عيبلل"


ВОПРОСЫ

У меня есть несколько:

  • Как сохранить контекст каждого символа во время печати?

  • Почему исходная строка выводится на экран как отдельные буквы, даже если она не была обработана?

  • При печати в файл слово меняется на противоположное (я предполагаю, что это связано с характером script справа налево). Есть ли способ предотвратить это?

  • Почему следующее не выполняется: $str !~ /\P{Bidi_Class: Right_To_Left}/;

Ответ 1

  • Исходный код, возвращаемый StackOverflow (как выбрано с помощью wget):

    ... ef bb 9f ef bb a0 ef ba 92 ef bb b4 ef bb 8a ...
    
    U+FEDF ARABIC LETTER LAM INITIAL FORM
    U+FEE0 ARABIC LETTER LAM MEDIAL FORM
    U+FE92 ARABIC LETTER BEH MEDIAL FORM
    U+FEF4 ARABIC LETTER YEH MEDIAL FORM
    U+FECA ARABIC LETTER AIN FINAL FORM
    
  • perl вывод я получаю из исходного кода, возвращаемого StackOverflow:

    ... ef bb 9f ef bb a0 ef ba 92 ef bb b4 ef bb 8a 0a
    ... ef bb 9f ef bb a0 ef ba 92 ef bb b4 ef bb 8a 0a
    ... ef bb 9f ef bb a0 ef ba 92 ef bb b4 ef bb 8a 0a
    
    U+FEDF ARABIC LETTER LAM INITIAL FORM
    U+FEE0 ARABIC LETTER LAM MEDIAL FORM
    U+FE92 ARABIC LETTER BEH MEDIAL FORM
    U+FEF4 ARABIC LETTER YEH MEDIAL FORM
    U+FECA ARABIC LETTER AIN FINAL FORM
    U+000A LINE FEED
    

    Итак, я получаю именно то, что в источнике, как и должно быть.

  • perl Вы получили:

    ... d8 b9 d9 8a d8 a8 d9 84 d9 84 0a
    ... d8 b9 d9 8a d8 a8 d9 84 d9 84 0a
    ... d8 b9 d9 8a d8 a8 d9 84 d9 84 0a
    
    U+0639 ARABIC LETTER AIN
    U+064A ARABIC LETTER YEH
    U+0628 ARABIC LETTER BEH
    U+0644 ARABIC LETTER LAM
    U+0644 ARABIC LETTER LAM
    U+000A LINE FEED
    

    Итак, у вас может быть багги Perl (который меняет и изменяет арабские символы и только те), но гораздо более вероятно, что ваши источники не содержат того, что вы думаете. Вам нужно проверить, какие байты составляют ваш источник.

  • echo Вы получили:

    ef bb 8a ef bb b4 ef ba 92 ef bb a0 ef bb 9f 0a
    
    U+FECA ARABIC LETTER AIN FINAL FORM
    U+FEF4 ARABIC LETTER YEH MEDIAL FORM
    U+FE92 ARABIC LETTER BEH MEDIAL FORM
    U+FEE0 ARABIC LETTER LAM MEDIAL FORM
    U+FEDF ARABIC LETTER LAM INITIAL FORM
    U+000A LINE FEED
    

    Существуют значительные различия в том, что вы получили от perl и от echo, поэтому неудивительно, что они проявляются по-разному.


Выход проверен с использованием:

$ perl -Mcharnames=:full -MEncode=decode_utf8 -E'
   say sprintf("U+%04X %s", $_, charnames::viacode($_))
      for unpack "C*", decode_utf8 pack "H*", $ARGV[0] =~ s/\s//gr;
' '...'

(Не забудьте поменять байты hexdump.)

Ответ 2

Может быть что-то странное с вашей оболочкой? Если я перенаправляю вывод в файл, результат будет таким же. Попробуйте это:

use strict;
use warnings;
use utf8;

binmode( STDOUT, ':utf8' );

use Encode qw< encode decode >;

my $str = 'ﻟﻠﺒﻴﻊ';                 # "For sale" 
my $enc = encode( 'UTF-8', $str );
my $dec = decode( 'UTF-8', $enc );

my $decoded = pack 'U0W*', map +ord, split //, $enc;

open(F1,'>',"origiinal.txt") or die;
open(F2,'>',"decoded.txt") or die;
open(F3,'>',"decoded2.txt") or die;

binmode(F1, ':utf8');binmode(F2, ':utf8');binmode(F3, ':utf8');

print F1 "$str\n";     #  ل ل ب ي ع   
print F2 "$dec\n";     #  ل ل ب ي ع
print F3 "$decoded\n";