pypdf XmpInformation 类完全指南:读写 PDF 的 XMP 元数据
2026/9/15 20:07:46 网站建设 项目流程

pypdf XmpInformation 类完全指南:读写 PDF 的 XMP 元数据

【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf

导读

PDF 文件除了页面内容之外,还携带一套结构化的元数据——XMP(Extensible Metadata Platform,可扩展元数据平台)。pypdf 通过XmpInformation类把这份隐藏在 PDF 根对象/Metadata流中的 XML 数据,包装成一系列可读可写的 Python 属性。读完本文,你将掌握:如何从PdfReader/PdfWriter中获取 XMP 元数据、如何读取和修改 Dublin Core(dc:*)、Adobe XMP(xmp:*)、PDF(pdf:*)、XMPMM(xmpMM:*)以及 PDF/A(pdfaid:*)各命名空间的字段、如何读取自定义元数据属性,并理解 pypdf 在解析 XMP 时的安全防护与底层实现机制。

本文以 pypdf 官方 API 文档 docs/modules/XmpInformation.rst 中定义的XmpInformation类为骨架展开,结合 pypdf/xmp.py 的完整源码与 tests/test_xmp.py 的测试用例进行纵深讲解。

XmpInformation 是什么

XmpInformation是 pypdf 中专门表示 XMP 元数据的类,定义于 pypdf/xmp.py,它同时继承自XmpInformationProtocol(协议约束)与PdfObject(PDF 对象基类)。

在 PDF 文件内部,XMP 元数据以一个类型为/Metadata、子类型为/Subtype /XML的流对象形式挂在文档目录(root)上。XmpInformation将这段 XML 流解析成 DOM 树(self.rdf_root),再通过一系列属性把 RDF 三元组映射成易用的 Python 数据结构。

构造时机上,通常不需要直接实例化它,而是通过三个入口间接获取:

  • PdfReader.xmp_metadata——只读访问,见 pypdf/_reader.py;
  • PdfWriter.xmp_metadata——可读可写,见 pypdf/_writer.py;
  • DictionaryObject.xmp_metadata——底层实现,从根对象字典中读取/Metadata键,见 pypdf/generic/_data_structures.py。
from pypdf import PdfReader reader = PdfReader("document.pdf") xmp = reader.xmp_metadata # 无 XMP 元数据时返回 None if xmp is not None: print(xmp.dc_title)

类文档中明确声明:若 XML 无效,构造时会抛出PdfReadError。这一点在 pypdf/xmp.py 的实现中得到印证——解析失败(AttributeErrorExpatError)会被统一包装为PdfReadError,缺少rdf:RDF根元素同样会报错。

支持的命名空间与常量

XmpInformation内部维护了一张命名空间 URI 到前缀的映射表(pypdf/xmp.py),所有属性都围绕这些命名空间展开:

常量命名空间 URI前缀对应属性组
RDF_NAMESPACEhttp://www.w3.org/1999/02/22-rdf-syntax-ns#rdf容器结构(Bag/Seq/Alt)
DC_NAMESPACEhttp://purl.org/dc/elements/1.1/dcdc_*系列
XMP_NAMESPACEhttp://ns.adobe.com/xap/1.0/xmpxmp_*系列
PDF_NAMESPACEhttp://ns.adobe.com/pdf/1.3/pdfpdf_*系列
XMPMM_NAMESPACEhttp://ns.adobe.com/xap/1.0/mm/xmpMMxmpmm_*系列
PDFAID_NAMESPACEhttp://www.aiim.org/pdfa/ns/id/pdfaidpdfaid_*系列
PDFX_NAMESPACEhttp://ns.adobe.com/pdfx/1.3/pdfxcustom_properties

其中PDFX_NAMESPACE是 Adobe 官方文档中专门用于存放"自定义元数据"(custom metadata)的命名空间:元素名即键名,内容即值;当键中包含非法 XML 标识符字符时,会用\u2182(ROMAN NUMERAL TEN THOUSAND)加 4 位 Unicode 十六进制编码进行转义,例如键"my car"会变成my\u21820020car。源码注释明确建议:除非必须兼容旧文件,否则应避免使用 pdfx 命名空间,优先采用自定义 schema 和语义明确的 XML 元素(pypdf/xmp.py)。

