zoukankan      html  css  js  c++  java
  • JavaScript 遍历文档生成目录结构

    一、需求描述

    在 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); } } }
  • 相关阅读:
    取消svn版本控制
    Sublime Text 2搭建Go开发环境(Windows)
    RESTful API 设计指南
    laravel-v5.1分页并带参数
    laravel使用的模板引擎 blade
    控制器中添加DB类才可以操作数据库表中的数据
    简单hello world
    创建控制器命令
    命令创建模型类
    IntelliJ IDEA类头注释和方法注释
  • 原文地址:https://www.cnblogs.com/wisewrong/p/14825769.html
Copyright © 2011-2022 走看看