空节点的非法自闭合节点表示法 - 使用 PHP DOMDocument 输出 XHTML


illegal self closing node notation for empty nodes - outputting XHTML with PHP DOMDocument

我在PHP中使用XPATH处理XHTML的XML兼容输入,如下所示:

$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。请考虑以下示例:

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

显然,自闭合div 是非法的(至少在我无法输出为 mime application/xhtml+xml 但被迫使用 mime text/html 的上下文中(,并且在所有其他情况下,它们肯定不会验证。

可以使用技巧规范化"非空"标签。这不是官方解决方案,但它有效。

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 $node) {
        if (in_array($node->nodeName, $voids)) {
            // A void tag.
            continue;
        }
        // Not a void tag. We inject a placeholder content.
        $node->appendChild(new DOMComment('NOT_VOID'));
    }
    
    // We remove the placeholders.
    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>
  1. 创建 DOMDocument 的实例并将文档类型设置为 XHTML:
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->xmlStandalone = true;
$dom->formatOutput = true;
$dom->preserveWhiteSpace = false;
$dom->loadXML('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">');
  1. 创建元素并将其附加到文档:
$html = $dom->createElement('html');
$dom->appendChild($html);
$body = $dom->createElement('body');
$html->appendChild($body);
$div = $dom->createElement('div');
$body->appendChild($div);
$span = $dom->createElement('span', 'This is an example');
$div->appendChild($span);
  1. 输出 XHTML 标记:
echo $dom->saveXML();

生成的输出将是有效的 XHTML,其中包含空元素的显式结束标记,例如 <div></div> 而不是 <div/>

如果你不知道HTML5可以作为XML编写和提供,看看这个:">对很多人来说似乎不是很清楚。因此,让我们澄清事实。HTML 5可以用HTML和XML编写。

接下来,将任何PHP示例作为XML实际提供,请设置相应的标头:

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

在实际的 XML 文档中,不能在没有右斜杠的情况下编写任何自结束标记。没有<br>而不是</br>等。有了这个前奏,让我们继续...

我们发现在 中使用 LIBXML_NOEMPTYTAG 选项

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

不是"解决"问题,而是扭转它。HTML5规范命名了许多"void元素"。它们是: area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr并引用规范:"void 元素不能有任何内容(因为没有结束标签,所以不能在开始标签和结束标签之间放置任何内容(。

由于它们定义了缺乏内容,因此可以通过简单的正则表达式(缺乏实际解决方案(来实现这一目标:

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

之后,我们可以继续我在问题中遇到的其他愚蠢的修复:

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