我的第一个猜测是 PHP DOM classes (使用 formatOutput 参数)。但是,我无法正确格式化和输出此 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><html></span>
<span><head></span>
<span><title></span>
Page Title
<span></title></span>
<span></head></span>
<span></html></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();
更新:我在示例中添加了一个预先格式化的代码块。
最佳答案
以下是对@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()
方法。不幸的是,由于我不知道的原因,这仍然留下了一些错误。请记住,如果您将空(或空白)字符串传递给
libxml
,DOMDocument 将始终输出错误(即使使用内部 load*()
错误并设置抑制标志)。方法。正则表达式
正则表达式
/>\s*</im
没有多大意义,最好使用 ~>[[:space:]]++<~m
也 catch \v
(垂直制表符)并且仅在空格实际存在时才替换( +
而不是 *
)而不回馈( ++
) - 这更快 - 并降低不区分大小写的开销(因为空格没有大小写)。您可能还想将换行符规范化为
\n
和其他控制字符(特别是在 HTML 的来源未知的情况下),因为 \r
将作为 
返回之后 saveXML()
例如。DOMDocument::$preserveWhitespace
运行上述正则表达式后是无用且不必要的。哦,我认为这里没有必要保护空白的 pre-like 标签。仅空白的片段是无用的。
附加 Flags为
loadHTML()
LIBXML_COMPACT
- “这可能会加快您的应用程序而无需更改代码”LIBXML_NOBLANKS
- 需要对此进行更多测试 LIBXML_NOCDATA
- 需要对此进行更多测试 LIBXML_NOXMLDECL
- 记录,但未实现 =( 更新:设置这些选项中的任何一个都会产生不格式化输出的效果。
上
saveXML()
DOMDocument::saveXML()
方法将输出 XML 声明。我们需要手动清除它(因为 LIBXML_NOXMLDECL
没有实现)。为此,我们可以使用 substr() + strpos()
的组合。寻找第一个换行符,甚至使用正则表达式来清理它。另一种选择,似乎有 an added benefit只是在做:
$dom->saveXML($dom->documentElement);
还有一点,如果你有内联标签都是空的,比如b
, i
或 li
在:<b class="carret"></b>
<i class="icon-dashboard"></i> Dashboard
<li class="divider"></li>
saveXML()
方法会严重破坏它们(将以下元素放在空元素中),弄乱整个 HTML。 Tidy 也有类似的问题,只不过它只是丢弃了节点。要解决此问题,您可以使用
LIBXML_NOEMPTYTAG
标志和 saveXML()
:$dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);
此选项将空(又名自关闭)标签转换为内联标签,并允许空内联标签。修复 HTML[5]
到目前为止,我们所做的所有事情,我们的 HTML 输出现在有两个主要问题:
$dom->documentElement
时它被剥离了)<br />
变成了两个( <br></br>
)等等 修复第一个相当容易,因为 HTML5 非常宽松:
"<!DOCTYPE html>\n" . $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);
取回我们的空标签,如下所示:area
base
basefont
(在 HTML5 中已弃用)br
col
command
embed
frame
(在 HTML5 中已弃用)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);
}
或者正则表达式:$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()
.此外,我不确定是否需要不区分大小写的版本。我的印象是 XML 标签总是小写的。 更新:标签总是小写的。上
<script>
和 <style>
标签这些标签总是将它们的内容(如果存在)封装到(未注释的)CDATA 块中,这可能会破坏它们的含义。您必须用正则表达式替换这些标记。
执行
function DOM_Tidy($html)
{
$dom = new \DOMDocument();
if (libxml_use_internal_errors(true) === true)
{
libxml_clear_errors();
}
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
$html = preg_replace(array('~\R~u', '~>[[:space:]]++<~m'), array("\n", '><'), $html);
if ((empty($html) !== true) && ($dom->loadHTML($html) === true))
{
$dom->formatOutput = true;
if (($html = $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG)) !== false)
{
$regex = array
(
'~' . preg_quote('<![CDATA[', '~') . '~' => '',
'~' . preg_quote(']]>', '~') . '~' => '',
'~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>~' => ' />',
);
return '<!DOCTYPE html>' . "\n" . preg_replace(array_keys($regex), $regex, $html);
}
}
return false;
}
关于php - 你如何在 PHP 中格式化 DOM 结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7997936/