DocTools · 文档批量处理

把一批中文文档拖进去,选一个操作,逐个文件给结果和产出路径 —— 规范化标点引号单位、 格式互转、按标题拆分、多份合并。原生 macOS app,全部在你自己的机器上跑,文件不出本机。 本页下半部分是它的只读那一半:把 .docx 拖进浏览器,当场出结构树、统计和终稿体检报告 —— 同样不上传,解析在你这个标签页里完成。

MIT 开源 macOS 15+ · Apple Silicon SwiftUI + Python 零上传 · 零账号

它替你解决什么

做中文正式文档的人,手上很少只有一个文件。一份报告常常是:十几篇 Markdown 草稿、 几个别人发来的 .doc、一叠导出的 Excel、还有几份 PPT。要交出去之前, 这堆东西得先被摆平三件事:

场景一

标点是混的

直引号和弯引号并存,半角逗号句号混进中文句子, 「平方米」「立方米」「小时」有的写汉字有的写符号。一份份开着 Word 查找替换, 改到第八份就会漏,而且替换一不留神会把 URL 和代码里的半角符号一起改坏。

场景二

格式是杂的

要把 PPT 的内容抽成 Markdown 归档, 要把写好的 Markdown 变成有正经标题样式的 Word,要把老掉牙的 .doc / .xls 升级成新格式,还要在 Excel / CSV / 纯文本之间来回倒。每一条都得找一个不同的工具。

场景三

结构要重新切

一篇长 Markdown 要按章节拆成独立文件分头写, 写完又要按顺序合回一篇;一个 Excel 的十几个 sheet 要拆成十几个独立表分发出去。

DocTools 把这三件事收进同一个窗口:拖一批文件进来 → 左边选操作 → 回车跑。 每个文件单独一行结果,成功的给产出路径,可以直接在 Finder 里定位;失败的告诉你为什么跳过, 不会因为其中一个文件不对就整批停住。

能做什么

下面 9 个操作是 app 启动时从后端读取并渲染成侧栏的全部内容 —— 不多不少。

操作它做什么吃什么格式产出
规范化 中文文本修复三件套一起做:引号统一成中文弯引号、英文标点转中文、 中文单位名转标准符号 docx · md · pptx docx 产 _fixed 副本,原件不动
引号统一 只动引号,不碰标点和单位 —— 当你只想修引号、其他一个字都别改的时候用它 docx · md 同上
字体统一 把 PPT 全篇字体统一,含母版和版式(只改正文页的工具会漏掉这两处, 导致新建页又变回去) pptx 原地覆写.backup
英文小写整理 把表格数据行 / 正文里的英文统一转小写。这是语义级的数据改写, 所以刻意独立成一个操作,不塞进“规范化”里连带执行 xlsx · xlsm · docx 产出副本
清页眉页脚 删掉 Word 的页眉页脚引用,正文一个字不改 docx _fixed 副本
格式转换 源格式自动认,你只选目标格式。完整矩阵见下一节 docx · doc · pptx · ppt · md · csv · txt · xls · xlsx 同目录同名新文件
拆分 Markdown 按标题拆成一章一个文件;Excel 按 sheet 拆成一表一个文件 md · xlsx · xlsm <文件名>_split/ 目录
合并 多个 Markdown 合成一篇;多个 txt 按列合成一个 CSV md · txt 单个合并文件
预览 Markdown 渲染成带样式的 HTML,用浏览器打开看 md 浏览器窗口

格式转换矩阵

只有下表这些组合有对应引擎。选了没有引擎的组合,它会明确告诉你“这条转换没引擎,跳过”, 而不是产出一个坏文件。

怎么做的
pptx / pptMarkdown逐页抽文字与结构
docxMarkdown调用 markitdown(临时环境,不用你预装)
MarkdownWord套模板生成,标题层级映射到 Word 样式,生成后自动再跑一遍文本修复
docxWord套模板重排(拿模板的样式重刷一遍版式)
csv / txt / xlsExcel表格引擎
xlsx / csvCSV / txt表格引擎
老 .doctxt系统自带 textutil 直转
老 .docWord / Markdown先用 LibreOffice 升级成 docx(没装则 textutil 兜底),再走 docx 那条路

“规范化”可以调到多细

规范化不是一个黑盒开关。它把“修哪些内容”和“改哪些范围”拆成两组独立选项, 由后端声明、前端渲染 —— 也就是说加旋钮只改后端脚本,不用重新编译 app。

选项说明
修哪些内容引号统一为中文引号直引号、日文角引号一律归成中文弯引号,并按出现顺序配对开合
英文标点转中文, : ; ! ? ( ) 七种
中文单位转标准符号如 平方米 → m²,只在紧跟数字时才换
引号设为宋体会把引号拆成独立的文字片段单独设字体
改哪些范围正文Word 文档里文字藏在六个不同的地方。多数工具只处理正文, 于是改完打开一看,表格里、批注里、修订痕迹里的旧标点还在。这里六处可以逐个开关。
表格
审阅修订
批注
脚注 / 尾注
页眉页脚

在浏览器里真跑一遍 · 终稿体检

