负面观察在量化的空白处停止?

时间:2013-10-28 22:31:12

标签: php regex negative-lookbehind

我需要插入<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*)完全被忽略了吗?

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

3 个答案:

答案 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);