标签:文章 date 清除 sar 第一部分 tac 特殊情况 代码 alt
一、需求描述
在 Word 中编辑文档的时候,可以在视图中打开导航窗格来查看目录树
类似的,现在需要基于页面上的文章,渲染出一个这样的目录结构
在网页上这些标题都是通过 <h1> 这样的标签渲染的,而且段落与标题之间是兄弟节点的关系
所以第一步只需要获取到文章的根节点,然后遍历 <h1> 这样的兄弟节点,就能拿到初步的目录结构
但有一种特殊情况需要考虑:
可能文章中的第一个标题并不是 h1,而是更低层级的标题,比如 h3,但在显示上依然需要作为一级标题来展示,因为在 h3 之前没有更大的标题
同样的,在 h1 下面如果先出现了 h3,紧接着又出现了 h2,那么先出现的 h3 实际上和后面的 h2 处于一个层级
也就是说类似这样的结构:
<h3>标题3</h3>
<h4>标题4</h4>
<h1>标题1</h1>
<h2>标题2</h2>
<h1>标题1</h1>
<h4>标题4</h4>
<h3>标题3</h3>
<h2>标题2</h2>
需要展示为:
二、程序设计
虽然页面上的文章是一棵 DOM 树,但由于标题元素是块级元素,所以实际上需要处理的树节点是平铺的,只有一个层级
也就是说,不管是怎样的文档,最终都能处理成这样的结构:
const article = [
{ tag: ‘h3‘,content: ‘标题3‘ },
{ tag: ‘p‘, content: ‘这里是第一部分的内容‘ },
{ tag: ‘h4‘, content: ‘标题4‘ },
{ tag: ‘p‘, content: ‘这里是第二部分的内容‘ },
{ tag: ‘p‘, content: ‘上面说得很好,接下来再补充一点‘ },
{ tag: ‘h1‘, content: ‘标题1‘ },
{ tag: ‘h2‘, content: ‘标题2‘ },
{ tag: ‘h1‘, content: ‘标题1‘ },
{ tag: ‘p‘, content: ‘刚才有一点忘记说了‘ },
{ tag: ‘p‘, content: ‘我话讲完,谁赞成,谁反对‘ },
{ tag: ‘h4‘, content: ‘标题4‘ },
{ tag: ‘h3‘, content: ‘标题3‘ },
{ tag: ‘p‘, content: ‘不好意思,你刚才说什么我没听清‘ },
{ tag: ‘h2‘, content: ‘标题2‘ },
{ tag: ‘p‘, content: ‘现在我再问一次,谁赞成,谁反对‘ },
]
所以对于文档本身,只需要做一次遍历即可
但是对于文档目录,由于最终计算的是一个相对层级,所以也不太方便使用固定长度的数组来记录层级
所以最终的解决方案是维护一个栈来记录标题的层级关系
在一开始的时候,对于标题节点无论是几级标题,都直接压栈
后面每次处理标题,都和栈尾的标题进行比较,如果当前的标题层级更深,则压入栈内,否则清除栈尾,并比较前一位标题
在处理标题层级的同时,还需要另外维护一个记录前缀的栈,这两个栈是映射关系
最终可以通过这两个栈,得到目录的完整文案,甚至是缩进量,所以出参可以这样的结构:
const result = [
{ title: ‘1 标题‘, indent: 0 },
{ title: ‘1.1 标题‘, indent: 1 },
]
三、代码实现
function getHeadingList(list) {
if (!Array.isArray(list)) {
return;
}
const reg = /h(\d)/; // 使用正则来匹配标题节点
const levelStack = []; // 记录标题层级
const prefixStack = []; // 记录前缀
return list.reduce((res, node) => {
const { tag, content } = node || {};
const tagSplited = reg.exec(tag);
if (!tagSplited) return res;
updateLevelList(levelStack, prefixStack, Number(tagSplited[1]));
res.push({
title: `${prefixStack.join(".")} ${content}`,
indent: prefixStack.length - 1,
});
return res;
}, []);
}
function updateLevelList(levelStack, prefixStack, current) {
const idx = levelStack.length - 1;
const lastLevel = levelStack[idx];
if (!lastLevel || current > lastLevel) {
// 当前为最深层级,压入栈尾
levelStack.push(current);
prefixStack.push(1);
return;
}
if (current === lastLevel) {
// 层级相等时,只修改前缀
prefixStack[idx]++;
} else if (current < lastLevel) {
// 当前层级更高,先和上一层级对比
const preIndex = idx - 1;
const preLevel = levelStack[preIndex];
if (current > preLevel) {
// 如果上一层级比当前层级更高,即 [1, 3, 2] 这种情况
prefixStack[idx]++;
levelStack[idx] = current;
} else {
// 删除栈尾,继续递归
levelStack.splice(idx, 1);
prefixStack.splice(idx, 1);
updateLevelList(levelStack, prefixStack, current);
}
}
}
标签:文章 date 清除 sar 第一部分 tac 特殊情况 代码 alt
原文地址:https://www.cnblogs.com/wisewrong/p/14825769.html