Xpoint
   [напомнить пароль]

проблемы с UTF8 и XML::XPath

Метки: [без меток]
2008-03-04 15:22:20 [обр] jodaka[досье]

Существует задача - из текстового файла в кодировке UTF8 заносить данные в XML файл, который тоже в UTF8. Вроде бы всё просто до безобразия (в теории), но на практике есть проблема:
для ряботы с xml используется XML::XPath

#парсим XML конфиг
my $xp = XML::XPath->new(filename => 'configuration.xml');

$val = 'текст на русском, который мы прочитали из .txt файла';
# а вот тут мы переписываем значение на новое
$xp->setNodeText(qq~/conf/param[\@name="$pname"]/description[\@lang="ru"]~, $val);
#теперь, если мы попробуем напечатать значение
print $xp->find(qq~/conf/param[\@name="$pname"]/description[\@lang="ru"]~)->get_node(1)->string_value();
#то увидим наш текст на русском языке. До сих пор все работает прекрасно

но вот мы попробуем записать весь наш измененный XML в файл
print FH $xp->find('/conf')->get_node(1)->toString;
вот тут-то мы мусор и получим.... все русские буквы корежатся...

в доках про toString вообще почти ничего не сказано... вроде он никак не должен влиять на кодировку.

Подскажите, что я делаю не так :)

спустя 15 минут [обр] ginnie(0/6)[досье]
Зачем использовать функцию (toString), про которую в документации ничего не написано?
спустя 5 минут [обр] ginnie(0/6)[досье]
Функция toString() нашлась в классе XML::XPath::Node::Element. Поясните, как именно корежатся русские буквы.
спустя 1 час 22 минуты [обр] Алексей Севрюков(61/1292)[досье]
perldoc open смотрели?
perldoc pelrfunc #open тоже не помешает.
спустя 4 минуты [обр] jodaka[досье]

toString описана в доках, просто там ровно одна строка описания, без указания каких либо тонкостей (т.е. подразумевается, что никаких преобразований с xml деревом не делается, просто тупо печать в строку)

выводится, наприемр, вот что (не знаю, как форум переварит эти кракозяблы, но попробую вставить):
тестовая строка на русском языке

Алексей, какое отношение open имеет к описанной мной проблеме? Если я ничего не путаю, то дело исключительно в XML::XPath.

спустя 1 минуту [обр] Алексей Севрюков(61/1292)[досье]
jodaka[досье] а в open (обоих) есть режим записи utf-8 данных. Я сам не пользовался, но мне почему то кажется что это то что нужно.
спустя 7 минут [обр] jodaka[досье]
Алексей, вы не понимаете — проблема не в open! Я без проблем могу записывать данные в UTF8. Но для начала мне эти данные нужно получить... а вот XML::XPath мне их отдавать не хочет в удобоваримом виде
спустя 11 минут [обр] Алексей Севрюков(61/1292)[досье]
jodaka[досье] А Вы другими способами проверяли что он отдает не то, что нужно? У Вас же судя по коду проблема как раз при записи в файл или тут $xp->find('/conf')->get_node(1)->toString.
спустя 3 минуты [обр] jodaka[досье]
Алексей Севрюков[досье] проблема имеено в $xp->find('/conf')->get_node(1)->toString — это он возвращает кракозяблы, которые я привел выше. Причем, если каждую из XML нод по отдельности вызывать и смотреть string_value - то возвращается нормальное значение (т.е. читаемый русский текст), а вот toString отдает уже что-то непонятное... он как-то перекодирует данные, но я пока не пойму как (и зачем)
спустя 24 минуты [обр] Алексей Севрюков(61/1292)[досье]

jodaka[досье]

Вот кусок кода из perldoc XML::XPath::Node

sub XMLescape {
    my ($str, $default) = @_;
    return undef unless defined $str;
    $default ||= '';
    
    if ($XML::XPath::EncodeUtf8AsEntity) {
        $str =~ s/([\xC0-\xDF].|[\xE0-\xEF]..|[\xF0-\xFF]...)|([$default])|(]]>)/
        defined($1) ? XmlUtf8Decode ($1) : 
        defined ($2) ? $DecodeDefaultEntity{$2} : "]]>" /egsx;
    }
    else {
        $str =~ s/([$default])|(]]>)/
        defined ($1) ? $DecodeDefaultEntity{$1} : ']]>' /gsex;
    }

