Skip to content

Word 文档转 html

doc 和 docx 的区别

我们这里处理的仅为 .docx格式

word 文档的格式分为两种.doc.docx

  • .doc:Microsoft Word 的旧二进制格式(主要是 Word 97–2003 使用),基于复合文件二进制格式(Compound File Binary Format)。
  • .docx:从 Word 2007 开始采用的 Office Open XML(OOXML)格式,本质上是一个 ZIP 压缩包,内部由若干 XML 文件和资源(图片、样式等)组成。

主要区别(逐项)

  1. 格式与结构
  • .doc:专有二进制格式,难以解析与处理。
  • .docx:基于 XML + ZIP,易解析、标准化、可被第三方工具读取/生成。
  1. 文件体积与压缩
  • .doc:通常比 .docx 更大(取决内容),没有内置 ZIP 压缩。
  • .docx:由于 ZIP 压缩,通常更小,尤其包含大量图片或重复内容时更明显。
  1. 可扩展性与互操作性
  • .doc:依赖微软实现,第三方支持有限且常有差异。
  • .docx:开放标准(OOXML),跨平台、跨语言工具支持好,便于程序化处理(直接操作 XML)。
  1. 兼容性
  • .doc:在非常旧的 Word 版本中兼容,但现代 Office 更偏向 .docx。
  • .docx:现代 Office 默认格式,推荐使用。旧版 Word(2003 及以前)需安装兼容包才能打开 .docx。
  1. 宏与安全
  • .doc:可以含有宏(VBA),因为是二进制,难以检测/审计,安全风险较高。
  • .docx:不包含宏;如果需要宏,使用 .docm(OOXML 的宏启用格式)。.docx 更容易做静态检查和杀毒处理。
  1. 恢复与损坏率
  • .doc:损坏后恢复更困难(二进制结构复杂)。
  • .docx:由于是多个独立 XML 文件和资源,单个子文件损坏时有时能部分恢复,恢复工具更常见。
  1. 功能与特性支持

现代 Word 的新特性、样式和数据结构优先在 .docx 支持,某些现代功能不能完整回退到 .doc

  1. 技术细节(补充)
  • MIME 类型.doc 通常为 application/msword;.docx 为 application/vnd.openxmlformats-officedocument.wordprocessingml.document。
  • 文件签名.docx 实际上是 ZIP(可以改后缀为 .zip 解压查看内部 XML)。

转换

- 安装库

bash
pnpm add docx-preview @mrli-utils/asyncpool
  • docx-preview:核心转换库
  • @mrli-utils/asyncpool:并发控制函数

- 转换函数封装

js
// parseDocx.js
import { renderAsync } from "docx-preview";
import { uploadFileToCos } from "@/api/upload"; // 文件上传接口
import { asyncPool } from "@mrli-utils/asyncpool"; // 并发控制函数

export async function parseDocxToHtmlStr(currentDocument) {
  if (!currentDocument) return;
  const container = document.createElement("div");
  // 1. 解析docx
  await renderAsync(currentDocument, container, null, {
    useBase64URL: true,
    inWrapper: false,
    renderHeaders: false,
    renderFooters: false,
  });
  // 2. 合并section, 去掉分页
  const setionDoms = Array.from(container.querySelectorAll("section.docx"));
  const section = document.createElement("section");
  setionDoms.reduce((pre, cur) => {
    cur.childNodes.forEach((n) => pre.appendChild(n));
    container.removeChild(cur);
    return pre;
  }, section);

  section.className = "docx";
  container.appendChild(section);

  // 处理图片等文件
  const hasImg = container.querySelectorAll("img").length > 0;
  // 有图片才会执行图片上传
  if (hasImg) await parseImgSrc(container);

  return {
    html: container.outerHTML,
    docxDom: container,
  };
}

// 解析图片src属性
async function parseImgSrc(container) {
  if (!container) return;
  const observer = new MutationObserver(loadSrc);
  observer.observe(container, {
    attributes: true,
    subtree: true,
    attributeFilter: ["src"],
  });
  let count = 0;
  async function loadSrc() {
    // 3. 处理图片
    let images = container.querySelectorAll("img");
    const params = { uploadPath: "static/images" }; // 上传接口的queryString参数
    // 只有在src属性全部加载完成后才触发文件上传
    if (images.length > 0 && ++count >= images.length) {
      images = Array.from(images);
      // 组装文件上传接口所需参数
      const formDatas = images.map((img, i) => {
        // 将base64转为file
        const file = dataURLtoFile(
          img.src,
          `img_${Date.now()}_${i}.png`,
          "image/png"
        );
        const formData = new FormData(); // 上传接口的body参数
        formData.append("file", file);
        return [params, formData]; // 上传接口入参(有两个)
      });
      // 图片上传, 并发量10
      const resAry = await asyncPool(10, formDatas, uploadFileToCos); // 图片并发上传,并发量10
      resAry.forEach(({ data }, index) => {
        const { code, data: url } = data || {};
        if (code === 0 && url) {
          images[index].src = url;
        }
      });
    }
  }
}

// base64转file
function dataURLtoFile(dataurl, filename, mine) {
  let arr = dataurl.split(","),
    mime = mine || arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  const file = new File([u8arr], filename, { type: mime });
  file.filename = filename;
  return file;
}

- 使用

js
import { parseDocxToHtmlStr } from "./parseDocx.js";

const docxFile = ""; // docx文件

/* 
  html为转换后的html字符串
  docxDom为转换后的dom元素
*/
const { html, docxDom } = parseDocxToHtmlStr(docxFile);
// ....