Lewati ke konten

Metadata: pembuatan paket XMP dan pembacaan streaming

Modul Metadata adalah lapisan Extensible Metadata Platform (XMP) milik mesin. Modul ini membangun paket XMP yang dibawa oleh berkas Portable Document Format (PDF) sebagai aliran metadata. Modul ini membaca paket yang sudah ada tanpa memuat seluruh dokumen ke dalam memori. Modul ini menghasilkan ekstensi XMP untuk jejak audit milik mesin.

Terminal window
composer require nextpdf/core:^3

PDF menyimpan metadata tingkat dokumen sebagai paket XMP di dalam aliran metadata yang dilampirkan ke katalog dokumen, sebagaimana dijelaskan oleh ISO 32000-2 §14.3. Modul ini menangani pembuatan dan konsumsi paket tersebut. Antarmuka modul ini sengaja dibuat kecil dan terfokus: tiga kelas di bawah NextPDF\Metadata\Xmp.

XmpMetadataBuilder menghasilkan paket tersebut. Builder ini menserialisasi sekumpulan properti menjadi dokumen XMP yang well-formed dan dibungkus dalam instruksi pemrosesan <?xpacket?> standar. Builder ini menggunakan globally unique identifier (GUID) paket yang kanonis dan byte-order mark yang ditetapkan oleh spesifikasi XMP. Output-nya adalah string byte yang disematkan Writer sebagai aliran metadata, yaitu representasi XMP di dalam PDF yang dijelaskan pada §14.3.

XmpStreamReader mengonsumsi sebuah paket. Reader ini dirancang untuk menghadapi input berbahaya. Sumber di-stream dalam potongan 64 KB ke berkas sementara yang dibatasi sebelum diurai. Reader menegakkan batas byte agregat selama penulisan tersebut. Entity loader libxml diatur ke null selama penguraian dan dipulihkan setelahnya. DOCTYPE memicu penolakan tegas. iterateProperties() mengembalikan generator yang menghasilkan tuple (namespaceUri, localName, textContent) untuk setiap elemen daun tanpa membangun seluruh pohon di dalam memori; hanya elemen saat ini dan simpul teksnya yang aktif dalam parser pada satu waktu. Paket yang terlalu besar memunculkan PacketTooLargeException; Extensible Markup Language (XML) yang malformed, DOCTYPE, atau input non-UTF-8 memunculkan InvalidConfigException.

XmpAuditFieldEmitter adalah ekstensi khusus mesin. Emitter ini merender AuditReport menjadi field XMP khusus di bawah namespace nextpdfAudit, sehingga audit konformansi sebuah dokumen ikut tersimpan bersama berkas sebagai XMP yang sesuai standar, bukan sebagai berkas sidecar. AuditReport yang direndernya tidak dihasilkan oleh emitter. Pemanggil mengaktifkan pengayaan dengan menjalankan render di bawah CssRenderingMode::Audit menggunakan auditCollector yang disediakan pemanggil dan dikonfigurasi melalui Config(auditCollector: ...). Collector ini dikendalikan oleh pemanggil: pemanggil mengisinya, dan emitter merender apa pun yang telah dikumpulkannya. Komponen ini lebih baru daripada antarmuka XMP inti (@since 5.4.0). Builder dan reader bersifat @since 2.0.0.

KelasAnggota utamaPeran
XmpMetadataBuilderbuild(): string, XPACKET_GUID, XPACKET_BOMMenserialisasi sekumpulan properti menjadi paket XMP (@since 2.0.0)
XmpStreamReaderiterateProperties(mixed $source, int $byteCap = DEFAULT_BYTE_CAP): \Generator, DEFAULT_BYTE_CAPReader XMP berbatas, streaming, dan menolak DOCTYPE (@since 2.0.0)
PacketTooLargeExceptionmemperluas NextPdfExceptionDimunculkan ketika paket XMP melampaui batas byte (@since 2.0.0)
XmpAuditFieldEmitterrender(?AuditReport $report): string, NAMESPACE_URIMerender jejak audit sebagai field XMP khusus (@since 5.4.0)

Jalankan composer docs:generate-api-php -- --module=Metadata untuk menghasilkan tabel PHPDoc lengkap.

Mengalirkan properti dari paket XMP yang sudah ada dengan batas byte eksplisit.

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

Membaca paket secara defensif dan memetakan kegagalan bertipe dari modul ke hasil tingkat aplikasi, alih-alih membiarkan kesalahan parser mentah lolos.

<?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 menolak DOCTYPE apa pun secara langsung. Ini adalah pertahanan terhadap XML External Entity (XXE), bukan sekadar peningkatan validasi; paket yang membutuhkan DOCTYPE tidak diterima. Bersihkan di hulu.
  • Secara baku, batas byte adalah 1 GiB (DEFAULT_BYTE_CAP). Nilai baku itu adalah batas atas, bukan rekomendasi. Berikan byteCap yang ketat untuk input yang tidak tepercaya.
  • iterateProperties() adalah sebuah generator. Konsumsi satu kali saja; iterasi kedua tidak akan mengulang dari awal.
  • Reader mengatur entity loader libxml ke null selama penguraian lalu memulihkannya. Jangan menjalankannya bersamaan dengan penguraian berbasis libxml lainnya dalam permintaan yang sama jika penguraian tersebut bergantung pada entity loader.
  • XmpAuditFieldEmitter::render(null) valid dan menghasilkan render kosong; AuditReport yang null berarti “tidak ada audit”, bukan kesalahan.

Builder bersifat linear terhadap jumlah properti. Penggunaan memori reader didominasi oleh rentang teks tunggal terpanjang, bukan oleh ukuran dokumen, karena hanya elemen saat ini yang aktif dalam parser; paket besar di-stream alih-alih dimuat ke dalam memori. Beban kerja referensi baku berada dalam anggaran 1500 ms wall / puncak 64 MB. Profil reproduktibilitasnya adalah structural: paket XMP mencatat stempel waktu modifikasi. Dua build dari metadata logis yang sama berbeda pada field tersebut, sementara strukturnya identik.

XmpStreamReader mengurai XML yang tidak tepercaya dan diperkuat sesuai kebutuhan tersebut. Pemotongan streaming dengan batas byte yang ditegakkan membatasi denial of service berbasis amplifikasi memori. Menolak DOCTYPE menutup XXE. LIBXML_NONET memblokir resolusi entitas melalui jaringan. Input non-UTF-8 ditolak. Tetap tetapkan byteCap yang sesuai dengan penerapan untuk setiap paket yang bersumber dari luar, alih-alih mengandalkan nilai baku gigabyte. Perlakukan nilai properti XMP sebagai string yang tidak tepercaya ketika nilai tersebut masuk kembali ke aplikasi. Lihat model ancaman mesin di /modules/core/security/.

Paket yang dihasilkan XmpMetadataBuilder adalah representasi aliran metadata XMP di dalam PDF yang didefinisikan dalam ISO 32000-2 §14.3 (). Bentuk serialisasi XMP itu sendiri diatur oleh spesifikasi XMP (ISO 16684-1), yang tidak terdapat dalam korpus sitasi terverifikasi. Persyaratan itu dirujuk berdasarkan nomor, bukan dikaitkan dengan potongan tertentu. Ini adalah fakta implementasi yang dihasilkan oleh src/Metadata/Xmp/ dan diuji oleh tests/Unit/Metadata/Xmp/. Konformansi metadata menyeluruh untuk sebuah profil (PDF/A, PDF/UA) divalidasi oleh oracle dan golden suite yang dijelaskan dalam /modules/core/conformance/.