Java spring使用Docx4j合并两个word文档并输出

前言

因为业务需求以及网上的解决方案不完整,花了两天时间研究出一行代码,所以写下此文就当2023与2024之间的承上启下之作了。(代码手打,有错自己改,狗头保命)

之前的解决方案

在网上搜索java spring中对于文档的合并输出,解决方案不外乎

  1. 使用商业付费package:Merge Docx java
  2. 使用altchunk,以下代码摘自:https://soaserele.blogspot.com/2011/07/merge-docx-files-in-java-using-docx4j.html
public class DocxService {
  private static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";

  public InputStream mergeDocx(final List<InputStream> streams) throws Docx4JException, IOException {

    WordprocessingMLPackage target = null;
    final File generated = File.createTempFile("generated", ".docx");

    int chunkId = 0;
    Iterator<InputStream> it = streams.iterator();
    while (it.hasNext()) {
      InputStream is = it.next();
      if (is != null) {
        if (target == null) {
          // Copy first (master) document
          OutputStream os = new FileOutputStream(generated);
          os.write(IOUtils.toByteArray(is));
          os.close();

          target = WordprocessingMLPackage.load(generated);
        } else {
          // Attach the others (Alternative input parts)
          insertDocx(target.getMainDocumentPart(), IOUtils.toByteArray(is), chunkId++);
        }
      }
    }

    if (target != null) {
      target.save(generated);
      return new FileInputStream(generated);
    } else {
      return null;
    }
  }

  private static void insertDocx(MainDocumentPart main, byte[] bytes, int chunkId) {
    try {
      AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(new PartName("/part" + chunkId + ".docx"));
      afiPart.setContentType(new ContentType(CONTENT_TYPE));
      afiPart.setBinaryData(bytes);
      Relationship altChunkRel = main.addTargetPart(afiPart);

      CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk();
      chunk.setId(altChunkRel.getId());

      main.addObject(chunk);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

以及自己编写相应的代码,但是需要懂docx4j的运用以及docx解压包之后的xml引用的原理,这里就不赘述了。

Docx4j从WordprocessingMLPackage的层面合并文档

因为具体需求,直接跳过基础转入合并部分:

public void mergeFile(WordprocessingMLPackage wordMLP, WordprocessingMLPackage wordMLToP) {
    try {
        //通过xpath获取docx中w:body的正文节点
        List<Object> bodies = wordMLToP.getMainDocumentPart().getJAXBNodesViaXPath("//w:body",false);
        //对于多个body逐次遍历加入,这里的样式默认与主文档有关
        for (Object bodyObject : bodies ) {
            Body body = (Body) bodyObject;
            for (Object content : body.getContent()) wordMLP.getMainDocumentPart().addObject(content);
        }
    } catch (Exception e) {
        throw TechnicalException(e.getMessage())
    }
}

但是这里的代码只考虑到了body部分,并没有考虑到docx中的relashionship中rId的重复和资源不能引入的问题,最后需要合并的文档也并没有做到另启一页。

加入分页符

private static final ObjectFactory objectFactory = new ObjectFactory();

void addPageBreak(MainDocumentPart dp) {
    P paragraph = objectFactory.createP();
    R run = objectFactory.createR();
    paragraph.getContent().add(run);
    Br br = objectFactory.createBr();
    run.getContent().add(br);
    br.setType(org.docx4j.wml.STBrType.PAGE);
    documentPart.setObject(paragraph);
}

重建图片索引

因为文档需要,一些标题段前需要svg进行修饰,网上目前给到的方案如下:

可以参考java - Merge word(docx) documents with DOCX4J: how to copy images? - Stack Overflow

List<Object> blips = s.getMainDocumentPart().getJAXBNodesViaXPath("//a:blip", false);
for (Object el : blips) {
    try {

        CTBlip blip = (CTBlip) el;
        RelationshipsPart parts = s.getMainDocumentPart().getRelationshipsPart();
        Relationship rel = parts.getRelationshipByID(blip.getEmbed());
        Part part = parts.getPart(rel);

        if (part instanceof ImagePngPart)
            System.out.println(((ImagePngPart) part).getBytes());
        if (part instanceof ImageJpegPart)
            System.out.println(((ImageJpegPart) part).getBytes());
        if (part instanceof ImageBmpPart)
            System.out.println(((ImageBmpPart) part).getBytes());
        if (part instanceof ImageGifPart)
            System.out.println(((ImageGifPart) part).getBytes());
        if (part instanceof ImageEpsPart)
            System.out.println(((ImageEpsPart) part).getBytes());
        if (part instanceof ImageTiffPart)
            System.out.println(((ImageTiffPart) part).getBytes());

        Relationship newrel = f.getMainDocumentPart().addTargetPart(part, AddPartBehaviour.RENAME_IF_NAME_EXISTS);

        blip.setEmbed(newrel.getId());
        f.getMainDocumentPart().addTargetPart(s.getParts().getParts().get(new PartName("/word/" + rel.getTarget())));

    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

这个代码中间的if可以删去,但是因为是对a:blip的全文搜索,所以对svg的引用一点作用都没有。即使用此段代码后,虽然media资源都被加入、引用都被覆写,但是因为document.xml中asvg:svgBlip对于r:embed的引用依然生效,所以合并后的media索引依然会被之前选择合并到的文档索引覆盖,就是在_rels目录下document.xml.rels会出现对于用一个id的重复指向。为了解决这一问题需要重置a:blip节点下的a:extList子节点。也就是在原来的答案代码中多加入一行代码:

...
blip.setEmbed(newrel.getId());
blip.setExtList(null);
...

至此就可以得到非常完好的合并wordprocessingpackage了。

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
使用docx4j进行HTML转Word时,你可以使用以下方法来设置生成的Word文档的大小: 1. 创建一个新的`org.docx4j.convert.out.common.writer.AbstractMessageWriter`对象,例如`org.docx4j.convert.out.common.writer.AbstractMessageWriter messageWriter = new AbstractMessageWriter();` 2. 通过`messageWriter.getSettings()`方法获取转换设置对象,例如`org.docx4j.convert.out.common.writer.AbstractMessageWriterSettings settings = messageWriter.getSettings();` 3. 设置Word文档的页面大小和边距。你可以使用`settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getDocumentPageSize()`方法来获取文档页面大小对象,并使用`setW()`和`setH()`方法设置宽度和高度。例如,设置为A4纸张大小: ```java settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getDocumentPageSize().setW(BigInteger.valueOf(11906)); settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getDocumentPageSize().setH(BigInteger.valueOf(16838)); ``` 4. 按需设置页边距。你可以使用`settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getPageMargins()`方法来获取页面边距对象,并使用`setLeft()`, `setRight()`, `setTop()`, `setBottom()`方法设置左、右、上、下的边距值。例如,设置边距为2.54厘米: ```java settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getPageMargins().setLeft(BigInteger.valueOf(1440)); settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getPageMargins().setRight(BigInteger.valueOf(1440)); settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getPageMargins().setTop(BigInteger.valueOf(1440)); settings.getWmlPackage().getMainDocumentPart().getPropertyResolver().getPageMargins().setBottom(BigInteger.valueOf(1440)); ``` 5. 调用`Docx4J.toPDF()`方法进行HTML转Word操作,例如: ```java Docx4J.toPDF(settings, messageWriter); ``` 请注意,以上代码片段提供了设置页面大小和边距的示例,你可以根据自己的需求进行调整。另外,确保你已经正确引入docx4j的相关依赖。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值