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

相关推荐
板面华仔6 分钟前
机器学习入门(二)——逻辑回归 (Logistic Regression)
python·机器学习
Hgfdsaqwr8 分钟前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
初九之潜龙勿用14 分钟前
C# 操作Word模拟解析HTML标记之背景色
开发语言·c#·word·.net·office
GHZhao_GIS_RS16 分钟前
python中的sort和sorted用法汇总
python·排序·列表
永恒的溪流21 分钟前
环境出问题,再修改
pytorch·python·深度学习
ruxshui22 分钟前
Python多线程环境下连接对象的线程安全管理规范
开发语言·数据库·python·sql
大模型玩家七七28 分钟前
向量数据库实战:从“看起来能用”到“真的能用”,中间隔着一堆坑
数据库·人工智能·python·深度学习·ai·oracle
2301_7634724632 分钟前
使用PyQt5创建现代化的桌面应用程序
jvm·数据库·python
爱学习的阿磊34 分钟前
Web开发与API
jvm·数据库·python
qq_1927798738 分钟前
Python多线程与多进程:如何选择?(GIL全局解释器锁详解)
jvm·数据库·python