PDFium Component tikrina PDF/A Info ir XMP metaduomenų atitikimą su TPdf.InspectPdfAMetadata ir sutvarko jį su TPdf.NormalizePdfAMetadata. ISO 19005-1 (su Cor.1 pataisomis) reikalauja, kad kiekvienas iš aštuonių susietų Info įrašų — nuo Title iki ModDate — neštų tokią pačią reikšmę kaip jo XMP savybė, o ne tiesiog egzistuotų; patikra skaito teisingą RDF formą, vardų erdves gretina pagal URI, o datas lygina kaip momentus
Defektų pranešimas, paprastai atveriantis šį pokalbį, atrodo nekenksmingas. Dokumentų valdymo sistema kiekvieno inkrementinio įrašymo metu įspaudžia naują /ModDate į Info žodyną, XMP paketo neliečia, ir po šešių mėnesių archyvo auditas pažymi tūkstančius failų kaip neatitinkančius. Abi datos yra. Jos tiesiog nustojo sutarti pirmajame redagavime, o buvimo patikra to niekada nepastebėjo. Title redagavimai per tik Info palaikančią API ir Author eilutė, tokia kaip Finance; Controlling, kurią kažkoks įrankis perskyrė į du dc:creator elementus, klysta taip pat
Kodėl PDF/A atmeta metaduomenis, esančius abiejose vietose?
PDF/A to neatmeta, nes ISO 19005-1 §6.7.3 yra reikšmės taisyklė, o ne buvimo taisyklė: 1 lentelė susieja aštuonis Info raktus su XMP savybėmis, ir kai Info raktas jau yra, susietoji XMP savybė privalo turėti atitikmenį. Baitų lygio skeneris, aprašytas PDF/A preflight patikroje su PDFium Component, tik patvirtina, kad xmp:CreateDate ir xmp:ModifyDate egzistuoja (pvaiMissingXmpDates). Nuo v3.72.0 TPdf.ValidatePdfA papildomai atlieka pilną reikšmių palyginimą ir prideda pvaiInfoXmpValueMismatch prie problemų aibės, kai XMP paketas yra, bet nesutampa su Info (neparsėjamas paketas skaitomas kaip nesutampantis). Trūkstamas paketas lieka pranešamas kaip pvaiMissingXmpMetadata, tad dvi problemos niekada nedubliuoja to paties defekto
Kokios RDF formos reikia kiekvienai susietai XMP savybei?
Kiekvienas iš aštuonių susiejimų turi fiksuotą XMP tipą, ir teisinga reikšmė netinkamame konteineryje vis tiek žlunga. ComparePdfAInfoAndXmp faile FPdfPdfa.pas savybes suranda pagal vardų erdvės URI, tad paketas, pririšantis http://purl.org/dc/elements/1.1/ prie neįprasto priešdėlio, skaitomas lygiai taip pat kaip naudojantis dc. Reikalingos formos yra:
- Title →
dc:titleir Subject →dc:description:rdf:Altkalbinė alternatyva, lyginama tik su josx-defaultelementu (kalbos žyma gretinama neatsižvelgiant į raidžių dydį); Alt bex-defaultskaitomas kaip trūkstamas - Author →
dc:creator:rdf:Seqsu lygiai vienu teksto elementu, laikančiu visą Info eilutę, tad kabliataškiu skiriama autorių sąrašas lieka vienu įrašu - Keywords →
pdf:Keywordsir Producer →pdf:Producer(vardų erdvėhttp://ns.adobe.com/pdf/1.3/): paprastos teksto savybės - Creator →
xmp:CreatorTool, CreationDate →xmp:CreateDate, ModDate →xmp:ModifyDate(vardų erdvėhttp://ns.adobe.com/xap/1.0/): paprastos teksto savybės
<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>
Tekstinės reikšmės lyginamos kaip tikslios Unicode kodo taškų sekos, be jokio apkarpytmo, raidžių dydžio suvienodinimo ar normalizacijos. Tarpas gale arba sukomponuota é vienoje pusėje ir išardyta e su jungiamuoju kirčiu kitoje — tikras nesutapimas. Info pusė visada ateina iš PDFium pačios PDFDocEncoding ir UTF-16 teksto dekodavimo per FPDF_GetMetaText, kas neleidžia bibliotekai iš naujo įgyvendinti eilučių dekodavimo ir padaryti subtilių klaidų; XMP pusė tokia švari, kokie buvo baitai, iš kurių ji atsirado, todėl koduoklių spąstai, gadinantys XMP metaduomenis Free Pascal, čia irgi svarbūs
Kada PDF data ir XMP data yra lygios?
PDF data ir XMP data lygios, kai abi aprašo tą patį momentą iki sekundės, su vienoda laiko juostos žinote abiejose pusėse. Abu parseriai priima teisėtą sutrumpintą tikslumą, tad D:2026 ir 2026 abu reiškia 2026 metų sausio 1, 00:00:00. Kai abi reikšmės neša laiko juostą, jos prieš palyginimą konvertuojamos į UTC: D:20260827093659+08'00' lygi 2026-08-27T01:36:59Z. Kai nei viena neša laiko juostos, vietiniai komponentai lyginami tokie, kokie parašyti. Kai tik viena pusė turi laiko juostą, rezultatas pamsValueMismatch, nes išgalvotas poslinkis būtų spėjimas. Ne nulinė trupmeninė sekundė, tokia kaip .250 XMP, irgi priverčia nesutapimą, nes PDF data neturi kaip jos išreikšti, o tyliai suapvalinus būtų paslėptas tikras nesutarimas; .000 priimama. Neparsėjamos reikšmės pranešamos atskirai kaip pamsInvalidInfoDate arba pamsInvalidXmpDate
Buvimas turi savo taisyklę. TPdfAMetadataValues.Present yra aibė, užpildoma apeinant aktyvaus trailer /Info žodyną, ir ji „rakto nėra“ laiko atskirai nuo „raktas yra su tuščia eilute“. Trūkstamas raktas duoda pamsNotRequired ir iš XMP nereikalauja nieko; /Title () yra, tad XMP paketas privalo nešti ir tuščią x-default pavadinimą
Kaip apžiūrėti Info ir XMP metaduomenis prieš išsaugant?
TPdf.InspectPdfAMetadata grąžina TPdfAMetadataReport su vienu TPdfAMetadataComparison laukui, kiekvienas laikantis Info reikšmę, XMP reikšmę ir TPdfAMetadataState, tad nesėkmę galima paaiškinti neįmintinant nė vienos patikros žymės. MismatchFields apibendrina žlungančią aibę, HasXmpPacket pasako, ar paketas rastas, o XmpParseError neša parserio pranešimą, kai paketas yra, bet neperskaitomas
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;
Ką keičia NormalizePdfAMetadata ir ką atsisako?
TPdf.NormalizePdfAMetadata Info žodyną laiko tiesos šaltiniu ir perrašo tik tas XMP savybes, kurių laukas pakliuvo į MismatchFields; visa kita pakete išgyvena. Title ir Subject rašomi į x-default elementą, kol kitos kalbinės alternatyvos lieka nepaliestos, Author tampa vieno elemento rdf:Seq, nežinomos vardų erdvės ir nesusijusios savybės išsaugomos, o XMP savybės trūkstamiems Info raktams paliekamos nelietos. Info data su laiko juosta rašoma kaip kanoninė UTC XMP data su Z priesaga; be juostos — išlaiko savo vietinius komponentus. Failo perkrova išsaugo per laikinąjį failą ir atomišką pakeitimą, o pats XMP atnaujinimas prijungiamas kaip inkrementinis atnaujinimas
Atsisakymai sąmoningi. Be XMP paketo metodas kelia EPdfError, nes pilno PDF/A identifikacijos ir metaduomenų rinkinio sudarymas yra SaveAsPdfA darbas, aptartas PDF/A archyvinių failų kūrime su PDFium Component. Sugadinta Info data kelia EPdfXmpError, užuot įrašiusi tikėtinai atrodančią klaidingą reikšmę, ir niekas neišsaugoma. Pasirašyti dokumentai atmetami, nebent kvietėjas perduoda AllowSignedDocument = True. Atitikimas yra viena ISO 19005-1 taisyklė, tad suvienodintas failas dar nėra automatiškai atitinkantis
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; // jau suderinta, failo neliečiame
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 // sugadinta Info data ar neperskaitomas paketas
raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
end;
finally
Pdf.Free;
end;
end;
Palyginimo paleidimas savo XMP pakete
ComparePdfAInfoAndXmp ir SynchronizePdfAInfoToXmp yra paprastos funkcijos FPdfPdfa, dirbančios su TPdfXmpPacket be įkėto dokumento, kas tinka vienetų testams ir srautams, surenkantiems XMP iš šablono. Vienas spąstų yra Present: įrašas, inicializuotas su Default(TPdfAMetadataValues), turi tuščią aibę, kiekvienas laukas tada praneša pamsNotRequired, ir palyginimas praeina tuščiai, kokių besiveršių būtumėte užpildęs
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 nusprendžia, kurie laukai privalomi; vienos reikšmės ignoruojamos
Info.Present := [pamfTitle, pamfAuthor, pamfModDate];
Packet := TPdfXmpPacket.Parse(TFile.ReadAllText(TemplateFile, TEncoding.UTF8));
try
Changed := SynchronizePdfAInfoToXmp(Info, Packet);
// xmp:ModifyDate dabar 2026-08-27T01:36:59Z, dc:creator — vieno elemento rdf:Seq
if Changed <> [] then
TFile.WriteAllBytes(TemplateFile, Packet.ToUtf8);
finally
Packet.Free;
end;
end;
Jei jūsų srautas archyvuoja dokumentus, kuriuos kitos sistemos nuolat redaguoja, derinkite naktinį InspectPdfAMetadata apžiūrėjimą su NormalizePdfAMetadata tiems failams, kurie nuslysta, ir laikykite ValidatePdfA vartais, dar ką nors išleisdami į ilgalaikį saugojimą. Tipizuota ataskaita, taisymo kelias ir likusi PDF/A įrankių dalis atkeliauja su PDFium Component for Delphi and C++Builder