regex 在PHP中将html标题转换为列表元素

avwztpqn  于 2023-10-22  发布在  PHP
关注(0)|答案(2)|浏览(118)

我正在学习php。我想展示一下这篇文章的目录。将标题(h2,h3,h4,...)转换为列表并创建链接。这是我的PHP代码。

$Post = '
<h2>Title 01</h2>
<h3>Title 01.01</h3>
<h3>Title 01.02</h3>
<h2>Title 02</h2>
<h3>Title 02.02</h3>
';

$c = 1;
$r = preg_replace_callback('~<h*([^>]*)>~i', function($res) use (&$c){
    return '<li><a id="#id'.$c++.'">'.$res[1].'</a></li>';
}, $Post);
$Post = $r;

echo '<ul>';
echo $Post;
echo '</ul>';

输出如下所示,但上面的代码工作错误。

<ul>
<li><a id="#id1">2</a></li>Title 01<li><a id="#id2">/h2</a></li>
<li><a id="#id3">3</a></li>Title 01.01<li><a id="#id4">/h3</a></li>
<li><a id="#id5">3</a></li>Title 01.02<li><a id="#id6">/h3</a></li>
<li><a id="#id7">2</a></li>Title 02<li><a id="#id8">/h2</a></li>
<li><a id="#id9">3</a></li>Title 02.02<li><a id="#id10">/h3</a></li>
</ul>

我知道PHP代码写得不正确,但我想显示如下输出。

<ul>
<li><a href="#id1">Title 01</a></li>
<li><a href="#id2">Title 01.01</a></li>
<li><a href="#id3">Title 01.02</a></li>
<li><a href="#id4">Title 02</a></li>
<li><a href="#id5">Title 02.02</a></li>
</ul>
qjp7pelc

qjp7pelc1#

你的正则表达式是不必要的复杂。
您可以使用<h.>(.*)</h.>来正确匹配您试图匹配的内容。
我将其添加到上面的片段中,以显示您想要的结果:

$post = '
<h2>Title 01</h2>
<h3>Title 01.01</h3>
<h3>Title 01.02</h3>
<h2>Title 02</h2>
<h3>Title 02.02</h3>
';

$c = 1;
$list_elements = preg_replace_callback('~<h.>(.*)</h.>~i', function($res) use (&$c){
    return '<li><a id="#id'.$c++.'">'.$res[1].'</a></li>';
}, $post);

echo '<ul>';
echo $list_elements;
echo '</ul>';

但是,正如注解中所建议的,如果这不仅仅是一个玩具示例,那么您可能应该在这里使用解析器。那么正则表达式几乎总是一种肯定会搬起石头砸自己的脚的方式。

nfeuvbwi

nfeuvbwi2#

你的正则表达式对于你要做的事情是错误的:

~<h*([^>]*)>~i

<h*意味着它将匹配一个尖括号,后跟 * 零个或多个 * h。这基本上意味着你的正则表达式匹配每个<>对之间的所有内容,(包括</...>)。
你可以这样做来从标题中提取标题:

~<h[1-6]>([^<]*)<\h[1-6]>~i

但是那些链接需要针对标题中的ID,所以你需要这样做来提取它们:

~<h[1-6] id="([^"]*)">([^<]*)<\h[1-6]>~i

但是如果标题上有其他属性呢?

~<h[1-6][^>]*(id="([^"*])"[^>]*)?>([^<]*)<\h[1-6]>~i

还是标题中的标记?
正则表达式不是解析HTML的好方法。这是一个强大的工具,可以使用它,但有更好的方法。

$doc = new DOMDocument();
$doc->loadHTML($post);

$xpath = new DOMXPath($doc);

$headings = $xpath->query('html/body//*[self::h1 or self::h2 or self::h3]');

$nav = $xpath->query('html/body//nav/ul');

foreach ($headings as $heading) {
  $link = $doc->createElement('a');
  $link->setAttribute('href', '#' . $heading->getAttribute('id'));
  $link->textContent = $heading->textContent;

  $nav->appendChild(
   $doc->createElement('li')
     ->appendChild($link)
  );
}

我假设标题中没有标记,但是如果需要的话,只需要进行几处修改就可以复制内部标记。

相关问题