元数据:XMP 包构建与流式读取
元数据模块是引擎的 XMP 层。它构建 PDF 以元数据流形式携带的 XMP 包,读取既有包而无需将整份文档载入内存,并输出引擎审计轨迹的 XMP 扩展。
composer require nextpdf/core:^3概念总览
标题为“概念总览”的章节PDF 会通过附加在文档目录上的元数据流中的 XMP 包,承载文档级元数据 —— ISO 32000-2 §14.3。本模块负责生成和消费该包。它刻意保持接口小而聚焦:NextPDF\Metadata\Xmp 之下的三个类。
XmpMetadataBuilder 生成该包。它将一组属性集合序列化为格式正确的 XMP 文档,并包裹在标准的 <?xpacket?> 处理指令之中。它使用 XMP 规范所固定的规范封包 GUID 与字节序标记。输出是 Writer 嵌入为元数据流的字节字符串 —— 即 §14.3 所描述的 PDF 内 XMP 表示形式。
XmpStreamReader 消费包。它面向恶意输入设计。解析前,来源会以 64 KB 为单位流式写入有界的临时文件。写入期间会强制施加总字节上限。libxml 的实体加载器会在解析期间被设为 null,并在解析后还原。DOCTYPE 会被直接拒绝。它提供 iterateProperties(),这是一个生成器,会针对每个叶节点元素产出 (namespaceUri, localName, textContent) 元组,而不会将整棵树物化 —— 任何时刻解析器中都只有当前元素及其文本节点存活。过大的包会引发 PacketTooLargeException;格式错误的 XML、DOCTYPE 或非 UTF-8 输入则会引发 InvalidConfigException。
XmpAuditFieldEmitter 是引擎专属的扩展。它将 AuditReport 渲染成 nextpdfAudit 命名空间下的自定义 XMP 字段,因此文档一致性审计会以符合标准的 XMP 形式随文件一同流通,而非以附属文件形式存在。它所渲染的 AuditReport 并非由发射器本身产生:只有当渲染在 CssRenderingMode::Audit 之下执行,并搭配调用方提供的 auditCollector(通过 Config(auditCollector: ...) 配置)时,才会启用此项充实处理。收集器由调用方驱动 —— 调用方喂入数据,发射器则渲染它所收集到的内容。它比核心 XMP 接口更新(@since 5.4.0)。构建器与读取器为 @since 2.0.0。
API 接口
标题为“API 接口”的章节| 类 | 主要成员 | 角色 |
|---|---|---|
XmpMetadataBuilder | build(): string、XPACKET_GUID、XPACKET_BOM | 将一组属性集合序列化为 XMP 包(@since 2.0.0) |
XmpStreamReader | iterateProperties(mixed $source, int $byteCap = DEFAULT_BYTE_CAP): \Generator、DEFAULT_BYTE_CAP | 有界、流式、拒绝 DOCTYPE 的 XMP 读取器(@since 2.0.0) |
PacketTooLargeException | 继承 NextPdfException | 当 XMP 包超过字节上限时引发(@since 2.0.0) |
XmpAuditFieldEmitter | render(?AuditReport $report): string、NAMESPACE_URI | 将审计轨迹渲染为自定义 XMP 字段(@since 5.4.0) |
运行 composer docs:generate-api-php -- --module=Metadata 可查看完整的 PHPDoc 表格。
代码示例 —— 快速上手
标题为“代码示例 —— 快速上手”的章节在明确的字节上限内,从既有 XMP 包中流式提取属性。
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Metadata\Xmp\XmpStreamReader;
$reader = new XmpStreamReader();
foreach ($reader->iterateProperties(file_get_contents('/srv/in/xmp.xml'), byteCap: 1_048_576) as [$ns, $name, $value]) { printf("%s:%s = %s\n", $ns, $name, $value);}代码示例 —— 生产环境
标题为“代码示例 —— 生产环境”的章节以防御性方式读取包,将模块的类型化失败映射为应用级结果,避免原始解析器错误逸出。
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Exception\InvalidConfigException;use NextPDF\Metadata\Xmp\PacketTooLargeException;use NextPDF\Metadata\Xmp\XmpStreamReader;use Psr\Log\LoggerInterface;
final readonly class XmpIngestService{ public function __construct( private XmpStreamReader $reader, private LoggerInterface $logger, ) {}
/** * @param resource|string $source A stream resource or XMP byte string. * * @return array<string, string> Flattened "ns:name" => value map. */ public function ingest(mixed $source): array { $properties = [];
try { // Cap untrusted XMP at 4 MB regardless of the 1 GiB default. foreach ($this->reader->iterateProperties($source, byteCap: 4_194_304) as [$ns, $name, $value]) { $properties["{$ns}:{$name}"] = $value; } } catch (PacketTooLargeException $e) { $this->logger->warning('XMP packet exceeded ingest cap; rejected.', ['error' => $e->getMessage()]);
return []; } catch (InvalidConfigException $e) { $this->logger->warning('XMP packet malformed or unsafe; rejected.', ['error' => $e->getMessage()]);
return []; }
return $properties; }}边界情况与陷阱
标题为“边界情况与陷阱”的章节XmpStreamReader会直接拒绝任何 DOCTYPE。这是一项 XXE 防御,而非验证便利措施 —— 需要 DOCTYPE 的包不会被接受;请在上游先行清理。- 字节上限默认为 1 GiB(
DEFAULT_BYTE_CAP)。该默认值是上限,而非建议值。对于不受信任的输入,请传入较紧的byteCap。 iterateProperties()是一个生成器。只能消费一次;再次迭代不会重放。- 读取器会在解析期间将 libxml 的实体加载器设为 null,并于之后还原。请勿在同一个请求中,将它与其他依赖实体加载器的 libxml 解析作业并行执行。
XmpAuditFieldEmitter::render(null)是有效的,并会产出空白渲染结果;null 的AuditReport代表“无审计”,而非错误。
构建器的资源耗用与属性数量呈线性关系。读取器的内存用量主要由最长的单个文本段决定,而非文档大小,因为解析器中只有当前元素存活 —— 大型包会被流式处理,而非整体载入。默认参考工作负载落在 1500 ms 挂钟时间/64 MB 峰值的预算之内。其可复现性配置文件为 structural:XMP 包会记录修改时间戳。相同逻辑元数据的两次构建,会在这些字段上有所差异,但结构相同。
安全注意事项
标题为“安全注意事项”的章节XmpStreamReader 是解析不受信任 XML 的解析器,并据此进行加固。搭配强制字节上限的流式分块,可限制内存放大型拒绝服务攻击。DOCTYPE 会被拒绝,以封闭 XXE。LIBXML_NONET 会阻止网络实体解析。非 UTF-8 的输入会被拒绝。对于任何来自外部的包,仍应设置适合部署环境的 byteCap,而非依赖 GB 级别的默认值。当 XMP 属性值重新进入应用程序时,请将其视为不受信任的字符串。请参阅 /modules/core/security/ 中的引擎威胁模型。
一致性
标题为“一致性”的章节XmpMetadataBuilder 所产生的包,是 ISO 32000-2 §14.3 () 所定义的 PDF 内 XMP 元数据流表示形式。XMP 序列化形式本身则由 XMP 规范(ISO 16684-1)所规范,而该规范并不在可验证的引用语料库中。该需求是以编号引用,而非以区块固定。这些是由 src/Metadata/Xmp/ 所产生、并由 tests/Unit/Metadata/Xmp/ 所验证的实现事实。针对某个配置文件(PDF/A、PDF/UA)的端到端元数据一致性,是由 /modules/core/conformance/ 中所描述的 oracle 与黄金测试套件来验证。
另请参阅
标题为“另请参阅”的章节- Document 模块 —— DPM 配套的 DPart 树。
- Audit 模块 —— 产生发射器所渲染的
AuditReport。 - Writer 模块 —— 将包嵌入为元数据流。
- 一致性总览
- 引擎安全模型