作者:小小明
Pandas數(shù)據(jù)處理專家,幫助一萬(wàn)用戶解決數(shù)據(jù)處理難題。
最近碰到一個(gè)需求:
雖然我沒(méi)完全看懂啥意思,但大意就是:
1.讀取word文檔,將其中所有的表格都寫(xiě)入到一個(gè)excel文件中
2.對(duì)寫(xiě)好的excel做出一些修改(包括改某幾個(gè)單元格的值和刪除行),然后將修改后的excel數(shù)據(jù)回填到word表對(duì)應(yīng)的位置
對(duì)于第一個(gè)需求,直接用pandas寫(xiě)出即可
對(duì)于第二個(gè)需求,先生成模板,再用docxtpl模板渲染
關(guān)于docxtpl,我已經(jīng)根據(jù)官方文檔,制作了一份操作手冊(cè):https://blog.csdn.net/as604049322/article/details/112008531
好了,就按照這個(gè)大致理解的需求開(kāi)始干:
讀取word文檔表格寫(xiě)入到excel
python代碼:
from docx import Document
import pandas as pd
doc = Document(r"test.docx")
writer = pd.ExcelWriter("test.xlsx")
for i, table in enumerate(doc.tables):
header = [cell.text for cell in table.rows[0].cells]
result = []
for row in table.rows[1:]:
tmp = []
for cell in row.cells:
tmp.append(cell.text)
result.append(tmp)
df = pd.DataFrame(result, columns=header)
df.to_excel(writer, sheet_name=f"{i}", index=False)
writer.save()
經(jīng)過(guò)上面代碼處理,就將這樣一個(gè)word文檔:
提取出來(lái)了這樣的一個(gè)excel文件:
整體效果已經(jīng)達(dá)到,但是我覺(jué)得如果能順便設(shè)置好列寬就好看點(diǎn),要設(shè)置好列寬,我的思路是計(jì)算出每列的字符串的最大長(zhǎng)度,但不能直接用字符長(zhǎng)度,每個(gè)中文字符會(huì)占用兩個(gè)長(zhǎng)度,所以我直接取gbk編碼后的字節(jié)長(zhǎng)度:
from docx import Document
import pandas as pd
import numpy as np
doc = Document(r"test.docx")
writer = pd.ExcelWriter("test.xlsx")
workbook = writer.book
for i, table in enumerate(doc.tables):
header = [cell.text for cell in table.rows[0].cells]
result = []
for row in table.rows[1:]:
tmp = []
for cell in row.cells:
tmp.append(cell.text)
result.append(tmp)
df = pd.DataFrame(result, columns=header)
df.to_excel(writer, sheet_name=f"{i}", index=False)
worksheet = writer.sheets[f"{i}"]
# 計(jì)算表頭的字符寬度
column_widths = (
df.columns.to_series()
.apply(lambda x: len(x.encode('gbk'))).values
)
# 計(jì)算每列的最大字符寬度
max_widths = (
df.astype(str)
.applymap(lambda x: len(x.encode('gbk')))
.agg(max).values
)
# 計(jì)算整體最大寬度
widths = np.max([column_widths, max_widths], axis=0)
for i, width in enumerate(widths):
worksheet.set_column(i, i, width)
writer.save()
結(jié)果:
有了一個(gè)合適的列寬,我看的舒服多了,至少我自己是滿意了,要用代碼加什么好看的樣式也簡(jiǎn)單,。
好了,現(xiàn)在開(kāi)始處理需求2:
讀取修改過(guò)的excel回填到word文檔中
讀取word并生成word模板
要回填到word文檔中,我們應(yīng)該事先生成能夠被doctpl解析的模板,我的思路是每個(gè)表格除了表頭以外全部刪除,然后動(dòng)態(tài)生成以下格式的模板:
xxx | xxx | xxx |
---|
{%tr for cells in rows0 %} | | |
{{ cells[0] }} | {{ cells[1] }} | {{ cells[2] }} |
{%tr endfor %} | | |
{{ footers0[0] }} | {{ footers0[1] }} | {{ footers0[2] }} |
到時(shí)候再直接根據(jù)excel的數(shù)據(jù)渲染就行,那么如何生成word模板呢?
直接看看我的代碼吧:
from docx import Document
def set_font_style(after_font_style, before_font_style):
after_font_style.bold = before_font_style.bold
after_font_style.italic = before_font_style.italic
after_font_style.underline = before_font_style.underline
after_font_style.strike = before_font_style.strike
after_font_style.shadow = before_font_style.shadow
after_font_style.size = before_font_style.size
after_font_style.color.rgb = before_font_style.color.rgb
after_font_style.name = before_font_style.name
doc = Document("test.docx")
for i, table in enumerate(doc.tables):
# 緩存最后一行的行對(duì)象
last_row = table.rows[-1]._tr
# 刪除除表頭外的所有行
for row in table.rows[1:]:
table._tbl.remove(row._tr)
# 表格添加一行,第一個(gè)單元格文本指定為指定的內(nèi)容
table.add_row().cells[0].text = '{%tr for cells in rows'+str(i)+' %}'
# 再添加一行用于保存中間的模板
row = table.add_row()
for j, cell in enumerate(row.cells):
cell.text = '{{ cells[%d] }}' % j
# 再添加一行用于保存endfor模板
table.add_row().cells[0].text = '{%tr endfor %}'
# 將直接緩存的最后一行添加到行尾
table._tbl.append(last_row)
# 表格的行尾修改完樣式后,還原回以前的樣式
for j, cell in enumerate(table.rows[-1].cells):
before_font_style = cell.paragraphs[0].runs[0].font
cell.text = '{{ footers%d[%d] }}' % (i, j)
after_font_style = cell.paragraphs[0].runs[0].font
set_font_style(after_font_style, before_font_style)
doc.save("test_template.docx")
模板生成的效果:
根據(jù)word模板回填word
有了模板就可以開(kāi)始根據(jù)模板回填word了,首先我把excel修改成這樣:
就是一個(gè)表改了兩個(gè)值,另一個(gè)表刪了兩行,保存后,執(zhí)行以下代碼:
from docxtpl import DocxTemplate
import pandas as pd
tpl = DocxTemplate('test_template.docx')
excel = pd.ExcelFile("test.xlsx")
context = {}
for sheet_name in excel.sheet_names:
data = pd.read_excel(excel, sheet_name).values
context[f'rows{sheet_name}'] = data[:-1].tolist()
context[f'footers{sheet_name}'] = data[-1].tolist()
tpl.render(context)
tpl.save('result.docx')
回填結(jié)果:
好了,到現(xiàn)在為止,我個(gè)人覺(jué)得已經(jīng)大體上完成效果了,。當(dāng)然還不夠完美,很多樣式適配都還沒(méi)有去做,首先是行高丟失,然后是修改數(shù)值后的千分符丟失,這都要等正式開(kāi)發(fā)后去適配,我這里不公布正式開(kāi)發(fā)的代碼,。
總結(jié)
你對(duì)doctpl怎么看呢?歡迎你在下方評(píng)論或留言表達(dá)你的看法,。