PDFium Component tarkistaa PDF/A:n Info-XMP-metadatan vastaavuuden TPdf.InspectPdfAMetadatailla ja korjaa sen TPdf.NormalizePdfAMetadatailla. ISO 19005-1 (Cor.1:n oikaisemana) vaatii jokaisen kahdeksan yhdistetyn Info-merkinnän, Titlesta ModDateen, kantavan samaa arvoa kuin sen XMP-ominaisuus, ei ainoastaan olemassaoloa; tarkistus lukee oikean RDF-muodon, vertaa nimiavaruuksia URI:lla ja vertaa päivämääriä hetkinä
Vikaraportti joka yleensä käynnistää tämän keskustelun näyttää harmitomalta. Asiakirjanhallintajärjestelmä leimaa uuden /ModDatein Info-sanakirjaan jokaisella inkrementaalisella tallennuksella, jättää XMP-paketin rauhaan, ja puoli vuotta myöhemmin arkistotarkastus liputtaa tuhansia tiedostoja vaatimustenvastaisiksi. Molemmat päivämäärät ovat siellä. Ne vain lakkasivat sopimasta ensimmäisessä muokkauksessa, eikä läsnäolotarkistus koskaan huomannut. Title-muokkaukset pelkän Info-API:n kautta sekä Author-merkkijono kuten Finance; Controlling jonka jokin työkalu jakoi kahdeksi dc:creator-merkinnäksi epäonnistuvat samalla tavalla
Miksi PDF/A hylkää metadatan joka on olemassa molemmissa paikoissa?
PDF/A hylkää sen koska ISO 19005-1 §6.7.3 on arvosääntö, ei läsnäolosääntö: Taulukko 1 yhdistää kahdeksan Info-avainta XMP-ominaisuuksiin, ja kun Info-avain on läsnä, yhdistetyn XMP-ominaisuuden on pidettävä vastaavaa arvoa. Artikkelissa PDF/A-preflight-validointi PDFium Componentilla kuvattu tavutason skanneri vain varmistaa että xmp:CreateDate ja xmp:ModifyDate ovat olemassa (pvaiMissingXmpDates). Versiosta v3.72.0 lähtien TPdf.ValidatePdfA ajaa lisäksi koko arvovertailun ja lisää pvaiInfoXmpValueMismatchin ongelmajoukkoon kun XMP-paketti on olemassa mutta on eri mieltä Infosta (jäsentymätön paketti lasketaan eri mieltä olevaksi). Puuttuva paketti raportoidaan yhä pvaiMissingXmpMetadataina, joten kaksi ongelmaa eivät koskaan laske samaa vikaa kahdesti
Mitä RDF-muotoa jokainen yhdistetty XMP-ominaisuus tarvitsee?
Jokaisella kahdeksasta yhdistämisestä on kiinteä XMP-tyyppi, ja oikea arvo väärässä säiliössä epäonnistuu silti. ComparePdfAInfoAndXmp tiedostossa FPdfPdfa.pas etsii ominaisuudet nimiavaruuden URI:lla, joten paketti joka sitoo http://purl.org/dc/elements/1.1/in epätavalliseen etuliitteeseen luetaan täsmälleen kuten dcää käyttävä. Vaaditut muodot ovat:
- Title →
dc:titleja Subject →dc:description:rdf:Alt-kielivaihtoehto, vertaillaan vain senx-default-merkintää vasten (kielitag täsmäytetään kirjainkokoa riippumattomasti); Alt ilmanx-defaultia lasketaan puuttuvaksi - Author →
dc:creator:rdf:Seqjossa on täsmälleen yksi tekstimerkintä joka pitää koko Info-merkkijonon, joten puolipisteillä erotettu author-luettelo pysyy yhtenä merkintänä - Keywords →
pdf:Keywordsja Producer →pdf:Producer(nimiavaruushttp://ns.adobe.com/pdf/1.3/): yksinkertaisia tekstiominaisuuksia - Creator →
xmp:CreatorTool, CreationDate →xmp:CreateDate, ModDate →xmp:ModifyDate(nimiavaruushttp://ns.adobe.com/xap/1.0/): yksinkertaisia tekstiominaisuuksia
<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>
Tekstiarvot vertaillaan tarkkoina Unicode-koodipisteseensseinä ilman trimmausta, kirjainkokojen muunnosta tai normalisointia. Loppu välilyönti, tai prekompositoitu é toisella puolella ja dekompositoitu e plus yhdistävä aksentti toisella, on aito ristiriita. Infopuoli tulee aina PDFiumin omasta PDFDocEncoding- ja UTF-16-tekstin dekoodauksesta FPDF_GetMetaTextin kautta, mikä estää kirjastoa toteuttamasta merkkijonodekoodausta uudelleen ja saamasta sitä hienovaraisesti väärin; XMP-puoli on yhtä puhdas kuin sitä tuottaneet tavut, minkä vuoksi koodisivuansat jotka korruptoivat XMP-metadataa Free Pascalissa merkitsevät täälläkin
Milloin PDF-päivämäärä ja XMP-päivämäärä ovat yhtä suuret?
PDF-päivämäärä ja XMP-päivämäärä ovat yhtä suuret kun ne kuvaavat saman hetken sekunnin tarkkuudella samalla aikavyöhyketiedolla molemmilla puolilla. Molemmat jäsennykset hyväksyvät laillisen kutistetun tarkkuuden, joten D:2026 ja 2026 tarkoittavat molemmat 1. tammikuuta 2026 kello 00:00:00. Kun molemmat arvot kantavat vyöhykettä, ne konvertoitu UTC:hin ennen vertailua: D:20260827093659+08'00' on yhtä suuri kuin 2026-08-27T01:36:59Z. Kun kumpikaan ei kanna vyöhykettä, paikalliset komponentit vertaillaan kirjoitettuina. Kun vain toisella puolella on vyöhyke, tulos on pamsValueMismatch, koska siirtymän keksiminen olisi arvaus. Nollasta poikkeava murtosekunti kuten .250 XMP:ssä pakottaa myös ristiriidan, koska PDF-päivämäärällä ei ole tapaa ilmaista sitä ja hiljainen pyöristys piilottaisi todellisen erimielisyyden; .000 hyväksytään. Jäsennyskelvottomat arvot raportoidaan erikseen pamsInvalidInfoDateina tai pamsInvalidXmpDateina
Läsnäololla on oma sääntönsä. TPdfAMetadataValues.Present on joukko joka täyttyy käymällä aktiivisen trailerin /Info-sanakirjan läpi, ja se pitää ”avain puuttuu” erillään ”avain läsnä tyhjällä merkkijonolla”. Puuttuva avain tuottaa pamsNotRequiredin eikä vaadi mitään XMP:ltä; /Title () on läsnä, joten XMP-paketin on kannettava myös tyhjä x-default-title
Miten tarkastelet Info- ja XMP-metadataa ennen tallennusta?
TPdf.InspectPdfAMetadata palauttaa TPdfAMetadataReportin yhdellä TPdfAMetadataComparisonilla kenttää kohti, kukin kantaa Info-arvon, XMP-arvon ja TPdfAMetadataStaten, joten epäonnistuminen voidaan selittää kääntämättä yhtään validointilippua takaperin. MismatchFields tiivistää epäonnistuvan joukon, HasXmpPacket kertoo löytyikö pakettia, ja XmpParseError kantaa jäsentimen viestin kun paketti on olemassa mutta sitä ei voi lukea
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;
Mitä NormalizePdfAMetadata muuttaa ja mistä se kieltäytyy?
TPdf.NormalizePdfAMetadata käsittelee Info-sanakirjan totuuden lähteenä ja kirjoittaa uudelleen vain ne XMP-ominaisuudet joiden kenttä päätyi MismatchFieldsiin; kaikki muu paketissa selviää. Title ja Subject kirjoitetaan x-default-merkintään kun taas muut kielivaihtoehdot pysyvät ehjinä, Author muuttuu yhden merkinnän rdf:Seqiksi, tuntemattomat nimiavaruudet ja asiaan liittymättömät ominaisuudet säilytetään, ja puuttuvien Info-avainten XMP-ominaisuuksia ei kosketeta. Vyöhykkeellinen Info-päivämäärä kirjoitetaan kanoniseksi UTC-XMP-päivämääräksi Z-päätteellä; vyöhykkeetön säilyttää paikalliset komponenttinsa. Tiedosto-overload tallentaa väliaikaisen tiedoston ja atomin korvauksen kautta, ja itse XMP-päivitys liitetään inkrementaalisena päivityksenä
Kieltäytymiset ovat tahallisia. Ilman XMP-pakettia metodi nostaa EPdfErrorin, koska täydellisen PDF/A-identifikaation ja metadatasetin rakentaminen on SaveAsPdfAn työtä, josta kertoo PDF/A-arkistotiedostojen luominen PDFium Componentilla. Muotoillut Info-päivämäärä nostaa EPdfXmpErrorin sen sijaan että kirjoittaisi uskottavan näköisen väärän arvon, eikä mitään tallenneta. Allekirjoitetut asiakirjat hylätään ellet kutsuja välitä AllowSignedDocument = True:ta. Vastaavuus on myös yksi ISO 19005-1:n säännöistä, joten normalisoitu tiedosto ei ole automaattisesti vaatimustenmukainen
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; // jo yhdenmukainen, jätä tiedosto rauhaan
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 // muotoillut Info-päivämäärä tai lukukelvoton paketti
raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
end;
finally
Pdf.Free;
end;
end;
Vertailun ajaminen omalle XMP-paketillesi
ComparePdfAInfoAndXmp ja SynchronizePdfAInfoToXmp ovat tavallisia funktioita FPdfPdfassa jotka toimivat TPdfXmpPacketilla ilman ladattua asiakirjaa, mikä sopii yksikkötesteihin ja putkiin jotka kokoavat XMP:n mallipohjasta. Yksi ansa on Present: Default(TPdfAMetadataValues)illa alustetulla recordilla on tyhjä joukko, jolloin jokainen kenttä raportoi pamsNotRequiredin ja vertailu läpäisee tyhjänpäiväisesti riippumatta siitä mitä arvoja täytit
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 päättää mitkä kentät ovat pakollisia; pelkät arvot ohitetaan
Info.Present := [pamfTitle, pamfAuthor, pamfModDate];
Packet := TPdfXmpPacket.Parse(TFile.ReadAllText(TemplateFile, TEncoding.UTF8));
try
Changed := SynchronizePdfAInfoToXmp(Info, Packet);
// xmp:ModifyDate on nyt 2026-08-27T01:36:59Z, dc:creator yhden merkinnän rdf:Seq
if Changed <> [] then
TFile.WriteAllBytes(TemplateFile, Packet.ToUtf8);
finally
Packet.Free;
end;
end;
Jos putkesi arkistoi asiakirjoja joita muut järjestelmät jatkuvasti muokkaavat, yhdistä yöllinen InspectPdfAMetadata-kierros NormalizePdfAMetadatain kanssa ajautuneille tiedostoille ja pidä ValidatePdfA porttina ennen kuin mikään lähtee pitkäaikaissäilytykseen. Tyypitetty raportti, korjauspolku ja loput PDF/A-työkaluista toimitetaan PDFium Component for Delphi and C++Builder -komponentissa