# WordUtils **Repository Path**: l11778/word-utils ## Basic Information - **Project Name**: WordUtils - **Description**: Word文档处理工具类,用于生成带有动态数据的Word报告 支持普通占位符替换、动态段落和动态表格处理,并保留文档样式 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-07-25 - **Last Updated**: 2025-09-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 【实战】基于POI的动态Word文档生成工具类:从模板到动态数据填充全解析 在日常开发中,动态生成Word文档是一个常见需求,比如自动生成报表、合同、日报等。但传统的Word生成方式往往面临样式丢失、动态内容处理复杂、代码冗余等问题。本文将分享一个基于Apache POI实现的通用Word文档处理工具类,支持占位符替换、动态段落/表格生成,并能完美保留模板样式,希望能为你的开发工作提供参考。 ## 一、需求背景与痛点 在企业级应用中,Word文档生成通常需要满足以下需求: - 支持**静态文本替换**(如替换 `${reportDate}` 为具体日期) - 支持**动态段落生成**(如根据数据列表生成多条描述段落) - 支持**动态表格生成**(如根据数据动态添加表格行,支持分组表格) - 保留模板中的**原有样式**(字体、颜色、对齐方式等) - 支持**条件样式**(如数值异常时高亮显示) - 自动处理**分页**(如不同模块单独分页) 传统实现方式往往需要大量重复代码处理样式和动态内容,维护成本高。为此,我们封装了一个通用工具类 `WordUtils`,一站式解决上述问题。 ## 二、核心功能概览 `WordUtils` 工具类基于 Apache POI(XWPF)开发,核心功能如下: | 功能 | 说明 | 应用场景 | |------|------|----------| | 普通占位符替换 | 替换文档中 `${key}` 格式的静态占位符 | 替换日期、标题、统计值等固定文本 | | 动态段落生成 | 根据数据列表生成多条带样式的段落 | 生成动态描述列表、日志记录等 | | 动态表格生成 | 支持普通表格和分组表格的动态行生成 | 生成数据报表、明细列表等 | | 样式保留 | 完美复制模板中的字体、颜色、对齐等样式 | 确保生成文档格式规范统一 | | 条件高亮 | 基于数据值自动高亮关键内容(如异常数值标红) | 突出显示警告信息、异常数据 | | 自动分页 | 为不同模块(如附件)自动添加分页符 | 确保文档结构清晰 | ## 三、核心代码解析 ### 3.1 入口方法:generateReport 工具类的入口方法为 `generateReport`,负责统筹整个文档生成流程: ```java public static void generateReport(String templatePath, HttpServletResponse response, Map simpleDataMap, Map>> tableDataMap, Map>>> stationDataMap) { try (InputStream templateInputStream = Files.newInputStream(Paths.get(templatePath)); XWPFDocument doc = new XWPFDocument(templateInputStream)) { // 1. 替换普通占位符 replaceSimplePlaceholders(doc, simpleDataMap); // 2. 处理动态段落 processDynamicParagraphs(doc, tableDataMap); // 3. 处理动态表格 processDynamicTables(doc, tableDataMap, stationDataMap); // 4. 处理分页 insertAttachmentsPageBreaks(doc); // 5. 输出到响应 setResponseHeader(response); try (OutputStream os = response.getOutputStream()) { doc.write(os); } } catch (IOException e) { log.error("导出Word文件时发生异常:", e); } } ``` 流程清晰:先加载模板,再依次处理静态占位符、动态段落、动态表格,最后处理分页并输出到响应流。 ### 3.2 普通占位符替换:replaceSimplePlaceholders 核心逻辑是扫描文档中的所有段落、表格单元格、页眉页脚,识别 `${key}` 格式的占位符并替换为实际数据,同时保留原有样式。 ```java private static void replaceSimplePlaceholders(XWPFDocument doc, Map simpleDataMap) { // 处理段落中的占位符 parseAllParagraph(doc.getParagraphs(), simpleDataMap); // 处理表格中的占位符 for (XWPFTable table : doc.getTables()) { for (XWPFTableRow row : table.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { parseAllParagraph(cell.getParagraphs(), simpleDataMap); } } } // 处理页眉页脚 for (XWPFHeader header : doc.getHeaderList()) { parseAllParagraph(header.getParagraphs(), simpleDataMap); } for (XWPFFooter footer : doc.getFooterList()) { parseAllParagraph(footer.getParagraphs(), simpleDataMap); } } ``` 关键在于 `parseAllParagraph` 和 `changeValues` 方法,通过识别占位符边界(`${` 和 `}`),提取键名并替换为数据值,同时通过 `applyValueWithStyle` 保留原有样式。 ### 3.3 动态段落生成:processDynamicParagraphs 动态段落用于根据数据列表生成多条结构相同的段落(如多条日志记录)。实现思路是: 1. 在模板中用 `${paragraph:name:start}` 和 `${paragraph:name:end}` 标记动态段落区域 2. 中间的段落作为模板段落 3. 根据数据列表复制模板段落,替换占位符后插入文档 核心代码片段: ```java // 查找动态段落标记 Matcher startMatcher = PARAGRAPH_START_PATTERN.matcher(text); if (startMatcher.find()) { String paraName = startMatcher.group(1); // 查找结束标记... // 保存模板段落样式 List runStructures = new ArrayList<>(); for (XWPFRun run : templatePara.getRuns()) { runStructures.add(new RunStructure(run, templatePara)); } // 删除标记段落,插入数据段落 for (Map data : dataList) { XWPFParagraph newPara = doc.insertNewParagraph(cursor); // 复制样式 for (RunStructure structure : runStructures) { structure.applyToRun(newPara); } // 替换占位符 parseThisParagraph(newPara, data, true); } } ``` `RunStructure` 类用于保存模板段落的样式信息(字体、颜色等),确保新生成的段落与模板样式一致。 ### 3.4 动态表格处理:processDynamicTables 表格是动态生成的重点难点,工具类支持两种表格类型:普通表格和分组表格。 #### 普通表格处理 普通表格(如数据明细列表)的处理流程: 1. 模板中用 `${row.key}` 标记表格行模板 2. 解析模板行结构(列名、合并单元格等) 3. 根据数据列表复制模板行,替换占位符后插入表格 核心代码: ```java // 解析模板行 List paramsList = parseTemplateRow(templateRow); // 遍历数据生成行 for (Map rowData : tableData) { XWPFTableRow newRow = table.insertNewTableRow(templateRowIndex); // 复制样式 copyRowFormatting(templateRow, newRow); // 填充数据 fillRowWithData(newRow, paramsList, rowData); } ``` #### 分组表格处理 分组表格(如按部门分组的统计数据)需要支持多级数据展示(如部门标题行、部门数据行、部门总计行)。实现思路是: 1. 模板中定义不同类型的行模板(标题行、数据行、总计行) 2. 根据分组数据依次插入标题行、数据行、总计行 3. 保留各级行的样式差异 核心代码: ```java // 遍历分组数据 for (Map plantGroup : plantGroups) { // 插入分组标题行 XWPFTableRow plantNameRow = table.insertNewTableRow(insertPosition); copyRowFormatting(plantNameTemplateRow, plantNameRow); fillRowWithData(plantNameRow, plantNameParams, plantGroup); // 插入分组数据行 for (Map station : stations) { XWPFTableRow stationRow = table.insertNewTableRow(insertPosition); // ...填充数据... } // 插入分组总计行 XWPFTableRow totalRow = table.insertNewTableRow(insertPosition); // ...填充总计数据... } ``` ### 3.5 样式保留与条件高亮 样式处理是提升文档美观度的关键,工具类通过以下方式确保样式一致性: 1. **样式复制**:通过 `copyRowFormatting`、`copyCellStructure` 方法复制模板的行、单元格、段落样式 2. **条件高亮**:对异常数据(如数值>0的故障数)自动标红,实现代码: ```java private static boolean checkShouldHighlight(Map data, String cellText) { for (String key : STYLE_KEYS) { if (cellText.equals(key)) { int value = Integer.parseInt(data.getOrDefault(key, "0")); return value > 0; // 数值>0时高亮 } } return false; } // 应用高亮样式 private static void applyValueWithStyle(XWPFRun run, String value, boolean shouldHighlight) { // 保留原有样式 CTRPr originalStyle = run.getCTR().isSetRPr() ? (CTRPr) run.getCTR().getRPr().copy() : null; run.setText(value, 0); if (originalStyle != null) { run.getCTR().setRPr(originalStyle); // 高亮处理 if (shouldHighlight) { run.setColor("FF0000"); // 红色高亮 } } } ``` ## 四、使用示例 下面通过一个简单示例说明如何使用 `WordUtils` 生成动态Word文档: ### 4.1 准备模板 在Word模板中定义: - 静态占位符:`${reportDate}`、`${totalCount}` - 动态段落区域:`${paragraph:logs:start}` 和 `${paragraph:logs:end}`,中间放 `${log.content}` 作为模板段落 - 动态表格:用 `${row.name}`、`${row.value}` 标记表格行模板 ### 4.2 准备数据 ```java // 1. 静态数据 Map simpleDataMap = new HashMap<>(); simpleDataMap.put("reportDate", "2025-07-24"); simpleDataMap.put("totalCount", "120"); // 2. 动态段落数据 List> logList = new ArrayList<>(); logList.add(Map.of("log.content", "08:00 系统启动成功")); logList.add(Map.of("log.content", "12:00 数据同步完成")); Map>> paragraphDataMap = Map.of("logs", logList); // 3. 表格数据 List> tableData = new ArrayList<>(); tableData.add(Map.of("row.name", "设备A", "row.value", "正常")); tableData.add(Map.of("row.name", "设备B", "row.value", "异常")); Map>> tableDataMap = Map.of("deviceStatus", tableData); ``` ### 4.3 调用工具类生成文档 ```java // 生成Word文档并响应给前端 WordUtils.generateReport( "templates/report_template.docx", // 模板路径 response, // HTTP响应 simpleDataMap, // 静态数据 tableDataMap, // 表格数据 new HashMap<>() // 分组表格数据(本例无) ); ``` ## 五、注意事项 1. **模板设计规范**: - 占位符格式必须为 `${key}`,动态标记需严格使用 `${paragraph:name:start}` 等格式 - 动态段落/表格的模板行需放在标记中间,避免多余内容 - 样式设置需在模板中完成(如高亮颜色、字体大小) 2. **数据格式要求**: - 静态数据用 `Map`,键对应模板中的占位符 - 动态段落/表格数据用 `List>`,列表长度决定生成的段落/行数 3. **性能优化**: - 大数据量表格建议分批生成,避免内存溢出 - 复杂模板建议预加载,减少IO操作 4. **兼容性问题**: - 仅支持 `.docx` 格式(不支持 `.doc`) - 复杂样式(如特殊字体、公式)可能存在兼容性问题,建议测试验证 ## 六、总结 `WordUtils` 工具类通过封装POI的复杂操作,实现了动态Word文档的高效生成,核心优势在于: - **样式保真**:完美保留模板样式,无需重复设置格式 - **灵活扩展**:支持静态文本、动态段落、表格等多种动态内容 - **易用性高**:通过简单的数据结构即可驱动文档生成,降低开发成本 该工具类已在实际项目中验证,可满足报表、日志、合同等多种场景需求。你可以根据实际业务需求扩展功能,如添加图片动态插入、水印处理等。 希望本文对你的Word文档生成开发有所帮助,完整代码可根据实际需求调整优化。如有问题或改进建议,欢迎在评论区交流!