Tehnički članak

PDF/A Info i XMP ekvivalentnost metapodataka u Delphiju

PDFium Component provjerava PDF/A Info-XMP ekvivalentnost metapodataka s TPdf.InspectPdfAMetadata i popravlja je s TPdf.NormalizePdfAMetadata. ISO 19005-1 (kako ga ispravlja Cor.1) traži da svaki od osam mapiranih Info unosa, od Title do ModDate, nosi istu vrijednost kao njegovo XMP svojstvo, ne samo da postoji; provjera čita točan RDF oblik, usklađuje namespaceove po URI-ju i uspoređuje datume kao trenutke

Bug izvještaj koji obično pokreće ovaj razgovor izgleda bezopasno. Sustav za upravljanje dokumentima utiskuje novi /ModDate u Info rječnik pri svakom inkrementalnom spremanju, XMP paket ostavlja na miru, i šest mjeseci kasnije arhivski audit obilježi tisuće datoteka kao nesukladne. Oba datuma su tu. Samo su prestali izjednačavati se kod prve izmjene, a provjera prisutnosti to nikad nije primijetila. Izmjene Titlea učinjene kroz Info-only API, i Author string poput Finance; Controlling koji je neki alat razdvojio u dva dc:creator unosa, padaju isto

Zašto PDF/A odbija metapodatke koji postoje na oba mjesta?

PDF/A to odbija jer je ISO 19005-1 §6.7.3 pravilo o vrijednosti, ne pravilo o prisutnosti: Tablica 1 preslikava osam Info ključeva na XMP svojstva, i jednom kad je Info ključ prisutan, mapirano XMP svojstvo mora nositi ekvivalentnu vrijednost. Skener na razini bajtova opisan u PDF/A preflight validaciji s PDFium Componentom samo potvrđuje da xmp:CreateDate i xmp:ModifyDate postoje (pvaiMissingXmpDates). Od v3.72.0 TPdf.ValidatePdfA dodatno izvodi potpunu usporedbu vrijednosti i dodaje pvaiInfoXmpValueMismatch u skup problema kad XMP paket postoji ali se ne slaže s Info (paket koji se ne može parsirati računa se kao neslaganje). Nepostojeći paket ostaje prijavljen kao pvaiMissingXmpMetadata, pa ta dva problema nikad ne dvostruko broje isti defekt

Koji RDF oblik svako mapirano XMP svojstvo treba?

