赞
踩
2024.5.31更新2点:1、发现有些中文自动编号不是叫chineseCounting,后面还带了thousand什么的,所以 if num_fmt == "chineseCounting" 要改为 if "chineseCounting" in num_fmt。2、之前对自动编号假设只有3级,虽然看起来数字是连续的,但实际上每个都可能是一组新的自动编号的第一个,对此,修正后对应代码应为:
start_lv123 = [i*0 for i in range(len(List_of_dict))] #原本是start_lv123=[0,0,0]
for k in range(len(List_of_dict)):
if "start" in List_of_dict[k].keys():
start_lv123[k]=int(List_of_dict[k]["start"].split('\'')[0])
我的上一次文章大概说明了基于docx库解决自动编号识别问题的原理和结论(参见上一篇文章http://t.csdnimg.cn/gozU9),在这篇文章进一步说明一些细节问题,最后附上代码。
我这里有一个text.docx文档(见下图),带有2级自动编号:一、二、 以及1.2. 。以此为例说明。
自动编号的种类、样式等信息储存在numbering.xml当中(每个docx都是一个zip,手动改变后缀名后可打开压缩包,找到/word/numbering.xml这个文件)。最上层的节点叫numbering,下面与4个节点,分别是2个num和2个abstractNum。每个num节点记录了某个numId值与某个abstractNumId 值的对应关系。节点abstractNum记录了每一个abstractNumId 值对应的自动编号长成什么样、起始序号是几、序号是中文还是数字等等信息。
而图中xml文件里面最上面那行的“numbering”节点所对应内容信息是在doc.part.numbering_part.numbering_definitions._numbering的位置。它的下一层有4个元素,对应xml的2个num和2个abstractNum节点;同时,它下面有个属性叫num_lst,记载了numId与abstractNumId 的对应关系(实际上与2个num节点应该是记录了同样的内容)。当时我是通过反复逐个print+dir观察出这些规律。
运行结果:
经过dir测试,num_lst是个列表,len(num_lst)结果为2,说明有2个元素。继续对num_lst[0]、num_lst[1]进行dir测试,发现属性当中有numId和abstractNumId这俩。
这就验证了,doc.part.numbering_part.numbering_definitions._numbering.num_lst储存了numId 与abstractNumId的这对值的对应关系。
从xml的abstractNum节点看出,在其下属的lvl节点中,记录了每种有自动编号的numFmt 、lvlText、start等信息,numFmt+lvlText表示其样式,例如“chinesecouting”、“%1、”,意思是中文加顿号,即“一、”“二、”。再如,“decimal”“%1.” 对应的是数字加点,即“1.”“2.”。start表示这种自动编号第一个的序号是几。
首先,找出lvl节点在abstractNum列表的index值。然后查看lvl节点所有子节点的tag,转为字符后,提取最后一组单引号内的字符串,其中就包含了start、numFmt、lvlText等关键信息。
我把每一级自动编号的numId、abstractNumId、start、numFmt、lvlText等信息装在一本本字典中,最后再用列表“List_of_dict”把这些字典装起来。
文档发生自动编号的信息储存在document.xml当中。每当发生自动编号,就会出现对应numId的值,以及对应在哪一段文字前插入了自动编号。
在document对象中,记录了在哪里发生自动编号的信息位置是在每一个paragraph的paragraph_format.element.pPr.numPr里面,如果发生了自动编号,就会出现"numId"这个属性。用循环+判断,如果遇到了numId,便用这个numId的值去查字典,遍历List_of_dict里面的每一本字典,便能找到对应的样式。
————————————————————————————————
以上是对上一篇文章的补充说明。最后附上完整代码。
————————————————————————————————
- from docx import Document
-
- def contains_words(strings, words): #判断是否包含所有关键词
- return any(words in s for s in strings)
-
- numbering_list = doc.part.numbering_part.numbering_definitions._numbering
- print("numbering_list =",numbering_list)
- print("dir(numbering_list) =",dir(numbering_list))
- print("dir(numbering_list.num_lst[0])=",dir(numbering_list.num_lst[0]))
- List_of_dict =[]
- for j in range(len(numbering_list.num_lst)):
- List_of_dict.append({}) #非常重要的细节处理:把自动编码转为纯文本。用字典存放一级、二级、三级标题信息。
- print(List_of_dict)
- for i in range(len(numbering_list.num_lst)): #把num_lst信息复刻到3本字典中
- numx = str(numbering_list.num_lst[i].numId)
- absNumx = str(numbering_list.num_lst[i].abstractNumId.val)
- List_of_dict[i]["numId"]=numx
- List_of_dict[i]["absNumId"] =absNumx
- for i in range(len(numbering_list.num_lst)):
- for nlx in numbering_list:
- ii = 999
- if hasattr(nlx,"attrib") :
- if nlx.attrib.keys()[0].endswith("abstractNumId") :
- if nlx.attrib.values()[0] == List_of_dict[i]["absNumId"]:
- for nlxx in nlx: #找出lvl标签在abstractNum列表的index值ii
- attr_dict = getattr(nlxx,"attrib",None)
- if attr_dict is not None:
- if contains_words(attr_dict.keys(),"ilvl"):
- if nlxx.tag[-3:] == "lvl" :
- ii=(nlx.index(nlxx))
- break
-
- if ii != 999 :
- for kk in nlx[ii]:
- kk_tag = kk.tag.split('}')
- kk_val = str(kk.attrib.values())
- print("kk_tag=",kk_tag,"kk_val=",kk_val)
- if len(kk_val)>1:
- List_of_dict[i][kk_tag[1]]=kk_val[2:-2]
- # print(f"List_of_dict[{i}][{kk_tag[1]}]=",List_of_dict[i][kk_tag[1]])
- ii = 999
-
- start_lv123 = [0,0,0]
- if len(List_of_dict)>3:
- for k in range(3):
- if "start" in List_of_dict[k].keys():
- start_lv123[k]=int(List_of_dict[k]["start"].split('\'')[0])
- else:
- for k in range(len(List_of_dict)):
- if "start" in List_of_dict[k].keys():
- start_lv123[k]=int(List_of_dict[k]["start"].split('\'')[0])
- print("start_lv123=",start_lv123)
-
- for d in doc.paragraphs: # 遍历所有段落,每当出现自动编号,在编号与后面紧接的文字之间,加入一份跟自动编号长得一样的纯文本。
- val_d = getattr(d.paragraph_format.element.pPr.numPr,"numId",None) #对于每个段落,逐个尝试是否存在numId这个属性,没有就赋值None
- if val_d is not None: #如果存在numId这个属性,下一步就拿val_d去查字典,读取应该替换哪种格式是%1、还是(%1) %1.
- for dd in range(len(List_of_dict)):
- if len(List_of_dict[dd]) > 0:
- if List_of_dict[dd]["numId"] != str(val_d.val): #如果对应一级标题的numId没对上号,就找二级标题、三级标题
- continue
- else: #查字典对上号了,读取对应的编号格式
- num_fmt =List_of_dict[dd]["numFmt"]
- auto_number = List_of_dict[dd]["lvlText"]
- if num_fmt == "chineseCounting":
- auto_number = auto_number.replace("%1",shuzizhuanhanzi[start_lv123[dd]])
- elif num_fmt == "decimal":
- auto_number = auto_number.replace("%1",str(start_lv123[dd]))
- print("auto_number= ",auto_number)
- d.text = auto_number + d.text
- start_lv123[dd] += 1 #对这个自动编码的序号处理完了,记得+1表示下一个
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。