DOMDocument appendXML со специальными символами

Я извлекаю некоторые строки html из своей базы данных, и я хотел бы проанализировать эти строки в своем DOMDocument. Проблема в том, что DOMDocument выдает предупреждения на специальные символы.

Предупреждение: DOMDocumentFragment::appendXML() [domdocumentfragment.appendxml]: Объект: строка 2: ошибка парсера: Объект 'nbsp' не определен в page.php в строке 189

Интересно, почему, и мне интересно, как это решить. Это некоторые фрагменты кода моей страницы. Как я могу исправить такие предупреждения?

$doc = new DOMDocument();

// .. create some elements first, like some divs and a h1 ..

while($row = mysql_fetch_array($result))
{
    $messageEl = $doc->createDocumentFragment();
    $messageEl->appendXML($row['message']); // gives it's warnings here!

    $otherElement->appendChild($messageEl);
}

echo $doc->saveHTML();

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

$implementation = new DOMImplementation();
$dtd = $implementation->createDocumentType('html','-//W3C//DTD XHTML 1.0 Transitional//EN','http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd');

$doc = $implementation->createDocument('','',$dtd);
$doc->validateOnParse = true;
$doc->formatOutput = true;

// in the same whileloop, I used the following:
$messageEl = $doc->createDocumentFragment();
$doc->validate(); // which stopped my code, but error- and warningless.
$messageEl->appendXml($row['message']);

Заранее спасибо!


person Marnix    schedule 10.01.2011    source источник
comment
Что именно содержит $row['message']?   -  person Tomalak    schedule 10.01.2011
comment
Он содержит фрагмент html, в котором большую часть времени находится просто ‹p›Stuff here‹/p›. Но он всегда может содержать и больше элементов.   -  person Marnix    schedule 10.01.2011
comment
Кроме того, почему вы создаете XML-документ в памяти только для того, чтобы сделать echo $doc->saveHTML(); в конце? Это не имеет никакого смысла. Вы могли бы просто отобразить HTML на странице без всего XML-шаманства, или нет?   -  person Tomalak    schedule 10.01.2011
comment
Я хотел бы сделать это, потому что мне очень нравится объектно-ориентированное программирование. Печать тегов вручную не дает мне вообще никакой структуры моего кода. Я хочу быть уверенным, что некоторые вещи будут напечатаны первыми, и я хотел бы держать обзор этого процесса.   -  person Marnix    schedule 10.01.2011


Ответы (5)


В XML нет  . Единственными символьными объектами, для которых определено фактическое имя (вместо использования числовой ссылки), являются &, <, >, " и '.

Это означает, что вы должны использовать числовой эквивалент неразрывного пробела, который равен   или (в шестнадцатеричном формате)  .

Если вы пытаетесь сохранить HTML в контейнере XML, сохраните его как текст. HTML и XML могут выглядеть одинаково, но они очень разные. appendXML() ожидает в качестве аргумента правильно сформированный XML. Вместо этого используйте свойство nodeValue, оно будет XML-кодировать вашу HTML-строку без каких-либо предупреждений.

