TOC的生成
TOC是什么
TOC 即 Table of Content,就是将文档中的 h1-h6
抽取出来,并结构化的展示;可以通过链接直接跳转到相应的内容 (这里我使用锚点)
这个本身是没什么好写的,但是涉及到树形的结构化处理,要考虑到怪异的层级问题。
方案
由于原始文档的格式(markdown,html,asciidoc)比较多,但是用于最后展示的还是 html
,所以为了方便统一的处理,先将文档都选染成html
- 使用
ParseDown
渲染markdown - 使用
Crawler
解析html - 提取
h{n}
,结构化h{n}
,并设置html中的id
属性 - 展示树形 TOC
预览
实现
安装依赖包
composer require erusev/parsedown-extra
composer require symfony/dom-crawler
composer require symfony/css-selector
代码实现
public function getTocAttribute() {
if ($this->headerNodes) {
return $this->headerNodes;
}
$crawler = new Crawler();
$crawler->addHtmlContent($this->html, 'utf-8');
$this->headerNodes = $crawler->filter('h1,h2,h3,h4,h5,h6')->each(function (Crawler $node, $i) {
$attrId = 'header_' . $i;
$node->getNode(0)->setAttribute('id', $attrId);
return [
'target' => '#' . $attrId,
'level' => intval(substr($node->nodeName(), 1)),
'text' => $node->text(),
];
});
$this->tocedHtml = $crawler->html();
return $this->headerNodes;
}
树形结构化
class MenuItem {
public $level;
public $text;
public $target;
public $children = [];
public function __construct($target, $level, $text) {
$this->target = $target;
$this->level = $level;
$this->text = $text;
}
}
$rootMenu = new MenuItem(-1, 0, '__root__');
foreach ($wiki->toc as $hn) {
$menuItem = new MenuItem($hn['target'], $hn['level'], $hn['text']);
// 每个菜单都从根目录开始寻找
$lastMenu = $rootMenu;
while (true) {
$nestMenu = end($lastMenu->children);
// 如果比上个层级还大,就挂载到他后面
if (!$nestMenu && $hn['level'] > $lastMenu->level) {
$lastMenu->children[] = $menuItem;
break;
}
// 如果循环下来发现没有匹配的层级,则放进根目录
if ($hn['level'] < $nestMenu->level) {
$rootMenu->children[] = $menuItem;
break;
}
// 如果和上个层级一样,就赛到上层的children
if ($nestMenu->level >= $hn['level']) {
$lastMenu->children[] = $menuItem;
break;
}
$lastMenu = $nestMenu;
}
}
渲染输出
function tree($menu) {
$html = '<ul>';
$html .= '<li><a href="' . $menu->target . '">' . $menu->text . '</a>';
if ($menu->children) {
foreach ($menu->children as $subMenu) {
$html .= tree($subMenu);
}
}
$html .= '</li></ul>';
return $html;
}
echo '<div class="wiki-index"><div class="wiki-index-header">文章目录</div><div class="wiki-index-body">';
foreach ($rootMenu->children as $menu) {
echo tree($menu);
}
echo '</div></div>';