Dublin Core 元数据:dc_*属性

Dublin Core(都柏林核心)是 XMP 中最常用的一组元数据。XmpInformation为每个dc:*元素提供了成对的 getter/setter 属性。按其数据类型可划分为四类:

单值属性(rdf:Description直接子元素)

  • dc_coverage:资源覆盖范围或范围的文字描述
  • dc_format:资源的 MIME 类型
  • dc_identifier:资源的唯一标识符
  • dc_source:当前资源派生自的原始资源的唯一标识符

无序数组(rdf:Bag

  • dc_contributor:除作者外的其他贡献者
  • dc_language:资源使用的语言
  • dc_publisher:发布者名称
  • dc_relation:与其他文档关系的文字描述
  • dc_subject:描述资源主题的关键词/短语
  • dc_type:文档类型的文字描述

有序数组(rdf:Seq

  • dc_creator:按优先级排序的作者姓名数组
  • dc_date:对资源有意义的日期数组,统一以 UTC 时区的datetime.datetime返回

多语言备选(rdf:Alt,按语言键控的字典)

  • dc_title:资源标题,键为语言代码(如"x-default""en"
  • dc_description:资源内容的多语言文字描述
  • dc_rights:用户对资源所拥有权利的多语言文字描述

dc_creator为例(pypdf/xmp.py):

# 读取 xmp.dc_creator # ['John Doe'] # 写入 xmp.dc_creator = ["Alice", "Bob"]

读取时,底层通过_get_seq_valuesrdf:Seq容器中提取rdf:li项;该函数还包含一个重要的容错分支(pypdf/xmp.py):部分应用程序违反 XMP 标准,把本应是rdf:Seq(有序数组)的dc:creator写成了rdf:Bag(无序数组),pypdf 会将其作为回退方案一并接受,对应测试见 tests/test_xmp.py 的test_dc_creator__bag_instead_of_seq

dc_date的读取还会经过_converter_date转换(pypdf/xmp.py),它用正则解析 ISO 8601 日期字符串,支持YYYYYYYY-MMYYYY-MM-DD、带时间与毫秒、以及Z±HH:MM时区偏移的完整形式;时区偏移会被换算为 UTC。写入时_format_datetime_utc会把带时区的datetime先转成 UTC,再格式化为形如2023-12-25T10:30:45.000000Z的字符串(pypdf/xmp.py)。

PDF 命名空间元数据:pdf_*属性

对应pdf:*元素,全部为单值字符串属性:

  • pdf_keywords:文档关键词(对应pdf:Keywords
  • pdf_pdfversion:PDF 文件版本,如1.01.3(对应pdf:PDFVersion
  • pdf_producer:生成该 PDF 的软件名称(对应pdf:Producer

这些字段与PdfReader.metadata(文档信息字典/Info)中的传统键存在对应关系,但存储位置不同:部分 PDF 使用 XMP 元数据流而非文档信息字典metadata属性不会读取这些流,必须通过xmp_metadata访问(pypdf/_writer.py 中的 docstring 明确说明了这一点)。

Adobe XMP 命名空间元数据:xmp_*属性

对应xmp:*元素,全部为单值属性:

  • xmp_create_date:资源最初创建的日期时间,返回 UTCdatetime对象xmp:CreateDate
  • xmp_modify_date:资源最后修改的日期时间(xmp:ModifyDate
  • xmp_metadata_date:元数据本身最近一次变更的日期时间(xmp:MetadataDate
  • xmp_creator_tool:创建资源时首次使用的工具名称(xmp:CreatorTool

这三个日期属性的 setter 都接受datetime对象(内部自动格式化为 UTC 字符串)或None(删除该字段),对应实现见 pypdf/xmp.py。

XMPMM 命名空间元数据:xmpmm_*属性

XMP Media Management(媒体管理)命名空间用于文档版本与衍生关系的追踪:

  • xmpmm_document_id:该资源所有版本与衍生形式的公共标识符(xmpMM:DocumentID
  • xmpmm_instance_id:文档某一特定实例的标识符,每次文件保存都会更新xmpMM:InstanceID

典型取值形如uuid:ca96e032-c2af-49bd-a71c-95889bafbf1d,测试用例见 tests/test_xmp.py。

PDF/A 合规元数据:pdfaid_*属性

对应pdfaid:*元素,用于声明文档符合的 PDF/A 归档标准:

  • pdfaid_part:符合的 PDF/A 标准部分(如123
  • pdfaid_conformance:符合级别(如ABU

测试用例 tests/test_xmp.py 验证了带 PDF/A 元数据的文件(021-pdfa/crazyones-pdfa.pdf)能正确读出pdfaid_part == "1"pdfaid_conformance == "B",而无 PDF/A 元数据的文件则返回None。pypdf 的 PDF/A 合规性分析还依赖该模块,相关文档可参考 docs/user/pdfa-compliance.md。

自定义属性:custom_properties

custom_properties属性(pypdf/xmp.py)返回一个字典,包含 pdfx 命名空间下所有自定义元数据键值对。读取时会自动把\u2182转义序列还原为原始字符(只对格式合法的转义做还原,遇到格式异常会跳过而非崩溃):

xmp.custom_properties # 例如:{'Style': 'FooBarStyle', 'other': 'worlds', '⏰': 'time'}

该属性是惰性计算的——首次访问时缓存到_custom_properties,后续直接返回。测试用例 tests/test_xmp.py 和样本测试 tests/test_xmp.py 均验证了这一行为。

底层解析机制与缓存

安全的 XML 解析器_XmpBuilder

XmpInformation使用自定义的_XmpBuilder(继承自ExpatBuilderNS,pypdf/xmp.py)解析 XMP 流,它做了两件事:

  1. 拒绝一切实体声明custom_entity_declaration_handler直接抛出ExpatError,从根源上阻断 XXE(外部实体注入)攻击。Python 标准库的 libexpat 默认限制只能拦截指数级实体膨胀,无法拦截二次方级膨胀造成的巨大内存占用,因此 pypdf 自己实现了这一层防护;
  2. 限制元素总数start_element_handler每解析一个元素就计数,超过xmp_maximum_element_count(默认 100,000)即抛LimitReachedError

对应测试覆盖了外部实体、指数膨胀、超大流和超量元素四种攻击场景,见 tests/test_xmp.py。

元素查找与缓存

解析后的 DOM 树保存在self.rdf_root中,get_element(about_uri, namespace, name)get_nodes_in_namespace(about_uri, namespace)提供了面向命名空间的通用查询接口(pypdf/xmp.py),可供需要读取非标准命名空间(如 TIFF)的进阶用法使用——测试中的get_all_tiff正是利用get_nodes_in_namespace读取http://ns.adobe.com/tiff/1.0/命名空间的例子(tests/test_xmp.py)。

所有 getter 都经过self.cache字典做结果缓存(以命名空间和元素名为键),重复读取同一属性不会重新遍历 DOM。

四种取值策略

属性 getter 底层对应四种取值函数,理解了它们就理解了所有属性返回类型的由来:

函数容器类型返回值
_get_single_value直接子元素/属性str或转换后的单值
_getter_bagrdf:Baglist[str](无序)
_get_seq_valuesrdf:Seq(回退rdf:Baglist(有序)
_get_langalt_valuesrdf:Altdict(键为xml:lang

_get_single_value同时兼容属性节点(attribute)与元素节点(element)两种 XMP 写法——测试样本中dc:source就是以属性形式存储的(tests/test_xmp.py)。

创建、修改与写回

从零创建:XmpInformation.create()

类方法create()(pypdf/xmp.py)基于模块内预定义的_MINIMAL_XMP模板(pypdf/xmp.py)创建一个空白的XmpInformation实例,模板已声明全部标准命名空间,x:xmptk标记为pypdf

import pypdf from pypdf import PdfWriter xmp = pypdf.xmp.XmpInformation.create() xmp.dc_title = {"x-default": "我的文档"} xmp.dc_creator = ["作者甲", "作者乙"] xmp.xmp_create_date = datetime.now(timezone.utc) writer = PdfWriter() writer.xmp_metadata = xmp writer.add_blank_page(width=200, height=200) with open("out.pdf", "wb") as f: writer.write(f)

创建后的对象所有字段均为空:dc_title == {}dc_creator == []xmp_create_date is None(tests/test_xmp.py)。

写入机制

每个属性 setter 都会调用对应的_set_*_values函数(如_set_single_value_set_bag_values_set_seq_values_set_langalt_values,见 pypdf/xmp.py),其流程为:清除缓存 → 获取或创建rdf:Description节点(_get_or_create_description)→ 删除同命名空间下旧的同名元素 → 按容器类型构建新的 XML 节点 → 调用_update_stream把 DOM 重新序列化为字节并写回底层流。因此对属性的修改是就地生效的,xmp.stream.get_data()会返回更新后的 XML。

写回 PDF:PdfWriter.xmp_metadata

PdfWriter.xmp_metadata的 setter(pypdf/_writer.py)接受三种值:

  • None:删除根对象中的/Metadata条目;
  • bytes:直接作为新的 XMP 流数据写入;
  • XmpInformation实例:取其内部流的字节数据写入。

写入时会确保/Metadata指向一个间接引用对象(IndirectObject),否则先创建新的StreamObject并注册到 writer。

已弃用的方法

XmpInformation.write_to_stream(stream, encryption_key=None)已被弃用,将在 pypdf 6.0.0 移除,官方建议改用PdfWriter.xmp_metadata(pypdf/xmp.py);其encryption_key参数自 5.0.0 起也不再提供替代方案。测试 tests/test_xmp.py 验证了弃用警告的触发。

资源消耗防护:相关配置项

XMP 解析受到 pypdf 全局配置的约束(定义于 pypdf/_configuration.py):

配置项默认值作用
xmp_maximum_input_length5,000,000(字节)解压后的 XMP 流最大允许长度,超限抛LimitReachedError
xmp_maximum_element_count100,000XMP 数据中允许的最大元素数量

这两个配置项的前身是 pypdf/xmp.py 中的模块级常量XMP_MAX_INPUT_LENGTHXMP_MAX_ELEMENT_COUNT(已标记弃用,迁移映射见 pypdf/_configuration.py)。需要调整时可通过pypdf.config.overwrite_configurationapply_configuration上下文管理器完成,用法可参考 docs/modules/configuration.rst。超限测试见 tests/test_xmp.py。

一个完整的读写示例

结合以上全部能力,下面是一个端到端的实操示例:读取现有 PDF 的 XMP 元数据、增补字段、写回新文件。

from datetime import datetime, timezone from pypdf import PdfReader, PdfWriter # 1. 读取现有元数据 reader = PdfReader("source.pdf") xmp = reader.xmp_metadata if xmp is None: xmp = __import__("pypdf").xmp.XmpInformation.create() # 2. 修改/新增字段(就地生效) xmp.dc_title = {"x-default": "更新后的标题"} xmp.dc_subject = ["pypdf", "XMP", "metadata"] xmp.xmp_modify_date = datetime.now(timezone.utc) xmp.pdf_keywords = "xmp; metadata; pypdf" # 3. 写回新文件 writer = PdfWriter(clone_from=reader) writer.xmp_metadata = xmp with open("output.pdf", "wb") as f: writer.write(f) # 4. 验证 check = PdfReader("output.pdf") assert check.xmp_metadata.dc_title["x-default"] == "更新后的标题"

需要说明的边界情况:如果 PDF 的/Metadata流中不是合法的 XML,PdfReader.xmp_metadata会抛出PdfReadError(测试见 tests/test_xmp.py);pypdf 并不会因此拒绝读取整个 PDF,只是 XMP 元数据不可用。此外,xmp_metadata从根对象读取时不受加密影响——PdfReader在访问时会临时覆盖加密设置(pypdf/_reader.py)。

小结

XmpInformation是 pypdf 中访问 XMP 元数据的统一入口,覆盖了 Dublin Core、Adobe XMP、PDF、XMPMM、PDF/A 五大标准命名空间及 pdfx 自定义属性,同时提供了从零创建、就地修改、写回 PDF 的完整链路。其底层的安全解析器、资源上限配置和命名空间级缓存机制,使其在解析不可信 PDF 时兼具健壮性与效率。更多元数据相关的背景知识可参见 docs/user/metadata.md,类定义的权威 API 清单见 docs/modules/XmpInformation.rst。

【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询