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

使用docx4j合并Docx文件时图片无法显示的问题

问题:docx4j合并Word文档后图片无法显示

使用docx4j合并两个Word文档时,链接、样式和文件结构都能正常合并,但图片无法显示,仅在对应位置留下空白区域,同时出现“Unable to render image”提示。

原实现代码

public byte[] unififyDocx(MultipartFile originalFile, String parentFilePath) {
    try {
        Document parentDoc = cmis.getFile(parentFilePath);
        WordprocessingMLPackage wordMLPackageParent = WordprocessingMLPackage.load(parentDoc.getContentStream().getStream());
        WordprocessingMLPackage wordMLPackageOriginal = WordprocessingMLPackage.load(originalFile.getInputStream());

        resolveRelationshipIdConflicts(wordMLPackageParent, wordMLPackageOriginal);
        wordMLPackageParent.getMainDocumentPart().getContent().addAll(wordMLPackageOriginal.getMainDocumentPart().getContent());
        mergeHyperlinkRelationships(wordMLPackageParent, wordMLPackageOriginal);
        mergeImagesRelationships(wordMLPackageParent, wordMLPackageOriginal);

        StyleDefinitionsPart originalStylesPart = wordMLPackageOriginal.getMainDocumentPart().getStyleDefinitionsPart();
        if (originalStylesPart != null) {
            StyleDefinitionsPart parentStylesPart = new StyleDefinitionsPart();
            parentStylesPart.setJaxbElement(originalStylesPart.getJaxbElement());
            wordMLPackageParent.getMainDocumentPart().addTargetPart(parentStylesPart);
        }

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        wordMLPackageParent.save(byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();

    } catch (Exception ex) {
        logger.error("Exception: ", ex);
        throw new GenericErrorException(erroPadrao);
    }
}

public static void resolveRelationshipIdConflicts(WordprocessingMLPackage model, WordprocessingMLPackage thesis) throws Exception {
    RelationshipsPart modelRelsPart = model.getMainDocumentPart().getRelationshipsPart();
    RelationshipsPart thesisRelsPart = thesis.getMainDocumentPart().getRelationshipsPart();

    Set<String> modelIds = new HashSet<>();
    for (Relationship rel : modelRelsPart.getRelationships().getRelationship()) {
        modelIds.add(rel.getId());
    }

    Relationships thesisRelationships = thesisRelsPart.getRelationships();
    for (Relationship rel : thesisRelationships.getRelationship()) {
        if (modelIds.contains(rel.getId())) {
            String newId = generateRandomRelId(modelRelsPart, thesisRelsPart);
            updateRelationshipId(thesis, rel.getId(), newId);
            rel.setId(newId);
        }
    }
}

public static void mergeImagesRelationships(WordprocessingMLPackage parentPkg, WordprocessingMLPackage originalPkg) throws Exception {
    RelationshipsPart parentRelsPart = parentPkg.getMainDocumentPart().getRelationshipsPart();
    RelationshipsPart originalRelsPart = originalPkg.getMainDocumentPart().getRelationshipsPart();
    
    for (Relationship rel : originalRelsPart.getRelationships().getRelationship()) {
        if (rel.getType().equals(Namespaces.IMAGE)) {
            parentRelsPart.addRelationship(rel);
            BinaryPartAbstractImage originalImagePart = (BinaryPartAbstractImage) originalPkg.getParts().get(new PartName("/word/"+ rel.getTarget()));

            ImagePngPart newImagePart = new ImagePngPart(new PartName("/word/media/" + originalImagePart.getPartName().getName().substring(originalImagePart.getPartName().getName().lastIndexOf("/") + 1)));
            newImagePart.setBinaryData(originalImagePart.getBuffer());

            parentPkg.getParts().getParts().put(newImagePart.getPartName(), newImagePart);
            parentPkg.getMainDocumentPart().addTargetPart(newImagePart);
            parentRelsPart.addPart(newImagePart, RelationshipsPart.AddPartBehaviour.RENAME_IF_NAME_EXISTS, parentPkg.getContentTypeManager());
        }
    }
}

private static void updateRelationshipId(WordprocessingMLPackage pkg, String oldId, String newId) throws Exception {
    String xmlContent = XmlUtils.marshaltoString(pkg.getMainDocumentPart().getJaxbElement());
    xmlContent = xmlContent.replaceAll("r:id=\"" + oldId + "\"", "r:id=\"" + newId + "\"");
    xmlContent = xmlContent.replaceAll("r:embed=\"" + oldId + "\"", "r:embed=\"" + newId + "\"");
    pkg.getMainDocumentPart().setContents((org.docx4j.wml.Document) XmlUtils.unmarshalString(xmlContent));
}

问题根源分析

  1. 合并顺序错误:先合并文档内容再处理图片关系,导致内容中的图片引用ID未同步更新到父文档的新关系ID。
  2. 图片关系重复添加:mergeImagesRelationships中同时调用addRelationship和addPart,造成关系冲突,且原关系的目标路径与新创建的图片部件不匹配。
  3. 手动处理图片部件风险:手动创建ImagePngPart容易出现路径命名冲突,且未确保文档中的r:embed引用与新关系ID绑定。

修正后的解决方案

1. 调整合并顺序

先处理图片/超链接关系并更新引用ID,再合并文档内容,确保引用与父文档关系一致:

public byte[] unififyDocx(MultipartFile originalFile, String parentFilePath) {
    try {
        Document parentDoc = cmis.getFile(parentFilePath);
        WordprocessingMLPackage wordMLPackageParent = WordprocessingMLPackage.load(parentDoc.getContentStream().getStream());
        WordprocessingMLPackage wordMLPackageOriginal = WordprocessingMLPackage.load(originalFile.getInputStream());

        // 1. 解决关系ID冲突
        resolveRelationshipIdConflicts(wordMLPackageParent, wordMLPackageOriginal);
        
        // 2. 先合并图片和超链接关系,同步更新原文档的引用ID
        mergeImagesRelationships(wordMLPackageParent, wordMLPackageOriginal);
        mergeHyperlinkRelationships(wordMLPackageParent, wordMLPackageOriginal);
        
        // 3. 最后合并文档内容
        wordMLPackageParent.getMainDocumentPart().getContent().addAll(wordMLPackageOriginal.getMainDocumentPart().getContent());

        // 合并样式
        StyleDefinitionsPart originalStylesPart = wordMLPackageOriginal.getMainDocumentPart().getStyleDefinitionsPart();
        if (originalStylesPart != null) {
            StyleDefinitionsPart parentStylesPart = new StyleDefinitionsPart();
            parentStylesPart.setJaxbElement(originalStylesPart.getJaxbElement());
            wordMLPackageParent.getMainDocumentPart().addTargetPart(parentStylesPart);
        }

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        wordMLPackageParent.save(byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();

    } catch (Exception ex) {
        logger.error("Exception: ", ex);
        throw new GenericErrorException(erroPadrao);
    }
}

2. 重构图片合并方法

使用docx4j内置方法处理图片部件,避免手动路径操作和重复关系添加:

public static void mergeImagesRelationships(WordprocessingMLPackage parentPkg, WordprocessingMLPackage originalPkg) throws Exception {
    RelationshipsPart parentRelsPart = parentPkg.getMainDocumentPart().getRelationshipsPart();
    RelationshipsPart originalRelsPart = originalPkg.getMainDocumentPart().getRelationshipsPart();
    
    for (Relationship rel : originalRelsPart.getRelationships().getRelationship()) {
        if (rel.getType().equals(Namespaces.IMAGE)) {
            // 获取原文档中的图片部件
            BinaryPartAbstractImage originalImagePart = (BinaryPartAbstractImage) originalPkg.getParts().get(new PartName("/word/" + rel.getTarget()));
            if (originalImagePart == null) continue;
            
            // 使用docx4j内置方法创建图片部件,自动处理命名冲突
            ImagePart newImagePart = ImagePart.createImagePart(parentPkg, originalImagePart.getBuffer());
            
            // 添加关系到父文档,获取新的关系ID
            String newRelId = parentRelsPart.addPart(newImagePart);
            
            // 更新原文档内容中的图片引用ID
            updateRelationshipId(originalPkg, rel.getId(), newRelId);
        }
    }
}

3. 优化引用ID更新方法(可选)

替换XML字符串替换为API遍历,避免解析错误:

private static void updateRelationshipId(WordprocessingMLPackage pkg, String oldId, String newId) throws Exception {
    TraversalUtil.visit(pkg.getMainDocumentPart().getJaxbElement(), new Callback() {
        @Override
        public List<Object> apply(Object o) {
            // 处理图片的r:embed引用
            if (o instanceof Drawing) {
                Drawing drawing = (Drawing) o;
                if (drawing.getInline() != null && drawing.getInline().getDocPr() != null) {
                    DocProperties docPr = drawing.getInline().getDocPr();
                    if (oldId.equals(docPr.getEmbed())) {
                        docPr.setEmbed(newId);
                    }
                }
            }
            return null;
        }

        @Override
        public boolean shouldTraverse(Object o) {
            return true;
        }
    });
}

关键修正说明

  • 合并顺序调整:确保关系ID更新后再合并内容,避免引用失效。
  • 内置方法创建图片部件:ImagePart.createImagePart自动处理路径命名冲突,保证图片部件正确注册。
  • 关系ID同步:更新原文档内容中的图片引用,使其与父文档的新关系ID绑定。

内容的提问来源于stack exchange,提问作者gmn_1450

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 06:00:55