OPC【6】:docx读写文档属性信息

概述

文档属性(core propertities)是docx文档的元信息,读写文档属性是OPC中解包与打包的一个缩影,理解文档属性的读写,可以加深对OPC中解包与打包的原理。另一方面,在docx三方库中,可以将其大致分为四大类:opc子库、oxml子库、proxy class、其它。
docx库组成
opc中定义的类主要与Package、Part、Relationship相关;oxml主要定义CT_为前缀的自定义元素类;proxy class一般封装CT_元素类,对外提供接口。本文结合文档属性读写,对这三种类型的类别定义也加以介绍。本文源码引用自python-docx=1.1.0

解包中的文档属性处理

序列化阶段

在序列化阶段,首先会处理“[Content_Types].xml”文件,收集包内节点的content_type信息。然后会创建序列化的包级别关系边集合,当解析“_rels/.rels”包级别关系文件时,某一关系边属性值Type等于
“http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties”, Target属性值等于“docProps/core.xml”,此关系边表示的是包节点对文档属性节点的引用。

创建序列化包内节点需要指定partname、content_type、reltype、blob、srels,对于文档属性序列化节点:

partname = "/docProps/core.xml"
content_type = "application/vnd.openxmlformats-package.core-properties+xml"  # CT.OPC_CORE_PROPERTIES
reltype = "application/vnd.openxmlformats-package.core-properties+xml"
blob = "..." # zip包内子文件docProps/core.xml的字符串
srels = None  # 文档属性节点不引用其它包内节点或者外部资源

所有输入信息均具备,因此可以创建完序列化的文档属性节点。

数据编排节点

在解包第二阶段,PartFactory根据序列化包内节点的content_type,选择合适的Part或者其子类,将序列化的包内节点转换为Part或者其子类的实例包内节点。在docx的初始化模块中,在PartFactory的part_type_for类属性字典中配置了CT.OPC_CORE_PROPERTIES与CorePropertiesPart的映射关系,因此选择CorePropertiesPart来实例化文档属性序列化包内节点。

from docx.opc.constants.Content_Type as CT
PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart

CorePropertiesPart 定义

CorePropertiesPart 的部分源码如下所示:

class CorePropertiesPart(XmlPart):
    @property
    def core_properties(self):
        return CoreProperties(self.element) 

CorePropertiesPart类的core_prepeties特性,返回的是CoreProperties代理类——定义于docx.opc.coreprops,该代理类初始化时传入的self.element是CT_CoreProperties实例——定义于docx.oxml.coreprops。

XmlPart 定义

CorePropertiesPart 类继承 docx.opc.part.XmlPart, docx.opc.part.XmlPart 继承 ocx.opc.part.Part。

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)

记录了CorePropertiesPart与XmlPart的定义,那CorePropertiesPart是如何实例化的呢?在PartFactory的__new__特殊方法定义如下:

class PartFactory(object):
	...

    def __new__(cls, partname, content_type, reltype, blob, package):
		...
        return PartClass.load(partname, content_type, blob, package)

在文档属性节点编排中,这里的PartClass就是CorePropertiesPart。调用load方法本质是调用XmlPart的load方法:1.第一步执行parse_xml(blob),由于parse_xml使用docx自己配置的oxml_parser,该解析器配置了register_element_cls('cp:coreProperties', CT_CoreProperties),因此函数调用会返回CT_CoreProperties实例对象。2.第二步将partname、content_type、element、package封装进实例属性。

从document_part到core_propetities_part

当解包完成后,如何从docx文档主体包内节点到文档属性包内节点呢?Part或者其子类初始化时为什么需要传入package呢?由于文档属性并未引用其它包内节点,因此需要通过抽象包做为中转站,即从文档主体包内节点->抽象包->文档属性包内节点。从类信息看,路线为DocumentPart->OpcPackage->CorePropertiesPart,即传入package位置参数的值为OpcPackage实例对象。

打包中的文档属性处理

打包文档属性信息相对比较简单。打包流程中依然会首先创建“[Content_types].xml”文件——从包内所有Part或其子类的实例包节点创建。然后将包级别关系写入“_rels/.rels”文件。因为文档属性节点未引用其它包内节点或者外部资源,因此将文档属性节点的信息写入zip包内子文件“docProps/core.xml”即可。

另外,在打包文档属性时,如果对文档属性进行了修改,需注意Part或者其子类的“blob”特性值。如PackageWriter的_write_parts类方法所示:

class PackageWriter(object):
    @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 属性返回值是 self._element元素转为 XML 后的字节流——在文档属性中,就是CT_CoreProperties元素实例, 而 Part.blob 则是初始化时传入的 blob, 并且不支持更改。注意文档延伸属性节点“docProps/app.xml”,默认使用Part类处理,所以修改单位、文档模版等文档属性,变化情况并不会存储到物理包文件中。

另外,当通过Coreproperties代理类修改文档属性时,代理类封装的CT_Coreproperties发生改变,将其转为 xml 作为 blob 属性值并写入文件, 则最终物理包的文档属性存储的是变化后的信息。

代理类Coreproperties修改的是CT_Coreproperties元素,以作者属性信息为例:

class CoreProperties:
    """Corresponds to part named ``/docProps/core.xml``, containing the core document
    properties for this document package."""

    def __init__(self, element):
        self._element = element

    @property
    def author(self):
        return self._element.author_text

    @author.setter
    def author(self, value):
        self._element.author_text = value
  • 初始化传入的实参就是CT_Coreproperties实例。

小结

本文从解包、打包,与docx库功能分类,两个角度对docx格式文档文档属性信息读写进行了解析,以加深对docx与opc的认知。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值