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段落字体样式进行覆盖,从而达到设置正文字体样式的效果。

相关推荐
huisheng_qaq5 小时前
【Python基础篇-07】深入理解python的面向对象编程
python·多态·继承·面向对象编程·封装
FanetheDivine5 小时前
学习python 2.语法速览
python
奕鼎竜瑆5 小时前
[新手小白也能学会] 01-PyTorch框架使用(上)
人工智能·pytorch·python
海天一色y5 小时前
图像分割全解析:从经典算法到深度学习的原理与实战(Python + MATLAB)
python·深度学习·算法
三点水-here6 小时前
从 Miniconda 迁移到 Miniforge3 全记录:安装、配置、环境迁移与排错实录
python·conda
海天鹰6 小时前
C#入门:显示窗口
c#
淡海水6 小时前
16-02-C#常用数据结构源码-附录B-源码索引速查
unity·c#·游戏引擎·il2cpp
夜雪一千6 小时前
Python URL解码详解:单层、双重URL解码实战与踩坑指南
linux·服务器·python
VIP_CQCRE6 小时前
用 Ace Data Cloud 一站式接入 AI 视频生成:HappyHorse Videos API 实战指南
python·aigc·api·ai视频·acedatacloud
kali-Myon6 小时前
定向 FGSM 攻击:让神经网络《指猫为狗》
图像处理·python·神经网络·安全·机器学习·fgsm