#?? could there be references that should not be expanded?
# e.g. should not replace &#nn; ¯ and &abc;
#    $str =~ s/&(?!($ReName|#[0-9]+|#x[0-9a-fA-F]+);)/&/go;

    $str;
}

#
# Opposite of XmlUtf8Decode plus it adds prefix "&#" or "&#x" and suffix ";"
# The 2nd parameter ($hex) indicates whether the result is hex encoded or not.
#
sub XmlUtf8Decode
{
    my ($str, $hex) = @_;
    my $len = length ($str);
    my $n;

    if ($len == 2) {
        my @n = unpack "C2", $str;
        $n = (($n[0] & 0x3f) << 6) + ($n[1] & 0x3f);
    }
    elsif ($len == 3) {
        my @n = unpack "C3", $str;
        $n = (($n[0] & 0x1f) << 12) + (($n[1] & 0x3f) << 6) + 
            ($n[2] & 0x3f);
    }
    elsif ($len == 4) {
        my @n = unpack "C4", $str;
        $n = (($n[0] & 0x0f) << 18) + (($n[1] & 0x3f) << 12) + 
            (($n[2] & 0x3f) << 6) + ($n[3] & 0x3f);
    }
    elsif ($len == 1) {    # just to be complete...
        $n = ord ($str);
    }
    else {
        die "bad value [$str] for XmlUtf8Decode";
    }
    $hex ? sprintf ("&#x%x;", $n) : "&#$n;";
}

XMLescape вызывается из метода XML::XPath::Text::toString(), который в Вашем случае вызывается через XML::XPath::Node::Element::toString().
Полагаю что проблема где то тут.

спустя 2 минуты [обр] jodaka[досье]

... сейчас посмотрю, зачем и когда вызывается XmlUtf8Decode, но вроде не похоже, что это он виноват

с помощью универсального декодера кириллицы (http://2cyr.com/decode/?lang=ru) удалось установить, что мои кракозяблы - это UTF8, который отображается, как iso8859-1. Осталось ещё понять, почему XML::XPath выбрал такую кодировку... и как это отключить :)
но уже стало немного проще

спустя 10 минут [обр] ginnie(0/6)[досье]
А не может проблема быть связана с join(), которая вызывается внутри toString()? Может кракозяблы, которые Вы привели - внутренний формат Perl? Может поможет Encode::encode_utf8($string)?
спустя 12 часов [обр] AB...(13/236)[досье]

На сколько я помню XML::XPath базируется на XML::LibXML. Там была проблема с именно с этим:

Update 1.64
strip-off UTF8 flag with $node->toString($format,1) for consistent behavior independent on the actual document encoding

Попробуйте обновить модули.

спустя 3 часа 43 минуты [обр] jodaka[досье]
версии у меня стоят самые последние...
спустя 1 день 20 часов [обр] AB...(13/236)[досье]

Приведенный Вами пример UTF8 двух битный. К примеру первый символ C3 или 11000011. Загляните на страницу RFC3629, там есть описание UTF8 и в частности

   Char. number range  |        UTF-8 octet sequence
      (hexadecimal)    |              (binary)
   --------------------+---------------------------------------------
   0000 0000-0000 007F | 0xxxxxxx
   0000 0080-0000 07FF | 110xxxxx 10xxxxxx
   0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

А вот так байты распределяются:

UTF-8 encodes each character in one to four octets (8-bit bytes):

   1. One byte is needed to encode the 128 US-ASCII characters (Unicode range U+0000 to U+007F).
   2. Two bytes are needed for Latin letters with diacritics and for characters from Greek, Cyrillic,
      Armenian, Hebrew, Arabic, Syriac and Thaana alphabets (Unicode range U+0080 to U+07FF).
   3. Three bytes are needed for the rest of the Basic Multilingual Plane (which contains virtually
      all characters in common use).
   4. Four bytes are needed for characters in the other planes of Unicode, which are rarely used in practice.

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

Powered by POEM™ Engine Copyright © 2002-2005