Perl utf 8 to windows 1251

На хабре уже есть хорошая статья об использовании UTF-8 в Perl — habrahabr.ru/post/53578. Я все же немного по своему хотел бы рассказать о кодировках. Очень мн...

На хабре уже есть хорошая статья об использовании UTF-8 в Perl — habrahabr.ru/post/53578. Я все же немного по своему
хотел бы рассказать о кодировках.

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

Определение кодировки исходного файла. Определение кодировки исходного документа, задача которая довольно часто встречается на практике. Возьмем как пример браузер. Кроме html файла он также может получать в HTTP ответе заголовок, который задает кодировку документа и этот заголовок может быть не правильным, поэтому ориентироваться только на него нельзя, как следствие, браузеры поддерживают возможность автоматической определения кодировки.

В Perl для этого вы можете использовать Encode::Guess, однако более «продвинутым» промышленным вариантом является Encode::Detect::Detector. Как написано в документации к нему, он предоставляет интерфейс к Мозиловскому универсальному определителю кодировки.

Если вы будете изучать исходный код, обратите внимание на файл vnsUniversalDetector.cpp и метод

nsresult nsUniversalDetector::HandleData(const char* aBuf, PRUint32 aLen)

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

  • EF BB BF UTF-8 encoded BOM
  • FE FF 00 00 UCS-4, unusual octet order BOM (3412)
  • FE FF UTF-16, big endian BOM
  • 00 00 FE FF UTF-32, big-endian BOM
  • 00 00 FF FE UCS-4, unusual octet order BOM (2143)
  • FF FE 00 00 UTF-32, little-endian BOM
  • FF FE UTF-16, little endian BOM

Далее анализируется каждый байт данных и анализируется относится ли символ к не US-ASCII (коды от 128 до 255) если да то создаются объекты классов:

  • nsMBCSGroupProber;
  • nsSBCSGroupProber;
  • nsLatin1Prober;

каждый из которых отвечает за анализ групп кодировок (MB – мультибайтовые, SB – однобайтовые).

Если же это US-ASCII то здесь 2-а варианта, либо это обыкновенный ASCII (pure ascii) либо файл содержащий escape последовательности и относится к таким кодировкам как ISO-2022-KR и т.п. (более подробно — en.wikipedia.org/wiki/ISO/IEC_2022). В этом случае используется детектор реализованный классом nsEscCharSetProber.

nsMBCSGroupProber поддерживает такие кодировки как: «UTF8», «SJIS», «EUCJP», «GB18030», «EUCKR», «Big5», «EUCTW».

nsSBCSGroupProber – такие как Win1251,koi8r,ibm866 и другие.

Определение однобайтовой кодировки базируется на анализе частоты вхождения 2-ух символьных последовательностей в текст.

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

Unicode и Perl. Исторический ракурс. Согласно www.unicode.org/glossary в Unicode есть 7 возможных схем кодирования: UTF-8, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE. Для самого термина Unicode дано следующее определение «…стандарт цифрового представления символов, которые используются в письме всеми языками мира…». Кроме этого также существует UTF-7, которая не является частью стандарта, но поддерживается Perl — Encode::Unicode::UTF7 (см.также RFC 2152).

UTF-7 практически не используется. Вот что написано в Encode::Unicode::UTF7 – «…Впрочем, если вы хотите использовать UTF-7 для документов в почте и веб страниц, не используйте ее, пока не удостоверетесь что получатели и читатели (в смысле этих документов) могут обрабатывать эту кодировку…».

Разработчики Perl следуя прогрессу в части повсеместной реализации кодировок Unicode в приложениях, также реализовали поддержку Unicode в Perl. Кроме того модуль Encode поддерживает также другие кодировки как однобайтовые так и многобайтовые, список которых можно просмотреть в пакете Encode::Config. Для работы с письмами, поддерживаются «MIME кодировки»: MIME-Header, MIME-B, MIME-Q, MIME-Header-ISO_2022_JP.

Следует сказать, что UTF-8 очень широко распространена в качестве кодировки для веб документов. UTF-16 используется в Java и Windows, UTF-8 и UTF-32 используется Linux и другими Unix-подобными системами.

Начиная с версии Perl 5.6.0 была изначально реализована возможность работы с Unicode. Тем не менее, для более серьезной работы с Unicode был рекомендован Perl 5.8.0. Perl 5.14.0 – первая версия в которой поддержка Unicode легко (почти) интегрируемая без нескольких подводных камней (исключения составляют некоторые различия в quotemeta). Версия 5.14 также исправляет ряд ошибок и отклонений от стандарта Unicode.

Visual Studio 2012 и кодировки (для сравнения с Perl). Когда мы пишем некоторое приложение на C# в Visual Studio мы не задумываемся о том, в какой кодировке все это хранится и обрабатывается. При создании документа в Vistual Studio она создаст его в UTF8 и еще добавит в заголовок BOM UTF8 — последовательность байтов 0xEF, 0xBB, 0xBF. Когда же мы конвертируем исходный файл (уже открытый в Visual Studio), например, с UTF8 в CP1251 то получим сообщение об ошибке
Some bytes have been replaced with the Unicode substitution character while loading … with Unicode (UTF-8) encoding. Saving the file will not preserve the original file contents.

Если открыть существующий файл в cp1251 – ToUpper(), например, будет отрабатывать корректно, а если конвертировать файл в KOI8-R а потом открыть в Visual Studio и выполнить, ни о какой корректной работе не может быть и речи, здесь среда не знает, что это KOI8-R, да и как она это может узнать?

“Unicode Bug в Perl”. Так же как и в Visual Studio, что-то похожое происходит и с программой на Perl, но разработчики Perl могут явно указывать кодировку исходного кода приложения. Вот почему когда начинающие программировать на perl открывают на русскоязычной Windows XP свой любимый редактор и в ANSI (тоесть cp1251) пишут что-то в духе

use strict;
use warnings;

my $a = "слово";
my $b = "СЛОВО";
my $c = “word”;

print "Words are equal" if uc($a) eq uc($b);

а на выходе получают, что строки в переменных не равны, им вначале сложно понять, что происходит. Аналогичные вещи происходят с регулярными выражениями, строковыми функциями (но uc($c) будет работать корректно).

Это так называемый «Unicode Bug» в Perl (более подробно смотрите в документаци), связанный с тем, что для разных однобайтовых кодировок, символы с кодами от 128 до 255 будут иметь разный смысл. Например, буква П в cp1251 – имеет код 0xCF, тогда как в CP866 – 0x8F, а в KOI8-R – 0xF0. Как в таком случае, отработать правильно таким строковым функциям как uc(), ucfirst(), lc(), lcfirst() или L, U в регулярных выражениях?

Достаточно «подсказать» интерпретатору, что кодировка исходного файла cp1251 и все будет работать правильно. Более точно в приведенном ниже коде, переменные $a и $b будут хранить строки во внутреннем формате Perl.

use strict;
use warnings;
use encoding 'cp1251';

my $a = "слово";
my $b = "СЛОВО";

print "equal" if uc($a) eq uc($b);

Внутренний формат строк в Perl. В не очень старых версиях Perl строки могут хранится в так называемом внутреннем формате (Perl’s internal form). Обратите внимание, что также они могут хранится как просто набор байтов. В примере выше, там, где явно не задавалась кодировка исходного файла (с помощью use encoding ‘cp1251’;) переменные $a, $b, $c хранят просто набор байтов (еще в документации к Perl используется термин последовательность октетов — a sequence of octets).

Внутренний формат от набора байтов отличается тем, что используется кодировка UTF-8 и для переменной включен флаг UTF8. Приведу пример. Изменим немного исходный код программы на следующий

use strict;
use warnings;
use encoding 'cp1251';
use Devel::Peek;


my $a = "слово";
my $b = "СЛОВО";

print Dump ($a);

Вот, что мы получим в результате

SV = PV(0x199ee4) at 0x19bfb4
REFCNT = 1
FLAGS = (PADMY,POK,pPOK,UTF8)
PV = 0x19316c «321201320273320276320262320276» [UTF8 «x{441}x{43b}x{43e}x{432}x{43e}»]
CUR = 10
LEN = 12

Обратите внимание, что FLAGS = (PADMY,POK,pPOK,UTF8). Если мы уберем use encoding ‘cp1251’;
то получим

SV = PV(0x2d9ee4) at 0x2dbfc4
REFCNT = 1
FLAGS = (PADMY,POK,pPOK)
PV = 0x2d316c «321201320273320276320262320276»
CUR = 10
LEN = 12

Когда мы указываем, что исходный код файла в кодировке cp1251 или какой-либо другой то Perl знает, что нужно конвертировать строковые литерали в исходном коде из указанной кодировки во внутренний формат (в данном случае из cp1251 во внутренний формат UTF-8 )и делает это.

Аналогичная проблема определения кодировки возникает при работе с данными получаемыми «извне», например файлов или веб. Рассмотрим каждый из случаев.

Пусть у нас есть файл в кодировке cp866, который содержит слово «Когда» (в текстовом файле слово Когда с большой буквы). Нам необходимо открыть его и проанализировать все строки на предмет нахождения слова «когда». Вот как это сделать правильно (при этом сам исходный код должен быть в utf8).

use strict;
use warnings;
use encoding 'utf8';

open (my $tmp, "<:encoding(cp866)", $ARGV[0]) or die "Error open file - $!";


while (<$tmp>)
{
	if (/когда/i)
	{
		print "OKn";
	}
}

close ($tmp);

Обратите внимание, что в случае если мы не будем использовать «<:encoding(cp866)», и укажем use encoding ‘cp866’ то регулярные выражения будут работать, но только с набором байт и /i работать не будет. Конструкция «<:encoding(cp866)» подсказывает Perl, что данные в текстовом файле в кодировке CP866, поэтому он правильно выполняет перекодировку из CP866 во внутренний формат (CP866 -> UTF8 + включает флаг UTF8).

Следующий пример, мы получаем страницу с помощью LWP::UserAgent. Вот правильний пример, как это нужно делать.

use strict;
use warnings;
use LWP::UserAgent;
use HTML::Entities;
use Data::Dumper;
use Encode;
use Devel::Peek;


my $ua = LWP::UserAgent->new();

my $res = $ua->get("http://wp.local");

my $content;

if (!$res->is_error)
{
	$content = $res->content;
}
else
{
	exit(1);
}

# Только если страница в UTF8, если в cp1251 - $content = decode('cp1251',$content);
# decode конвертирует из utf8 байтов (последовательности октетов) во внутренний формат Perl

$content = decode('utf8',$content);

# теперь переменная $content содержит текст во внутреннем формате, с которым можно работать другим модулям, таким как, например, HTML::Entities, а также строковым функциями, регулярными выражениями и т.д.

decode_entities($content);

Обратите внимание на вызов $content = decode(‘utf8’,$content).

LWP::UserAgent работает с байтами, он не знает, и это не его забота, в какой кодировке страница в однобайтовой cp1251 или в UTF8, мы должны явно указывать это. К сожалению, много литературы содержит примеры на английском языке и для более старых версий Perl, как следствие, в этих примерах нет ничего о перекодировке.

Например, роботы поисковых систем (или другой код), должны не только правильно определять кодировку страниц, не используя заголовки ответов серверов или содержимое HTML тега meta, которые могут быть ошибочными, но и определять язык страницы. Поэтому не думайте, что все вышесказанное должны делать только программисты на Perl.

На примере получения внешних данных с веб сайта мы подошли к рассмотрению использования модуля Encode. Вот его основное API, очень важное в работе любого Perl программиста:

$string = decode(ENCODING, OCTETS[, CHECK]). Выполняет конвертацию набора байтов (октетов) из кодировки ENCODING во внутренний формат Perl;

$octets = encode(ENCODING, STRING[, CHECK]). Выполняет конвертацию из внутреннего формата Perl в набор байтов в кодировке ENCODING.

[$length =] from_to($octets, FROM_ENC, TO_ENC [, CHECK]). Выполняет конвертацию байтов из одной кодировки в другую.

В примере, в котором мы открывали текстовый файл в CP866 мы можем не указывать <:encoding(cp866). Тогда, при каждой операции чтения мы будем получать набор байтов в CP866. Мы можем сами конвертировать их во внутренний формат с помощью

$str = decode(‘cp866’,$str)

и дальше работать с переменной $str.

Кто-то может предположить, что можно в качестве исходного текста программы использовать utf8, а кроме того, перекодировать из cp866 в utf8 и все будет работать как нужно. Это не так, рассмотрим пример (в текстовом файле слово Когда с большой буквы).

use strict;
use warnings;
use encoding 'utf8';
use Encode;

#open (my $tmp, "<:encoding(cp866)", $ARGV[0]) or die "Error open file - $!";
open (my $tmp, "<", $ARGV[0]) or die "Error open file - $!";


while (<$tmp>)
{

	my $str = $_;


	Encode::from_to($str,'cp866','utf8');

	if ($str=~/когда/i)
	{
		print "OKn";
	}
}

close ($tmp);

$str после выполнения Encode::from_to($str,’cp866′,’utf8′) содержит данные в utf8 но как последовательность байтов (октетов) поэтому /i не работает. Чтобы все работало как нужно добавить вызов

$str = decode('utf8',$str)

Конечно же более простым вариантом является одна строка вместо двух

$str = decode(‘cp866’,$str)

Внутренний формат строк Perl, более подробно. Мы уже говорили о том, что регулярные выражения, часть модулей и строковые функции корректно работают со строками, которые хранятся не как набор байтов а во внутреннем представлении Perl. Также было сказано, что в качестве внутреннего формата хранения строк в Perl используется UTF-8. Эта кодировка выбрана не просто так. Часть кодов символов в этой кодировке от 0-127 совпадает с ASCII (US-ASCII), которые как раз отвечают за английский алфавит, вот почему вызов uc для строки с кодами от 0 до 127 отрабатывает правильно и это будет работать в независимости от однобайтовой кодировки в которой сохранен исходный код. Для UTF8 все так же работает корректно.

Однако это еще не все, что нужно знать.

