news 2026/10/11 2:13:14

Word内容提取与Word转HTML:Apache POI处理doc、docx与国产套件文档的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Word内容提取与Word转HTML:Apache POI处理doc、docx与国产套件文档的完整指南

简介:这是一份面向Java开发者和前端工程师的文档转换实践资源,围绕Apache POI讲解从老版Word文档、新版Word文档及WPS生成文档中提取文本、样式、图片和表格的方法,最终输出适合网页直接展示的HTML页面。压缩包共有一百三十一个文件,大小约七百二十六KB,以可扩展标记语言、图片、属性文件和Java源码及编译产物为主,同时提供演示页面、打包脚本、说明文档与项目工程文件,便于直接阅读和运行调试。目前已有1365人学习,覆盖文档提取、样式映射、图片导出、表格转换到前端适配的完整链路,适合需要快速掌握POI文档转换能力、或搭建自动发布与内容管理系统的开发者。通过这份资源可获得可运行的示例项目、转换后的页面样例、配套配置文件与常见问题处理办法,便于直接借鉴转换流程、样式处理与图片导出思路,减少重复踩坑。

1. 为什么说 word 内容提取与 word 转 html 是一套流水线

接到过一个需求:把一批历史文档做站内预览,文档里既有 doc 也有 docx,还有不少是国产办公套件另存出来的文件。第一反应是用 Apache POI 做 word 内容提取,然后 word 转 html 在浏览器里直接展示。真正动手之后发现,这条路线的难点不在“能不能转出来”,而在转出来之后样式还像不像原文档、图片是不是散了、表格是不是还挤在一行里。本文就以 POI 为主线,把 doc、docx 以及国产套件导出文档的转换路径、参数选择和踩坑点一次讲透。

这套方案适合的读者是:要批量处理本地 Word 文档、对 HTML 结果要求“可读可用”的后端工程师。你会从本文拿到最小可运行代码,也能看到我踩过的几个和表格样式、图片路径、字体映射有关的坑。先提醒一句:不用指望一份代码通吃所有文档。任何转换工具面对 Word 这种“排版即内容”的文档,都会有妥协;关键是知道妥协发生在哪里,以及怎么补。

2. 先分清 doc、docx 和国产套件文档的结构,再选 POI 组件

2.1 docx 是 ZIP 压缩包,doc 是不透明的二进制复合文档

docx 文件你用任何解压软件打开,就能看到里面的目录结构:word/document.xml 是正文,word/media 下是图片,docProps 里是属性信息。POI 处理 docx 时,本质上是把这个 ZIP 包里的 XML 解析成对象模型,所以它能把段落、表格、图片分得很清楚。你可以把 document.xml 理解为一棵排版树,XWPF 负责把这棵树变成 Java 对象。

doc 就不是这个套路了。doc 是 OLE 复合文档,文件内部是一个复杂目录结构,正文内容、格式信息、图片都以二进制块的方式存放,解析难度明显比 XML 高。POI 里有专门的 HWPF 模块处理它,但 HWPF 对复杂样式和嵌套表格的支持一直不如 XWPF。所以如果你的项目里两种格式都有,最佳策略不是用两套转换代码并行处理,而是优先把 doc 转成 docx,再统一走 docx 路线。这个思路后面章节会展开,先把格式差异放在脑子里,遇到问题才知道去哪层找原因。

2.2 提取 Word 内容时,XWPF 和 HWPF 怎么分工

文档格式建议入口所属组件主要用途
docxXWPFDocumentpoi-ooxml段落、表格、图片、页眉页脚提取
docHWPFDocumentpoi-scratchpad老版本 Word 兼容读取
国产套件另存的 docxXWPFDocumentpoi-ooxml解析常规内容,处理兼容标记

选择依据很简单:只要你能拿到 docx,就先用 XWPF。XWPF 的XWPFDocument以段落为单位组织内容,遍历起来直观,转换库也围绕它做了大量适配。HWPF 属于 poi-scratchpad 这个模块,很多 API 多年来没有大更新,适合“能读出文本”这个最低目标,不适合追求还原排版效果。

