Perl: упаковка последовательности байтов в строку - PullRequest
2 голосов
/ 30 марта 2020

Я пытаюсь запустить простой тест, в котором я хочу иметь двоичные строки различного формата и распечатать их. На самом деле, я пытаюсь исследовать проблему, из-за которой sprintf не может иметь дело с строкой широких символов, переданной для заполнителя %s.

В этом случае двоичная строка должна содержать только кириллицу c "д" (потому что он выше ISO-8859-1)

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

Но ничего, что не проходит через pack работает.

  • Для случая UTF-8 мне нужно установить флаг UTF-8 для строки $ch, но как.
  • Сбой случая UCS-2, и я Предположим, это потому, что для ISO 108859 * нет способа для Perl UCS-2, так что этот тест, вероятно, чушь, верно?

Код:

#!/usr/bin/perl

use utf8; # Meaning "This lexical scope (i.e. file) contains utf8"

# https://perldoc.perl.org/open.html

use open qw(:std :encoding(UTF-8));

sub showme {
   my ($name,$ch) = @_;
   print "-------\n";
   print "This is test: $name\n";

   my $ord = ord($ch); # ordinal computed outside of "use bytes"; actually should yield the unicode codepoint

   {
      # https://perldoc.perl.org/bytes.html
      use bytes;
      my $mark = (utf8::is_utf8($ch) ? "yes" : "no");
      my $txt  = sprintf("Received string of length: %i byte, contents: %vd, ordinal x%04X, utf-8: %s\n", length($ch), $ch, $ord, $mark);
      print $txt,"\n";
   }

   print $ch, "\n";
   print "Combine: $ch\n";
   print "Concat: " . $ch . "\n";
   print "Sprintf: " . sprintf("%s",$ch) . "\n";
   print "-------\n";
}


showme("Cryillic direct" , "д");
showme("Cyrillic UTF-8"  , pack("HH","D0","B4"));  # UTF-8 of д is D0B4
showme("Cyrillic UCS-2"  , pack("HH","04","34"));  # UCS-2 of д is 0434

Токовый выход:

Хорошо выглядит

-------
This is test: Cryillic direct
Received string of length: 2 byte, contents: 208.180, ordinal x0434, utf-8: yes

д
Combine: д
Concat: д
Sprintf: д
-------

Это нет. Откуда происходит 176? ??

-------
This is test: Cyrillic UTF-8
Received string of length: 2 byte, contents: 208.176, ordinal x00D0, utf-8: no

а
Combine: а
Concat: а
Sprintf: а
-------

Это еще хуже.

-------
This is test: Cyrillic UCS-2
Received string of length: 2 byte, contents: 0.48, ordinal x0000, utf-8: no

0
Combine: 0
Concat: 0
Sprintf: 0
-------

Ответы [ 3 ]

4 голосов
/ 30 марта 2020

У вас две проблемы.


Ваши звонки на pack неверны

Каждый H представляет один шестнадцатеричный ди git.

$ perl -e'printf "%vX\n", pack("HH", "D0", "B4")'       # XXX
D0.B0

$ perl -e'printf "%vX\n", pack("H2H2", "D0", "B4")'     # Ok
D0.B4

$ perl -e'printf "%vX\n", pack("(H2)2", "D0", "B4")'    # Ok
D0.B4

$ perl -e'printf "%vX\n", pack("(H2)*", "D0", "B4")'    # Better
D0.B4

$ perl -e'printf "%vX\n", pack("H*", "D0B4")'           # Alternative
D0.B4

STDOUT ожидает декодированный текст, но вы предоставляете закодированный текст

Сначала давайте взглянем на строки, которые вы создаете (как только проблема указанное выше исправлено). Все, что вам для этого нужно, - это формат %vX, который предоставляет значение каждого символа в шестнадцатеричном формате, разделенное точкой.

  • "д" создает односимвольную строку. Этот символ является кодовой точкой Unicode для д.

    $ perl -e'use utf8; printf("%vX\n", "д");'
    434
    
  • pack("H*", "D0B4") создает двухсимвольную строку. Эти символы представляют собой кодировку UTF-8 д.

    $ perl -e'printf("%vX\n", pack("H*", "D0B4"));'
    D0.B4
    
  • pack("H*", "0434") создает двухсимвольную строку. Эти символы являются кодировками UCS-2be и UTF-16be д.

    $ perl -e'printf("%vX\n", pack("H*", "0434"));'
    4.34
    

Обычно дескриптор файла ожидает строку байтов (символов со значениями в 0 .. 255) быть напечатанным к нему. Эти байты дословно выводятся. [1] [2]

Когда слой кодирования (например, :encoding(UTF-8)) добавляется к дескриптору файла, он ожидает Вместо него должна быть напечатана строка кодовых точек Unicode (он же декодированный текст).

