java通过poi-tl模板引擎生成表格(Word)

java通过poi-tl生成表格以及源码分析

 最近导出的word文件要求是越来越多了,而且对样式也做了很多要求,今天参考文档学习了一下普通表格构建表格、动态构建word表格的方法。

依赖

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

模板

在这里插入图片描述 1. {{ID}}、{{ID}} 普通文本信息占位符;
2. {{#order}} 普通word表格占位符;
3. 最后的表格是动态构建word表格的模板,样式已经设置好;

如何动态生成表格

当需求中的表格更加复杂的时候,我们完全可以设计好那些固定的部分,将需要动态渲染的部分单元格交给自定义模板渲染策略。poi-tl提供了抽象表格策略 DynamicTableRenderPolicy 来实现这样的功能。

public abstract class DynamicTableRenderPolicy implements RenderPolicy {
  public abstract void render(XWPFTable table, Object data);
}

{{detailTable}}标签可以在表格内的任意单元格内,DynamicTableRenderPolicy会获取XWPFTable对象进而获得操作整个表格的能力。
1、首先新建渲染策略DetailTablePolicy,继承于抽象表格策略,重写其render方法。

public class DetailTablePolicy extends DynamicTableRenderPolicy

2、然后将模板标签{{detailTable}}设置成此策略。

 Configure config = Configure.builder().bind("detailTable", new DetailTablePolicy()).build();

参考文档及分析

但是在测试是发现文档中例子始终无法生成最下面的表格,即货物明细表,
debug发现,策略类中的data为空,难怪没有生成表格。
在这里插入图片描述
但是数据明明已经初始化好了,那接下来就是数据在传输的是时候在哪丢失或者传进去的参数根本就没被取到。

跟踪代码,
在这里插入图片描述
最后在ElementProcessor中,有这样一段代码

void visit(ElementTemplate eleTemplate) {
        RenderPolicy policy = eleTemplate.findPolicy(template.getConfig());
        Objects.requireNonNull(policy, "Cannot find render policy: [" + eleTemplate.getTagName() + "]");
        if (policy instanceof DocxRenderPolicy) return;
        logger.info("Start render Template {}, Sign:{}, policy:{}", eleTemplate, eleTemplate.getSign(),
                ClassUtils.getShortClassName(policy.getClass()));
        policy.render(eleTemplate, renderDataCompute.compute(eleTemplate.getTagName()), template);
    }

通过policy.render(eleTemplate, renderDataCompute.compute(eleTemplate.getTagName()), template);这个方法最后进入到我们自己写的策略类DetailTablePolicy ,真是妙啊!
那既然是从这里进入我们的策略类,那也是参数的入口,就让我们看看为什么DetailTablePolicy 中data为null?
在这里插入图片描述
data就是renderDataCompute.compute(eleTemplate.getTagName()),其中
eleTemplate.getTagName()为detail_table而且renderDataCompute也没有与detail_table相对应的属性,虽然理论上应该是获取detailTable,但是现实是木有取到。因此也验证了我们前面的猜测,数据确实是传过来了,只是没有获取到。因此我就有个大胆的猜测,是不是因为属性字段(detail_table和detailTable)没有完全对应,因此我就把detail_table替换成了detailTable(代码和docx模板全部替换),结果妹想到呀,成了!!!
哈哈哈,真是走了狗屎运了。还望路过的各位大佬指点指点

补充renderDataCompute.compute(eleTemplate.getTagName()):
虽然走了狗屎运,但是还是想探个究竟,compute到底做了些什么
进入DefaultELRenderDataCompute类中

public Object compute(String el) {
        try {
            if (null != envObject && !el.contains("#this")) {
                try {
                    Object val = envObject.eval(el);
                    if (null != val) {
                        return val;
                    }
                } catch (Exception e) {
                    // ignore
                }
            }
            // 进入这个方法
            return elObject.eval(el);
        } catch (ExpressionEvalException e) {
            if (isStrict) throw e;
            // Cannot calculate the expression, the default returns null
            return null;
        }
    }

进入DefaultEL类中

public Object eval(String el) {
		// THIS是什么? private static final String THIS = "#this";
        if (THIS.equals(el)) {
            return model;
        }
        Dot dot = new Dot(el);
        // 继续进入
        return dot.eval(this);
    }

在Dot类中终于找到了!!!

public Object eval(DefaultEL elObject) {
		// 如果cache中有el相等的key,则返回cache中相应的value
        if (elObject.cache.containsKey(el)) return elObject.cache.get(el);
        // 如果target为null result就为evalKey(elObject.model)
        // evalKey   是啥???  真是要了老命了,我以为结束了呢,继续看方法evalKey吧
        Object result = null != target ? result = evalKey(target.eval(elObject)) : evalKey(elObject.model);
        if (null != result) elObject.cache.put(el, result);
        return result;
    }

	
    private Object evalKey(Object obj) {
        if (null == obj) {
            throw new ExpressionEvalException("Error eval " + key + ", the value of " + target + " is null");
        }
        final Class<?> objClass = obj.getClass();
        if (obj instanceof String || obj instanceof Number || obj instanceof java.util.Date || obj instanceof Collection
                || obj instanceof Boolean || objClass.isArray() || objClass.isPrimitive()) {
            throw new ExpressionEvalException(
                    "Error eval " + key + ", the type of " + target + "must be Hash, but is " + objClass);
        }
		// 重点是这里  如果是Map类型  返回对应的value
		// 但是我们的elObject.model是Map吗? 看下图
        if (obj instanceof Map) {
            return ((Map<?, ?>) obj).get(key);
        }

        // introspector
        Method readMethod = ReadMethodFinder.find(objClass, key);
        if (null != readMethod) {
            try {
                return readMethod.invoke(obj);
            } catch (Exception e) {
                logger.info("Introspector {} fail: {}", key, e.getMessage());
            }
        }
		// 通过对象的类信息反射返回对应属性的值
        // reflect
        Field field = FieldFinder.find(objClass, key);
        if (null == field) {
            throw new ExpressionEvalException("Cannot find property " + key + " from " + objClass);
        } else {
            try {
                return field.get(obj);
            } catch (Exception e) {
                throw new ExpressionEvalException("Error read the property:" + key + " from " + objClass);
            }
        }
    }

elObject.model和elObject.cache的类型
在这里插入图片描述

static Field find(Class<?> objClass, String key) {
        Class<?> clazz = objClass;
        Field field = null;
        while (clazz != Object.class) {
            try {
                field = findInClazz(clazz, key);
                // 暴力访问
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException e) {
                // do nothing, go to super class
            } catch (Exception e) {
                logger.warn("Error read the property:" + key + " from " + objClass);
            }
            clazz = clazz.getSuperclass();
        }
        return null;

    }


static Field findInClazz(Class<?> clazz, String key) throws NoSuchFieldException {
        Field field = null;
        try {
        	// 通过类信息获取对应字段的值
            field = clazz.getDeclaredField(key);
            return field;
        } catch (Exception e) {
        }

        Field[] fields = cache.get(clazz);
        if (null == fields) {
            fields = clazz.getDeclaredFields();
            cache.put(clazz, fields);
        }
        for (Field f : fields) {
            Name annotation = f.getAnnotation(Name.class);
            if (null != annotation && key.equals(annotation.value())) {
                return f;
            }
        }
        throw new NoSuchFieldException(key);
    }

Poi-tl Documentation

附上代码如下:

代码

  • 实体类DetailData(动态构建word表格的数据)
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;
    }
}
  • 实体类PaymentData (相当于数据集,包换word模板中需要的数据都在这里)
