Именованные объекты в инкапсулированном XML вызывают ошибки синтаксического анализа

У меня есть XML-документы, содержащие другие XML-документы, инкапсулированные как CDATA, например:

    <mds>
      <md>
        <value>
          <![CDATA[<?xml version="1.0" encoding="UTF-8"?><record xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/">
             <dc:title>some text containing &amp;</dc:title></record>]]>
        </value>
      </md>
    </mds>

Я извлекаю этот XML и dc:title из него, используя LibXML:

$dcrawData = <get the CDATA from above>;
$dcDOM = $::PRSR->load_xml(expand_entities => 0, string => $dcRawData);
$dcTitle = $dcDOM->findvalue("//dc:title");

Затем я вставляю его в другой раздел XML, выполняя замену строки:

<mods:titleInfo>
    <mods:title>some text containing &</mods:title>
</mods:titleInfo>

Как видите, объект & расширяется и становится одним &. Это проблема, потому что теперь результирующий XML генерирует ошибку синтаксического анализа, потому что любой синтаксический анализатор ожидает здесь именованный объект.

Есть ли способ запретить LibXML расширять именованные объекты при использовании findvalue или перекодировать их перед использованием значения? В других записях могут быть и другие. Параметр expand_entities не имеет значения.


person jackthehipster    schedule 26.08.2014    source источник


Ответы (3)


Хорошо, я думаю, что нашел решение. XML::Entities выполнит работу по перекодированию объектов в строке.

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

Так что на данный момент я использую

$dcTitle = encode_entities($dcDOM->findvalue("//dc:title"),'&<>"');

кодировать только амперсанд и несколько специальных символов xml.

person jackthehipster    schedule 26.08.2014
comment
Учитывая, что значение, с которым вы работаете, не будет содержать никаких тегов элементов, экранирования только & и < должно быть достаточно, чтобы гарантировать правильно сформированный вывод (если вы сохраните полученный файл в правильной кодировке). - person Ian Roberts; 26.08.2014
comment
Ян, спасибо, я подумаю. Я не знаю данных близко, но похоже, что там будет только текст на разных языках (utf-8) и некоторые неприятные вещи, такие как амперсанд. - person jackthehipster; 26.08.2014
comment
findvalue никогда не даст вам элементы - он делает to_literal для всего, что возвращает выражение XPath, поэтому в этом случае он даст вам одну строку, содержащую конкатенацию строковых значений (т.е. текстовое содержимое) всех dc:title элементов в документе. ты смотришь. - person Ian Roberts; 26.08.2014
comment
@ Ян: Ты прав. И в других местах в моем проекте я иду по маршруту findnodes, чтобы избежать объединения нескольких элементов в одно значение поиска. Но в этом случае будет только один заголовок, и об этом не нужно беспокоиться. - person jackthehipster; 27.08.2014

Затем я вставляю его в другой раздел XML, выполняя замену строки

Не надо. Если вы хотите вставить данные в XML-документ, вам следует сделать это с помощью API с поддержкой XML, который выполнит любое необходимое экранирование за вас.

person Ian Roberts    schedule 26.08.2014
comment
Да, я понимаю это. Но это означало бы выбросить очень, очень сложный сценарий для проекта переноса данных и перестроить все с нуля, создав множество других проблем, связанных с созданием сложного XML с помощью DOM... пространство имен и обработка префиксов, идентификаторы элементов, которые ссылки внутри документа - это только то, что сразу приходит на ум. Вы правы, конечно, что это был бы правильный способ сделать это. - person jackthehipster; 26.08.2014
comment
@jackthehipster достаточно честно, учитывая более широкий контекст, я бы, вероятно, сделал то же самое. - person Ian Roberts; 26.08.2014

Затем я вставляю его в другой раздел XML, выполняя замену строки

Это та часть, которую вы делаете неправильно. Вы вставляете текст в XML, не превращая его в XML. (Это называется ошибкой внедрения.) Вам необходимо экранировать &, < и любые символы, не входящие в набор символов документа.

sub text_to_xml {
   my ($s) = @_;
   for ($s) {
      s/&/&amp;/g;
      s/</&lt;/g;
      s/"/&quot;/g;  # So it can be used for attributes too
      s/'/&apos;/g;  # So it can be used for attributes too
   }
   return $s;
}

Не забывайте, что вам также нужно будет закодировать его в соответствии с кодировкой документа.

person ikegami    schedule 26.08.2014
comment
Смотрите мой ответ выше. Сейчас я делаю то же самое, используя XML::Entities::encode_entities(). - person jackthehipster; 27.08.2014