UTF-8 vs utf8 vs UTF8. Кодировка UTF-8 со временем стала более «строгой» (например, наличие определенных символов было запрещено). Поэтому реализация UTF-8 в Perl устарала. Начиная с Perl 5.8.7 “UTF-8” означает современный «диалент» более «строгий», тогда как “utf8” означает более «либеральный старый диалект». Вот небольшой пример

use strict;
use warnings;
use Encode;

# символ который не используется в UTF-8 
my $str = "x{FDD0}";

$str = encode("UTF-8",$str,1); # Ошибка
$str = encode("utf8",$str,1); # OK

Таким образом дефис между “UTF” и “8” важен, без него Encode становится более либеральной и возможно чрезмерно разрешительной. Если выполнить

use strict;
use warnings;
use Encode;

my $str = sprintf ("%s | %s | %s | %s | %sn",
   find_encoding("UTF-8")->name ,
   find_encoding("utf-8")->name ,
   find_encoding("utf_8")->name ,
  	find_encoding("UTF8")->name ,
	find_encoding("utf8")->name 

	);

print $str;

Мы получим следующий результат — utf-8-strict | utf-8-strict | utf-8-strict | utf8 | utf8.

Работа с консолью. Рассмотрим консоль ОС семейства Windows. Как все знают в Windows есть понятие кодировки Unicode, ANSI, OEM. API самой ОС поддерживает 2-а типа функций, которые работают с ANSI и Unicode (UTF-16). ANSI зависит от локализации ОС, для русской версии используется кодировка CP1251. OEM – это кодировка, которая используется для операций ввода/вывода консоли, для русскоязычной Windows – это CP866. Эта та кодировка, которая была предложена в русскоязычной MS-DOS, а позже перекочевала и в Windows для обратной совместимости со старым ПО. Вот почему, следующая программа в utf-8

use strict;
use warnings;
use Encode;

use encoding 'utf8';


my $str = 'Привет мир';

print $str;

не выведет заветной строки, мы же выводим UTF8, когда нужно CP866. Здесь нужно использовать модуль Encode::Locale. Если просмотреть его исходный код то можно увидеть, что для ОС Windows он определяет кодировку ANSI и консоли и создает алиасы console_in, console_out, locale, locale_fs. Все что остается сделать это немного изменить нашу программу.

use strict;
use warnings;
use Encode::Locale;
use Encode;

use encoding 'utf8';

my $str = 'Привет мир';

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

print $str;

P.S. Эта статья для тех, кто начинает работать с Perl и может быть она немного шереховата. Готов выслушать и реализовать пожелания относительно расширения статьи.

Опять в php, использование iconv дает сбои.

function cp1251_to_utf8 ($txt)  {
    $in_arr = array (
&nbsp;       chr(208), chr(192), chr(193), chr(194),
     &nbsp;  chr(195), chr(196), chr(197), chr(168),
     &nbsp;  chr(198), chr(199), chr(200), chr(201),
     &nbsp;  chr(202), chr(203), chr(204), chr(205),
     &nbsp;  chr(206), chr(207), chr(209), chr(210),
     &nbsp;  chr(211), chr(212), chr(213), chr(214),
     &nbsp;  chr(215), chr(216), chr(217), chr(218),
     &nbsp;  chr(219), chr(220), chr(221), chr(222),
     &nbsp;  chr(223), chr(224), chr(225), chr(226),
     &nbsp;  chr(227), chr(228), chr(229), chr(184),
     &nbsp;  chr(230), chr(231), chr(232), chr(233),
     &nbsp;  chr(234), chr(235), chr(236), chr(237),
     &nbsp;  chr(238), chr(239), chr(240), chr(241),
     &nbsp;  chr(242), chr(243), chr(244), chr(245),
     &nbsp;  chr(246), chr(247), chr(248), chr(249),
     &nbsp;  chr(250), chr(251), chr(252), chr(253),
     &nbsp;  chr(254), chr(255)
    );

    $out_arr = array (
     &nbsp;  chr(208).chr(160), chr(208).chr(144), chr(208).chr(145),
     &nbsp;  chr(208).chr(146), chr(208).chr(147), chr(208).chr(148),
     &nbsp;  chr(208).chr(149), chr(208).chr(129), chr(208).chr(150),
     &nbsp;  chr(208).chr(151), chr(208).chr(152), chr(208).chr(153),
     &nbsp;  chr(208).chr(154), chr(208).chr(155), chr(208).chr(156),
     &nbsp;  chr(208).chr(157), chr(208).chr(158), chr(208).chr(159),
     &nbsp;  chr(208).chr(161), chr(208).chr(162), chr(208).chr(163),
     &nbsp;  chr(208).chr(164), chr(208).chr(165), chr(208).chr(166),
     &nbsp;  chr(208).chr(167), chr(208).chr(168), chr(208).chr(169),
     &nbsp;  chr(208).chr(170), chr(208).chr(171), chr(208).chr(172),
     &nbsp;  chr(208).chr(173), chr(208).chr(174), chr(208).chr(175),
     &nbsp;  chr(208).chr(176), chr(208).chr(177), chr(208).chr(178),
     &nbsp;  chr(208).chr(179), chr(208).chr(180), chr(208).chr(181),
     &nbsp;  chr(209).chr(145), chr(208).chr(182), chr(208).chr(183),
     &nbsp;  chr(208).chr(184), chr(208).chr(185), chr(208).chr(186),
     &nbsp;  chr(208).chr(187), chr(208).chr(188), chr(208).chr(189),
     &nbsp;  chr(208).chr(190), chr(208).chr(191), chr(209).chr(128),
     &nbsp;  chr(209).chr(129), chr(209).chr(130), chr(209).chr(131),
     &nbsp;  chr(209).chr(132), chr(209).chr(133), chr(209).chr(134),
     &nbsp;  chr(209).chr(135), chr(209).chr(136), chr(209).chr(137),
     &nbsp;  chr(209).chr(138), chr(209).chr(139), chr(209).chr(140),
     &nbsp;  chr(209).chr(141), chr(209).chr(142), chr(209).chr(143)
    );

    $txt = str_replace($in_arr,$out_arr,$txt);
&nbsp;   return $txt;
}

Источник

     Начал с энтузиазмом заниматься Perl’ом и сразу же стал биться как рыба об лёд с не читабельным отображением текстовых данных (строк) в консоле (командной строке).
Кое как всё же я нашёл лунку в которую нырнуть.

Получилось разрешить эту проблему, сопоставив сведения из двух типов источников. Один относился к теме перекодировки шрифтов в Perl’е (ссылка на источник), другой, к смене кодировки в консоле (ссылка на источник). Из первого узнаём как перекодировать в Перле и как открыть файл в определённой кодировке, из второго, в какую кодировку использовать при выводе текстовых данных в консоле.

   Итак, чтобы текст на экране консоли был читабельным можно в настройках программы в которой пишется программный код Перл установить DOS кодировку (числовой код 866). Тогда весь текст кода будет в той же кодировки, что используется в консоли. Однако это приведёт к к проблемам с использованием русских букв в названии файлов, а также при записи русского текста во внешние файлы.
    Поэтому более удобно использовать две функции decode и encode из модуля Encode. Обе функции имеют два параметра: decode(«кодировка», «текст»), encode(«кодировка», «текст»). Первая переводит текст из произвольной кодировки в некий внутренний формат перла, а вторая из внутреннего в любой другой. При этом перевести кодировку напрямую из произвольного формата в любой другой не получится.

