PHP "pretty print" HTML (not Tidy)

33
我正在使用PHP中的DOM扩展来构建一些HTML文档,并且我希望输出的格式能够很好地排版(包括换行和缩进),以便于阅读。然而,根据我进行的许多测试:
  1. 使用"formatOutput = true"在saveHTML()中根本不起作用,只能在saveXML()中起作用。
  2. 即使我使用了saveXML(),它仍然只对通过DOM创建的元素起作用,而不对通过loadHTML()加载的元素起作用,即使设置了"preserveWhiteSpace = false"。
如果有人知道不同的方法,我真的很想知道他们是如何使其工作的。 所以,我有一个DOM文档,并且我正在使用saveHTML()来输出HTML。由于它来自DOM,我知道它是有效的,没有必要进行任何"Tidy"或验证。
我只是在寻找一种从DOM扩展接收到的输出中获取格式良好的输出的方法。 注:正如你可能已经猜到的,我不想使用Tidy扩展,因为a)它做了比我需要的更多的事情(标记已经是有效的);b)它实际上会对HTML内容进行更改(例如HTML 5 doctype和一些元素)。 后续:

好的,通过下面的答案的帮助,我弄清楚了为什么DOM扩展没有起作用。虽然给出的示例可以工作,但它在我的代码中仍然无法工作。通过this的评论,我发现如果你有任何文本节点,其中isWhitespaceInElementContent()为true,那么在该点之后将不会应用任何格式。无论preserveWhiteSpace是否为false,都会发生这种情况。解决办法是删除所有这些节点(尽管我不确定这是否会对实际内容产生不良影响)。

3个回答

36

你说得对,HTML好像没有缩进(其他人也感到困惑)。XML可以工作,即使有加载的代码。

<?php
function tidyHTML($buffer) {
    // load our document into a DOM object
    $dom = new DOMDocument();
    // we want nice output
    $dom->preserveWhiteSpace = false;
    $dom->loadHTML($buffer);
    $dom->formatOutput = true;
    return($dom->saveHTML());
}

// start output buffering, using our nice
// callback function to format the output.
ob_start("tidyHTML");

?>
<html>
    <head>
    <title>foo bar</title><meta name="bar" value="foo"><body><h1>bar foo</h1><p>It's like comparing apples to oranges.</p></body></html>
<?php
// this will be called implicitly, but we'll
// call it manually to illustrate the point.
ob_end_flush();
?>

结果:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title>foo bar</title>
<meta name="bar" value="foo">
</head>
<body>
<h1>bar foo</h1>
<p>It's like comparing apples to oranges.</p>
</body>
</html>

与 saveXML() 相同...

<?xml version="1.0" standalone="yes"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
  <head>
    <title>foo bar</title>
    <meta name="bar" value="foo"/>
  </head>
  <body>
    <h1>bar foo</h1>
    <p>It's like comparing apples to oranges.</p>
  </body>
</html>

也许在加载HTML之前忘记设置preserveWhiteSpace=false?

免责声明:我从tyson clugg/php手册评论中窃取了大部分演示代码。懒惰的我。


更新: 我现在记得几年前我尝试过同样的事情,并遇到了同样的问题。我通过应用一个肮脏的解决方法来解决了这个问题(性能不是关键):我只是在 SimpleXML 和 DOM 之间进行了一些转换,直到问题消失。我想可能是通过使用 DOM 加载,然后使用 simplexml_import_dom 导入,输出字符串,再用 DOM 解析并最终漂亮地打印出来,以此来除去那些节点。据我所记,这个方法可以工作(但速度非常慢)。

谢谢。通过您的示例和php.net上的评论,我已经解决了问题(请参见上面的跟进)。 - Jack Sleight
使用DOM的解决方案对我来说似乎相当沉重。它的速度快还是慢?值得在较小的代码片段上使用它,还是只在整个页面上使用它? - sumid
在使用saveXML()时,遇到一些没有值的标签(例如<textarea type="text" name="name"></textarea>),它会将其转换为<textarea type="text" name="name"/>。有没有办法解决这个问题? - user2533777
3
我在此声明,根据MIT许可证的条款,我将我的PHP代码版本在这个答案中公开发布。任何人都可以使用它! - Tyson
我被当场抓住了。 - stefs

4
结果:
<!DOCTYPE html>
<html>
    <head>
        <title>My website</title>
    </head>
</html>

请考虑:
function indentContent($content, $tab="\t"){
    $content = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $content); // add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries)
    $token = strtok($content, "\n"); // now indent the tags
    $result = ''; // holds formatted version as it is built
    $pad = 0; // initial indent
    $matches = array(); // returns from preg_matches()
    // scan each line and adjust indent based on opening/closing tags
    while ($token !== false && strlen($token)>0){
        $padPrev = $padPrev ?: $pad; // previous padding //Artis
        $token = trim($token);
        // test for the various tag states
        if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)){// 1. open and closing tags on same line - no change
            $indent=0;
        }elseif(preg_match('/^<\/\w/', $token, $matches)){// 2. closing tag - outdent now
            $pad--;
            if($indent>0) $indent=0;
        }elseif(preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches)){// 3. opening tag - don't pad this one, only subsequent tags (only if it isn't a void tag)
            foreach($matches as $m){
                if (preg_match('/^<(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)/im', $m)){// Void elements according to http://www.htmlandcsswebdesign.com/articles/voidel.php
                    $voidTag=true;
                    break;
                }
            }
            $indent = 1;
        }else{// 4. no indentation needed
            $indent = 0;
        }

        if ($token == "<textarea>") {
            $line = str_pad($token, strlen($token) + $pad, $tab, STR_PAD_LEFT); // pad the line with the required number of leading spaces
            $result .= $line; // add to the cumulative result, with linefeed
            $token = strtok("\n"); // get the next token
            $pad += $indent; // update the pad size for subsequent lines
        } elseif ($token == "</textarea>") {
            $line = $token; // pad the line with the required number of leading spaces
            $result .= $line . "\n"; // add to the cumulative result, with linefeed
            $token = strtok("\n"); // get the next token
            $pad += $indent; // update the pad size for subsequent lines
        } else {
            $line = str_pad($token, strlen($token) + $pad, $tab, STR_PAD_LEFT); // pad the line with the required number of leading spaces
            $result .= $line . "\n"; // add to the cumulative result, with linefeed
            $token = strtok("\n"); // get the next token
            $pad += $indent; // update the pad size for subsequent lines
            if ($voidTag) {
                $voidTag = false;
                $pad--;
            }
        }           

    return $result;
}

//$htmldoc - DOMdocument Object!

$niceHTMLwithTABS = indentContent($htmldoc->saveHTML(), $tab="\t");

echo $niceHTMLwithTABS;

将会生成以下 HTML:

  • 根据“级别”缩进
  • 在块级元素后换行
  • 而不影响内联和自闭合元素

这个函数(它是我使用的类的一个方法)主要基于:https://stackoverflow.com/a/7840997/7646824


我觉得有人刚刚把我的代码编辑成了一个无法运行的版本。原本有一个while循环的开始,但现在却没有结束了。 - Artis Zel
我觉得,有人刚刚把我的代码编辑成了不可用的状态。原本有一个while循环的开头,但现在却没有了结束。 - undefined

-2

你可以使用 hl_tidy 函数的代码,该函数属于 htmLawed 库。

// indent using one tab per indent, with all HTML being within an imaginary div
$out = hl_tidy($in, 't', 'div')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接