// document fragment is completely unnecessary
$otherElement->nodeValue = $row['message'];
person Tomalak    schedule 10.01.2011
comment
Итак, я должен сначала проанализировать каждую строку и сопоставить их с некоторыми эквивалентами, чтобы синтаксический анализатор xml мог снова сопоставить их? Есть ли функция для этого в PHP? - person Marnix; 10.01.2011
comment
@Marnix: Нет, конечно нет. Нет необходимости изменять вашу входную строку, чтобы она работала с XML, вы просто используете неправильную функцию. Смотрите отредактированный ответ. - person Tomalak; 10.01.2011
comment
Это не работает. nodeValue также печатает теги. Итак, мой вывод теперь содержит '‹p›Stuff here‹/p›». ‹p› преобразуется в текст вместо тега p. - person Marnix; 10.01.2011
comment
@Marnix: Как я уже сказал, XML и HTML — это не одно и то же. Вы не можете смешивать их, если вы не используете XHTML (и я полагаю, что содержимое вашей базы данных не является допустимым XHTML). Если вы хотите использовать шаблоны (а это то, на что похоже), используйте механизм шаблонов, такой как Smarty, в качестве основы для вашей страницы, а не XML-документ. Общий совет: если сделать что-то простое слишком сложно, возможно, вы используете неправильные инструменты. - person Tomalak; 10.01.2011
comment
Я принимаю комментарий smarty, а не фактический ответ. Smarty действительно хорошо справляется с печатью. Шаблоны делают код более читабельным, что я и искал. - person Marnix; 10.01.2011
comment
@Marnix: мой комментарий к Smarty может решить вашу настоящую проблему, но мой ответ верен на заданный вами вопрос. Дело в том, что вы задали не тот вопрос. ;-) - person Tomalak; 10.01.2011

Это сложно, потому что на самом деле это несколько проблем в одной.

Как указывает Томалак, в XML нет  . Итак, вы правильно сделали, указав DOMImplementation, потому что в XHTML есть  . Но для того, чтобы DOM знал, что документ XHTML, вам нужно загрузить и проверить DTD. DTD находится по адресу

http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd

но поскольку ежедневно на эту страницу поступают миллионы запросов, W3C решил заблокировать доступ к странице, если в запросе не отправлен UserAgent. Чтобы предоставить UserAgent, вы должны создать собственный контекст потока.

В коде:

// make sure DOM passes a User Agent when it fetches the DTD
libxml_set_streams_context(
    stream_context_create(
        array(
            'http' => array(
                'user_agent' => 'PHP libxml agent',
            )
        )
    )
);

// specify the implementation
$imp = new DOMImplementation;

// create a DTD (here: for XHTML)
$dtd = $imp->createDocumentType(
    'html',
    '-//W3C//DTD XHTML 1.0 Transitional//EN',
    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
);

// then create a DOMDocument with the configured DTD
$dom = $imp->createDocument(NULL, "html", $dtd);
$dom->encoding = 'UTF-8';
$dom->validate();

$fragment = $dom->createDocumentFragment();
$fragment->appendXML('
    <head><title>XHTML test</title></head>
    <body><p>Some text with a &nbsp; entity</p></body>
    '
);
$dom->documentElement->appendChild($fragment);
$dom->formatOutput = TRUE;
echo $dom->saveXml();

Это все еще занимает некоторое время (не спрашивайте меня, почему), но в конце вы получите (переформатированный для SO)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC 
    "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>XHTML test</title>
    </head>
    <body>
        <p>Some text with a &nbsp; entity</p>
    </body>
</html>

Также см. проблема DOMDocument::validate()

person Gordon    schedule 10.01.2011
comment
Для этого требуется, чтобы содержимое базы данных было действительным фрагментом XHTML для начала, плюс это решает проблему, в которой OP не нуждается (так сказать). Он пытается заново изобрести механизм HTML-шаблонов на основе XML-документов, что является излишне болезненным подходом к проблеме, которая уже была решена другими способами. Если я правильно понял, он хочет использовать XML, потому что хочет использовать XML - слабая причина, ИМХО. В любом случае, +1 за старание. - person Tomalak; 10.01.2011
comment
На самом деле я не хочу использовать XML, а просто использую DOMDocumentFragment.appendHTML(), которого не существует. +1 за код, но я не буду его использовать. Smarty работал на меня! - person Marnix; 10.01.2011

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

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

person Dennis Kreminsky    schedule 10.01.2011
comment
Из комментариев к моему ответу вы можете видеть, что CDATA не собирается делать то, что намеревается. ;-) - person Tomalak; 10.01.2011
comment
сорри, видимо пропустил :) - person Dennis Kreminsky; 10.01.2011

Вот еще один подход, потому что нам не нужны медленные сетевые запросы (или вообще любые сетевые запросы, возникающие в результате пользовательского ввода):

