POI导出word,增强方案

poi导出word的常规使用方式

先看看常规使用poi生成word的一些方法

// 创建文档对象
XWPFDocument docxDocument = new XWPFDocument();
// 创建段落对象
XWPFParagraph paragraphX = docxDocument.createParagraph();
// 创建文本对象
XWPFRun runX = paragraphX.createRun();

设计思路

主要是思路是将某个模板(自己定义的)上传至服务器,然后解析成对应的ftl文件,之后服务在做报告时,就可以直接拿取对应的ftl文件来生成报告。这种方式会比较灵活,现在用的也比较多。但是对于一份简单的,不需要ftl的文件来作为中间生成文件时,那么可能会比较难了。比如,接下来我们要学习的,使用word的模板作为报告模板,然后直接生成需要的报告。这个思路大概是:用户直接设计一个word模板,然后上传至服务器,服务器将其直接保存到对应的文件夹下;在要生成报告时,直接拿word的模板来填充数据。这样,好像也不是很难,但是想想细节上的地方,可能很多东西就不好做了。

问题所在

我们在使用word作为一份模板时,第一步要解决的就是向模板中定义的字段替换成自己要填充的数据,如:${name}要换成自己的名字;其次就是word中也有表格,如何向word上直接画出表格;再次,表格的行列要怎么合并; 这些都是我们开发中会遇到的问题。

代码演示

public void replaceParagraph(XWPFParagraph xWPFParagraph, Map<String, Object> parametersMap) {
        List<XWPFRun> runs = xWPFParagraph.getRuns();
        String xWPFParagraphText = xWPFParagraph.getText();

        //正则匹配字符串{****}
        String regEx = "\\{.+?\\}";
        Pattern pattern = Pattern.compile(regEx);
        Matcher matcher = pattern.matcher(xWPFParagraphText);

        if (matcher.find()) {
            // 查找到有标签才执行替换
            int beginRunIndex = xWPFParagraph.searchText("{", new PositionInParagraph()).getBeginRun();// 标签开始run位置
            int endRunIndex = xWPFParagraph.searchText("}", new PositionInParagraph()).getEndRun();// 结束标签

            StringBuffer key = new StringBuffer();

            if (beginRunIndex == endRunIndex) {
                // {**}在一个run标签内
                XWPFRun beginRun = runs.get(beginRunIndex);
                String beginRunText = beginRun.text();

                int beginIndex = beginRunText.indexOf("{");
                int endIndex = beginRunText.indexOf("}");
                int length = beginRunText.length();

                if (beginIndex == 0 && endIndex == length - 1) {
                    // 该run标签只有{**}
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    key.append(beginRunText.substring(1, endIndex));
                    insertNewRun.setText(getValueBykey(key.toString(), parametersMap));
                    xWPFParagraph.removeRun(beginRunIndex + 1);
                } else {
                    // 该run标签为**{**}** 或者 **{**} 或者{**}**,替换key后,还需要加上原始key前后的文本
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    key.append(beginRunText.substring(beginRunText.indexOf("{") + 1, beginRunText.indexOf("}")));
                    String textString = beginRunText.substring(0, beginIndex) + getValueBykey(key.toString(), parametersMap)
                            + beginRunText.substring(endIndex + 1);

                    insertNewRun.setText(textString);
                    xWPFParagraph.removeRun(beginRunIndex + 1);
                }

            } else {
                // {**}被分成多个run

                // 先处理起始run标签,取得第一个{key}值
                XWPFRun beginRun = runs.get(beginRunIndex);
                String beginRunText = beginRun.text();
                int beginIndex = beginRunText.indexOf("{");
                if (beginRunText.length() > 1) {
                    key.append(beginRunText.substring(beginIndex + 1));
                }
                ArrayList<Integer> removeRunList = new ArrayList<Integer>();//需要移除的run
                // 处理中间的run
                for (int i = beginRunIndex + 1; i < endRunIndex; i++) {
                    XWPFRun run = runs.get(i);
                    String runText = run.text();
                    key.append(runText);
                    removeRunList.add(i);
                }

                // 获取endRun中的key值
                XWPFRun endRun = runs.get(endRunIndex);
                String endRunText = endRun.text();
                int endIndex = endRunText.indexOf("}");
                //run中**}或者**}**
                if (endRunText.length() > 1 && endIndex != 0) {
                    key.append(endRunText.substring(0, endIndex));
                }

                // 取得key值后替换标签

                // 先处理开始标签
                if (beginRunText.length() == 2) {
                    // run标签内文本{
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    insertNewRun.setText(getValueBykey(key.toString(), parametersMap));
                    xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
                } else {
                    // 该run标签为**{**或者 {** ,替换key后,还需要加上原始key前的文本
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    String textString = beginRunText.substring(0, beginRunText.indexOf("{")) + getValueBykey(key.toString(), parametersMap);
                    // 分行处理 有#的时候分行(给一个标记)
                    if (textString.contains("#")) {
                        String[] textStrings = textString.split("#");
                        for (int i = 0; i < textStrings.length; i++) {
                            insertNewRun.setText(textStrings[i]);
                            insertNewRun.addBreak();// 换行
                        }
                    } else {
                        insertNewRun.setText(textString);
                    }


                    xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
                }

                // 处理结束标签
                if (endRunText.length() == 1) {
                    // run标签内文本只有}
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
                    insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
                    // 设置文本
                    insertNewRun.setText("");
                    xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run

                } else {
                    // 该run标签为**}**或者 }** 或者**},替换key后,还需要加上原始key后的文本
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
                    insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
                    // 设置文本
                    String textString = endRunText.substring(endRunText.indexOf("}") + 1);
                    insertNewRun.setText(textString);
                    xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run
                }

                // 处理中间的run标签
                for (int i = 0; i < removeRunList.size(); i++) {
                    XWPFRun xWPFRun = runs.get(removeRunList.get(i));//原始run
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(removeRunList.get(i));
                    insertNewRun.getCTR().setRPr(xWPFRun.getCTR().getRPr());
                    insertNewRun.setText("");
                    xWPFParagraph.removeRun(removeRunList.get(i) + 1);//移除原始的run
                }

            }// 处理${**}被分成多个run
            replaceParagraph(xWPFParagraph, parametersMap);
        }

    }

