Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

多行动态表头excel导入 #274

Closed
linmii opened this issue Jul 28, 2022 · 27 comments
Closed

多行动态表头excel导入 #274

linmii opened this issue Jul 28, 2022 · 27 comments

Comments

@linmii
Copy link
Contributor

linmii commented Jul 28, 2022

对于用ListMapSheet导出的多行动态表头excel,是否有方便的方法进行导入呢?
看目录导入的方法,获取到sheet后,rows返回第1行开始的数据,dataRows返回第2行开始的数据,都不太好处理数据。
希望能像导出的时候使用List一样,导入的时候也可以转成Map。
以下图为例,Map中的key分别姓名、二级机构、...、2021-07-01得分、2021-07-01考试时长、2021-07-02得分、2021-07-02考试时长...

image

@wangguanquan
Copy link
Owner

因为很难知道哪里是头哪里是数据的原因,所以多行表头建议二次封装,Row提供了类似jdbc的基础接口,可以通过下标获取单元格内容

@linmii
Copy link
Contributor Author

linmii commented Jul 28, 2022

在接触eec之前,一直使用的是easypoi,支撑设置导入参数,在导入的时候设置表头的行数,是否也可以通过这种方式进行处理呢?
当然,通过列索引的方式获取数据也是比较方便,之前看到都是转成对象,没有注意到可以通过列索引获取数据
image

@wangguanquan
Copy link
Owner

可以考虑加入类似的参数。

row是Stream包裹所以可以使用filter做类似的处理,sheet.rows().filter(r -> r.getRowNum()>=2).map(this:toMap).collect(),在toMap方法里将row转map这样处理看上去也不会太乱

@wangguanquan
Copy link
Owner

不过并不建议将数据转为Map,因为每一行都需要将表头作为key,1w行数据转Map就需要保存1w次表头,太浪费空间了。

@linmii
Copy link
Contributor Author

linmii commented Jul 29, 2022

转map浪费空间这个确实无解了,只是方便操作一下。通过列索引的方式,就是比较硬编码了。
如果日期下的二级表头增加了一列,列索引就得改代码了。map方式通过key.startWith和key.endWith方式,就不受影响

@wangguanquan
Copy link
Owner

有好的想法欢迎提交Pull Request

@linmii
Copy link
Contributor Author

linmii commented Jul 30, 2022

最近没时间,加班状态中……等有时间了先研究研究文档格式
eec确实好用!!!

@wangguanquan
Copy link
Owner

好用是话请给个Star并推荐给身边的朋友

@linmii
Copy link
Contributor Author

linmii commented Jul 31, 2022

从第一次看到eec,就已经Star了,现在是看到有用到导入导出的就说试试这个,哈哈^_^

@linmii
Copy link
Contributor Author

linmii commented Jul 31, 2022

发现一个问题,通过列索引的方式读取excel文件时候,每一行获取到的列数不一样,通过sheet.dimension.lastColumn循环获取列数据的时候会报错:java.lang.ArrayIndexOutOfBoundsException: Index 26 out of bounds for length 26
是不是应该判断一下,在获取sheet.dimension.lastColumn范围内的列数据时,如果没有,返回null,超过范围的才报这个错误?
可能是这个excel格式不标准?附件已通过邮箱发送

image

@wangguanquan
Copy link
Owner

每一行的列数不同是正常的,比例你的示例文件里有些人没有参与过考试或仅参与部分考试,这些行只能读到前几列。

所以每一行都有不同的列范围,不可以通过dimension来判断,你必须通过Row对象的getFirstColumnIndexgetLastColumnIndex来进行判断

dimension是整个Worksheet的有效范围,这里有效包含文字,背景,字体,边框,格式化等一切excel有效数据,所以dimension一般可用于文件行数的大致判断,比如要限制不能超过1w行,此时就可以使用dimension而不需要读取完整个文件,另一个用途是可以用来判断是同步处理还是异步处理,你可以设置阈值比如1千行,当文件行数小于1行时同步处理否则另起线程异步处理。

@linmii
Copy link
Contributor Author

linmii commented Aug 1, 2022

特意看了下Row#to方法,确实是根据columns.length方法循环设置值的,那就是这边获取值的时候要做下判断了。
其实之前想的时,如果columnIndex >= columns.lengthRow#get*方法不做rangeCheck校验,直接返回null。这样可能会能业务造成影响?

@wangguanquan
Copy link
Owner

wangguanquan commented Aug 1, 2022 via email

@linmii
Copy link
Contributor Author

linmii commented Aug 1, 2022

你能将测试代码和抛异常的堆栈信息贴一下吗?我还没找到到问题的原因

