OPC实践:通过 python-docx 读取 docx 文档

news2024/12/26 13:40:59

概述

本文记录下列命令执行的过程,通过对过程中的关键步骤进行记录,掌握 python-docx 库中 opc 与 parts 模块的源码、以及加深对 OPC 的理解。

import  docx

# fp 为 docx 文件路径, docx 包含一个 hello 字符串、一张 jepg 图片及一个表格。
document = docx.api.Document(fp)  

上述命令执行的大体流程如下图所示:

读取docx文件流程图

 

docx.package.Package.open() 第一步详解

pkg_reader = PackageReader.from_file(pkg_file)

docx.opc.pkgreader.PackageReader 部分源码如下所示:

class PackageReader(object):
    """
    Provides access to the contents of a zip-format OPC package via its
    :attr:`serialized_parts` and :attr:`pkg_srels` attributes.
    """
    def __init__(self, content_types, pkg_srels, sparts):
        super(PackageReader, self).__init__()
        self._pkg_srels = pkg_srels  # 存储 package 与主部件之间的关系
        self._sparts = sparts  # 存储“序列化”的 parts

    @staticmethod
    def from_file(pkg_file):
        """
        Return a |PackageReader| instance loaded with contents of *pkg_file*.
        """
        phys_reader = PhysPkgReader(pkg_file)  # docx 文件遵循 zip 标准, 因此使用 docx.opc.phys_pkg._ZipPkgReader 读取类
        content_types = _ContentTypeMap.from_xml(phys_reader.content_types_xml)  # 取出 [Content_Types].xml 内容, 并使用lxml.etree 解析,构建 part 与媒体类型之间的映射关系
        pkg_srels = PackageReader._srels_for(phys_reader, PACKAGE_URI)  # 取出 _rels.rels.xml 内容,得到“序列化”的 Relationships,这些关系记录了 package 与主部件之间的关系。
        sparts = PackageReader._load_serialized_parts(
            phys_reader, pkg_srels, content_types
        )
        phys_reader.close()  # docx.opc.phys_pkg._ZipPkgReader 释放资源
        return PackageReader(content_types, pkg_srels, sparts)  # 构建 PackageReader, 将 package 与主部件之间的“序列化”关系、以及“序列化” parts 存储在实例属性中,便于后续构建 Package 是访问。 

其中比较重要的部分就是创建“序列化” parts 的部分,PackageReader._load_serialized_parts 的源码如下所示:

    @staticmethod
    def _load_serialized_parts(phys_reader, pkg_srels, content_types):
        """
        Return a list of |_SerializedPart| instances corresponding to the
        parts in *phys_reader* accessible by walking the relationship graph
        starting with *pkg_srels*.
        """
        sparts = []
        part_walker = PackageReader._walk_phys_parts(phys_reader, pkg_srels)  # 返回一个生成器
        for partname, blob, reltype, srels in part_walker:
            content_type = content_types[partname]  # 此处就用到了之前构建的“part name” 与 “part content_type” 之间的映射关系
            spart = _SerializedPart(
                partname, content_type, reltype, blob, srels
            )
            sparts.append(spart)
        return tuple(sparts)


@staticmethod
    def _walk_phys_parts(phys_reader, srels, visited_partnames=None):
        """
        Generate a 4-tuple `(partname, blob, reltype, srels)` for each of the
        parts in *phys_reader* by walking the relationship graph rooted at
        srels.
        """
        if visited_partnames is None:
            visited_partnames = []
        for srel in srels:
            if srel.is_external:
                continue
            partname = srel.target_partname
            if partname in visited_partnames:
                continue
            visited_partnames.append(partname)
            reltype = srel.reltype
            part_srels = PackageReader._srels_for(phys_reader, partname)  # 得到是是 part 的引用关系列表
            blob = phys_reader.blob_for(partname)
            yield (partname, blob, reltype, part_srels)
            next_walker = PackageReader._walk_phys_parts(
                phys_reader, part_srels, visited_partnames
            )
            for partname, blob, reltype, srels in next_walker:
                yield (partname, blob, reltype, srels)