Ваша программа добавляет слой кодирования к STDOUT (используя прагму use open), поэтому вы должны предоставить UCP (декодированный текст) до print и say. Вы можете получить декодированный текст из закодированного текста, используя, например, функцию Encode decode.

use utf8;
use open qw( :std :encoding(UTF-8) );
use feature qw( say );

use Encode qw( decode );

say "д";                   # ok  (UCP of "д")
say pack("H*", "D0B4");    # XXX (UTF-8 encoding of "д")
say pack("H*", "0434");    # XXX (UCS-2be and UTF-16be encoding of "д")

say decode("UTF-8",    pack("H*", "D0B4"));   # ok (UCP of "д")
say decode("UCS-2be",  pack("H*", "0434"));   # ok (UCP of "д")
say decode("UTF-16be", pack("H*", "0434"));   # ok (UCP of "д")

Для случая UTF-8 мне нужно установить UTF -8 флаг на

Нет, вам нужно декодировать строки.

Флаг UTF-8 не имеет значения. Установлен флаг или нет изначально не имеет значения. Установлен флаг или нет после того, как строка декодирована, не имеет значения. Флаг указывает, как строка хранится внутри, что вас не должно волновать.

Например, возьмите

use strict;
use warnings;
use open qw( :std :encoding(UTF-8) );
use feature qw( say );

my $x = chr(0xE9);

utf8::downgrade($x);   # Tell Perl to use the UTF8=0 storage format.
say sprintf "%s %vX %s", utf8::is_utf8($x) ? "UTF8=1" : "UTF8=0", $x, $x;

utf8::upgrade($x);   # Tell Perl to use the UTF8=1 storage format.
say sprintf "%s %vX %s", utf8::is_utf8($x) ? "UTF8=1" : "UTF8=0", $x, $x;

Выводит

UTF8=0 E9 é
UTF8=1 E9 é

Независимо от флаг UTF8 выводит кодировку UTF-8 (C3 A9) предоставленного UCP (U + 00E9).


Я полагаю, это потому, что нет путь для Perl UCS-2 из ISO-8859-1, так что тест, вероятно, является бредом, верно?

В лучшем случае можно использовать эвристику, чтобы угадать, закодирована ли строка используя iso-latin-1 или UCS-2be. Я подозреваю, что можно получить довольно точные результаты (например, те , которые вы получили бы для iso-latin-1 и UTF-8.)

Я не уверен, почему вы подняли iso- latin-1, так как ничто в вашем вопросе не относится к iso-latin-1.


  1. За исключением Windows, где слой :crlf добавлен в дескрипторы по умолчанию.

  2. Вы получаете предупреждение Wide character, если вы предоставляете строку, содержащую символ, который не является байтом, и вместо нее выводится кодировка utf8 строки.

1 голос
/ 31 марта 2020

Оба - хороший ответ. Вот небольшое расширение кода Polar Bear для вывода подробностей о строке:

use strict;
use warnings;
use feature 'say';

use utf8;
use Encode;

sub about {
   my($str) = @_;
   # https://perldoc.perl.org/bytes.html
   my $charlen = length($str);
   my $txt;
   {
      use bytes;
      my $mark = (utf8::is_utf8($str) ? "yes" : "no");
      my $bytelen = length($str);
      $txt  = sprintf("Length: %d byte, %d chars, utf-8: %s, contents: %vd\n", 
                      $bytelen,$charlen,$mark,$str);
   }
   return $txt;
}

my $str;

my $utf8   = 'Привет Москва';
my $ucs2le = '1f044004380432043504420420001c043e0441043a0432043004';    # Little Endian
my $ucs2be = '041f044004380432043504420020041c043e0441043a04320430';    # Big Endian
my $utf16  = '041f044004380432043504420020041c043e0441043a04320430';
my $utf32  = '0000041f0000044000000438000004320000043500000442000000200000041c0000043e000004410000043a0000043200000430';

binmode STDOUT, ':utf8';

say 'UTF-8:   ' . $utf8;
say about($utf8);

{
   my $str = pack('H*',$ucs2be);
   say 'UCS-2BE: ' . decode('UCS-2BE',$str);
   say about($str);
}

{
   my $str = pack('H*',$ucs2le);
   say 'UCS-2LE: ' . decode('UCS-2LE',$str);
   say about($str);
}

{
   my $str = pack('H*',$utf16);
   say 'UTF-16:  '. decode('UTF16',$str);
   say about($str);
}

{
   my $str = pack('H*',$utf32);
   say  'UTF-32:  ' . decode('UTF32',$str);
   say about($str);
}

# Try identity transcoding

{
   my $str_encoded_in_utf16 = encode('UTF16',$utf8);
   my $str = decode('UTF16',$str_encoded_in_utf16);
   say 'The same: ' . $str;
   say about($str);
}

Выполнение этого дает:

UTF-8:   Привет Москва
Length: 25 byte, 13 chars, utf-8: yes, contents: 208.159.209.128.208.184.208.178.208.181.209.130.32.208.156.208.190.209.129.208.186.208.178.208.176

