我的第一个猜测是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();
更新:我在示例中添加了预先格式化的代码块。
答案 0 :(得分:7)
以下是对@hijarian回答的一些改进:
如果您不调用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}}。

无用且无用。
哦,我认为没有必要在这里保护空白的预先标记。只有空白的片段是无用的。
saveXML()
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输出现在有两个主要问题:
saveXML()
时它被剥离了)$dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);
变成了两个($dom->documentElement
)等等修复第一个相当容易,因为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声明。