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

相关推荐
luckys.one6 小时前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
ccut 第一混6 小时前
c# 调用basler 相机
c#·halcon·basler
TomCode先生6 小时前
c#动态树形表达式详解
开发语言·c#
大翻哥哥7 小时前
Python 2025:量化金融与智能交易的新纪元
开发语言·python·金融
zhousenshan8 小时前
Python爬虫常用框架
开发语言·爬虫·python
IMER SIMPLE9 小时前
人工智能-python-深度学习-经典神经网络AlexNet
人工智能·python·深度学习
CodeCraft Studio9 小时前
国产化Word处理组件Spire.DOC教程:使用 Python 将 Markdown 转换为 HTML 的详细教程
python·html·word·markdown·国产化·spire.doc·文档格式转换
专注API从业者9 小时前
Python/Java 代码示例:手把手教程调用 1688 API 获取商品详情实时数据
java·linux·数据库·python
java1234_小锋10 小时前
[免费]基于Python的协同过滤电影推荐系统(Django+Vue+sqlite+爬虫)【论文+源码+SQL脚本】
python·django·电影推荐系统·协同过滤
看海天一色听风起雨落10 小时前
Python学习之装饰器
开发语言·python·学习