在 2022年8月1日,11:29,linmii @.**> 写道: 特意看了下Row#to方法,确实是根据columns.length方法循环设置值的,那就是这边获取值的时候要做下判断了。 其实之前想的时,如果columnIndex >= columns.length,Row#get方法不做rangeCheck校验,直接返回null。这样可能会能业务造成影响? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.

还是之前说的根据sheet.lastColumnIndex方法循环获取每一列的值会报错。

只是突然想到了Row#to方法,会不会也存在ArrayIndexOutOfBoundsException,但是看源码是使用row.columns.length循环取值赋值的,不会存在这个问题。

上次的回复只是说另外一个想法:Row#get**方法不做rangeCheck校验,如果columnIndex >= columns.length,直接返回null,而不是抛出ArrayIndexOutOfBoundsException异常,这样是否可行?

@wangguanquan
Copy link
Owner

wangguanquan commented Aug 2, 2022

如果可以的话还请将测试代码和抛异常的堆栈信息贴一下吧,ArrayIndexOutOfBoundsException我本地确实没有复现,但感觉应该是一个隐藏的BUG,所以还是想复现出来。

下面是范围判断的部分代码,目前仅判断了负数的情况,如果超过范围将返回一个未实例化的Cell,所以就算空Excel文件使用row.getString(1000)也不会抛ArrayIndexOutOfBoundsException异常的

/**
 * Check the cell ranges,
 *
 * @param index the index
 * @exception IndexOutOfBoundsException If the specified {@code index}
 * argument is negative
 */
protected void rangeCheck(int index) {
    if (index < 0)
        throw new IndexOutOfBoundsException("Index: " + index + " is negative.");
}

/**
 * Returns {@link Cell}
 *
 * @param i the position of cell
 * @return the {@link Cell}
 */
protected Cell getCell(int i) {
    rangeCheck(i);
    return i < lc ? cells[i] : UNALLOCATED_CELL;
}

/**
 * Search {@link Cell} by column name
 *
 * @param name the column name
 * @return the {@link Cell}
 */
protected Cell getCell(String name) {
    int i = hr.getIndex(name);
    rangeCheck(i);
    return i < lc ? cells[i] : UNALLOCATED_CELL;
}

@linmii
Copy link
Contributor Author

linmii commented Aug 3, 2022

是我这边代码写错了,lastColumnIndex用了<=,导致报这个错误

    val headRows = 2
    val result = mutableListOf<Map<String, String>>()
    ExcelReader.read(Path("C:/Users/user/Desktop/测试.xlsx"))
        .use {
            val sheet = it.sheet(0)
            val lastColumnIndex = sheet.dimension.lastColumn.toInt()
            val headColumnArray: Array<Array<String?>> =
                Array(headRows) { arrayOfNulls(lastColumnIndex) }
            val headArray = arrayOfNulls<String>(lastColumnIndex)
            sheet.reset()
            sheet.rows()
                .forEach { row ->
                    if (row.rowNum <= headRows) {
                        (0 until lastColumnIndex).forEach { index ->
                            headColumnArray[row.rowNum - 1][index] =
                                if (index > row.lastColumnIndex)
                                    headColumnArray[row.rowNum - 1][index - 1]
                                else
                                    row.getString(index)
                                        ?: if (row.rowNum== headRows) headColumnArray[row.rowNum - 1][index - 1] else ""
                        }
                        if (row.rowNum == headRows) {
                            (0 until lastColumnIndex).forEach { index ->
                                headArray[index] =
                                    (0 until headRows).map { r -> headColumnArray[r][index] }
                                        .joinToString()
                            }
                        }
                    } else {
                        val map = mutableMapOf<String, String>()
                        (0 until lastColumnIndex).forEach { index ->
                            map[headArray[index]!!] =
                                if (index > row.lastColumnIndex) ""
                                else row.getString(index)
                        }
                    }
                }
        }

@wangguanquan
Copy link
Owner

fix#306 分支增加指定表头的位置的方法,可以通过sheet#header(from, to) 指定多行表头,对于上面给的多行表头特别合适

尝鲜的话可以clone并切换到分支fix#306并使用mvn clean install本地打包,就可以使用如下代码进行使用

@Test public void testMerge() throws IOException {
    try (ExcelReader reader = ExcelReader.read(Paths.get("test.xlsx"))) {
        Sheet sheet = reader.sheet(0);
        Row header = sheet.header(1, 3).getHeader(); // 指定从第1行到第3行为表头(不包含第3行)
        println(header);

        sheet.rows().forEach(row -> {
            print(row.getString("2021-07-01:得分"));
            print('|');
            print(row.getString("2021-07-01:考试时长"));
            println();
            print(row.getString("2021-07-02:得分"));
            print('|');
            print(row.getString("2021-07-02:考试时长"));
            println();
            print(row.getString("2021-07-03:得分"));
            print('|');
            print(row.getString("2021-07-03:考试时长"));
            println();
        });
    }
}