Вот как это может выглядеть:

use Encode qw(decode encode);
my $text = ‘Русские буквы в системной кодировке windows 1251’;
      $text = decode(«cp1251», $text);
      $text = encode(«cp866», $text);
print $text;

Для функции decode указывается параметр «cp1251» кодировки в которой написан текст. Если это unicode, то указывается «utf8» (или 4, 16, 32, 64). Для функции encode указывается кодировка «cp866» в которую будет перекодирован текст для вывода в консоли.

Можно использовать более короткую запись:

use Encode qw(decode encode);
my $text = ‘Русские буквы в кодировке unicode UTF8’;
print encode(«cp866», decode(«utf8»$text));

Теперь пример как делается чтение из файла с произвольной кодировкой.

open(my $fh, ‘<:encoding(cp1251)’, «data.txt»);

Т.е. просто указывается параметр encoding(cp1251), где cp1251 кодировка текста в файле. При этом текст кодируется в некой внутренней перловской кодировке.
Данную операцию нужно проделывать обязательно для того, чтобы потом иметь возможность перекодировать текст в любую другую кодировку. При этом, чтобы в дальнейшем корректно отобразить в консоле текст полученный таким образом из файла, нужно использовать уже только функцию encode.

Добавлено через 4 минуты
Добрый день!
volodin661, спасибо, что ответили.

Последовал вашему совету. К сожалению, результат всё-таки отличается от ожидаемого.
Сделал dump переменной, заданной в коде скрипта.

Perl
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/perl -w
use Devel::Peek;
$str = "320300304310323321 316320300312313";
print Dump ($str);
 
# ./script.pl
SV = PV(0x1dceb78) at 0x1ded120
  REFCNT = 1
  FLAGS = (POK,pPOK)
  PV = 0x1de7970 "320300304310323321 316320300312313"0
  CUR = 12
  LEN = 16

Затем сделал дамп внешней переменной, обработанной вашим методом.

Perl
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/perl -w
use Devel::Peek;
$str = pack 'C*', map oct, $ARGV[0] =~ /\(d{3})/g;
print Dump ($str);
 
# ./convert.pl "320300304310323321 316320300312313"
SV = PV(0x1c1db78) at 0x1c3c110
  REFCNT = 1
  FLAGS = (POK,pPOK)
  PV = 0x1c5e7e0 "320300304310323321316320300312313"0
  CUR = 11
  LEN = 48

1) Регулярка сожрала пробел, из-за чего CUR меньше на единицу. К сожалению, не очень силён в regexp, поэтому пока не могу сообразить, как сделать так, чтобы пробел оставался.
2) Во втором случае длина в байтах (LEN) в 3 раза больше, чем в первом.

27

января

2021

Perl

Лучшая статья по юникоду в перле, которую встречал, и которая, к сожалению, осталась только на веб-архиве:
http://www.nestor.minsk.by/sr/2008/09/sr80902.html.

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

В перл-чатике пишут:

она кстати уже устарела как минимум в нескольких местах


Вступление

Для многих не секрет, что на данный момент восьмибитовые кодировки в значительной мере устарели. Основная тому причина — невозможность вместить в одну кодировку достаточное количество символов. Когда необходимо поддерживать ограниченное количество групп символов (к примеру, кириллицу и латиницу), мы можем воспользоваться koi8-r, cp1251 или iso-8859-5. Но если возникает потребность использовать несколько языков или специальные символы, то одной емкости одной кодировки становится недостаточно. Вот тут и может помочь использование юникода.

Для начала определим неясности с терминологией. Многие так или иначе сталкивались с понятиями Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4, и все они так обозначали юникод. Что же значит каждый из них?

Unicode — Character Encoding Standart

Стандарт для цифрового представления символов использующихся во всех языках. Поддерживается и развивается Консорциумом юникода (unicode.org).

UCS — Universal Character Set

Международный стандарт ISO/IEC 10646, который идентичен стандарту Unicode.

UTF — Unicode (or UCS) Transformation Format

Способ представления символов Unicode в виде последовательности целых положительных чисел. — UTF-8, UTF-16, UTF-32 — различные UTF-трансформации, которые оперируют числами, занимающими соответственно 8, 16 и 32 бита. В UTF-8 минимальный размер символа — один октет (байт), максимальный — шесть. В UTF-16 минимальный размер — два октета, максимальный — четыре. В UTF-32 любой символ представляется в виде четырех октетов.

UCS-2, UCS-4

Способы кодирования по ISO/IEC 10646. Универсальный набор символов, закодированный двумя или четырьмя октетами (байтами) соответственно. UCS-2 полностью входит в UTF-16, но в UTF-16 есть составные символы (из четырех октетов), которые не входят в UCS-2. UCS-4 тождественна UTF-32.

Итог: Unicode — набор символов, определенным образом упорядоченных: каждому символу поставлена в соответствие кодовая позиция. Любая из кодировок UTF — это представление символов Unicode в виде последовательности чисел. Поэтому, говоря, например, о переводе проекта на юникод, в большинстве случаев мы подразумеваем поддержку какой-либо из трансформаций. При необходимости за уточнениями можно обратиться к словарю терминов www.unicode.org/glossary и документации на сайте unicode.org.

С точки зрения программиста наиболее комфортной для работы выглядит UTF-32. В этой кодировке мы имеем постоянный размер символа. Но с практической точки зрения, в простейшем случае, при использовании только латиницы, мы получаем затраты по объему в четыре раза (по сравнению с обычной восьмибитовой кодировкой). Вторая проблема при переходе на UTF-32 — необходимость полной замены исходного кода и всех текстов. Поэтому в качестве переходной альтернативы была разработана кодировка UTF-8. Ее особенность состоит в том, что часть символов, попадающая в ACSII, сохраняет свои коды и представления. В результате если у нас был исходный код, написанный в latin-1, то при переходе на UTF-8 ничего не нужно менять. На сегодня наибольшую популярность получила именно кодировка UTF-8, как наиболее комфортная для плавного перехода. Эту кодировку поддерживает подавляющее большинство программного обеспечения и средств разработки.

Аргументацией против перехода на юникод достаточно часто выступает ложное мнение о том, что перл его не поддерживает, или поддерживает недостаточно хорошо. Чаще всего такое мнение складывается из-за неправильного использования имеющихся средств. Миф о неумении перла работать с юникодом я и постараюсь развеять. Также аргументом может выступать излишний объем по сравнению с восьмибитовой кодировкой. Но если оценить, то получается что количество текста, который действительно увеличивается в объеме, по сравнению с общим объемом кода проекта, крайне незначителен. Под задачей перехода на юникод (а конкретно — на кодировку UTF-8) я буду понимать следующие требования: исходный код в UTF-8, корректная работа встроенных функций и регулярных выражений с использованием возможностей юникода, а также корректное взаимодействие с окружением.

