基于Node.js+Pandoc实现Markdown文件无损转换为Word文档的小工具

基于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 matter
  • fs-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;
  }
}

实现原理

  1. 文件读取:使用Node.js的fs模块异步读取Markdown文件
  2. 元数据提取 :调用extractFrontMatter()方法提取YAML front matter
  3. 命令构建 :构建Pandoc命令行参数
    • --standalone:生成完整的独立文档
    • --toc:自动生成目录
    • --toc-depth=3:目录包含3级标题
    • --metadata:设置文档元数据(标题、作者等)
  4. 进程执行 :使用spawn()创建子进程执行Pandoc命令
    • stdio: 'inherit':将Pandoc的输出直接显示在控制台
  5. 异步处理 :返回Promise,监听子进程的closeerror事件
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;
  }
}

实现原理

  1. 动态导入 :使用ES模块的动态导入功能导入glob模块
  2. 文件匹配:使用glob模式匹配所有Markdown文件
  3. 路径处理 :使用path.join()构建完整的输入输出路径
  4. 循环转换 :遍历所有匹配的文件,依次调用convert()方法
  5. 异步控制 :使用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获取命令行参数
  • 两种模式
    1. 单个文件转换:node pandoc-converter.js <输入文件> [输出文件]
    2. 批量转换: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

本文原创,原创不易,如需转载,请联系作者授权。

相关推荐
森森-曦12 小时前
在word中如何设置从第二页开始页码编号
word
2501_9428189120 小时前
AI 多模态全栈项目实战:Vue3 + Node 打造 TTS+ASR 全家桶!
vue.js·人工智能·node.js
爱学习 爱分享20 小时前
word中批量替换
word
selina892121 小时前
word中脚注编号如何设置不显示?
word
叫我莫言鸭1 天前
关于word生成报告的POI学习
学习·word
前端流一1 天前
[疑难杂症] 浏览器集成 browser-use 踩坑记录
前端·node.js
AscendKing1 天前
java poi word首行插入文字
java·c#·word
旺仔nai糖1 天前
关于重装word后endnote无法正常使用的问题
word
大布布将军1 天前
⚡后端安全基石:JWT 原理与身份验证实战
前端·javascript·学习·程序人生·安全·node.js·aigc