在创建“序列化” parts 时,采用了深度优先搜索的策略,初次调用 PackageReader._walk_phys_parts 方法时,第二个位置参数传入的实参值是 pkg_srels, 其表示“ package 与 主部件之间的引用关系”。在迭代子节点时, 第二个位置参数传入的便是 part 的引用关系 part_srels

这里分别举两个例子进一步阐释,当 pkg_rels 中的关系对应的是 “_rels/.rels.xml” 中的下列项时:

<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>

此时 Relationship 两端中的 source 便是 package, 而 target 便是 “docProps/core.xml”, 当调用 PackageReader._srels_for(phys_reader, "docProps/core.xml") ,返回的 part_srels 并不包含任何引用关系,因为 “docProps/core.xml” 存储的是文档属性值,并不需要再引用其它 part 或者其它外部资源,因此引用关系为空, 即输出 ("docProps/core.xml", blob, "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" , part_srels) 之后,新创建的 next_walker 实质是一个空迭代器,因此会直接跳到 “_rels/.rels.xml” 中的下一项。

而当下一项是:

<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>

由于 “word/document.xml” 存储了 docx 文档的文本信息,因此一般情况都会引用其它 part,比如引用字体信息:

<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>

那么调用 PackageReader._srels_for(phys_reader, "word/document.xml") 得到的 part_srels 就包含其它关系,此时就会进入“以 word/document.xml 为 source part” 的搜索中。

在深度优先策略搜索的策略下,最终得到所有“序列化”的 parts。

Serialized means any target part is referred to via its partname rather than a direct link to an in-memory |Part| object.

 

docx.package.Package.open() 第二步详解

package = cls()

这里需要注意,返回的是 docx.package.Package 实例,而非 docx.opc.package.OpcPackage 实例, 因为上层调用是 Package.open(), 因此 cls 参数实质是指 docx.package.Package。

 

docx.package.Package.open() 第三步详解

Unmarshaller.unmarshal(pkg_reader, package, PartFactory)  # 将 package_reader 中的 pkg_rels 与 spart 编排到 package 中,并对 part 与 package 做后处理, 这里的 part交由PartFactory 管理。

docx.__init__.py 对 docx.opc.part.PartFactory 进行了初始化配置, 指明了不同类型的 part,采用不同的 Part 类来构建。

def part_class_selector(content_type, reltype):
    if reltype == RT.IMAGE:
        return ImagePart
    return None


PartFactory.part_class_selector = part_class_selector
PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart
PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart
PartFactory.part_type_for[CT.WML_FOOTER] = FooterPart
PartFactory.part_type_for[CT.WML_HEADER] = HeaderPart
PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart
PartFactory.part_type_for[CT.WML_SETTINGS] = SettingsPart
PartFactory.part_type_for[CT.WML_STYLES] = StylesPart
class Unmarshaller(object):
    """Hosts static methods for unmarshalling a package from a |PackageReader|."""

    @staticmethod
    def unmarshal(pkg_reader, package, part_factory):
        """
        Construct graph of parts and realized relationships based on the
        contents of *pkg_reader*, delegating construction of each part to
        *part_factory*. Package relationships are added to *pkg*.
        """
        parts = Unmarshaller._unmarshal_parts(
            pkg_reader, package, part_factory
        )  # 编排 parts
        Unmarshaller_unmarshal_relationships(pkg_reader, package, parts)  # 编排关系
        for part in parts.values():
            part.after_unmarshal()  # 对 part 进行后处理
        package.after_unmarshal()  # 对 package 进行后处理

将 XML 文件解析为 ElementTree 发生在此处, 以编排 “/docProps/core.xml” part 为例,编排首先会从 PackageReader 中取出“序列化” 的 part —— PackageReader 的实例属性中存储了 spart 与 pkg_srels。 序列化的 part 中包含 “/docProps/core.xml” partname、content_type、reltype、blob 值,根据 content_type 值选择对应的 CorePropertiesPart 类,然后先将 blob 解析成 ElementTree,注意 CorePropertiesPart.load() 中使用的 parse_xml 函数是 docx.oxml.init.py 中定义的, 并且将 CT_Coreproperties 类注册到 “http://schemas.openxmlformats.org/package/2006/metadata/core-properties” 命名空间下, 最后 CT_CoreProperties 元素会封装在 CorePropertiesPart 中。

