如何使用Apache POI替换Word指定图片为自定义生成图片
问题描述
- 基于Apache POI编写Word文档图片替换逻辑时,现有代码会无差别替换文档内所有嵌入图片,无法精准选中目标图片替换,导致非占位图片被错误覆盖为生成的二维码。
- 替换前文档效果:

- 错误替换后的文档效果:

原有问题代码
private void findImages(XWPFParagraph p) { for (XWPFRun r : p.getRuns()) { for (XWPFPicture pic : r.getEmbeddedPictures()) { XWPFPicture picture = pic; XWPFPictureData source = picture.getPictureData(); BufferedImage qrCodeImage = printVersionService.generateQRCodeImage("JASAW EMA WWS"); File imageFile = new File("image.jpg"); try { ImageIO.write(qrCodeImage, "jpg", imageFile); } catch (IOException e) { e.printStackTrace(); } try ( FileInputStream in = new FileInputStream(imageFile); OutputStream out = source.getPackagePart().getOutputStream(); ) { byte[] buffer = new byte[2048]; int length; while ((length = in.read(buffer)) > 0) { out.write(buffer, 0, length); } } catch (Exception ex) { ex.printStackTrace(); } } } }
核心诉求
- 实现仅替换指定目标图片,不影响文档内其他非目标图片
- 支持识别带自定义标识的占位图形,将其替换为自定义函数生成的图片
实现方案
问题根源是原有逻辑遍历到任意嵌入图片就直接覆盖图片数据流,没有加身份校验逻辑。要实现精准替换,按以下步骤操作即可:
- 给占位图加唯一识别标记
制作Word模板时,给需要替换的占位图片设置可选文字(Alt文本)作为唯一标识,比如给二维码占位图统一设置可选文字为QR_CODE_PLACEHOLDER。该标识不会在文档正常浏览时显示,POI可直接读取识别。
设置路径:Word中右键点击占位图片 -> 选择「查看Alt文本」/「大小和属性」-> 在可选文字的说明栏填入自定义的唯一标识即可。 - 替换逻辑增加标识校验,仅替换匹配到标记的图片
遍历图片时先读取图片的可选文字,和预设的占位标识匹配成功后再执行替换操作,同时注意覆盖数据流前先清空原有内容,避免新旧图片内容拼接导致文件损坏。修正后的代码如下:
private void findImages(XWPFParagraph p) { // 此处标识和模板中给占位图设置的可选文字保持一致 final String TARGET_MARK = "QR_CODE_PLACEHOLDER"; for (XWPFRun r : p.getRuns()) { for (XWPFPicture pic : r.getEmbeddedPictures()) { // 读取当前图片的描述文本(即可选文字) String picDesc = pic.getDescription(); // 非目标图片直接跳过,不做处理 if (picDesc == null || !picDesc.trim().equals(TARGET_MARK)) { continue; } // 匹配到目标占位图,执行替换逻辑 XWPFPictureData sourceData = pic.getPictureData(); BufferedImage qrCodeImage = printVersionService.generateQRCodeImage("JASAW EMA WWS"); File tempImageFile = new File("temp_qr.jpg"); try { ImageIO.write(qrCodeImage, "jpg", tempImageFile); } catch (IOException e) { e.printStackTrace(); } try (FileInputStream in = new FileInputStream(tempImageFile); OutputStream out = sourceData.getPackagePart().getOutputStream()) { // 清空原有图片的数据流,避免残留旧内容导致图片损坏 out.flush(); byte[] buffer = new byte[2048]; int readLength; while ((readLength = in.read(buffer)) > 0) { out.write(buffer, 0, readLength); } // 替换完成后删除临时文件 tempImageFile.delete(); } catch (Exception ex) { ex.printStackTrace(); } } } }
补充:如果之前是用文本占位符(比如
{{QR_IMG}})标记图片位置,也可以先遍历段落文本匹配占位符,删除占位文本和相邻的临时占位图后,在对应run位置插入新生成的图片即可。但用图片可选文字做标记的方案实现成本更低,也不容易打乱原有文档格式。
内容的提问来源于stack exchange,提问作者Beigut Beisenkhan
相关产品推荐
相关产品推荐