public class PaymentData {

    private  String NO;
    private  String ID;
    private  String taitou;
    private  String consignee;
    private  String subtotal;
    private  String tax;
    private  String transform;
    private  String other;
    private  String unpay;
    private  String total;
    private TableRenderData order;
    private DetailData detailTable;

    public String getNO() {
        return NO;
    }

    public void setNO(String NO) {
        this.NO = NO;
    }

    public String getID() {
        return ID;
    }

    public void setID(String ID) {
        this.ID = ID;
    }

    public String getTaitou() {
        return taitou;
    }

    public void setTaitou(String taitou) {
        this.taitou = taitou;
    }

    public String getConsignee() {
        return consignee;
    }

    public void setConsignee(String consignee) {
        this.consignee = consignee;
    }

    public String getSubtotal() {
        return subtotal;
    }

    public void setSubtotal(String subtotal) {
        this.subtotal = subtotal;
    }

    public String getTax() {
        return tax;
    }

    public void setTax(String tax) {
        this.tax = tax;
    }

    public String getTransform() {
        return transform;
    }

    public void setTransform(String transform) {
        this.transform = transform;
    }

    public String getOther() {
        return other;
    }

    public void setOther(String other) {
        this.other = other;
    }

    public String getUnpay() {
        return unpay;
    }

    public void setUnpay(String unpay) {
        this.unpay = unpay;
    }

    public String getTotal() {
        return total;
    }

