主题
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 文件和资源(图片、样式等)组成。
主要区别(逐项)
- 格式与结构
.doc:专有二进制格式,难以解析与处理。.docx:基于 XML + ZIP,易解析、标准化、可被第三方工具读取/生成。
- 文件体积与压缩
.doc:通常比.docx更大(取决内容),没有内置 ZIP 压缩。.docx:由于 ZIP 压缩,通常更小,尤其包含大量图片或重复内容时更明显。
- 可扩展性与互操作性
.doc:依赖微软实现,第三方支持有限且常有差异。.docx:开放标准(OOXML),跨平台、跨语言工具支持好,便于程序化处理(直接操作 XML)。
- 兼容性
.doc:在非常旧的 Word 版本中兼容,但现代 Office 更偏向 .docx。.docx:现代 Office 默认格式,推荐使用。旧版 Word(2003 及以前)需安装兼容包才能打开 .docx。
- 宏与安全
.doc:可以含有宏(VBA),因为是二进制,难以检测/审计,安全风险较高。.docx:不包含宏;如果需要宏,使用 .docm(OOXML 的宏启用格式)。.docx更容易做静态检查和杀毒处理。
- 恢复与损坏率
.doc:损坏后恢复更困难(二进制结构复杂)。.docx:由于是多个独立 XML 文件和资源,单个子文件损坏时有时能部分恢复,恢复工具更常见。
- 功能与特性支持
现代 Word 的新特性、样式和数据结构优先在 .docx 支持,某些现代功能不能完整回退到 .doc。
- 技术细节(补充)
MIME 类型:.doc通常为 application/msword;.docx为 application/vnd.openxmlformats-officedocument.wordprocessingml.document。文件签名:.docx实际上是 ZIP(可以改后缀为 .zip 解压查看内部 XML)。
转换
- 安装库
bash
pnpm add docx-preview @mrli-utils/asyncpooldocx-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);
// ....