另外提一句:同一个 doc 文件,从不同版本办公软件里保存出来的二进制结构也有细微差别,所以用 HWPF 之前,最好先用一个简单文档验证解析结果。我遇到过同一个 doc,旧版 Word 打开正常,POI 一读就报告“需要显式参数”的异常,后来把文档另存成 docx 就顺畅了。这里的经验是:结构兼容性比 API 能力更常成为拦路虎。

2.3 国产套件导出文档为什么格外容易翻车

国产办公套件在保存为兼容格式时,经常会在 document.xml 里写入一些自定义属性,比如把样式名标记成带前缀的字符串,或把字体名写成特定的中文别名而不是标准字体族名。POI 的 XWPF 解析这些内容时不会报错,但转换出来的 HTML 会多出很多奇怪的 class,或者 CSS 里出现空字体。这并不是 POI 本身坏了,而是 WordprocessingML 规范对扩展属性本来就留有空间,不同厂商实现自由度过高。

我的习惯是:拿到新文件后,先不急着写转换代码,把该 docx 的 document.xml 里与样式相关标签中出现的属性名扫一遍,看有没有明显非标准的前缀。这个过程用文本编辑器就能完成。很多所谓的解析玄学,最后都印证在 XML 原始数据里,而不是在转换代码里。提前看过原始数据,后面调试时就能直接判断“是 POI 没解析出来”,还是“源文档本来就这么写”。

3. POI 实现 word 转 html 的最小可运行方案

3.1 Maven 依赖设置:只引入转换所需模块

如果你的项目是 Maven 管理,依赖建议这样加:

<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi.version}</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>${poi.version}</version> </dependency>

poi.version 放在统一依赖管理里,一般取 5.x 系列,具体版本以你自己项目的依赖树为准,不建议单独写死。poi-ooxml 会连带引入 poi、poi-ooxml-lite 和 XMLBeans,这几个包体积不小,但都是转换 docx 必需的。如果只处理 docx,poi-scratchpad 不加也行;加上是为了给旧 doc 留一条备用通道。

这里要注意版本冲突。很多项目本身已经引入 XMLBeans 或 commons-io,POI 对这些间接依赖版本比较敏感。如果启动时出现 NoSuchMethodError,先别怀疑代码,查一下 Maven 依赖树里有没有两个版本的 poi-ooxml。

3.2 核心代码:把 XWPFDocument 输出成 HTML

以下是最小可运行版本,能处理多数普通 docx:

import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.converter.xhtml.XHTMLConverter; import org.apache.poi.xwpf.converter.xhtml.XHTMLOptions; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; public class DocxToHtmlDemo { public static void main(String[] args) throws Exception { try (FileInputStream in = new FileInputStream("source.docx"); OutputStream out = new FileOutputStream("result.html")) { XWPFDocument document = new XWPFDocument(in); XHTMLOptions options = XHTMLOptions.create(); XHTMLConverter.getInstance().convert(document, out, options); } } }

这段代码背后做了三件事:把正文段落拆出来,按原有顺序排列;把段落里的 run 合并成可见文本,并在必要位置生成 span 标签;把字符格式映射为内联 CSS。肉眼看到的输出简洁,实际上已经把大部分字符级格式转掉了。

常见的做法是牺牲微格式来换取稳定输出,所以不要期望它做到和打印版一模一样。转换后要放到真实浏览器里验证,尤其要关注中文字体和表格列宽。如果只是提取纯文本,不需要 HTML,可以跳过转换器,后面第 3.4 节再说。

XHTMLOptions 是主要调参入口。默认情况下,图片会以 base64 形式嵌进 HTML,适合单文件预览;如果文档里图片很多,默认方式会让 HTML 体积翻好几倍。这个参数我会在下一小节展开讲,因为它是线上环境最常被忽略的一项。

3.3 图片内嵌与外置:体积和路径要提前想清楚

默认XHTMLOptions.create()会把 Word 里的图片以 base64 方式嵌进 HTML。好处是单文件即开即用,坏处是文件体积变大,而且浏览器解析大 base64 字符串时会卡。我处理过一份五十页的 docx,默认转换后 HTML 有几十 MB,几乎没法在低配服务器上预览。