另外对于读写文档的核心属性 docx.document.Document().core_properties, 遵从以下的操作顺序“Document -> DocumentPart -> Package -> CorePropertiesPart -> CT_Coreproperties -> docx.opc.coreprops.CoreProperties ”, docx.opc.coreprops.CoreProperties 会封装 CT_Coreproperties, 并对外提供读写接口。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/172985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

<Python的列表和元组>——《Python》

目录 1.列表 1.1 列表的概念 1.2 创建列表 1.3 访问下标 1.4 切片操作 1.5 遍历列表元素 1.6 新增元素 1.7 查找元素 1.8 删除元素 1.9 连接列表 2. 元组 1.列表 1.1 列表的概念 编程中, 经常需要使用变量, 来保存/表示数据. 如果代码中需要表示的数据个数比较少,…

初识 Bootstrap(前端开发框架)

初识 Bootstrap&#xff08;前端开发框架&#xff09;参考Bootstrap特点获取目录结构jQuery 与 Popper准备工作包含 jQuery 与 Poppermetabox-sizing基本模板无注释版本注释版本参考 项目描述Bootstrap 官方教程https://getbootstrap.net/docs/getting-started/introduction/百…

字节青训前端笔记 | HTTP 使用指南

本节课介绍 Http 协议的基本定义和特点&#xff0c;在此基础上&#xff0c;对于 Http 协议的发展历程及报文结构展开进一步分析。 从输入字符串到打开网页 输入地址浏览器处理输入信息浏览器发请求到达服务器服务器返回信息浏览器读取响应信息浏览器渲染页面加载完成 什么是…

KVM虚拟化简介 | 初识

目录 1、kvm架构 2、架构解析 3、kvm和qemu的作用 1、kvm架构 2、架构解析 从rhel6开始使用&#xff0c;红帽公司直接把KVM的模块做成了内核的一部分。xen用在rhel6之前的企业版中默认内核不支持&#xff0c;需要重新安装带xen功能的内核KVM 针对运行在x86 硬件上的、驻留在内…

配置 Git 连接 GitHub

文章目录0.安装 Git1.注册 GitHub 账号2.配置 Git 的用户名和邮箱3.为本机生成 SSH 密钥对4.将公钥拷贝到 GitHub 上5.测试0.安装 Git Git 官网链接&#xff1a;https://git-scm.com/ Git 官网下载链接&#xff1a;https://git-scm.com/downloads 1.注册 GitHub 账号 GitHu…

蓝桥杯STM32G431RBT6学习——定时器PWM输出

蓝桥杯STM32G431RBT6学习——定时器PWM输出 前言 PWM波输出作为定时器的一个常用功能&#xff0c;也属于高频的考点。从数据手册的定时器解析可以了解到&#xff08;上篇描述&#xff09;&#xff1a;除了基本定时器&#xff08;TIM6、7&#xff09;外&#xff0c;其他所有定…

全国产网管型工业交换机的几种管理方式

全国产网管型工业交换机按其字面上的意思&#xff0c;一是全国产化&#xff08;工业交换机&#xff09;&#xff0c;就是交换机内部95%以上元器件的国内生产制造&#xff0c;重要的硬件芯片&#xff0c;比如交换机芯片、管理芯片、接口芯片等必须是国内厂商在国内研发、生产、制…

学习记录664@项目管理之项目进度管理

什么是项目进度管理 项目进度管理包括为管理项目按时完成所需的7个过程&#xff0c;具体为: 规划进度管理过程一一制定政策、程序和文档以管理项目进度。定义活动过程一一识别和记录为完成项目可交付成果而需采取的具体行动。排列活动顺序过程一一识别和记录项目活动之间的关…

【Kubernetes 企业项目实战】04、基于 K8s 构建 EFK+logstash+kafka 日志平台(下)