poi-tl 处理word

表格的操作方式和上面的代码也是大同小异,也是要写一个标签,才能知道哪里需要循环,从上面的代码就可以看出用原生poi操作word显得非常复杂,这个时候我就要给大家推荐一种新的poi的操作, poi-template-language.
模板和插件构建了整个Poi-tl 的核心。
基于Apache POI进行了一些增强封装,如合并多个Word文档、合并单元格、图片处理等,插件机制使得可以基于模板引擎特性扩展出更丰富的功能。

<dependency>
  <groupId>com.deepoove</groupId>
  <artifactId>poi-tl</artifactId>
  <version>1.4.2</version>
</dependency>

2分钟快速入门

从一个超级简单的例子开始:把{{title}}替换成"Poi-tl 模板引擎"。

  1. 新建文档template.docx,包含文本{{title}}
  2. TDO模式:Template + data-model = output
// 核心API采用了极简设计,只需要一行代码
XWPFTemplate template = XWPFTemplate.compile("~/template.docx").render(new HashMap<String, Object>(){{
        put("title", "Poi-tl 模板引擎");
}});
FileOutputStream out = new FileOutputStream("out_template.docx");
template.write(out);
out.flush();
out.close();
template.close();

基本语法

所有的语法结构都是以 {{ 开始,以 }} 结束。

文本模板 {{var}}

TextRenderDataString数据模型,继承模板样式的同时,也可以自定义颜色、字体等样式。

Map<String, Object> datas = new HashMap<String, Object>();
datas.put("author", new TextRenderData("00FF00", "Sayi卅一"));
datas.put("introduce", "http://www.deepoove.com");

图片模板 {{@var}}

//本地图片
put("localPicture", new PictureRenderData(120, 120, "src/test/resources/sayi.png"));
//本地图片byte数据
put("localBytePicture", new PictureRenderData(100, 120, ".png", BytePictureUtils.getLocalByteArray(new File("src/test/resources/logo.png"))));

