Műszaki cikk

PDF/A Info és XMP metaadat egyezőség Delphiben

A PDFium Component a PDF/A Info-XMP metaadat egyezőséget a TPdf.InspectPdfAMetadata-tal ellenőrzi, és a TPdf.NormalizePdfAMetadata-tal javítja. Az ISO 19005-1 (a Cor.1 által javított) mindegyiknél a nyolc leképezett Info bejegyzésnek, a Title-től a ModDate-ig, megköveteli, hogy ugyanazt az értéket hordozza, mint a hozzá tartozó XMP tulajdonság, nem csupán azt, hogy létezzen; az ellenőrzés a helyes RDF alakot olvassa, a névtereket URI alapján párosítja, a dátumokat pedig pillanatokként hasonlítja

A hibajelentés, amely ezt a beszélgetést jellemzően elindítja, ártatlannak tűnik. Egy dokumentumkezelő rendszer minden növekményes mentéskor új /ModDate-et pecsétel az Info szótárba, az XMP csomagot békén hagyja, és hat hónappal később egy archív audit ezer fájlt jelöl meg nem megfelelőként. Mindkét dátum ott van. Csak az első szerkesztésnél hagytak fel az egyetértéssel, és egy jelenlét-ellenőrzés soha nem vette észre. A csak Info API-n keresztül végzett címszerkesztések, és az olyan Finance; Controlling szerző string, amelyet egyes eszközök két dc:creator elemre bontanak, ugyanígy buknak

Miért utasítja el a PDF/A a mindkét helyen meglévő metaadatot?

A PDF/A azért utasítja el, mert az ISO 19005-1 §6.7.3 értékszabály, nem jelenléti szabály: az 1. táblázat nyolc Info kulcsot képez le XMP tulajdonságokra, és ha egy Info kulcs egyszer jelen van, a leképezett XMP tulajdonságnak egyenértékű értéket kell hordoznia. A PDF/A preflight validáció a PDFium Component-tel cikkben leírt bájtszintű szkenner csak azt erősíti meg, hogy az xmp:CreateDate és az xmp:ModifyDate létezik (pvaiMissingXmpDates). A v3.72.0 óta a TPdf.ValidatePdfA emellett lefuttatja a teljes érték-összehasonlítást, és pvaiInfoXmpValueMismatch-ot fűz a hibakészlethez, ha van XMP csomag, de ellentmond az Info-nak (az elemezhetetlen csomag ellentmondásnak számít). A hiányzó csomag továbbra is pvaiMissingXmpMetadata-ként kerül jelentésre, így a két hiba soha nem számolja duplán ugyanazt a hibát

Milyen RDF alakot igényel mindegyik leképezett XMP tulajdonság?

