使用Python识别word文档中的各级标题(遍历样式法)

写在前面:
本文通过word中“段落”设置中的大纲级别识别标题,如下图所示。不是通过“第X章”或“X.X.X”的字符识别。
在这里插入图片描述
文章使用的是python中处理word文档的python-docx库。如何使用python-docx打开并遍历docx中的各个段落,这里就不详细介绍了,只放一段代码。

path = r'./'
fileList = os.listdir(path)
# 遍历当前目录下的所有文件
for docxFile in fileList:
	# 找到以.docx结尾的文件
    if docxFile.endswith('.docx'):
		# 读取docx文档
		document = docx.Document(docxFile)
		# 逐段读取docx文档的内容
		for paragraph in document.paragraphs:
			# 判断该段落的标题级别
			# 这里用isTitle()临时代表,具体见下文介绍的方法
			isTitle(paragraph)

方法1 通过“样式名称”识别标题

def isTitle(paragraph):
	if paragraph.style.name == 'Heading 1':
		print(‘这是一个一级标题’)
	elif paragraph.style.name == 'Heading 2':
		print(‘这是一个二级标题’)
	elif paragraph.style.name == 'Heading 3':
		print(‘这是一个三级标题’)

这是网上搜到最多的一种方法。“Heading 1”是word默认自带的一个样式,下图红框标出的就是Heading 1。
在这里插入图片描述
这种方法的缺点显而易见:大部分文档都不是用word默认 'Heading 1~3’去设置标题的,而是自定义了其他的样式,或者不使用样式,直接在右键-段落里面设置大纲级别。这种方法并不适用。

下面会介绍两种更好的方法。

方法2 通过xml读取“段落格式”的大纲级别

对于没有使用样式,直接在word“段落”页面设置大纲级别的文档,可以使用这种方法:
通过xml读取段落格式的大纲级别(即本文第一张图上所示的大纲级别)。

以下内容参考自博文:https://blog.csdn.net/qq_38237214/article/details/132824134

xml中的<w:outlineLvl w:val=“0”/>表示这个段落的大纲级别是1级,也就是一级标题。val=“0”的数字是从0开始的。类似的,<w:outlineLvl w:val=“1”/>代表二级标题,<w:outlineLvl w:val=“2”/>代表三级标题。

原理很简单,就是找段落的xml里有没有<w:outlineLvl,如果有的话找到它的val是几。实现代码如下:

def isTitle(paragraph):
	# 读取该段落的xml
    paragraphXml = paragraph._p.xml
    # 搜索段落中是否有<w:outlineLvl字段
    if paragraphXml.find('<w:outlineLvl') >= 0:
		startIndex = paragraphXml .find('<w:outlineLvl')
		endIndex = paragraphXml .find('>', start_index)
		outlineLvl = xml[start_index:end_index+1]
		outlineLvl = re.search("\d+", outlineLvl ).group()
		print(f"outlineLvl = {outlineLvl}")

方法3 通过xml读取自定义样式的大纲级别

对于使用自定义样式设置标题的文档,即便在word“段落”页面上能看到“大纲级别”,但通过方法2读段落的xml(paragraph._p.xml)并不能找到<w:outlineLvl w:val=“0”/>。

我对docx不是很懂,猜测是因为这个大纲级别的属性是样式赋予的,所以并不会在paragraph._p.xml中体现出来。因此,需要从段落的样式的xml(paragraph.style.element.xml)中找<w:outlineLvl w:val=“0”/>字段。

读到这里,仿照着方法2的代码,从paragraph.style.element.xml里面搜索<w:outlineLvl就可以了。但是还有一个坑:word的样式是可以嵌套的,就像C里面子类继承父类的所有属性一样。word中样式(style)的父类叫做base_style。例如,用户自定义了一个样式A,A设置了大纲级别1,然后又在A的基础上调整了一下字体字号,形成了样式B。那么B同样会拥有大纲级别1的属性,在word的“段落”页面也能看到“大纲级别1”被正确地设置了,但是B.style.element.xml是没有<w:outlineLvl w:val=“0”/>的,A的xml中才有。

所以最万无一失的做法是,像遍历链表一样把一段文字的style及其所有的base_style的xml全部搜一遍,找到有没有<w:outlineLvl w:val=“0”/>藏在里面。

叙述起来很复杂,直接上终极代码了。这个代码整合了方法2和方法3,目前看来能应对绝大多数文档了。

def getOutlineLevel(inputXml):
    """
    功能 从xml字段中提取出<w:outlineLvl w:val="number"/>中的数字number
    参数 inputXml
    返回 number
    """
    start_index = inputXml.find('<w:outlineLvl')
    end_index = inputXml.find('>', start_index)
    number = inputXml[start_index:end_index+1]
    number = re.search("\d+", number).group()
    return number

def isTitle(paragraph):
    """
    功能 判断该段落是否设置了大纲等级
    参数 paragraph:段落
    返回 None:普通正文,没有大纲级别 0:一级标题 1:二级标题 2:三级标题
    """
    # 如果是空行,直接返回None
    if paragraph.text.strip() == '':
        return None
        
    # 如果该段落是直接在段落里设置大纲级别的,根据xml判断大纲级别
    paragraphXml = paragraph._p.xml
    if paragraphXml.find('<w:outlineLvl') >= 0:
        return getOutlineLevel(paragraphXml)
    # 如果该段落是通过样式设置大纲级别的,逐级检索样式及其父样式,判断大纲级别
    targetStyle = paragraph.style
    while targetStyle is not None:
        # 如果在该级style中找到了大纲级别,返回
        if targetStyle.element.xml.find('<w:outlineLvl') >= 0:
            return getOutlineLevel(targetStyle.element.xml)
        else:
            targetStyle = targetStyle.base_style
    # 如果在段落、样式里都没有找到大纲级别,返回None
    return None
  • 24
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河上七月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值