Zum Inhalt springen

Metadaten: XMP-Paketerstellung und Streaming-Lesen

Das Metadata-Modul bildet die XMP-Schicht der Engine. Es erstellt das XMP-Paket, das ein PDF als Metadaten-Stream mitführt. Vorhandene Pakete liest es, ohne das gesamte Dokument in den Speicher zu laden. Außerdem gibt es die XMP-Erweiterung der Engine für den Audit-Trail aus.

Terminal-Fenster
composer require nextpdf/core:^3

Ein PDF führt Metadaten auf Dokumentebene als XMP-Paket in einem dem Dokumentkatalog zugeordneten Metadaten-Stream mit — ISO 32000-2 §14.3. Dieses Modul ist für die Erzeugung und Verarbeitung dieses Pakets zuständig. Die Oberfläche ist bewusst klein und fokussiert: drei Klassen unter NextPDF\Metadata\Xmp.

Die Klasse XmpMetadataBuilder erzeugt das Paket. Sie serialisiert einen Eigenschaftssatz als wohlgeformtes XMP-Dokument, eingebettet in die standardmäßigen <?xpacket?>-Verarbeitungsanweisungen. Sie verwendet die kanonische Paket-GUID und die Bytereihenfolge-Markierung, die die XMP-Spezifikation festlegt. Die Ausgabe ist die Bytefolge, die der Writer als Metadaten-Stream einbettet — die in §14.3 beschriebene XMP-Darstellung innerhalb des PDF.

Die Klasse XmpStreamReader verarbeitet ein Paket. Sie ist für feindliche Eingaben ausgelegt. Die Quelle wird vor dem Parsen in 64-KB-Blöcken in eine begrenzte temporäre Datei gestreamt. Während des Schreibens erzwingt der Reader eine aggregierte Byte-Obergrenze. Der libxml-Entity-Loader wird für die Dauer des Parsens auf null gesetzt und anschließend wiederhergestellt. Ein DOCTYPE führt zu einer harten Ablehnung. Die Klasse stellt iterateProperties() bereit, einen Generator, der für jedes Blattelement ein (namespaceUri, localName, textContent)-Tupel liefert, ohne den gesamten Baum zu materialisieren — zu jedem Zeitpunkt sind im Parser nur das aktuelle Element und sein Textknoten aktiv. Ein übergroßes Paket löst PacketTooLargeException aus; fehlerhaftes XML, ein DOCTYPE oder eine Nicht-UTF-8-Eingabe löst InvalidConfigException aus.

XmpAuditFieldEmitter ist die Engine-spezifische Erweiterung. Er rendert einen AuditReport in ein benutzerdefiniertes XMP-Feld im Namespace nextpdfAudit, sodass das Konformitäts-Audit eines Dokuments als standardkonformes XMP mit der Datei mitgeführt wird und nicht in einer Beidatei. Der AuditReport, den er rendert, wird nicht vom Emitter selbst erzeugt: Die Anreicherung wird aktiviert, wenn ein Rendering unter CssRenderingMode::Audit mit einem vom Aufrufer bereitgestellten auditCollector läuft, der über Config(auditCollector: ...) konfiguriert wird. Der Collector wird vom Aufrufer gesteuert — der Aufrufer speist ihn, und der Emitter rendert alles, was er gesammelt hat. Der Emitter ist neuer als die Kern-XMP-Oberfläche (@since 5.4.0). Builder und Reader sind @since 2.0.0.

KlasseWichtige MemberRolle
XmpMetadataBuilderbuild(): string, XPACKET_GUID, XPACKET_BOMSerialisiert einen Eigenschaftssatz in ein XMP-Paket (@since 2.0.0)
XmpStreamReaderiterateProperties(mixed $source, int $byteCap = DEFAULT_BYTE_CAP): \Generator, DEFAULT_BYTE_CAPBegrenzter Streaming-XMP-Reader mit DOCTYPE-Ablehnung (@since 2.0.0)
PacketTooLargeExceptionerweitert NextPdfExceptionWird ausgelöst, wenn ein XMP-Paket die Byte-Obergrenze überschreitet (@since 2.0.0)
XmpAuditFieldEmitterrender(?AuditReport $report): string, NAMESPACE_URIRendert den Audit-Trail als benutzerdefiniertes XMP-Feld (@since 5.4.0)

