项目利用这个模板引擎实现了超级复杂的world导出,现在记录一下项目中实际使用的一些操作逻辑。
1、poi-tl简介
至于非常详细的介绍,请看上面的World模板引擎
- poi-tl(poi template language)是Word模板引擎,基于Word模板和数据生成新的文档。(基于Apache POI)
- 具体用到的一些功能
引擎功能 | 描述 | 标签 |
---|---|---|
文本 | 将标签渲染为文本 | |
图片 | 将标签渲染为图片 | {{@xx}} |
表格 | 将标签渲染为表格 | {{#xx}} |
列表 | 将标签渲染为列表 | {{*xx}} |
图表 | 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)等图表渲染 | |
文本框 | 文本框内标签支持 | |
样式 | 模板即样式,同时代码也可以设置样式 | |
模板嵌套 | 模板包含子模板,子模板再包含子模板 | {{+var}} |
合并 | Word合并Merge,也可以在指定位置进行合并 | |
用户自定义函数(插件) | 在文档任何位置执行函数 | |
If Condition判断 | 内隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等) | |
Foreach Loop循环 | 循环某些文档内容(包括文本、段落、图片、表格、列表、图表等) | |
Loop表格行 | 循环渲染表格的某一行 | |
Loop表格列 | 循环渲染表格的某一列 | |
Loop有序列表 | 支持有序列表的循环,同时支持多级列表 | |
图片替换 | 将原有图片替换成另一张图片 | |
书签、锚点、超链接 | 支持设置书签,文档内锚点和超链接功能 | |
强大的表达式 | 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL… | |
标签定制 | 支持自定义标签前后缀 |
-
对于模板嵌套
模板标签
{{?sections}}{{/sections}} 区块对标签
引用标签 : 在图或者图表中添加标签,可直接操作图或者图表数据
2、不足之处
- 针对不同的桥型,各个部位下的部件都不相同,所以使用easy-poi需要针对不同的桥梁编辑多个不同的模板
- 在各个部件位置上的图片,表格,文本数据不能使用一个标签来代替,导致需要针对的标签数量成倍的增加
- 项目中针对不同的桥梁类型也需要不一样的模板,比如城市桥梁,公路桥梁,公路桥梁里面又分什么单幅桥,双福桥,三幅桥等等,导致会有很多冗余的代码产生
- 仅仅支持07版本的word也是只能生成后缀是docx的文档
- 要导出表格的话,需要自己操作XWPFTable对象。在word中插入表格
3、大致实现思路
我们在做的时候是引入的1.9.0版本,目前版本已经更新到1.11.1
- 把整个文档需要替换的地方使用{{XXXX }}进行标记,并将{{XXXX}}这些里面的内容保存到数据库中
整个模板中分为三个类型,第一个系统默认的(用F打头),第二个文本,第三个图片(@F打头)
{{+XXX}}是自定义模板嵌套,模板里面需要子模板
-
写一个计算的注解,@CalculateAnn,并写一个bean,利用反射将所有标记有@CalculateAnn注解的方法解析出来,并利缓存起来,后面需要使用
- 写一个枚举类用来识别对应的标记具体是那个模板,code就是在模板中定义的字段
-
具体实现对应的含有注解的方法,返回Object
-
实现具体导出方法
1)获取数据库中当前项目的所有模板字段
2)配置具体有表格,比如图片,设备表、计算表,人员表,病害统计,总结等等
这里面的DetailTablePolicy和MemberTablePolicy这两个是复写了的,详细代码,贴到了最后面,都是根据实际需求进行的改造
3)初始化数据项目中的很多数据,进行缓存,在具体的数据实现中需要用到(这块就根据自己的业务量来,我们业务数据太多,每次去重复查询不合适,所以临时保存到内存中)
4)准备一个渲染Map,这个map是为了模板输出数据的额
Map<String, Object> renderMap = new HashMap<>(allField.size());
5)循环模板字段列表,用invoke调用具体方法,并将结果放到渲染Map中
6)最后清缓存数据,以及将所有的数据放到模板中进行具体的结果输出
XWPFTemplate template = XWPFTemplate.compile(path, config);
template.render(renderMap);
template.writeAndClose(outputStream);
4、部分的代码
MethodName
@AllArgsConstructor
@Getter
/**按各个部位分组
*/
public enum MethodName {
//构件按照部件分组,存放在不同的表格中,这里面的message是公路桥梁的
COMPONENT_TOP1("F_componentInfo_top1", "public/component_template.docx", ".1."),
COMPONENT_LOW1("F_componentInfo_low1", "public/component_template.docx", ".2."),
COMPONENT_DECK1("F_componentInfo_deck1", "public/component_template.docx", ".3."),
COMPONENT_TOP_L("F_componentInfo_top_l", "public/component_template.docx", ".1.1."),
COMPONENT_LOW_L("F_componentInfo_low_l", "public/component_template.docx", ".1.2."),
COMPONENT_DECK_L("F_componentInfo_deck_l", "public/component_template.docx", ".1.3."),
COMPONENT_TOP_M("F_componentInfo_top_m", "public/component_template.docx", ".3.1."),
COMPONENT_LOW_M("F_componentInfo_low_m", "public/component_template.docx", ".3.2."),
COMPONENT_DECK_M("F_componentInfo_deck_m", "public/component_template.docx", ".3.3."),
COMPONENT_TOP_R("F_componentInfo_top_r", "public/component_template.docx", ".2.1."),
COMPONENT_LOW_R("F_componentInfo_low_r", "public/component_template.docx", ".2.2."),
COMPONENT_DECK_R("F_componentInfo_deck_r", "public/component_template.docx", ".2.3."),
//具体病害图片及描述
// DISEASE("F_diseaseInfo", "", "病害信息"),
//上部结构计算结果
CALCULATION_TOP("F_calculation_top", "public/calculation_template_highway.docx", ".1."),
//桥面系计算结果
CALCULATION_DECK("F_calculation_deck", "public/calculation_template_highway.docx", ".3."),
//下部结构计算结果
CALCULATION_LOW("F_calculation_low", "public/calculation_template_highway.docx", ".2."),
//各个部位的评定计算结果
CALCULATION_CATEGORY("F_calculation_category", "public/calculation_category.docx", ".4."),
//总的计算结果
CALCULATION_RESULT("F_result","","计算结果"),
// CALCULATION_RESULT_TOP("F_calculation_result_top","",""),
// CALCULATION_RESULT_DECK("F_calculation_result_deck","",""),
// CALCULATION_RESULT_LOW("F_calculation_result_low","",""),
//参与人员的信息表格
PERSONNEL("F_personnel", "public/person_template.docx", "人员信息"),
//项目中使用的设备,
DEVICE("F_device", "public/device_template.docx", "设备库信息"),
FRONT_IMG("F_ZhengMianZhao", "", "正面照"),
FACADE_IMG("F_LiMianZhao", "", "立面照"),
PLAN_IMG("F_PingMianTu", "", "平面示意图"),
SKETCH_IMG("F_LiMianTu", "", "立面示意图"),
CROSS_IMG("F_HengDuanMianTu", "", "横断面图"),
GPS_IMG("F_DingWeiTu", "", "GPS图"),
MEMBER("F_member", "public/member_template.docx", ".1"),
//检测目的及内容 {{F_checkContent}}
CHECK_CONTENT("F_checkContent", "", "检测目的及内容"),
//检测结论test_conclusion_text_city.docx
TEST_CONCLUSION("F_test_conclusion", "public/test_conclusion_text_highway.docx", ""),
//重点关注
MAIN_DISEASE_TABLE("F_main_disease_table", "public/main_disease_table.docx", "重点关注表"),
//病害数量统计
DISEASE_STATISTICS_TABLE("F_disease_statistics_table","public/disease_statistics.docx","病害数量统计表"),
//技术评定汇总表
CALCULATION_SUMMARY_TABLE("F_calculation_summary_table","public/calculation_summary_table.docx",".4."),
PROJECT_INFO("F_info", "", "基础信息,包含项目中的信息:建设单元。项目名称等"),
BRIDGE_DISEASE("F_bridge_disease", "", "桥梁主要病害统计");
private String code;
private String templatePath;
private String message;
public static MethodName findByField(String field) {
for (MethodName value : MethodName.values()) {
if (value.getCode().equals(field)) {
return value;
}
}
return null;
}
DetailTablePolicy
public class DetailTablePolicy extends DynamicTableRenderPolicy {
@Override
public void render(XWPFTable table, Object data) throws Exception {
if (null == data) {
return;
}
DetailData detailData = (DetailData) data;
int startRow = detailData.getStartRow() == null ? 1 : detailData.getStartRow();
List<RowRenderData> dataList = detailData.getRowRenderData();
if (ComUtil.isNotEmpty(dataList)) {
int index = dataList.get(0).getCells().size();
table.removeRow(startRow);
for (int i = 0; i < dataList.size(); i++) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(startRow);
for (int j = 0; j < dataList.get(i).getCells().size(); j++) {
insertNewTableRow.createCell();
}
TableRenderPolicy.Helper.renderRow(table.getRow(startRow++), dataList.get(i));
}
if (detailData.isMerge()){
TableTools.mergeCellsVertically(table, index -1 , 1, dataList.size());
}
}
}
}
MemberTablePolicy
public class MemberTablePolicy implements RenderPolicy {
int startRow;
public MemberTablePolicy() {
this(Constant.GenericNumber.NUMBER_TWO);
}
public MemberTablePolicy(int startRow) {
this.startRow = startRow;
}
@Override
public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
if (null == data) {
return;
}
MemberTable detailData = (MemberTable) data;
if (ComUtil.isEmpty(detailData.getCells()) || ComUtil.isEmpty(detailData.getRows())) {
return;
}
RunTemplate runTemplate = (RunTemplate) eleTemplate;
XWPFRun run = runTemplate.getRun();
try {
if (!TableTools.isInsideTable(run)) {
throw new IllegalStateException(
"The template tag " + runTemplate.getSource() + " must be inside a table");
}
XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
XWPFTable table = tagCell.getTableRow().getTable();
run.setText("", 0);
//insert cells
processCel(table, tagCell, template, detailData.getCells());
//insert rows
final List<List<RowRenderData>> rows = detailData.getRows();
table.removeRow(startRow);
processRow(table, rows, startRow);
} catch (Exception e) {
throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
}
}
private void processRow(XWPFTable table, List<List<RowRenderData>> dataList, int startRow) throws Exception {
if (ComUtil.isEmpty(dataList)){
return;
}
int index = startRow;
for (List<RowRenderData> data : dataList) {
for (int i = data.size() - 1; i >= 0; i--) {
RowRenderData rowRenderData = data.get(i);
XWPFTableRow insertNewTableRow = table.insertNewTableRow(startRow);
for (int j = 0; j < rowRenderData.getCells().size(); j++) {
insertNewTableRow.createCell();
}
TableRenderPolicy.Helper.renderRow(table.getRow(startRow), rowRenderData);
}
if (data.size() > 1){
TableTools.mergeCellsVertically(table, 1, index, index + data.size() - 1);
}
startRow = startRow + data.size();
index = index + data.size();
}
}
private void processCel(XWPFTable table, XWPFTableCell tagCell, XWPFTemplate template, List<List<Object>> dataList) throws Exception {
if (ComUtil.isEmpty(dataList)){
return;
}
int templateColIndex = getTemplateColIndex(tagCell);
int actualColIndex = getActualInsertPosition(tagCell.getTableRow(), templateColIndex);
XWPFTableCell firstCell = tagCell.getTableRow().getCell(actualColIndex);
int width = firstCell.getWidth();
TableWidthType widthType = firstCell.getWidthType();
if (TableWidthType.DXA != widthType || width == 0) {
throw new IllegalArgumentException("template col must set width in centimeters.");
}
int rowSize = table.getRows().size();
int count = dataList.stream().mapToInt(List::size).sum();
int colWidth = processLoopColWidth(table, width, templateColIndex, count);
int index = templateColIndex;
for (List<Object> data : dataList) {
if (data == null || data.size() == 0) {
continue;
}
processLoop(data, table, templateColIndex, rowSize, template, colWidth);
if (data.size() > 1){
TableTools.mergeCellsHorizonal(table, 0, index, index + data.size() - 1);
}
templateColIndex = templateColIndex + data.size();
index++;
}
//delete last cell
for (int i = 0; i < rowSize; i++) {
XWPFTableRow row = table.getRow(i);
int actualInsertPosition = getActualInsertPosition(row, templateColIndex);
if (-1 == actualInsertPosition) {
minusGridSpan(row, templateColIndex);
continue;
}
removeCell(row, actualInsertPosition);
}
}
private void processLoop(List<Object> data, XWPFTable table, int templateColIndex,
int rowSize, XWPFTemplate template, int colWidth) throws Exception {
Iterator<?> iterator = ((Iterable<?>) data).iterator();
int insertPosition;
TemplateResolver resolver = new TemplateResolver(template.getConfig().copy("[", "]"));
while (iterator.hasNext()) {
insertPosition = templateColIndex++;
List<XWPFTableCell> cells = new ArrayList<>();
for (int i = 0; i < rowSize; i++) {
XWPFTableRow row = table.getRow(i);
int actualInsertPosition = getActualInsertPosition(row, insertPosition);
if (-1 == actualInsertPosition) {
addColGridSpan(row, insertPosition);
continue;
}
XWPFTableCell templateCell = row.getCell(actualInsertPosition);
templateCell.setWidth(colWidth + "");
XWPFTableCell nextCell = insertCell(row, actualInsertPosition);
setTableCell(row, templateCell, actualInsertPosition);
// double set row
XmlCursor newCursor = templateCell.getCTTc().newCursor();
newCursor.toPrevSibling();
XmlObject object = newCursor.getObject();
nextCell = new XWPFTableCell((CTTc) object, row, (IBody) nextCell.getPart());
setTableCell(row, nextCell, actualInsertPosition);
cells.add(nextCell);
}
RenderDataCompute dataCompute = template.getConfig().getRenderDataComputeFactory()
.newCompute(iterator.next());
cells.forEach(cell -> {
List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
new DocumentProcessor(template, resolver, dataCompute).process(templates);
});
}
}
private int getTemplateColIndex(XWPFTableCell tagCell) {
return (getColIndex(tagCell) + 1);
}
private void minusGridSpan(XWPFTableRow row, int templateColIndex) {
XWPFTableCell actualCell = getActualCell(row, templateColIndex);
CTTcPr tcPr = actualCell.getCTTc().getTcPr();
CTDecimalNumber gridSpan = tcPr.getGridSpan();
gridSpan.setVal(BigInteger.valueOf(gridSpan.getVal().longValue() - 1));
}
private void addColGridSpan(XWPFTableRow row, int insertPosition) {
XWPFTableCell actualCell = getActualCell(row, insertPosition);
CTTcPr tcPr = actualCell.getCTTc().getTcPr();
CTDecimalNumber gridSpan = tcPr.getGridSpan();
gridSpan.setVal(BigInteger.valueOf(gridSpan.getVal().longValue() + 1));
}
private int processLoopColWidth(XWPFTable table, int width, int templateColIndex, int count) {
CTTblGrid tblGrid = TableTools.getTblGrid(table);
count = count == 0 ? 1 : count;
int colWidth = width / count;
for (int j = 0; j < count; j++) {
CTTblGridCol newGridCol = tblGrid.insertNewGridCol(templateColIndex);
newGridCol.setW(BigInteger.valueOf(colWidth));
}
tblGrid.removeGridCol(templateColIndex + count);
return colWidth;
}
private int getSize(Iterable<?> data) {
int size = 0;
Iterator<?> iterator = data.iterator();
while (iterator.hasNext()) {
iterator.next();
size++;
}
return size;
}
@SuppressWarnings("unchecked")
private void removeCell(XWPFTableRow row, int actualInsertPosition) {
List<XWPFTableCell> cells = (List<XWPFTableCell>) ReflectionUtils.getValue("tableCells", row);
cells.remove(actualInsertPosition);
row.getCtRow().removeTc(actualInsertPosition);
}
@SuppressWarnings("unchecked")
private XWPFTableCell insertCell(XWPFTableRow tableRow, int actualInsertPosition) {
CTRow row = tableRow.getCtRow();
CTTc newTc = row.insertNewTc(actualInsertPosition);
XWPFTableCell cell = new XWPFTableCell(newTc, tableRow, tableRow.getTable().getBody());
List<XWPFTableCell> cells = (List<XWPFTableCell>) ReflectionUtils.getValue("tableCells", tableRow);
cells.add(actualInsertPosition, cell);
return cell;
}
@SuppressWarnings("unchecked")
private void setTableCell(XWPFTableRow row, XWPFTableCell templateCell, int pos) {
List<XWPFTableCell> rows = (List<XWPFTableCell>) ReflectionUtils.getValue("tableCells", row);
rows.set(pos, templateCell);
row.getCtRow().setTcArray(pos, templateCell.getCTTc());
}
private int getColIndex(XWPFTableCell cell) {
XWPFTableRow tableRow = cell.getTableRow();
int orginalCol = 0;
for (int i = 0; i < tableRow.getTableCells().size(); i++) {
XWPFTableCell current = tableRow.getCell(i);
int intValue = 1;
CTTcPr tcPr = current.getCTTc().getTcPr();
if (null != tcPr) {
CTDecimalNumber gridSpan = tcPr.getGridSpan();
if (null != gridSpan) {
intValue = gridSpan.getVal().intValue();
}
}
orginalCol += intValue;
if (current == cell) {
return orginalCol - intValue;
}
}
return -1;
}
private int getActualInsertPosition(XWPFTableRow tableRow, int insertPosition) {
int orginalCol = 0;
for (int i = 0; i < tableRow.getTableCells().size(); i++) {
XWPFTableCell current = tableRow.getCell(i);
int intValue = 1;
CTTcPr tcPr = current.getCTTc().getTcPr();
if (null != tcPr) {
CTDecimalNumber gridSpan = tcPr.getGridSpan();
if (null != gridSpan) {
intValue = gridSpan.getVal().intValue();
}
}
orginalCol += intValue;
if (orginalCol - intValue == insertPosition && intValue == 1) {
return i;
}
}
return -1;
}
private XWPFTableCell getActualCell(XWPFTableRow tableRow, int insertPosition) {
int orginalCol = 0;
for (int i = 0; i < tableRow.getTableCells().size(); i++) {
XWPFTableCell current = tableRow.getCell(i);
int intValue = 1;
CTTcPr tcPr = current.getCTTc().getTcPr();
if (null != tcPr) {
CTDecimalNumber gridSpan = tcPr.getGridSpan();
if (null != gridSpan) {
intValue = gridSpan.getVal().intValue();
}
}
orginalCol += intValue;
if (orginalCol - 1 >= insertPosition) {
return current;
}
}
return null;
}
}