-1

我需要插入<p>标签来包围 HTML 片段中的每个列表元素。这不能创建嵌套段落,这就是为什么我想使用前瞻/后瞻断言来检测内容是否已经包含在段落标签中。

到目前为止,我已经想出了以下代码。

此示例使用否定的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*当使用否定后向断言时,正则表达式中的空格 () 会被完全忽略吗?

更重要的是:我还能尝试什么来实现上述目标?

4

3 回答 3

2

因为正则表达式没有以任何方式锚定,它可以随意松散。

在这种情况下,让我们看看如何分解您的字符串。方括号表示尝试的匹配。

... </p>[   </li>] // Fails, lookbehind assertion denies match
... </p> [  </li>] // Succeeds, lookbehind sees a space, not </p>

因此,您只需匹配少一个空格即可看到匹配成功,这就是您</p>在结果中看到两者之间有空格的原因。

在正则表达式中没有简单的解决方法。他来的小马。因此,请尝试使用解析器。

$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
于 2013-10-28T22:40:59.073 回答
1

你有这个:

</p>   </li>

而且你的正则表达式在这里不匹配:

</p>   </li>
    ^

因为</p>前面有一个。但它在这里匹配:

</p>   </li>
     ^

因为前面的文字不是</p>,而是

您需要一个 HTML 解析器。PHP 有几个,但我不是一个 PHP 开发人员,所以我不能特别推荐任何一个。有关一些建议,请参阅此问题

于 2013-10-28T22:38:25.363 回答
0

这可能会有所帮助。

$html = preg_replace('@(<li[^>]*>)([^</li>]+)(?!\s*<p)@i', '$1<p>$2</p>', $html);
于 2013-10-28T23:33:31.890 回答