上班有任务之后感觉效率就提高了,果然,人活着还是得有目标、有动力。
我们发现word文档的内容顺序不对,但是有几百条标题,上千页文档,手动排序虽然也可以,但是费时费力,而且万一以后需求又有了变化就白给了,所以依然以python-docx为基础,开发了一个sort脚本
预备知识
我们首先要知道docx的格式,docx的主体本质上是一个xml
<w:body>
body之间的内容就是word文档的内容
</w:body>
<w:p>
p之间的内容就是文档内一个段落的内容
</w:p>
<w:r>
r之间的内容是段落内的小块,当一个段落内字体以及格式不一样是,会存在多个run
</w:r>
这个模块这表示该段落p(模块r)的类型
<w:pPr>
p:
<w:Style w:val = "Heading 1">标题
r:
<w:b/>加粗
<w:i/>斜体
</w:pPr>
<w:t>
这个部分用于存放文本内容
</w:t>
上述格式的级别包含关系示例:
<w:body>
<w:p>
<w:pPr>
<w:Style w:val = "Heading 1">
</w:pPr>
<w:r>
<w:pPr>
<w:b/>
</w:pPr>
<w:t>Heading1</w:t>
</w:r>
</w:p>
</w:body>
上述示例,在word中显示就是一个加粗的,段落与字体格式为Heading 1的,文本显示为Heading1的一级标题
本方法的适用类型
Notice
仅适用于以下结构的文档,文档的主体部分仅存在段落结构,所有其他的内容都归于段落内
<w:body>
#段落1
<w:p>
标题
</w:p>
#段落2
<w:p>
正文、图片
</w:p>
#段落3
<w:p>
表格
</w:p>
...
#段落n
<w:p>
标题n
</w:p>
...
<w:body>
排序思路
对于这种结构的文档,那么我们的思路就很清晰了:
- 遍历文档的所有段落
- 找到文档格式(Style)为标题(Heading)的段落
- 把所有标题段落的文本(Text)取出生成一个StringList,并按照自己的需求排序
- 最后根据StringList的顺序,为文档重新排序,即移动paragraph
这样我们就完成了对于文档的排序
实际操作
import
from os import write
from sys import flags
from docx import Document
from docx.shared import Inches
str_list_sort
第一个函数是排序函数,我们将一个二维列表根据列表的第一个元素(我们的标题)的字母(不区分大小写)从小到大排序,最后返回标题的String,以及标题对应的段落的开始序号和结束序号
[HeadingStr, ParagraphStartIndex, ParagraphEndIndex]
本函数的原型是在网上抄的,感谢一波忘记是谁的不知名网友
#将所有字符串转化为小写进行比较排序
def str_list_sort(string_list):
listtemp = []
auxiliary_list = []
for i in range(0,len(string_list)):
listtemp.append(string_list[i][0].lower())
listtemp.append(string_list[i][0])
listtemp.append(string_list[i][1])
listtemp.append(string_list[i][2])
auxiliary_list.append(listtemp)
listtemp = []
#将字符串列表转化为:[['str1','str1.lower()'], ['str2','str2.lower()'], ...]的格式
#auxiliary_list = [(x.lower(), x, index, endindex) for x in string_list[0] for index in string_list[1] for endindex in string_list[2]]
#排序
auxiliary_list.sort()
#读取排序后的字符串列表(读取第二位字符串)
new_list = [(x[1], x[2], x[3]) for x in auxiliary_list]
return new_list
insertParagraph
第二个函数是将原始文档中的段落,插入新的文档中
输入:段落起始序号,段落结束序号,原始文档的路径,新文档的对象,新文档的插入起始位置
def insertParagraph(begin, end, filePath, dNew, startIndex):
'''
flag = False
write = False
dOri = Document(filePath + 'MC-Basic Document User.docx')
for element in dOri.element.body:
d = Document()
d.element.body.append(element)
if len(d.paragraphs) > 0:
p = d.paragraphs[0]
#print(p.text)
if len(p.text) > 0:
if str == p.text:
flag = True
write = True
elif "Heading 1" == p.style.name:
flag = False
if write:
break
if(flag):
dNew.element.body.append(element)
'''
#print(1)
dOri = Document(filePath)
i = begin
while i <= end:
dNew.element.body.append(dOri.paragraphs[i]._p)
end = end - 1
这个函数非常简短着重解释以下几点:
'''
这个变量代表:docx类.第i个段落方法.指向该段落本体的指针
因此,当将该变量append到新文档中,会导致旧文档的paragraphs的第i个成员丢失
于是,我们不需要改变序号i,一直读取paragraphs[i]._p,即可实现顺序添加段落
于是,我们通过end-- 的方式完成遍历添加段落
'''
dOri.paragraphs[i]._p
'''
这个变量代表:docx方法类.指向docx本体类的指针.下属的body元素
通过append的方法,把paragraph的本体添加进去
通过这种方法,能够实现对paragraph的整体添加,而不是只加入文本和格式,
确保了表格,超链接,标签等等内容不会丢失
'''
dNew.element.body
sortHeading
第三个函数就是实现通过标题对文档进行sort的函数
输入原始文档的对象(其实好像不需要)和路径
def sortHeading(document, fileName):
'''
为了确保文档设置的格式统一性,我们不使用Document()创建新的空白文档
而是通过读取原始文档,再将文档的内容清空来实现创建空白文档
'''
#generate blank document with original Styles and Settings
dNew = Document(fileName)
dNew._body.clear_content()
#define tool variable
headStr = []
tempList = []
index = 0
end = 0
#loop all paragraphs
for p in document.paragraphs:
'''
我们希望能够不遗漏任何东西,完成全部迁移
由于在docx中,标题级别的段落和正文级别的段落在xml格式中都
属于paragraph,不存在包含关系,只存在Style的区别,于是我们
寻找style为Heading 1的段落,并将此时的序号index作为该标题
的起始序号,并将上一个标题的终止序号设置为index - 1
将这三个元素保存到tempList中,
再将tempList保存袋headStr中,通过str_list_sort实现标题排序
'''
if p.style.name == 'Heading 1':
endindex = index - 1
end = max(end, endindex + 1)
if len(tempList) > 0:
tempList.append(endindex)
headStr.append(tempList)
tempList = []
tempList.append(p.text)
tempList.append(index)
index = index + 1
headStr = str_list_sort(headStr)
'''
根据排完序的二维列表生成新的docx文档
'''
for i in range(0, len(headStr)):
insertParagraph(headStr[i][1], headStr[i][2], fileName, dNew, headStr[0][1])
#insertParagraph(end, len(document.paragraphs), document, dNew)
return dNew
至此我们就完成了对一个文档基于标题的重新排序