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

相关推荐
Java Fans1 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手1 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
算法小白(真小白)1 小时前
低代码软件搭建自学第二天——构建拖拽功能
python·低代码·pyqt
唐小旭1 小时前
服务器建立-错误:pyenv环境建立后python版本不对
运维·服务器·python
码农君莫笑1 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
旭东怪1 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
007php0071 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
Chinese Red Guest2 小时前
python
开发语言·python·pygame
鲤籽鲲2 小时前
C# Random 随机数 全面解析
android·java·c#