目录 一、安装收集日志组件 Fluentd 二、kibana 可视化展示查询 k8s 容器日志 三、测试 efk 收集生产环境业务 pod 日志 四、基于 EFKlogstashkafka 构建高吞吐量的日志平台 4.1 部署 fluentd 4.2 接入 kafka 4.3 配置 logstash 4.4 启动 logstash 本篇文章所用到的资料…

对象的比较

Java中基本类型间的元素比较&#xff0c;可以直接通过">"、"<"、""等符号判断大小&#xff0c;也可使用compareTo比较大小或者equals判断是否相等&#xff0c;作为引用类型的String类不可以使用">"、"<"比较大小…

2023最新 - 谷歌学术文献Bibtex批量获取脚本

首先&#xff0c;自行解决网络访问问题&#xff0c;保证能访问到谷歌学术&#xff0c;否则下面可免看 第一步&#xff1a;安装 selenium python 安装 selenium pip install selenium 第二步&#xff1a;安装 Chrome 浏览器 http://chorm.com.cn/ 第三步&#xff1a;根据 …

Linux应用基础——控制服务与守护进程

一、systemd基本介绍 1.作用 systemd守护进程管理Linux的启动&#xff0c;一般包括服务启动和服务管理&#xff0c;它可以在系统引导时以及运行中的系统上激活系统资源、服务器守护进程和其他进程 2.守护进程 守护进程是执行各种任务的后台等待或运行的进程&#xff0c;一般…

day18|235. 二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08…

【容器技术——Docker基本使用】

文章目录docker1 概述1.1 是什么1.2 相关资源2 使用2.1 镜像2.1.1 拉取镜像2.2.2 列出镜像2.2.3 删除镜像2.2 容器2.2.1 运行容器2.2.2 查看容器2.2.3 启动和关闭容器2.2.4 删除容器2.3 制作镜像2.4 Docker 仓库2.4.1 注册登录2.4.2 推送镜像2.5 dockerfile2.5.1 构建镜像2.5.2…

关于CIS移植的一些基本概念

1. 摄像头sensor 的原理 定时脉冲生成器会生成clock&#xff0c;用于访问image sensor 阵列中的行&#xff0c;预充电&#xff0c;并且按顺序采样像素阵列中的所有行。在一个行的预充电和采样的时间段里&#xff0c;像素的电荷量会随着曝光时间而逐渐减少。这就是快门结构中的曝…

Python采集周边烤肉店数据,康康哪一家最好吃?

人生苦短&#xff0c;我用Python 这不是天气开始突然大范围降温了吗&#xff1f; 降温就要吃烤肉啊 滋辣滋辣的声音特别好听&#xff5e; 放假吃烤肉真的特别快乐~ 天冷了&#xff0c;逛街…… 天冷了&#xff0c;吃烤肉…… 天冷了&#xff0c;喝奶茶…… 有温度的冬天&a…

p83出现的问题(空指针异常)原因及解决方案

我的GitHub&#xff1a;Powerveil GitHub我的Gitee&#xff1a;Powercs12 (powercs12) - Gitee.com皮卡丘每天学Java相关知识&#xff1a;Mybatis Plus、Spring Security本质原因&#xff1a;我们自己将Article字段的update字段做了自动填充导致自动填充时无法获取当前用户(程…

pytorch Conv2d令人迷惑的几个参数

本篇仅用来记录nn.Conv2d()中容易令人不解的几个参数 1. nn.Conv2d() 的输出维度的计算 相信大家都看过官网给出的计算方式: 直接套公式即可, 但需要注意的是, 最终计算的结果要向下取整. 2. dilation 官方解释为: dilation (int or tuple, optional) – Spacing between k…

贪心 455. 分发饼干

455. 分发饼干 难度简单636 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&…

为什么STM32设置Flash地址0x08000000而不是0x00000000?STM32的启动过程

STM32F103ZE芯片存储空间的地址映射关系图。 在MDK编译程序设置ROM和RAM地址时候发现&#xff1a;    IROM1为片上程序存储器&#xff0c;即片上集成的Flash存储器&#xff0c;对该处理器Flash大小为512KB&#xff0c;即0x80000 地址区间为0x8000000~0x0807FFFF  IRAM1为片…