返回

简化 Docx4J 报表生成:高效处理表格和多行数据

java

简化 Docx4J 生成报表

使用 Docx4J 从带有占位符的 Word 模板生成报表,看似简单,但实际操作中可能遇到不少坑。本文将介绍如何利用 Docx4J 更高效地处理报表生成,特别是包含表格和多行数据的情况。

问题

假设有一个 Word 模板,页眉包含占位符${creationDate},正文包含一个表格,表格内容如下:

ID    | First Name   |  Last Name  |  Age   |  Gender  |
-----------------------------------------------------------
${id} | ${firstName} | ${lastName} | ${age} | ${gender} |

我们需要用 Java 程序读取这个模板,并将占位符替换成实际数据。表格中的数据可能包含多行,需要动态生成多行表格内容。

常规解决方案与优化

通常,我们会使用 Docx4J 的 VariableReplace 功能替换简单的占位符。但是,对于表格中的多行数据,这种方法就显得力不从心了。更有效的方法是利用 Docx4J 的 XML 处理能力直接操作 Word 文档的底层 XML 结构。

步骤一:解析模板

首先,加载 Word 模板并将其转换为 org.docx4j.wml.Document 对象。

WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File("template.docx"));

步骤二:替换页眉占位符

可以使用 VariableReplace 处理简单的占位符,例如页眉中的 ${creationDate}

HashMap<String, String> mappings = new HashMap<>();
mappings.put("creationDate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
VariablePrepare.prepare(wordMLPackage); // 预处理,确保占位符可替换
wordMLPackage.getMainDocumentPart().variableReplace(mappings);

步骤三:处理表格数据

关键在于如何处理表格中的多行数据。我们需要找到表格占位符所在的行,然后根据数据循环生成新的行,并将新行插入到表格中。

List<Object> tables = getAllElementFromObject(wordMLPackage.getMainDocumentPart(), Tbl.class);
for (Object tbl : tables) {
    List<Object> rows = getAllElementFromObject(tbl, Tr.class);
    for (Object tr : rows) {
        if (containsPlaceholder(tr)) {  // 判断该行是否包含占位符
            int index = rows.indexOf(tr); // 获取占位符所在行的索引
            List<Map<String, String>> data = getData(); // 获取需要填充的数据
            for (Map<String, String> rowData : data) {
                Tr newRow = XmlUtils.deepCopy((Tr) tr);  // 复制占位符所在的行作为新行
                replacePlaceholdersInRow(newRow, rowData);  // 替换新行中的占位符
                rows.add(index + 1, newRow); // 将新行插入到表格中
                index++; // 更新索引
            }
            rows.remove(tr); // 删除原始的占位符行
            break; // 处理完一个表格就跳出
        }
    }
}

// ... containsPlaceholder, replacePlaceholdersInRow, getData 和 getAllElementFromObject 的实现方法详见下方

辅助函数示例:

private boolean containsPlaceholder(Object obj) {
    // ... 使用正则表达式或其他方法判断对象是否包含占位符
}


private void replacePlaceholdersInRow(Tr row, Map<String, String> data) {
    // ... 遍历 row 中的 Tc (单元格) ,并使用 data 中的值替换占位符
}


private List<Map<String, String>> getData() {
    // ... 模拟从数据源获取数据
}


private static List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
    List<Object> result = new ArrayList<Object>();
    if (toSearch.isAssignableFrom(obj.getClass())) {
        result.add(obj);
    } else if (obj instanceof ContentAccessor) {
        List<?> children = ((ContentAccessor) obj).getContent();
        for (Object child : children) {
            result.addAll(getAllElementFromObject(child, toSearch));
        }

    }
    return result;
}

步骤四:保存文档

最后,将修改后的文档保存到新的文件中。

wordMLPackage.save(new File("output.docx"));

安全建议

  • 确保模板文件的来源可靠,避免恶意代码注入。
  • 对用户输入的数据进行校验和转义,防止 XSS 攻击。

通过上述方法,我们可以更灵活地控制报表生成过程,并高效地处理多行数据。希望本文能帮助开发者简化 Docx4J 报表生成的操作。