A nyolc leképezés mindegyikének rögzített XMP típusa van, és a helyes érték rossz tárolóban is elbukik. A FPdfPdfa.pas ComparePdfAInfoAndXmp függvénye a tulajdonságokat névtér URI alapján keresi, tehát egy olyan csomag, amely a http://purl.org/dc/elements/1.1/-t szokatlan előtaghoz köti, pontosan úgy olvasható, mint egy, amely a dc-t használja. A szükséges alakok:

  • Title → dc:title és Subject → dc:description: egy rdf:Alt nyelvi alternatíva, amelyet csak az x-default elemével hasonlítanak (a nyelvcímke kis-nagybetűre érzéketlenül egyezik); az x-default nélküli Alt hiányzóként számít
  • Author → dc:creator: egy rdf:Seq pontosan egy szövegelemmel, amely a teljes Info stringet hordozza, tehát a pontosvesszővel elválasztott szerzőlista egyetlen bejegyzés marad
  • Keywords → pdf:Keywords és Producer → pdf:Producer (névtér http://ns.adobe.com/pdf/1.3/): egyszerű szövegtulajdonságok
  • Creator → xmp:CreatorTool, CreationDate → xmp:CreateDate, ModDate → xmp:ModifyDate (névtér http://ns.adobe.com/xap/1.0/): egyszerű szövegtulajdonságok
A nyolc leképezett pár, amelyet a ComparePdfAInfoAndXmp ellenőriz a PDF/A metaadat egyezőségére Delphiben: a Title és a Subject rdf:Alt-t x-default elemmel igényel, az Author egyelemes rdf:Seq-t, a Keywords, Producer, Creator és a két dátum egyszerű szöveg, mindegyiket XMP névtér URI alapján keresik, előtag helyett
Ha egy Info kulcs egyszer jelen van, az ISO 19005-1 megköveteli, hogy a leképezett XMP tulajdonság a szükséges RDF alakban egyenértékű értéket hordozzon, tehát a rossz tárolóban lévő érték is elbukik
<dc:title><rdf:Alt><rdf:li xml:lang="x-default">Quarterly Report 2026</rdf:li></rdf:Alt></dc:title>
<dc:creator><rdf:Seq><rdf:li>Finance; Controlling</rdf:li></rdf:Seq></dc:creator>
<pdf:Producer>PDFium Component</pdf:Producer>

A szövegértékeket pontos Unicode kódpont-sorozatokként hasonlítják, vágás, kisbetűsítés vagy normalizálás nélkül. Egy záró szóköz, vagy az egyik oldalon előkomponált é, a másikon dekomponált e meg egy kombináló ékezet, valódi eltérés. Az Info oldal mindig a PDFium saját, a PDFDocEncoding és UTF-16 szöveg dekódolásából jön az FPDF_GetMetaText útján, ami meggátolja, hogy a könyvtár újraimplementálja a string dekódolást, és finoman elrontsa; az XMP oldal csak annyira tiszta, mint a bájtok, amelyekből készült, ezért a kódlap-csapdák, amelyek Free Pascal alatt rontják az XMP metaadatot, itt is számítanak

Mikor egyenlő egy PDF dátum és egy XMP dátum?

Egy PDF dátum és egy XMP dátum akkor egyenlő, ha ugyanazt a pillanatot írják le másodperc pontossággal, mindkét oldalon azonos időzóna-tudatossággal. Mindkét elemző elfogadja a legális, csökkentett pontosságot, tehát a D:2026 és a 2026 egyaránt 2026. január 1., 00:00:00. Ha mindkét érték hordoz zónát, UTC-re konvertálódnak az összehasonlítás előtt: a D:20260827093659+08'00' egyenlő a 2026-08-27T01:36:59Z-vel. Ha egyik sem hordoz zónát, a helyi komponensek írott alakjukban hasonlítanak össze. Ha csak az egyik oldalnak van zónája, az eredmény pamsValueMismatch, mert egy eltolás kitalálása találgatás lenne. Egy nem nulla törtszekundum, például a .250 az XMP-ben, szintén eltérést kikényszerít, hiszen egy PDF dátumnak nincs módja kifejezni, és csendben lekerekíteni elrejtené a valódi ellentmondást; a .000 elfogadott. Az elemezhetetlen értékek külön kerülnek jelentésre, pamsInvalidInfoDate vagy pamsInvalidXmpDate néven

Hogyan dönti el a PDFium Component Delphiben, hogy egy PDF Info dátum és egy XMP dátum egyenlő-e: két zóna UTC-re konvertál és pillanatokként hasonlít, két zóna nélküli érték írott alakjában hasonlít, egy magányos zóna pamsValueMismatch, a nem nulla törtszekundum nem fejezhető ki, az elemezhetetlen értékek pedig külön kerülnek jelentésre
Az egyenlőség azt jelenti, hogy ugyanaz a pillanat másodperc pontossággal, mindkét oldalon azonos időzóna-tudatossággal, tehát egy eltolás kitalálása vagy egy törtszekundum lekerekítése elrejtené a valódi ellentmondást

A jelenlétnek saját szabálya van. A TPdfAMetadataValues.Present egy halmaz, amelyet az aktív trailer /Info szótárának bejárása tölt, és elkülöníti a „kulcs hiányzik"-et a „kulcs jelen van, üres stringgel" állapottól. A hiányzó kulcs pamsNotRequired-t ad, és semmit sem követel az XMP-től; az /Title () jelen van, tehát az XMP csomagnak üres x-default címet is hordoznia kell

Hogyan vizsgálhatja meg az Info és XMP metaadatot mentés előtt?

A TPdf.InspectPdfAMetadata egy TPdfAMetadataReport-ot ad vissza, mezőnként egy TPdfAMetadataComparison-nal, mindegyik hordozza az Info értéket, az XMP értéket és egy TPdfAMetadataState-et, tehát egy hiba magyarázható anélkül, hogy egyetlen validációs jelzőt kellene visszafejteni. A MismatchFields összefoglalja a bukó készletet, a HasXmpPacket megmondja, találtak-e csomagot, az XmpParseError pedig az elemző üzenetét hordozza, ha a csomag létezik, de nem olvasható

uses
  System.SysUtils, PDFium, FPdfPdfa;

const
  FieldNames: array[TPdfAMetadataField] of string = (
    'Title', 'Author', 'Subject', 'Keywords',
    'Creator', 'Producer', 'CreationDate', 'ModDate');
  StateNames: array[TPdfAMetadataState] of string = (
    'not required', 'equivalent', 'XMP missing', 'XMP type mismatch',
    'value mismatch', 'invalid Info date', 'invalid XMP date');

procedure ReportMetadata(Pdf: TPdf);
var
  Report: TPdfAMetadataReport;
  Item: TPdfAMetadataComparison;
begin
  Report := Pdf.InspectPdfAMetadata;
  if Report.XmpParseError <> '' then
    Writeln('XMP packet unreadable: ', Report.XmpParseError)
  else if not Report.HasXmpPacket then
    Writeln('No XMP packet at all');
  for Item in Report.Comparisons do
    if not Item.IsEquivalent then
      Writeln(Format('%-12s %-18s Info="%s" XMP="%s"',
        [FieldNames[Item.Field], StateNames[Item.State],
         Item.InfoValue, Item.XmpValue]));
end;

Mit változtat meg a NormalizePdfAMetadata, és mit tagad meg?

A TPdf.NormalizePdfAMetadata az Info szótárt tekinti az igazság forrásának, és csak azokat az XMP tulajdonságokat írja újra, amelyek mezője a MismatchFields-be került; a csomag minden mása túléli. A Title és a Subject az x-default elemébe íródik, miközben a többi nyelvi alternatíva érintetlen marad, az Author egyelemes rdf:Seq-vé válik, az ismeretlen névterek és a nem kapcsolódó tulajdonságok megőrződnek, és a hiányzó Info kulcsok XMP tulajdonságai érintetlenek maradnak. Egy zónás Info dátum kanonikus UTC XMP dátumként íródik Z utótaggal; egy zóna nélküli megtartja a helyi komponenseit. A fájl túlterhelése ideiglenes fájlon és atomi cserén keresztül ment, maga az XMP frissítés pedig növekményes frissítésként fűződik hozzá

Mit ír újra a NormalizePdfAMetadata, amikor Delphiben, PDFium Component-tel javítja a PDF/A metaadatot: az Info az igazság forrása, csak a MismatchFields bejegyzések íródnak vissza x-default Alt szövegként, egyelemes Seq-ként vagy kanonikus UTC dátumként, miközben az ismeretlen névterek, a nem kapcsolódó tulajdonságok és a hiányzó kulcs tulajdonságai érintetlenül túlélnek
A javítás megtagadja a hiányzó XMP csomagot, a hibás Info dátumot és az aláírt dokumentumokat, mert egy teljes PDF/A metaadatkészlet felépítése a SaveAsPdfA dolga, nem egy célzott egyezőség-javításé

A megtagadások szándékosak. XMP csomag nélkül a metódus EPdfError-t dob, mert egy teljes PDF/A azonosító- és metaadatkészlet felépítése a SaveAsPdfA dolga, amelyet a PDF/A archív fájlok készítése a PDFium Component-tel cikk fed le. Egy hibás Info dátum EPdfXmpError-t dob ahelyett, hogy hihetőnek tűnő rossz értéket írna, és semmi sem mentődik. Az aláírt dokumentumok elutasításra kerülnek, hacsak a hívó AllowSignedDocument = True-t nem ad át. Az egyezőség az ISO 19005-1 egyetlen szabálya is egyben, tehát egy normalizált fájl nem automatikusan megfelelő

uses
  System.SysUtils, PDFium, FPdfPdfa, FPdfXmp;

procedure NormalizeArchive(const Source, Target: string);
var
  Pdf: TPdf;
  Report: TPdfAMetadataReport;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := Source;
    Pdf.Active := True;
    Report := Pdf.InspectPdfAMetadata;
    if Report.IsEquivalent then
      Exit;                      // már konzisztens, hagyjuk a fájlt békében
    if not Report.HasXmpPacket then
      raise Exception.Create('No XMP packet: convert with SaveAsPdfA instead');
    try
      if not Pdf.NormalizePdfAMetadata(Target) then
        raise Exception.Create('Normalized save failed');
    except
      on E: EPdfXmpError do      // hibás Info dátum vagy olvashatatlan csomag
        raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
    end;
  finally
    Pdf.Free;
  end;
end;

Az összehasonlítás futtatása a saját XMP csomagján

A ComparePdfAInfoAndXmp és a SynchronizePdfAInfoToXmp sima függvények a FPdfPdfa-ban, amelyek betöltött dokumentum nélkül dolgoznak egy TPdfXmpPacket-en, ami egységtesztekhez és olyan folyamokhoz illik, amelyek sablonból raknak össze XMP-t. Az egyetlen csapda a Present: egy Default(TPdfAMetadataValues)-nal inicializált rekord üres halmazzal indul, minden mező ekkor pamsNotRequired-t jelent, és az összehasonlítás üresen átmegy, bármilyen értékeket is töltött be

uses
  System.SysUtils, System.IOUtils, FPdfPdfa, FPdfXmp;

procedure AlignTemplate(const TemplateFile: string);
var
  Info: TPdfAMetadataValues;
  Packet: TPdfXmpPacket;
  Changed: TPdfAMetadataFields;
begin
  Info := Default(TPdfAMetadataValues);
  Info.Title := 'Quarterly Report 2026';
  Info.Author := 'Finance; Controlling';
  Info.ModDate := 'D:20260827093659+08''00''';
  // A Present dönti el, mely mezők kötelezőek; a value-k önmagukban figyelmen kívül maradnak
  Info.Present := [pamfTitle, pamfAuthor, pamfModDate];

  Packet := TPdfXmpPacket.Parse(TFile.ReadAllText(TemplateFile, TEncoding.UTF8));
  try
    Changed := SynchronizePdfAInfoToXmp(Info, Packet);
    // Az xmp:ModifyDate most 2026-08-27T01:36:59Z, a dc:creator egyelemes rdf:Seq
    if Changed <> [] then
      TFile.WriteAllBytes(TemplateFile, Packet.ToUtf8);
  finally
    Packet.Free;
  end;
end;

Ha az Ön folyamata olyan dokumentumokat archivál, amelyeket más rendszerek folyamatosan szerkesztenek, párosítson egy éjszakai InspectPdfAMetadata átvizsgálást a NormalizePdfAMetadata-dal a sodródó fájlokra, és tartsa a ValidatePdfA-t kapunak, mielőtt bármi elindulna hosszú távú tárolásra. A típusos jelentés, a javítási út és a PDF/A eszközkészlet többi része a PDFium Component Delphihez és C++Builderhez csomagban érkezik