На хабре уже есть хорошая статья об использовании 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 «\321\201\320\273\320\276\320\262\320\276″\0 [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 «\321\201\320\273\320\276\320\262\320\276″\0
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 "OK\n";
}
}
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 "OK\n";
}
}
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 | %s\n",
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 и может быть она немного шереховата. Готов выслушать и реализовать пожелания относительно расширения статьи.
Начал с энтузиазмом заниматься 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.
Максимально автоматизированный способ перевода сайта с windows-1251 на utf-8.
1. Надо перекодировать базу данных. Это делается в линухе так:
mysqldump -ulogin -ppassword --default-character-set=cp1251 dbname > ./dbname1251.sql iconv -f windows-1251 -t utf-8 ./dbname1251.sql > ./dbname_utf8.sql mysql -ulogin -ppassword dbname --default-character-set=utf8 <./dbname_utf8.sql
Мне больше понравилось конвертировать sql-файл скриптом, вместо iconv:
#!perl # use strict; use lib "../../lib"; use cyrillic qw (detect convert); my %encode = ( 'win' => 'utf' ); my %change = ( 'cp1251' => 'utf8' ); $/ = undef; ParseFile("./db_param.sql"); sub ParseFile() { my $file = shift; if ( -f $file ) { open my $fh, "<", $file or die "can't read file $file: $!"; my $content = <$fh>; close $fh; #die detect( $content); # if ( detect($content) eq "1251" ) { while(my ($k, $v)=each %change) { $content =~ s/\Q$k/$v/gs; } print "Converting: $file\n"; convert( 'win', 'utf', \$content ); open $fh, ">", $file or die "can't read file $file: $!"; print $fh $content; close $fh; # } } }
В процессе импорта перекодированной базы у меня возникла ошибка.
ERROR 1153 (08S01) at line 1188: Got a packet bigger than 'max_allowed_packet' bytes
В windows эта же ошибка выглядела так:
ERROR 2006 (HY000) at line 1188: MySQL server has gone away
Лечится это настройкой max_allowed_packet в my.cnf:
... [mysqld] max_allowed_packet=128M
потом надо перезапустить mysql:
./mysql restart
2. Если в БД были таблицы, создержащие blob’ы, то они испротились. Их надо вытаскивать из старой базы, и если в blob’ах хранился текст в старой кодировке, то перекодировать его вручную. Либо написать скрипт.
И так, если есть таблицы с blob’ами, то надо их сключить из дампа:
mysqldump -ulogin -ppassword --default-character-set=cp1251 --ignore-table=dbname.Cr2Parameters, dbname.FUCUser, dbname.FUCIDEm dbname > ./dbname1251.sql
3. pack & unpack строк в utf8.
Возможно придется переписать вызовы unpack и pack. Подробности описаны в
perldoc perlpacktut
4. Перевести все исходники из windows-1251 в utf-8. В моем случае это русские шаблоны (файлы с расширением *.rus в папках разной вложенности), и модули perl *.pm, javascript *.js. Вот скрипт, который делает это автоматически:
#!perl # use strict; use lib "../../lib"; use cyrillic qw (detect convert); my @filemasks = ( "pm", "pl", "rus", "js" ); my %encode = ( 'win' => 'utf' ); $/ = undef; ParseDir("./"); sub ParseDir() { my $path = shift; my @files; opendir my $dh, $path; my @allfiles = readdir($dh); closedir $dh; foreach my $mask (@filemasks) { push @files, grep {(/(\.${mask})$/) && (-f "$path/$_") } @allfiles; } #die @files; ParseFile("$path/$_") foreach @files; my @dirs = grep { !(/^\./) && -d "$path/$_" } @allfiles; ParseDir ("$path/$_") foreach @dirs; } sub ParseFile() { my $file = shift; return if($file=~/utf/); if ( -f $file ) { open my $fh, "<", $file; my $content = <$fh>; close $fh; #die detect( $content); if ( detect($content) eq "1251" ) { print "Converting: $file\n"; convert( 'win', 'utf', \$content ); open $fh, ">", $file; print $fh $content; close $fh; } } }
6. при подключении к БД:
$encoding="utf8"; $dbh->do("SET NAMES '".$encoding."'"); $dbh->do("SET CHARACTER SET '".$encoding."'");
7. HTTP RESPONSE:
$encoding="utf-8"; print "Content-type: text/html;charset=".$encoding;
8. .htaccess
AddDefaultCharset utf-8
9. поискать в исходниках, если где-то что-то конвертировалось из 1251 в utf, закомментировать, например так:
#!/usr/bin/perl # use strict; my @filemasks=("*.pm","*.pl"); my %change=("convert" => "#convert; my $file; my ($k, $v); $/=undef; foreach (@filemasks) { next if($_=~/repl.pl/); print "Prepare filemask: $_\n"; foreach $file (glob $_) { print "Prepare file: $file\n"; open my $fh, "<", $file; my $content=<$fh>; close $fh; while(($k, $v)=each %change) { $content =~ s/\Q$k/$v/gs; } open $fh, ">", $file; print $fh $content; close $fh; } }
#!/usr/bin/perl -w
use Encode qw(encode decode);
$content = "Величина месячного взноса?";
$content = Encode::encode('cp866', Encode::decode('cp1251', $content));
print $content;
$pmt=<STDIN>;
chomp $pmt;
$content = "Годовая процентная ставка? (пример 7% - 0.07)";
$content = Encode::encode('cp866', Encode::decode('cp1251', $content));
print $content;
$interest=<STDIN>;
chomp $interest;
$content = "Период депозита в месяцах?";
$content = Encode::encode('cp866', Encode::decode('cp1251', $content));
print $content;
$mons=<STDIN>;
chomp $mons;
# В формуле заложена месячная процентная ставка
$interest/=12;
$total=$pmt*((1+$interest)**($mons-1))/$interest;
$content = "После";
$content = Encode::encode('cp866', Encode::decode('cp1251', $content));
$content2 = "месяцев при ежемесячной ставке";
$content2 = Encode::encode('cp866', Encode::decode('cp1251', $content2));
print "$content $mons $content2 $interest\n";
$content = "У вас будет сумма";
$content = Encode::encode('cp866', Encode::decode('cp1251', $content));
print "$content $total.\n";
- Печать
Страницы: [1] Вниз
Тема: Конвертация текста из WIN в UTF8 средствами perl (Прочитано 4353 раз)
0 Пользователей и 1 Гость просматривают эту тему.
mastak777
Раньше программировал в Винде, тексты (скрипты, формы и т.п.) были в основном в кодировке WIN. Сейчас перешел на Ubuntu и по ряду причин удобнее поменять кодировку на UTF.
Написал модулек, который переконвертирует все папки в UTF.
Но есть такой нуансик. В одном файле могут встречаться строчки с разной кодировкой — WIN, KOI8, ENG. WIN нормально конвертируется в UTF8, а вот KOI8 что-то не очень. Вместо русского текста получается нечитаемый русский, к примеру, вместо «Сайту» (типа, «Сайту 5 лет») получается
яЮИРС
Строка кода:
my $ch = Lingua::DetectCharset::Detect($str); # определяем кодировку - KOI8
Перепробовал несколько методов — результат один и тот же. Благо что не много таких строчек. Они (по-видимому) получаются в результате того, что файлы на сервере правятся не одним человеком. В кодировке Винды никаких различий не замечалось, а сейчас могут полезть глюки.
$str = Convert::Cyrillic::cstocs('koi8', 'UTF8', $str); # Конвертируем
После перекодировки получаем (проверяем по строкам в каком-нибудь файле):
ENG остается ENG (эти строки я пропускаю)
Кто-нибудь сталкивался с такой проблемкой?
WIN становится UTF8 (и нормально читаются)
KOI8 становится ENG (и не читаются, а должны бы тоже стать UTF8. Почему?)
« Последнее редактирование: 22 Июля 2011, 09:53:36 от mastak777 »
RazrFalcon
1) не вин, а cp1251
2) iconv
mastak777
1) не вин, а cp1251
2) iconv
iconv — это консольная команда, а я же говорю о модулях perl. Не с консоли же я буду перекодировать файлы, заброшу модуль на сервер и перекодирую все прямо там. Ну и у себя тем же способом.
Впрочем, если б вы поподробнее писали, может было понятнее.
С консоли, кстати, тоже пытался — результат тот же.
И так
iconv -f win -t utf8 live.txt
И так
iconv -f koi8 -t utf8 live.txt
В строке, с кодировкой KOI8 — нечитаемая латиница. Остальные строки перекодируются нормально.
« Последнее редактирование: 22 Июля 2011, 12:33:22 от mastak777 »
wl
После перекодировки получаем (проверяем по строкам в каком-нибудь файле):
ENG остается ENG (эти строки я пропускаю)
Кто-нибудь сталкивался с такой проблемкой?
WIN становится UTF8 (и нормально читаются)
KOI8 становится ENG (и не читаются, а должны бы тоже стать UTF8. Почему?)
Бывало нечто похожее.
Все зависит от версии перла и от того, как читаются строки из файла.
Полагаю, у вас >= 5.6
По умолчанию кодировкой всего прочитанного считается utf-8, там специальный флаг стоит.
Это читали?
Да, а утилита iconv используется неправильно, правильная кодировка у нее называется не win, а cp1251
« Последнее редактирование: 22 Июля 2011, 13:21:43 от wl »
На свете феньки есть такие, брат Горацио, которых лохи просто не секут. (Шекспир, «Гамлет», вольный перевод)
mastak777
Разобрался. Спасибо. Здесь промелькнул пост (не запомнил чей), который и помог разобраться.
Дело в том, что
my $ch = Lingua::DetectCharset::Detect($str);
выдает довольно скудную инфу о кодировке — WIN, KOI8, UTF8, ENG.
В частности под KOI8 может находиться довольно широкий диапазон кодировок. их можно посмотреть
$ iconv -l
Тогда с консоли подобрал нужную кодировку, в данном случае KOI8 = cp1251
$ perl -pe 'BEGIN { binmode(STDIN,":encoding(cp1251)"); binmode(STDOUT, ":encoding(utf8)"); } ' < live.txt
А могло быть что-нибудь другое. Я полагал, что KOI8 это и есть KOI8.
В итоге получилось так.
my $ch = Lingua::DetectCharset::Detect($str);
В общем запутано немного, сам не все понял, но проблема решена.
if($ch eq 'WIN'){
$str = Convert::Cyrillic::cstocs('WIN', 'UTF8', $str);
}elsif($ch eq 'KOI8'){
$converter = Text::Iconv->new("cp1251", "utf-8");
$str = $converter->convert($str);
}
Спасибо за подсказку.
« Последнее редактирование: 22 Июля 2011, 13:24:53 от mastak777 »
wl
данном случае KOI8 = cp1251
Вот это круто!
Если у вас кодировка определилась как koi-8, но на самом деле это cp-1251, значит, она определилась неправильно.
На свете феньки есть такие, брат Горацио, которых лохи просто не секут. (Шекспир, «Гамлет», вольный перевод)
mastak777
Если у вас кодировка определилась как koi-8, но на самом деле это cp-1251, значит, она определилась неправильно.
Хм. Она, оказывается неправильно определилась! Ну надо же.. Вот ведь, стали модули делать, даже кодировку определить не могут.
Ясно одно. То, что выдает детектер
my $ch = Lingua::DetectCharset::Detect($str);
не является кодировкой в прямом смысле.
Мне этой инфы достаточно, чтобы продолжить работу. Вникать в нюансы и тонкости нет времени и желания.
wl
Если в алгоритме используется частотный анализ, а текст короткий, то ничего удивительного.
На свете феньки есть такие, брат Горацио, которых лохи просто не секут. (Шекспир, «Гамлет», вольный перевод)
mastak777
Если в алгоритме используется частотный анализ, а текст короткий, то ничего удивительного.
Ну, да. Вот такая конструкция не распознается.
my $str='$main::H{NAME}=~s/[^\w\.À-ßà-ÿ ]//g;';
Выдает ENG , типа нет кириллицы. А отдельными символами вообще труба.
my $ch = Lingua::DetectCharset::Detect($str);
- Печать
Страницы: [1] Вверх