基于 python-docx 读写 App Properties
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
环境依赖
Office Word 2013
python-docx == 0.8.11
概述
python-docx 支持对 docx 文档 core properties 的读写操作——即查看与改写 “/docProps/core.xml” 中的字段值但并未提供对 app properties 的读写 api。 参考 python-docx 源码中 docx.opc.parts.CorePropertiesPart docx.oxml.CT_CorePropertiesdocx.opc.CoreProperties 的源码以及 docx.opc.package.OpcPackage 的 open 与 save 源码 实现对 app properties 的读写操作。
python-docx 实现 core properties 的读写功能
读取 “/docProps/app.xml” 中的字段值相对写操作更为容易由于 docx 遵循 zip 标准因此使用标准库 zipfile 打开 “/docProps/app.xml” 文件然后使用正则匹配就能实现读操作的目标。本部分采用另外一种更简便的方式实现——即模拟 docx.opc.parts.CorePropertiesPart docx.oxml.CT_CoreProperties 以及 docx.opc.CoreProperties 的逻辑。
在 docx.opc.package.OpcPackage.open 编排“序列化” part 时会依据 part 的媒体类型、docx.opc.part.PartFactory 选择合适的 PartClass 来创建一个“序列化”的 part 实例 其中 docx.__init__.py 对 docx.opc.part.PartFactory.part_type_for 字典进行了配置。
from docx.opc.constants.Content_Type as CT
PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart
CorePropertiesPart 的部分源码如下所示
class CorePropertiesPart(XmlPart):
@property
def core_properties(self):
return CoreProperties(self.element) # docx.opc.CoreProperties
CorePropertiesPart 类继承 docx.opc.part.XmlPart docx.opc.part.XmlPart 继承 docx.opc.part.Part。这里的 self.element 本质是 CT_CoreProperties 的实例那么 CT_CoreProperties 是何时实例化的呢—— 在 docx.opc.package.OpcPackage 编排 “/docProps/core.xml” 时
@staticmethod
def _unmarshal_parts(pkg_reader, package, part_factory):
"""
Return a dictionary of |Part| instances unmarshalled from
*pkg_reader*, keyed by partname. Side-effect is that each part in
*pkg_reader* is constructed using *part_factory*.
"""
parts = {}
for partname, content_type, reltype, blob in pkg_reader.iter_sparts():
parts[partname] = part_factory(
partname, content_type, reltype, blob, package
)
return parts
当 partname 为 “/docProps/core.xml” 时 content_type 等于 docx.opc.constants.Content_Type.OPC_CORE_PROPERTIES 这时 PartFactory 会使用 CorePropertiesPart 类来创建实例
class PartFactory(object):
part_class_selector = None
part_type_for = {}
default_part_type = Part
def __new__(cls, partname, content_type, reltype, blob, package):
PartClass = None
if cls.part_class_selector is not None:
part_class_selector = cls_method_fn(cls, 'part_class_selector')
PartClass = part_class_selector(content_type, reltype)
if PartClass is None:
PartClass = cls._part_cls_for(content_type)
return PartClass.load(partname, content_type, blob, package)
即最后一句执行 CorePropertiesPart .load(partname, content_type, blob, package)
注意 XmlPart 与 Part 类 load 方法的差异
class XmlPart(Part):
def __init__(self, partname, content_type, element, package):
super(XmlPart, self).__init__(
partname, content_type, package=package
)
self._element = element
@classmethod
def load(cls, partname, content_type, blob, package):
element = parse_xml(blob)
return cls(partname, content_type, element, package)
XmlPart.load 函数中的 parse_xml 来源于 docx.oxml.__init__.py 在该模块中对 core properties 所在的命名空间注册了 CT_CorePerperties 类
register_element_cls('cp:coreProperties', CT_CoreProperties)
因此 parse_xml(blob)
会返回 CT_CoreProperties 实例元素并且被封装到 CorePropertiesPart._element 实例属性中。
而当使用 python-docx 接口读取 core_properties 时
docx.document.Document().core_properties
会依次进入 docx.parts.document.DocumentPart -> docx.opc.package.OpcPackage -> docx.opc.parts.coreprops.CorePropertiesPart -> doc.opc.coreprops.CorePropertiesdoc.opc.coreprops.CoreProperties 对属性的读写本质是对 doc.oxml.coreprops.CT_CoreProperties 元素中子节点的读写。
对于写操作有一点需要注意
class PackageWriter(object):
@staticmethod
def write(pkg_file, pkg_rels, parts):
"""
Write a physical package (.pptx file) to *pkg_file* containing
*pkg_rels* and *parts* and a content types stream based on the
content types of the parts.
"""
phys_writer = PhysPkgWriter(pkg_file)
PackageWriter._write_content_types_stream(phys_writer, parts)
PackageWriter._write_pkg_rels(phys_writer, pkg_rels)
PackageWriter._write_parts(phys_writer, parts) # 将 part 对象写入文件
phys_writer.close()
@staticmethod
def _write_parts(phys_writer, parts):
"""
Write the blob of each part in *parts* to the package, along with a
rels item for its relationships if and only if it has any.
"""
for part in parts:
phys_writer.write(part.partname, part.blob)
if len(part._rels):
phys_writer.write(part.partname.rels_uri, part._rels.xml)
注意 phys_writer.write(part.partname, part.blob)
第二项参数是 “part.blob” 分别查看 XMLPart 与 Part 的 blob 属性
class XmlPart(Part):
def __init__(self, partname, content_type, element, package):
super(XmlPart, self).__init__(
partname, content_type, package=package
)
self._element = element
@property
def blob(self):
return serialize_part_xml(self._element)
class Part(object):
def __init__(self, partname, content_type, blob=None, package=None):
super(Part, self).__init__()
self._partname = partname
self._content_type = content_type
self._blob = blob
self._package = package
@property
def blob(self):
return self._blob
可以发现 XmlPart 中的 blob 属性返回值是 CT_CoreProperties 元素转为 XML 后的字节流 而 Part.blob 则是初始化时传入的 blob 并且不支持更改。当通过 docx.opc.coreprops.Coreproperties 代理类修改文档 core 属性时 内部的 docx.oxml.coreprops.CT_Coreproperties 发生改变将其转为 xml 作为 blob 属性值并写入文件 则最终文档的 core 属性的变化写入了文件。但是如果是 “/docProps/app.xml” 默认情况下会创建一个 Part 类 对于读操作还好但是写操作发生变化的属性信息无法再赋值回 blob 中因为 Part 类的 blob 属性是一个仅读属性。解决办法的核心就是“使用 docx.opc.part.XmlPart 编排 /docProps/app.xml”。
python-docx 实现 app properties 的读写功能
import docx
from docx.opc.part import PartFactory, XmlPart
from docx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
# 使用 docx.opc.part.XmlPart 来编排 /docProps/app.xml
PartFactory.part_type_for[CT.OFC_EXTENDED_PROPERTIES] = XmlPart
document = docx.api.Document()
app_properties_part = document._part._package.part_related_by(RT.EXTENDED_PROPERTIES)
app_properties_element = app_properties_part._element
# 以 Company 字段值的读写为例
for child in app_properties_element:
if child.tag == "{http://schemas.openxmlformats.org/officeDocument/2006/extended-properties}Company":
print(child.text) # 读取源单位名称
child.text = "123456" # 写入新的单位名称
break
document.save("temporary.docx")
待完成
定义AppPropertiesPart、CT_AppProperties、AppProperties 实现目标功能。
[元素tag 前缀为 ap](https://learn.microsoft.com/zh-cn/dotnet/api/documentformat.openxml.extendedproperties?view=openxml-2.8.1)
命名空间http://schemas.openxmlformats.org/officeDocument/2006/extended-properties
与package的关系类型http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties
媒体类型application/vnd.openxmlformats-officedocument.extended-properties+xml