你如何在PHP中格式化DOM结构?

时间:2011-11-03 15:56:41

标签: php html dom

我的第一个猜测是PHP DOM classesformatOutput参数)。但是,我无法正确地格式化和输出这个HTML块。如您所见,缩进和对齐不正确。

$html = '
<html>
<body>
<div>

<div>

        <div>

                <p>My Last paragraph</p>
            <div>
                            This is another text block and some other stuff.<br><br>
                Again we will start a new paragraph
                            and some other stuff
                            <br>
        </div>
</div>
        <div>
                        <div>
                            <h1>Another Title</h1>
                                                    </div>
                        <p>Some text again <b>for sure</b></p>
                </div>
</div>
<div>
    <pre><code>
    <span>&lt;html&gt;</span>
        <span>&lt;head&gt;</span>
            <span>&lt;title&gt;</span>
                Page Title
            <span>&lt;/title&gt;</span>
            <span>&lt;/head&gt;</span>
    <span>&lt;/html&gt;</span>
    </code></pre>
</div>
</div>
</body>
</html>';

header('Content-Type: text/plain');
libxml_use_internal_errors(TRUE);

$dom = new DOMDocument;
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadHTML($html);
print $dom->saveHTML();

更新:我在示例中添加了预先格式化的代码块。

2 个答案:

答案 0 :(得分:7)

以下是对@hijarian回答的一些改进:

LibXML错误

如果您不调用libxml_use_internal_errors(true),PHP将输出找到的所有HTML错误。但是,如果您调用该函数,则不会抑制错误,而是通过调用libxml_get_errors()来查看它们可以检查的堆。这个问题是它吃掉了内存,而且知道DOMDocument非常挑剔。如果您批量处理大量文件,最终将耗尽内存。有两种解决方案:

if (libxml_use_internal_errors(true) === true)
{
    libxml_clear_errors();
}

由于libxml_use_internal_errors(true)返回此设置的先前值(默认false),因此如果您多次运行它,则只会清除错误(如批处理中那样)。

另一种选择是将LIBXML_NOERROR | LIBXML_NOWARNING标志传递给loadHTML()方法。不幸的是,由于我不知道的原因,这仍然留下了一些错误。

请记住,如果将空(或 blankish )字符串传递给{,则DOMDocument将始终输出错误(即使在使用内部libxml错误并设置抑制标志时) {1}}方法。

正则表达式

正则表达式load*()没有多大意义,最好使用/>\s*</im来捕获~>[[:space:]]++<~m(垂直制表符),只有在实际存在空格时才能替换({ {1}}而不是\v)而没有回馈(+) - 这更快 - 并且丢弃不明智的开销(因为空格没有大小写)。

您可能还希望将新行标准化为*和其他控制字符(特别是如果HTML的来源未知),因为++将在\n之后返回\r例如{1}}。

运行上述正则表达式后,

&#23;无用且无用。

哦,我认为没有必要在这里保护空白的预先标记。只有空白的片段是无用的。

saveXML()

的其他Flags
  • DOMDocument::$preserveWhitespace - “这可能会加快您的申请速度而无需更改代码”
  • loadHTML() - 需要对此进行更多测试
  • LIBXML_COMPACT - 需要对此进行更多测试
  • LIBXML_NOBLANKS - 记录但未实施=(

更新:设置其中任何一个选项都会产生不格式化输出的效果。

LIBXML_NOCDATA

LIBXML_NOXMLDECL方法将输出XML声明。我们需要手动清除它(因为saveXML()没有实现)。为此,我们可以使用DOMDocument::saveXML()的组合来查找第一个换行符,甚至可以使用正则表达式来清理它。

另一个似乎只有an added benefit的选项就是:

LIBXML_NOXMLDECL

另一件事,如果你有内嵌标签是空的,例如substr() + strpos()$dom->saveXML($dom->documentElement); b

i

li方法会严重破坏它们(将下面的元素放在空元素中),弄乱整个HTML。 Tidy也有类似的问题,除了它只是丢弃节点。

要解决此问题,您可以使用<b class="carret"></b> <i class="icon-dashboard"></i> Dashboard <li class="divider"></li> 标记以及saveXML()

LIBXML_NOEMPTYTAG

此选项会将空(又称自动关闭)标记转换为内联标记,并允许空内联标记。

修复HTML [5]

到目前为止我们所做的所有事情,我们的HTML输出现在有两个主要问题:

  1. 没有DOCTYPE(当我们使用saveXML()时它被剥离了)
  2. 空标记现在是内嵌标记,意味着一个$dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG); 变成了两个($dom->documentElement)等等
  3. 修复第一个相当容易,因为HTML5非常宽松:

    <br />

    要获取我们的空标签,请执行以下操作:

    • <br></br>
    • "<!DOCTYPE html>\n" . $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);
    • area在HTML5中弃用
    • base
    • basefont
    • br
    • col
    • command在HTML5中弃用
    • embed
    • frame
    • hr
    • img
    • input
    • keygen
    • link
    • meta
    • param
    • source

    我们可以在循环中使用track

    wbr

    或正则表达式:

    str_[i]replace

    这是一项代价高昂的操作,我没有对它们进行基准测试,所以我无法告诉你哪一项表现更好,但我猜是foreach (explode('|', 'area|base|basefont|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr') as $tag) { $html = str_ireplace('>/<' . $tag . '>', ' />', $html); } 。另外,我不确定是否需要不区分大小写的版本。我的印象是XML标签总是小写的。 更新:标记始终是小写的。

    $html = preg_replace('~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>\b~i', '/>', $html); preg_replace()标签

    这些标签将始终将其内容(如果存在)封装到(未注释的)CDATA块中,这可能会破坏其含义。你必须用正则表达式替换这些令牌。

    实施

    <script>

答案 1 :(得分:5)

以下是php.net上的评论:http://ru2.php.net/manual/en/domdocument.save.php#88630

看起来当你从字符串加载HTML时(就像你一样),DOMDocument变得懒惰,并且不会格式化其中的任何内容。

这是解决问题的有效方法:

// Clean your HTML by hand first
$html = preg_replace('/>\s*</im', '><', $html);
$dom = new DOMDocument;
$dom->loadHTML($html);
$dom->formatOutput = true;
$dom->preserveWhitespace = false;
// Use saveXML(), not saveHTML()
print $dom->saveXML();

基本上,你抛弃标签之间的空格并使用saveXML()而不是saveHTML()。 saveHTML()只是在这种情况下不起作用。但是,您会在第一行文本中获得XML声明。