太長了,我決定還是拆開三篇寫。
(二)表格篇(table)(本篇)
選你所需即可。下面開始正文。
上一篇我們講了用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,就介紹這么多。