内容提要,学习本文,你会学到:
markmap
这个库实现的核心技术原理- 一种抽象思想,结构化数据转化为另外一种呈现的方式
Markmap
是一个非常有用的工具,它可以将 Markdown 文本转换成交互式的思维导图
,我在工作中经常会用到这个工具,比如:
- 在会议中使用 Markdown 记录要点,然后转换成思维导图,以便更直观地查看讨论的结构和关键点。
- 将学习笔记转换成思维导图,方便后续直观的复习,毕竟文字的东西不如图容易记忆。
因此之前就了解了下这个工具的实现原理,今天整理了下之前的笔记,写了出来。
学习本文,我们不仅仅可以了解markmap这个库实现的技术原理,今天更重要的其实是,学会一种思想,即Markdown 由于其结构化的特性,可以很容易地被解析成 AST(抽象语法树),这为转换成各种不同格式提供了可能,今天markdown可以转换为 思维导图,明天就可以转换为PPT,这里面透露这的道理,我想表达的意思是,其实他们是相通的。先剧透一下,如下图。
Markmap实现的核心原理
先不讲这么多了,先来了解一下,markmap 的核心技术原理,markmap是开源的,其github地址在此,https://github.com/markmap/markmap ,6k的点赞,然后其issue也不多,让我没法拒绝去扒开他的原理一探究竟。
它的实现原理基于几个关键的技术点:
- Markdown 解析:Markmap 使用 Markdown 解析器(如
marked
或其他库)来解析输入的 Markdown 文本,将其转换成一个抽象语法树(AST)。这个 AST 描述了 Markdown 文本的结构,包括标题、列表、代码块等。 - 树形结构转换:将 Markdown 的 AST 转换成树形结构,这个结构更适合用来表示思维导图。在这个过程中,通常会将 Markdown 中的标题转换成思维导图的主节点和子节点。
- SVG 渲染:使用 D3.js 或类似的库来将树形结构渲染成 SVG 图形。D3.js 提供了强大的数据可视化工具,可以用来创建和操作 SVG 元素,从而生成动态的、可交互的思维导图。
- 交互性: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腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!