PDFium Component kontrollerar PDF/A Info-till-XMP-metadataekvivalens med TPdf.InspectPdfAMetadata och lagar den med TPdf.NormalizePdfAMetadata. ISO 19005-1 (som korrigerats av Cor.1) kräver att vart och ett av de åtta mappade Info-fälten, Title till ModDate, bär samma värde som sin XMP-egenskap, inte bara att det finns; kontrollen läser den korrekta RDF-formen, matchar namespaces efter URI och jämför datum som tidpunkter
Felrapporten som brukar inleda det här samtalet ser ofarlig ut. Ett dokumenthanteringssystem stämplar en ny /ModDate i Info-ordboken vid varje inkrementell sparning, lämnar XMP-paketet orört, och ett halvår senare flaggar en arkivgranskning tusentals filer som inkonforma. Båda datumen finns där. De slutade bara instämma vid den första redigeringen, och en närvarokontroll märkte aldrig det. Titelredigeringar gjorda via ett API som bara når Info, och en Author-sträng som Finance; Controlling som något verktyg delade i två dc:creator-poster, faller på samma sätt
Varför avvisar PDF/A metadata som finns på båda ställena?
PDF/A avvisar den för att ISO 19005-1 §6.7.3 är en värdesregel, inte en närvaroregel: Tabell 1 mappar åtta Info-nycklar till XMP-egenskaper, och när en Info-nyckel väl finns måste den mappade XMP-egenskapen hålla ett ekvivalent värde. Den bytenivåskanner som beskrivs i PDF/A-preflightvalidering med PDFium Component bekräftar bara att xmp:CreateDate och xmp:ModifyDate finns (pvaiMissingXmpDates). Sedan v3.72.0 kör TPdf.ValidatePdfA dessutom hela värdesjämförelsen och lägger till pvaiInfoXmpValueMismatch i problemuppsättningen när ett XMP-paket finns men motsäger Info (ett paket som inte går att parsa räknas som motsägande). Ett saknat paket fortsätter rapporteras som pvaiMissingXmpMetadata, så de två problemen räknar aldrig dubbelt för samma defekt
Vilken RDF-form kräver varje mappad XMP-egenskap?
Vart och ett av de åtta mappningarna har en fast XMP-typ, och ett korrekt värde i fel behållare faller fortfarande. ComparePdfAInfoAndXmp i FPdfPdfa.pas slår upp egenskaper efter namespace-URI, så ett paket som binder http://purl.org/dc/elements/1.1/ till ett ovanligt prefix läses exakt som ett som använder dc. De krävda formerna är:
- Title →
dc:titleoch Subject →dc:description: ettrdf:Alt-språkalternativ, jämfört bara mot sinx-default-post (språktaggen matchas skiftlägesokänsligt); en Alt utanx-defaulträknas som saknad - Author →
dc:creator: enrdf:Seqmed exakt ett textobjekt som håller hela Info-strängen, så en semikolonseparerad författarlista förblir en enda post - Keywords →
pdf:Keywordsoch Producer →pdf:Producer(namespacehttp://ns.adobe.com/pdf/1.3/): enkla textegenskaper - Creator →
xmp:CreatorTool, CreationDate →xmp:CreateDate, ModDate →xmp:ModifyDate(namespacehttp://ns.adobe.com/xap/1.0/): enkla textegenskaper
<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>
Textvärden jämförs som exakta Unicode-kodpunktssekvenser, utan trimning, skiftlägesomvandling eller normalisering. Ett avslutande mellanslag, eller ett prekomponerat é på ena sidan och ett dekomponerat e plus kombinerande accent på den andra, är en äkta mismatch. Info-sidan kommer alltid från PDFiums egen avkodning av PDFDocEncoding och UTF-16-text genom FPDF_GetMetaText, vilket hindrar biblioteket från att omimplementera strängavkodning och få den subtilt fel; XMP-sidan är bara så ren som de byte som producerade den, vilket är varför kodsidfallorna som korrumperar XMP-metadata under Free Pascal spelar roll här också
När är ett PDF-datum och ett XMP-datum lika?
Ett PDF-datum och ett XMP-datum är lika när de beskriver samma tidpunkt med sekundnoggrannhet, med samma tidszonskunskap på båda sidor. Båda parserna accepterar laglig reducerad precision, så D:2026 och 2026 betyder båda 1 januari 2026, 00:00:00. När båda värdena bär en zon konverteras de till UTC före jämförelsen: D:20260827093659+08'00' är lika med 2026-08-27T01:36:59Z. När ingen av dem bär en zon jämförs de lokala komponenterna som skrivna. När bara ena sidan har en zon blir resultatet pamsValueMismatch, för att hitta på en offset vore en gissning. En icke-noll bråkdels sekund som .250 i XMP tvingar också fram en mismatch, eftersom ett PDF-datum inte har något sätt att uttrycka den och att tyst avrunda bort den skulle dölja en verklig oenighet; .000 accepteras. Värden som inte går att parsa rapporteras separat som pamsInvalidInfoDate eller pamsInvalidXmpDate
Närvaro har sin egen regel. TPdfAMetadataValues.Present är en mängd som fylls genom att gå igenom den aktiva trailerns /Info-ordbok, och den håller "nyckel saknas" isär från "nyckel finns med tom sträng". En saknad nyckel ger pamsNotRequired och kräver ingenting av XMP; /Title () finns, så XMP-paketet måste bära en tom x-default-titel också
Hur inspekterar du Info- och XMP-metadata innan sparning?
TPdf.InspectPdfAMetadata returnerar en TPdfAMetadataReport med en TPdfAMetadataComparison per fält, vart och ett med Info-värdet, XMP-värdet och en TPdfAMetadataState, så ett misslyckande kan förklaras utan att baklängeskonstruera en enda valideringsflagga. MismatchFields sammanfattar den fallande uppsättningen, HasXmpPacket talar om huruvida ett paket hittades, och XmpParseError bär parsermeddelandet när paketet finns men inte kan läsas
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;
Vad ändrar NormalizePdfAMetadata, och vad vägrar det?
TPdf.NormalizePdfAMetadata behandlar Info-ordboken som sanningskällan och skriver bara om de XMP-egenskaper vars fält hamnade i MismatchFields; allt annat i paketet överlever. Title och Subject skrivs in i x-default-posten medan andra språkalternativ lämnas intakta, Author blir en rdf:Seq med en post, okända namespaces och orelaterade egenskaper bevaras, och XMP-egenskaper för saknade Info-nycklar lämnas orörda. Ett Info-datum med zon skrivs som ett kanoniskt UTC XMP-datum med suffixet Z; ett zonlöst behåller sina lokala komponenter. Filöverlagringen sparar via en tillfällig fil och en atomär ersättning, och XMP-uppdateringen själv läggs till som en inkrementell uppdatering
Vägrarna är medvetna. Utan XMP-paket kastar metoden EPdfError, för att bygga en komplett PDF/A-identifierings- och metadatamängd är SaveAsPdfA:s jobb, som tas upp i att skapa PDF/A-arkivfiler med PDFium Component. Ett felformat Info-datum kastar EPdfXmpError i stället för att skriva ett felaktigt värde som ser trovärdigt ut, och ingenting sparas. Undertecknade dokument avvisas om anroparen inte skickar AllowSignedDocument = True. Ekvivalens är för övrigt en av ISO 19005-1:s regler, så en normaliserad fil är inte automatiskt en konform sådan
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; // redan konsistent, lämna filen i fred
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 // felformat Info-datum eller oläsligt paket
raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
end;
finally
Pdf.Free;
end;
end;
Köra jämförelsen på ditt eget XMP-paket
ComparePdfAInfoAndXmp och SynchronizePdfAInfoToXmp är vanliga funktioner i FPdfPdfa som arbetar på ett TPdfXmpPacket utan något laddat dokument, vilket passar enhetstester och pipelines som bygger XMP från en mall. Den enda fällan är Present: en post initierad med Default(TPdfAMetadataValues) har en tom mängd, varje fält rapporterar då pamsNotRequired, och jämförelsen går igenom vakant oavsett vilka värden du fyllt i
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 avgör vilka fält som är obligatoriska; värden ensamma ignoreras
Info.Present := [pamfTitle, pamfAuthor, pamfModDate];
Packet := TPdfXmpPacket.Parse(TFile.ReadAllText(TemplateFile, TEncoding.UTF8));
try
Changed := SynchronizePdfAInfoToXmp(Info, Packet);
// xmp:ModifyDate är nu 2026-08-27T01:36:59Z, dc:creator en rdf:Seq med en post
if Changed <> [] then
TFile.WriteAllBytes(TemplateFile, Packet.ToUtf8);
finally
Packet.Free;
end;
end;
Om din pipeline arkiverar dokument som andra system fortsätter redigera, para ihop en nattlig InspectPdfAMetadata-svepning med NormalizePdfAMetadata för filerna som driver, och behåll ValidatePdfA som grinden innan något lämnar för långtidsförvaring. Den typade rapporten, reparationsvägen och resten av PDF/A-verktygen levereras i PDFium Component for Delphi and C++Builder