Tehnični članak

Enakovrednost metapodatkov PDF/A Info in XMP v Delphiju

PDFium Component preverja enakovrednost metapodatkov Info-XMP za PDF/A s TPdf.InspectPdfAMetadata in jo popravlja s TPdf.NormalizePdfAMetadata. ISO 19005-1 (kot jo je popravil Cor.1) zahteva, da vsak od osmih preslikanih vnosov Info, od Title do ModDate, nosi isto vrednost kot njegova lastnost XMP, ne le da obstaja; preizkus prebere pravilno obliko RDF, imenske prostore ujema po URI in datume primerja kot trenutke

Poročilo o hrošču, ki ponavadi začne ta pogovor, izgleda nenevarno. Sistem za upravljanje dokumentov žigi nov /ModDate v slovar Info ob vsakem inkrementalnem shranjevanju, paket XMP pusti pri miru, pol leta pozneje pa arhivska revizija označi tisoče datotek kot neskladne. Oba datuma sta tam. Pravkar sta prenehala sovpadati ob prvi spremembi in preizkus prisotnosti tega nikoli ni opazil. Spremembe naslova, narejene prek API-ja samo za Info, in niz Author, kot je Finance; Controlling, ki ga je kakšno orodje razcepilo v dva vnosa dc:creator, spodletijo enako

Zakaj PDF/A zavrne metapodatke, ki obstajajo na obeh mestih?

PDF/A jo zavrne, ker je ISO 19005-1 §6.7.3 pravilo vrednosti in ne pravilo prisotnosti: Tabela 1 preslika osem ključev Info na lastnosti XMP in ko je ključ Info enkrat prisoten, mora preslikana lastnost XMP nositi enakovredno vrednost. Bajtni pregledovalnik, opisan v validaciji PDF/A preflight s komponento PDFium Component, le potrdi, da xmp:CreateDate in xmp:ModifyDate obstajata (pvaiMissingXmpDates). Od v3.72.0 TPdf.ValidatePdfA dodatno požene polno primerjavo vrednosti in doda pvaiInfoXmpValueMismatch k nizu težav, kadar paket XMP obstaja, a se ne strinja z Info (paket, ki ga ni mogoče razčleniti, šteje kot nesoglasje). Manjkajoči paket ostane prijavljen kot pvaiMissingXmpMetadata, tako da ti dve težavi nikoli ne preštejeta iste napake

Katero obliko RDF potrebuje vsaka preslikana lastnost XMP?

