Html 转 Docx

131 阅读3分钟

最近工作中遇到一个md转docx的需求,故整理记录一下研究实现过程;

方案不用说,这种一般只能看有没有开源库实现了这个功能;

然后是前端做还是后端做的问题(都可以做);

前端实现:

经过一系列的搜索,最终锁定前端通常使用这两个库:

html‑to‑docx

html-to-docx 是一个 js 库,用于将 HTML 文档转换为 Microsoft Word 2007+、LibreOffice Writer、Google Docs、WPS Writer 等支持的 DOCX 格式。

它受到了html-docx-js项目的启发,但缓解了生成的文档与 Google Docs 和 libreOffice Writer 等不支持altchunks功能的文字处理器不兼容的问题。

目前它还不能直接在浏览器中使用,但已经针对 React 进行了测试。

它主要是配合 Nodejs 服务端使用

优点:功能相对丰富,支持复杂结构(表格、图片等)比一些老包强。

html‑docx‑js

既能在 现代浏览器 上使用,又能在 Nodejs 端使用

支持 UMD ,不支持 ESM

优点:轻量,基本需求可以满足。

缺点:对复杂样式、图片、表格等支持可能不如 html-to-docx 好。

前端实现步骤(html-docx-js):

项目情况:vue3+vite

模块化问题

  • 如果你在 Vue 3 / Vite / React 等现代项目中直接用 ESM:

    import htmlDocx from 'html-docx-js';
    

    会报错:With statements cannot be used with the "esm" output format due to strict mode → 因为源码使用了 with,而现代打包器(Vite/Rollup/ESM)在严格模式下不允许 with

  • 解决办法:

    1. 使用 UMD 版本,通过 <script> 标签挂全局对象。
    2. 或自己用 vite.config.js 配置 optimizeDepsbuild.commonjsOptions 强制兼容 CommonJS。

最终使用方法1 解决,方法2配置后没有解决报错。

项目中index.html 代码如下:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>md转docx</title>
    <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
    <!-- 引入库 -->
    <script src="https://unpkg.com/html-docx-js/dist/html-docx.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="src/main.ts"></script>
  </body>
</html>

引入之后,全局会添加一个方法: window.htmlDocx

组件中具体使用代码如下:

if (window.htmlDocx) {
  const html = `
    // ....
  `
  const buffer = window.htmlDocx.asBlob(html);
  const blob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'markdown.docx';
  a.click();
  URL.revokeObjectURL(url); // 释放URL.createObjectURL内存
}

总结:

以上代码就实现了 html 转 docx的功能,最终实现效果一般,稍微复杂一点的结构可能就不理想; 例如有 嵌套的 ol - li ul - li 等效果就很不理想;正常的 p、h<1-5>标签等还是可以的;

  • 如果需要转换的 html 比较简单这个方案还是可以的

后端实现:

现在前端都流行大前端,所以采用 nestjs + html-to-docx 接口实现此功能:

下载依赖: npm i html-to-docx -S

新建一个模块: nest g res fileOperation

file-operation.module.ts

import { Module } from '@nestjs/common';
import { FileOperationService } from './file-operation.service';
import { FileOperationController } from './file-operation.controller';
​
@Module({
  controllers: [FileOperationController],
  providers: [FileOperationService],
})
export class FileOperationModule {}
​

file-operation.service.ts

import { BadRequestException, Injectable } from '@nestjs/common';
// 引入方式这里注意一下,应为是 UMD,最好使用 require
const htmlToDocx = require('html-to-docx');
​
@Injectable()
export class FileOperationService {
  // html 转 docx
  async generateDocxFromHtml(html: string): Promise<Buffer> {
    if (typeof html !== 'string' || html.trim().length === 0) {
      throw new BadRequestException('参数 html 必须为非空字符串');
    }
    try {
      const fileBuffer = await htmlToDocx(html, null, {
        table: { row: { cantSplit: true } }, // 可选配置
        footer: false,
        pageNumber: false,
      });
      return fileBuffer;
    } catch (error) {
      console.log('error', error);
      throw new BadRequestException('生成 Word 文档失败');
    }
  }
}
​

file-operation.controller.ts

import { Controller, Post, Body, Res, BadRequestException } from '@nestjs/common';
import { FileOperationService } from './file-operation.service';
import { FastifyReply } from 'fastify';
​
@Controller('file-operation')
export class FileOperationController {
  constructor(private readonly fileOperationService: FileOperationService) {}
​
  @Post('htmlToDocx')
  async htmlToDocx(@Body() body: { html: string }, @Res() res: FastifyReply) {
    if (!body || typeof body.html !== 'string') {
      throw new BadRequestException('请求体缺少 html 字符串');
    }
    const buffer = await this.fileOperationService.generateDocxFromHtml(body.html);
    res.header('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
    res.header('Content-Disposition', 'attachment; filename="document.docx"');
    return res.raw.end(buffer);
  }
}

以上代码就实现了一个html 转 docx的接口;

总结:

经过测试 html-to-docx 实现的效果要比 html-docx-js 效果好很多;

其它:

网络上还有很多其它的实现方式:

  • pandoc

  • htmldocx(Python 库)

  • 商业付费:AIEditor 自带富文本转word、pdf等

    测试了效果比较好

  • 商业/付费库:如 Aspose.Words for .NET