使用docx4j将XHTML转换为DOCX时页眉页脚无法正常生成的解决方法
解决XHTML生成DOCX时页眉页脚不生效的问题
你当前的问题在于所用的docx4j XHTMLImporter并不支持CSS Paged Media规范里的@page、position: running这类规则,导致页眉页脚的div被当成普通正文内容渲染,而非真正的Word页眉页脚。要实现带页码的页眉页脚,需要通过docx4j的API手动构建页眉页脚组件,而非依赖XHTML的CSS定义。
具体步骤:
- 分离XHTML内容:先从输入的XHTML中移除页眉、页脚对应的div,只保留正文部分,避免它们被当成正文导入。
- 手动创建页眉:通过docx4j的API创建页眉部件,并设置居中样式的页眉文本。
- 手动创建带页码的页脚:构建页脚部件,添加固定文本,再插入Word的页码和总页数字段(Word会自动计算并渲染实际数值)。
- 关联页眉页脚到文档:将创建好的页眉页脚部件关联到文档的节设置中,确保每页都应用。
完整代码示例:
String xhtmlText = addDoctype(pXHTML); // 先移除XHTML中的header和footer div(可以用字符串替换或XML解析处理) xhtmlText = xhtmlText.replaceAll("<div class=\"header\">.*?</div>", "").replaceAll("<div class=\"footer\">.*?</div>", ""); WordprocessingMLPackage wordMLPackageXHTML = WordprocessingMLPackage.createPackage(); MainDocumentPart mainDocPart = wordMLPackageXHTML.getMainDocumentPart(); // 1. 导入XHTML正文内容 XHTMLImporterImpl xhtmlImporter = new XHTMLImporterImpl(wordMLPackageXHTML); mainDocPart.getContent().addAll(xhtmlImporter.convert(xhtmlText, null)); // 2. 创建页眉 HeaderPart headerPart = new HeaderPart(new PartName("/word/header1.xml")); mainDocPart.addTargetPart(headerPart); // 构建页眉文本段落(居中) P headerPara = Context.getWmlObjectFactory().createP(); PPr headerPPr = Context.getWmlObjectFactory().createPPr(); Jc headerJc = Context.getWmlObjectFactory().createJc(); headerJc.setVal(JcEnumeration.CENTER); headerPPr.setJc(headerJc); headerPara.setPPr(headerPPr); R headerRun = Context.getWmlObjectFactory().createR(); Text headerText = Context.getWmlObjectFactory().createText(); headerText.setValue("this is the header"); headerRun.getContent().add(headerText); headerPara.getContent().add(headerRun); headerPart.getContent().add(headerPara); // 3. 创建带页码的页脚 FooterPart footerPart = new FooterPart(new PartName("/word/footer1.xml")); mainDocPart.addTargetPart(footerPart); // 构建页脚段落(居中) P footerPara = Context.getWmlObjectFactory().createP(); PPr footerPPr = Context.getWmlObjectFactory().createPPr(); Jc footerJc = Context.getWmlObjectFactory().createJc(); footerJc.setVal(JcEnumeration.CENTER); footerPPr.setJc(footerJc); footerPara.setPPr(footerPPr); // 添加页脚固定文本 R footerTextRun = Context.getWmlObjectFactory().createR(); Text footerText = Context.getWmlObjectFactory().createText(); footerText.setValue("this is the footer with page numbers "); footerTextRun.getContent().add(footerText); footerPara.getContent().add(footerTextRun); // 添加页码字段(PAGE) R pageNumRun = Context.getWmlObjectFactory().createR(); FldChar fldStart = Context.getWmlObjectFactory().createFldChar(); fldStart.setFldCharType(FldCharType.START); pageNumRun.getContent().add(fldStart); InstrText pageInstr = Context.getWmlObjectFactory().createInstrText(); pageInstr.setValue("PAGE \\* MERGEFORMAT"); pageNumRun.getContent().add(pageInstr); FldChar fldSep = Context.getWmlObjectFactory().createFldChar(); fldSep.setFldCharType(FldCharType.SEPARATE); pageNumRun.getContent().add(fldSep); Text pagePlaceholder = Context.getWmlObjectFactory().createText(); pagePlaceholder.setValue("1"); // Word会自动替换为实际页码 pageNumRun.getContent().add(pagePlaceholder); FldChar fldEnd = Context.getWmlObjectFactory().createFldChar(); fldEnd.setFldCharType(FldCharType.END); pageNumRun.getContent().add(fldEnd); footerPara.getContent().add(pageNumRun); // 添加分隔符 "/" R slashRun = Context.getWmlObjectFactory().createR(); Text slashText = Context.getWmlObjectFactory().createText(); slashText.setValue("/"); slashRun.getContent().add(slashText); footerPara.getContent().add(slashRun); // 添加总页数字段(NUMPAGES) R totalPagesRun = Context.getWmlObjectFactory().createR(); FldChar totalStart = Context.getWmlObjectFactory().createFldChar(); totalStart.setFldCharType(FldCharType.START); totalPagesRun.getContent().add(totalStart); InstrText totalInstr = Context.getWmlObjectFactory().createInstrText(); totalInstr.setValue("NUMPAGES \\* MERGEFORMAT"); totalPagesRun.getContent().add(totalInstr); FldChar totalSep = Context.getWmlObjectFactory().createFldChar(); totalSep.setFldCharType(FldCharType.SEPARATE); totalPagesRun.getContent().add(totalSep); Text totalPlaceholder = Context.getWmlObjectFactory().createText(); totalPlaceholder.setValue("1"); // Word会自动替换为实际总页数 totalPagesRun.getContent().add(totalPlaceholder); FldChar totalEnd = Context.getWmlObjectFactory().createFldChar(); totalEnd.setFldCharType(FldCharType.END); totalPagesRun.getContent().add(totalEnd); footerPara.getContent().add(totalPagesRun); footerPart.getContent().add(footerPara); // 4. 将页眉页脚关联到文档节设置 SectionWrapper section = wordMLPackageXHTML.getDocumentModel().getSections().get(0); SectPr sectPr = section.getSectPr(); if (sectPr == null) { sectPr = Context.getWmlObjectFactory().createSectPr(); mainDocPart.getContent().add(sectPr); section.setSectPr(sectPr); } // 关联页眉 HeaderReference headerRef = Context.getWmlObjectFactory().createHeaderReference(); headerRef.setType(HdrFtrRef.DEFAULT); headerRef.setId(mainDocPart.getRelationshipsPart().getRelationship(headerPart).getId()); sectPr.getEGHdrFtrReferences().add(headerRef); // 关联页脚 FooterReference footerRef = Context.getWmlObjectFactory().createFooterReference(); footerRef.setType(HdrFtrRef.DEFAULT); footerRef.setId(mainDocPart.getRelationshipsPart().getRelationship(footerPart).getId()); sectPr.getEGHdrFtrReferences().add(footerRef); // 保存文档 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); wordMLPackageXHTML.save(byteArrayOutputStream);
关键说明:
- docx4j的XHTMLImporter仅支持基础的HTML和CSS样式,不支持分页媒体相关的CSS规则,所以必须通过API手动构建页眉页脚。
- 插入的
PAGE和NUMPAGES是Word的域代码,打开文档时Word会自动计算并显示当前页码和总页数。
内容的提问来源于stack exchange,提问作者user26566721
相关产品推荐
相关产品推荐