Vsaka od osmih preslikav ima fiksni tip XMP in pravilna vrednost v napačnem vsebniku vseeno spodleti. ComparePdfAInfoAndXmp v FPdfPdfa.pas lastnosti poišče po URI imenskega prostora, zato je paket, ki veže http://purl.org/dc/elements/1.1/ na nenavadno predpono, prebran točno tako kot tisti, ki uporablja dc. Zahtevane oblike so:

  • Title → dc:title in Subject → dc:description: jezikovna alternativa rdf:Alt, primerjana le s svojim vnosom x-default (jezikovna oznaka se ujema brez razlikovanja velikih in malih črk); Alt brez x-default se šteje za manjkajočega
  • Author → dc:creator: rdf:Seq s točno enim besedilnim vnosom, ki nosi celoten niz Info, tako da seznam avtorjev, ločenih s podpičji, ostane en sam vnos
  • Keywords → pdf:Keywords in Producer → pdf:Producer (namespace http://ns.adobe.com/pdf/1.3/): preproste besedilne lastnosti
  • Creator → xmp:CreatorTool, CreationDate → xmp:CreateDate, ModDate → xmp:ModifyDate (namespace http://ns.adobe.com/xap/1.0/): preproste besedilne lastnosti
Osem preslikanih parov, ki jih ComparePdfAInfoAndXmp preverja za enakovrednost metapodatkov PDF/A v Delphiju: Title in Subject potrebujeta rdf:Alt z vnosom x-default, Author enovnosni rdf:Seq, Keywords, Producer, Creator in dva datuma pa preprosto besedilo, vsak poiščen po URI imenskega prostora XMP in ne po predponi
Ko je ključ Info enkrat prisoten, zahteva ISO 19005-1, da preslikana lastnost XMP nosi enakovredno vrednost v zahtevani obliki RDF, zato vrednost v napačnem vsebniku vseeno spodleti
<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>

Besedilne vrednosti so primerjane kot točna zaporedja kodnih točk Unicode, brez obrezovanja, zlaganja velikosti ali normalizacije. Presledek na koncu ali vnaprej sestavljeno é na eni strani in razstavljeno e z združevalnim naglasom na drugi je pravo nesorazmerje. Stran Info vedno prihaja iz lastnega dekodiranja PDFium besedil PDFDocEncoding in UTF-16 prek FPDF_GetMetaText, kar knjižnici odvzame, da bi znova implementirala dekodiranje nizov in se subtilno zmotila; stran XMP je čista le toliko, kot so bajti, ki so jo ustvarili, zato pasti kodnih strani, ki pod Free Pascal pokvarijo metapodatke XMP, tu štejejo prav tako

Kdaj sta datum PDF in datum XMP enaka?

Datum PDF in datum XMP sta enaka, ko opisujeta isti trenutek do sekunde, z istim znanjem o časovnem pasu na obeh straneh. Oba razčlenjevalnika sprejmeta zakonito zmanjšano natančnost, zato D:2026 in 2026 oba pomenita 1. januar 2026, 00:00:00. Ko obe vrednosti nosita pas, sta pred primerjavo pretvorjeni v UTC: D:20260827093659+08'00' je enako 2026-08-27T01:36:59Z. Ko nobena ne nosi pasu, so lokalne komponente primerjane tako, kot so zapisane. Ko le ena stran ima pas, je rezultat pamsValueMismatch, ker bi izmišljanje odmika bilo ugibanje. Necel del sekunde, ki ni nič, kot je .250 v XMP, prav tako vsili nesorazmerje, ker ga datum PDF ne zna izraziti in bi ga tiho zaokroževanje proč skrilo pravo nesoglasje; .000 je sprejet. Nerazčlenljive vrednosti so prijavljene ločeno kot pamsInvalidInfoDate ali pamsInvalidXmpDate

Kako PDFium Component v Delphiju odloči, da sta datum Info PDF in datum XMP enaka: dva pasova se pretvorita v UTC in primerjata trenutka, dve vrednosti brez pasu se primerjata tako, kot sta zapisani, en sam pas je pamsValueMismatch, necel del sekunde, ki ni nič, se ne da izraziti, nerazčlenljive vrednosti pa se prijavijo ločeno
Enakost pomeni isti trenutek do sekunde z istim znanjem o časovnem pasu na obeh straneh, zato bi izmišljanje odmika ali zaokroževanje ulomka sekunde proč skrilo pravo nesoglasje

Prisotnost ima svoje pravilo. TPdfAMetadataValues.Present je množica, napolnjena z obhodom slovarja /Info dejavnega trailerja, in ločuje »ključ manjka« od »ključ prisoten s praznim nizom«. Manjkajoči ključ da pamsNotRequired in od XMP ne zahteva ničesar; /Title () je prisoten, zato mora paket XMP nositi tudi prazen naslov x-default

Kako pregledate metapodatke Info in XMP, preden shranite?

TPdf.InspectPdfAMetadata vrne TPdfAMetadataReport z enim TPdfAMetadataComparison na polje, vsak nosi vrednost Info, vrednost XMP in TPdfAMetadataState, tako da spodletelost lahko razložite brez povratnega inženiringa posamezne validacijske zastavice. MismatchFields povzame spodleteli nabor, HasXmpPacket pove, ali je bil paket najden, XmpParseError pa nosi sporočilo razčlenjevalnika, kadar paket obstaja, a ga ni mogoče brati

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;

Kaj NormalizePdfAMetadata spremeni in česa odklanja?

TPdf.NormalizePdfAMetadata slovar Info obravnava kot vir resnice in prepisuje le lastnosti XMP, katerih polje je pristalo v MismatchFields; vse ostalo v paketu preživi. Title in Subject sta zapisana v vnos x-default, medtem ko ostanejo druge jezikovne alternative nedotaknjene, Author postane enovnosni rdf:Seq, neznani imenski prostori in nesorodne lastnosti so ohranjene, lastnosti XMP za manjkajoče ključe Info pa ostanejo pri miru. Datum Info s časovnim pasom je zapisan kot kanonični datum XMP UTC s pripono Z; tisti brez pasu obdrži svoje lokalne komponente. Preobremenitev za datoteko shrani skozi začasno datoteko in atomsko zamenjavo, posodobitev XMP sama pa se pripne kot inkrementalna posodobitev

Kaj NormalizePdfAMetadata prepisuje pri popravljanju metapodatkov PDF/A v Delphiju s PDFium Component: Info je vir resnice, nazaj se zapišejo le vnosi MismatchFields, kot besedilo Alt x-default, enovnosni Seq ali kanonični datum UTC, neznani imenski prostori, nesorodne lastnosti in lastnosti manjkajočih ključev pa preživijo nedotaknjeni
Popravek odklanja manjkajoči paket XMP, nepravilno oblikovan datum Info in podpisane dokumente, ker je gradnja popolnega nabora metapodatkov PDF/A naloga SaveAsPdfA in ne usmerjenega popravka enakovrednosti

Zavrnitve so bile namerne. Brez paketa XMP metoda sproži EPdfError, ker je gradnja popolne identifikacije PDF/A in nabora metapodatkov naloga SaveAsPdfA, pokrite v ustvarjanju arhivskih datotek PDF/A s komponento PDFium Component. Nepravilno oblikovan datum Info sproži EPdfXmpError, namesto da bi zapisal verjetno videčo napačno vrednost, in nič ni shranjeno. Podpisani dokumenti so zavrnjeni, razen če klicnik podate AllowSignedDocument = True. Enakovrednost je eno izmed pravil ISO 19005-1, normalizirana datoteka pa zato ni samodejno skladna

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;                      // že usklajeno, datoteko pustite pri 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      // nepravilno oblikovan datum Info ali neberljiv paket
        raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
    end;
  finally
    Pdf.Free;
  end;
end;

Zagon primerjave na lastnem paketu XMP

ComparePdfAInfoAndXmp in SynchronizePdfAInfoToXmp sta navadni funkciji v FPdfPdfa, ki delujeta na TPdfXmpPacket brez naloženega dokumenta, kar ustreza enotskim testom in cevovodom, ki sestavljajo XMP iz predloge. Ena sama past je Present: zapis, inicializiran s Default(TPdfAMetadataValues), ima prazno množico, vsako polje potem poroča pamsNotRequired in primerjava gre skozi povsem prazno, ne glede na to, katere vrednosti ste vnesli

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 odloča, katera polja so obvezna; same vrednosti so ignorirane
  Info.Present := [pamfTitle, pamfAuthor, pamfModDate];

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

Če Vaš cevovod arhivira dokumente, ki jih drugi sistemi kar naprej urejajo, združite nočni prehod InspectPdfAMetadata s NormalizePdfAMetadata za datoteke, ki zdrsnijo, ValidatePdfA pa ohranite kot pregrado, preden karkoli odpotuje v dolgoročno hrambo. Tipizirano poročilo, pot popravila in preostalo orodjarino PDF/A pošilja PDFium Component za Delphi in C++Builder