PDFium Component tjekker PDF/A Info-til-XMP metadata-ækvivalens med TPdf.InspectPdfAMetadata og reparerer den med TPdf.NormalizePdfAMetadata. ISO 19005-1 (som rettet af Cor.1) kræver, at hver af de otte mappede Info-entries, Title gennem ModDate, bærer samme værdi som sin XMP-egenskab, ikke blot eksisterer; tjekket læser den korrekte RDF-form, matcher namespaces efter URI og sammenligner datoer som tidspunkter
Fejlrapporten, der sædvanligvis starter denne samtale, ser harmløs ud. Et dokumentstyringssystem stempler en ny /ModDate ind i Info-dictionaryen ved hver inkrementel gemning, lader XMP-pakken være, og seks måneder senere flagger en arkivrevision tusindvis af filer som ikke-konforme. Begge datoer er der. De holdt bare op med at være enige ved den første redigering, og et tilstedeværelsestjek lagde aldrig mærke til det. Titelredigeringer foretaget gennem et Info-only API, og en Author-streng som Finance; Controlling, som et eller andet værktøj delte i to dc:creator-items, fejler på samme måde
Hvorfor afviser PDF/A metadata, der findes begge steder?
PDF/A afviser det, fordi ISO 19005-1 §6.7.3 er en værdiregel, ikke en tilstedeværelseregel: Tabel 1 mapper otte Info-nøgler til XMP-egenskaber, og så snart en Info-nøgle er til stede, skal den mappede XMP-egenskab holde en ækvivalent værdi. Den byteniveau-scanner, der er beskrevet i PDF/A preflight-validering med PDFium Component, bekræfter kun, at xmp:CreateDate og xmp:ModifyDate eksisterer (pvaiMissingXmpDates). Siden v3.72.0 kører TPdf.ValidatePdfA desuden den fulde værdisammenligning og tilføjer pvaiInfoXmpValueMismatch til issue-sættet, når en XMP-pakke eksisterer, men er uenig med Info (en pakke, der ikke kan parses, tæller som uenig). En manglende pakke forbliver rapporteret som pvaiMissingXmpMetadata, så de to issues tæller aldrig samme defekt dobbelt
Hvilken RDF-form skal hver mappede XMP-egenskab have?
Hver af de otte mappinger har en fast XMP-type, og en korrekt værdi i den forkerte container fejler stadig. ComparePdfAInfoAndXmp i FPdfPdfa.pas slår egenskaber op efter namespace-URI, så en pakke, der binder http://purl.org/dc/elements/1.1/ til et usædvanligt præfiks, læses præcis som en, der bruger dc. De krævede former er:
- Title →
dc:titleog Subject →dc:description: etrdf:Altsprog-alternativ, sammenlignet kun med sitx-default-item (sprogtagget matches case-insensitivt); en Alt udenx-defaulttæller som manglende - Author →
dc:creator: enrdf:Seqmed præcis ét tekstitem, der holder hele Info-strengen, så en semikolon-adskilt forfatterliste forbliver ét enkelt entry - Keywords →
pdf:Keywordsog Producer →pdf:Producer(namespacehttp://ns.adobe.com/pdf/1.3/): simple tekstegenskaber - Creator →
xmp:CreatorTool, CreationDate →xmp:CreateDate, ModDate →xmp:ModifyDate(namespacehttp://ns.adobe.com/xap/1.0/): simple tekstegenskaber
<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>
Tekstværdier sammenlignes som eksakte Unicode kodepunkt-sekvenser, uden trimming, case folding eller normalisering. Et efterstillede mellemrum, eller et precomposed é på den ene side og et decomposed e plus kombinerende accent på den anden, er en ægte mismatch. Info-siden kommer altid fra PDFiums egen dekodning af PDFDocEncoding og UTF-16-tekst gennem FPDF_GetMetaText, hvilket holder biblioteket fra selv at genimplementere strengdekodning og få det subtilt forkert; XMP-siden er kun så ren som de bytes, der producerede den, hvilket er grunden til, at codepage-fælderne, der korrumperer XMP-metadata under Free Pascal, også tæller her
Hvornår er en PDF-dato og en XMP-dato ens?
En PDF-dato og en XMP-dato er ens, når de beskriver samme tidspunkt på sekundet, med samme tidszoneviden på begge sider. Begge parsere accepterer lovlig reduceret præcision, så D:2026 og 2026 betyder begge 1. januar 2026, 00:00:00. Bærer begge værdier en zone, konverteres de til UTC før sammenligning: D:20260827093659+08'00' er lig 2026-08-27T01:36:59Z. Bærer ingen af dem en zone, sammenlignes de lokale komponenter, som de er skrevet. Har kun den ene side en zone, bliver resultatet pamsValueMismatch, for at opfinde et offset ville være et gæt. En ikke-nul brøkdel af et sekund, såsom .250 i XMP, tvinger også en mismatch frem, da en PDF-dato ikke kan udtrykke den, og stille at runde den væk ville skjule en reel uenighed; .000 accepteres. Uparsebare værdier rapporteres separat som pamsInvalidInfoDate eller pamsInvalidXmpDate
Tilstedeværelse har sin egen regel. TPdfAMetadataValues.Present er et sæt fyldt ved at gennemløbe den aktive trailers /Info-dictionary, og det holder "nøgle fraværende" adskilt fra "nøgle til stede med en tom streng". En fraværende nøgle giver pamsNotRequired og kræver intet af XMP; /Title () er til stede, så XMP-pakken skal bære en tom x-default-titel også
Hvordan inspicerer man Info- og XMP-metadata før gemning?
TPdf.InspectPdfAMetadata returnerer en TPdfAMetadataReport med én TPdfAMetadataComparison pr. felt, hver med Info-værdien, XMP-værdien og en TPdfAMetadataState, så en fejl kan forklares uden at reverse-engineere et enkelt valideringsflag. MismatchFields opsummerer det fejlande sæt, HasXmpPacket fortæller dig, om en pakke blev fundet, og XmpParseError bærer parser-beskeden, når pakken eksisterer, men ikke kan læses
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;
Hvad ændrer NormalizePdfAMetadata, og hvad nægter den?
TPdf.NormalizePdfAMetadata behandler Info-dictionaryen som sandhedskilden og omskriver kun de XMP-egenskaber, hvis felt havnede i MismatchFields; alt andet i pakken overlever. Title og Subject skrives ind i x-default-itemet, mens andre sprog-alternativer forbliver intakte, Author bliver til en ét-item rdf:Seq, ukendte namespaces og urelaterede egenskaber bevares, og XMP-egenskaber for fraværende Info-nøgler efterlades urørt. En Info-dato med zone skrives som en kanonisk UTC XMP-dato med Z-suffiks; en zone-løs beholder sine lokale komponenter. Fil-overloaden gemmer gennem en midlertidig fil og en atomær erstatning, og selve XMP-opdateringen tilføjes som en inkrementel opdatering
Afvisningerne er bevidste. Uden en XMP-pakke rejser metoden EPdfError, for at bygge et komplet PDF/A-identifikations- og metadata-sæt er SaveAsPdfA's opgave, dækket i at skabe PDF/A arkivfiler med PDFium Component. En misdannet Info-dato rejser EPdfXmpError i stedet for at skrive en plausibel forkert værdi, og intet gemmes. Signerede dokumenter afvises, medmindre kalderen giver AllowSignedDocument = True med. Ækvivalens er også én regel i ISO 19005-1, så en normaliseret fil er ikke automatisk en konform en
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; // allerede konsistent, rør ikke filen
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 // misdannet Info-dato eller ulæselig pakke
raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
end;
finally
Pdf.Free;
end;
end;
At køre sammenligningen på din egen XMP-pakke
ComparePdfAInfoAndXmp og SynchronizePdfAInfoToXmp er almindelige funktioner i FPdfPdfa, der virker på en TPdfXmpPacket uden dokument indlæst, hvilket passer til unit-teste og pipelines, der samler XMP fra en skabelon. Den ene fælde er Present: en record initialiseret med Default(TPdfAMetadataValues) har et tomt sæt, hvert felt rapporterer så pamsNotRequired, og sammenligningen består vakuant, uanset hvilke værdier du fyldte ind
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 beslutter, hvilke felter er obligatoriske; værdier alene ignoreres
Info.Present := [pamfTitle, pamfAuthor, pamfModDate];
Packet := TPdfXmpPacket.Parse(TFile.ReadAllText(TemplateFile, TEncoding.UTF8));
try
Changed := SynchronizePdfAInfoToXmp(Info, Packet);
// xmp:ModifyDate er nu 2026-08-27T01:36:59Z, dc:creator en ét-item rdf:Seq
if Changed <> [] then
TFile.WriteAllBytes(TemplateFile, Packet.ToUtf8);
finally
Packet.Free;
end;
end;
Arkiverer din pipeline dokumenter, som andre systemer vedbliver med at redigere, så par en natlig InspectPdfAMetadata-omgang med NormalizePdfAMetadata for de filer, der driver, og behold ValidatePdfA som gaten, før noget går til langtidsopbevaring. Den typede rapport, reparationsstien og resten af PDF/A-værktøjet leveres i PDFium Component for Delphi and C++Builder