如果希望图片外置,需要给 options 挂一个图片路径解析器。POI 5.x 里相关 setter 在不同小版本间有过调整,用的时候先看当前版本的 API 提示。大体思路是:为图片指定输出目录和 URL 前缀,让 HTML 里的 img src 指向外部文件而不是 data URI。

XHTMLOptions options = XHTMLOptions.create(); // 注意:不同 POI 小版本的 setter 名称有差异,以当前依赖的实际 API 为准 options.setURIResolver(new MyImageResolver()); options.setImageManager(new MyImageManager());

我的建议是:内部预览系统,图片外置优先;外部交付 HTML 给客户,内嵌优先。原因很简单,外置图片容易出现路径问题,一旦 HTML 被二次移动,图片就全部丢失。内嵌虽然体积大,但交付物只有一个文件,不会出现引用断掉的问题。

3.4 不依赖转换器手工提取内容:段落、表格与图片

有些需求根本不需要 HTML,只要求把 word 内容提取出来做全文检索或入库。这时候用转换器反而重。POI 提供更轻的XWPFWordExtractor,一行就能拿全量文本:

import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.extractor.XWPFWordExtractor; try (XWPFDocument doc = new XWPFDocument(new FileInputStream("source.docx")); XWPFWordExtractor extractor = new XWPFWordExtractor(doc)) { String text = extractor.getText(); }

getText()会把正文、表格里的文字按出现顺序拼接出来,适合索引场景。但需要注意的是,它也会把页眉页脚里的文字带出来,做搜索时这些内容可能造成干扰。如果需要更精细的提取,建议直接遍历XWPFDocument的段落和表格:

for (XWPFParagraph paragraph : doc.getParagraphs()) { System.out.println(paragraph.getText()); } for (XWPFTable table : doc.getTables()) { for (XWPFTableRow row : table.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { System.out.println(cell.getText()); } } }

这样就能按自己的结构控制输出,比如段落加标题层级,表格转成 JSON。对于 word 内容提取这类需求,遍历对象模型比直接转 HTML 更容易调试,也更容易定制。

4. 旧 doc 与国产套件文档的另一条路:先转 docx 再走 XWPF

4.1 HWPF 转换器的价值与天花板

如果一定要直接用 POI 转 doc,可以用 scratchpad 里的 WordToHtmlConverter。它的用法和 XHTMLConverter 不一样,需要先生成 DOM 再序列化:

import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.converter.WordToHtmlConverter; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; HWPFDocument doc = new HWPFDocument(new FileInputStream("old.doc")); WordToHtmlConverter converter = new WordToHtmlConverter( DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()); converter.processDocument(doc); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform( new DOMSource(converter.getDocument()), new StreamResult(new FileOutputStream("old.html")));

这段代码确实能跑,但我要泼一盆冷水:HWPF 对简单文档效果尚可,一旦遇到分栏、文本框、艺术字、复杂表格,输出结果会明显失真。图片位置漂移是常态,文字重叠也不稀奇。我第一次用它处理一份带文本框的 doc,生成的 HTML 里文本框变成了独立区块,完全不在原文位置。

所以我的结论是:HWPF 只适合应急读取,不适合做正式转换流水线。真正可靠的路径是把 doc 先转成 docx,再用第 3 章的 XWPF 方案处理。转换动作可以在服务端批量完成。

4.2 服务端批量把 doc 转成 docx

在服务器上安装带有无头模式的办公套件,然后一条命令就能批量转换:

