php - 使用 PHP 缩进 HTML(5) 时出现问题

标签 php html domdocument indentation tidy

Disclaimer: Please bare with the length of this question. This is a recurring question for a real world problem that I've seen asked hundreds of times with no clear, working solution ever being presented.

我有数百个 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;
}

它有效但有两个缺陷:HTML 注释,scriptstyle标签缩进不正确:

<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并坚持删除任何内联标签,迫使我绕过它。为了清楚地说明这一点,这里有一些例子:

  • <br>空标签
  • <i>text</i>内联标签
  • <i class="icon-home"></i> 内联标签(来自 Font Awesome 的示例)

如果您检查代码,您会注意到我已经说明了 b , imenu使用不完美的标签 str_replace hack - 我本可以使用更强大的正则表达式,甚至 str_ireplace完成同样的事情,但为了我的目的str_replace更快,足够好。但是,这仍然会留下我没有考虑的任何其他内联标签,这很糟糕。

所以我求助于DOMDocument , 但我很快发现为了 formatOutput为了工作我必须:

  1. 去除标签之间的所有空格(当然使用正则表达式:'~>[[:space:]]++<~m' > ><)
  2. 将所有换行组合转换为\n所以它不编码 \r作为&#23;例如
  3. 将输入字符串加载为 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 周围的方 block 和 style内部 HTML,这使它们的内容无效,我必须返回并 preg_replace那些 token 再次。这个“有效”:

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 most recommended and validated methods of indenting HTML不要在野外为 HTML5 生成正确或可靠的结果,我不得不屈服于 dark god Cthulhu .

我确实尝试了其他库,例如:

  • html5lib - 无法获得 DOMDocument::$formatOutput上类
  • tidy-html5 - 与正常问题相同 tidy , 除了它支持 HTML5 标签/属性

目前,如果没有更好的解决方案,我正在考虑编写仅适用于正则表达式的东西。但我想也许DOMDocument可能被迫使用 HTML5 和 script/style使用自定义 XSLT 标记。我以前从未接触过 XSLT,所以我不知道这是否现实,也许你们中的一位 XML 专家可以告诉我,也许可以提供一个起点。

最佳答案

您没有提到您的意图是为了生产目的还是为了开发目的而转换页面,例如调试 HTML 输出时。

如果是后者,并且由于您已经提到编写基于 Regex 的解决方案,我已经编写了 Dindent为此目的。

您没有包含输入样本和预期输出。您可以使用 sandbox 测试我的实现.

关于php - 使用 PHP 缩进 HTML(5) 时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17172824/

相关文章:

php - 外键可以为空或留空吗?

php - Codeigniter - 1 分钟后删除一条记录

php - 如何找到邻近梅登黑德网格的定位器代码?

html - 鼠标悬停更改图像位置和大小

javascript - 如何将当前登录用户选择为我选择的选项值

javascript - 没有得到正确的宽度 Jquery

php - 多类(class)、相互关联的学校时间表作为 MySQL 数据库

php - 识别html示例中的所有类值

java - 如何在“with\”转义时避免转义 &

php - 如何获取 DOMElement 节点的 html 代码?