文章目录
- 使用python-docx创建文档非常简单: from docx import Document doc = Document() doc.add_paragraph(“Python-docx是一个用于创建”) doc.save(“文件1.docx”)
- 默认字体对中文支持不佳,需要单独设置中文字体: from docx.oxml.ns import qn def set_chinese_font(run, zh_font_name=”宋体”, en_font_name=”Times New Roman”): run.font.name = en_font_name run._element.rPr.rFonts.set(qn(“w:eastAsia”), zh_font_name) doc = Document() paragraph = doc.add_paragraph() run = paragraph.add_run(‘这是一段设置了中文字体的文本。’) set_chinese_font(run) doc.save(“文件1.docx”) 注意:保存文件时,文件不能被打开,否则会报PermissionError错误。
- doc = Document(‘example.docx’) 注意事项: 必须是标准docx文件,不能是doc文件 不能是strict open XML格式
- # 遍历段落 for para in doc.paragraphs[:3]: print(para) print(para.text) print() # 遍历表格 for table in doc.tables: for row in table.rows: for cell in row.cells: print(cell.text)
- doc.add_heading(“1.1 Transformer整体工作流程”, 2) doc.add_heading(“Transformer整体架构”, 3) 注意:需要文档里有对应的标题样式,否则会报错。
- 添加段落 text = “””Transformer 模型由编码器(Encoder)和解码器(Decoder)组成。…””” paragraph1 = doc.add_paragraph(text) 首行缩进 首行缩进2字符: paragraph_format = paragraph1.paragraph_format paragraph_format.first_line_indent = 0 paragraph_format.element.pPr.ind.set(qn(“w:firstLineChars”), ‘200’) 首行缩进固定距离: para_format.first_line_indent = Pt(10) 段落对齐 from docx.enum.text import WD_PARAGRAPH_ALIGNMENT paragraph1.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT 删除段落 p = paragraph1._element p.getparent().remove(p) 换行处理 # 将文本按换行符分割成多个段落 for one_paragraph_text in text.split(“n”): temp_paragraph = doc.add_paragraph(one_paragraph_text) paragraph_format = temp_paragraph.paragraph_format paragraph_format.first_line_indent = 0 paragraph_format.element.pPr.ind.set(qn(“w:firstLineChars”), “200”) 常用段落格式 from docx.shared import Pt para_format = temp_paragraph.paragraph_format para_format.line_spacing = Pt(18) # 行间距(固定值) para_format.space_before = Pt(3) # 段前距离 para_format.space_after = Pt(0) # 段后距离 para_format.right_indent = Pt(20) # 右侧缩进 para_format.left_indent = Pt(0) # 左侧缩进
- from docx.shared import RGBColor, Pt # 加粗文本 temp_paragraph.add_run(‘加粗文本’).bold = True # 红色斜体文本 run = temp_paragraph.add_run(‘红色斜体文本’) run.font.color.rgb = RGBColor(255,0,0) # 设置红色 run.font.size = Pt(14) # 字号14磅 run.bold = True # 加粗 run.italic = True # 斜体 run.underline = True # 下划线 # 下标和上标 run2 = temp_paragraph.add_run(“1”) run2.font.subscript = True # 下标 run3 = temp_paragraph.add_run(“2”) run3.font.superscript = True # 上标
- 创建表格 table = doc.add_table(rows=4, cols=5) table.style = “Grid Table 1 Light” # 应用预定义样式 填充单元格 # 方式1:直接指定单元格 cell = table.cell(0, 1) cell.text = “parrot, possibly dead” # 方式2:通过行获取单元格 row = table.rows[1] cells = row.cells cells[0].text = “Foo bar to you.” cells[1].text = “And a hearty foo bar to you too sir!” 获取可用表格样式 from docx.enum.style import WD_STYLE_TYPE styles = doc.styles for s in styles: if s.type == WD_STYLE_TYPE.TABLE: print(s.name) 增加和删除行 # 增加一行 row = table.add_row() # 删除一行 def remove_row(table, row): tbl = table._tbl tr = row._tr tbl.remove(tr) row = table.rows[len(table.rows) – 1] remove_row(table, row) 批量填充数据 # 方式1:一行一行添加 items = ( (7, “1024”, “Plush kittens”), (3, “2042”, “Furbees”), (1, “1288”, “French Poodle Collars, Deluxe”), ) for item in items: cells = table.add_row().cells cells[0].text = str(item[0]) cells[1].text = item[1] cells[2].text = item[2] # 方式2:批量填充 for row in table.rows: for cell in row.cells: cell.text = “数据单元” 合并单元格 table.cell(0, 0).merge(table.cell(1, 1)) # 跨行列合并 表格格式设置 # 表格宽度自适应 table.autofit = True # 指定行高 from docx.shared import Cm table.rows[0].height = Cm(0.93) # 修改表格字体大小 table.style.font.size = Pt(15) # 设置单元格对齐 from docx.enum.table import WD_ALIGN_VERTICAL cell = table.cell(0, 0) cell.paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER # 复制表格 from copy import deepcopy table_copy = deepcopy(doc.tables[0]) para1 = doc.add_paragraph() para1._p.addnext(table_copy._element)
- 插入图片 from io import BytesIO import base64 # 普通插入 doc.add_picture(‘图片1.png’) doc.add_picture(‘图片2.png’, width=Inches(2.5), height=Inches(2)) # 使用base64插入 picture2_base64 = open(“图片2base64.txt”).read() img2_buf = base64.b64decode(picture2_base64) doc.add_picture(BytesIO(img2_buf)) # 并排放图 run = doc.add_paragraph().add_run() run.add_picture(“图片1.png”, width=Inches(2.5), height=Inches(2)) run.add_picture(“图片1.png”, width=Inches(2.5), height=Inches(2))
- doc.add_page_break()
- # 修改已有样式 doc.styles[“Normal”].font.size = Pt(14) doc.styles[‘Normal’].font.name = ‘Arial’ doc.styles[‘Normal’]._element.rPr.rFonts.set(qn(‘w:eastAsia’), ‘楷体’) # 创建自定义段落样式 from docx.enum.style import WD_STYLE_TYPE UserStyle1 = doc.styles.add_style(‘UserStyle1’, WD_STYLE_TYPE.PARAGRAPH) UserStyle1.font.size = Pt(40) UserStyle1.font.color.rgb = RGBColor(0xff, 0xde, 0x00) UserStyle1.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER UserStyle1.font.name = ‘微软雅黑’ UserStyle1._element.rPr.rFonts.set(qn(‘w:eastAsia’), ‘微软雅黑’) # 使用自定义样式 doc.add_paragraph(‘自定义段落样式’, style=UserStyle1)
- 首先创建一个包含占位符的Word模板,占位符使用双花括号{{}}包裹。
- from docxtpl import DocxTemplate, InlineImage, RichText tpl = DocxTemplate(“docxexample.docx”) text = “””Transformer 模型由编码器(Encoder)和解码器(Decoder)组成…””” picture1 = InlineImage(tpl, image_descriptor=”图片1.png”) # 准备数据 paragraphs1 = [ “步骤1:输入表示(Input Representation)”, “步骤2:编码器处理(Encoder Processing)”, “步骤3:解码器处理(Decoder Processing)”, ] paragraphs2 = [ {“step”: 1, “text”: “输入向量(词嵌入+位置编码)进入编码器层。”}, {“step”: 2, “text”: “自注意力子层。”}, {“step”: 3, “text”: “前馈网络子层。”}, ] table = [ {“character”: “并行计算”, “description”: “编码器可并行处理整个序列(与RNN不同)”}, {“character”: “自注意力”, “description”: “每个词直接关联所有词,捕获长距离依赖”}, {“character”: “位置编码”, “description”: “为无顺序的注意力机制注入位置信息”}, ] alerts = [ { “date”: “2015-03-10”, “desc”: RichText(“Very critical alert”, color=”FF0000″, bold=True), “type”: “CRITICAL”, “bg”: “FF0000”, }, # … 其他数据 ] # 渲染模板 context = { “title”: “Transformer”, “text_body”: text, “picture1”: picture1, “picture2”: picture2, “paragraphs1”: paragraphs1, “paragraphs2”: paragraphs2, “runs”: paragraphs1, “display_paragraph”: True, “table1”: table, “table2”: table, “alerts”: alerts, } tpl.render(context) tpl.save(“文件3.docx”)
- 设置单元格边框 from docx.oxml import OxmlElement from docx.oxml.ns import qn def set_cell_border(cell, **kwargs): tc = cell._tc tcPr = tc.get_or_add_tcPr() tcBorders = tcPr.first_child_found_in(“w:tcBorders”) if tcBorders is None: tcBorders = OxmlElement(“w:tcBorders”) tcPr.append(tcBorders) for edge in (“left”, “top”, “right”, “bottom”, “insideH”, “insideV”): edge_data = kwargs.get(edge) if edge_data: tag = “w:{}”.format(edge) element = tcBorders.find(qn(tag)) if element is None: element = OxmlElement(tag) tcBorders.append(element) for key in [“sz”, “val”, “color”, “space”, “shadow”]: if key in edge_data: element.set(qn(“w:{}”.format(key)), str(edge_data[key])) # 使用示例 set_cell_border( table.cell(0, 0), top={“sz”: 4, “val”: “single”, “color”: “#000000”, “space”: “0”}, bottom={“sz”: 4, “val”: “single”, “color”: “#000000”, “space”: “0”}, left={“sz”: 4, “val”: “single”, “color”: “#000000”, “space”: “0”}, right={“sz”: 4, “val”: “single”, “color”: “#000000”, “space”: “0”}, )
- def add_hyperlink(paragraph, url, text): part = paragraph.part r_id = part.relate_to( url, “http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink”, is_external=True, ) hyperlink = OxmlElement(“w:hyperlink”) hyperlink.set(qn(“r:id”), r_id) run = OxmlElement(“w:r”) run_text = OxmlElement(“w:t”) run_text.text = text run.append(run_text) hyperlink.append(run) paragraph._p.append(hyperlink) p = doc.add_paragraph(“点击访问: “) add_hyperlink(p, “https://www.baidu.com”, “示例链接”)
- 提取文档中的图片 import zipfile from xml.etree.ElementTree import fromstring def extract_images(docx_path, output_dir): with zipfile.ZipFile(docx_path) as z: try: doc_rels = z.read(‘word/_rels/document.xml.rels’).decode(‘utf-8’) except KeyError: return [] root = fromstring(doc_rels) rels = [] for child in root: if ‘Type’ in child.attrib and child.attrib[‘Type’] == RT.IMAGE: rels.append((child.attrib[‘Id’], child.attrib[‘Target’])) images = [] for rel_id, target in rels: try: image_data = z.read(‘word/’ + target) image_name = target.split(‘/’)[-1] with open(f”{output_dir}/{image_name}”, ‘wb’) as f: f.write(image_data) images.append(image_name) except KeyError: continue return images print(extract_images(“Transformer原理纯享版.docx”, “pictures”)) 插入浮动图片 # 插入“衬于文字下方”的浮动图片 # 如将 behindDoc=”1″ 改成0就是“浮于文字上方”了 # refer to docx.oxml.shape.CT_Inline class CT_Anchor(BaseOxmlElement): “”” “<w:anchor>“ element, container for a floating image. “”” extent = OneAndOnlyOne(‘wp:extent’) docPr = OneAndOnlyOne(‘wp:docPr’) graphic = OneAndOnlyOne(‘a:graphic’) @classmethod def new(cls, cx, cy, shape_id, pic, pos_x, pos_y): “”” Return a new “<wp:anchor>“ element populated with the values passed as parameters. “”” anchor = parse_xml(cls._anchor_xml(pos_x, pos_y)) anchor.extent.cx = cx anchor.extent.cy = cy anchor.docPr.id = shape_id anchor.docPr.name = ‘Picture %d’ % shape_id anchor.graphic.graphicData.uri = ( ‘http://schemas.openxmlformats.org/drawingml/2006/picture’ ) anchor.graphic.graphicData._insert_pic(pic) return anchor @classmethod def new_pic_anchor(cls, shape_id, rId, filename, cx, cy, pos_x, pos_y): “”” Return a new `wp:anchor` element containing the `pic:pic` element specified by the argument values. “”” pic_id = 0 # Word doesn’t seem to use this, but does not omit it pic = CT_Picture.new(pic_id, filename, rId, cx, cy) anchor = cls.new(cx, cy, shape_id, pic, pos_x, pos_y) anchor.graphic.graphicData._insert_pic(pic) return anchor @classmethod def _anchor_xml(cls, pos_x, pos_y): return ( ‘<wp:anchor distT=”0″ distB=”0″ distL=”0″ distR=”0″ simplePos=”0″ relativeHeight=”0″ n’ ‘ behindDoc=”1″ locked=”0″ layoutInCell=”1″ allowOverlap=”1″ n’ ‘ %s>n’ ‘ <wp:simplePos x=”0″ y=”0″/>n’ ‘ <wp:positionH relativeFrom=”page”>n’ ‘ <wp:posOffset>%d</wp:posOffset>n’ ‘ </wp:positionH>n’ ‘ <wp:positionV relativeFrom=”page”>n’ ‘ <wp:posOffset>%d</wp:posOffset>n’ ‘ </wp:positionV>n’ ‘ <wp:extent cx=”914400″ cy=”914400″/>n’ ‘ <wp:wrapNone/>n’ ‘ <wp:docPr id=”666″ name=”unnamed”/>n’ ‘ <wp:cNvGraphicFramePr>n’ ‘ <a:graphicFrameLocks noChangeAspect=”1″/>n’ ‘ </wp:cNvGraphicFramePr>n’ ‘ <a:graphic>n’ ‘ <a:graphicData uri=”URI not set”/>n’ ‘ </a:graphic>n’ ‘</wp:anchor>’ % ( nsdecls(‘wp’, ‘a’, ‘pic’, ‘r’), int(pos_x), int(pos_y) ) ) # refer to docx.parts.story.BaseStoryPart.new_pic_inline def new_pic_anchor(part, image_descriptor, width, height, pos_x, pos_y): “””Return a newly-created `w:anchor` element. The element contains the image specified by *image_descriptor* and is scaled based on the values of *width* and *height*. “”” rId, image = part.get_or_add_image(image_descriptor) cx, cy = image.scaled_dimensions(width, height) shape_id, filename = part.next_id, image.filename return CT_Anchor.new_pic_anchor(shape_id, rId, filename, cx, cy, pos_x, pos_y) # refer to docx.text.run.add_picture def add_float_picture(p, image_path_or_stream, width=None, height=None, pos_x=0, pos_y=0): “””Add float picture at fixed position `pos_x` and `pos_y` to the top-left point of page. “”” run = p.add_run() anchor = new_pic_anchor(run.part, image_path_or_stream, width, height, pos_x, pos_y) run._r.add_drawing(anchor) # refer to docx.oxml.__init__.py register_element_cls(‘wp:anchor’, CT_Anchor) document = Document() # add a floating picture p = document.add_paragraph() add_float_picture(p, ‘图片1.png’) # add text p.add_run(‘Hello World ‘*50) document.save(‘文件2.docx’) # https://www.cnblogs.com/dancesir/p/17788854.html
- # 分2栏 section = doc.sections[0] sectPr = section._sectPr cols = sectPr.xpath(‘./w:cols’)[0] cols.set(qn(‘w:num’),’2′)
- # 普通页眉 doc = Document(‘Transformer原理纯享版.docx’) doc.sections[0].header.paragraphs[0].text = “这是第1节页眉” # 分奇偶设置页眉 doc.settings.odd_and_even_pages_header_footer = True doc.sections[0].even_page_header.paragraphs[0].text = “这是偶数页页眉” doc.sections[0].header.paragraphs[0].text = “这是奇数页页眉” # 设置首页页眉 doc.sections[0].different_first_page_header_footer = True doc.sections[0].first_page_header.paragraphs[0].text = “这是首页页眉”
- # 插入目录(不会更新域) paragraph = doc.paragraphs[0].insert_paragraph_before() run = paragraph.add_run() fldChar = OxmlElement(‘w:fldChar’) fldChar.set(qn(‘w:fldCharType’), ‘begin’) instrText = OxmlElement(‘w:instrText’) instrText.set(qn(‘xml:space’), ‘preserve’) instrText.text = r’TOC o “1-3” h z u’ fldChar2 = OxmlElement(‘w:fldChar’) fldChar2.set(qn(‘w:fldCharType’), ‘separate’) fldChar3 = OxmlElement(‘w:t’) fldChar3.text = “Right-click to update field.” fldChar2.append(fldChar3) fldChar4 = OxmlElement(‘w:fldChar’) fldChar4.set(qn(‘w:fldCharType’), ‘end’) r_element = run._r r_element.append(fldChar) r_element.append(instrText) r_element.append(fldChar2) r_element.append(fldChar4) # 自动更新目录 import lxml name_space = “{http://schemas.openxmlformats.org/wordprocessingml/2006/main}” update_name_space = “%supdateFields” % name_space val_name_space = “%sval” % name_space try: element_update_field_obj = lxml.etree.SubElement(doc.settings.element, update_name_space) element_update_field_obj.set(val_name_space, “true”) except Exception as e: del e
- from docxcompose.composer import Composer master = Document(“文件1.docx”) composer = Composer(master) doc1 = Document(“文件2.docx”) composer.append(doc1) doc2 = Document(“文件3.docx”) composer.append(doc2) composer.save(“combined.docx”) 注意:合并文档时,后面的文档会跟随第一个文档的格式。
目录
- 所需库及安装
- 1. 基础操作
- 1.1 创建和保存文档
- 1.2 设置中文字体
- 1.3 导入现有文档
- 1.4 遍历文档内容
- 2. 文档格式设置
- 2.1 小标题
- 2.2 段落处理
- 2.3 字符格式设置
- 2.4 表格处理
- 2.5 图片处理
- 2.6 分页符
- 2.7 样式管理
- 3. 使用docxtpl进行模板填充
- 3.1 创建模板
- 3.2 填充模板
- 4. 进阶功能
- 4.1 表格高级操作
- 4.2 超链接
- 4.3 图片高级操作
- 4.4 分栏
- 4.5 页眉页脚
- 4.6 目录
- 4.7 文档合并
- 总结
在开始之前,需要安装以下Python库:
- python-docx:用于创建和修改Word文档
- docxtpl:用于基于模板填充Word文档
- docxcompose:用于合并多个Word文档
- lxml:XML处理库
可以通过pip安装:
pip install python-docx docxtpl docxcompose lxml
或者使用uv:
uv add python-docx docxtpl docxcompose lxml
使用python-docx创建文档非常简单:
from docx import Document
doc = Document()
doc.add_paragraph("Python-docx是一个用于创建")
doc.save("文件1.docx")
默认字体对中文支持不佳,需要单独设置中文字体:
from docx.oxml.ns import qn
def set_chinese_font(run, zh_font_name="宋体", en_font_name="Times New Roman"):
run.font.name = en_font_name
run._element.rPr.rFonts.set(qn("w:eastAsia"), zh_font_name)
doc = Document()
paragraph = doc.add_paragraph()
run = paragraph.add_run('这是一段设置了中文字体的文本。')
set_chinese_font(run)
doc.save("文件1.docx")
注意:保存文件时,文件不能被打开,否则会报PermissionError错误。
doc = Document('example.docx')
注意事项:
- 必须是标准docx文件,不能是doc文件
- 不能是strict open XML格式
# 遍历段落
for para in doc.paragraphs[:3]:
print(para)
print(para.text)
print()
# 遍历表格
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
print(cell.text)
doc.add_heading("1.1 Transformer整体工作流程", 2)
doc.add_heading("Transformer整体架构", 3)
注意:需要文档里有对应的标题样式,否则会报错。
添加段落
text = """Transformer 模型由编码器(Encoder)和解码器(Decoder)组成。...""" paragraph1 = doc.add_paragraph(text)
首行缩进
首行缩进2字符:
paragraph_format = paragraph1.paragraph_format
paragraph_format.first_line_indent = 0
paragraph_format.element.pPr.ind.set(qn("w:firstLineChars"), '200')
首行缩进固定距离:
para_format.first_line_indent = Pt(10)
段落对齐
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT paragraph1.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
删除段落
p = paragraph1._element p.getparent().remove(p)
换行处理
# 将文本按换行符分割成多个段落
for one_paragraph_text in text.split("n"):
temp_paragraph = doc.add_paragraph(one_paragraph_text)
paragraph_format = temp_paragraph.paragraph_format
paragraph_format.first_line_indent = 0
paragraph_format.element.pPr.ind.set(qn("w:firstLineChars"), "200")
常用段落格式
from docx.shared import Pt para_format = temp_paragraph.paragraph_format para_format.line_spacing = Pt(18) # 行间距(固定值) para_format.space_before = Pt(3) # 段前距离 para_format.space_after = Pt(0) # 段后距离 para_format.right_indent = Pt(20) # 右侧缩进 para_format.left_indent = Pt(0) # 左侧缩进
from docx.shared import RGBColor, Pt
# 加粗文本
temp_paragraph.add_run('加粗文本').bold = True
# 红色斜体文本
run = temp_paragraph.add_run('红色斜体文本')
run.font.color.rgb = RGBColor(255,0,0) # 设置红色
run.font.size = Pt(14) # 字号14磅
run.bold = True # 加粗
run.italic = True # 斜体
run.underline = True # 下划线
# 下标和上标
run2 = temp_paragraph.add_run("1")
run2.font.subscript = True # 下标
run3 = temp_paragraph.add_run("2")
run3.font.superscript = True # 上标
创建表格
table = doc.add_table(rows=4, cols=5) table.style = "Grid Table 1 Light" # 应用预定义样式
填充单元格
# 方式1:直接指定单元格 cell = table.cell(0, 1) cell.text = "parrot, possibly dead" # 方式2:通过行获取单元格 row = table.rows[1] cells = row.cells cells[0].text = "Foo bar to you." cells[1].text = "And a hearty foo bar to you too sir!"
获取可用表格样式
from docx.enum.style import WD_STYLE_TYPE
styles = doc.styles
for s in styles:
if s.type == WD_STYLE_TYPE.TABLE:
print(s.name)
增加和删除行
# 增加一行
row = table.add_row()
# 删除一行
def remove_row(table, row):
tbl = table._tbl
tr = row._tr
tbl.remove(tr)
row = table.rows[len(table.rows) - 1]
remove_row(table, row)
批量填充数据
# 方式1:一行一行添加
items = (
(7, "1024", "Plush kittens"),
(3, "2042", "Furbees"),
(1, "1288", "French Poodle Collars, Deluxe"),
)
for item in items:
cells = table.add_row().cells
cells[0].text = str(item[0])
cells[1].text = item[1]
cells[2].text = item[2]
# 方式2:批量填充
for row in table.rows:
for cell in row.cells:
cell.text = "数据单元"
合并单元格
table.cell(0, 0).merge(table.cell(1, 1)) # 跨行列合并
表格格式设置
# 表格宽度自适应 table.autofit = True # 指定行高 from docx.shared import Cm table.rows[0].height = Cm(0.93) # 修改表格字体大小 table.style.font.size = Pt(15) # 设置单元格对齐 from docx.enum.table import WD_ALIGN_VERTICAL cell = table.cell(0, 0) cell.paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER # 复制表格 from copy import deepcopy table_copy = deepcopy(doc.tables[0]) para1 = doc.add_paragraph() para1._p.addnext(table_copy._element)
插入图片
from io import BytesIO
import base64
# 普通插入
doc.add_picture('图片1.png')
doc.add_picture('图片2.png', width=Inches(2.5), height=Inches(2))
# 使用base64插入
picture2_base64 = open("图片2base64.txt").read()
img2_buf = base64.b64decode(picture2_base64)
doc.add_picture(BytesIO(img2_buf))
# 并排放图
run = doc.add_paragraph().add_run()
run.add_picture("图片1.png", width=Inches(2.5), height=Inches(2))
run.add_picture("图片1.png", width=Inches(2.5), height=Inches(2))
doc.add_page_break()
# 修改已有样式
doc.styles["Normal"].font.size = Pt(14)
doc.styles['Normal'].font.name = 'Arial'
doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), '楷体')
# 创建自定义段落样式
from docx.enum.style import WD_STYLE_TYPE
UserStyle1 = doc.styles.add_style('UserStyle1', WD_STYLE_TYPE.PARAGRAPH)
UserStyle1.font.size = Pt(40)
UserStyle1.font.color.rgb = RGBColor(0xff, 0xde, 0x00)
UserStyle1.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
UserStyle1.font.name = '微软雅黑'
UserStyle1._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
# 使用自定义样式
doc.add_paragraph('自定义段落样式', style=UserStyle1)
docxtpl可以将Word文档制作成模板,实现数据自动填充。
首先创建一个包含占位符的Word模板,占位符使用双花括号{{}}包裹。
from docxtpl import DocxTemplate, InlineImage, RichText
tpl = DocxTemplate("docxexample.docx")
text = """Transformer 模型由编码器(Encoder)和解码器(Decoder)组成..."""
picture1 = InlineImage(tpl, image_descriptor="图片1.png")
# 准备数据
paragraphs1 = [
"步骤1:输入表示(Input Representation)",
"步骤2:编码器处理(Encoder Processing)",
"步骤3:解码器处理(Decoder Processing)",
]
paragraphs2 = [
{"step": 1, "text": "输入向量(词嵌入+位置编码)进入编码器层。"},
{"step": 2, "text": "自注意力子层。"},
{"step": 3, "text": "前馈网络子层。"},
]
table = [
{"character": "并行计算", "description": "编码器可并行处理整个序列(与RNN不同)"},
{"character": "自注意力", "description": "每个词直接关联所有词,捕获长距离依赖"},
{"character": "位置编码", "description": "为无顺序的注意力机制注入位置信息"},
]
alerts = [
{
"date": "2015-03-10",
"desc": RichText("Very critical alert", color="FF0000", bold=True),
"type": "CRITICAL",
"bg": "FF0000",
},
# ... 其他数据
]
# 渲染模板
context = {
"title": "Transformer",
"text_body": text,
"picture1": picture1,
"picture2": picture2,
"paragraphs1": paragraphs1,
"paragraphs2": paragraphs2,
"runs": paragraphs1,
"display_paragraph": True,
"table1": table,
"table2": table,
"alerts": alerts,
}
tpl.render(context)
tpl.save("文件3.docx")
设置单元格边框
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
def set_cell_border(cell, **kwargs):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
tcBorders = tcPr.first_child_found_in("w:tcBorders")
if tcBorders is None:
tcBorders = OxmlElement("w:tcBorders")
tcPr.append(tcBorders)
for edge in ("left", "top", "right", "bottom", "insideH", "insideV"):
edge_data = kwargs.get(edge)
if edge_data:
tag = "w:{}".format(edge)
element = tcBorders.find(qn(tag))
if element is None:
element = OxmlElement(tag)
tcBorders.append(element)
for key in ["sz", "val", "color", "space", "shadow"]:
if key in edge_data:
element.set(qn("w:{}".format(key)), str(edge_data[key]))
# 使用示例
set_cell_border(
table.cell(0, 0),
top={"sz": 4, "val": "single", "color": "#000000", "space": "0"},
bottom={"sz": 4, "val": "single", "color": "#000000", "space": "0"},
left={"sz": 4, "val": "single", "color": "#000000", "space": "0"},
right={"sz": 4, "val": "single", "color": "#000000", "space": "0"},
)
def add_hyperlink(paragraph, url, text):
part = paragraph.part
r_id = part.relate_to(
url,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
is_external=True,
)
hyperlink = OxmlElement("w:hyperlink")
hyperlink.set(qn("r:id"), r_id)
run = OxmlElement("w:r")
run_text = OxmlElement("w:t")
run_text.text = text
run.append(run_text)
hyperlink.append(run)
paragraph._p.append(hyperlink)
p = doc.add_paragraph("点击访问: ")
add_hyperlink(p, "https://www.baidu.com", "示例链接")
提取文档中的图片
import zipfile
from xml.etree.ElementTree import fromstring
def extract_images(docx_path, output_dir):
with zipfile.ZipFile(docx_path) as z:
try:
doc_rels = z.read('word/_rels/document.xml.rels').decode('utf-8')
except KeyError:
return []
root = fromstring(doc_rels)
rels = []
for child in root:
if 'Type' in child.attrib and child.attrib['Type'] == RT.IMAGE:
rels.append((child.attrib['Id'], child.attrib['Target']))
images = []
for rel_id, target in rels:
try:
image_data = z.read('word/' + target)
image_name = target.split('/')[-1]
with open(f"{output_dir}/{image_name}", 'wb') as f:
f.write(image_data)
images.append(image_name)
except KeyError:
continue
return images
print(extract_images("Transformer原理纯享版.docx", "pictures"))
插入浮动图片
# 插入“衬于文字下方”的浮动图片
# 如将 behindDoc="1" 改成0就是“浮于文字上方”了
# refer to docx.oxml.shape.CT_Inline
class CT_Anchor(BaseOxmlElement):
"""
``<w:anchor>`` element, container for a floating image.
"""
extent = OneAndOnlyOne('wp:extent')
docPr = OneAndOnlyOne('wp:docPr')
graphic = OneAndOnlyOne('a:graphic')
@classmethod
def new(cls, cx, cy, shape_id, pic, pos_x, pos_y):
"""
Return a new ``<wp:anchor>`` element populated with the values passed
as parameters.
"""
anchor = parse_xml(cls._anchor_xml(pos_x, pos_y))
anchor.extent.cx = cx
anchor.extent.cy = cy
anchor.docPr.id = shape_id
anchor.docPr.name = 'Picture %d' % shape_id
anchor.graphic.graphicData.uri = (
'http://schemas.openxmlformats.org/drawingml/2006/picture'
)
anchor.graphic.graphicData._insert_pic(pic)
return anchor
@classmethod
def new_pic_anchor(cls, shape_id, rId, filename, cx, cy, pos_x, pos_y):
"""
Return a new `wp:anchor` element containing the `pic:pic` element
specified by the argument values.
"""
pic_id = 0 # Word doesn't seem to use this, but does not omit it
pic = CT_Picture.new(pic_id, filename, rId, cx, cy)
anchor = cls.new(cx, cy, shape_id, pic, pos_x, pos_y)
anchor.graphic.graphicData._insert_pic(pic)
return anchor
@classmethod
def _anchor_xml(cls, pos_x, pos_y):
return (
'<wp:anchor distT="0" distB="0" distL="0" distR="0" simplePos="0" relativeHeight="0" n'
' behindDoc="1" locked="0" layoutInCell="1" allowOverlap="1" n'
' %s>n'
' <wp:simplePos x="0" y="0"/>n'
' <wp:positionH relativeFrom="page">n'
' <wp:posOffset>%d</wp:posOffset>n'
' </wp:positionH>n'
' <wp:positionV relativeFrom="page">n'
' <wp:posOffset>%d</wp:posOffset>n'
' </wp:positionV>n'
' <wp:extent cx="914400" cy="914400"/>n'
' <wp:wrapNone/>n'
' <wp:docPr id="666" name="unnamed"/>n'
' <wp:cNvGraphicFramePr>n'
' <a:graphicFrameLocks noChangeAspect="1"/>n'
' </wp:cNvGraphicFramePr>n'
' <a:graphic>n'
' <a:graphicData uri="URI not set"/>n'
' </a:graphic>n'
'</wp:anchor>' % ( nsdecls('wp', 'a', 'pic', 'r'), int(pos_x), int(pos_y) )
)
# refer to docx.parts.story.BaseStoryPart.new_pic_inline
def new_pic_anchor(part, image_descriptor, width, height, pos_x, pos_y):
"""Return a newly-created `w:anchor` element.
The element contains the image specified by *image_descriptor* and is scaled
based on the values of *width* and *height*.
"""
rId, image = part.get_or_add_image(image_descriptor)
cx, cy = image.scaled_dimensions(width, height)
shape_id, filename = part.next_id, image.filename
return CT_Anchor.new_pic_anchor(shape_id, rId, filename, cx, cy, pos_x, pos_y)
# refer to docx.text.run.add_picture
def add_float_picture(p, image_path_or_stream, width=None, height=None, pos_x=0, pos_y=0):
"""Add float picture at fixed position `pos_x` and `pos_y` to the top-left point of page.
"""
run = p.add_run()
anchor = new_pic_anchor(run.part, image_path_or_stream, width, height, pos_x, pos_y)
run._r.add_drawing(anchor)
# refer to docx.oxml.__init__.py
register_element_cls('wp:anchor', CT_Anchor)
document = Document()
# add a floating picture
p = document.add_paragraph()
add_float_picture(p, '图片1.png')
# add text
p.add_run('Hello World '*50)
document.save('文件2.docx')
# https://www.cnblogs.com/dancesir/p/17788854.html
# 分2栏
section = doc.sections[0]
sectPr = section._sectPr
cols = sectPr.xpath('./w:cols')[0]
cols.set(qn('w:num'),'2')
# 普通页眉
doc = Document('Transformer原理纯享版.docx')
doc.sections[0].header.paragraphs[0].text = "这是第1节页眉"
# 分奇偶设置页眉
doc.settings.odd_and_even_pages_header_footer = True
doc.sections[0].even_page_header.paragraphs[0].text = "这是偶数页页眉"
doc.sections[0].header.paragraphs[0].text = "这是奇数页页眉"
# 设置首页页眉
doc.sections[0].different_first_page_header_footer = True
doc.sections[0].first_page_header.paragraphs[0].text = "这是首页页眉"
# 插入目录(不会更新域)
paragraph = doc.paragraphs[0].insert_paragraph_before()
run = paragraph.add_run()
fldChar = OxmlElement('w:fldChar')
fldChar.set(qn('w:fldCharType'), 'begin')
instrText = OxmlElement('w:instrText')
instrText.set(qn('xml:space'), 'preserve')
instrText.text = r'TOC o "1-3" h z u'
fldChar2 = OxmlElement('w:fldChar')
fldChar2.set(qn('w:fldCharType'), 'separate')
fldChar3 = OxmlElement('w:t')
fldChar3.text = "Right-click to update field."
fldChar2.append(fldChar3)
fldChar4 = OxmlElement('w:fldChar')
fldChar4.set(qn('w:fldCharType'), 'end')
r_element = run._r
r_element.append(fldChar)
r_element.append(instrText)
r_element.append(fldChar2)
r_element.append(fldChar4)
# 自动更新目录
import lxml
name_space = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
update_name_space = "%supdateFields" % name_space
val_name_space = "%sval" % name_space
try:
element_update_field_obj = lxml.etree.SubElement(doc.settings.element, update_name_space)
element_update_field_obj.set(val_name_space, "true")
except Exception as e:
del e
from docxcompose.composer import Composer
master = Document("文件1.docx")
composer = Composer(master)
doc1 = Document("文件2.docx")
composer.append(doc1)
doc2 = Document("文件3.docx")
composer.append(doc2)
composer.save("combined.docx")
注意:合并文档时,后面的文档会跟随第一个文档的格式。
本文介绍了Python处理Word文档的完整流程,包括:
- 使用python-docx进行基础的文档创建、编辑和格式化
- 使用docxtpl实现基于模板的自动化数据填充
- 使用docxcompose合并多个Word文档
- 各种进阶功能如设置单元格边框、插入超链接、提取图片、设置页眉页脚等
这些技术可以广泛应用于自动化报告生成、批量文档处理、合同模板填充等场景,大大提高工作效率。
以上就是从基础到进阶详解Python处理Word文档的完全指南的详细内容,更多关于Python处理Word文档的资料请关注风君子博客其它相关文章!
您可能感兴趣的文章:
- Python实现Word图片提取与批量处理的最佳实践
- Python自动化处理Excel、Word、PDF文档的操作大全
- 利用Python实现Word文档的智能解析与图片处理
- Python自动化办公全攻略之Excel/Word/PDF/邮件批量处理
- Python处理Word文档中的批注(添加、删除)
- Python实现Word文档自动化处理操作
- Python使用python-docx实现自动化处理Word文档
- python使用python-docx处理word的方法示例