Python-docx 深入word源码 自定义带有序号的段落中的序号字体样式设置

  1. numbering.xml
  2. document.xml

代码实现与效果展示

python 复制代码
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.oxml.xmlchemy import BaseOxmlElement
from docx.shared import StoryChild, RGBColor, Pt


def SetFontByXml(element, font_name, font_size, is_bold=False):
    '''
    通过修改XML来自定义字体
    element: 可以是需要设置序号字体的带有序号的段落; 也可以是run对象
    '''
    if element is None:
        return

    # 判断rPr标签是否存在
    if isinstance(element, StoryChild): # doc element
        rPr_elements = element._element.find(qn('w:rPr'))
    elif isinstance(element, BaseOxmlElement): # CT by xml
        rPr_elements = element.xpath('.//w:rPr')
    else:
        return

    if rPr_elements is not None and len(rPr_elements) > 0:
        rPr_element = GetFirstValidElement(elements=rPr_elements)
        rPr_element.clear() # 清空原先的设置
    else:
        rPr_element = OxmlElement('w:rPr')
        
    # 增加w:rFonts标签和w:sz标签
    font_name_element = OxmlElement('w:rFonts') # 修改字体名称
    font_name_element.set(qn('w:ascii'), font_name)
    font_name_element.set(qn('w:h-ansi'), font_name)
    font_name_element.set(qn('w:east-asian'), font_name)
    font_size_element = OxmlElement('w:sz') # 修改字体大小
    font_size_element.set(qn('w:val'), str(GetFontSize(font_size) * 2)) # val是pt的2倍

    if is_bold:
        bold_element = OxmlElement('w:b') # 字体加粗
        rPr_element.append(bold_element)

    rPr_element.append(font_name_element)
    rPr_element.append(font_size_element)

    # 将已定义的字体标签添加到rPr标签内
    if isinstance(element, StoryChild): # doc element
        element._element.insert(index=0, element=rPr_element)
    elif isinstance(element, BaseOxmlElement): # CT by xml
        element.insert(index=0, element=rPr_element) # 添加到w:t前


# 根据传入的中英文(例如五号/10.5)获取字号数组
def GetFontSize(font_size):
    chinese_sizes = {
        "初号": 42, "小初": 36, "一号": 26, "小一": 24,
        "二号": 22, "小二": 18, "三号": 16, "小三": 15,
        "四号": 14, "小四": 12, "五号": 10.5, "小五": 9,
        "六号": 7.5, "小六": 6.5, "七号": 5.5, "八号": 5
    }

    if isinstance(font_size, float) or isinstance(font_size, int):
        # 如果是整数,假设为英文字号
        return float(font_size)
    elif font_size.isdigit():
        # 如果是纯数字,假设为英文字号
        return float(font_size)
    elif font_size in chinese_sizes:
        # 如果是中文字号,映射为磅数
        return chinese_sizes[font_size]
    else:
        # 如果不是中文也不是纯数字,返回None或者设定一个默认值
        return None

# 定义字体样式
def SetFont(run, name, size, is_bold=False, is_italic=False, color=None) -> None:
    '''
    通过传入元素的run对象进行字体属性设置
    name表示字体名称、size表示字体大小(并用整数表示)、is_bold表示字体是否加粗
    is_italic表示字体是否为斜体、color表示字体颜色(用Tuple表示RGB)
    '''
    if run:
        font = run.font
        font.name = name  # 设置字体名称
        font.size = Pt(GetFontSize(font_size=size))   # 设置字体大小
        run._element.rPr.rFonts.set(qn("w:eastAsia"), name) # 设置中文字体属性
        font.bold = is_bold  # 粗体
        font.italic = is_italic # 斜体
        if color:  # 颜色
            font.color.rgb = RGBColor(*color)


doc = Document()

p = doc.add_paragraph(style='List Number')
SetFontByXml(p, font_name='黑体', font_size='一号', is_bold=True)
r = p.add_run('123')
SetFont(run=r, name='宋体', size='小四')

# 保存文档
doc.save('./test.docx')

以下是test.docx的内容展示,可以发现序号正文被设置为了不同的字体样式

原理与思路

问题

由于直接修改run对象的字体样式只对正文生效,而对序号样式不生效。所以可以考虑通过修改word文档的XML来使序号字体样式生效。

一开始我尝试先对word/numbering.xml中的序号字体样式进行修改,发现有时候序号样式不生效,经过测试发现会被word/document.xml对应的段落字体样式覆盖。所以说明document.xml的字体样式优先级比numbering.xml高。

解决方案

发现了字体样式优先级的问题后,可以考虑只对document.xml中的样式进行设置。通过查看其XML代码,可以发现同时设置序号与正文字体样式设置的XML模板:

xml 复制代码
<w:p w14:paraId="45659768"
     w14:textId="3BB12CE7"
     w:rsidR="004438A0"
     w:rsidRPr="00843982"
     w:rsidRDefault="002B1108"
     w:rsidP="002B1108">
	<w:rPr>
		<w:rFonts w:ascii="楷体"
		          w:eastAsia="楷体"
		          w:hAnsi="楷体"/>
	</w:rPr>
	<w:pPr>
		<w:pStyle w:val="a3"/>
		<w:numPr>
			<w:ilvl w:val="0"/>
			<w:numId w:val="1"/>
		</w:numPr>
		<w:ind w:firstLineChars="0"/>
	</w:pPr>
	<w:r w:rsidRPr="00013C3C">
		<w:rPr>
			<w:rFonts w:ascii="宋体"
			          w:eastAsia="宋体"
			          w:hAnsi="宋体"/>
		</w:rPr>
		<w:t>测试文本</w:t>
	</w:r>
</w:p>

其中用于设置序号字体(楷体)样式的<w:rPr>标签放在<w:p>或者<w:pPr>的下一级均可使序号字体样式生效,而正文的字体样式(宋体)的<w:rPr>放在<w:r>的下一级即可。

这说明其实设置序号字体样式的方式是通过设置整个段落w:p标签的样式,然后run对象中的字体样式优先级比w:p中的rPr高,会将w:p段落字体样式进行覆盖,从而达到设置正文字体样式的效果。

相关推荐
Sylvia33.3 小时前
LOL实时数据接入深度解析:从WebSocket到完整数据模型
java·网络·python·websocket·网络协议
梦想不只是梦与想3 小时前
Web 服务器网关协议:ASGI 和 WSGI
服务器·python·fastapi
ai小陈5 小时前
PyTorch DataLoader数据加载性能排查:GPU利用率低的实操指南
人工智能·pytorch·python·深度学习·ai·gpu算力
点心的游戏开发世界8 小时前
Unity C# 脚本学习笔记:命名空间与 using
学习·unity·c#
2601_962299248 小时前
Azure python操作系统列表
python·操作系统·azure·虚拟机·存储配置文件
学术 学术 Fun9 小时前
如何用 API 与 Webhook 批量把图表图片转换成 VSDX
python·自动化·api·visio
淡海水9 小时前
07-04-并发-ConcurrentBag-T-工作窃取WorkStealing算法
开发语言·算法·c#·bag·concurrent·workstealing
点心的游戏开发世界9 小时前
Unity C# 脚本学习笔记:泛型
学习·unity·c#
2601_9621005410 小时前
python包和模块的内容整理
python·模块·导入·虚拟环境·
二进制漫游记10 小时前
FastAPI项目集成 Qdrant向量数据库+阿里云Embedding完整实战(工具封装+业务调用)
数据库·python·阿里云·embedding