用python解析word文件(二):table

太长了,我决定仍是拆开三篇写。
 

(二)表格篇(table)(本篇)html

(三)样式篇(style)python

选你所需便可。下面开始正文。算法


 

上一篇咱们讲了用python-docx解析docx文件中的段落,也就是paragraph,不过细心的同窗可能发现了,只有天然段是能够用paragraph处理的,若是word中有表格,根本读都读不到。这是正常的,由于表格在docx中是另外一个类。
 
一个word文档中大概有这么几种类型的内容:paragraph(段落),table(表格),character(我也不知道该怎么叫,字符?)。我如今要解析的word文档中,基本都是段落和表格,因此character的具体内容我也没有特别关注。本文主要来说一下如何从word中解析出表格,并在html中展现出来。
 
首先,很简单,使用
docx.tables
 
能够得到文档中的所有表格。跟excel中相似,word文档的表格也是分行(row)和列(column)的,读的方法是,对每个table,先读出所有的rows,再对每个row读出所有的column,这里的每行中的一列叫作一个单元格(cell),cell能作到的就跟一个paragraph相似了。若是用不着那么麻烦地得到表格的样式,就直接用
cell.text
 
获取单元格内容就行了。那么,一个二重循环,就获取到了table中的所有文字内容。
可是这是不够的。个人目的是要在html上展现出来。因此,须要在这一堆内容上添加html标签。具体的作法,咱们来举个栗子吧。
 
不对,拿错了。应该是这样:
 
这是一个word中的table。按照上面的方法,咱们能够写代码以下:
for t in docx.tables:
    # todo

 

但其实对于word中的table,并无这么简单。有的时候,明明这一行只有一列,可是却读出多个值。那么,对于相邻的相同内容,就须要作去重处理。固然,这里的“去重”也不是list(set())这么简单的,由于一行中的全部列应当有顺序。因此,咱们采用添加元素的方式:
_table_list = []
for i, row in enumerate(table.rows):   # 读每行
    row_content = []
    for cell in row.cells:  # 读一行中的全部单元格
        c = cell.text
        if c not in row_content:
            row_content.append(c)
    # print(row_content)
    _table_list.append(row_content)

 

当要添加的元素跟行尾相同时不添加。结果是,上面的tables处理完后,是这样的一堆列表(上面代码中print的位置打印出的结果):
['咱们来插入一个表格']
['这是一级标题1', '这是二级标题1.1', '这是三级标题1.1.1', '总结']
['这是一级标题1', '这是二级标题1.1', '这是三级标题1.1.2', '总结']
['这是一级标题1', '这是二级标题1.1', '这是三级标题1.1.3', '总结']
['这是一级标题1', '这是二级标题1.2', '这是三级标题1.2.1', '总结']
['这是一级标题1', '这是二级标题1.3', '这是三级标题1.3.1', '总结']
['这是一级标题1', '这是二级标题1.3', '这是三级标题1.3.2', '总结']
['别忙,还有内容']
['内容', '另外一段内容']

 

不过在去重以后,是无法直接用的……表格是个方格,从总体上来讲就是一个矩阵,只是有些位置合并了单元格而已,咱们如今的二维数组各列可不是对齐的。因此接下来,须要进行填充处理。填充的方式并无 一腚之龟必定之规,这是由于咱们并不知道表格的具体规则如何。好在我要填的表有几本规则:最多4列,若是某一行只有一个元素,就扩充为4个,若是有两个元素,就扩充为前两个元素一致,后两个一致,即[0,1]的列表变成[0,0,1,1]这种形式。没有一行三个元素的时候。我用了一个简单的函数对每行进行了处理,这样每一行都变成了4列,整个二维数组也变成了一个矩阵形式。
 
个人手动填充代码是这样的:
def _fill_blank(table):
    cols = max([len(i) for i in table])
 
    new_table = []
    for i, row in enumerate(table):
        new_row = []
        [new_row.extend([i] * int(cols / len(row))) for i in row]
        print(new_row)
        new_table.append(new_row)
 
    return new_table

 

生成的结果是:
['咱们来插入一个表格', '咱们来插入一个表格', '咱们来插入一个表格', '咱们来插入一个表格']
['这是一级标题1', '这是二级标题1.1', '这是三级标题1.1.1', '总结']
['这是一级标题1', '这是二级标题1.1', '这是三级标题1.1.2', '总结']
['这是一级标题1', '这是二级标题1.1', '这是三级标题1.1.3', '总结']
['这是一级标题1', '这是二级标题1.2', '这是三级标题1.2.1', '总结']
['这是一级标题1', '这是二级标题1.3', '这是三级标题1.3.1', '总结']
['这是一级标题1', '这是二级标题1.3', '这是三级标题1.3.2', '总结']
['别忙,还有内容', '别忙,还有内容', '别忙,还有内容', '别忙,还有内容']
['内容', '内容', '另外一段内容', '另外一段内容']

 

