Недопустимая нотация самозакрывающегося узла для пустых узлов - вывод XHTML с PHP DOMDocument

Я обрабатываю XML-совместимый ввод XHTML с использованием XPATH в PHP следующим образом:

$xml=new DOMDocument();
$xml->loadXML(utf8_encode($temp));
[...]
$temp=utf8_decode($xml->saveXML());

Возникающая проблема заключается в том, что узлы, которые могут не закрываться самостоятельно в соответствии со спецификациями HTML5, например

<textarea id="something"></textarea>

или div, чтобы использовать JS

<div id="someDiv" class="whaever"></div>

возвращаться как

<textarea id="something" />

а также

<div id="someDiv" class="whaever" />

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

В то же время XPATH настаивает на выпуске

xmlns:default="http://www.w3.org/1999/xhtml

и на отдельных узлах, недавно созданных, он помещает такие вещи, как <default:p>, Как мне остановить это, не прибегая к глупому поиску и заменить следующим образом:

$temp=str_replace(' xmlns:default="http://www.w3.org/1999/xhtml" '," ",$temp);
$temp=str_replace(' xmlns:default="http://www.w3.org/1999/xhtml"'," ",$temp);
$temp=str_replace('<default:',"<",$temp);
$temp=str_replace('</default:',"</",$temp);

?

РЕДАКТИРОВАТЬ: У меня действительно возникают проблемы с глупым поиском и заменой, и я не собираюсь атаковать выходной XHTML с помощью RegExp. Рассмотрим этот пример:

<div id="videoPlayer0" class="videoPlayerPlacement" data-xml="video/cp_IV_a_1.xml"/>

Очевидно, что самозакрывающиеся div недопустимы (по крайней мере, в одном контексте, где я не могу вывести как mime application/xhtml+xml, но вынужден использовать mime text/html), и во всех других случаях они точно не проверяются.

2 ответа

Решение

Извините за поздний ответ, но вы знаете... это было Рождество.:D

function export_html(DOMDocument $dom)
{
        $voids = ['area',
                  'base',
                  'br',
                  'col',
                  'colgroup',
                  'command',
                  'embed',
                  'hr',
                  'img',
                  'input',
                  'keygen',
                  'link',
                  'meta',
                  'param',
                  'source',
                  'track',
                  'wbr'];

        // Every empty node. There is no reason to match nodes with content inside.
        $query = '//*[not(node())]';
        $nodes = (new DOMXPath($dom))->query($query);

        foreach ($nodes as $n) {
                if (! in_array($n->nodeName, $voids)) {
                        // If it is not a void/empty tag,
                        // we need to leave the tag open.
                        $n->appendChild(new DOMComment('NOT_VOID'));
                }
        }

        // Let's remove the placeholder.
        return str_replace('<!--NOT_VOID-->', '', $dom->saveXML());
}

В вашем примере

$dom = new DOMDocument();
$dom->loadXML(<<<XML
<html>
        <textarea id="something"></textarea>
        <div id="someDiv" class="whaever"></div>
</html>
XML
);

echo export_html($dom); будет производить

<?xml version="1.0"?>
<html>
    <textarea id="something"></textarea>
    <div id="someDiv" class="whaever"></div>
</html>

Счастливого Рождества! ^ _ ^

Источники:

  • http://fr.php.net/manual/en/class.domdocument.php#domdocument.props.documentelement
  • http://fr.php.net/manual/en/domdocument.savexml.php
  • http://stackru.com/questions/23622858/how-to-write-xml-self-closing-tag-using-domdocument
<?php
$content = '<root><textarea id="something"></textarea><div id="someDiv" class="whatever"></div><img src="your_src" /><br /><br /></root>';

$xml = new DOMDocument('1.0');
$xml->loadXML(utf8_encode($content));
$xml->formatOutput = true; 
$temp=$xml->saveXML(NULL, LIBXML_NOEMPTYTAG);
$temp = utf8_decode($temp);

$closings = array('area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr');
foreach($closings AS $c){
    $temp = str_replace('</'.$c.'>', '', $temp);
}

var_dump($temp);

Should you not know that HTML5 can be written and served as XML look at this: "It seems not very clear for many people. So let’s set the record straight. HTML 5 can be written in html and XML."

Next to actually serve any PHP example as XML set the according header:

header("content-type: application/xhtml+xml; charset=UTF-8");

In actual XML documents you cannot have any self closing tags written without a closing slash. No <br> instead of </br> etc. With that prelude let's go on...

We found that using the LIBXML_NOEMPTYTAG option in

$xml=new DOMDocument();
$xml->loadXML(utf8_encode($temp));
  // do stuff with the DOM
$temp=utf8_decode($xml->saveXML(NULL, LIBXML_NOEMPTYTAG));

does not "solve" the problem but reverses it. The HTML5 spec names a number of "void elements". they are: area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr and to quote the spec on them: "Void elements can't have any contents (since there's no end tag, no content can be put between the start tag and the end tag)."

Because of their defined lack of content the void elements can be used to get this right by a simple RegExp (in lack of an actual solution):

$temp = preg_replace('#></(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)>#si', '/>', $temp);

After which we can go on with the other stupid fixes I had in the question:

$temp=str_replace(' xmlns:default="http://www.w3.org/1999/xhtml"','',$temp);
$temp=str_replace('<default:',"<",$temp);
$temp=str_replace('</default:',"</",$temp);
Другие вопросы по тегам