几天前,作者为 PHP 8.4 的新Dom\\HTMLDocument
类写了一个简陋的美化打印器链接,之后重新编写使其更快且更符合风格。它能将给定的 HTML 代码:
<html lang="en-GB"><head><title id="something">Test</title></head><body><h1 class="top upper">Testing</h1><main><p>Some <em>HTML</em> and an <img src="example.png" alt="Alternate Text"></p>Text not in an element<ol><li>List</li><li>Another list</li></ol></main></body></html>
转换为:
<!doctype html>
<html lang=en-GB>
<head>
<title id=something>Test</title>
</head>
<body>
<h1 class="top upper">Testing</h1>
<main>
<p>
Some
<em>HTML</em>
and an
<img src=example.png alt="Alternate Text">
</p>
Text not in an element
<ol>
<li>List</li>
<li>Another list</li>
</ol>
</main>
</body>
</html>
作者称其“有主见”,因为它做了以下几点:
- 属性除非必要否则不使用引号。
- 每个元素都有逻辑缩进。
- CSS 和 JS 的文本内容不变,不进行美化、压缩或正确性检查。
元素的文本内容可能有额外的换行和制表符,浏览器通常会忽略多个空白字符,除非 CSS 另有规定,但这会破坏包含标记的
<pre>
块。
该美化打印器主要用于使标记易于阅读,因为根据专家观点,程序应主要为人们阅读而编写。
它的工作原理如下:当元素不是元素时?当它是一个空元素时!
现代 HTML 有“空元素”的概念,如
<a>
必须有闭合标签,但空元素不需要。该打印器维护了一个不能显式关闭的元素列表。$void_elements = [ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr", ];
制表符🆚空格
使用制表符,用户可根据个人偏好设置制表宽度,不会与语义上重要的空白混淆。
$indent_character = "\t";
设置 DOM
新的
HTMLDocument
对使用过之前版本的人来说应该很熟悉。$html = '<html lang="en-GB"><head><title id="something">Test</title></head><body><h1 class="top upper">Testing</h1><main><p>Some <em>HTML</em> and an <img src="example.png" alt="Alternate Text"></p>Text not in an element<ol><li>List</li><li>Another list</li></ol></main></body></html>'; $dom = Dom\HTMLDocument::createFromString( $html, LIBXML_NOERROR, "UTF-8" );
若不想自动添加
<head>
和<body>
元素,可使用LIBXML_HTML_NOIMPLIED
标志。引号引还是不引?
传统上 HTML 属性需要引号,但现代 HTML 允许属性在不包含特定字符时不使用引号。该函数用于检查属性值是否需要引号。
function value_unquoted( $haystack ) { // 必须不包含特定字符 $needles = [ // https://infra.spec.whatwg.org/#ascii-whitespace "\t", "\n", "\f", "\n", " ", // https://html.spec.whatwg.org/multipage/syntax.html#unquoted "\"", "'", "=", "<", ">", "`" ]; foreach ( $needles as $needle ) { if ( str_contains( $haystack, $needle ) ) { return false; } } // 必须不为空 if ( $haystack == null ) { return false; } return true; }
递归再递归
遍历 DOM 树,正确缩进打印打开的元素及其属性,若有文本内容则打印,若元素需要关闭则打印相应的缩进。
function serializeHTML( $node, $treeIndex = 0, $output = "") { global $indent_character, $preserve_internal_whitespace, $void_elements; // 手动添加 doctype if ( $output == "" ) { $output.= "<!doctype html>\n"; } if( property_exists( $node, "localName" ) ) { // 这是一个元素 // 获取所有属性 $attributes = ""; if ( property_exists($node, "attributes")) { foreach( $node->attributes as $attribute ) { $value = $attribute->nodeValue; // 只有值包含特定字符时才添加引号 $quote = value_unquoted( $value )? "" : "\""; $attributes.= " {$attribute->nodeName}={$quote}{$value}{$quote}"; } } // 打印打开的元素和所有属性 $output.= "<{$node->localName}{$attributes}>"; } else if( property_exists( $node, "nodeName" ) && $node->nodeName == "#comment" ) { // 注释 $output.= "<!-- {$node->textContent} -->"; } // 增加缩进 $treeIndex++; $tabStart = "\n". str_repeat( $indent_character, $treeIndex ); $tabEnd = "\n". str_repeat( $indent_character, $treeIndex - 1); // 节点是否有子元素? if( property_exists( $node, "childElementCount" ) && $node->childElementCount > 0 ) { // 循环遍历子元素 $i=0; while( $childNode = $node->childNodes->item( $i++ ) ) { // 这是一个文本节点? if ($childNode->nodeType == 3 ) { // 只有在内容中没有 HTML 时才打印输出,忽略空元素 if ( !str_contains( $childNode->textContent, "<" ) && property_exists( $childNode, "localName" ) && !in_array( $childNode->localName, $void_elements ) ) { $output.= $tabStart. $childNode->textContent; } } else { $output.= $tabStart; } // 递归缩进所有子元素 $output = serializeHTML( $childNode, $treeIndex, $output ); }; // 后缀添加"\n"和适当数量的"\t" $output.= "{$tabEnd}"; } else if ( property_exists( $node, "childElementCount" ) && property_exists( $node, "innerHTML" ) ) { // 如果没有子元素且节点包含内容,则打印内容 $output.= $node->innerHTML; } // 关闭元素,除非它是一个空元素 if( property_exists( $node, "localName" ) &&!in_array( $node->localName, $void_elements ) ) { $output.= "</{$node->localName}>"; } // 返回完全缩进的 HTML 字符串 return $output; }
打印出来
序列化后的字符串硬编码了
<!doctype html>
,通过echo serializeHTML( $dom->documentElement );
显示完整的 HTML。下一步
请在 GitLab 上提出任何问题或留下评论。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。