UCS-2BE: Привет Москва
Length: 26 byte, 26 chars, utf-8: no, contents: 4.31.4.64.4.56.4.50.4.53.4.66.0.32.4.28.4.62.4.65.4.58.4.50.4.48

UCS-2LE: Привет Москва
Length: 26 byte, 26 chars, utf-8: no, contents: 31.4.64.4.56.4.50.4.53.4.66.4.32.0.28.4.62.4.65.4.58.4.50.4.48.4

UTF-16:  Привет Москва
Length: 26 byte, 26 chars, utf-8: no, contents: 4.31.4.64.4.56.4.50.4.53.4.66.0.32.4.28.4.62.4.65.4.58.4.50.4.48

UTF-32:  Привет Москва
Length: 52 byte, 52 chars, utf-8: no, contents: 0.0.4.31.0.0.4.64.0.0.4.56.0.0.4.50.0.0.4.53.0.0.4.66.0.0.0.32.0.0.4.28.0.0.4.62.0.0.4.65.0.0.4.58.0.0.4.50.0.0.4.48

The same: Привет Москва
Length: 25 byte, 13 chars, utf-8: yes, contents: 208.159.209.128.208.184.208.178.208.181.209.130.32.208.156.208.190.209.129.208.186.208.178.208.176

И небольшую диаграмму, которую я сделал в качестве обзора в следующий раз , покрытие encode, decode и pack. Потому что лучше быть готовым в следующий раз.

perl_strings_and_encode_decode

(приведенная выше диаграмма и ее файл graphml доступны здесь )

1 голос
/ 30 марта 2020

Пожалуйста, посмотрите, если следующий код демонстрации какой-либо помощи

use strict;
use warnings;
use feature 'say';

use utf8;     # https://perldoc.perl.org/utf8.html
use Encode;   # https://perldoc.perl.org/Encode.html

my $str;

my $utf8   = 'Привет Москва';
my $ucs2le = '1f044004380432043504420420001c043e0441043a0432043004';    # Little Endian
my $ucs2be = '041f044004380432043504420020041c043e0441043a04320430';    # Big Endian
my $utf16  = '041f044004380432043504420020041c043e0441043a04320430';
my $utf32  = '0000041f0000044000000438000004320000043500000442000000200000041c0000043e000004410000043a0000043200000430';

# https://perldoc.perl.org/functions/binmode.html

binmode STDOUT, ':utf8'; 

# https://perldoc.perl.org/feature.html#The-'say'-feature

say 'UTF-8:   ' . $utf8;  

# https://perldoc.perl.org/Encode.html#THE-PERL-ENCODING-API

$str = pack('H*',$ucs2be);
say 'UCS-2BE: ' . decode('UCS-2BE',$str);  

$str = pack('H*',$ucs2le);
say 'UCS-2LE: ' . decode('UCS-2LE',$str);

$str = pack('H*',$utf16);
say 'UTF-16:  '. decode('UTF16',$str);

$str = pack('H*',$utf32);
say 'UTF-32:  ' . decode('UTF32',$str);

Вывод

UTF-8:   Привет Москва
UCS-2BE: Привет Москва
UCS-2LE: Привет Москва
UTF-16:  Привет Москва
UTF-32:  Привет Москва

Поддерживаемые кириллицы c кодировки

use strict;
use warnings;
use feature 'say';

use Encode;
use utf8;

binmode STDOUT, ':utf8';

my $utf8 = 'Привет Москва';
my @encodings = qw/UCS-2 UCS-2LE UCS-2BE UTF-16 UTF-32 ISO-8859-5 CP855 CP1251 KOI8-F KOI8-R KOI8-U/;

say '
:: Supported Cyrillic encoding
---------------------------------------------
UTF-8       ', $utf8;

for (@encodings) {
    printf "%-11s %s\n", $_, unpack('H*', encode($_,$utf8));
}

Вывод

:: Supported Cyrillic encoding
---------------------------------------------
UTF-8       Привет Москва
UCS-2       041f044004380432043504420020041c043e0441043a04320430
UCS-2LE     1f044004380432043504420420001c043e0441043a0432043004
UCS-2BE     041f044004380432043504420020041c043e0441043a04320430
UTF-16      feff041f044004380432043504420020041c043e0441043a04320430
UTF-32      0000feff0000041f0000044000000438000004320000043500000442000000200000041c0000043e000004410000043a0000043200000430
ISO-8859-5  bfe0d8d2d5e220bcdee1dad2d0
CP855       dde1b7eba8e520d3d6e3c6eba0
CP1251      cff0e8e2e5f220cceef1eae2e0
KOI8-F      f0d2c9d7c5d420edcfd3cbd7c1
KOI8-R      f0d2c9d7c5d420edcfd3cbd7c1
KOI8-U      f0d2c9d7c5d420edcfd3cbd7c1

Документация Кодировка :: Поддерживается

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