我的第一个猜测是PHP DOM类(使用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();
Run Code Online (Sandbox Code Playgroud)
更新:我在示例中添加了预格式化的代码块.
以下是对@hijarian答案的一些改进:
如果您不调用libxml_use_internal_errors(true),PHP将输出找到的所有HTML错误.但是,如果您调用该函数,则不会抑制错误,而是会通过调用来检查它们libxml_get_errors().这个问题是它吃掉了内存,而且知道DOMDocument非常挑剔.如果您批量处理大量文件,最终将耗尽内存.有两种解决方案:
if (libxml_use_internal_errors(true) === true)
{
libxml_clear_errors();
}
Run Code Online (Sandbox Code Playgroud)
由于libxml_use_internal_errors(true)返回此设置的先前值(默认值false),因此如果您多次运行它(例如在批处理中),则仅具有清除错误的效果.
另一种选择是将LIBXML_NOERROR | LIBXML_NOWARNING标志传递给loadHTML()方法.不幸的是,由于我不知道的原因,这仍然留下了一些错误.
请记住,libxml如果将空(或空白)字符串传递给load*()方法,DOMDocument将始终输出错误(即使在使用内部错误和设置抑制标志时).
正则表达式/>\s*</im没有多大意义,最好~>[[:space:]]++<~m还使用它来捕获\v(垂直制表符),只有当空格实际存在时才会替换(+而不是*)而不返回(++) - 这更快 - 并且放弃不区分大小写开销(因为空白没有案例).
您可能还需要正常化以换行符\n和其它控制字符(特别是如果在HTML的起源不详),因为一\r会回来后saveXML()的实例.
DOMDocument::$preserveWhitespace 运行上面的正则表达式后没用,也没用.
哦,我认为没有必要在这里保护空白的预先标记.仅限空白的片段是无用的.
loadHTML()LIBXML_COMPACT - "这可以加快你的应用程序,而无需更改代码"LIBXML_NOBLANKS- 需要对此进行更多测试LIBXML_NOCDATA- 需要对此进行更多测试LIBXML_NOXMLDECL - 记录但未实施=(更新:设置任何这些选项将导致不格式化输出.
saveXML()该DOMDocument::saveXML()方法将输出XML声明.我们需要手动清除它(因为LIBXML_NOXMLDECL没有实现).要做到这一点,我们可以使用组合substr() + strpos()来寻找第一个换行符,甚至使用正则表达式来清理它.
另一个似乎有额外好处的选择就是:
$dom->saveXML($dom->documentElement);
Run Code Online (Sandbox Code Playgroud)
另一件事,如果你有内联标签是空的,如b,i或li在:
<b class="carret"></b>
<i class="icon-dashboard"></i> Dashboard
<li class="divider"></li>
Run Code Online (Sandbox Code Playgroud)
该saveXML()方法将严重破坏它们(将以下元素放在空元素中),弄乱整个HTML.Tidy也有类似的问题,除了它只是丢弃节点.
要解决这个问题,您可以使用以下LIBXML_NOEMPTYTAG标志saveXML():
$dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);
Run Code Online (Sandbox Code Playgroud)
此选项将空(也称为自动关闭)标记转换为内联标记,并允许空内联标记.
到目前为止我们所做的所有事情,我们的HTML输出现在有两个主要问题:
$dom->documentElement)<br />变成了两个(<br></br>),依此类推修复第一个是相当容易的,因为HTML5非常宽松:
"<!DOCTYPE html>\n" . $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);
Run Code Online (Sandbox Code Playgroud)
要获取我们的空标签,请执行以下操作:
areabasebasefont(在HTML5中弃用)brcolcommandembedframe(在HTML5中弃用)hrimginputkeygenlinkmetaparamsourcetrackwbr我们可以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);
}
Run Code Online (Sandbox Code Playgroud)
或正则表达式:
$html = preg_replace('~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>\b~i', '/>', $html);
Run Code Online (Sandbox Code Playgroud)
这是一个代价高昂的操作,我没有对它们进行基准测试,所以我不能告诉你哪一个表现更好,但我猜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;
}
Run Code Online (Sandbox Code Playgroud)
这是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();
Run Code Online (Sandbox Code Playgroud)
基本上,您将标记之间的空格扔掉,并使用saveXML()而不是saveHTML()。saveHTML()在这种情况下不起作用。但是,您会在文本的第一行中得到一个XML声明。