XML и ручной ввод

Один из самых неприятных моментов в программировании сайтов, использующих XML, — это обработка кода, пришедшего от визуального редактора.

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

Прямолинейное решение — экранировать все угловые скобки, обрамляющие теги, амперсанды и HTML-сущности, а потом обернуть все это в блок CDATA — ненамного лучше полного вырезания тегов, ибо приводит, среди прочего, к тому, что полученный код неудобен для обработки с помощью XSLT. Например, довольно сложно (и даже неоправданно сложно) будет дополнить внешние ссылки классом external:

Читайте об этом на <a href=»http://wikipedia.org/»>Википедии</a>.

Разумеется, возможно решить задачу, привлекая Perl, но чем сильнее разметка превращается в текст, тем сложнее обрабатывать более сложные случаи.

Все, что нужно сделать, — заставить себя один раз написать валидатор и затем им с удовольствием пользоваться.

Я использую примерно такой вариант. Полученный от пользователя код встраивается в итоговоый XML-документ так, словно он был написан участниками комитета W3C в редакторе Altova XML Spy:

my $xmlParser = new XML::LibXML();
$xmlParser->expand_entities(0);
my $contentFragment =
    $xmlParser->parse_xml_chunk(make_well_formed_xml($content));
$contentNode->appendChild($contentFragment);

Функция make_well_formed_xml() выполняет несколько последовательных преобразований, включая правку с помощью libtidy и замену сущностей:

sub make_well_formed_xml {
    my $content = shift;
    utf8::decode($content);
    my $tidy = HTML::Tidy->new({
        'output-xhtml' => 1,
        'doctype' => 'omit',
        'show-body-only' => 1,
        'fix-uri' => 1,
    });
    $content = $tidy->clean($content);
    $content =~ s{<}{<!ENTITY lt>}g;
    $content = XML::Entities::decode('all', $content);
    $content =~ s{<!ENTITY lt>}{<}g;
    $content =~ s{&}{&}g;
    return $content;
}

Грязные места в коде — декодирование UTF-8 и замену открывающей угловой скобки — конечно нужно заменить более аккуратным кодом, но поскольку все теперь локализовано в единой функции, это можно отложить на потом, и такой код будет работать годами 🙂

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *