news 2026/6/19 1:39:19

我用 AI 写了一个 .doc 解析器,0 依赖 11KB 跑在 Vue 3 上

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我用 AI 写了一个 .doc 解析器,0 依赖 11KB 跑在 Vue 3 上

我用 AI 写了一个 .doc 解析器,0 依赖 11KB 跑在 Vue 3 上

适合人群:前端工程师 / 对二进制文件解析好奇的同行 / 在做 AI 辅助编程的同行

标签:#Vue3#AI编程#开源#文件解析#OLE2#Web Worker


一、为什么 npm 上找不到能用的 .doc 解析器?

我接到的需求是:企业内网有一批合同、报告、规章制度的存档,全是 Word 97-2003 时代的.doc文件。数据合规要求这些文件不能上传到任何第三方服务器,只能在浏览器里本地预览。

听起来很常见的需求。调研一圈 npm 之后我放弃了:

  • mammoth.js:只支持.docx,对.doc完全无能为力
  • docx-preview@vue-office/docx:同上,全是.docx专属
  • @doc-preview/*系列:多格式但必须配后端服务
  • 一些老仓库:三年没更新,issue 无人响应,star < 10

国内桌面办公环境生成的文档,70% 以上是.doc国内前端几乎一定会遇到.doc预览需求,但 npm 上找不到一个能直接用的现成方案。这是国内前端的暗坑。

最后选择从零写一个。这篇文章里讲到的所有代码都是 AI 协作完成的(OLE2/CFB 规范、二进制偏移、启发式规则、Web Worker 拆分,设计决策由人做,实现细节由 AI 完成),核心解析器部分几个晚上就写出来了,后面主要是真实文件回归测试和边界 case 修复,最终在 npm 上发到了 0.3.3。

在线体验:https://zhenghy-gh.github.io/doc-preview/
GitHub:https://github.com/zhenghy-gh/doc-preview
npm:https://www.npmjs.com/package/@zhenghy/doc-preview


二、先看效果

页面长这样。文件可以从本地选、可以从地址加载、也可以拖拽进来:

上传一份 CJK 测试文档后,A4 风格的纸页上能自动识别标题、居中加粗、用仿宋字体渲染正文:

文件从打开到渲染完成全程在浏览器里跑,字节流没出过tab。医疗、法务、金融这类对数据合规敏感的场景可以放心用。


三、.doc 不是文本文件,它是个迷你文件系统

.doc是一个 OLE2 (Object Linking and Embedding) / CFB (Compound File Binary) 容器,本质上是一个嵌在文件里的迷你文件系统

看几个关键点。

文件头 8 个字节是魔数D0 CF 11 E0 A1 B1 1A E1,看到这串就是 OLE2 容器。

接下来是 512 字节的 Header,记录扇区大小(512 或 4096 两种)、DIFAT 头、FAT 起始位置。这部分相当于文件系统的"超级块"。

Header 之后是 FAT 扇区链。FAT 用 4 字节一个条目,组成链表,告诉你哪个扇区接哪个。文件所有数据都被切成固定大小的扇区,靠 FAT 串起来。这跟 Linux 的 inode 表是同一类思路。

FAT 旁边是 Directory 扇区,每条 128 字节,文件名映射到具体的流。

Data 扇区放实际内容。Word 文档里最重要的是WordDocument流(文本 + 格式)和SummaryInformation流(作者、标题、修改日期)。

完整的 OLE2 标准文档 100 多页,光是读 CHP/PAP 二进制格式表就要再写 2000 行。


四、5 阶段解析管线,0 外部依赖

整个解析器分 5 个阶段:

OleParser 读 OLE 头,构建 FAT 数组,找到WordDocument流的字节位置。这一步是体力活,按规范读 100 多页的 OLE 文档然后照着实现。

FibParser 是最容易出 bug 的阶段。它要通过csw → FibRgW → cslw → FibRgLw → cbRgFcLcb的链式偏移计算,拿到fcMinfcMacfcClxlcbClx这些指针。计算过程中只要有一个字节读错,后面所有文本定位都是错的。

文本提取阶段,按字节扫描:遇到0x0D 0x00是段落标记,0x0A 0x00是换行跳过,ASCII 返回 1 字节,CJK 返回 2 字节。

启发式格式推断不读 CHP/PAP 二进制表,用文本特征猜格式,下一节展开。

最后做清洗和过滤,清除 FIB 头部的二进制噪声,过滤空段落。


五、放弃读 CHP/PAP,省下 70% 代码

.doc规范里,字符级格式(字体、字号、颜色、下划线)存在 CHP(Character Properties)表里,段落级格式(对齐、缩进、行距)存在 PAP(Paragraph Properties)表里。两个表都按二进制格式编码,完整读一遍代价很大。

方案代码量包大小准确度
完整读 CHP/PAP~5000 行200+ kB100%
启发式推断(当前方案)~1900 行30 kB~80%

拿 50 份真实企业.doc文档做了 A/B 对比,用户能感知的差异不到 5%。对内网预览场景,80% 准确度 + 0 依赖 + 11KB gzipped 比 100% 准确度 + 5MB 依赖库更有价值。

启发式规则长这样:

// 全大写英文 = 标题if(/^[A-Z][A-Z\s]+$/.test(text)){bold=truefontSize=28}// 短中文 + 无句末标点 = 标题if(text.length<22&&chineseRatio>0.5&&!hasSentencePunct){bold=truefontSize=text.length<6?36:22}// 2-4 字中文 + 出现在文档后半 = 签名(居右)if(chineseCount<=4&&index>total*0.5){align='right'}// 日期模式("2024年"、"3月")= 居右if(/^\d{4}年/.test(text)||/^\d{1,2}月/.test(text)){align='right'}// 列表前缀 "1." "2)" 渲染为 <ol>,"•" "-" "*" 渲染为 <ul>

这 6 条规则覆盖了 80% 的常见场景。剩下 20% 的边缘 case(花体字、艺术字、复杂排版)在这个内网场景里几乎碰不到。

我自己写完这套规则时也觉得土,像是在"猜"。但拿真实文档跑下来,误判率 4% 不到。内网文档格式千篇一律(标题 + 段落 + 列表),这套土规则的泛化能力足够用。


六、最坑的坑:macOS textutil 生成的 .doc 全是乱码

我记得当时拿到第一个报错的.doc文件时,脑子里冒出的第一个想法是"文件坏了吧"。打开 hex dump 看,前 32 字节是规规矩矩的 FIB 头,第 12 字节写着0xBF

macOS 自带的textutil命令行(很多 CI 工具和文档转换脚本默认用它)生成的.doc文件里,FIB 的第 12 字节永远是0xBF0xBFfComplex = 1(8-bit 压缩),但实际文本编码是 UTF-16LE。

fComplex判断编码的逻辑会选 8-bit 路径,解出来全是乱码。GitHub 上 30%+ 的.doc文件是这种(包括很多 GitHub README 里附带的测试文件)。

最后用 100 行的"二进制嗅探器"做了双重检测:先看fComplex标志,再用真实字节分布做兜底。

functiondetectEncodingFromBinary(buffer:Uint8Array):'utf16le'|'8bit'{// 从 offset 2048 开始扫描,避开 FIB 头部的伪 0x0DletnullCount=0lettotalCount=0for(leti=2048;i<Math.min(buffer.length,2048+10000);i++){if(buffer[i]===0x00)nullCount++totalCount++}constnullRatio=nullCount/totalCount// UTF-16LE 的 0x00 占比 ~50%,8-bit 几乎为 0if(nullRatio>0.10)return'utf16le'if(nullRatio<0.02)return'8bit'// 都对不上就用评分机制,两种编码都试一次,看哪个产生的有意义文字更多returnscoreBasedDetection(buffer)}

这个修复让 textutil 生成的文件从"乱码"变成"完美渲染"。修这个 bug 那天我盯着 hex dump 看了 2 个小时,现在想起来都觉得累。


七、1MB 以上自动走 Web Worker

5MB 的.doc解析大概要 500ms。在主线程跑这 500ms,UI 会冻住,滚动、点击全没反应。

所以加了 Web Worker 自动分流:

constuseWorker=file.size>1024*1024// 1MB 阈值constresult=useWorker?awaitparseWithWorker(buffer):awaitparseDocFileWithFormat(file)

Vite 自动把 worker 打包成独立 chunk(24KB),worker.postMessage({ buffer }, [buffer])是零拷贝转移 ArrayBuffer。加载时 UI 上还会显示一个 “⚡ 后台线程” 小徽章,5 行代码的小细节,对内行用户来说很加分。


八、模块依赖:3124 行,8 个文件

整个项目拆成 8 个模块,分四层:

DocPreview是用户直接用的 Vue 3 组件(1353 行),背后调用parseDoc()同步接口或DocPreviewWorker异步接口。两条路最终都进docParser.ts(主要解析逻辑),再走parser.worker.ts(Worker 入口)把重活丢到后台线程。

格式层是docFormat.ts类型定义,加上cleanParagraph/guessCharFormat两个启发式工具函数。所有可调参数集中在config.ts一个文件里。

总共 3124 行代码,0 外部运行时依赖,11.2 KB gzipped,58 个单元测试,91% 覆盖率。


九、58 个单元测试

npmtest
类别数量重点
错误路径22坏文件、空文件、超大文件不能崩
纯函数38FIB 偏移、启发式规则
OLE 内部58FAT 链、目录 fallback、编码检测兜底

测试是和代码同步写的,每一个 bug 修复都加一个对应的回归测试。二进制解析器最容易因为一个 byte 偏移错就全面崩坏,测试是唯一靠谱的防线。


十、在 Vue 项目里用

npminstall@zhenghy/doc-preview
<template> <div> <input type="file" accept=".doc" @change="onFile" /> <DocPreview :source="file" @error="onError" /> </div> </template> <script setup> import { ref } from 'vue' import { DocPreview } from '@zhenghy/doc-preview' const file = ref() function onFile(e) { file.value = e.target.files[0] } function onError(msg) { console.error(msg) } </script>

支持独立 HTML 用法(CDN 引入):

<linkrel="stylesheet"href="https://unpkg.com/@zhenghy/doc-preview/dist/style.css"/><scripttype="module">import{createApp}from'https://unpkg.com/vue@3/dist/vue.esm-browser.js'import{DocPreview}from'https://unpkg.com/@zhenghy/doc-preview/dist/doc-preview.js'createApp({components:{DocPreview},data:()=>({file:null}),template:`<input type="file" @change="e => this.file = e.target.files[0]" accept=".doc" /> <DocPreview :source="file" />`}).mount('#app')</script>

十一、性能

实测数据(MacBook Pro M1 / Chrome 128):

文件大小主线程解析Worker 解析
100 KB~30ms-
1 MB~200ms~210ms
5 MB~900ms(UI 冻 900ms)~500ms(60 FPS 全程不掉)
10 MB~1.8s(UI 卡死)~1.2s(60 FPS 全程不掉)

阈值是 1MB,刚好对应"小文件不值得起 Worker 的开销"和"大文件 UI 必卡"的分界线。Worker 启动本身有 5-10ms 成本,所以 100KB 那种小文件跑主线程反而更快。


十二、它做不到的事

诚实说一下不支持的场景:

  • .docx(Open XML),那是 zip + xml,需要专门的解析器
  • 图片、表格、图表,OLE2 容器里嵌入的 OLE 对象暂时没解析
  • 修订模式、批注等协作功能
  • 完整的 CHP/PAP 二进制表,用 80% 准确度的启发式代替

对纯文本 + 基础格式的.doc预览(占企业内网老格式文档场景的 90%+),它能完美胜任。


十三、AI 写二进制解析器,到底行不行

讲几个我观察到的具体现象。

OLE2/CFB 这种有 MS 官方文档、有 GitHub 上开源参考实现的领域,AI 生成初版特别快。第一次让 AI 写 OLE 头解析,给的代码基本就能跑。二进制偏移的修修补补(+2还是+4,大端还是小端)改一次就过,比手写节约 80% 时间。Web Worker 拆分、TypeScript 类型定义、单元测试样板这些套路化工作,AI 完成度也很高。

但 AI 在两个具体的地方会卡住。

第一个是性能微优化。我当时最头疼的就是 Worker 阈值设成 1MB 还是 500KB,扫描窗口开多大。这些数字不会从天上掉下来,得拿真实文件做 A/B 实验。AI 不会主动给你跑这种实验,它默认给你"行业惯例"的数,但.doc解析没行业惯例可言。

第二个是边界 case。textutil 那个0xBF坑,AI 第一版用了错误的 fComplex 启发式,靠真实用户反馈 + 真实.doc文件测试才修好。我现在仓库里还留着一个 issue 标签叫needs-real-file-test,专门标记那些 AI 写的代码但没经过真实文件回归的地方。

API 设计也是。AI 给的 API 经常过度设计,得人来砍。parseDocFile/parseDocFileWithFormat/parseDocFileFromBuffer三个函数一开始 AI 都要,我没要,只留了一个统一的parseDoc+ 一个 Worker 入口。砍完之后 API 表面积小了 60%,用户接入成本也低了一档。

几个具体的经验:

  • 单元测试覆盖率要做到 91% 这种程度才算够。AI 写的代码不能像审人类代码那样靠直觉审,必须有可执行的回归网
  • 公开仓库 + 真实用户反馈是 AI 编程项目最宝贵的资源。开发期间有好几个关键 bug 修复都来自真实.doc文件,不是 AI 主动想出来的
  • AI 写代码 + 人类定方向 + 真实文件回归测试这三件事缺一不可,少任何一件都会在某个边界 case 上翻车

十四、最后

写完这个项目最大的感受是:国内前端几乎一定会踩到老.doc这个坑,但 npm 上没有现成方案。如果你正好也卡在这,欢迎把场景(文件大小、来源系统、是否需要保留图片)发到评论区,我看看能不能给你 demo 测一下。

仓库:https://github.com/zhenghy-gh/doc-preview
npm:https://www.npmjs.com/package/@zhenghy/doc-preview
在线 Demo:https://zhenghy-gh.github.io/doc-preview/


附:常见问题

Q:支持 .docx 吗?
A:不支持。.docx是 Open XML 格式(本质是 zip + xml),跟.doc(OLE2 二进制)是两套完全不同的规范。.docx推 mammoth.js、docx-preview 那些。

Q:支持图片、表格吗?
A:不支持。OLE2 容器里嵌入的 OLE 对象(图片、Excel 表格、公式)我还没解析。理论上能解,工作量是当前的 2-3 倍。

Q:能解析 macOS Pages / WPS 写的 .doc 吗?
A:能解析,但有坑。WPS 写的.doc一般兼容性好。macOStextutil转换的.doc有个0xBF标志位问题,前面第六节讲过。

Q:解析速度怎么样?
A:见第十一节。1MB 以下主线程解析,1MB 以上自动走 Worker,5MB 大约 500ms 跑完。

Q:能在 Node.js 里用吗?
A:能。parseDocFileFromBuffer(buffer)是同步函数,Node.js 里直接用。Worker 函数在 Node 端也能用,但要import路径不一样。

Q:跟 mammoth.js 的核心区别?
A:mammoth 只支持.docx,且只输出 HTML,不保留原始段落结构。本库专注.doc,返回带字符级样式的结构化数据(FormattedParagraph[]),方便二次处理。

Q:可以商用吗?
A:MIT 协议,随便用。唯一限制:不能用来做解析盗版电子书那种事情。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/19 1:39:09

深度学习语义分割:从FCN、U-Net到DeepLab的工程实践全解析

1. 从像素分类到场景理解&#xff1a;语义分割的工程价值如果你做过图像分类任务&#xff0c;比如判断一张图片里是猫还是狗&#xff0c;那你已经迈入了计算机视觉的大门。但很多时候&#xff0c;仅仅知道“有什么”是远远不够的。在自动驾驶的场景里&#xff0c;系统不仅要知道…

作者头像 李华
网站建设 2026/6/19 1:36:52

AI产品PMF验证:从技术Demo到付费转化的关键指标体系构建

AI产品PMF验证&#xff1a;从技术Demo到付费转化的关键指标体系构建一、AI产品的死亡谷&#xff1a;技术可行性与商业可行性的断裂 AI创业中最常见的失败模式不是技术做不出来&#xff0c;而是做出来了没人用。团队花了三个月训练模型、打磨Prompt、优化推理速度&#xff0c;上…

作者头像 李华
网站建设 2026/6/19 1:35:16

微服务智能治理:基于可观测性数据的流量调度与故障自愈

微服务智能治理&#xff1a;基于可观测性数据的流量调度与故障自愈 一、微服务治理的"人工运维"瓶颈&#xff1a;为什么 MTTR 总是降不下来 微服务架构下&#xff0c;故障恢复时间&#xff08;MTTR&#xff09;的瓶颈往往不在定位问题&#xff0c;而在执行恢复。运维…

作者头像 李华
网站建设 2026/6/19 1:31:09

最终验证和AI重点测试:项目开发周期的最终环节

现在&#xff0c;我们确认开发已经到了最终阶段。经过我们同大模型的确认&#xff0c;有关任务除寻找测试数据存在困难外已全部完成。但这并不影响我们执行功能性测试。我们大量使用脚本进行了自动化测试&#xff0c;对于重中之重的AI模块我们用了一点真实数据进行了单独的手动…

作者头像 李华
网站建设 2026/6/19 1:24:18

嵌入式电机控制:M/T法测速与开关磁阻电机换相算法详解

1. 项目概述与核心价值在嵌入式电机控制的世界里&#xff0c;速度和位置是驱动一切动作的基石。无论是让机械臂精准定位&#xff0c;还是让风扇平稳运行&#xff0c;控制器都需要实时、准确地知道电机“跑”得多快、转到了哪里。速度计算&#xff0c;这个看似基础的环节&#x…

作者头像 李华