Svako od osam mapiranja ima fiksni XMP tip, i točna vrijednost u krivom spremniku i dalje pada. ComparePdfAInfoAndXmp u FPdfPdfa.pas traži svojstva po namespace URI-ju, pa paket koji veže http://purl.org/dc/elements/1.1/ na neobičan prefiks čita se točno kao onaj koji koristi dc. Traženi oblici su:

  • Title → dc:title i Subject → dc:description: rdf:Alt jezična alternativa, uspoređivana samo s njenim x-default unosom (jezična oznaka poklapa se neosjetljivo na velika i mala slova); Alt bez x-default računa se kao nepostojeći
  • Author → dc:creator: rdf:Seq s točno jednim tekstualnim unosom koji drži cijeli Info string, pa autor popis odvojen točka-zarezom ostaje jedan unos
  • Keywords → pdf:Keywords i Producer → pdf:Producer (namespace http://ns.adobe.com/pdf/1.3/): jednostavna tekstualna svojstva
  • Creator → xmp:CreatorTool, CreationDate → xmp:CreateDate, ModDate → xmp:ModifyDate (namespace http://ns.adobe.com/xap/1.0/): jednostavna tekstualna svojstva
Osam mapiranih parova koje ComparePdfAInfoAndXmp provjerava za PDF/A ekvivalentnost metapodataka u Delphiju: Title i Subject trebaju rdf:Alt s x-default unosom, Author rdf:Seq s jednim unosom, Keywords, Producer, Creator i dva datuma su jednostavan tekst, svako traženo po XMP namespace URI-ju a ne po prefiksu
Jednom kad je Info ključ prisutan, ISO 19005-1 traži da mapirano XMP svojstvo nosi ekvivalentnu vrijednost u traženom RDF obliku, pa točna vrijednost u krivom spremniku i dalje pada
<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>

Tekstualne vrijednosti uspoređuju se kao točni slijedovi Unicode code pointova, bez odrezivanja, pretvaranja velikih i malih slova ili normalizacije. Prazno mjesto na kraju, ili sklopljeni é s jedne strane i razloženi e plus kombinirajući akcenat s druge, pravi je nesuklad. Info strana uvijek dolazi iz PDFium vlastitog dekodiranja PDFDocEncoding i UTF-16 teksta kroz FPDF_GetMetaText, što biblioteku odvraća od ponovne implementacije dekodiranja stringova i suptilne greške; XMP strana je čista onoliko koliko su čisti bajtovi koji su je proizveli, zato i codepage zamke koje kvare XMP metapodatke pod Free Pascalom ovdje imaju veze

Kada su PDF datum i XMP datum jednaki?

PDF datum i XMP datum jednaki su kad opisuju isti trenutak do sekunde, s istim znanjem o vremenskoj zoni na objema stranama. Oba parsera primaju zakonitu smanjenu preciznost, pa D:2026 i 2026 oba znače 1. siječnja 2026, 00:00:00. Kad obje vrijednosti nose zonu, pretvaraju se u UTC prije usporedbe: D:20260827093659+08'00' jednako je 2026-08-27T01:36:59Z. Kad nijedna ne nosi zonu, lokalne komponente uspoređuju se kako su zapisane. Kad samo jedna strana ima zonu, rezultat je pamsValueMismatch, jer bi izmišljanje pomaka bilo pogađanje. Nenulta decimalna sekunda poput .250 u XMP-u također nameće nesuklad, jer PDF datum nema načina izraziti je, a tiho bi je zaokruživanje sakrilo stvarno neslaganje; .000 se prima. Neparabilne vrijednosti javljaju se odvojeno kao pamsInvalidInfoDate ili pamsInvalidXmpDate

Kako PDFium Component odlučuje da su PDF Info datum i XMP datum jednaki u Delphiju: dvije zone pretvaraju se u UTC i uspoređuju trenutke, dvije vrijednosti bez zone uspoređuju se kako su zapisane, sama jedna zona je pamsValueMismatch, nenulta decimalna sekunda ne može se izraziti, a neparabilne vrijednosti javljaju se odvojeno
Jednakost znači isti trenutak do sekunde s istim znanjem o vremenskoj zoni na objema stranama, pa bi izmišljeni pomak ili zaokružena decimalna sekunda sakrili stvarno neslaganje

Prisutnost ima vlastito pravilo. TPdfAMetadataValues.Present je skup koji se puni šetnjom kroz /Info rječnik aktivnog trailera, i on drži "ključ izostao" odvojeno od "ključ prisutan s praznim stringom". Izostali ključ daje pamsNotRequired i od XMP-a ništa ne traži; /Title () je prisutan, pa XMP paket mora nositi i prazan x-default naslov

Kako pregledati Info i XMP metapodatke prije spremanja?

TPdf.InspectPdfAMetadata vraća TPdfAMetadataReport s jednom TPdfAMetadataComparison po polju, svaka drži Info vrijednost, XMP vrijednost i TPdfAMetadataState, pa se pad može objasniti bez reverse-engineeringa pojedinačne validacijske zastavice. MismatchFields sažima padajući skup, HasXmpPacket kaže je li paket nađen, a XmpParseError nosi parserovu poruku kad paket postoji ali se ne može čitati

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;

Što NormalizePdfAMetadata mijenja, i što odbija?

TPdf.NormalizePdfAMetadata tretira Info rječnik kao izvor istine i prepravlja samo XMP svojstva čije je polje završilo u MismatchFields; sve ostalo u paketu preživi. Title i Subject upisuju se u x-default unos dok druge jezične alternative ostaju netaknute, Author postaje rdf:Seq s jednim unosom, nepoznati namespaceovi i nesrodna svojstva čuvaju se, a XMP svojstva za izostale Info ključeve ostavljaju se na miru. Datum Info s zonom piše se kao kanonski UTC XMP datum sa sufiksom Z; onaj bez zone zadržava svoje lokalne komponente. Overload s datotekom sprema kroz privremenu datoteku i atomsku zamjenu, a sama XMP izmjena dodaje se kao inkrementalni update

Što NormalizePdfAMetadata prepravlja kad popravlja PDF/A metapodatke u Delphiju s PDFium Componentom: Info je izvor istine, samo se unosi MismatchFields zapisuju natrag kao x-default Alt tekst, Seq s jednim unosom ili kanonski UTC datum, dok nepoznati namespaceovi, nesrodna svojstva i svojstva izostalih ključeva preživljavaju netaknuta
Popravak odbija nepostojeći XMP paket, neispravan Info datum i potpisane dokumente, jer je gradnja potpunog PDF/A skupa metapodataka posao SaveAsPdfA, ne ciljane popravke ekvivalentnosti

Odbijanja su namjerna. Bez XMP paketa metoda diže EPdfError, jer je gradnja potpunog PDF/A identificiranja i skupa metapodataka posao SaveAsPdfA, obrađen u stvaranju PDF/A arhivskih datoteka s PDFium Componentom. Neispravan Info datum diže EPdfXmpError umjesto da napiše uvjerljivo izgledajuću krivu vrijednost, i ništa se ne sprema. Potpisani dokumenti odbijaju se osim ako pozivatelj ne proslijedi AllowSignedDocument = True. Ekvivalentnost je i jedna od pravila ISO 19005-1, pa normalizirana datoteka nije automatski sukladna

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;                      // već usklađeno, datoteku ostavite na miru
    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      // neispravan Info datum ili nečitljiv paket
        raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
    end;
  finally
    Pdf.Free;
  end;
end;

Pokretanje usporedbe na vlastitom XMP paketu

ComparePdfAInfoAndXmp i SynchronizePdfAInfoToXmp obične su funkcije u FPdfPdfa koje rade nad TPdfXmpPacket bez učitanog dokumenta, što odgovara jediničnim testovima i pipelineima koji sklapaju XMP iz predloška. Jedna je zamka Present: zapis inicijaliziran s Default(TPdfAMetadataValues) ima prazan skup, svako polje tada javlja pamsNotRequired, i usporedba prolazi prazno bez obzira na vrijednosti koje ste upisali

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''';
  // Present odlučuje koja su polja obavezna; same vrijednosti se ignoriraju
  Info.Present := [pamfTitle, pamfAuthor, pamfModDate];

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

Ako Vaš pipeline arhivira dokumente koje drugi sustavi i dalje uređuju, spojite noćni prolaz InspectPdfAMetadata s NormalizePdfAMetadata za datoteke koje odmu, a ValidatePdfA držite kao kapiju prije nego bilo što otputuje u dugoročno spremanje. Tipizirani izvještaj, putanja popravke i ostatak PDF/A alata isporučuju se u PDFium Componentu za Delphi i C++Builder