markmap 核心原理解析

2023-11-29 11:09:17 浏览数 (3)

内容提要,学习本文,你会学到:

  • markmap这个库实现的核心技术原理
  • 一种抽象思想,结构化数据转化为另外一种呈现的方式

Markmap 是一个非常有用的工具,它可以将 Markdown 文本转换成交互式的思维导图,我在工作中经常会用到这个工具,比如:

  • 在会议中使用 Markdown 记录要点,然后转换成思维导图,以便更直观地查看讨论的结构和关键点。
  • 将学习笔记转换成思维导图,方便后续直观的复习,毕竟文字的东西不如图容易记忆。

因此之前就了解了下这个工具的实现原理,今天整理了下之前的笔记,写了出来。

学习本文,我们不仅仅可以了解markmap这个库实现的技术原理,今天更重要的其实是,学会一种思想,即Markdown 由于其结构化的特性,可以很容易地被解析成 AST(抽象语法树),这为转换成各种不同格式提供了可能,今天markdown可以转换为 思维导图,明天就可以转换为PPT,这里面透露这的道理,我想表达的意思是,其实他们是相通的。先剧透一下,如下图。

Markmap实现的核心原理

先不讲这么多了,先来了解一下,markmap 的核心技术原理,markmap是开源的,其github地址在此,https://github.com/markmap/markmap ,6k的点赞,然后其issue也不多,让我没法拒绝去扒开他的原理一探究竟。

它的实现原理基于几个关键的技术点:

  1. Markdown 解析:Markmap 使用 Markdown 解析器(如 marked 或其他库)来解析输入的 Markdown 文本,将其转换成一个抽象语法树(AST)。这个 AST 描述了 Markdown 文本的结构,包括标题、列表、代码块等。
  2. 树形结构转换:将 Markdown 的 AST 转换成树形结构,这个结构更适合用来表示思维导图。在这个过程中,通常会将 Markdown 中的标题转换成思维导图的主节点和子节点。
  3. SVG 渲染:使用 D3.js 或类似的库来将树形结构渲染成 SVG 图形。D3.js 提供了强大的数据可视化工具,可以用来创建和操作 SVG 元素,从而生成动态的、可交互的思维导图。
  4. 交互性:Markmap 允许用户与生成的思维导图进行交互,比如展开或折叠节点,这通常是通过监听 DOM 事件并相应地更新 SVG 元素来实现的。

下面是这个过程的序列图

实际上,我们不难发现,树形结构转换就是这个库的重点即,怎么讲markdown结果的文本转换最终转换为可渲染成svg的语言的,带着这个重点,我们去看一看,他是如何实现的。

这个过程涉及到遍历 AST 并创建一个节点树,其中每个节点代表一个思维导图的节点。为了极度的简化,我整理出了一个简化的 JavaScript 伪代码,描述了这个转换过程的核心逻辑:

代码语言:javascript复制
function transformASTToTree(ast) {
  const rootNode = createNode('root');
  let currentNode = rootNode;

  for (const item of ast) {
    switch (item.type) {
      case 'heading':
        // 根据标题级别确定节点的深度
        const level = item.depth;
        // 如果当前标题级别更深,就创建一个子节点
        if (level > currentNode.level) {
          const childNode = createNode(item.text, level);
          currentNode.children.push(childNode);
          currentNode = childNode;
        } else {
          // 如果标题级别不深或者相同,就回溯到正确的父级
          while (currentNode.level >= level) {
            currentNode = currentNode.parent;
          }
          const siblingNode = createNode(item.text, level);
          currentNode.children.push(siblingNode);
          currentNode = siblingNode;
        }
        break;
      // 处理其他类型的节点...
    }
  }

  return rootNode;
}

function createNode(text, level = 0) {
  return {
    text,
    level,
    children: [],
    parent: null
  };
}

其整个过程可以使用下面的序列图表示:

以上就是这个库的核心原理。

思考一点有意思的

其实不难发现,还有一些库是将markdown转换为PPT,他们的思路都貌似出奇的一致,markdown其实是一种结构话的标记语言,那么,他就可以转化为 ast,然后通过ast转化为其他的语言,所以,这种思路很重要,那么markdown借助ast还可能萌发出什么新的玩法呢,我想大概可以有一下的玩法:

  • 将 Markdown 转换为代码注释或文档,甚至是基于特定模板的代码框架。
  • Markdown 中嵌入的数据可以通过 AST 转换成图表或其他可视化元素。
  • Markdown 文档可以转换成交互式的 Web 应用,实际上 vite里面已经支持,感兴趣的可以了解下。

这里之给出一个markdown转代码的示例,比如我们有一个这样的markdown文件

代码语言:javascript复制
# Class: Car

## Properties

- `string` color
- `int` year

## Methods

### Method: start

- Description: Start the car engine.
- Returns: `void`

### Method: getColor

- Description: Get the color of the car.
- Returns: `string`

我们就可以写一个工具

代码语言:javascript复制
import re

# 假设的 Markdown 文本
markdown_text = """
# Class: Car

## Properties

- `string` color
- `int` year

## Methods

### Method: start

- Description: Start the car engine.
- Returns: `void`

### Method: getColor

- Description: Get the color of the car.
- Returns: `string`
"""

# 解析类名
class_match = re.search(r'# Class: (\w )', markdown_text)
class_name = class_match.group(1) if class_match else 'UnknownClass'

# 解析属性
properties = re.findall(r'- `(\w )` (\w )', markdown_text)

# 解析方法
methods = re.findall(r'### Method: (\w ).*?- Returns: `(\w )`', markdown_text, re.DOTALL)

# 生成代码
code = f"class {class_name}:\n"
code  = "    def __init__(self):\n"
for prop_type, prop_name in properties:
    code  = f"        self.{prop_name} = None  # Expected type: {prop_type}\n"

for method_name, return_type in methods:
    return_statement = 'pass' if return_type == 'void' else f'return None  # Expected type: {return_type}'
    code  = f"\n    def {method_name}(self):\n"
    code  = f"        {return_statement}\n"

print(code)

下面是我们转换的结果,基本上就实现了将markdown转化为代码,虽然我们取巧,并没有用ast,但是思路上实际是一致,解析里面结构化的信息,来做转化。

将虽然,这种方式不见得比我们直接写代码来的快,但是毕竟这里打开了另外一种思路。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