Корень зла или суть проблемы

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

Возьмем любой текстовый редактор с поддержкой UTF-8, напишем в нем простой кириллический символ А и сохраним в файл. Посмотрим шестнадцатеричное представление этого файла. В нем будет два байта: 0xD090. Это и есть представление символа CYRILLIC CAPITAL LETTER A, закодированное при помощи UTF-8. Но считывая в программе на Perl UTF-данные из различных источников, мы получаем совершенно различные их представления. Если взять Data::Dumper и сделать дамп таких строк, то возможны следующие варианты:

"А"
"x{410}"
"x{d0}x{90}"

В первом варианте мы имеем строку, про которую перл не знает, что это строка. Для него это набор байт. Во втором случае мы имеем юникодный символ с кодом 0410. Если обратиться к таблице символов Unicode, то мы узнаем, что это и есть CYRILLIC CAPITAL LETTER A. Третий случай — это два символа Unicode с кодами 00d0 и 0090. Первая строка — набор октетов без флага. Вторая строка — юникодный символ, на этой строке флаг включен. Третий случай — это с нашей точки зрения «поломанные» данные. На строке октетов был принудительно включен UTF-флаг, при этом каждый октет стал отдельным символом. В большинстве задач мы будем стремиться ко второму варианту.

Для начала нужно определиться, как мы можем преобразовать данные из одного представления в другой. Для данной задачи есть как минимум два хороших способа, каждый со своими плюсами и минусами — utf8::* и Encode::*.

utf8::* хорошо использовать, когда исходные данные пришли в нашу программу уже в кодировке UTF-8.

utf8::downgrade снимает флаг со строки:

utf8::downgrade("x{d0}x{90}") :'А'

utf8::upgrade выставляет флаг на строку:

utf8::upgrade('А') : "x{d0}x{90}"

utf8::encode преобразует символы в октеты, снимает флаг:

utf8::encode("x{410}") : "А"

utf8::decode преобразует октеты в символы, выставляет флаг (флаг выставляется только в том случае, если строка содержит символы с кодами, большими 255; см. perunicode).

utf8::decode("А") : "x{410}"

utf8::is_utf8 проверяет состояние флага. возвращает 1 в том случае, если на строке установлен utf-флаг:

utf8::is_utf8("x{410}") = 1
utf8::is_utf8("x{d0}x{90}") = 1
utf8::is_utf8('А') = undef

Для использования данных функций не нужно выполнять use utf8; этот модуль всегда загружен и он не экспортирует свои функции, так что указывать utf8:: придется явно.

Encode::* хорошо использовать, когда исходные данные имеются в разных кодировках. Также данный модуль хорош для различных преобразований между кодировками. Некоторые функции аналогичны utf8::*.

_utf8_off снимает флаг со строки. _utf8_on выставляет флаг на строку. encode_utf8 преобразует символы в октеты, снимает флаг. decode_utf8 преобразует преобразует октеты в символы, выставляет флаг (см. комментарий по поводу utf8::decode). encode преобразует символы в октеты указанной кодировки, снимает флаг:

encode("cp1251","x{410}") = chr(0xC0)

decode преобразует октеты указанной кодировки в символы, выставляет флаг (см. упомянутый комментарий).

decode("cp1251",chr(0xC0)) = "x{410}"
decode("MIME-Header", "=?iso-8859-1?Q?Belgi=eb?=")
= "Belgix{451}" (Belgiё)

Теперь нам известно, как преобразовывать данные. Попробуем воспользоваться этим знанием на практике.

Исходный код

Напишем простую программу в кодировке UTF-8, запустим ее и посмотрим на вывод.

$_ = "А";

print Dumper $_; # "А"
print lc; # А
print /(w)/; # nothing
print /(а)/i; # nothing

Как мы видим, строка оказалась без флага, встроенные функции (lc) не работают, регулярные выражения не работают. Воспользуемся уже известной нам функцией utf8::decode:

$_ = "А";
utf8::decode($_);
print Dumper $_; # "x{410}"
print lc; # а
print /(w)/; # А
print /(а)/i; # nothing ?

Теперь эта строка юникодная, встроенные функции работают, первое регулярное выражение работает. Что же не так со вторым? Проблема в том, что символ, находящийся в регулярном выражении, — тоже кириллический, и он остался без флага. Возможны достаточно сложные варианты, которые я встречал в различном коде:

print /(x{430})/i;

или

use charnames ':full';
print /(N{CYRILLIC SMALL LETTER A})/i;

или даже

$a = ''.qr/(а)/i;
utf8::decode($a);
print /$a/;

Но есть более удобный способ. Директива use utf8 «выполняет» utf8::decode( <SRC> ).

use utf8;
$_ = "А";
print Dumper $_; # "x{410}"
print lc; # а
print /(w)/; # А
print /(а)/i; # А

Все работает, никакой черной магии.

Также отмечу существование похожей директивы use encoding ‘utf8’. Она делает почти то же самое, но use encoding, во-первых, не является лексической директивой (ее действие не ограничивается блоком, и при выходе из блока сохранится), во-вторых, обладает «магическим» поведением, сходным с source filters. В общем случае использование use encoding для utf-8 не рекомендуется.

Ввод и вывод

Итак, все работает, но при этом мы получаем странное предупреждение, которого раньше не было:

Wide character in print at...

Проблема заключается в том, что перл не знает, поддерживается ли utf-8 данным дескриптором. Мы можем ему об этом сообщить:

binmode(STDOUT,':utf8'); # binmode используется
# с уже открытым дескриптором

Точно так же возможно указать, что некоторый, открываемый нами файл — в кодировке UTF-8 через так называемые PerlIO Layers:

open my $f, '<:utf8', 'file.txt';

Также мы можем снять флаг со строки перед выводом (utf8::encode) и отдать дескриптору поток байтов. Но есть простая директива use open, которая поможет решить данные вопросы:

use open ':utf8'; # только для файлов
use open qw(:std :utf8); # файлы и STD*
# подробнее - perldoc open

Еще мы можем при помощи PerlIO указать поддерживаемую кодировку, если, например, хотим писать некоторый лог-файл в cp1251.

binmode($log, ':encoding(cp1251)');

Строки с флагом при работе с этим дескриптором будут автоматически переведены в указанную кодировку средствами PerlIO.

В результате вышеизложенного мы можем делать даже так:

use strict; use utf8; use open qw(:std :utf8);
my $все = "тест";
sub печатать (@) { print @_ }
печатать $все;

Для простоты можно сделать очень простой «прагматический» модуль, который будет для нас выполнять сразу все три действия, чтоб не писать трижды use:

package unistrict;
use strict(); use utf8(); use open();
sub import {
$^H |= $utf8::hint_bits;
$^H |= $strict::bitmask{$_} for qw(refs subs vars);
@_ = qw(open :std :utf8);
goto &open;::import;
}

И в дальнейшем:

use unistrict;

Что касается самого перла — это все, что нужно знать, для того, чтобы успешно использовать utf-8. Но еще мы рассмотрим на примерах, как скорректировать работу того или иного модуля, если он не соответствует нашим требованиям.

Окружение

