Perl: строковый литерал в модуле в latin1 — я хочу utf8

В модуле Date::Holidays::DK названия некоторых датских праздников записываются в кодировке Latin1. Например, 1 января — Нютарсдаг. Что я должен сделать с $x ниже, чтобы получить правильную строку в кодировке utf8?

use Date::Holidays::DK;
my $x = is_dk_holiday(2011,1,1);

Я пробовал различные комбинации use utf8 и no utf8 до/после use Date::Holidays::DK, но это не дает никакого эффекта. Я также пытался использовать Encode decode, но безуспешно. В частности,

use Date::Holidays::DK;
use Encode;
use Devel::Peek;
my $x = decode("iso-8859-1", 
           is_dk_holiday(2011,1,1)
          );
Dump($x);
print "January 1st is '$x'\n";

дает результат

SV = PV(0x15eabe8) at 0x1492a10
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK,UTF8)
  PV = 0x1593710 "Nyt\303\245rsdag"\0 [UTF8 "Nyt\x{e5}rsdag"]
  CUR = 10
  LEN = 16
January 1st is 'Nyt sdag'

(с недопустимым символом между t и s).


person Villemoes    schedule 14.07.2011    source источник


Ответы (2)


используйте utf8 и не используйте utf8 до/после использования Date::Holidays::DK, но, похоже, это не имеет никакого эффекта.

Правильный. Прагма utf8 указывает только на то, что исходный код программы написан в UTF-8.

Я также пытался использовать декодирование Encode, но безуспешно.

Вы не правильно это восприняли, вы ведь правильно сделали. Теперь у вас есть строка символов Perl, и вы можете ею манипулировать.

с недопустимым символом между t и s

Вы также неправильно интерпретируете это, на самом деле это символ å.


Вы хотите вывести UTF-8, поэтому вам не хватает шага кодирования.

my $octets = encode 'UTF-8', $x;
print $octets;

Пожалуйста, прочтите http://p3rl.org/UNI для ознакомления с темой кодирования. Вы всегда должны декодировать и кодировать, явно или неявно.

person daxim    schedule 14.07.2011

use utf8 только намекает интерпретатору/компилятору Perl, что ваш файл закодирован в UTF-8. Если у вас есть строки с установленным старшим битом, он автоматически закодирует их в Unicode.

Если у вас есть переменная, закодированная в iso-8859-1, вы должны ее декодировать. Тогда ваша переменная находится во внутреннем формате юникода. Это utf8, но вам все равно, какую кодировку Perl использует внутри.

Теперь, если вы хотите напечатать такую ​​строку, вам нужно преобразовать строку Unicode обратно в строку байтов. Вам нужно сделать encode в этой строке. Если вы не выполните кодировку вручную, perl сама закодирует ее обратно в iso-8859-1. Это кодировка по умолчанию.

Прежде чем напечатать свою переменную $x, вам нужно сделать с ней $x = encode('UTF-8', $x).

Для правильной обработки UTF-8 вам всегда нужно decode() каждый внешний ввод через ввод-вывод. И вам всегда нужно encode() все, что покидает вашу программу.

Чтобы изменить кодировку ввода/вывода по умолчанию, вы можете использовать что-то вроде этого.

use utf8;
use open ':encoding(UTF-8)';
use open ':std';

Первая строка говорит, что ваш исходный код закодирован в utf8. Вторая строка говорит, что каждый ввод/вывод должен автоматически кодироваться в utf8. Важно отметить, что open() также открывает файл в режиме utf8. Если вы работаете с двоичными файлами, вам нужно вызвать binmode() для дескриптора.

Но вторая строка не меняет обработку STDIN, STDOUT или STDERR. Третья строка изменит это.

Вероятно, вы можете использовать модуль utf8:all, который упрощает этот процесс. Но всегда хорошо понимать, как все это работает за кулисами.

Чтобы исправить свой пример. Один из возможных способов таков:

#!/usr/bin/env perl
use Date::Holidays::DK;
use Encode;
use Devel::Peek;
my $x = decode("iso-8859-1", 
           is_dk_holiday(2011,1,1)
          );