多行表头使用(:)冒号拼接,上面代码表头输出如下

姓名 | 二级机构名称 | 2021-07-01:得分 | 2021-07-01:考试时长 | 2021-07-02:得分 | 2021-07-02:考试时长 | 2021-07-03:得分 | 2021-07-03:考试时长
----|------------|----------------|--------------------|----------------|--------------------|----------------|-------------------|

@linmii
Copy link
Contributor Author

linmii commented Nov 17, 2022

Row header = sheet.header(1, 2).getHeader();
HeaderRow#mergeCellsIfNull的第二个参数Row[] rows设置header出错
rows[0].cells只有26列,rows[1].cells有27列,实际应该是27列,因为第1行最后是合并单元格Z1:AA1,只算了26列,运行row.cells[26].setSv("2021-07-10")时数据下标越界

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 26 out of bounds for length 26
	at org.ttzero.excel.reader.HeaderRow.mergeCellsIfNull(HeaderRow.java:751)
	at org.ttzero.excel.reader.HeaderRow.with(HeaderRow.java:118)
	at org.ttzero.excel.reader.XMLSheet.getHeader(XMLSheet.java:309)
	at org.ttzero.excel.reader.XMLSheet.getHeader(XMLSheet.java:272)

image

@linmii
Copy link
Contributor Author

linmii commented Nov 17, 2022

XMLSheet#getHeader(int, int) 292行设置r.lc = row.lc;后,应该判断下最后一列是否是合并单元格,如果是,需要加上合并单元格占用的列数-1,不知道应该要怎么获取这个

@wangguanquan
Copy link
Owner

HeaderRow#with方法有传入合并单元格信息,现在的表头是什么样的,最好能给个截图看下,这个分支还在开发测试样本不够

@linmii
Copy link
Contributor Author

linmii commented Nov 17, 2022

表头如下:
image

样本文件也发邮箱了。

@linmii
Copy link
Contributor Author

linmii commented Nov 17, 2022

我是在想是不是在XMLSheet#getHeader(int, int)中处理会好些?HeaderRow#withHeaderRow#mergeCellsIfNull中的rows参数,都是由XMLSheet#getHeader(int, int)创建并传入的,如果在HeaderRow#with中进行处理,是重新创建row并覆盖原来的值,因为之前的row长度不正确

@wangguanquan
Copy link
Owner

是的,外部提前处理更合理

@wangguanquan
Copy link
Owner

已更新,请pull新代码尝试

仔细看了一下这里不能缩小lc的值相反要扩大,不然与第二行对应不上,缩小后最后一个头只有"考试时长"不会有"2021-07-10:考试时长"

@linmii
Copy link
Contributor Author

linmii commented Nov 29, 2022

下面的代码会报错,还是之前的错误:
Row header = sheet.header(1, 2).getHeader();
HeaderRow#mergeCellsIfNull的第二个参数Row[] rows设置header出错
rows[0].cells只有26列,rows[1].cells有27列,实际应该是27列,因为第1行最后是合并单元格Z1:AA1,只算了26列,运行row.cells[26].setSv("2021-07-10")时数组下标越界。

在获取Rows[]的时候,lc应该取指定表头行中的最大列,我提交了PR,你看下有没有可以优化的地方。

HeaderRow增加了getNames()方法,可以遍历表头获取数据。

Row header = sheet.header(1, 2).getHeader();
System.out.println(header);
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 26 out of bounds for length 26
	at org.ttzero.excel.reader.HeaderRow.mergeCellsIfNull(HeaderRow.java:751)
	at org.ttzero.excel.reader.HeaderRow.with(HeaderRow.java:118)
	at org.ttzero.excel.reader.XMLSheet.getHeader(XMLSheet.java:309)
	at org.ttzero.excel.reader.XMLSheet.getHeader(XMLSheet.java:272)

@wangguanquan
Copy link
Owner

感谢你的PR,稍后会review

你可以尝试拉取最新的fix#306分支代码,看看是否满足你的场景。

@linmii
Copy link
Contributor Author

linmii commented Nov 29, 2022

刚刚试了下,fix306最新的代码确实已经处理了异常,可以正常使用,应该是我之前拉的不是最新的代码,看了下提交日志,你是在HeaderRow#mergeCellsIfNull方法进行的处理,我把这部分逻辑提前到XMLSheet#getHeader中了,你看看哪种方式更好。
另外我PR的代码中,HeaderRow#getNames方法我觉得还是有必要的,动态获取的表头,方便根据name获取cell的内容

@linmii linmii closed this as completed Feb 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants