POI的XWPFParagraph.getRuns分段问题

博客提到模板在后台打印会分段,原因是${name}为手打输入,若先在记事本输入再复制粘贴进docx则不会出现分段问题,还展示了复制粘贴后的后台打印情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是我的模板这里写图片描述
后台打印出来是分段的
这里写图片描述
造成这样的原因是${name}是手打上去的,如果先在记事本输入,再复制粘贴进docx则不会出现分段问题
以下是我复制粘贴后的后台打印
这里写图片描述

<think>我们使用Apache POI的XWPF组件来处理docx格式的Word文档。根据用户引用[1][2][3][4]中提到的信息,我们可以通过以下步骤实现: 1. 准备一个包含占位符的Word模板(.docx格式) 2. 使用POI读取模板,定位到需要替换文本和插入图片的位置 3. 对于循环插入图片,我们可以在模板中使用一个特殊的占位符(例如:`${image_placeholder}`),然后通过循环替换该占位符为多张图片 但是,POI本身并不直接支持模板引擎(如Freemarker)那样的循环标签。因此,我们需要通过编程方式来实现循环插入图片。具体思路如下: - 在模板中,我们可以在需要插入图片的位置放置一个文本占位符(例如:`${image_loop}`) - 在代码中,我们首先替换其他普通占位符(如文本) - 然后,我们找到这个图片循环占位符所在的位置(比如一个段落或者一个表格单元格) - 删除这个占位符,并在该位置循环插入多张图片(每张图片插入到一个新的段落或运行中) 然而,引用[3]中提到了一种插入图片的方法,但没有详细说明循环插入。因此,我们将结合引用[1]中提到的poi-tl(一个基于POI的模板引擎)来简化操作,因为poi-tl支持循环标签。但用户要求使用Apache POI,所以我们将使用纯POI方式。 由于纯POI操作循环插入图片相对复杂,这里我们给出两种方案: 方案一:使用纯Apache POI(不依赖poi-tl)实现循环插入图片(适用于图片数量已知且位置固定) 方案二:使用poi-tl模板引擎(推荐,因为它支持循环标签) 由于用户引用中提到了poi-tl(引用[1]),并且它也是基于POI的,所以我们可以考虑使用poi-tl。但用户问题中明确要求使用Apache POI,因此我们先给出纯POI的实现,再简要介绍poi-tl。 下面分别介绍两种方案: 方案一:纯Apache POI实现循环插入图片 步骤: 1. 准备模板:在Word模板中,在需要循环插入图片的位置放置一个占位符,例如:`${image_placeholder}` 2. 加载模板,替换其他文本占位符 3. 查找包含图片占位符的段落(或运行),然后在该位置循环插入图片(每张图片插入到一个新的段落) 注意:由于图片插入需要替换占位符,并且可能插入多张,因此我们需要删除占位符,然后逐个插入图片。 代码示例(关键部分): ```java // 加载模板 FileInputStream fis = new FileInputStream("template.docx"); XWPFDocument document = new XWPFDocument(fis); // 先替换普通文本占位符(如${name}等) for (XWPFParagraph paragraph : document.getParagraphs()) { List<XWPFRun> runs = paragraph.getRuns(); if (runs != null) { for (XWPFRun run : runs) { String text = run.getText(0); if (text != null && text.contains("${name}")) { text = text.replace("${name}", "张三"); run.setText(text, 0); } // ... 其他文本替换 } } } // 替换表格中的文本占位符(如果需要) for (XWPFTable table : document.getTables()) { for (XWPFTableRow row : table.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { for (XWPFParagraph paragraph : cell.getParagraphs()) { for (XWPFRun run : paragraph.getRuns()) { String text = run.getText(0); if (text != null && text.contains("${name}")) { text = text.replace("${name}", "张三"); run.setText(text, 0); } } } } } } // 循环插入图片:先找到图片占位符的位置,然后插入多张图片 List<String> imagePaths = Arrays.asList("image1.jpg", "image2.png"); // 图片路径列表 // 遍历所有段落,查找图片占位符 for (int i = 0; i < document.getParagraphs().size(); i++) { XWPFParagraph paragraph = document.getParagraphs().get(i); String paragraphText = paragraph.getText(); if (paragraphText != null && paragraphText.contains("${image_placeholder}")) { // 找到占位符所在的段落,先删除这个段落(或者只删除占位符运行,然后插入图片) // 但为了插入多张图片,我们可以在该段落后面逐个插入图片,然后删除该段落 // 或者在该段落中删除占位符,然后在该段落后面插入多个新段落(每个段落放一张图片) // 这里选择删除整个段落,然后在原位置插入多个图片段落(也可以只删除占位符运行,但操作复杂) document.removeBodyElement(document.getPosOfParagraph(paragraph)); // 在原来的位置插入多张图片(每张图片插入为一个新段落) for (String imagePath : imagePaths) { XWPFParagraph imageParagraph = document.insertNewParagraph(i); // 在位置i插入新段落 XWPFRun imageRun = imageParagraph.createRun(); // 插入图片 FileInputStream imageStream = new FileInputStream(imagePath); imageRun.addPicture(imageStream, XWPFDocument.PICTURE_TYPE_JPEG, imagePath, Units.toEMU(200), Units.toEMU(150)); // 设置宽高 imageStream.close(); i++; // 因为我们在当前位置插入了一个段落,所以索引要后移 } break; // 假设只有一个图片循环占位符,找到并处理后就退出循环 } } // 保存文档 FileOutputStream out = new FileOutputStream("output.docx"); document.write(out); out.close(); document.close(); ``` 注意:上述代码在删除占位符段落并插入多个图片段落时,通过调整索引i来处理循环。这种方式适用于占位符单独占据一个段落的情况。如果占位符不是单独段落,则需要更精细的操作(如只删除运行中的占位符文本,然后在该运行后面插入图片,但这样可能导致图片和文本混在一起,所以通常建议将图片占位符单独放在一个段落)。 方案二:使用poi-tl模板引擎(推荐,因为它支持循环语法) 引用[1]中提到了poi-tl,它支持类似Freemarker的语法。使用poi-tl可以更简单地实现循环插入图片。 步骤: 1. 引入依赖(如引用[4]中提到的,但版本更新): ```xml <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.10.0</version> </dependency> ``` 2. 创建模板:在Word模板中,使用`{{@var}}`插入图片,使用`{{#var}}...{{/var}}`实现循环。 例如,循环插入多张图片: ``` {{#images}} {{@this}} {{/images}} ``` 3. 代码实现: ```java Map<String, Object> data = new HashMap<>(); // 图片列表,每个图片使用PictureRenderData对象 List<PictureRenderData> imageList = new ArrayList<>(); imageList.add(new PictureRenderData(100, 120, "image1.jpg")); imageList.add(new PictureRenderData(100, 120, "image2.png")); data.put("images", imageList); // 其他数据 data.put("name", "张三"); // 编译模板 XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data); // 输出文件 template.writeAndClose(new FileOutputStream("output.docx")); ``` 使用poi-tl更加简洁,而且支持循环、条件等复杂语法。 总结: 如果项目允许引入额外的库,推荐使用poi-tl(方案二)来实现循环插入图片,因为它更简单且功能强大。 如果必须使用纯Apache POI,则采用方案一,但需要注意处理占位符的位置和插入图片的细节。 注意:在纯POI方案中,插入图片时可能需要调整图片尺寸(使用Units.toEMU方法将像素或毫米转换为EMU单位)。 以上两种方案均可实现基于模板生成Word并循环插入图片。 根据用户引用,我们还注意到引用[3]中有一个插入图片的方法`imagerList(document, pack, bean.getCreateuser().getId())`,但未给出具体实现,所以这里我们按照常规方式实现。 最后,我们生成相关问题:</think>### Apache POI 实现基于模板生成 Word 文档并循环插入图片的方法 #### 核心原理 通过 Apache POI 的 XWPF 组件处理 docx 模板文件,定位模板中的占位符,循环替换为动态数据和图片。图片插入需转换为二进制流并设置尺寸参数[^3][^4]。 --- ### 实现步骤(Java + Apache POI) #### 1. 环境准备 **Maven 依赖**: ```xml <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.0</version> </dependency> ``` #### 2. 模板设计 在 Word 模板中设置占位符: - 文本占位符:`{userName}` - 图片占位符:`{imagePlaceholder}`(建议单独占一行段落) #### 3. 核心代码实现 ```java import org.apache.poi.xwpf.usermodel.*; import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; public class WordGenerator { public static void main(String[] args) throws Exception { // 1. 加载模板 XWPFDocument doc = new XWPFDocument( new FileInputStream("template.docx") ); // 2. 准备动态数据 Map<String, String> textData = new HashMap<>(); textData.put("{userName}", "张三"); textData.put("{sexName}", "男"); // 3. 准备图片数据(路径列表) List<String> imagePaths = Arrays.asList( "photo1.jpg", "photo2.png", "chart.png" ); // 4. 替换文本占位符 replaceTextPlaceholders(doc, textData); // 5. 循环插入图片 insertImages(doc, imagePaths, 200, 150); // 宽200px, 高150px // 6. 保存文档 FileOutputStream out = new FileOutputStream("output.docx"); doc.write(out); out.close(); } // 替换文本占位符 private static void replaceTextPlaceholders(XWPFDocument doc, Map<String, String> data) { for (XWPFParagraph p : doc.getParagraphs()) { for (XWPFRun run : p.getRuns()) { String text = run.getText(0); if (text != null) { for (Map.Entry<String, String> entry : data.entrySet()) { if (text.contains(entry.getKey())) { text = text.replace(entry.getKey(), entry.getValue()); run.setText(text, 0); } } } } } } // 循环插入图片 private static void insertImages(XWPFDocument doc, List<String> imagePaths, int width, int height) throws Exception { // 查找图片占位符位置 int placeholderPos = -1; List<XWPFParagraph> paragraphs = doc.getParagraphs(); for (int i = 0; i < paragraphs.size(); i++) { XWPFParagraph p = paragraphs.get(i); if (p.getText().contains("{imagePlaceholder}")) { placeholderPos = i; break; } } if (placeholderPos == -1) return; // 删除占位符段落 doc.removeBodyElement(doc.getPosOfParagraph(paragraphs.get(placeholderPos))); // 循环插入图片 for (String imgPath : imagePaths) { // 创建新段落 XWPFParagraph imgPara = doc.insertNewParagraph(placeholderPos); XWPFRun run = imgPara.createRun(); // 插入图片 try (FileInputStream is = new FileInputStream(imgPath)) { run.addPicture(is, getImageType(imgPath), imgPath, Units.toEMU(width), Units.toEMU(height) ); run.addBreak(); // 图片后换行 } placeholderPos++; // 移动插入位置 } } // 获取图片类型 private static int getImageType(String imgPath) { String ext = imgPath.substring(imgPath.lastIndexOf(".") + 1).toLowerCase(); switch (ext) { case "jpg": case "jpeg": return XWPFDocument.PICTURE_TYPE_JPEG; case "png": return XWPFDocument.PICTURE_TYPE_PNG; case "gif": return XWPFDocument.PICTURE_TYPE_GIF; default: return XWPFDocument.PICTURE_TYPE_PNG; } } } ``` --- ### 关键要点说明 1. **图片插入原理**: - 使用 `XWPFRun.addPicture()` 方法插入二进制图片流 - 尺寸单位需转换为 **EMU**(英制度量单位) - 公式:$ \text{EMU} = \text{像素} \times 9525 $[^3] 2. **循环插入策略**: ```mermaid graph TD A[定位图片占位符] --> B[删除占位符段落] B --> C{遍历图片列表} C --> D[创建新段落] D --> E[插入图片并设置尺寸] E --> F[添加换行符] F --> C ``` 3. **性能优化**: - 批量处理时使用 `try-with-resources` 确保流关闭 - 预加载图片到内存(小图适用) - 异步生成文档避免阻塞主线程 --- ### 替代方案:poi-tl 模板引擎 若需要更复杂的模板功能(如嵌套循环),推荐使用 **poi-tl**: ```java // poi-tl 示例 Configure config = Configure.builder().build(); XWPFTemplate template = XWPFTemplate.compile("template.docx", config); List<PictureRenderData> images = new ArrayList<>(); images.add(Pictures.ofLocal("img1.png").size(200, 150).create()); images.add(Pictures.ofLocal("img2.jpg").size(300, 200).create()); Map<String, Object> data = new HashMap<>(); data.put("images", images); // 模板中使用 {{#images}}{{@this}}{{/images}} template.render(data).writeToFile("output.docx"); ``` 优势:支持循环标签 `{{#list}}...{{/list}}` 和图片指令 `{{@img}}`[^1] --- ### 常见问题解决 1. **图片位置错乱**: - 确保占位符独占一个段落 - 插入后添加 `run.addBreak()` 换行 - 使用 `Units.toEMU()` 精确控制尺寸 2. **大文档优化**: ```java // 分段处理避免OOM for (int i = 0; i < 1000; i++) { if (i % 100 == 0) { doc.write(new FileOutputStream("part_" + i + ".docx")); doc = new XWPFDocument(); // 创建新文档 } // 插入内容... } ``` > 示例项目参考: > - [POI 官方示例](https://poi.apache.org/components/document/) > - [poi-tl 模板引擎](http://deepoove.com/poi-tl/)[^1] ---
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值