基于Node.js的Markdown到Word文档转换工具,利用Pandoc命令行工具实现高质量、无损的文档转换。工具支持单个文件转换和批量转换,能够保留Markdown文档的格式、表格、列表等元素,并支持从YAML front matter中提取元数据。

注意!
需要提前全局安装Pandoc,否则会报错,脚本无法正常运行,Pandoc安装需要科学上网。
Pandoc官网下载:下载地址
百度网盘下载:下载地址
项目结构
├── package.json # 项目配置和依赖管理
├── pandoc-converter.js # 主程序文件
├── test.md # 测试用的Markdown文件(包含表格数据)
├── test.docx # 转换生成的Word文档
├── 执行命令.txt # 使用示例
└── .gitignore # Git忽略文件配置
依赖分析
核心依赖
- Pandoc:跨平台的文档转换工具,支持多种文档格式转换
- Node.js:运行环境
Node.js包依赖(package.json)
js-yaml:解析YAML front matterfs-extra:增强的文件系统操作glob:文件模式匹配(动态导入)- 多个markdown-it插件:用于其他转换方案(本项目主要使用Pandoc)
核心代码分析:pandoc-converter.js
1. 文件头部和模块导入
javascript
#!/usr/bin/env node
// Shebang指令,表示这是一个可执行的Node.js脚本
const fs = require('fs');
const path = require('path');
const { execSync, spawn } = require('child_process');
const yaml = require('js-yaml');
// 导入必要的Node.js核心模块和第三方模块
2. PandocConverter类
构造函数
javascript
class PandocConverter {
constructor() {
this.checkPandoc(); // 初始化时检查Pandoc是否安装
}
checkPandoc()方法
javascript
checkPandoc() {
try {
// 根据操作系统使用不同的命令检查Pandoc
if (process.platform === 'win32') {
execSync('where pandoc', { stdio: 'pipe' }); // Windows系统
} else {
execSync('which pandoc', { stdio: 'pipe' }); // macOS/Linux系统
}
console.log('✓ 检测到 Pandoc');
return true;
} catch (error) {
// 如果未检测到Pandoc,提供安装指南
console.error('❌ 未检测到 Pandoc!');
console.error('请安装 Pandoc:');
console.error(' - macOS: brew install pandoc');
console.error(' - Ubuntu/Debian: sudo apt-get install pandoc');
console.error(' - Windows: 下载地址: https://pandoc.org/installing.html');
console.error(' - 或使用: winget install JohnMacFarlane.Pandoc');
return false;
}
}
实现原理:
- 使用
execSync同步执行系统命令检查Pandoc是否在PATH中 - 跨平台兼容:Windows使用
where命令,其他系统使用which命令 - 提供详细的安装指南帮助用户解决问题
convert()方法 - 核心转换功能
javascript
async convert(inputPath, outputPath) {
try {
console.log(`正在转换: ${inputPath} -> ${outputPath}`);
// 读取文件内容以提取可能的front matter
const content = await fs.promises.readFile(inputPath, 'utf-8');
const { metadata } = this.extractFrontMatter(content);
// 构建pandoc命令参数
const args = [
inputPath,
'-o', outputPath,
'--standalone', // 生成完整的文档(包含HTML头部等)
'--toc', // 生成目录
'--toc-depth=3' // 目录深度为3级
];
// 添加metadata(从front matter提取)
if (metadata.title) {
args.push(`--metadata=title:${metadata.title}`);
}
if (metadata.author) {
args.push(`--metadata=author:${metadata.author}`);
}
// 执行pandoc命令
const child = spawn('pandoc', args, { stdio: 'inherit' });
return new Promise((resolve, reject) => {
child.on('close', (code) => {
if (code === 0) {
console.log(`✅ 转换完成: ${outputPath}`);
resolve(true);
} else {
reject(new Error(`Pandoc 转换失败,退出码: ${code}`));
}
});
child.on('error', (error) => {
reject(error);
});
});
} catch (error) {
console.error('转换失败:', error.message);
throw error;
}
}
实现原理:
- 文件读取:使用Node.js的fs模块异步读取Markdown文件
- 元数据提取 :调用
extractFrontMatter()方法提取YAML front matter - 命令构建 :构建Pandoc命令行参数
--standalone:生成完整的独立文档--toc:自动生成目录--toc-depth=3:目录包含3级标题--metadata:设置文档元数据(标题、作者等)
- 进程执行 :使用
spawn()创建子进程执行Pandoc命令stdio: 'inherit':将Pandoc的输出直接显示在控制台
- 异步处理 :返回Promise,监听子进程的
close和error事件
extractFrontMatter()方法
javascript
extractFrontMatter(content) {
const lines = content.split('\n');
// 检查是否包含YAML front matter(以---开始和结束)
if (lines[0] && lines[0].trim() === '---') {
let yamlEnd = -1;
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '---') {
yamlEnd = i;
break;
}
}
if (yamlEnd > 0) {
try {
// 提取YAML内容并解析
const yamlContent = lines.slice(1, yamlEnd).join('\n');
const metadata = yaml.load(yamlContent) || {};
return { metadata };
} catch (e) {
// 忽略解析错误,返回空元数据
}
}
}
return { metadata: {} };
}
实现原理:
- YAML front matter识别 :检查文件是否以
---开始 - 内容提取 :找到结束的
---,提取之间的内容 - YAML解析 :使用
js-yaml库解析YAML内容 - 错误处理:如果解析失败,返回空元数据对象
convertBatch()方法 - 批量转换
javascript
async convertBatch(inputDir, outputDir, pattern = '**/*.md') {
try {
const { default: glob } = await import('glob'); // 动态导入glob模块
const files = glob.sync(pattern, { cwd: inputDir });
console.log(`找到 ${files.length} 个Markdown文件`);
for (const file of files) {
const inputPath = path.join(inputDir, file);
const outputPath = path.join(
outputDir,
file.replace(/\.md$/i, '.docx') // 将.md扩展名替换为.docx
);
await this.convert(inputPath, outputPath);
}
console.log(`✅ 批量转换完成,共转换 ${files.length} 个文件`);
} catch (error) {
console.error('批量转换失败:', error.message);
throw error;
}
}
实现原理:
- 动态导入 :使用ES模块的动态导入功能导入
glob模块 - 文件匹配:使用glob模式匹配所有Markdown文件
- 路径处理 :使用
path.join()构建完整的输入输出路径 - 循环转换 :遍历所有匹配的文件,依次调用
convert()方法 - 异步控制 :使用
await确保顺序执行
3. 命令行接口(CLI)
javascript
// 检查是否直接运行此脚本(而不是作为模块导入)
if (require.main === module) {
const args = process.argv.slice(2); // 获取命令行参数
if (args.length < 1) {
// 显示帮助信息
console.log(`
🎯 Pandoc Markdown 转 Word 工具
================================
使用方法:
node pandoc-converter.js <输入文件.md> [输出文件.docx]
示例:
node pandoc-converter.js README.md
node pandoc-converter.js README.md output.docx
批量转换:
node pandoc-converter.js batch <输入目录> [输出目录]
示例:
node pandoc-converter.js batch ./docs ./output
`);
process.exit(1);
}
const converter = new PandocConverter();
async function run() {
if (args[0] === 'batch') {
// 批量转换模式
const inputDir = args[1] || './';
const outputDir = args[2] || './word_output';
await converter.convertBatch(inputDir, outputDir);
} else {
// 单个文件转换模式
const inputFile = args[0];
let outputFile = args[1];
if (!fs.existsSync(inputFile)) {
console.error(`❌ 文件不存在: ${inputFile}`);
process.exit(1);
}
// 如果没有指定输出文件,自动生成.docx文件名
if (!outputFile) {
const ext = path.extname(inputFile).toLowerCase();
if (ext === '.md' || ext === '.markdown') {
outputFile = inputFile.replace(/\.(md|markdown)$/i, '.docx');
} else {
outputFile = inputFile + '.docx';
}
}
await converter.convert(inputFile, outputFile);
}
}
run().catch(console.error);
}
命令行接口设计:
- 参数解析 :使用
process.argv获取命令行参数 - 两种模式 :
- 单个文件转换:
node pandoc-converter.js <输入文件> [输出文件] - 批量转换:
node pandoc-converter.js batch <输入目录> [输出目录]
- 单个文件转换:
- 智能默认值 :
- 未指定输出文件时,自动将
.md扩展名替换为.docx - 批量转换时,默认输入目录为当前目录,输出目录为
./word_output
- 未指定输出文件时,自动将
- 错误处理:检查文件是否存在,提供清晰的错误信息
4. 模块导出
javascript
module.exports = PandocConverter;
// 导出类,允许其他模块导入和使用
实现原理详解
1. Pandoc的核心作用
Pandoc是一个"文档转换的瑞士军刀",支持多种文档格式之间的转换。在本工具中:
- 输入格式:Markdown(支持CommonMark、GitHub Flavored Markdown等)
- 输出格式:DOCX(Microsoft Word文档格式)
- 转换过程:Pandoc将Markdown解析为抽象语法树(AST),然后根据目标格式生成对应的文档结构
2. 无损转换的关键特性
- 表格支持:完美转换Markdown表格为Word表格
- 目录生成:自动根据标题层级生成目录
- 样式保留:保留粗体、斜体、代码块等格式
- 元数据支持:支持YAML front matter中的标题、作者等信息
- 跨平台兼容:生成的Word文档可在不同版本的Microsoft Word中打开
3. 异步处理机制
- 使用
async/await语法处理文件读取和进程执行 spawn()创建非阻塞的子进程,避免阻塞主线程- Promise封装提供更好的错误处理和流程控制
4. 错误处理和健壮性
- 检查Pandoc是否安装,提供清晰的安装指南
- 文件存在性检查,避免无效路径
- try-catch块捕获和处理异常
- 详细的日志输出,便于调试
使用示例
1. 单个文件转换
bash
# 基本用法(自动生成test.docx)
node pandoc-converter.js test.md
# 指定输出文件名
node pandoc-converter.js test.md output.docx
# 转换其他目录的文件
node pandoc-converter.js ../docs/README.md
2. 批量转换
bash
# 转换当前目录所有Markdown文件
node pandoc-converter.js batch .
# 转换指定目录到指定输出目录
node pandoc-converter.js batch ./docs ./output
本文原创,原创不易,如需转载,请联系作者授权。