<?php
$document = new \DOMDocument();
$document->loadHTML('<html><body></body></html>');

$html = '<b>test&nbsp;</b>';
$fragment = $document->createDocumentFragment();

$html = '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE document [
<!ENTITY nbsp   "&#160;" >
]>
<document>'.$html.'</document>';

$newdom = new \DOMDocument();
$newdom->loadXML($html, LIBXML_HTML_NOIMPLIED | LIBXML_NOCDATA | LIBXML_NOENT | LIBXML_NONET | LIBXML_NOBLANKS);

foreach ($newdom->documentElement->childNodes as $childnode)
  $fragment->appendChild($fragment->ownerDocument->importNode($childnode, TRUE));

$document->getElementsByTagName('body')[0]->appendChild($fragment);

echo $document->saveHTML();

Здесь мы включаем соответствующую часть DTD, а именно определения сущности latin1 в качестве внутреннего определения DOCTYPE. Затем содержимое HTML упаковывается в элемент документа, чтобы иметь возможность обрабатывать последовательность дочерних элементов. Затем проанализированные узлы импортируются и добавляются в целевую DOM.

Наша фактическая реализация использует file_get_contents для загрузки DTD, содержащего все определения сущностей из локального файла.

person Ivo Smits    schedule 28.05.2019

В то время как smarty может быть хорошей ставкой (зачем изобретать колесо в 14-й раз?), ettranger может быть прав. Есть ситуации, в которых вы не хотите использовать что-то излишнее, например, совершенно новый (и неизученный) пакет, но больше похоже на то, что вы хотите опубликовать некоторые данные из базы данных, которая просто содержит html-материал, с которым у анализатора XML есть проблемы.

Предупреждение, следующее простое решение, но не делайте этого, если вы не УВЕРЕНЫ, что вам это сойдет с рук! (Я сделал это, когда у меня было около 2 часов до дедлайна, и у меня не было времени на учебу, оставив одинокую реализацию чего-то вроде smarty...)

Прежде чем вставлять строку в функцию appendXML, пропустите ее через файл preg_replace. Например, заменить все & NBSP; символы с [some_prefix]_nbsp. Затем на странице, где вы показываете html, сделайте это наоборот.

И Престо! знак равно

Пример кода: Код, который помещает текст во фрагмент документа:

// add text tag to p tag.
// print("CCMSSelTextBody::getDOMObject: strText: ".$this->m_strText."<br>\n");
$this->m_strText = preg_replace("/&nbsp;/", "__nbsp__", $this->m_strText);
$domTextFragment = $domDoc->createDocumentFragment();
$domTextFragment->appendXML(utf8_encode($this->m_strText));
$p->appendChild($domTextFragment);
// $p->appendChild(new DOMText(utf8_encode($this->m_strText)));

Код, который анализирует строку и записывает html:

// Instantiate template.
$pTemplate = new CTemplate($env, $pageID, $pUser, $strState);

// Parse tag-sets.
$pTemplate->parseTXTTags();
$pTemplate->parseCMSTags();

// present the html code.
$html = $pTemplate->getPageHTML();
$html = preg_replace("/__nbsp__/", "&nbsp;", $html);
print($html);

Вероятно, стоит придумать более сильную замену. (Если вы настаиваете на тщательности: выполните md5 для значения time() и жестко закодируйте результат в виде префикса. Итак, как в первом фрагменте:

$this->m_strText = preg_replace("/&nbsp;/", "4597ee308cd90d78aa4655e76bf46ee0_nbsp", $this->m_strText);

А во втором:

$html = preg_replace("/4597ee308cd90d78aa4655e76bf46ee0_nbsp/", "&nbsp;", $html);

Сделайте то же самое для любых других тегов и вещей, которые вам нужно обойти.

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

Используйте вышеперечисленное на свой страх и риск.

person Martin Stam    schedule 05.12.2011