最近在用QT开发一个需要读取Excel表格的项目。使用了QXlsx来读取最新的Excel表格格式文件也就是*.xlsx格式。但出现了一个问题,有的文件可以正确读取表单的列数,有的文件却不行。
读取列数的代码如下:
int columns = workSheet->dimension().columnCount();
这我就很头痛了,到网上查了一下,确实也有其他的开发者遇到同样的坑。可以参考:
QT xlsx 里遇到的一点坑_qxlsx中dimension().rowcount()取得值不对-优快云博客
他的做法就是不断获取第一行的单元格,直到取到空为止。这样就数出了列数。
我比较较真,要彻底搞清楚是什么问题。这个QXlsx的库,当初是下载了源码自己编出来的。既然有源码是可以彻底搞清楚的。我跟踪了一下源码,读取出现错误的xlsx文件,看到了获取行列数的地方:

我们看到,在文件xlsxworksheet.cpp里,有一个读取xml文件的函数。这个dimension第一次赋值就在这里。我们可以看到,它读取了一个节点名称为<dimension>的节点,它有一个叫ref的属性值为"A1",然后呢CellRange的构造函数接受了"A1",对其进行了解析,解析结果就是(1,1)-(1,1)这样一个单元范围。等到整个文件内容读取完,经过各种处理后这个dimension变成了(1,1)-(1,13),根据这个单元的范围,得到的行数是13,列数是1。然而我这个文件有多达28列。
我们再打开一个可以正确读取列数的文件看看这里的值:

这次读取出来的是"A1:V24",其实A1和V24就是Excel表格里的两个格子的标号,一个代表所有单元格范围的表左上角单元,另一个是左下角的。
现在我们清楚了,应该是xlsx格式里面存储的数据本身有问题。这里我们要知道一些xlsx格式方面的知识。在2003年以后微软升级了Office系列的文档格式,使其成为了一个国际标准。2003以前的格式是微软自己研发的一种格式(其实是一种本地文件系统),而新标准的格式是一堆目录和xml文件的压缩包。你可以尝试把xlsx文件解压。

如果你安装了某种zip压缩解压的软件,就可以轻松地解压它。我们解压出来看看。

我们进入xl目录,看到有一个worksheets目录,记得上面那个代码的文件好像就叫xlsxworksheets。

在worksheets里面我们看到了一个xml文件sheet1.xml。这也就理解了为什么代码中是加载xml文件。
我们打开它,好家伙dimension和ref属性赫然在列:

于是乎用同样方法,我打开了读取列错误的文件:

同样打开读取列正确的文件:

可见,这里面记录的ref属性与QXlsx库读出来的值是一致的。
也就是说不知道什么原因,sheet1.xml文件中dimension节点ref的属性在某种情况下并不能反映内容单元格的最大范围。而QXlsx获取列数的算法又依赖于这一属性。但奇怪的是,虽然只读取到A1但是行数是正确的。也就是说QXlsx在加载文件的过程中会刷新最大行数,但不知道什么原因没有刷新最大列数。所以QXlsx的算法存在bug,一般我看到bug是必须灭掉的。
接下来就要考虑怎么修正代码了。其实仔细观察也不难发现,sheet1.xml中有一个sheetData的节点,这个节点里按行存放了各单元的内容:
<sheetData>
<row r="1">
<c r="A1" s="1" t="inlineStr">
<is><t>序号</t></is>
</c>
<c r="B1" s="1" t="inlineStr">
<is><t>数据id</t></is>
</c>
<c r="C1" s="1" t="inlineStr">
<is><t>部门</t></is>
</c>
...
</sheetData>
一个row就是一行的数据,row节点中的c节点就是一个单元(cell),而c节点的r属性就是Excel表的列号。说明QXlsx代码中读取或者处理这个属性没有刷新dimension。
找到处理sheetData的代码:

处理row的分支

处理c和r的代码

可以看到这里的写法,r属性被读出以后构造了pos。当r是空的时候,就用当前的row_num作为cell的行,并将col_num加一作为它的列,这是在没有r属性时给的默认值。也就是r不为空的话,col_num是不会被刷新的。这就是bug!这个函数的底部有一个用row_num和col_num刷新dimension的动作,我这里就不贴出来了。也就是说这里在处理r属性的时候没有正确刷新col_num是导致这个bug的根源。
我们在判断r为空的条件下加一个非空情况的分支,在这个分支中刷新col_num:

重新编译整个QXlsx以后进行测试,完全正确。通过 workSheet->dimension().columnCount() 就取到了正确的列数。
至此整个找问题和修bug的过程就结束了。其实,很多bug深入再看一步也许并不难解决。
463






