notion converter重构 - 解析与渲染

最近Notion Converter完成了一次非常大的更新,彻底重构了底层的解析和渲染逻辑,这篇文章想和大家分享一下重构历程。同时我也把解析器开源在了Github

先大概介绍一下老版本的逻辑吧。因为我们一开始就不打算让用户走Notion集成的方式使用插件,会很麻烦,所以解析都是通过jQuery读取Notion页面的dom结构,然后根据class执行不同的渲染逻辑。代码结构大概是这样

kotlin 复制代码
for (let i = 0; i < $notionPageChildrenNodes.length; i++) {
  let $this = $($notionPageChildrenNodes[i]);
  const className = $this.attr("class");
  if (className.indexOf("notion-header-block") !== -1) {
    $wechatOutput.append(this.wechatRender.renderHeader($this));
  } else if (className.indexOf("notion-text-block") !== -1) {
    $wechatOutput.append(this.wechatRender.renderText($this));
  }
}

这种方式在大部分正常场景都工作的很好,但在一些复杂场景下,就无法解析了。

Notion的页面结构是非常自由的,你可以在段落里缩进段落,列表里写引用块,代码块,并列多个表格等等。这种无限嵌套的结构在老版本里是无法解析的,即使我在里面加了一些解析缩进块的逻辑,也做不到解析无限嵌套,同时这个逻辑也影响了复制为markdown的功能,于是就萌生了重构的念头。在老版本里为什么需要通过jQuery来解析渲染?因为需要获取到dom节点的内容。有没有不通过jQuery就能获取到dom节点内容的方法?有,浏览器原生的DOMParser。它接收html字符串,然后解析成dom树的结构返回,当然原生的DOMParser不是很友好,我选择了开源的html-dom-parser,专注于解析html,并且优化了返回的数据结构。于是重构思路呼之欲出,即

  1. 先通过html-dom-parser把Notion文章解析成中间态的数据结构

    vbnet 复制代码
    export type DomNode = {
    type: string // h1, p, text...
    children: DomNode[]
    text: string // 如果type=text时的文本
    }
    ​
  2. 创建微信公众号和markdown渲染器,都通过读取解析的结果进行渲染,只不过一个渲染html,另一个渲染markdown

计算机领域有一句经典语录,程序=算法+数据结构,这么一看上面的思路完美符合这句语录

贴一下重构后的伪代码

kotlin 复制代码
function doParse(element) {
  const classes = element.attribs.class
    if (classes.indexOf('notion-header-block') !== -1) {
      const children = []
      this.parseHeader(element, children)
      return {
        type: 'h1',
        children
      }
    }
}
​
parseHeader(element, children) {
    for (const c of element.children) {
      if (c.type === 'text') {
        children.push(this.parseText(c))
      } else {
        if (c.attribs.hasOwnProperty('class')) {
          const classes = c.attribs.class
          if (classes.split(' ').some(it => this.notionBlockClasses.indexOf(it) !== -1)) {
            children.push(this.doParse(c))
          } else {
            this.parseHeader(c, children)
          }
        } else {
          this.parseHeader(c, children)
        }
      }
    }
  },
​

这里重点讲一下递归的思路。Notion里每一个块嵌套的dom层级都很深,加上无法通过jQuery直接查找层级获取数据,所以递归的思路就是遍历当前节点的子节点,如果是文本就解析。否则判断子节点是否有class,如果有,判断class是否在目标解析class集合里,这一步就是解析嵌套块的关键,通过这一步就实现了像标题嵌套链接,列表嵌套代码块的解析,以及无限层级缩进的解析。

解析中最麻烦的是列表的解析,Notion的列表块不是用的原生ul和ol实现的,而且用的复杂的div布局,所以Notion的列表块序号可以做到非常自由,可以随意从任意数字开始,但是公众号里用的是原生的列表标签,所以我在解析完Notion的列表块后,必须把相邻列表块进行合并,转换成标准列表结构。同时还要记录列表项的层级,在渲染的时候设置对应层级的list-type

最后解析出来的结果如下

原文链接

相关推荐
じòぴé南冸じょうげん9 小时前
小程序的project.private.config.json是无依赖文件,那可以删除吗?
前端·小程序·json
会豪10 小时前
Electron主进程渲染进程如何优雅的进行通信
前端
jianghaha201110 小时前
前端 Word 模板参入特定数据 并且下载
前端·word
跟橙姐学代码10 小时前
轻松搞定 Python 模块与包导入:新手也能秒懂的入门指南
前端·python·ipython
aiwery10 小时前
大模型场景下的推送技术选型:轮询 vs WebSocket vs SSE
前端·agent
会豪10 小时前
前端插件-不固定高度的DIV如何增加transition
前端
却尘10 小时前
Server Actions 深度剖析(2):缓存管理与重新验证,如何用一行代码干掉整个客户端状态层
前端·客户端·next.js
小菜全10 小时前
Vue 3 + TypeScript 事件触发与数据绑定方法
前端·javascript·vue.js
Hilaku10 小时前
面试官开始问我AI了,前端的危机真的来了吗?
前端·javascript·面试
shellvon11 小时前
前端攻防:揭秘 Chrome DevTools 与反调试的博弈
前端·逆向