使用Poi-tl實現(xiàn)的文檔導出

World模板引擎

項目利用這個模板引擎實現(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、不足之處

image.png
  • 針對不同的橋型,各個部位下的部件都不相同,所以使用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打頭)
image.png

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


image.png

自定義模板嵌套的具體模板
  • 寫一個計算的注解,@CalculateAnn,并寫一個bean,利用反射將所有標記有@CalculateAnn注解的方法解析出來,并利緩存起來,后面需要使用


    @CalculateAnn
解析所有使用了此注解的方法
  • 寫一個枚舉類用來識別對應的標記具體是那個模板,code就是在模板中定義的字段
    image.png
具體有哪些模板
  • 具體實現(xiàn)對應的含有注解的方法,返回Object


    image.png
  • 實現(xiàn)具體導出方法
    1)獲取數(shù)據(jù)庫中當前項目的所有模板字段


    image.png

2)配置具體有表格,比如圖片,設備表、計算表,人員表,病害統(tǒng)計,總結等等


image.png

這里面的DetailTablePolicyMemberTablePolicy這兩個是復寫了的,詳細代碼,貼到了最后面,都是根據(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中


image.png

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;
    }

}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容