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 值对应的自动编号长成什么样、起始序号是几、序号是中文还是数字等等信息。

(一)numId与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的这对值的对应关系。

(二)abstractNum包含样式、起始数字等关键信息。

从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表示下一个

作者:cr3109585

物联沃分享整理
物联沃-IOTWORD物联网 » error code: 525

发表回复