Pular para o conteúdo

Metadata: criação de pacote XMP e leitura via streaming

O módulo Metadata é a camada Extensible Metadata Platform (XMP) do motor. Ele cria o pacote XMP que um arquivo Portable Document Format (PDF) carrega como fluxo de metadados, lê um pacote existente sem carregar o documento inteiro na memória e emite a extensão XMP da trilha de auditoria do motor.

Terminal window
composer require nextpdf/core:^3

Um PDF armazena os metadados em nível de documento como um pacote XMP em um fluxo de metadados anexado ao catálogo do documento, conforme descrito por ISO 32000-2 §14.3. Este módulo é responsável pela produção e pelo consumo desse pacote. A superfície da API é deliberadamente pequena e focada: três classes em NextPDF\Metadata\Xmp.

XmpMetadataBuilder produz o pacote. Ele serializa um conjunto de propriedades em um documento XMP bem-formado, envolvido pelas instruções de processamento padrão <?xpacket?>. Ele usa o globally unique identifier (GUID) canônico do pacote e a byte-order mark definidos pela especificação XMP. A saída é a cadeia de bytes que o Writer incorpora como fluxo de metadados, a representação XMP dentro do PDF descrita na §14.3.

XmpStreamReader consome um pacote. Ele foi projetado para entrada hostil. A fonte é transmitida em blocos de 64 KB para um arquivo temporário limitado antes da análise. O leitor aplica um limite agregado de bytes durante essa escrita. O carregador de entidades do libxml é definido como null durante a análise e restaurado em seguida. Um DOCTYPE aciona uma rejeição definitiva. iterateProperties() retorna um gerador que produz tuplas (namespaceUri, localName, textContent) para cada elemento folha, sem construir a árvore inteira na memória; apenas o elemento atual e seu nó de texto permanecem vivos no analisador a qualquer momento. Um pacote grande demais lança PacketTooLargeException; Extensible Markup Language (XML) malformado, um DOCTYPE ou entrada que não seja UTF-8 lança InvalidConfigException.

XmpAuditFieldEmitter é a extensão específica do motor. Ele renderiza um AuditReport em um campo XMP personalizado no namespace nextpdfAudit, de modo que a auditoria de conformidade de um documento acompanhe o arquivo como XMP em conformidade com os padrões, em vez de seguir como arquivo paralelo. O AuditReport que ele renderiza não é produzido pelo emissor. O chamador ativa o enriquecimento executando uma renderização sob CssRenderingMode::Audit com um auditCollector fornecido pelo chamador e configurado por meio de Config(auditCollector: ...). O coletor é guiado pelo chamador: o chamador o alimenta, e o emissor renderiza o que ele tiver coletado. Ele é mais recente que a superfície XMP do core (@since 5.4.0). O construtor e o leitor são @since 2.0.0.

ClasseMembros principaisFunção
XmpMetadataBuilderbuild(): string, XPACKET_GUID, XPACKET_BOMSerializa um conjunto de propriedades em um pacote XMP (@since 2.0.0)
XmpStreamReaderiterateProperties(mixed $source, int $byteCap = DEFAULT_BYTE_CAP): \Generator, DEFAULT_BYTE_CAPLeitor XMP limitado, por streaming e que rejeita DOCTYPE (@since 2.0.0)
PacketTooLargeExceptionestende NextPdfExceptionLançada quando um pacote XMP excede o limite de bytes (@since 2.0.0)
XmpAuditFieldEmitterrender(?AuditReport $report): string, NAMESPACE_URIRenderiza a trilha de auditoria como um campo XMP personalizado (@since 5.4.0)

Execute composer docs:generate-api-php -- --module=Metadata para gerar a tabela PHPDoc completa.

Transmita propriedades de um pacote XMP existente com um limite de bytes explícito.

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

Leia um pacote de forma defensiva e mapeie as falhas tipadas do módulo para um resultado em nível de aplicação, em vez de permitir que falhas brutas do analisador escapem.

<?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 rejeita qualquer DOCTYPE imediatamente. Trata-se de uma defesa contra XML External Entity (XXE), não de um refinamento de validação; um pacote que precisa de um DOCTYPE não é aceito. Faça a sanitização na origem.
  • O limite de bytes tem como padrão 1 GiB (DEFAULT_BYTE_CAP). Esse padrão é um teto, não uma recomendação. Passe um byteCap restrito para entrada não confiável.
  • iterateProperties() é um gerador. Consuma-o uma única vez; iterá-lo duas vezes não o repete.
  • O leitor define o carregador de entidades do libxml como null durante a análise e depois o restaura. Não o execute simultaneamente com outra análise baseada em libxml na mesma requisição se essa análise depender do carregador de entidades.
  • XmpAuditFieldEmitter::render(null) é válido e produz uma renderização vazia; um AuditReport null significa “sem auditoria”, não um erro.

O construtor é linear em relação à quantidade de propriedades. O uso de memória do leitor é dominado pela maior sequência única de texto, não pelo tamanho do documento, porque apenas o elemento atual permanece vivo no analisador; pacotes grandes são transmitidos em vez de carregados na memória. A carga de trabalho de referência padrão permanece dentro de um orçamento de 1500 ms de tempo decorrido / 64 MB de pico. O perfil de reprodutibilidade é structural: um pacote XMP registra carimbos de data e hora de modificação. Duas compilações dos mesmos metadados lógicos diferem nesses campos, embora a estrutura delas seja idêntica.

XmpStreamReader analisa XML não confiável e é reforçado de acordo. O fatiamento por streaming com um limite de bytes imposto contém uma negação de serviço por amplificação de memória. Rejeitar DOCTYPE fecha o XXE. LIBXML_NONET bloqueia a resolução de entidades pela rede. Entrada que não seja UTF-8 é recusada. Ainda assim, defina um byteCap apropriado para a implantação em qualquer pacote de origem externa, em vez de depender do padrão de gigabytes. Trate os valores de propriedades XMP como cadeias não confiáveis quando eles reentrarem na aplicação. Consulte o modelo de ameaças do motor em /modules/core/security/.

O pacote que XmpMetadataBuilder produz é a representação do fluxo de metadados XMP dentro do PDF definida na ISO 32000-2 §14.3 (). A própria forma de serialização XMP é regida pela especificação XMP (ISO 16684-1), que não está no corpus de citações verificáveis. Esse requisito é referenciado por número, não fixado a um trecho. Esses são fatos de implementação produzidos por src/Metadata/Xmp/ e exercitados por tests/Unit/Metadata/Xmp/. A conformidade de metadados de ponta a ponta para um perfil (PDF/A, PDF/UA) é validada pelas suítes oracle e golden descritas em /modules/core/conformance/.