Под окружением понимаются различные модули (как входящие в стандартную поставку, так и с CPAN), с которыми взаимодействует наше приложение. То есть, например, модуль бизнес-логики считается частью приложения и подразумевается, что он работает в подготовленной среде с корректными строками (с флагом), а вот модули, отвечающие за ввод и вывод — это окружение.

DBI.pm

По умолчанию большинство DBD возвращают данные без флага.

my $dbh = DBI->connect('DBI:mysql:test');
($a) = $dbh->selectrow_array('select "А"');
print '$a = ',Dumper $a; # 'А'

Но опять же, для большинства DBD уже сделана поддержка utf-8.

DBD::mysql : mysql_enable_utf8 (требует DBD::mysql >= 4.004)
DBD::Pg : pg_enable_utf8 (требует DBD::Pg >= 1.31)
DBI:SQLite : unicode (требует DBD::SQLite >= 1.10)

Пример использования:

my $dbh = DBI->connect('DBI:Pg:dbname=test');
$dbh->{pg_enable_utf8} = 1;
($a) = $dbh->selectrow_array('select "А"');
print '$a = ',Dumper $a; # "x{410}"

Template Toolkit

В TT заявлена поддержка UTF-8, но при этом имеются некоторые особенности. Чтобы файл шаблона был воспринят и перекодирован в строки с флагом, в начале каждого файла должен быть так называемый BOM-header. BOM расшифровывается как Byte Order Mark (порядок следования байт). Но получается, что BOM имеет значение только для UTF-16 и UTF-32, у которых минимальная единица — два или четыре октета (байта). Для UTF-8 BOM по спецификациям признается опциональным. А если учесть, что в шелл-скриптах наличие BOM перед шебангом (например, #!/usr/bin/perl) «ломает» скрипт, то его использование зачастую вообще сомнительно. Для UTF-8 BOM — это три байта 0xEFBBBF или x{feff}. Соответственно, если вы хотите, чтобы TT читал файлы без BOM, но при этом все корректно работало, предлагаю воспользоваться одним из двух вариантов решения проблемы:

package Template::Provider::UTF8;
use base 'Template::Provider';
use bytes;
our $bom = "x{feff}"; our $len = length($bom);
sub _decode_unicode {
my ($self,$s) = @_;
# if we have bom, strip it
$s = substr($s, $len) if substr($s, 0, $len) eq $bom;
# then decode the string to chars representation
utf8::decode($s);
return $s;
}
package main;
my $context = Template::Context->new({
LOAD_TEMPLATES => [ Template::Provider::UTF8->new(), ] });
my $tt = Template->new( 'file',{ CONTEXT => $context }, ... );

или

package Template::Utf8Fix;
BEGIN {
use Template::Provider;
use bytes; no warnings 'redefine';
my $bom = "x{feff}"; my $len = length($bom);
*Template::Provider::_decode_unicode = sub {
my ($self,$s) = @_;
# if we have bom, strip it
$s = substr($s, $len) if substr($s, 0, $len) eq $bom;
# then decode the string to chars representation
utf8::decode($s);
return $s;
}
}

package main;
use Template::Utf8Fix; # 1 раз в любом месте проекта
my $tt = Template->new( 'file', ... );

CGI.pm

Наиболее часто используемым модулем при разработке CGI-приложений начального уровня является CGI.pm. У него есть много недостатков (подробнее можно ознакомиться в докладе Анатолия Шарифулина c YAPC::Russia 2008: http://event.perlrussia.ru/yr2008/media/video.html), но тем не менее, модуль крайне популярен. Рассмотрим, что нужно сделать, чтобы получать от него переданные аргументы в виде строк с флагом.

Для версии ниже 3.21 рабочим способом может быть только переопределение метода param (аналогично примерам про TT). Начиная с версии 3.21 до 3.31 нужно указать кодировку раньше, чем будет обращение к методу param():

# Запрос: test.cgi?utf=%d0%90
use CGI 3.21;

$cgi->charset('utf-8');
$a = $cgi->param('utf');
print $cgi->header();
print Dumper $a; # "x{410}"

Начиная с версии 3.31 данный способ перестает работать, но появляется другой способ: указание тега :utf8 при импорте:

# Запрос: test.cgi?utf=%d0%90
use CGI 3.31 qw(:utf8);

$a = $cgi->param('utf');
print $cgi->header();
print Dumper $a; # "x{410}"

Замечания

Следует также обратить внимание на термины, касающиеся UTF-8. Официальное название кодировки — UTF-8. На вебе имя часто встречается в нижнем регистре — utf-8. В перле кодировка называется utf8. Различия между ними следующие:

* utf8 — unrestricted UTF-8 encoding. Нестрогая UTF-8. Это может быть любая последовательность чисел в диапазоне 0..FFFFFFFF.

* utf-8 — strict UTF-8 encoding. Строгая UTF-8. Это может быть только некоторая последовательность чисел из диапазона 0..10FFFF, которая регламентирована стандартом Unicode (см. unicode.org/versions/Unicode5.0.0).

Таким образом:

— utf-8 является подмножеством utf8;

— перл поддерживает любые, в том числе так называемые ill-formed последовательности.

Также хочу обратить внимание, что в регулярных выражениях метасимвол w работает по-разному в зависимости от контекста. Так, при использовании qr/[w]/, метасимвол будет воспринят в байтовой семантике (так как в случае перечисления всех символов класса w из таблицы Unicode данный паттерн был бы крайне объемным и в результате — медленным).

Проблемы

В режиме utf-8 не используйте локали (см. perldoc perlunicode). Их использование может привести к неочевидным результатам.

Встроенные функции работают значительно медленнее на строках с флагом.

Также встречаются крайне странные и неприятные ошибки:

use strict;use utf8;
my $str = 'тест'; my $dbs = 'это тестовая строка';
for ($str,$dbs) {
sprintf "%-8s : %-8sn", $_, uc;
print ++$a;
}
for ($dbs,$str) {
sprintf "%-8s : %-8sn", $_, uc;
print ++$a;
}

Результат:

123panic: memory wrap at test.pl line 12.

или

use strict;
my $str = "x{442}";
my $dbs = "x{43e} x{442}x{435}x{441}".
"x{442} x{43e}x{432}x{430}".
"x{44f} x{441}x{442}x{440}";
sprintf "%1sn",lc for ($dbs,$str);

Результат:

Out of memory!

Также есть не совсем адекватное поведение:

use strict; use utf8;
print "1234567890123456780n";
printf "%-4.4s:%-4.4sn", 'itstest','itstest';
printf "%-4.4s:%-4.4sn", "этотест","этотест";

Результат:

1234567890123456780
itst:itst
этот :этот

Данные проблемы связаны с ошибками в реализации встроенной функции sprintf. Так что отформатировать вывод юникодных строк при помощи %*.*s в sprintf не получится. Решение проблемы находится в стадии разработки.

Кроме того

Расскажу о паре интересных вещей, которые можно сделать, когда у нас имеются строки с UTF-флагом. Для начала упомяну достаточно интересный модуль Text::Unidecode:

use utf8;
use Text::Unidecode;
print unidecode "x{5317}x{4EB0}";
# That prints: Bei Jing
print unidecode "Это тест";
# That prints: Eto tiest

Данный модуль позволяет получить фонетическую транслитерацию большинства символов юникода в ASCII. Кстати, этот модуль используется на pause.perl.org при транслитерировании имен, содержащих символы, выходящие за рамки latin-1.

Еще одно достаточно интересное применение юникоду я нашел в проекте, который целиком работает на koi8-r. Нижеприведенный пример показывает, как можно пользоваться возможностями регулярных выражений с функционалом юникода, не переводя весь проект на UTF-8:

sub filter_koi ($) {
# передем koi8-r в строку
local $_ = Encode::decode('koi8-r', shift);
# заменим все html-entity
# соответствующими символами юникода
s{&#(d+);}{chr($1)}ge;
# проведем некоторые замены
# пробельные символы пробелом
s{(?:p{WhiteSpace}|p{Z})}{ }g;

# все кавычки – двойными
s{p{QuotationMark}}{"}g;

# минусы, дефисы, тире и т.п. - дефисами
s{p{Dash}}{-}g;

# символ переноса тоже дефисом
s{p{Hyphen}}{-}g;

# символ троеточия тремя точками
s{x{2026}}{...}g;

# символ номера заменяем на N
s{x{2116}}{N}g;
# Вернем строку обратно в кодировке koi8-r
return Encode::encode('koi8-r',$_);
}

Как известно, на странице, отданной, к примеру, в кодировке koi8-r, есть возможность ввести символы, не входящие в эту кодировку. Они приходят на серверную сторону в виде html-сущностей &#….; Хранить с ними данные неудобно, к тому же не всегда в качестве вывода используется HTML. Данная функция преобразует отсутствующие в кодировке символы в некоторые визуальные аналоги. Для поиска и замены используются классы символов (character class), такие как, например, QuotationMark. В него входят всевозможные кавычки из всех языков.

Ответы на многие вопросы можно найти в документации к перлу:

perldoc perluniintro
perldoc perlunicode
perldoc Encode
perldoc encoding

Автор с удовольствием ответит на любые вопросы по работе с юникодом в перле по электронной почте или в рассылке группы Moscow.pm.

Владимир Перепелица, Москва, mons@cpan.org


Ещё из перл-чатика по теме, начиная с коммента https://t.me/modernperl/178819:

Oleg Pronin, [22 Jan 2021 02:05:22]:

Я заметил, что многие путаются в utf8 сильно и получают wide character или кракозябры (двойной энкод etc). Хотя тема на самом деле дико простая.

Все что нужно знать:

1) у перла строка может представлять 2 сущности — byte stream и string. is_utf8 говорит какой из режимов включен. В режиме строки просто некоторые функции (substr, lenght И так далее) меняют свой behaviour с побайтового на посимвольный, что занимает дополнительное процессорное время естественно, потому что в пасяти все равно utf8.

2) внутренее представление не изменяется никак при переходе из одного режима в другой. decode_utf8 не делает нихрена кроме как проверяет что там нет инвалидных последовательностей utf8 и переключает режим.

3) чтобы не запутаться где какой режим, простое правило — все что приходит извне (чтение из сокета, файла, stdin, …) — всегда байтовое. Соответсвенно все что туда отправляется должно быть тоже (иначе будет wide character, однако изза совпадения внутреннего прдставления, все будет работать).

4) еще простое правило — функция is_utf8 за крайне редкими исключениями не должна использоваться. Вы всегда должны знать где у вас байты а где символы. Для этого как правило сразу на входе декодят и в приложении используются символы (кроме случая когда данные бинарные должны быть), и энкодятся в самом конце перед записью в канал.

