
简介这份资源面向Java后端开发者与办公自动化场景提供使用docx4j配合docx4j-ImportXHTML将HTML转换为Word的完整工程示例解决模板占位符替换、XHTML内容导入及格式保真等常见难题。压缩包共170个文件约16.71MB以132个xml配置与映射文件、10个java源码、10个class编译文件为主另含8个html模板、3个ttf字体及yaml、md等辅助文件结构上覆盖模板准备、内容填充、格式转换到页眉页脚、水印、目录增强并支持进一步导出PDF。已有761人学习下载适合需要快速落地文档转换功能的中高级开发者参考可从中获取可运行的转换流程、模板设计思路与排错方向。1. 用 docx4j 把 HTML 转 Word为什么我最后选了 ImportXHTML 这条路后台富文本编辑器里攒了一堆 HTML 片段运营要一键导出成 Word 存档这个需求我接过三次。前两次分别用 POI 手搓和前端 JS 方案都翻车了POI 对 CSS 支持几乎为零table边框全丢前端方案生成的.doc本质是改了后缀的 HTMLWord 打开提示格式不符。第三次换成 docx4j docx4j-ImportXHTML才算把h1~h6、表格、内联样式、图片这些常见结构稳定落进.docx。docx4j 是一套基于 JAXB 的 Java 库直接操作 OOXML也就是.docx的底层 XML 结构而 docx4j-ImportXHTML 是它的扩展模块专门把 XHTML 的 DOM 树映射成 WordprocessingML 元素。适合谁后端 Java 服务里要做「HTML 富文本 → Word 文档」的服务端转换尤其是内容里带表格、带样式、带图片的场景。如果你只是想把 Markdown 转 Word或者需要公式图片转 Word 这种偏排版的活这套组合不是最优解但纯 HTML 结构转换它够用。2. 环境搭建与最小可运行示例从 Maven 依赖到第一个 docx2.1 依赖选型为什么是 docx4j 而不是 POI先说选型理由。Apache POI 的XWPFDocument也能生成 Word但它的 HTML 转换能力基本靠XWPFDocument手写映射遇到嵌套div、stylecolor:#333这类内联样式就得自己解析。docx4j-ImportXHTML 内置了 XHTML 到 WordprocessingML 的转换器能识别大部分 CSS 2.1 属性省掉大量映射代码。Maven 依赖如下注意 docx4j 版本和 ImportXHTML 版本要对应我用的 11.4.x 系列dependencies !-- docx4j 核心操作 OOXML -- dependency groupIdorg.docx4j/groupId artifactIddocx4j-JAXB-ReferenceImpl/artifactId version11.4.9/version /dependency !-- HTML 导入模块核心转换器在这里 -- dependency groupIdorg.docx4j/groupId artifactIddocx4j-ImportXHTML/artifactId version11.4.9/version /dependency !-- 日志桥接docx4j 内部用 slf4j -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.9/version /dependency /dependenciesdocx4j-JAXB-ReferenceImpl是 JAXB 实现JDK 11 之后 JAXB 从标准库移除必须显式引入否则启动就报ClassNotFoundException: javax.xml.bind.JAXBContext。这是第一个高频坑后面避坑章节还会展开。2.2 最小转换代码把一段 HTML 写进 docx下面这段代码是能直接跑通的最小示例输入一段带标题、段落、表格的 HTML输出output.docximport org.docx4j.Docx4J; import org.docx4j.convert.in.xhtml.XHTMLImporterImpl; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import java.io.File; public class HtmlToWordDemo { public static void main(String[] args) throws Exception { // 1. 创建空的 WordprocessingML 包相当于一个空白 docx WordprocessingMLPackage wordMLPackage WordprocessingMLPackage.createPackage(); // 2. 初始化 XHTML 导入器绑定到目标包 XHTMLImporterImpl importer new XHTMLImporterImpl(wordMLPackage); // 3. 准备 HTML 字符串注意必须是合法 XHTML标签闭合 String html htmlbody h1 stylecolor:#2c3e50;季度报告/h1 p本季度营收 strong1200 万/strong同比增长 18%。/p table border1trtd区域/tdtd营收/td/tr trtd华东/tdtd500 万/td/tr/table /body/html; // 4. 转换把 HTML 转成 WordprocessingML 元素并追加到文档主体 wordMLPackage.getMainDocumentPart().getContent().addAll( importer.convert(html, null) ); // 5. 保存为 docx wordMLPackage.save(new File(output.docx)); System.out.println(生成完毕output.docx); } }逻辑说明createPackage()建的是空文档XHTMLImporterImpl构造时必须传入目标WordprocessingMLPackage因为它要往包里注册图片、样式等资源。convert(html, null)第二个参数是 base URL处理相对路径图片时才需要纯文本传null即可。返回的是ListObject直接addAll到主文档部分的内容列表。参数说明importer.convert()内部会解析 HTML 的 DOM遍历节点按标签类型调用对应的转换器。h1映射成P加PPr里的outlineLvlstrong映射成R加b属性table映射成Tbl。这些映射规则是固定的改不了所以输入 HTML 的结构越规范输出越可控。2.3 输入 HTML 的预处理别把脏 HTML 直接喂进去实际业务里拿到的 HTML 往往不干净br没闭合、nbsp;满天飞、img没有src。docx4j-ImportXHTML 底层用 jsoup 解析对不合法标签有一定容错但遇到未闭合的p嵌套div这种转换结果会错位。我一般先过一遍 jsoup 清洗import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Entities; public class HtmlCleaner { public static String clean(String rawHtml) { // 用 jsoup 解析自动补全未闭合标签 Document doc Jsoup.parse(rawHtml); // 输出为 XHTML 语法标签自闭合实体转义规范 doc.outputSettings() .syntax(Document.OutputSettings.Syntax.xml) .escapeMode(Entities.EscapeMode.xhtml) .charset(UTF-8); return doc.html(); } }Jsoup.parse()会补全htmlheadbody结构syntax(xml)让输出符合 XHTML 规范escapeMode(xhtml)把nbsp;转成#160;这种数字实体避免转换器解析实体时出错。这一步不做后面表格错行、中文乱码的概率会明显上升。3. 样式、表格与图片的映射细节哪些 CSS 能生效哪些直接丢3.1 内联样式映射规则与失效边界docx4j-ImportXHTML 对 CSS 的支持集中在行内style属性外部style标签里的类选择器基本不认。能生效的属性我实测过的有color、background-color、font-size、font-family、font-weight、font-style、text-align、text-decoration、border表格和单元格、padding、margin部分生效、width、height。失效的典型display:flex、position:absolute、float、box-shadow、border-radius。这些是 CSS 布局属性Word 的排版模型里没有对应概念转换器直接忽略。所以如果你的 HTML 是用 Flex 布局做的卡片转出来会变成一堆上下堆叠的段落这是预期行为不是 bug。字号映射有个坑CSS 的px和 Word 的pt不是 1:1。转换器内部按1px ≈ 0.75pt换算font-size:16px转出来约 12pt。如果你要求精确字号建议在 HTML 里直接用pt单位比如font-size:12pt转换器会原样映射。3.2 表格转换边框、列宽与合并单元格表格是 HTML 转 Word 最容易出问题的部分。先看一段带边框和列宽的 HTMLString tableHtml table styleborder-collapse:collapse;width:100%; tr td styleborder:1px solid #000;width:30%;padding:4pt;姓名/td td styleborder:1px solid #000;width:70%;padding:4pt;部门/td /tr tr td styleborder:1px solid #000;padding:4pt;张三/td td styleborder:1px solid #000;padding:4pt;研发部/td /tr /table;border-collapse:collapse转换器能识别会生成tblBorders统一边框。width百分比会转成tblW的pct类型。但注意Word 表格列宽的实际渲染受tblLayout影响默认是autofit会按内容重新分配列宽你设的 30%/70% 可能被改。要强制固定列宽得在转换后手动设置// 转换后遍历表格设置固定布局 import org.docx4j.wml.Tbl; import org.docx4j.wml.TblPr; import org.docx4j.wml.TblLayout; ListObject converted importer.convert(tableHtml, null); for (Object obj : converted) { if (obj instanceof Tbl) { Tbl tbl (Tbl) obj; TblPr tblPr tbl.getTblPr(); if (tblPr null) { tblPr new TblPr(); tbl.setTblPr(tblPr); } // 设置固定布局列宽按 tblGrid 走 TblLayout layout new TblLayout(); layout.setType(TblLayoutType.FIXED); tblPr.setTblLayout(layout); } }合并单元格方面rowspan和colspan转换器支持会生成vMerge和gridSpan。但有个已知问题跨行合并时如果被合并的单元格里有内容内容会丢失只保留第一个单元格的。所以 HTML 里做合并时被合并位置最好留空td/td。3.3 图片处理base64 与本地路径两种方式HTML 里的图片有两种常见形式img srcdata:image/png;base64,...和img src/path/to/img.png。前者转换器能直接解析 base64 并嵌入 docx后者需要你提供 base URL 或者先把图片下载到本地。base64 方式最省事代码不用改转换器自动处理String imgHtml p截图/p img srcdata:image/png;base64,iVBORw0KGgoAAAANSUhEUg... stylewidth:400px;height:200px;/; // 直接 convert图片会作为 Part 嵌入 docx wordMLPackage.getMainDocumentPart().getContent().addAll( importer.convert(imgHtml, null) );本地路径方式需要设置 base URL转换器会用这个 URL 去解析相对路径// 假设图片在 /var/www/static/ 下HTML 里写 img srclogo.png String baseUrl file:///var/www/static/; wordMLPackage.getMainDocumentPart().getContent().addAll( importer.convert(imgHtml, baseUrl) );注意baseUrl必须以/结尾否则路径拼接会出错。另外图片尺寸HTML 里的width/height如果是px转换器会按px * 9525转成 EMUWord 的英制单位400px约等于3810000 EMU。如果图片实际像素和 CSS 尺寸差距大Word 里会拉伸变形建议 HTML 里就按实际显示尺寸写。4. 避坑与排查五个让我加班到凌晨的转换问题4.1 现象启动报 JAXBContext 找不到原因JDK 11 及以上版本移除了javax.xml.bind包docx4j 依赖 JAXB 做 XML 序列化缺了它直接启动失败。解决引入docx4j-JAXB-ReferenceImpl依赖或者手动加jaxb-apijaxb-runtime。我推荐前者版本和 docx4j 对齐省得自己配。如果项目里已经用了其他 JAXB 实现注意排除冲突否则会报Provider not found。4.2 现象中文变成乱码或方框原因两个可能。一是 HTML 字符串本身编码不对Java 源文件默认编码和 HTML 声明不一致二是转换器读取时没指定字符集。解决确保 HTML 字符串在 Java 里是 UTF-8convert()之前先new String(html.getBytes(ISO-8859-1), UTF-8)这种转码不要做直接保证源头是 UTF-8。如果 HTML 来自 HTTP 请求检查Content-Type的charset。另外 docx 保存时用wordMLPackage.save(file)默认就是 UTF-8不用额外设。4.3 现象表格边框在 Word 里不显示原因HTML 里用了border1这种 HTML 属性而不是 CSSborder。转换器只认 CSS 样式HTML 属性忽略。解决把border1改成styleborder:1px solid #000;并且加在table和每个td上。如果嫌麻烦可以在转换后统一遍历Tbl设置tblBorders但那样所有表格边框样式就统一了失去灵活性。4.4 现象转换后文档打开提示「内容有问题」原因生成的 docx 里某个 XML 节点不符合 OOXML schema常见于手动往getContent()里塞了非法对象或者图片 Part 的关系 ID 没注册。解决先用wordMLPackage.save(new File(debug.docx))保存然后用解压工具打开 docx看word/document.xml里有没有异常节点。更快的办法是开启 docx4j 的校验wordMLPackage.setValidationMode(true)保存时会抛具体错误。多数情况是图片没走importer.convert()而是手动构造Drawing导致的统一走转换器就没这问题。4.5 现象大文档转换内存溢出原因docx4j 默认把整个文档树加载到内存HTML 超过几万行时 JVM 堆不够。解决调大-Xmx是最直接的但治标不治本。更好的做法是分片转换把大 HTML 按div或section拆成多个片段逐段convert()后addAll每段转换完手动System.gc()一下虽然不优雅但有效。另外图片尽量用 base64 内联避免转换器频繁读磁盘。5. 进阶技巧用 XSLT 做后处理与批量转换的工程化收尾5.1 转换后微调用 XSLT 统一改样式转换器生成的 WordprocessingML 里样式是内联的RPr和PPr想统一改字体、行距一个个遍历太累。我一般用 XSLT 做后处理把document.xml抽出来跑一遍样式替换import org.docx4j.XmlUtils; import javax.xml.transform.*; import javax.xml.transform.stream.*; import java.io.StringReader; import java.io.StringWriter; public class StylePostProcessor { public static String applyXslt(String documentXml, String xsltPath) throws Exception { TransformerFactory factory TransformerFactory.newInstance(); Source xslt new StreamSource(new File(xsltPath)); Transformer transformer factory.newTransformer(xslt); Source xml new StreamSource(new StringReader(documentXml)); StringWriter writer new StringWriter(); transformer.transform(xml, new StreamResult(writer)); return writer.toString(); } }XSLT 里写模板匹配w:rFonts把w:ascii和w:eastAsia统一改成目标字体。这样比在 Java 里遍历R对象快得多而且规则改起来只动 XSLT 文件不用重新编译。5.2 批量转换的线程安全与资源释放XHTMLImporterImpl不是线程安全的每个线程要 new 一个实例。WordprocessingMLPackage也是别想着复用。批量转换时用线程池每个任务独立创建包和导入器ExecutorService pool Executors.newFixedThreadPool(4); for (String html : htmlList) { pool.submit(() - { try { WordprocessingMLPackage pkg WordprocessingMLPackage.createPackage(); XHTMLImporterImpl importer new XHTMLImporterImpl(pkg); pkg.getMainDocumentPart().getContent().addAll(importer.convert(html, null)); pkg.save(new File(out_ Thread.currentThread().getId() .docx)); } catch (Exception e) { // 记录日志别让异常吞掉 e.printStackTrace(); } }); } pool.shutdown();注意save()之后WordprocessingMLPackage不会自动释放资源大文件场景下建议显式置空引用让 GC 回收。5.3 验证转换结果三个必查项转换完别急着交付我每次都会走一遍这三步第一用 Word 打开看有没有「内容有问题」提示有就按 4.4 排查第二检查表格列宽和边框这是最容易和预期不符的地方第三随机抽几段文字对比 HTML 源和 Word 里的字号、颜色是否一致。这三步走完基本能拦住 90% 的返工。从那以后我每次做 HTML 转 Word都强制先跑一遍 jsoup 清洗再转换最后用 Word 打开验证。这套流程帮我省掉了至少三次线上事故。希望帮到你。本文还有配套的精品资源点击获取