Ga naar inhoud

Metadata: XMP-pakket bouwen en streamend lezen

De Metadata-module vormt de Extensible Metadata Platform (XMP)-laag van de engine. De module bouwt het XMP-pakket dat een Portable Document Format (PDF)-bestand als metadatastream meedraagt. Ze leest een bestaand pakket zonder het hele document in het geheugen te laden en rendert ook de audit-trail-XMP-uitbreiding van de engine.

Terminal window
composer require nextpdf/core:^3

Een PDF slaat metadata op documentniveau op als een XMP-pakket in een metadatastream die aan de documentcatalogus is gekoppeld, zoals beschreven in ISO 32000-2 §14.3. Deze module is verantwoordelijk voor het aanmaken en uitlezen van dat pakket. Het API-oppervlak is bewust klein en doelgericht: drie klassen onder NextPDF\Metadata\Xmp.

XmpMetadataBuilder produceert het pakket. De builder serialiseert een set eigenschappen naar een correct opgemaakt XMP-document, omsloten door de standaard <?xpacket?>-verwerkingsinstructies. De builder gebruikt de canonieke globally unique identifier (GUID) van het pakket en de byte-order-mark die door de XMP-specificatie zijn vastgelegd. De uitvoer is de bytestring die de Writer als metadatastream insluit: de in-PDF-XMP-representatie die in §14.3 wordt beschreven.

XmpStreamReader leest een pakket uit. De reader is ontworpen met kwaadaardige invoer als uitgangspunt. De bron wordt vóór het parseren in chunks van 64 KB naar een begrensd tijdelijk bestand gestreamd. Tijdens dat schrijven handhaaft de reader een totale bytelimiet. De libxml-entity-loader wordt voor het parseren op null gezet en daarna hersteld. Een DOCTYPE leidt tot een harde afwijzing. iterateProperties() geeft een generator terug die voor elk leaf-element (namespaceUri, localName, textContent)-tuples levert zonder de hele boom in het geheugen op te bouwen; op elk moment houdt de parser alleen het huidige element en de bijbehorende tekstnode vast. Een te groot pakket veroorzaakt PacketTooLargeException; onjuist opgemaakte Extensible Markup Language (XML), een DOCTYPE of niet-UTF-8-invoer veroorzaakt InvalidConfigException.

XmpAuditFieldEmitter is de engine-specifieke uitbreiding. De emitter rendert een AuditReport naar een aangepast XMP-veld onder de nextpdfAudit-namespace, zodat de conformiteitsaudit van een document als standaardconforme XMP met het bestand meereist in plaats van als losse bijlage. Het AuditReport dat de emitter rendert, wordt niet door de emitter zelf geproduceerd. De aanroeper activeert verrijking door te renderen onder CssRenderingMode::Audit met een door de aanroeper geleverde auditCollector die via Config(auditCollector: ...) is geconfigureerd. De aanroeper stuurt de collector aan: hij voedt hem, en de emitter rendert wat de collector heeft verzameld. De emitter is nieuwer dan het core-XMP-API-oppervlak (@since 5.4.0). De builder en reader zijn @since 2.0.0.

KlasseBelangrijkste ledenRol
XmpMetadataBuilderbuild(): string, XPACKET_GUID, XPACKET_BOMSerialiseert een set eigenschappen naar een XMP-pakket (@since 2.0.0)
XmpStreamReaderiterateProperties(mixed $source, int $byteCap = DEFAULT_BYTE_CAP): \Generator, DEFAULT_BYTE_CAPBegrensde, streamende XMP-reader die DOCTYPE afwijst (@since 2.0.0)
PacketTooLargeExceptionbreidt NextPdfException uitWordt gegooid wanneer een XMP-pakket de bytelimiet overschrijdt (@since 2.0.0)
XmpAuditFieldEmitterrender(?AuditReport $report): string, NAMESPACE_URIRendert de audit-trail als een aangepast XMP-veld (@since 5.4.0)

Voer composer docs:generate-api-php -- --module=Metadata uit om de volledige PHPDoc-tabel te genereren.

Stream eigenschappen uit een bestaand XMP-pakket onder een expliciete bytelimiet.

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

Lees een pakket defensief en wijs de getypeerde fouten van de module toe aan een uitkomst op applicatieniveau, in plaats van onbewerkte parserfouten te laten ontsnappen.

<?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 wijst elke DOCTYPE rechtstreeks af. Dit is een verdediging tegen XML External Entity (XXE), geen validatieformaliteit; een pakket dat een DOCTYPE nodig heeft, wordt niet geaccepteerd. Schoon het stroomopwaarts op.
  • De bytelimiet is standaard 1 GiB (DEFAULT_BYTE_CAP). Die standaardwaarde is een bovengrens, geen aanbeveling. Geef een strakke byteCap mee voor niet-vertrouwde invoer.
  • iterateProperties() is een generator. Verbruik hem eenmalig; een tweede iteratie kan niet worden herhaald.
  • De reader zet de libxml-entity-loader voor het parseren op null en herstelt deze daarna. Voer hem niet gelijktijdig uit met ander op libxml gebaseerd parseren binnen hetzelfde verzoek wanneer dat parseren afhankelijk is van de entity-loader.
  • XmpAuditFieldEmitter::render(null) is geldig en levert een lege renderuitvoer op; een null-AuditReport betekent “geen audit”, geen fout.

De builder is lineair in het aantal eigenschappen. Het geheugengebruik van de reader wordt bepaald door de langste afzonderlijke tekstreeks, niet door de documentgrootte, omdat alleen het huidige element in de parser aanwezig is; grote pakketten worden gestreamd in plaats van in het geheugen geladen. De standaard referentiebelasting blijft binnen een budget van 1500 ms wandkloktijd / 64 MB piek. Het reproduceerbaarheidsprofiel is structural: een XMP-pakket legt wijzigingstijdstempels vast. Twee builds van dezelfde logische metadata verschillen in die velden, terwijl hun structuur identiek is.

XmpStreamReader parseert niet-vertrouwde XML en is daarop gehard. Streamende chunking met een afgedwongen bytelimiet beperkt denial-of-service door geheugenversterking. Het afwijzen van DOCTYPE blokkeert XXE. LIBXML_NONET blokkeert het resolven van entiteiten via het netwerk. Niet-UTF-8-invoer wordt geweigerd. Stel toch voor elk extern verkregen pakket een bij de deployment passende byteCap in, in plaats van te vertrouwen op de gigabyte-standaardwaarde. Behandel XMP-eigenschapswaarden als niet-vertrouwde strings wanneer ze opnieuw de applicatie binnenkomen. Zie het dreigingsmodel van de engine in /modules/core/security/.

Het pakket dat XmpMetadataBuilder produceert, is de in-PDF-XMP-metadatastream-representatie die is gedefinieerd in ISO 32000-2 §14.3 (). De XMP-serialisatievorm zelf wordt geregeld door de XMP-specificatie (ISO 16684-1), die niet in het verifieerbare citatiecorpus zit. Naar die eis wordt op nummer verwezen en niet vastgepind op een chunk. Deze implementatiefeiten worden geproduceerd door src/Metadata/Xmp/ en getoetst door tests/Unit/Metadata/Xmp/. End-to-end-metadataconformiteit voor een profiel (PDF/A, PDF/UA) wordt gevalideerd door de oracle- en golden-suites die worden beschreven in /modules/core/conformance/.