我需要插入<p>
标记来包围HTML片段中的每个列表元素。这个一定不能创建嵌套段落,这就是为什么我想使用lookahead / lookbehind断言来检测内容是否已经包含在段落标记中。
到目前为止,我已经提出了以下代码。
此示例使用负向lookbehind断言来匹配每个</li>
结束标记,该标记不在之前由</p>
结束标记和任意空格匹配:
$html = <<<EOF
<ul>
<li>foo</li>
<li><p>fooooo</p></li>
<li class="bar"><p class="xy">fooooo</p></li>
<li> <p> fooooo </p> </li>
</ul>
EOF;
$html = preg_replace('@(<li[^>]*>)(?!\s*<p)@i', '\1<p>', $html);
$html = preg_replace("@(?<!</p>)(\s*</li>)@i", '</p>\1', $html);
echo $html, PHP_EOL;
让我惊讶会产生以下输出:
<ul>
<li><p>foo</p></li>
<li><p>fooooo</p></li>
<li class="bar"><p class="xy">fooooo</p></li>
<li> <p> fooooo </p> </p> </li>
</ul>
开头标记的插入按预期工作,但请注意在最后一个列表元素中插入的其他</p>
标记!
有人可以解释为什么在使用负后瞻性断言时,正则表达式中的空格(\s*
)完全被忽略了吗?
更重要的是:我可以尝试什么来实现上述目标?
答案 0 :(得分:2)
因为正则表达式没有以任何方式锚定,所以它可以随意松散。
在这种情况下,让我们来看看你的字符串是如何分解的。方括号表示尝试匹配。
... </p>[ </li>] // Fails, lookbehind assertion denies match
... </p> [ </li>] // Succeeds, lookbehind sees a space, not </p>
因此,您只需匹配一个较少的空格即可看到匹配成功,这就是您在结果中看到两个</p>
之间的空格的原因。
在Regex中没有简单的解决方法。 THE PONY HE COMES。所以请尝试使用解析器。
$dom = new DOMDocument();
$dom->loadHTML($html);
$lis = $dom->getElementsByTagName('li');
foreach($lis as $li) {
if( !$li->getElementsByTagName('p')->length) {
$p = $dom->createElement("p");
while($li->firstChild) $p->appendChild($li->firstChild);
$li->appendChild($p);
}
}
$output = $dom->saveHTML($dom->getElementsByTagName('body')->item(0));
$output = substr($output,strlen("<body>"),-strlen("</body>")); // strip body tag
答案 1 :(得分:1)
你有这个:
</p> </li>
你的正则表达式与此不匹配:
</p> </li>
^
因为前面有一个</p>
。但它在这里匹配:
</p> </li>
^
因为前面的文字不是</p>
,而是。
你想要一个HTML解析器。 PHP有几个,但我不是一个PHP开发人员,所以我不能特别推荐任何。有关一些建议,请参阅this question。
答案 2 :(得分:0)
这可能会有所帮助。
$html = preg_replace('@(<li[^>]*>)([^</li>]+)(?!\s*<p)@i', '$1<p>$2</p>', $html);