soffice --headless --convert-to docx --outdir /data/converted /data/source/*.doc

这条命令的要点有三个:--headless表示不启动图形界面;--convert-to docx是目标格式;--outdir指定输出目录。如果你在容器环境里跑,需要注意中文语言包和字体是否安装,否则转换出来的 docx 在后续转 HTML 时会出现字体缺失。

我一般会把这个命令包一层脚本,在批量转换前先做文件类型判断,避免把 docx 再转一次。转完以后,再对生成的 docx 做一次连通性检查,确认 POI 能正常打开。这个预转换步骤看起来多了一道工序,却让后面所有代码只面向 XWPF,维护成本低很多。

4.3 转换前对源文档做快速体检

批量转换时,我习惯先用一段几分钟能写完的脚本,把所有 doc 文件用 POI 打开一遍,只做打开和关闭,不做任何解析。这样能提前把损坏文件挑出来:

try (HWPFDocument doc = new HWPFDocument(new FileInputStream(file))) { // 只验证可读性 } catch (Exception e) { System.out.println("坏文件: " + file.getAbsolutePath()); }

把文件分成“正常、可疑、损坏”三类后,再进入正式转换。这样可以避免转换到一半被某个坏文件打断,也方便排查“为什么某个文件转出的 HTML 是空的”。这类问题多数不是代码 bug,而是源文件本身就存在问题,提前体检能省下大量排错时间。

5. word 转 html 的常见问题与避坑记录

5.1 图片只显示边框,src 丢失或指向本地磁盘

现象:转出来的 HTML 里,图片标签在,但 src 是空字符串,或者指向file:///C:/...这种本地路径。浏览器打开后只看到灰色边框,看不到图。

原因:docx 里的图片通常存在 word/media 目录,通过关系文件与正文关联。如果源文档在生成过程中图片引用关系被破坏,POI 按关系查找图片时会失败,就会退而求其次写出原始路径。另一种常见场景是拿 HWPF 转 doc,OLE 里的图片流很难被转换器正确映射。

解决:先在预处理阶段检查 docx 包内 word/media 目录是否存在图片,以及 document.xml.rels 里是否包含 image 关系。如果这些正常,问题多半出在 POI 图片解析逻辑上,可以升级 POI 小版本或改用图片外置方案。如果是 doc 文件,别花时间调 HWPF,直接先转成 docx 再走 XWPF。

5.2 中文变成方块

现象:HTML 中文字正常,浏览器打开后变成一个个方块,英文和数字正常,中文全是乱码或占位符。

原因:转换出来的 CSS font-family 里可能写了源文档指定的中文字体名,但浏览器环境里没有安装这个字体;或者源文档把字体名写成区域化别名,POI 原样输出后找不到对应字体。这不算转换失败,而是字体回退链断裂。

解决:在转换产物里追加一段公共样式,强制 font-family 优先用系统常用中文字体,例如微软雅黑、苹方,最后加 sans-serif 兜底。不要依赖 Word 文档里的字体名,因为文档字体是给本地 Word 用的,浏览器环境未必有。把字体映射做成配置文件,不同部署环境可以覆盖。

5.3 表格列宽全部变成均分

现象:源文档里表格第一列很窄、第二列很宽,转出来后各列宽度变成等分,页面比例失调。

原因:docx 表格列宽同时存在 tblGrid 的 gridCol 定义和每个单元格的 tcW 属性。POI 转换时对不同属性的处理优先级和浏览器不一致,多数情况下会丢列宽比例。嵌套表格更容易加剧问题,内层表格宽度会被浏览器重新分配。

解决:在 HTML 公共样式里给表格加table-layout: fixed,同时根据实际需求设置整表宽度。如果要求更精确,可以在转换后用解析 tblGrid 的方式生成 colgroup。我的血泪经验是:表格是输出 HTML 最容易失真的元素,通用 CSS 至少能避免列宽对不齐,但要完全一致还得靠针对表格的专门处理。

5.4 单个大文档把 JVM 内存耗尽

现象:转换十几页的小文档没问题,一旦处理几十 MB 的 docx,程序直接抛出 OutOfMemoryError,或 JVM 长时间停顿。

原因:POI 解析时会把所有 XML 对象加载进内存,XHTMLConverter 又会在内存里生成 DOM 树,双重放大内存占用。如果文档里还有大图片 base64 内嵌,内存消耗会成倍上涨。

解决:转换前评估文档大小,超过阈值的文件走分拆方案,按章节拆成多个 docx 再逐个转换;或者单独给转换服务开一个 JVM,设置合理 Xmx。另一个技巧是图片外置而不是 base64 内嵌,能显著降低内存峰值。如果只是做全文检索,优先用 XWPFWordExtractor,不要走完整转换。

5.5 页眉页脚混进正文

现象:正文开头或结尾出现页码、公司名称、日期等页眉页脚内容,而且位置不稳定。

原因:POI 的 XHTMLConverter 对页眉页脚的处理不完全可控,它在部分版本里会把 header/footer 内容也输出成 div。更麻烦的是,这些内容在 XML 里本来就不属于正文区域,但转换器为了完整性会一并导出。

解决:转换前先明确业务上是否需要页眉页脚。需要的话,转换后用 HTML 工具把 header 区域摘出来单独存;不需要的话,在预处理阶段对文件做拆分或清理。不要试图在转换后靠正则删掉所有疑似页眉的内容,那样误伤正文的风险很大。

6. 进阶:给批量转换结果做样式补偿与人工抽验

当你要处理的不再是单个文件,而是上千个历史文档时,转换代码本身反而没有样式补偿重要。POI 转出来的 HTML 是“内容完成但样式粗糙”的产物,直接交付很难让人满意。我的做法是在转换后统一插入一套公共样式,覆盖字体、表格、图片三类高频问题。

公共样式里我至少会做三件事:给所有 img 加max-width: 100%,避免图片超出预览容器;给所有 table 加table-layout: fixed和合适的边框;给 body 指定中文字体回退链。这样即使源文档格式千奇百怪,最终展示页面也能保持基本可读。

抽验环节也必不可少。我习惯写一个简单的统计脚本,对比三个指标:原 docx 的 word/media 图片数量与输出 HTML 中 image 标签数量;原文档表格数量与输出 HTML 中 table 数量;转换后 HTML 中是否出现空段落堆积。这批指标对不上的文件,不用打开就能判断为可疑。

批量转换跑完后,我会手动打开三份文档:一份纯文本文档、一份带表格的文档、一份带复杂图片的文档。只看统计文件生成数是不够的,很多样式问题只有在真实浏览器里才能暴露出来。这个习惯帮我躲过好几次批量发布时的翻车,希望帮到你。

本文还有配套的精品资源,点击获取

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

稀土抑烟剂:火灾中的隐形安全屏障

做阻燃材料这些年&#xff0c;我有个越来越深的体会&#xff1a;火灾现场真正让人逃不掉的&#xff0c;往往不是火&#xff0c;而是烟。浓烟不光让人窒息&#xff0c;几秒钟就能把视线完全遮住&#xff0c;逃生通道变得一片漆黑&#xff0c;人在里面很快就会失去方向。消防救援…

作者头像 李华
网站建设 2026/10/11 2:07:33

VCD驱动的动态IR drop分析:RedHawk实战经验与vectorless对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/11 2:07:23

VS+QGIS+Qt 地图画点:坐标转换与事件处理实战

简介&#xff1a;这份资源面向具备一定C基础、希望入门GIS桌面开发的学习者&#xff0c;聚焦在Windows 10环境下用Visual Studio、QGIS与Qt实现「地图上画点」这一典型场景。内容围绕环境搭建、项目创建、调用QGIS与Qt接口、加载网络地图、新建图层、经纬度转墨卡托坐标以及标注…

作者头像 李华
网站建设 2026/10/11 2:06:57

2200E标签打印机二次开发包实战:DLL调用与避坑指南

简介&#xff1a;面向标签打印软件开发者的2200E标签打印机二次开发包&#xff0c;提供V2.072版本的完整SDK接口与示例工程。该版本为2011年8月发布&#xff0c;开发者可通过DLL、API等方式调用打印功能&#xff0c;实现标签格式设计、TrueType字体设置、QR码与DataMatrix码生成…

作者头像 李华
网站建设 2026/10/11 2:03:27

curl 命令转 Python 脚本:curl2py 解析原理与避坑指南

简介&#xff1a;curl2py 是一份面向 Python 开发者与运维人员的轻量工具脚本&#xff0c;用于将日常调试中常见的 curl 命令快速转换为可直接运行的 Python 脚本&#xff0c;省去手工改写请求头、参数与数据体的重复劳动&#xff0c;适合需要频繁对接 HTTP 接口、做接口调试或…

作者头像 李华