使用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)); }
问题根源分析
- 合并顺序错误:先合并文档内容再处理图片关系,导致内容中的图片引用ID未同步更新到父文档的新关系ID。
- 图片关系重复添加:
mergeImagesRelationships中同时调用addRelationship和addPart,造成关系冲突,且原关系的目标路径与新创建的图片部件不匹配。 - 手动处理图片部件风险:手动创建
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
相关产品推荐
相关产品推荐

