wangguanquan / eec

A fast and lower memory excel write/read tool.一个非POI底层,支持流式处理的高效且超低内存的Excel读写工具
https://github.com/wangguanquan/eec/wiki
Apache License 2.0
222 stars 58 forks source link

TemplateSheet模板导出 如何支持list对应多行模板 #403

Open zzmark opened 1 day ago

zzmark commented 1 day ago

问题有两个,

  1. TemplateSheet 是逐行读取模板数据再替换,以下模板只能识别到第一行,如何支持这样的逻辑
序号 编码 名称1 名称2 数量
${list.group}          
${list.no} ${list.code} ${list.name} ${list.name2} ${list.num}
  1. 我有多层级的嵌套结构,数据如下:
    [
    {
    "group": "G1",
    "row": [
      {
        "no": 1,
        "code": "A"
      },
      {
        "no": 2,
        "code": "B"
      }
    ]
    },
    {
    "group": "G2",
    "row": [
      {
        "no": 3,
        "code": "C"
      }
    ]
    }
    ]
配套模板: 序号 编码 名称1 名称2 数量
${list.group}          
${list.row.no} ${list.row.code} ${list.row.name} ${list.row.name2} ${list.row.num}
期望的结果格式: 序号 编码 名称1 名称2 数量
G1          
1 A
2 B
G2          
3 C

现在的逻辑,标记list是namespace整个标记,没法识别里面的结构 如何支持这样的逻辑

wangguanquan commented 1 day ago

TemplateSheet目前没有分组的功能,建议直接使用ListSheet或者SimpleSheet,使用ListSheet时需要先处理一次数据即可,将row这一层提出,并将group转换为row一样的格式即可。

List<Item> list = new ArrayList<>();
for (List<Group> group : groupList) {
    // 将Group转为Item
    Item item = new Item();
    item.setNo(group.getGroup());
    list.add(item);

    // 将row追加到Group之后
    list.addAll(group.getRow());
}
zzmark commented 1 day ago

TemplateSheet目前没有分组的功能,建议直接使用ListSheet或者SimpleSheet,使用ListSheet时需要先处理一次数据即可,将row这一层提出,并将group转换为row一样的格式即可。

有点难,输出的表格是个报告单,格式相当复杂,分组表格只是中间的一部分,上下还有其它内容。 若是使用 ListSheet或者SimpleSheet,其它部分需要通过代码编写,成本有点高。 目前的业务场景使用 TemplateSheet 类似技术是唯一方案。

除此之外其实还要有根据值修改样式的需要,这部分我自行改了些代码实现。 复杂key反射这个也自行实现了。 但分组和多行这两个点实在没研究明白

wangguanquan commented 1 day ago

如果不需要做成通用功能的话则可以只配置row这一行,重写TemplateSheet#getNodeValue方法,将group转为单独的对象不移动下标

zzmark commented 1 day ago

如果不需要做成通用功能的话则可以只配置row这一行,重写TemplateSheet#getNodeValue方法,将group转为单独的对象不移动下标

目前重写TemplateSheet#getNodeValue方法,配合表达式引擎,实现了复杂格式。 还在找方法实现多行,光靠已有的语法感觉不太够用

wangguanquan commented 18 hours ago

你说的多行是指group行吗?如果是的话可以按照我上面回复的重写TemplateSheet#getNodeValue方法,将group转为单独的对象不移动下标即可,我写一个示例可以参考

复制下面的代码并替换模版路径可运行

@Test public void groupTest() throws IOException {
    List<ItemGroup> list = new ArrayList<>();
    ItemGroup group1 = new ItemGroup();
    group1.group = "G1";
    group1.row = Arrays.asList(new Item("1", "A"), new Item("2", "B"));
    list.add(group1);

    ItemGroup group2 = new ItemGroup();
    group2.group = "G2";
    group2.row = Arrays.asList(new Item("3", "C"), new Item("4", "D"));
    list.add(group2);

    new Workbook()
        .addSheet(new MyGroupTemplateSheet(defaultTestPath.resolve("template测试.xlsx"))
            .setData("list", list))
        .writeTo(defaultTestPath.resolve("result.xlsx"));
}

public static class ItemGroup {
    private String group;
    List<Item> row;
}

public static class Item {
    private String no;
    private String code;
    public Item() { }
    public Item(String no, String code) {
        this.no = no;
        this.code = code;
    }
}

public static class MyGroupTemplateSheet extends TemplateSheet {
    // i 记录Group下标
    // ii 记录子集下标
    // ri 记录row下标
    private int i = 0, ii = -1, ri;
    public MyGroupTemplateSheet(Path templatePath) {
        super(templatePath);
    }

    @Override
    protected void fillValue(Row row, Cell cell, PreCell pn, Column emptyColumn) {
        if (ri == 0) ri = row.index;
        else if (ri < row.index) {
            ri = row.index;
            ii++; // 移动子集游标
        }
        super.fillValue(row, cell, pn, emptyColumn);
    }

    @Override
    protected Object getNodeValue(Node node) {
        // 纯文本
        if ((node.option & 1) == 0) return node.val;
        ValueWrapper vw = namespaceMapper.get(node.namespace);
        Object e = null;
        if (vw != null) {
            Object o = vw.list != null && i < vw.list.size() ? vw.list.get(i) : null; // <- 注意这里不使用ValueWrapper的游标
            // ItemGroup对象特殊处理
            if (o instanceof ItemGroup) {
                ItemGroup sub = (ItemGroup) o;
                // Group行
                if (ii < 0) {
                    // Group名放到序号列
                    e = "row.no".equals(node.val) ? sub.group : null;
                } else {
                    List<Item> rows = sub.row;
                    // Group结束
                    if (ii >= rows.size()) {
                        ii = -1;
                        i++;
                       // 判断是否已全部结束,全部结束后将option标记为-1
                        if (i >= vw.list.size()) vw.option = -1;
                        // 取下一个分组
                        else e = getNodeValue(node);
                    } else {
                        // FIXME 这里我只是简单取值,实际场景自行处理,提前将子集的反射属性放入accessibleObjectMap即可
                        Item item = rows.get(ii);
                        try {
                            Field field = item.getClass().getDeclaredField(node.val.substring(4));
                            field.setAccessible(true);
                            e = field.get(item);
                        } catch (Exception ex) {
                            // Ignore
                        }
                    }
                }
                // 重置ValueWrapper游标,不然会跳过整个group
                vw.i = i - 1; // 父类resetBlockData方法会在写完整行后将游标向后移动一次,所以-1为了避免跳过最后一组
            } else e = super.getNodeValue(node);
        }
        return e;
    }
}
wangguanquan commented 18 hours ago

模板如下

序号: 编码
${list.row.no} ${list.row.code}
zzmark commented 18 hours ago

good,晚点我尝试一下