像我这样有规律的表格能够这样作,若是表格毫无规律,就只能听天由命了。
 
那么,为何要先去重,再扩充?真的不是吃饱了撑的吗?
 
缘由是,我在我项目中要处理的表,一行里面最多有4列,第一行原本只有1列,结果我读出来了6列。至于为何会这样,我也不清楚,只能说,用户对于word的用法是五花八门的,只要能作出来想要的样子,就彻底没有规矩可言。鬼知道他们是否是把一个4个单元格拆成了6个,又合并成了1个。若是我直接使用6列的结果,是没办法作成html样式的table的。
 
作扩展的目的,主要是为了合并单元格。在html标签中,用rowspan和colspan来表示跨行和跨列。举个列子,若是一行是跨4列的,这一行应该是
<table border="1" align="center">
    <tr align="center"><td colspan="4">Row One</td></tr>
    <tr align="center"><td>Row Two</td><td>Row Two</td><td>Row Two</td><td>Row Two</td></tr>
</table>

 

造成这样一个表格:
 
若是是跨行的,这一列应该是
<table border="1" align="center">
    <tr><td rowspan="3">Left</td><td>Right</td></tr>
    <tr><td>Right</td></tr>
    <tr><td>Right</td></tr>
</table>

 

造成这样一个表格:
 
因此,咱们的下一步工做就是数数。数清楚在一行中有多少个相同的列,合并到一块儿,整个矩阵中有多少个相同的行,合并到一块儿。所谓的合并,就是数量加到上一行/列上去,而本行为空。下图是算法的示意:
 
合并行:
 
 
合并列:
 
 
 
在这样一个二维数组中,每个元素是一个三元组,第一个元素是表格中的文本内容,第二个元素是rowspan的数量,第三个元素是colspan的数量。若是本行/列跟上一个有内容的行/列内容相同,就把内容加到那一行/列中,本行/列为["", 0, 0];若是不一样,则保留。
 
代码是酱婶的:
def _table_matrix():
    if not table:
        return ""
 
    # 处理同一行的各列
    temp_matrix = []
    for row in table:
        if not row:
            continue
 
        col_last = [row[0], 1, 1]
        line = [col_last]
        for i, j in enumerate(row):
            if i == 0:
                continue
 
            if j == col_last[0]:
                col_last[2] += 1
                line.append(["", 0, 0])
            else:
                col_last = [j, 1, 1]
                line.append(col_last)
 
        temp_matrix.append(line)
 
    # 处理不一样行
    matrix = [temp_matrix[0]]
    last_row = []
    for i, row in enumerate(temp_matrix):
        if i == 0:
            last_row.extend(row)
            continue
 
        new_row = []
        for p, r in enumerate(row):
            if p >= len(last_row):
                break
 
            last_pos = last_row[p]
 
            if r[0] == last_pos[0] and last_pos[0] != "":
                last_row[p][1] += 1
                new_row.append(["", 0, 0])
            else:
                last_row[p] = row[p]
                new_row.append(r)
 
        matrix.append(new_row)
 
    return matrix

 

逻辑上会有一点点难读,在什么状况下数量加1,在哪里加1,须要比较细致地算,不然必定会乱。
 
最后这个数组出来以后,就能够转化html了。这个很简单,套上tr和td标签便可。代码以下:
def table2html(t):
    table = _fill_blank(t)
    matrix = _table_matrix(table)
 
    html = ""
    for row in matrix:
        tr = "<tr>"
        for col in row:
            if col[1] == 0 and col[2] == 0:
                continue
 
            td = ["<td"]
            if col[1] > 1:
                td.append(" rowspan=\"%s\"" % col[1])
            if col[2] > 1:
                td.append(" colspan=\"%s\"" % col[2])
            td.append(">%s</td>" % col[0])
 
            tr += "".join(td)
        tr += "</tr>"
        html += tr
 
    return html

 

我没有套table标签,由于这个能够在页面上调一调样式。没必要彻底拘泥于word中的样子,也没办法彻底按word来——若是一板一眼地按照word的方式设置,出来的html上的table确定是错乱的,缘由嘛,仍是在于用户的使用习惯。
 
最后,要是在jinja模板中使用的话,记得把字符串传到页面上的时候加上safe过滤器。
{{ table|safe }}

 

出来的表格大概是这样的:
 
最后咱们来对比一下word和table:
 
若是手工调整一下html table的样式,两者应该能够长得很像的,对吧?
 
好了,关于table,就介绍这么多。
相关文章
相关标签/搜索