You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用docx4j将XHTML转换为DOCX时页眉页脚无法正常生成的解决方法

解决XHTML生成DOCX时页眉页脚不生效的问题

你当前的问题在于所用的docx4j XHTMLImporter并不支持CSS Paged Media规范里的@page、position: running这类规则,导致页眉页脚的div被当成普通正文内容渲染,而非真正的Word页眉页脚。要实现带页码的页眉页脚,需要通过docx4j的API手动构建页眉页脚组件,而非依赖XHTML的CSS定义。

具体步骤:

  1. 分离XHTML内容:先从输入的XHTML中移除页眉、页脚对应的div,只保留正文部分,避免它们被当成正文导入。
  2. 手动创建页眉:通过docx4j的API创建页眉部件,并设置居中样式的页眉文本。
  3. 手动创建带页码的页脚:构建页脚部件,添加固定文本,再插入Word的页码和总页数字段(Word会自动计算并渲染实际数值)。
  4. 关联页眉页脚到文档:将创建好的页眉页脚部件关联到文档的节设置中,确保每页都应用。

完整代码示例:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 07:50:03