免责声明:请坦白这个问题的长度。这是我见过的现实世界问题中反复出现的问题
数百次都没有明确的、可行的解决方案
呈现。
我有数百个 HTML 文件,我想使用 PHP 进行批量缩进。起初我想到使用 Tidy,但你应该知道,它默认与 HTML5 标签和属性不兼容,经过一些研究和更多测试,我想出了以下“伪造”HTML 5 支持的实现:
function Tidy5($string, $options = null, $encoding = 'utf8')
{
$tags = array();
$default = array
(
'anchor-as-name' => false,
'break-before-br' => true,
'char-encoding' => $encoding,
'decorate-inferred-ul' => false,
'doctype' => 'omit',
'drop-empty-paras' => false,
'drop-font-tags' => true,
'drop-proprietary-attributes' => false,
'force-output' => true,
'hide-comments' => false,
'indent' => true,
'indent-attributes' => false,
'indent-spaces' => 2,
'input-encoding' => $encoding,
'join-styles' => false,
'logical-emphasis' => false,
'merge-divs' => false,
'merge-spans' => false,
'new-blocklevel-tags' => ' article aside audio details dialog figcaption figure footer header hgroup menutidy nav section source summary track video',
'new-empty-tags' => 'command embed keygen source track wbr',
'new-inline-tags' => 'btidy canvas command data datalist embed itidy keygen mark meter output progress time wbr',
'newline' => 0,
'numeric-entities' => false,
'output-bom' => false,
'output-encoding' => $encoding,
'output-html' => true,
'preserve-entities' => true,
'quiet' => true,
'quote-ampersand' => true,
'quote-marks' => false,
'repeated-attributes' => 1,
'show-body-only' => true,
'show-warnings' => false,
'sort-attributes' => 1,
'tab-size' => 4,
'tidy-mark' => false,
'vertical-space' => true,
'wrap' => 0,
);
$doctype = $menu = null;
if ((strncasecmp($string, '<!DOCTYPE', 9) === 0) || (strncasecmp($string, '<html', 5) === 0))
{
$doctype = '<!DOCTYPE html>'; $options['show-body-only'] = false;
}
$options = (is_array($options) === true) ? array_merge($default, $options) : $default;
foreach (array('b', 'i', 'menu') as $tag)
{
if (strpos($string, '<' . $tag . ' ') !== false)
{
$tags[$tag] = array
(
'<' . $tag . ' ' => '<' . $tag . 'tidy ',
'</' . $tag . '>' => '</' . $tag . 'tidy>',
);
$string = str_replace(array_keys($tags[$tag]), $tags[$tag], $string);
}
}
$string = tidy_repair_string($string, $options, $encoding);
if (empty($string) !== true)
{
foreach ($tags as $tag)
{
$string = str_replace($tag, array_keys($tag), $string);
}
if (isset($doctype) === true)
{
$string = $doctype . "\n" . $string;
}
return $string;
}
return false;
}
它可以工作,但有 2 个缺陷:HTML 注释,script
and style
标签没有正确缩进:
<link href="/_/style/form.css" rel="stylesheet" type="text/css"><!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!--<script type="text/javascript" src="//raw.github.com/kevinburke/tecate/master/tecate.js"></script>-->
</script><script charset="UTF-8" src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.0.0/js/locales/bootstrap-datepicker.pt.js" type="text/javascript">
</script><!--<script src="/3rd/parsley/i18n/messages.pt_br.js"></script>-->
<!--<script src="//cdnjs.cloudflare.com/ajax/libs/parsley.js/1.1.10/parsley.min.js"></script>-->
<script src="/3rd/select2/locales/select2_locale_pt-PT.js" type="text/javascript">
</script><script src="/3rd/tcrosen/bootstrap-typeahead.js" type="text/javascript">
还有另一个更严重的缺陷:Tidy 转换了所有menu
标签到ul
并坚持放弃任何empty内联标签,迫使我绕过它。为了完全清楚地说明这一点,这里有一些例子:
-
<br>
空标签
-
<i>text</i>
内联标签
-
<i class="icon-home"></i>
empty内联标签(示例来自 Font Awesome)
如果您检查代码,您会发现我已经考虑了b
, i
and menu
标签使用不完美 str_replace
hack - 我可以使用更强大的正则表达式,甚至str_ireplace
完成同样的事情,但为了我的目的str_replace
更快而且足够好。然而,这仍然留下了任何其他empty内联标签我没有考虑到这一点,这很糟糕。
所以我转向DOMDocument
,但我很快发现为了formatOutput
为了工作我必须:
- 去除标签之间的所有空格(当然使用正则表达式:
'~>[[:space:]]++<~m'
> ><
)
- 将所有换行符组合转换为
\n
所以它不编码\r
as 
例如
- 将输入字符串加载为 HTML,输出为 XML
令我惊讶的是,DOMDocument 也存在空内联标签的问题,基本上,每当它看到<i class="icon-home"></i><someOtherTag>text</someOtherTag>
或类似的,它会将其变成<i class="icon-home"><someOtherTag>text</someOtherTag></i>
这将完全扰乱浏览器对页面的渲染。为了克服这个问题,我发现使用LIBXML_NOEMPTYTAG
随着DOMDocument::saveXML()
将打开任何没有内容的标签(包括真正的空标签,例如<br />
) 到内联结束标记中,例如:
-
<i class="icon-home"></i>
保持不变(应该如此)
-
<br>
变成<br></br>
搞乱浏览器渲染(再次)
为了解决这个问题,我必须使用一个正则表达式来查找~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>~
并将匹配的字符串替换为简单的/>
。另一个主要问题是saveXML()
是它添加了<![CDATA[
.. ]]>
我周围的街区script
and style
内部 HTML,这使得它们的内容无效,我必须返回并preg_replace
再次那些令牌。这个“有效”:
function DOM5($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;
}
似乎是two 最推荐和经过验证的 HTML 缩进方法 https://stackoverflow.com/a/3577662/89771无法在野外为 HTML5 生成正确或可靠的结果,我不得不屈服于黑暗之神克苏鲁 http://www.codinghorror.com/blog/2009/11/parsing-html-the-cthulhu-way.html.
我确实尝试过其他库,例如:
-
html5lib http://code.google.com/p/html5lib/- 无法得到
DOMDocument::$formatOutput
to work
-
整洁的html5 https://github.com/w3c/tidy-html5- 与正常情况相同的问题
tidy
,除了它支持 HTML5 标签/属性
此时,如果不存在更好的解决方案,我正在考虑编写一些仅适用于正则表达式的东西。但我想也许DOMDocument
可能被迫使用 HTML5 和script
/ style
使用自定义 XSLT 进行标记。我以前从未使用过 XSLT,所以我不知道这是否现实,也许你们中的一位 XML 专家可以告诉我,也许可以提供一个起点。