ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

pypdf XmpInformation 类完全指南:读写 PDF 的 XMP 元数据 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 文件除了页面内容之外还携带一套结构化的元数据——XMPExtensible Metadata Platform可扩展元数据平台。pypdf 通过XmpInformation类把这份隐藏在 PDF 根对象/Metadata流中的 XML 数据包装成一系列可读可写的 Python 属性。读完本文你将掌握如何从PdfReader/PdfWriter中获取 XMP 元数据、如何读取和修改 Dublin Coredc:*、Adobe XMPxmp:*、PDFpdf:*、XMPMMxmpMM:*以及 PDF/Apdfaid:*各命名空间的字段、如何读取自定义元数据属性并理解 pypdf 在解析 XMP 时的安全防护与底层实现机制。本文以 pypdf 官方 API 文档 docs/modules/XmpInformation.rst 中定义的XmpInformation类为骨架展开结合 pypdf/xmp.py 的完整源码与 tests/test_xmp.py 的测试用例进行纵深讲解。XmpInformation 是什么XmpInformation是 pypdf 中专门表示 XMP 元数据的类定义于 pypdf/xmp.py它同时继承自XmpInformationProtocol协议约束与PdfObjectPDF 对象基类。在 PDF 文件内部XMP 元数据以一个类型为/Metadata、子类型为/Subtype /XML的流对象形式挂在文档目录root上。XmpInformation将这段 XML 流解析成 DOM 树self.rdf_root再通过一系列属性把 RDF 三元组映射成易用的 Python 数据结构。构造时机上通常不需要直接实例化它而是通过三个入口间接获取PdfReader.xmp_metadata——只读访问见 pypdf/_reader.pyPdfWriter.xmp_metadata——可读可写见 pypdf/_writer.pyDictionaryObject.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 的实现中得到印证——解析失败AttributeError或ExpatError会被统一包装为PdfReadError缺少rdf:RDF根元素同样会报错。支持的命名空间与常量XmpInformation内部维护了一张命名空间 URI 到前缀的映射表pypdf/xmp.py所有属性都围绕这些命名空间展开常量命名空间 URI前缀对应属性组RDF_NAMESPACEhttp://www.w3.org/1999/02/22-rdf-syntax-ns#rdf容器结构Bag/Seq/AltDC_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 标识符字符时会用\u2182ROMAN 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:Bagdc_contributor除作者外的其他贡献者dc_language资源使用的语言dc_publisher发布者名称dc_relation与其他文档关系的文字描述dc_subject描述资源主题的关键词/短语dc_type文档类型的文字描述有序数组rdf:Seqdc_creator按优先级排序的作者姓名数组dc_date对资源有意义的日期数组统一以 UTC 时区的datetime.datetime返回多语言备选rdf:Alt按语言键控的字典dc_title资源标题键为语言代码如x-default、endc_description资源内容的多语言文字描述dc_rights用户对资源所拥有权利的多语言文字描述以dc_creator为例pypdf/xmp.py# 读取 xmp.dc_creator # [John Doe] # 写入 xmp.dc_creator [Alice, Bob]读取时底层通过_get_seq_values从rdf: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 日期字符串支持YYYY、YYYY-MM、YYYY-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:Keywordspdf_pdfversionPDF 文件版本如1.0、1.3对应pdf:PDFVersionpdf_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:CreateDatexmp_modify_date资源最后修改的日期时间xmp:ModifyDatexmp_metadata_date元数据本身最近一次变更的日期时间xmp:MetadataDatexmp_creator_tool创建资源时首次使用的工具名称xmp:CreatorTool这三个日期属性的 setter 都接受datetime对象内部自动格式化为 UTC 字符串或None删除该字段对应实现见 pypdf/xmp.py。XMPMM 命名空间元数据xmpmm_*属性XMP Media Management媒体管理命名空间用于文档版本与衍生关系的追踪xmpmm_document_id该资源所有版本与衍生形式的公共标识符xmpMM:DocumentIDxmpmm_instance_id文档某一特定实例的标识符每次文件保存都会更新xmpMM:InstanceID典型取值形如uuid:ca96e032-c2af-49bd-a71c-95889bafbf1d测试用例见 tests/test_xmp.py。PDF/A 合规元数据pdfaid_*属性对应pdfaid:*元素用于声明文档符合的 PDF/A 归档标准pdfaid_part符合的 PDF/A 标准部分如1、2、3pdfaid_conformance符合级别如A、B、U测试用例 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_propertiescustom_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 解析器_XmpBuilderXmpInformation使用自定义的_XmpBuilder继承自ExpatBuilderNSpypdf/xmp.py解析 XMP 流它做了两件事拒绝一切实体声明custom_entity_declaration_handler直接抛出ExpatError从根源上阻断 XXE外部实体注入攻击。Python 标准库的 libexpat 默认限制只能拦截指数级实体膨胀无法拦截二次方级膨胀造成的巨大内存占用因此 pypdf 自己实现了这一层防护限制元素总数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标记为pypdfimport 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(width200, height200) with open(out.pdf, wb) as f: writer.write(f)创建后的对象所有字段均为空dc_title {}、dc_creator []、xmp_create_date is Nonetests/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。写回 PDFPdfWriter.xmp_metadataPdfWriter.xmp_metadata的 setterpypdf/_writer.py接受三种值None删除根对象中的/Metadata条目bytes直接作为新的 XMP 流数据写入XmpInformation实例取其内部流的字节数据写入。写入时会确保/Metadata指向一个间接引用对象IndirectObject否则先创建新的StreamObject并注册到 writer。已弃用的方法XmpInformation.write_to_stream(stream, encryption_keyNone)已被弃用将在 pypdf 6.0.0 移除官方建议改用PdfWriter.xmp_metadatapypdf/xmp.py其encryption_key参数自 5.0.0 起也不再提供替代方案。测试 tests/test_xmp.py 验证了弃用警告的触发。资源消耗防护相关配置项XMP 解析受到 pypdf 全局配置的约束定义于 pypdf/_configuration.py配置项默认值作用xmp_maximum_input_length5,000,000字节解压后的 XMP 流最大允许长度超限抛LimitReachedErrorxmp_maximum_element_count100,000XMP 数据中允许的最大元素数量这两个配置项的前身是 pypdf/xmp.py 中的模块级常量XMP_MAX_INPUT_LENGTH、XMP_MAX_ELEMENT_COUNT已标记弃用迁移映射见 pypdf/_configuration.py。需要调整时可通过pypdf.config.overwrite_configuration或apply_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_fromreader) 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流中不是合法的 XMLPdfReader.xmp_metadata会抛出PdfReadError测试见 tests/test_xmp.pypypdf 并不会因此拒绝读取整个 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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进