简介这是一份面向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 groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version${poi.version}/version /dependency dependency groupIdorg.apache.poi/groupId artifactIdpoi-scratchpad/artifactId version${poi.version}/version /dependencypoi.version 放在统一依赖管理里一般取 5.x 系列具体版本以你自己项目的依赖树为准不建议单独写死。poi-ooxml 会连带引入 poi、poi-ooxml-lite 和 XMLBeans这几个包体积不小但都是转换 docx 必需的。如果只处理 docxpoi-scratchpad 不加也行加上是为了给旧 doc 留一条备用通道。这里要注意版本冲突。很多项目本身已经引入 XMLBeans 或 commons-ioPOI 对这些间接依赖版本比较敏感。如果启动时出现 NoSuchMethodError先别怀疑代码查一下 Maven 依赖树里有没有两个版本的 poi-ooxml。3.2 核心代码把 XWPFDocument 输出成 HTML以下是最小可运行版本能处理多数普通 docximport 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 再走 XWPF4.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 转 docOLE 里的图片流很难被转换器正确映射。解决先在预处理阶段检查 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 中是否出现空段落堆积。这批指标对不上的文件不用打开就能判断为可疑。批量转换跑完后我会手动打开三份文档一份纯文本文档、一份带表格的文档、一份带复杂图片的文档。只看统计文件生成数是不够的很多样式问题只有在真实浏览器里才能暴露出来。这个习惯帮我躲过好几次批量发布时的翻车希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?