OPC【3】:包内节点

概述

将抽象包看成一个图数据结构,在OPC【2】1中介绍了关系边的表示及功能,但是图中的包内节点对象如何表示呢?本文将介绍python-docx定义Part节点的逻辑,并介绍一个重要文件“[Content_Types].xml”。本文源码源于python-docx==1.10

包内节点

包内节点对象(Part)需要关注Part定义、partname命名规范、以及part与relationships之间的关系。

docx.opc.part.Part

Part是OPC中的重要概念,在docx中定义如下:

class Part:
    """Base class for package parts.

    Provides common properties and methods, but intended to be subclassed in client code
    to implement specific part behaviors.
    """

    def __init__(
        self,
        partname: str,
        content_type: str,
        blob: bytes | None = None,
        package: Package | None = None,
    ):
        super(Part, self).__init__()
        self._partname = partname
        self._content_type = content_type
        self._blob = blob
        self._package = package

    @property
    def blob(self):
        """Contents of this package part as a sequence of bytes.

        May be text or binary. Intended to be overridden by subclasses. Default behavior
        is to return load blob.
        """
        return self._blob
  1. Part是docx内定义的一个基础类,对于物理包内的若干类型的子文件,通常需要使用不同的Part子类去处理子文件包含的字节流数据。例如,在docx中,XmlPart子类就是负责处理所有的xml格式子文件。
  2. Part对象拥有唯一的名称、类型、字节流。

docx.opc.packuri.PackURI

packuri包内统一资源标识符用于管理partname命名规范。

class PackURI(str):
    """Provides access to pack URI components such as the baseURI and the filename
    slice.

    Behaves as |str| otherwise.
    """

    _filename_re = re.compile("([a-zA-Z]+)([1-9][0-9]*)?")

    def __new__(cls, pack_uri_str):
        if pack_uri_str[0] != "/":
            tmpl = "PackURI must begin with slash, got '%s'"
            raise ValueError(tmpl % pack_uri_str)
        return str.__new__(cls, pack_uri_str)

    @property
    def baseURI(self):
        """The base URI of this pack URI, the directory portion, roughly speaking.

        E.g. ``'/ppt/slides'`` for ``'/ppt/slides/slide1.xml'``. For the package pseudo-
        partname '/', baseURI is '/'.
        """
        return posixpath.split(self)[0]
  1. partname必须以“/”起始,“/”表示包的根路径。
  2. baseURI特性存储的是partname的文件夹路径部分,类似于文件系统中的文件夹路径。

节点级别关系边集合管理

Part通过特性“rels”管理节点级别关系边集合:

class Part
	...
    @lazyproperty
    def rels(self):
        """|Relationships| instance holding the relationships for this part."""
        return Relationships(self._partname.baseURI)

包内节点可能拥有节点级别关系边集合、也可能没有。如果拥有,会在包内节点下,创建一个“_rels”文件夹,并创建一个“rels”格式文件记录其拥有的节点级别关系边——所以传入Relationships的参数是self._partname.baseURI。docx.opc.rel.Relationships的定义如下:

class Relationships(Dict[str, "_Relationship"]):
    """Collection object for |_Relationship| instances, having list semantics."""

    def __init__(self, baseURI: str):
        super(Relationships, self).__init__()
        self._baseURI = baseURI
        self._target_parts_by_rId: Dict[str, Any] = {}

    def add_relationship(
        self, reltype: str, target: str | Any, rId: str, is_external: bool = False
    ) -> "_Relationship":
        """Return a newly added |_Relationship| instance."""
        rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
        self[rId] = rel
        if not is_external:
            self._target_parts_by_rId[rId] = target
        return rel

    @property
    def related_parts(self):
        """Dict mapping rIds to target parts for all the internal relationships in the
        collection."""
        return self._target_parts_by_rId
  1. 继承python字典,key存储的是“rId”属性值, value存储的是一条关系边信息。
  2. "_target_parts_by_rId"实例属性存储的是与该节点有引用关系的其它包内节点。

[Content_Types].xml

[Content_Types].xml是物理包内的一个重要子文件,其内记录了包内所有节点的content_type——创建包内节点时必须指明content_type,因此在对物理包进行解包时,会首先处理该文件,后续文章中会仔细介绍。一个简单的[Content_Types].xml 文件内容如下:

    xml = """
 <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="emf" ContentType="image/x-emf"/>
  <Default Extension="jpeg" ContentType="image/jpeg"/>
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
  <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
  <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
  <Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/>
  <Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/>
  <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
  <Override PartName="/word/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
  <Override PartName="/word/webSettings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"/>
  </Types>
    """

文件根节点为 CT_Types 元素,该元素包含若干子元素 CT_Default 与 CT_Override元素 。

Default elements shall define default mappings from the extensions of part names to media types. Override elements shall specify media types on parts that are not covered by, or are not consistent with, the default mappings.For all parts of the package other than Relationships parts, the Media Types stream shall specify either: one matching Default element, or one matching Override element, or both a matching Default element and a matching Override element, in which case, the Override element takes precedence.

There shall not be more than one Default element for any given extension, and there shall not be more than one Override element for any given part name.

  1. CT_Default 元素中的 Extension 值指明“part name 为指定的后缀值时,应该对应什么媒体类型——即怎样处理该种类型的文件”。
  2. CT_Override 元素中的 PartName 指明了 part 在package 中的位置,ContentType 指明了 part 内容的类型以及处理方式。
  3. CT_Override元素指明了CT_Default元素覆盖不了的part节点该如何处理,其优先级高于CT_Default。

对于上文引用部分中提到的 “matching” 是指:

A Default element shall match any part whose name ends with a period (“.”) followed by the value of this attribute. An Override element shall match a part whose name is equal to the value of this attribute.

docx.opc.oxml.CT_Types的源码定义:

class CT_Types(BaseOxmlElement):
    """
    ``<Types>`` element, the container element for Default and Override
    elements in [Content_Types].xml.
    """
    
    @staticmethod
    def new():
        """
        Return a new ``<Types>`` element.
        """
        xml = '<Types xmlns="%s"/>' % nsmap['ct']
        types = parse_xml(xml)
        return types

小结

本文介绍了Part对象的定义、命名规范、对关系边的管理、以及content_type管理,与关系边对象结合,就构成了解析OPC核心概念抽象包的基础。


  1. 【OPC】:关系边 ↩︎

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值