項目利用這個模板引擎實現(xiàn)了超級復雜的world導出,現(xiàn)在記錄一下項目中實際使用的一些操作邏輯。
1、poi-tl簡介
至于非常詳細的介紹,請看上面的World模板引擎
- poi-tl(poi template language)是Word模板引擎,基于Word模板和數(shù)據(jù)生成新的文檔。(基于Apache POI)
- 具體用到的一些功能
| 引擎功能 | 描述 | 標簽 |
|---|---|---|
| 文本 | 將標簽渲染為文本 | |
| 圖片 | 將標簽渲染為圖片 | {{@xx}} |
| 表格 | 將標簽渲染為表格 | {{#xx}} |
| 列表 | 將標簽渲染為列表 | {{*xx}} |
| 圖表 | 條形圖(3D條形圖)、柱形圖(3D柱形圖)、面積圖(3D面積圖)、折線圖(3D折線圖)、雷達圖、餅圖(3D餅圖)等圖表渲染 | |
| 文本框 | 文本框內(nèi)標簽支持 | |
| 樣式 | 模板即樣式,同時代碼也可以設置樣式 | |
| 模板嵌套 | 模板包含子模板,子模板再包含子模板 | {{+var}} |
| 合并 | Word合并Merge,也可以在指定位置進行合并 | |
| 用戶自定義函數(shù)(插件) | 在文檔任何位置執(zhí)行函數(shù) | |
| If Condition判斷 | 內(nèi)隱藏或者顯示某些文檔內(nèi)容(包括文本、段落、圖片、表格、列表、圖表等) | |
| Foreach Loop循環(huán) | 循環(huán)某些文檔內(nèi)容(包括文本、段落、圖片、表格、列表、圖表等) | |
| Loop表格行 | 循環(huán)渲染表格的某一行 | |
| Loop表格列 | 循環(huán)渲染表格的某一列 | |
| Loop有序列表 | 支持有序列表的循環(huán),同時支持多級列表 | |
| 圖片替換 | 將原有圖片替換成另一張圖片 | |
| 書簽、錨點、超鏈接 | 支持設置書簽,文檔內(nèi)錨點和超鏈接功能 | |
| 強大的表達式 | 完全支持SpringEL表達式,可以擴展更多的表達式:OGNL, MVEL… | |
| 標簽定制 | 支持自定義標簽前后綴 |
-
對于模板嵌套
image.png
模板標簽
{{?sections}}{{/sections}} 區(qū)塊對標簽
引用標簽 : 在圖或者圖表中添加標簽,可直接操作圖或者圖表數(shù)據(jù)
2、不足之處

- 針對不同的橋型,各個部位下的部件都不相同,所以使用easy-poi需要針對不同的橋梁編輯多個不同的模板
- 在各個部件位置上的圖片,表格,文本數(shù)據(jù)不能使用一個標簽來代替,導致需要針對的標簽數(shù)量成倍的增加
- 項目中針對不同的橋梁類型也需要不一樣的模板,比如城市橋梁,公路橋梁,公路橋梁里面又分什么單幅橋,雙福橋,三幅橋等等,導致會有很多冗余的代碼產(chǎn)生
- 僅僅支持07版本的word也是只能生成后綴是docx的文檔
- 要導出表格的話,需要自己操作XWPFTable對象。在word中插入表格
3、大致實現(xiàn)思路
我們在做的時候是引入的1.9.0版本,目前版本已經(jīng)更新到1.11.1
- 把整個文檔需要替換的地方使用{{XXXX }}進行標記,并將{{XXXX}}這些里面的內(nèi)容保存到數(shù)據(jù)庫中
整個模板中分為三個類型,第一個系統(tǒng)默認的(用F打頭),第二個文本,第三個圖片(@F打頭)

{{+XXX}}是自定義模板嵌套,模板里面需要子模板


-
寫一個計算的注解,@CalculateAnn,并寫一個bean,利用反射將所有標記有@CalculateAnn注解的方法解析出來,并利緩存起來,后面需要使用
@CalculateAnn

- 寫一個枚舉類用來識別對應的標記具體是那個模板,code就是在模板中定義的字段
image.png

-
具體實現(xiàn)對應的含有注解的方法,返回Object
image.png -
實現(xiàn)具體導出方法
1)獲取數(shù)據(jù)庫中當前項目的所有模板字段
image.png
2)配置具體有表格,比如圖片,設備表、計算表,人員表,病害統(tǒng)計,總結等等

這里面的DetailTablePolicy和MemberTablePolicy這兩個是復寫了的,詳細代碼,貼到了最后面,都是根據(jù)實際需求進行的改造
3)初始化數(shù)據(jù)項目中的很多數(shù)據(jù),進行緩存,在具體的數(shù)據(jù)實現(xiàn)中需要用到(這塊就根據(jù)自己的業(yè)務量來,我們業(yè)務數(shù)據(jù)太多,每次去重復查詢不合適,所以臨時保存到內(nèi)存中)
4)準備一個渲染Map,這個map是為了模板輸出數(shù)據(jù)的額
Map<String, Object> renderMap = new HashMap<>(allField.size());
5)循環(huán)模板字段列表,用invoke調(diào)用具體方法,并將結果放到渲染Map中

6)最后清緩存數(shù)據(jù),以及將所有的數(shù)據(jù)放到模板中進行具體的結果輸出
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"),
//檢測目的及內(nèi)容 {{F_checkContent}}
CHECK_CONTENT("F_checkContent", "", "檢測目的及內(nèi)容"),
//檢測結論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", "重點關注表"),
//病害數(shù)量統(tǒng)計
DISEASE_STATISTICS_TABLE("F_disease_statistics_table","public/disease_statistics.docx","病害數(shù)量統(tǒng)計表"),
//技術評定匯總表
CALCULATION_SUMMARY_TABLE("F_calculation_summary_table","public/calculation_summary_table.docx",".4."),
PROJECT_INFO("F_info", "", "基礎信息,包含項目中的信息:建設單元。項目名稱等"),
BRIDGE_DISEASE("F_bridge_disease", "", "橋梁主要病害統(tǒng)計");
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;
}
}




