Python读取docx表格中的合并单元格信息⽬录
⼀、问题背景
⽤pywin32运⾏速度太慢,我⼀般⽤docx库对Word表格进⾏处理。
注意docx库的安装库名是python-docx⽽不是docx,pip安装⽅法是:
pip install python-docx
1.1 常规写法
读取表格⼀般⽤这样的写法:
def ReadDocx(file):
doc = docx.Document(file)
data =[]
for table in doc.tables:
data.append([])
for row ws:
data[-1].append([])
for cell lls:
data[-1][-1].)
return data
1.2 奇怪问题
有的时候,读取的表格有⼀些离谱,⽐如,当表格是这样⼦的时候:
读取出来的结果却是:
>>> data
[[['苹果','苹果','⾹蕉'],
['阿猫','阿狗','阿狗']]]
问题出现在了哪⾥呢?docx库读取的表格,认为⾥⾯存在合并单元格,把它看成了2×3的表格。
如果是Excel,在读取表格的同时也可以获取合并单元格的信息,但是在docx库中不⽀持这样做的⽅法。
在docx库中,读取合并单元格内的⽂本,获得的全部都是同⼀内容。
但是通过单元格的⽂本来判断是否是合并单元格,显然是不合适的。因为在不同单元格⾥,⽂本也可以是“相同”的,所以这样的判断是不够“严谨”的。
⼆、发现线索
2.1 前途光明
但是在绝境之中我发现了⼀个线索。在合并单元格中,获取第⼀⾏的内容:
row = ws[0].cells
判断在表格中被合并的两个单元格,是否指向同⼀地址,返回结果是True:
>>> row[0]is row[1]
True
这样可以解决同⼀⾏中合并单元格的判断,同理⽤lumns[c].cells,也可以判断纵向单元格的合并情况。
2.2 道路曲折
但是,如果存在单元格,既有横向合并,也有纵向合并,⽐如row1是第⼀⾏,row2是第⼆⾏:
计算row1[0] is row2[1],却不能得到True的结果。
同样如果⽤ll(r, c)的⽅式进⾏访问:
>>> ll(0,0)ll(1,1)
False
也不能得到预期的结果。
三、顺藤摸⽠
所以是为什么呢?
查看row的信息,输出结果为:
>>> row
<docx.table._Row object at 0x00000000039FDF98>
3.1 源代码
到docx.table库的_Row类,到对应的源代码:
class _Row(Parented):
...
@property
def cells(self):
return tuple(w_cells(self._index))
这⾥调⽤了⼀个row_cells⽅法,继续追踪,该⽅法出现在了docx.table的Table类中:
class Table(Parented):
...
def row_cells(self, row_idx):
column_count = self._column_count
start = row_idx * column_count
end = start + column_count
return self._cells[start:end]
返回了⼀个self._cells值,并对数组进⾏切⽚,也就是这样的做法导致了第⼀⾏的cells和第⼆⾏的cells地址值⽆法互相确认。
继续查_cells值定义的位置,依然是在Table类中的⼀个受@property保护的⽅法返回值:
class Table(Parented):
...
@property
def_cells(self):
col_count = self._column_count
cells =[]
for tc in self._tbl.iter_tcs():
for grid_span_idx in id_span):
if tc.vMerge == ST_Merge.CONTINUE:
cells.append(cells[-col_count])
elif grid_span_idx >0:
cells.append(cells[-1])
else:
cells.append(_Cell(tc, self))
return cells
3.1 分析原因
图穷⼔见,看到这⾥就很容易理解为什么合并单元格⾥的单元会被认为是同⼀地址值的原因了。
当满⾜条件tc.vMerge == ST_Merge.CONTINUE和grid_span_idx > 0时,最终结果数组的cells都是直接添加了⼀个原本数组中已经含有的元素。⽽这导致了判断c1 is c2的时候,得到了True的返回值。
阅读源代码,也⽐较好理解,cells中的各个单元格在表中按照从上⾄下、从左⾄右的顺序排列。当tc.vMerge == ST_Merge.CONTINUE的时候,单元格纵向重复,grid_span_idx > 0时,单元格横向重复。
在合并单元格中,所有的引⽤的都是来⾃于合并区域的第⼀个“格”,那么通过c1 is c2就可以判断两个单元格是否是同属于⼀个区域的合并单元格。
3.3 取得所需
访问@property保护的变量实际上是运⾏了⼀次函数,所以将返回值赋值给临时变量,然后进⾏后续运算会更具有效率,并且代码具有可读性。
获取所有单元格列表、表宽度、和单元格数⽬:
doc = docx.Document(file)
for table in doc.tables:
cells = table._cells
cols = table._column_count
length =len(cells)
...
四、破解办法
4.1 到"合并"单元格
在返回的数组cells中,第⼀个出现重复的单元格,就是合并单元格区域的左上⾓的单元格;最后⼀次出现重复的单元格,就是右下⾓的单元格。
参考代码:
for i, cell in enumerate(cells):
if cell in cells[:i]:# 如果该单元格不是在表中第⼀次出现则跳过
continue
for j in range(length -1,0,-1):# 倒序查
if cell is cells[j]:# 到"相同"的单元格,如果没有"合并"单元格,则会倒序到"⾃⼰"
break
if i != j:# 如果正序查和倒序查的索引值不同,则说明是"合并"单元格
...
这样可以判断出“合并”单元格的第⼀个“格⼦”,和最后⼀个“格⼦”。
4.2 转换⾏列信息
⾏数也是已知的,那么获取到合并单元格的“合并”区域就很容易知道了:
if i != j:
r1, c1 =divmod(i, cols)# 合并单元格区域的"起始"位置,同时也是左上⾓单元格的⾏列坐标
r2, c2 =divmod(j, cols)# 合并单元格区域的"结束"位置,同时也是右下⾓单元格的⾏列信息
merge = r1, r2 +1, c1, c2 +1# 转为xlrd风格的单元格合并⾏列信息
最后,我按照xlrd库的风格,将合并单元格的信息整理为xlrd库中的顺序。
4.3 ⾃定义规则合并
有了合并单元格的信息,再对单元格合并,就很简单了。
根据需要,可以实现纵向重复、横向重复、横向重复区域缩紧,或组合的⽅式进⾏“合并”:
def MergeCell(data, merge, merge_x=True, merge_y=True, strip_x=False):
data2 =[]
for sheet_data, sheet_merge in zip(data, merge):
# merge cell
for r1, r2, c1, c2 in sheet_merge:
for r in range(r1, r2):
for c in range(c1, c2):
if(not merge_x and c > c1)or(not merge_y and r > r1):
sheet_data[r][c]=None if strip_x else''
python怎么读取xls文件else:
sheet_data[r][c]= sheet_data[r1][c1]
# strip x
if strip_x:
sheet_data =[[cell for cell in row if cell is not None]for row in sheet_data]
data2.append(sheet_data)
return data2
附、读取xls⽂件中的合并单元格
另外我前⾯提到多次的读取Excel的xlrd库,这个库的运⾏速度⽐pywin32要快很多,我常⽤的读取和清洗⽅法也分享⼀下:
def ReadExcel(file):
# only ".xls" type contain merge_info
xls = xlrd.open_workbook(file, formatting_info=True)
data =[]
for sheet in xls.sheets():
sheet_name = sheet.name
sheet_data =[]
for row in ws):
rows = w_values(row)
for c, cell in enumerate(rows):
if isinstance(cell,float):
if cell.is_integer():
rows[c]=str(int(cell))
else:
rows[c]=str(cell)
sheet_data.append(rows)
data.append(sheet_data)
merge =[d_cells for sheet in xls.sheets()]
return data, merge
但是需要注意,xlrd库只有读取xls格式的Excel⽂件才有合并单元格的信息。若xlsx格式的表格也要读取合并单元格的信息,可以先将⽂件转为xls的格式后再做读取。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论