5) некоторые библиотеки избавляют вас от необходимости энкодить и декодить. Например json::xs::decode_json() ожидает от вас бинарный поток и порождает структуру с символами. А на encode ожидает символы и порождает байты. Обычно это интуитивно логично и ожидаемо.

В случаи работы через обьект JSON::XS->new->utf8->

Метод utf8 как раз заставляет его порождать на декод и ожидать на энкод символы. Если его не писать то в структуре которую вернет декод будут тоже байты (лучше так не делать, только если точно уверены что там только инглиш). Но на вход декод и выход энкод всегда байты, это не меняется, иначе бессмыслица.

Еще например template toolkit аналогично, считывает с диска байты, декодит в символы, ожидает от вас переменные в символах, рендерит и энкодит в байты итоговый результат.

6) многие считают что utf8 это символы. Нет. Это байты. Utf8 это способ сериализации кодов юникод, то есть бинарный режим. В перле вообще нет настоящего символьного режима. Он чисто виртуальный (выполняется в рантайме, разбирая каждый байт). Например когда строке в режиме символов говорят substr на 10й символ, перл не может херак и встать на 10й символ сразу как в бинарном режиме, ему придется линейно идти от начала строки отсчитывая символы из байтов.

Настоящий символьный режим был бы если бы перл представлял строку в памяти как utf32 — коды юникода (и памяти занимало бы в 2-4 раза больше, но работало бы быстрее). В перле в режиме символов всегда бинарный режим utf8 под капотом, и виртуальное рантайм эмуляция.

И да еще забыл

7) use utf8;

Не имеет ничего общего с рантайм перекодировками данных, это просто хелпер который автомат делает decode_utf8 на все литералы написанные в этом файле / области видимости. Таким образом все литералы становятся строками. Но вычитанные в рантайме нет!

Обычно мало захардкоженных не инглиш строк в программе, поэтому он не особо полезен.

  • NAME
  • SYNOPSIS
  • DESCRIPTION
    • INTRODUCTION
    • METHODS
      • utf2cyrillic($str,$to)
    • PACKAGE VARIABLES
      • $CCCP::Encode::Entities
      • $CCCP::Encode::ToText
      • $CCCP::Encode::CharMap
      • $CCCP::Encode::Regexp
  • OVERHEAD
  • SEE ALSO
  • AUTHOR

NAME

CCCP::Encode — Perl extension for character encodings from utf-8 to any cyrillic (koi8-r, windows-1251, etc.)

Version 0.03

SYNOPSIS

    use CCCP::Encode;
    
    $CCCP::Encode::ToText = 0; # default
    $CCCP::Encode::Entities = 'xml'; # default    
    my $str = "если в слове 'хлеб' поменять 4 буквы, то получится — ПИВО";
    print CCCP::Encode->utf2cyrillic($str,'koi8-r');
    # output in koi8-r:
    # если в слове 'хлеб' поменять 4 буквы, то получится — ПИВО
    
    $str = "Иероглифы: 牡 マ キ グ ナ ル フ";
    print CCCP::Encode->utf2cyrillic($str,'windows-1251');
    # output in windows-1251:
    # Иероглифы: 牡 マ キ グ ナ ル フ 
        
        --------------------------
        
        $CCCP::Encode::ToText = 0; # default
        $CCCP::Encode::Entities = 'html';            
    print CCCP::Encode->utf2cyrillic($str,'koi8-r');
    # output in koi8-r:
    # если в слове 'хлеб' поменять 4 буквы, то получится — ПИВО

    $str = "Иероглифы: 牡 マ キ グ ナ ル フ";
    print CCCP::Encode->utf2cyrillic($str,'windows-1251');
    # output in windows-1251:
    # Иероглифы: 牡 マ キ グ ナ ル フ
    
    --------------------------
         
    $CCCP::Encode::ToText = 1;
    print CCCP::Encode->utf2cyrillic($str,'koi8-r');
    # output in koi8-r:
    # если в слове 'хлеб' поменять 4 буквы, то получится -- ПИВО  
    
    $CCCP::Encode::CharMap = {"x{2014}" => '-'};
    print CCCP::Encode->utf2cyrillic($str,'koi8-r');
    # output in koi8-r:
    # если в слове 'хлеб' поменять 4 буквы, то получится - ПИВО  
    

DESCRIPTION

This module convert utf string to cyrillic in two mode:

  • convert to cyrillic string with html entites,

  • convert to cyrillic string to only plain/text character.

By default for unknown character used HTML::Entities for html entites and for plain/text encoding used Text::Unidecode. You can override the map to encoding for any character. And can override regexp for replace character.

INTRODUCTION

Ajax library (on frontend) send data in utf-8. If you have backend on koi8-r, windows-1251, etc. You have problem:

    use Encode;
    ...
    my $data = $post->param('any');
    # $data = "если в слове 'хлеб' поменять 4 буквы, то получится — ПИВО";
    Encode::from_to($data,'utf-8','koi8-r');
    print $data;
    # output:
    # если в слове 'хлеб' поменять 4 буквы, то получится ? ПИВО

Method from_to from module Encode replace uncnown character on ‘?‘. This data go to save in your database. And you write a guano-magic code for fixing this problem. All developers, who have database not in utf, known about this problem.

And another case:

Getting data from rss-channels in utf-8 and saving in cyrillic database (for example mysql with default charset koi8-r or windows-1251).

CCCP::Encode fix this problem.

METHODS

utf2cyrillic($str,$to)

$str target string. $to encoding name, analogue $to in Encode::from_to($str,'utf-8',$to)

PACKAGE VARIABLES

$CCCP::Encode::Entities

Ignored if $CCCP::Encode::ToText is true. Default value ‘xml’. ‘xml’ mode — replace all uncnown character in traget charset to valid xml numeric entities (i.e. —). ‘html’ mode — replace all uncnown character in traget charset to html numeric entities (i.e. —).

$CCCP::Encode::ToText

Default is false.

If $CCCP::Encode::ToText is false, when utf2cyrillic return decode string whis replace uncnown character from you definition (see $CCCP::Encode::CharMap) or html entities from HTML::Entities.

If $CCCP::Encode::ToText is true, when utf2cyrillic return decode string in plain/text format whis replace uncnown character from you definition (see $CCCP::Encode::CharMap) or used Text::Unidecode.

$CCCP::Encode::CharMap

Default is empty hashref.

You can custom define map for any characters. This is wery flexible if you need custom replace (different of HTML::Entities or Text::Unidecode). Example:

    $CCCP::Encode::CharMap = {
        "x{2014}" => '-',
        "x{2015}" => 'foo'
    };

$CCCP::Encode::Regexp

By default value is [^p{Cyrillic}|p{IsLatin}|p{InBasic_Latin}] — replace any character which not in Cyrillic or Latin map exist. You can override this expression.

See more on http://www.regular-expressions.info/unicode.html

OVERHEAD

    CCCP::Encode with $CCCP::Encode::Entities eq "html":  
        2 wallclock secs ( 1.63 usr +  0.01 sys =  1.64 CPU) @ 60975.61/s (n=100000)
    
    CCCP::Encode with $CCCP::Encode::Entities eq "xml":  
        3 wallclock secs ( 2.49 usr +  0.00 sys =  2.49 CPU) @ 40160.64/s (n=100000)
    
    CCCP::Encode with $CCCP::Encode::ToText eq "1":  
        4 wallclock secs ( 3.85 usr +  0.02 sys =  3.87 CPU) @ 25839.79/s (n=100000)
            
    Encode::from_to(...) :  
        2 wallclock secs ( 1.93 usr +  0.01 sys =  1.94 CPU) @ 51546.39/s (n=100000)

SEE ALSO

  • Encode

  • Text::Unidecode

Ivan Sivirinov

в PHP все делалось так:


$b = iconv("UTF-8","windows-1251", $a );

В Perl делаеться так:


use Encode;
$b = Encode::encode('cp1251',$a);

Пояснения:
Из коробки Perl не умеет конвертировать, поэтому оптимально подключить лишь Encode. Кстати можно было написать и так:


use Encode qw(encode);
$b = encode('cp1251',$a);

В этому случае код перекодировки стал более приятным, однако потерянный неймспейс в дальнейшем мог бы спровоцировать непонятки.

Ну и как видно название кодировочки разное. В PHP iconv универсальна, а в случае с Perl не совсем ясно из какой кодировки происходит конверт, поэтомувообще гвооря правильнее написать так:


$title = Encode::encode('cp1251',$a) if Encode::is_utf8 ($a);

В этом случае конверт будет происходить только если задетектиться UTF в строке, что собственно и лучше честно говоря.

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

RXL

Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW


samlab, указание предпочтений не гарантирует возврат именно в этой кодировке, равно как и автоматическую перекодировку.
Следует смотреть в ответе следующие места:

1. В заголовке HTTP-ответа, поле Content-Type. Там указан тип документа, но в доп. параметре может быть (как правило, но не обязательно) и кодировка.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17

В принципе, можно ограничиться только этим пунктом, но если хочется поддержать и криворуких админов, то см. п.2.

2. Если в п.1. указан text/html, то можно распарсить контент и просмотреть наличие в заголовке HTML тега^

<meta http-equiv=»Conetnt-Type» content=»text/html; charset=…..» />

Найденную кодировку можно использовать для перекодирования исходного контента.

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

3. Кодировку можно попытаться определить, анализируя непосредственно контент.
Во-первых, посмотри в инете способы автоопределения UTF-16 (крайне редкий случай применения, но встречал)  и UTF-8 (с BOM и без).  Вероятность встретить UTF-32 стремится к нулю. На эти кодировки надо тестировать с самого начала.
Если это не Unicode, то это 8-битная кодировка. Вычисли гистограмму распределения символов по кодам и еще одну — по типам (цифра, знак, пробел, большие/маленькие латинские, большие/маленькие локальные, символы из верхнего диапазона. На основе их анализа и будешь принимать решение. Первую можно построить один раз, а вторую надо делать для каждой тестируемой кодировки.
Например, latin1 будет 99-100% укладываться в диапазон 1..127 (возможны редкие символы в диапазоне 128-255).
Многие 8-битные кодировки будут иметь значительную долю в «большие/маленькие локальные».
Определить таки кодировку точно невозможно. По этому должна быть таблица предпочтений — приоритетов проверяемых кодировок.

Понравилась статья? Поделить с друзьями:
  • Performance options windows 11 где находится
  • Performance monitor не работает как исправить windows 7
  • Performance monitor users в русской windows
  • Perfom exe что это за процесс windows 10
  • Perfnet 2004 windows 10 как исправить