Führen Sie composer docs:generate-api-php -- --module=Metadata aus, um die vollständige PHPDoc-Tabelle zu erhalten.

Streamen Sie die Eigenschaften aus einem vorhandenen XMP-Paket mit einer expliziten Byte-Obergrenze.

<?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);
}

Lesen Sie ein Paket defensiv und ordnen Sie die typisierten Fehler des Moduls einem Ergebnis auf Anwendungsebene zu, statt rohe Parser-Fehler nach außen dringen zu lassen.

<?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 lehnt jeden DOCTYPE strikt ab. Dies ist eine XXE-Abwehr, kein Validierungsdetail — ein Paket, das einen DOCTYPE benötigt, wird nicht akzeptiert; bereinigen Sie es vorgelagert.
  • Die Byte-Obergrenze beträgt standardmäßig 1 GiB (DEFAULT_BYTE_CAP). Dieser Standardwert ist eine Obergrenze, keine Empfehlung. Geben Sie für nicht vertrauenswürdige Eingaben einen knapp bemessenen byteCap an.
  • iterateProperties() ist ein Generator. Verwenden Sie ihn einmal; ein zweiter Durchlauf wiederholt die Werte nicht.
  • Der Reader setzt den libxml-Entity-Loader für das Parsen auf null und stellt ihn anschließend wieder her. Führen Sie den Reader nicht gleichzeitig mit anderem libxml-basiertem Parsen aus, das im selben Request vom Entity-Loader abhängt.
  • XmpAuditFieldEmitter::render(null) ist gültig und liefert ein leeres Rendering; ein auf null gesetzter AuditReport bedeutet „kein Audit“, keinen Fehler.

Der Builder skaliert linear mit der Anzahl der Eigenschaften. Der Speicherbedarf des Readers wird vom längsten einzelnen Textlauf bestimmt, nicht von der Dokumentgröße, weil im Parser nur das aktuelle Element aktiv ist — große Pakete werden gestreamt statt geladen. Die standardmäßige Referenz-Workload bleibt innerhalb des Budgets von 1500 ms Wandzeit / 64 MB Spitzenwert. Das Reproduzierbarkeitsprofil ist structural: Ein XMP-Paket enthält Änderungszeitstempel. Zwei Builds derselben logischen Metadaten unterscheiden sich in diesen Feldern, während die Struktur identisch ist.

XmpStreamReader ist ein Parser für nicht vertrauenswürdiges XML und entsprechend gehärtet. Das blockweise Streaming mit erzwungener Byte-Obergrenze begrenzt Denial-of-Service durch Speicheramplifikation. Ein DOCTYPE wird abgelehnt, um XXE auszuschließen. LIBXML_NONET blockiert die Auflösung von Netzwerk-Entities. Nicht-UTF-8-Eingaben werden abgewiesen. Legen Sie dennoch für jedes extern bezogene Paket einen zum Deployment passenden byteCap fest, anstatt sich auf den Gigabyte-Standardwert zu verlassen. Behandeln Sie XMP-Eigenschaftswerte als nicht vertrauenswürdige Zeichenketten, wenn sie wieder in die Anwendung gelangen. Siehe das Bedrohungsmodell der Engine unter /modules/core/security/.

Das von XmpMetadataBuilder erzeugte Paket ist die in ISO 32000-2 §14.3 definierte XMP-Metadaten-Stream-Darstellung innerhalb des PDF (). Die XMP-Serialisierungsform selbst wird von der XMP-Spezifikation (ISO 16684-1) geregelt, die nicht im verifizierbaren Zitierkorpus enthalten ist. Diese Anforderung wird über ihre Nummer referenziert, nicht chunk-fixiert. Diese Implementierungsfakten ergeben sich aus src/Metadata/Xmp/ und werden von tests/Unit/Metadata/Xmp/ geprüft. Die End-to-End-Metadatenkonformität für ein Profil (PDF/A, PDF/UA) wird durch die Oracle- und Golden-Suites validiert, die unter /modules/core/conformance/ beschrieben sind.