在这种特定情况下很难使用 DOMDocument,因为它会自动将文本节点包装为<p>
标签(并添加 doctype、head、html)。一种方法是使用以下方法构建一个模式作为词法分析器(?(DEFINE)...)
特征和命名子模式:
$html = <<<EOD
Don't wrap me<p>Hello</p><div class="text">wrap me please!</div><span class="title">wrap me either!</span> Don't wrap me <h1>End</h1>
EOD;
$pattern = <<<'EOD'
~
(?(DEFINE)
(?<self> < [^\W_]++ [^>]* > )
(?<comment> <!-- (?>[^-]++|-(?!->))* -->)
(?<cdata> \Q<![CDATA[\E (?>[^]]++|](?!]>))* ]]> )
(?<text> [^<]++ )
(?<tag>
< ([^\W_]++) [^>]* >
(?> \g<text> | \g<tag> | \g<self> | \g<comment> | \g<cdata> )*
</ \g{-1} >
)
)
# main pattern
(?: \g<tag> | \g<self> | \g<comment> | \g<cdata> )+
~x
EOD;
$html = preg_replace($pattern, '<code>$0</code>', $html);
echo htmlspecialchars($html);
The (?(DEFINE)..)
功能允许将定义部分放入正则表达式模式中。此定义部分和其中的命名子模式不匹配任何内容,它们稍后将在主模式中使用。
(?<abcd> ...)
定义一个您可以稍后重用的子模式\g<abcd>
。在上面的模式中,以这种方式定义的子模式是:
-
self: 描述一个自闭合标签
-
comment: 用于 html 注释
-
cdata:对于 cdata
-
text: 用于文本(除标签、注释或 cdata 以外的所有内容)
-
tag: 对于非自闭合的 html 标签
self:
[^\W_]
是一个获得的技巧\w
没有下划线。[^\W]++
代表标签名称并且也在tag
子模式。
[^>]*
意味着所有不是>
零次或多次。
comment:
(?>[^-]++|-(?!->))*
描述 html 注释中所有可能的内容:
(?> # open an atomic group
[^-]++ # all that is not a literal -, one or more times (possessive)
| # OR
- # a literal -
(?!->) # not followed by -> (negative lookahead)
)* # close and repeat the group zero or more times
cdata:
之间的所有字符\Q..\E
被视为文字字符,特殊字符,例如[
不需要逃避。 (这只是使模式更具可读性的一个技巧)。
CDATA 中允许的内容的描述方式与 html 注释中的内容相同。
text:
[^<]++
直到左尖括号或字符串末尾的所有字符。
tag:
这是最有趣的子模式。第 1 行和第 3 行是开始标签和结束标签。请注意,在第 1 行中,标记名称是使用捕获组捕获的。在第 3 行中,\g{-1}
指的是最后定义的捕获组匹配的内容(“-1”表示“左边一个”)。
第 2 行描述了开始标签和结束标签之间可能的内容。您可以看到,此描述不仅使用之前定义的子模式,还使用当前子模式本身来允许嵌套标签。
一旦设置了所有项目并关闭了定义部分,您就可以轻松编写主要模式。