Markdown 导出 Word 文档技术方案

Markdown 导出 Word 文档技术方案

一、整体架构

Markdown 源文本
marked.js 渲染
HTML 字符串
DOM 解析器
docx 库构建
Word 文档
Mermaid 预处理
SVG → PNG

核心设计原则:统一 HTML 源架构

  • Markdown 渲染后维护一份干净的 HTML 变量
  • 预览展示和 Word 导出共用同一 HTML 源
  • Mermaid SVG 转图片逻辑复用,确保一致性

二、导出流程详解

2.1 第一阶段:Markdown → HTML

javascript 复制代码
// 使用 marked.js 将 Markdown 转为 HTML
import { marked } from 'marked'
const htmlContent = marked.parse(markdownText)

2.2 第二阶段:Mermaid 预处理



遍历所有 pre.mermaid
是否已渲染 SVG?
提取 SVG 内容
调用 mermaid.render
SVG → PNG 转换
存入 mermaidImages Map

关键函数

  • collectMermaidImages() - 预收集所有 Mermaid 图表
  • svgToPngForWord() - SVG 转 PNG(base64)

2.3 第三阶段:HTML → docx 元素

h1-h6
p
pre
ul/ol
img
blockquote
table
创建临时 DOM 容器
解析 HTML 字符串
递归遍历 DOM 节点
节点类型?
创建 Heading
创建 Paragraph
创建代码块
递归处理列表
创建 ImageRun
创建引用块
创建 Table

2.4 第四阶段:生成 Word 文件

javascript 复制代码
const doc = new Document({
    sections: [{
        children: docxElements  // 转换后的元素数组
    }]
})
const buffer = await Packer.toBlob(doc)
await saveBlobToFile(buffer, filename)

三、遇到的问题及解决方案

问题 1:第三方库支持不足

项目 描述
现象 使用 html-to-docx 库导出时,代码块和图片丢失
原因 该库对复杂 HTML 结构(嵌套 table、inline style 包装)支持差
方案 弃用 html-to-docx,改用 docx 库自建解析器
经验 第三方库有局限性,核心功能需自主可控

问题 2:有序列表编号丢失

项目 描述
现象 <ol> 导出后无编号,只有文字内容
原因 <li> 第一个子元素是 <p> 时(如 <li><p>内容</p></li>),textBuffer 为空,flushTextBuffer() 不输出 prefix
方案 检测到 <p> 是首个子元素时,将 prefix 与 <p> 内容合并后再输出

问题代码结构

html 复制代码
<!-- 简单结构(正常)-->
<ol>
  <li>项目一</li>
</ol>

<!-- 复杂结构(编号丢失)-->
<ol>
  <li><p>项目一</p></li>
</ol>

修复逻辑

javascript 复制代码
if (childTag === 'p' && isFirst && !textBuffer.trim()) {
    // 首个子元素是 <p>,合并 prefix 和 <p> 内容
    textBuffer = child.textContent
    flushTextBuffer()  // 此时 prefix + textBuffer 一起输出
}

问题 3:嵌套列表内容丢失

项目 描述
现象 多级嵌套的 <ul>/<ol> 只输出第一层
原因 仅使用 li.textContent 获取内容,未递归遍历子元素
方案 实现通用递归解析架构,支持任意深度嵌套

错误做法

javascript 复制代码
// ❌ 只取文本,嵌套结构丢失
const text = li.textContent

正确做法

javascript 复制代码
// ✅ 递归处理所有子元素
function processElement(element, indentLevel) {
    for (const child of element.children) {
        if (child.tagName === 'UL' || child.tagName === 'OL') {
            result.push(...processElement(child, indentLevel + 1))
        }
        // ... 其他元素处理
    }
}

问题 4:内联元素错误换行

项目 描述
现象 这是**粗体**文字 被拆成三行
原因 <strong><em> 等内联元素被误判为块级元素,触发换行
方案 精确区分内联/块级元素,内联元素追加到 textBuffer,块级元素才触发 flush