Dump($x);
print encode("UTF-8", "January 1st is '$x'\n");
person David Raab    schedule 14.07.2011
comment
Я хочу, чтобы вы удалили абзац об is_utf8. - person daxim; 14.07.2011
comment
Знаете ли вы лучший способ проверить, закодирована ли строка внутри Unicode? Потом заменю. - person David Raab; 14.07.2011
comment
ITYM, чтобы сказать, внутренне закодировано в кодировке UTF-8, потому что что-то, закодированное в наборе символов, таком как Unicode, не имеет никакого смысла. В ответ: вам все равно, и флаг SvUTF8 или его отсутствие не могут вам сказать (это то, что на самом деле проверяет is_utf8). Программист должен только отслеживать: Я уже декодировал входящие октеты? Я уже закодировал исходящие символьные данные? Как Perl внутренне кодирует символьные данные, это его личное дело (это сложнее, чем вы думаете), и вы не должны связываться с функциями из модуля utf8. Его документация говорит об этом. - person daxim; 14.07.2011
comment
Если вы хотите написать модуль, который правильно обрабатывает строку юникода и общается с внешним миром, вам нужно знать, закодирована ли строка в юникоде или нет (да, юникод — это не кодировка, и внутри это utf-8, а пользователю все равно, какое внутреннее представление, пользователю должно быть важно только, юникод это или нет). Но, конечно, вы также не можете заботиться о юникоде и позволить пользователю, использующему ваш модуль, обрабатывать его самостоятельно, но мне это не нравится. В Perl есть строки в формате Unicode, и автор модуля должен их учитывать. Я всегда открыт для лучшего пути. Не делай этого, это не лучший способ. - person David Raab; 14.07.2011
comment
Извините, но это совсем не так. is_utf8 не указывает, нужно ли что-то кодировать. На самом деле Perl не имеет возможности узнать, нужно ли кодировать строку или нет. Если бы он это сделал, он мог бы сделать это сам. (Я бы подробно развенчал ваши утверждения, но этот блок действительно не подходит для объяснения чего-либо.) Что касается того, что делать вместо этого, вы должны декодировать все на входе и кодировать все на выходе. Если вы хотите иметь дело как с закодированными, так и с декодированными строками, вам нужно будет вручную отслеживать, что есть что. - person ikegami; 14.07.2011
comment
Поскольку вы оба жалуетесь, я удалю предложение. И создайте из него вопрос чуть позже (завтра). - person David Raab; 14.07.2011
comment
@Sid Burn, например, в $name = "\x{C9}ric";, $name содержится текстовая строка альтернативного написания моего имени. Поскольку это текст, его необходимо закодировать. В $control_seq = "\xC9\x72\x69\x63"; $control_seq содержит строку byte для управления некоторым устройством. Поскольку это байты, их нельзя кодировать. Обе строки неразличимы. - person ikegami; 14.07.2011
comment
Если не расшифровать, то это не текст, а просто байты. Ваше предположение состоит в том, что \x{c9}ric находится в кодировке ISO-8859-1. Если это так, вам нужно расшифровать его. Если вы это сделаете, то в результирующей переменной вы получите строку юникода, а utf8::is_utf8() вернет true. Если utf8::is_utf8() возвращает true, вам нужно закодировать() его перед печатью. Например, \x{c9}ric напечатает недопустимую строку на терминале UTF-8. Правильный способ распечатать это print encode("UTF-8", decode("iso-8859-1", "\x{c9}ric")). Без декодирования ваша строка - это просто байтовая строка. - person David Raab; 15.07.2011
comment
Проблема в том, что если вы используете, например, HTML::Entities для декодирования текста ascii с помощью '', вы получите строку символов, закодированную внутри Latin1 и без флага UTF8. Если бы вы полагались на то, что говорит вам предикат is_utf8, вы бы подумали, что это байты, но это не так, и если вы попытаетесь декодировать его, вы получите сбой. Подробнее см. в мой блог. - person zby; 29.08.2011
comment
Я дал новые комментарии к вашему блогу. А еще Latin1 — это просто байты. Текст существует только в сознании человека. Компьютер видит только байты. Пока вы не выполняете его decode(), для Perl это всего лишь байты, и вам нужно знать, какая это кодировка, текст это или изображение. - person David Raab; 31.08.2011
comment
Sid - с этим никто не спорит - проблема заключалась в использовании is_utf8 для ответа на вопрос, нужно ли вам decode() или нет. - person zby; 01.09.2011
comment
если is_utf8() возвращает true, вам не нужно что-то decode(). Он уже расшифрован. - person David Raab; 02.09.2011