python如何解析word文件格式(.docx)
python如何解析word文件格式(.docx)
.docx
文件遵从开源的“Office Open XML标准”,这意味着我们能用python的文本操作对它进行操作(实际上PPT和Excel也是)。而且这并不是重复造轮子,因为市面上操作.docx
的库限制性非常强:
python-docx
是开源的,但不支持高级操作,例如增加批注、修订等。spire.doc
支持高级操作,但需要商业许可微软开放了word的api接口,但不是跨平台的。只支持win平台,且学习门槛较高
所以我们从开源的标准入手,用python实现操作word文件的功能。
看下图,把.docx
文件的后缀手动改为.zip
竟然可以直接解压。原来.docx
本质上是一个zip压缩包。
解压后的word文件漏出了他的真实面目。原来.docx
由很多.xml
文件(及其他)组成。注意下图框出的word/document.xml
,他是我们操作word文件的主角(有些高级功能不在其中,比如批注在另外的xml中)。因为其中记录了word文档的文本、字体、段落格式等。.xml
是一个纯文本文件,理论上我们用python可以操作word/document.xml
中定义的任何元素。
解释一下
.xml
格式:你可以粗略的把它理解为.html
。区别在于.html
的标记是预先定义好的,.xml
的标记由架构或文档的作者定义,并且是无限制的。如果你没有接触过的话,我建议百度一下,有助于理解下面的内容。
我举个例子来看一下word/document.xml
的结构,具体的含义写在注释里了:
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> # 注意这行,下文要用到!
<w:body> # body是文档的主体,是个nodelist,一般只有一个元素
<w:p w:rsidR="00F921A6" w:rsidRDefault="00000000"> # p代表paragraph段落
<w:pPr> # pPr是段落属性
<w:numPr>
<w:ilvl w:val="255"/>
<w:numId w:val="0"/>
</w:numPr>
<w:pBdr> # 段落边框
<w:top w:val="none" w:sz="0" w:space="0" w:color="000000"/>
<w:left w:val="none" w:sz="0" w:space="0" w:color="000000"/>
<w:bottom w:val="none" w:sz="0" w:space="7" w:color="000000"/>
<w:right w:val="none" w:sz="0" w:space="0" w:color="000000"/>
</w:pBdr>
<w:widowControl/> # 控制孤行
<w:spacing w:line="560" w:lineRule="exact"/> # 行间距
<w:ind w:firstLineChars="200" w:firstLine="640"/> # 首行缩进
<w:outlineLvl w:val="1"/> # 标题级别
<w:rPr> # rPr是段落内的文本属性
<w:rFonts w:ascii="方正仿宋_GBK" w:eastAsia="方正仿宋_GBK" w:hAnsi="Times New Roman"/>
<w:kern w:val="0"/>
<w:sz w:val="32"/>
<w:szCs w:val="32"/>
</w:rPr>
</w:pPr>
<w:r> # r代表run,可以理解为连续的文本块
<w:rPr> # rPr是文本属性
<w:rFonts w:ascii="方正楷体_GBK" w:eastAsia="方正楷体_GBK" w:hAnsi="Times New Roman" w:hint="eastAsia"/> # 字体
<w:kern w:val="0"/> # 字间距
<w:sz w:val="32"/> # 字号
<w:szCs w:val="32"/> # 字号?不知道
</w:rPr>
<w:t>这是一段word中的文本</w:t> # t是文本
</w:r>
</w:p>
</w:body>
</w:document>
注意看上面xml的第一行,xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
定义了document
及其子元素的命名空间。xmlns
用来声明属性,比如这里xmlns:w
代表绑定了w
为前缀,那么子元素中所有前缀为w
的都绑定到命名空间"http://schemas.openxmlformats.org/wordprocessingml/2006/main"
。为什么要着重讲命名空间呢?因为docx的前缀不是统一的,比如Microsoft Word一般用w
,但是wps就不用w
了,记得在操作之前先确定命名空间。
以上只是举个列子,实际上各种标记种类和用法非常多。具体的标记用法参考OOXML标准,你可以在下图画红框的地方查找(仅适用于Microsoft Word)。
OOXML标准的中文翻译:https://hellowac.github.io/ecma-376-zh-cn/
python-docx
对word的基础操作支持的不错,所以我们只需要自己开发高级功能部分,如批注、修订等。并且我建议大家可以在 python-docx
的基础上开发,这样省时省力。
下面是批注功能的实现,其他功能的开发也大同小异。
# 代码来源于网络,我稍做修改并添加注释。
from datetime import datetime
from typing import List
from xml.etree.ElementTree import Element, tostring
from docx import Document
from docx.opc.part import Part
from docx.opc.constants import RELATIONSHIP_TYPE, CONTENT_TYPE
from docx.opc.oxml import parse_xml
from docx.opc.packuri import PackURI
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
# 命名空间
_COMMENTS_PART_DEFAULT_XML_BYTES = (
b"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
<w:comments
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"
xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart"
xmlns:lc="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"
xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml"
xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"
xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
xmlns:cr="http://schemas.microsoft.com/office/comments/2020/reactions">
</w:comments>
"""
).strip()
def add_comment_to_elements_in_place(
docx_doc: Document,
elements: List[Element],
comment_text: str,
author: str="AI助手",
) -> None:
'''给段落添加批注'''
if not elements:
return
# 尝试获取已有的批注,如果不存在的话就新建一个comments.xml
try:
comments_part = docx_doc.part.part_related_by(
RELATIONSHIP_TYPE.COMMENTS
)
except KeyError:
comments_part = Part(
partname=PackURI("/word/comments.xml"),
content_type=CONTENT_TYPE.WML_COMMENTS,
blob=_COMMENTS_PART_DEFAULT_XML_BYTES,
package=docx_doc.part.package,
)
docx_doc.part.relate_to(comments_part, RELATIONSHIP_TYPE.COMMENTS)
comments_xml = parse_xml(comments_part.blob)
# 创建批注信息(id/author/date)
comment_id = str(len(comments_xml.findall(qn("w:comment"))))
comment_element = OxmlElement("w:comment")
comment_element.set(qn("w:id"), comment_id)
comment_element.set(qn("w:author"), author)
comment_element.set(qn("w:date"), datetime.now().isoformat())
# 创建批注文本元素
comment_paragraph = OxmlElement("w:p")
comment_run = OxmlElement("w:r")
comment_text_element = OxmlElement("w:t")
comment_text_element.text = comment_text
comment_run.append(comment_text_element)
comment_paragraph.append(comment_run)
comment_element.append(comment_paragraph)
comments_xml.append(comment_element)
comments_part._blob = tostring(comments_xml)
# 创建批注范围开始和结束元素,分别放在开头和结尾
comment_range_start = OxmlElement("w:commentRangeStart")
comment_range_start.set(qn("w:id"), comment_id)
comment_range_end = OxmlElement("w:commentRangeEnd")
comment_range_end.set(qn("w:id"), comment_id)
elements[0].insert(0, comment_range_start)
elements[-1].append(comment_range_end)
# 为每个元素添加批注引用
comment_reference = OxmlElement("w:r")
comment_reference_run = OxmlElement("w:r")
comment_reference_run_properties = OxmlElement("w:rPr")
comment_reference_run_properties.append(
OxmlElement("w:rStyle", {qn("w:val"): "CommentReference"})
)
comment_reference_run.append(comment_reference_run_properties)
comment_reference_element = OxmlElement("w:commentReference")
comment_reference_element.set(qn("w:id"), comment_id)
comment_reference_run.append(comment_reference_element)
comment_reference.append(comment_reference_run)
elements[0].append(comment_reference)
作者:唐BiuBiu