    public void setTotal(String total) {
        this.total = total;
    }

    public TableRenderData getOrder() {
        return order;
    }

    public void setOrder(TableRenderData order) {
        this.order = order;
    }

    public DetailData getDetailTable() {
        return detailTable;
    }

    public void setDetailTable(DetailData detailTable) {
        this.detailTable = detailTable;
    }
}
  • 策略类(动态构建表格的策略方法)
public class DetailTablePolicy extends DynamicTableRenderPolicy {
    // 货品填充数据所在行数
    int goodsStartRow = 2;
    // 人工费填充数据所在行数
    int laborsStartRow = 5;

    @Override
    public void render(XWPFTable table, Object data) throws Exception {
        if (null == data) return;
        DetailData detailData = (DetailData) data;

        // 人工费
        List<RowRenderData> labors = detailData.getLabors();
        if (null != labors) {
            table.removeRow(laborsStartRow);
            // 循环插入行
            for (int i = 0; i < labors.size(); i++) {
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(laborsStartRow);
                for (int j = 0; j < 7; j++) insertNewTableRow.createCell();

                // 合并单元格
                TableTools.mergeCellsHorizonal(table, laborsStartRow, 0, 3);
                // 单行渲染
                TableRenderPolicy.Helper.renderRow(table.getRow(laborsStartRow), labors.get(i));
            }
        }

        // 货物
        List<RowRenderData> goods = detailData.getGoods();
        if (null != goods) {
            table.removeRow(goodsStartRow);
            for (int i = 0; i < goods.size(); i++) {
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
                for (int j = 0; j < 7; j++) insertNewTableRow.createCell();
                TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), goods.get(i));
            }
        }
    }

@DisplayName("Example for Table")
public class generateTable{
	  String resource = "src/main/resources/payment.docx";
    PaymentData datas = new PaymentData();
	
	// 初始化数据
    @BeforeEach
    public  void init() {
        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 = Rows.of("日期", "订单编号", "销售代表", "离岸价", "发货方式", "条款", "税号").bgColor("F2F2F2").center()
                .textColor("7F7f7F").textFontFamily("Hei").textFontSize(9).create();
        RowRenderData row = Rows.of("2018-06-12", "SN18090", "李四", "5000元", "快递", "附录A", "T11090").center().create();
        BorderStyle borderStyle = new BorderStyle();
        borderStyle.setColor("A6A6A6");
        borderStyle.setSize(4);
        borderStyle.setType(XWPFTable.XWPFBorderType.SINGLE);
        TableRenderData table = Tables.ofA4MediumWidth().addRow(header).addRow(row).border(borderStyle).center()
                .create();
        datas.setOrder(table);

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

	// 测试方法
    @Test
    public void testPaymentExample() throws Exception {
        Configure config = Configure.builder().bind("detailTable", new DetailTablePolicy()).build();
        XWPFTemplate template = XWPFTemplate.compile(resource, config).render(datas);
        template.writeToFile("out_example_payment.docx");
    }



}



  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
poi-tl是一个用于生成Word文档的Java模板引擎,它可以方便地创建和操作Word表格。在使用poi-tl生成Word表格时,你需要添加以下依赖到你的项目中: ```xml <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.10.0</version> </dependency> ``` 通过使用poi-tl提供的API,你可以轻松地创建和设置表格的样式。例如,如果你想要整行加粗,可以使用`textBold()`方法来设置行数据的样式。以下是一个示例代码: ```java RowRenderData titleRow = Rows.of(firstLine.getTitle(), null, null, null, null, null).textBold().create(); ``` 如果你想要指定特定单元格的样式,比如让表格中每一行的第二列内容加粗并且为红色字体,你可以使用以下方法: ```java CellStyle boldAndRedTextStyle = new CellStyle(); boldAndRedTextStyle.setDefaultParagraphStyle(ParagraphStyle.builder().withDefaultTextStyle(Style.builder().buildColor("FF00000").buildBold().build()).build()); for (RowRenderData rowRenderData : tableRenderData.getRows()) { rowRenderData.getCells().get(1).setCellStyle(boldAndRedTextStyle); } ``` 这样,你就可以使用poi-tl生成带有特定样式的Word表格了。 #### 引用[.reference_title] - *1* [java通过poi-tl模板引擎生成表格Word)](https://blog.csdn.net/qq_45731464/article/details/119247125)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [[编程] POI-TL 根据模版生成Word文档的一些使用技巧汇总](https://blog.csdn.net/lmcboy/article/details/128216186)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值