元素分类

javascript 复制代码
// 块级元素(触发换行)
const blockTags = ['p', 'pre', 'div', 'ul', 'ol', 'li', 'blockquote', 'table', 'img']

// 内联元素(不换行,追加到 buffer)
const inlineTags = ['strong', 'em', 'code', 'a', 'span', 's', 'del', 'b', 'i', 'u']

问题 5:Mermaid CSS 残留污染

项目 描述
现象 导出的 Word 中出现 #mermaid-xxx{fill:#333;...} 乱码
原因 Mermaid 渲染后 <pre> 中残留 CSS 样式代码,被当作普通代码块输出
方案 正则检测 #mermaid-xxx + {...} 模式,显式跳过不输出

检测逻辑

javascript 复制代码
const text = preElement.textContent
const isMermaidCss = /#mermaid-[^\s]+/.test(text) && /\{[^}]*\}/.test(text)
if (isMermaidCss) {
    continue  // 跳过,不输出
}

问题 6:Buffer 类型不兼容

项目 描述
现象 saveBlobToFile 报错:blob.arrayBuffer is not a function
原因 html-to-docx 返回的是 Node.js Buffer,而非浏览器 Blob
方案 fileSaver.js 中兼容处理,检测类型后统一转换
javascript 复制代码
let arrayBuffer
if (blobOrBuffer instanceof Blob) {
    arrayBuffer = await blobOrBuffer.arrayBuffer()
} else if (Buffer.isBuffer(blobOrBuffer)) {
    arrayBuffer = blobOrBuffer.buffer
}

问题 7:HTML 预览列表无样式

项目 描述
现象 预览区的 <ol> 无编号,<ul> 无符号
原因 Tailwind CSS Preflight 重置了 list-style-type: none
方案 .prose 样式中手动覆盖
css 复制代码
.prose :deep(ol) {
    list-style-type: decimal;
}
.prose :deep(ul) {
    list-style-type: disc;
}

四、架构演进历程

v1.0.2 完善
v1.0.1 重构
v1.0.0 初版
html-to-docx 库
简单 HTML 结构
问题多:图片丢失、代码块丢失
迁移到 docx 库
自建 DOM 解析器
解决图片和代码块问题
通用递归架构
支持任意嵌套
精确内联/块级区分
智能文件命名


五、核心经验总结

序号 经验
1 自建解析器更可控:第三方库有局限性,核心功能需自主实现
2 递归处理是必须的:HTML 结构可无限嵌套,必须支持任意深度
3 内联/块级必须精确区分:错误分类会导致格式混乱
4 CSS 框架有副作用:Tailwind Preflight 会重置原生样式
5 统一 HTML 源架构:预览和导出共用同一数据源,确保一致性
6 预收集机制:Mermaid 图表先收集到 Map,解析时直接查表
相关推荐
busideyang1 小时前
MATLAB vs Rust在嵌入式领域的角色定位
开发语言·matlab·rust
ghie90901 小时前
蚁群全局最优算法:原理、改进与MATLAB实现
开发语言·算法·matlab
’长谷深风‘1 小时前
线程函数接口和属性
c语言·开发语言·线程·进程·软件编程
wangluoqi1 小时前
c++ 图论-强连通分量 小总结
开发语言·c++
Cxiaomu2 小时前
Python 文件解析: Excel / Word / PDF 的解析、处理、预览与下载
python·word·excel
啊哈哈哈哈哈啊哈哈2 小时前
AOP笔记
java·开发语言
晔子yy2 小时前
AI编程时代:简单聊聊Agent技术
开发语言·ai
xyq20242 小时前
Scala 提取器(Extractor)
开发语言
A懿轩A2 小时前
【Java 基础编程】Java 正则表达式实战:Pattern/Matcher、元字符与常用正则,验证与提取必备
java·开发语言·正则表达式