表格模板 {{#var}}

RowRenderData header = RowRenderData.build(new TextRenderData("FFFFFF", "姓名"), new TextRenderData("FFFFFF", "学历"));
RowRenderData row = RowRenderData.build("张三", "研究生");
put("table", new MiniTableRenderData(header, Arrays.asList(row)));

列表模板 {{*var}}

put("feature", new NumbericRenderData(new ArrayList<TextRenderData>() {
  {
    add(new TextRenderData("Plug-in grammar, add new grammar by yourself"));
    add(new TextRenderData("Supports word text, header, footer..."));
    add(new TextRenderData("Templates, not just templates, but also style templates"));
  }
}));

文档模板 {{+var}}

DocxRenderData数据模型,支持Word文档的合并,文档模板(重复文档段落)被集合数据循环渲染后合并。

List<SegmentData> segments = new ArrayList<SegmentData>();
SegmentData s1 = new SegmentData();
s1.setTitle("经常抱怨的自己");
s1.setContent("每个人生活得都不容易,经常向别人抱怨的人,说白了就是把对方当做“垃圾场”,你一股脑地将自己的埋怨与不满倒给别人,自己倒是爽了,你有考虑过对方的感受吗?对方的脸上可能一笑了之,但是心里可能有一万只草泥马奔腾而过。");
segments.add(s1);

SegmentData s2 = new SegmentData();
s2.setTitle("拖拖拉拉的自己");
s2.setContent("能够今天做完的事情,不要拖到明天,你的事情没有任何人有义务去帮你做;不要做“宅男”、不要当“宅女”,放假的日子约上三五好友出去转转;经常动手做家务,既能分担伴侣的负担,又有一个干净舒适的环境何乐而不为呢?");
segments.add(s2);

put("docx_word", new DocxRenderData(new File("~/segment.docx"), segments));

详细示例

有如下付款通知书模板,我们现在要动态填充内容

/**
 * 付款通知书:表格操作示例
 */
public class PaymentExample {

    PaymentData datas = new PaymentData();

    Style headTextStyle = new Style();
    TableStyle headStyle = new TableStyle();
    TableStyle rowStyle = new TableStyle();

    @Before
    public void init() {
        headTextStyle.setFontFamily("Hei");
        headTextStyle.setFontSize(9);
        headTextStyle.setColor("7F7F7F");

        headStyle.setBackgroundColor("F2F2F2");
        headStyle.setAlign(STJc.CENTER);

        rowStyle = new TableStyle();
        rowStyle.setAlign(STJc.CENTER);


        datas.setNO("KB.6890451");
        datas.setID("ZHANG_SAN_091");
        datas.setTaitou("深圳XX家装有限公司");
        datas.setConsignee("丙丁");

        datas.setSubtotal("8000");
        datas.setTax("600");
        datas.setTransform("120");
        datas.setOther("250");
        datas.setUnpay("6600");
        datas.setTotal("总共:7200");


        RowRenderData header = RowRenderData.build(new TextRenderData("日期", headTextStyle),
                new TextRenderData("订单编号", headTextStyle), new TextRenderData("销售代表", headTextStyle),
                new TextRenderData("离岸价", headTextStyle), new TextRenderData("发货方式", headTextStyle),
                new TextRenderData("条款", headTextStyle), new TextRenderData("税号", headTextStyle));
        header.setStyle(headStyle);

        RowRenderData row = RowRenderData.build("2018-06-12", "SN18090", "李四", "5000元", "快递", "附录A", "T11090");
        row.setStyle(rowStyle);
        MiniTableRenderData miniTableRenderData = new MiniTableRenderData(header, Arrays.asList(row), MiniTableRenderData.WIDTH_A4_MEDIUM_FULL);
        miniTableRenderData.setStyle(headStyle);
        datas.setOrder(miniTableRenderData);

        DetailData detailTable = new DetailData();
        RowRenderData good = RowRenderData.build("4", "墙纸", "书房+卧室", "1500", "/", "400", "1600");
        good.setStyle(rowStyle);
        List<RowRenderData> goods = Arrays.asList(good, good, good);
        RowRenderData labor = RowRenderData.build("油漆工", "2", "200", "400");
        labor.setStyle(rowStyle);
        List<RowRenderData> labors = Arrays.asList(labor, labor, labor, labor);
        detailTable.setGoods(goods);
        detailTable.setLabors(labors);
        datas.setDetailTable(detailTable);
    }

    @Test
    public void testResumeExample() throws Exception {
        Configure config = Configure.newBuilder().customPolicy("detail_table", new DetailTablePolicy()).build();
        XWPFTemplate template = XWPFTemplate.compile("src/test/resources/付款通知书.docx", config).render(datas);
        FileOutputStream out = new FileOutputStream("e:/out_付款通知书.docx");
        template.write(out);
        out.flush();
        out.close();
        template.close();
    }

}
public class DetailData {
    
    // 货品数据
    private List<RowRenderData> goods;
    
    // 人工费数据
    private List<RowRenderData> labors;

    public List<RowRenderData> getGoods() {
        return goods;
    }

    public void setGoods(List<RowRenderData> goods) {
        this.goods = goods;
    }

    public List<RowRenderData> getLabors() {
        return labors;
    }

    public void setLabors(List<RowRenderData> labors) {
        this.labors = labors;
    }
}

// 省略了后面的构造方法
public class PaymentData {
    private MiniTableRenderData order;
    private String NO;
    private String ID;
    private String taitou;
    private String consignee;
    @Name("detail_table")
    private DetailData detailTable;
    private String subtotal;
    private String tax;
    private String transform;
    private String other;
    private String unpay;
    private String total;
    }

运行代码,导出的word结果;

由上面可以看出这样的操作word更加简化了我们的操作方法。

猜你喜欢

转载自blog.csdn.net/AllenJoe666/article/details/85064209