下面这个工具不是示意图:把 .docx 拖进来,浏览器会当场把它当成 ZIP 拆开 —— 用原生的 DecompressionStream('deflate-raw') 解压,配一个手写的 ZIP 中央目录读取器定位条目,取出 word/document.xmlword/styles.xml, 再用 DOMParser 解析出段落、样式、标题层级和表格。没有引任何第三方库, 也没有任何网络请求.md / .txt 走纯文本路径,同样跑完整体检。

正在检测浏览器能力…
把 .docx / .md / .txt 拖到这里(可以一次拖多个)
或者点这里选文件 —— 文件只在你的浏览器里被读取,不上传、不留存
“生成示例 .docx”是真的在内存里组装一个合法的 ZIP(含 [Content_Types].xmlword/document.xmlword/styles.xml、一张真图片), 用 CompressionStream('deflate-raw') 压缩,再原路走一遍上面那个 ZIP 读取器。 所以它同时也是这条解析链的自测:能读出来,说明读取器是通的。 示例文里的问题(缺空格、连续空段、标题跳级、标点混用)都是故意埋的,人物是“张教练 / 李同学”, 全部为合成内容。

它查哪几件事

体检项判据为什么它值得单独查
中英文之间缺空格 汉字与拉丁字母直接相邻(数字不算,中文里“第3章”本来就常写在一起) 混排不留空格会让整段的灰度看起来发糊;而它散在全文,肉眼一页页找必漏
连续空段 正文层出现 ≥2 个连续空段落 用回车顶版面的老习惯。转 PDF、换纸张、改字号之后,空段会把分页顶到奇怪的地方
标题层级跳级 标题序列里出现 h1 → h3 这种跨级(层级取 outlineLvl,缺了再看样式名) 跳级的直接后果是自动生成的目录塌掉、导航面板结构错位
全角半角标点混用 半角 , . ; : ! ? ( ) 紧挨着汉字出现(网址、邮箱、行内代码已排除) 一份稿子里两种标点并存是最典型的“多人合稿没统一”痕迹
中文里的直引号 含汉字的段落里出现 "' 顺带查的第五项:直引号在正式稿里应该是弯引号
手打首行缩进 段落以空格 / 全角空格 / 制表符开头 顺带查的第六项:缩进该由段落样式给,手打的空格换个字号就散架

另一个演示:先看清它会怎么改字

这个小演示把“规范化”里的纯文本规则(引号 / 标点 / 单位)用 JavaScript 照着后端那份实现重写了一遍,直接跑在你这个浏览器标签页里。改动会高亮出来。 它只是让你在下载前先看清改字的分寸 —— 真正批量改写 docx / pptx 的,是你机器上的那个 app。

试试这几句

两处刻意的克制,都是踩过坑加上的: 中文单位只在紧跟数字时才替换 —— 否则“小时候”会被改成“h候”、“毫米波”会被改成“mm波”。 网址、邮箱、行内代码、 千分位数字整段跳过 —— 那里的半角符号是给机器读的,转成全角等于把链接点坏、把代码改到跑不了。

怎么用

xattr -cr "/Applications/DocTools.app"

也可以右键点 app → 打开,然后在「系统设置 → 隐私与安全性」里放行。 或者装了 Xcode 的话直接从源码构建:./build.sh --install

第一次跑会慢一两分钟,之后秒开。后端把依赖写在脚本头部(PEP 723), 首次运行由 uv 现场解析安装 —— 这是一次性成本,不是它平时的速度。

用 app

  1. 把文件(或一整批混着不同格式的文件)拖进窗口。
  2. 左侧侧栏选操作;“格式转换”这类还要在上方选目标格式。
  3. ⌘R 跑。⌘K 是命令面板,直接搜操作名跳过去。
  4. 右侧逐文件出结果,点产出路径在 Finder 里定位。

用命令行

后端本身就是个可以单独调用的命令行程序,返回 JSON —— 这意味着你可以把它接进自己的脚本、 Makefile 或定时任务里,不必开 GUI。在仓库根目录:

# 看有哪些操作、每个操作支持哪些格式和选项
python3 backend/doc_gui_backend.py gui-ops

# 批量规范化,但这次不动单位、不动标点,只修引号
python3 backend/doc_gui_backend.py gui-run --op clean \
        --opt rule.units=0 --opt rule.punct=0 --files 甲.docx 乙.docx

# 只对正文和表格生效,跳过批注与修订
python3 backend/doc_gui_backend.py gui-run --op clean \
        --opt scope.comments=0 --opt scope.revision=0 --files 报告.docx

# Markdown 批量转 Word(套模板 + 自动文本修复)
python3 backend/doc_gui_backend.py gui-run --op convert --to word --files 第一章.md 第二章.md

还有一层更朴素的调度器接口,直接打动词,人看的彩色日志而非 JSON:

