用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,就介紹這麼多。
相關文章
相關標籤/搜索