python3 backend/doc_dispatch.py convert --to md 汇报材料.pptx
python3 backend/doc_dispatch.py split  长文.md
python3 backend/doc_dispatch.py merge  01.md 02.md 03.md
python3 backend/doc_dispatch.py clean  草稿.md
python3 backend/doc_dispatch.py view   说明.md
当前公开版的 SwiftUI 界面还没把上面那些勾选项渲染成复选框(界面层与内部版本是分叉的), 所以要精细控制“修哪些 × 改哪些范围”,走上面的 --opt 命令行。 GUI 里跑则按全开的默认值执行。

技术上怎么做的

一壳两层。SwiftUI 那一层只做界面:侧栏、拖放区、文件列表、结果卡、命令面板。 它不解析任何文档格式,也不写业务逻辑 —— 所有真正的活都交给本机的一个 Python 进程, 双方靠一份 JSON 信封契约通信:前端启动时问一次“有哪些操作”,界面完全由这份回答渲染出来; 执行时把操作名、目标格式、选项和文件列表递过去,拿回逐文件的成败与产出路径。 结果就是:加一个新操作、给某个操作加一组选项,改的是 Python 脚本, app 不用重新编译

后端本身是纯路由。docx 的 XML 改写、pptx 的母版遍历、xlsx 的 sheet 拆分、 Markdown 的标题解析各自是独立引擎,调度器只负责按文件后缀把活分给谁 —— 所以一个引擎出问题, 炸的是那一个文件那一行,不会掀翻整批。同理,批量执行时每个文件的输出被单独捕获, 互不干扰地汇总成一张结果表。

几个不显眼但决定成败的细节:写文件前先给原件留副本或走 _fixed 旁路, 默认不覆盖你的输入;Word 文本修复直接操作文档 XML 的文字片段, 因此能覆盖正文之外的表格、批注、修订、脚注、页眉页脚;单位替换和半角转全角都带边界守卫 (见上面那个演示);GUI 程序看不到你 shell 里的 PATH, 所以它会去几个标准位置找 uv,找不到时直接说“找不到 uv”而不是静默失败。

本页那个体检器是怎么读 docx 的

步骤用到什么细节
① 定位中央目录手写解析 从文件尾部往回扫 EOCD 签名(可能被注释顶开,所以要扫不是直接取末 22 字节), 读出条目数与中央目录偏移。碰上 ZIP64 会明确报错,不装作读懂了
② 逐条读目录项DataView 每项 46 字节定长头 + 变长文件名,取压缩方式、压缩前后大小、本地头偏移。 大小以中央目录为准 —— 本地头在带 data descriptor 的包里可能是 0
③ 找到数据起点手写解析 本地头是 30 字节定长 + 文件名 + 扩展域,三者相加才是真正的数据偏移, 而且扩展域长度经常与中央目录里的那份不一样,必须重读
④ 解压DecompressionStream('deflate-raw') 浏览器原生。方式 0(stored)直接切片,方式 8(deflate)走流;其余方式如实报“不支持”
⑤ 解析 XMLDOMParser 按命名空间取 w:p / w:tbl / w:t; 标题层级先认 w:outlineLvl,没有再查 styles.xml 里样式名, 这样中英文版 Word 存的样式 ID 不同也能认出来
⑥ 体检与导出Blob + a[download] 报告在内存里拼成 Markdown,通过 Blob 直接落到你的下载目录,全程无服务端

为什么批量那一半没有在线版

这是最常被问的一句,所以直说。上面那个体检器是只读的 —— 读进来、看一眼、给一份报告, 不改你的文件,所以放在浏览器里没有代价。批量改写是另一回事。

整批改写搬到网页上会退化。网页版能给的流程只有“上传 → 处理 → 下载”。 一次处理三五个文件还行;几十个文件时,你要先把它们传上去,等,再把产物一个个下回来, 然后自己在下载文件夹里跟原始目录做对照 —— 这比在本机上拖进去、就地产出、 点一下在 Finder 里看要难用得多,不是好一点点的差距。

而且那意味着把没公开的稿子交给一台服务器。需要批量规范化的文档, 通常正是还没定稿、还不能外传的那一批。为了省一次安装,把它们上传到别人的机器上, 这笔账不划算。本机运行则根本不存在这个问题:没有账号,没有上传,没有服务端日志。

下载 / 源码

GitHub · MIT 许可

github.com/zengtianli/doc-tools

下载成品仓库的 Releases 页有打包好的 DocTools-<版本>-arm64.zip
从源码构建git clone 之后 ./build.sh --install(需要 Xcode)
只要后端backend/ 目录可以单独拿走当命令行工具用,不需要 app
许可MIT —— 随便改,随便用
合成数据声明:本页“生成一份示例 .docx”产生的文档(人物“张教练 / 李同学”、训练手册内容、 表格与那张色块图)全部是当场编出来的合成内容,不来自任何真实文档; “规范化改字预览”里的四条例句同样是编的。
你自己拖进来的文件不上传、不缓存、不写 localStorage —— 读完就在内存里,关掉标签页即消失; 体检报告由你点“导出”时在本地生成。本页无任何外部请求(无 CDN、无字体、无统计)。
真实系统在你自己的机器上:macOS app DocTools.app + 可单独调用的 Python 后端, 源码见 github.com/zengtianli/doc-tools