/ModDate (D:20260827093659+08'00') taşıyan bir Info sözlüğü ile xmp:ModifyDate'i 2026-08-27T01:36:59Z olarak taşıyan bir XMP paketi aynı saniyeyi anlatır ve bir PDF/A doğrulayıcının bu çifti kabul etme için her hakkı vardır. Ama belgenin Subject dizgesini dc:subject içine kopyalayın; metin karakter karakter eşleşse bile paket başarısız olur. ISO 19005-1 §6.7.3, eşdeğer değerleri belirli XMP özelliklerinde, belirli RDF şekillerinde ister
Bu konuyu çoğunlukla açan hata raporu zararsız görünür. Bir belge yönetim sistemi, her artımlı kayıtta Info sözlüğüne taze bir /ModDate damgalayıp XMP paketini rahat bırakır. Aylar sonra bir arşiv denetimi binlerce dosyayı işaretler. İki tarih de oradadır; ilk düzenlemede anlaşmayı bırakmışlardır ve yalnızca özelliğin varlığına bakan bir denetim bunu hiç görmedi. Yalnızca Info üzerinden çalışılan bir API ile yapılan başlık düzenlemeleri de aynı şekilde başarısız olur; bazı araçların iki dc:creator öğesine böldüğü Finance; Controlling gibi bir Author dizgesi de öyle
ISO 19005-1'ın Info ve XMP'den gerektirdikleri
ISO 19005-1'ın Tablo 1'i, sekiz Info anahtarını XMP özellikleriyle eşleştirir ve §6.7.3, bir Info anahtarı varsa eşlenen XMP özelliğinin eşdeğer bir değer taşıması gerektiğini söyler. Olmayan bir anahtar hiçbir şey istemez. PDF/A-2 ile PDF/A-3, aynı sekiz anahtar için aynı eşdeğerlik gereksinimini korur. PDF/A-4 tersine gider ve Info sözlüğünün kendisini kısıtlar: yalnızca bir katalog /PieceInfo yanında görünebilir ve o zaman ModDate'ten başka bir şey taşıyamaz; TPdf.ValidatePdfA bunu pvaiPdfA4InfoWithoutPieceInfo ve pvaiPdfA4InfoInvalid üzerinden bildirir
PDFium Component'te karşılaştırma, FPdfPdfa.pas içindeki ComparePdfAInfoAndXmp'te yaşar. Her özelliği ad alanı URI'siyle arar, yani http://purl.org/dc/elements/1.1/ bağını sıra dışı bir öneke yapan bir paket, dc kullanan paketin aynısı gibi okunur. Eşlenen şekiller şunlardır:
- Title →
dc:titleve Subject →dc:description: birrdf:Altdil alternatifi, yalnızcax-defaultöğesiyle karşılaştırılır; dil etiketi büyük küçük harf duyarsız eşleşir vex-defaulttaşımayan bir Alt eksik sayılır - Author →
dc:creator: Info dizgesinin tamamını taşıyan tam olarak tek metin öğeli birrdf:Seq, böylece noktalı virgülle ayrılmış yazar listesi tek girdi olarak kalır - Keywords →
pdf:Keywordsve Producer →pdf:Producer(http://ns.adobe.com/pdf/1.3/ad alanı): basit metin özellikleri - Creator →
xmp:CreatorTool, CreationDate →xmp:CreateDate, ModDate →xmp:ModifyDate(http://ns.adobe.com/xap/1.0/ad alanı): basit metin özellikleri
Subject satırı, elle kurulmuş şablonların en sık yanıldığı yerdir. Dublin Core'un dc:subject'i anahtar sözcüklerin düzensiz bir çantasıdır (bag) ve ad, insanları Info Subject'i oraya bırakmaya davet eder. Karşılaştırma dc:description'u arar, hiçbir şey bulamaz ve doğru metin bir özellik ötede dururken Subject'i pamsXmpMissing olarak bildirir. Doğru şekillerdeki üç satır şöyle görünür:
<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>
Doğru bir dizge neden hâlâ karşılaştırmayı geçemez?
Doğru bir dizge, yanlış kaptayken ya da tek bir kod noktası farklıyken başarısız olur. Metin değerleri, kırpma, harf katlaması ya da normalization olmadan, tam Unicode kod noktası dizileri olarak karşılaştırılır. Sondaki boşluk bir uyuşmazlıktır. Bir taraftaki önceden birleşmiş é, öteki taraftaki ayrışık e artı birleştirici akut vurgu ile de uyuşmaz; iki yazım ekranda çoğu zaman aynı görünür
Şekil hataları kendilerine ait bir durum alır. Düz metin olarak yazılmış bir dc:title, rdf:Bag olarak bildirilmiş bir dc:creator ya da iki öğeli bir dc:creator Seq'si, pamsValueMismatch yerine tümüyle pamsXmpTypeMismatch üretir; böylece bir rapor, aracın yanlış RDF yapısını seçtiğini, farklı metin yazmış olmasından ayırt edebilir. İki sorun genellikle bir işlem hattının farklı yerlerinden gelir ve yanlışını onarmak bir öğleden sonrası boşa harcar
Info tarafı her zaman, TPdf.GetMetadata'ın da kullandığı yol olan FPDF_GetMetaText ile PDFium'un PDFDocEncoding ve UTF-16BE dizgelerinden kendi çözümünden gelir; dolayısıyla bayt sırası işareti taşıyan ya da kaçışlı sekizlik dizi içeren bir dizge, motorun okuduğu gibi okunur. XMP tarafı ise onu üreten baytlar kadar temizdir. Yanlış kod sayfasıyla kurulan bir paket, hiçbir karşılaştırmanın affetmeyeceği bozuk karakterler (mojibake) taşıyabilir; Free Pascal altında XMP metadata'yı bozan kod sayfası tuzakları bu yüzden aynı kontrol listesine aittir
Bir PDF tarihi ile bir XMP tarihi ne zaman eşittir?
Bir PDF tarihi ile bir XMP tarihi, her iki tarafta aynı saat dilimi bilgisini taşıyarak aynı anı saniyeye kadar anlatıyorlarsa eşittir. İki ayrıştırıcı da yasal kısaltılmış hassasiyeti kabul eder, yani D:2026 ile 2026 ikisi de 1 Ocak 2026, gece yarısı demektir. İki değer de bölge taşıdığında her biri önce UTC'ye çevrilir: D:20260827093659+08'00', 2026-08-27T01:36:59Z'ye eşittir. Hiçbiri bölge taşımadığında yerel bileşenler yazıldığı gibi karşılaştırılır
Yalnızca bir taraf bölge taşıdığında sonuç pamsValueMismatch'tur. Bölgesiz değere bir ofset uydurmak tahmin olur ve sunucunun saat dilimine denk gelen bir tahmin, bir makinede geçip bir başkasında kalırdı. XMP'deki .250 gibi sıfır olmayan kesirli saniye de uyuşmazlığı zorunlu kılar, çünkü bir PDF tarihinin onu ifade etme yolu yoktur; .000 kabul edilir. Hiç ayrıştırılamayan değerler pamsInvalidInfoDate ya da pamsInvalidXmpDate olarak bildirilir
İki tarih dil bilgisi birbirinin yerine geçmez ve ayrıştırıcılar her tarafı kendi kuralına bağlar. PDF tarafında ofset dakikalarını çevreleyen kesme imeleri isteğe bağlıdır ve Z kabul edilir. XMP tarafında ofsetin iki nokta üst üste gereksinimi vardır: 2026-08-27T09:36:59+08:00 ayrıştırılır, +0800 ise pamsInvalidXmpDate'tir. XMP saati, bir T göründüğünde en az saat ve dakikayı ister; 14 saati aşan ofsetler iki tarafta da reddedilir. Çevreleyen boşluklar, tarih ayrıştırılmadan önce kırpılır — metin alanlarına tanınmayan bir incelik
Varlığın kendi kuralı vardır. TPdfAMetadataValues.Present, etkin trailer'ın /Info sözlüğü gezilerek doldurulan bir kümedir ve anahtar yok ile anahtar var ama dizge boş ayrımını korur. Olmayan bir anahtar pamsNotRequired verir. /Title () mevcuttur, dolayısıyla paketin boş bir x-default başlığı da taşıması gerekir
Dosyaya dokunmadan önce alan bazlı raporu okumak
TPdf.InspectPdfAMetadata, alan başına bir TPdfAMetadataComparison taşıyan bir TPdfAMetadataReport döndürür. Her girdi Info değerini, XMP değerini ve bir TPdfAMetadataState taşır; böylece bir başarısızlık alan alan açıklanabilir. MismatchFields başarısız kümeyi toplar, HasXmpPacket paket bulunup bulunmadığını söyler ve XmpParseError, paket var ama okunamıyorsa ayrıştırıcı mesajını taşır. Bu son durumda mevcut her alan pamsXmpMissing bildirir, çünkü paketten hiçbir şey okunamamıştır
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;
TPdf.ValidatePdfA, aynı karşılaştırmayı sorun kümesine katınır. PDFium Component ile PDF/A preflight doğrulaması yazısında anlatılan bayt düzeyi tarama, paket her iki tarih özelliğini de taşımıyorsa pvaiMissingXmpDates ve Info CreationDate ya da ModDate taşıdığı hâlde pakette eşleşen tarih özelliği yoksa pvaiInfoXmpDateMismatch bildirir. Üstüne ValidatePdfA, tam değer karşılaştırmasını da çalıştırır ve okunamayan bir paket dahil, paket var ama Info ile uyuşmıyorsa pvaiInfoXmpValueMismatch ekler. Hiç paketi olmayan bir belge yalnızca pvaiMissingXmpMetadata olarak bildirilir, böylece bir kusur iki kez görünmez. Akış düzeyindeki ValidatePdfACompliance işlevi yalnızca bayt düzeyi taramayı yapar
function MetadataGateOk(Pdf: TPdf): Boolean;
var
Res: TPdfAValidationResult;
begin
Res := Pdf.ValidatePdfA;
if pvaiMissingXmpMetadata in Res.Issues then
Writeln('No usable XMP packet: rebuild the file with SaveAsPdfA');
if pvaiInfoXmpDateMismatch in Res.Issues then
Writeln('Info has a date property the XMP packet lacks entirely');
if pvaiInfoXmpValueMismatch in Res.Issues then
Writeln('Mapped values disagree: run InspectPdfAMetadata for details');
Result := Res.IsCompliant;
end;
NormalizePdfAMetadata'ın yeniden yazdıkları ve durduğu yer
TPdf.NormalizePdfAMetadata, Info sözlüğünü doğru kaynağı sayar ve alanı MismatchFields'e düşmüş XMP özelliklerini yeniden yazar. Title ile Subject x-default öğesine gider, diğer dil alternatifleri sağlam kalır. Author, tek öğeli bir rdf:Seq'ye dönüşür. Bölgeli bir Info tarihi, Z sonekli kurallı bir UTC XMP tarihi olarak yazılır ve bölgesiz olan yerel bileşenlerini korur. Bilinmeyen ad alanları, ilgisiz özellikler ve olmayan Info anahtarları için XMP özellikleri rahat bırakılır; yazım geri yapılırken var olan bir önek bağı yeniden kullanılır
Info'yu yetkili kılmak, gerçek dosyalarda ortaya çıkan bir bedel taşır. İki kişiyi Finance ve Controlling olarak listeleyen bir dc:creator Seq'si, Finance; Controlling yazan tek öğeyle değiştirilir ve yanlış kaptan saklanan bir dc:title, yalnızca x-default taşıyan taze bir Alt olarak yeniden kurulur; eski kabın taşıdığı her şey gider. Bu kayıp burada kabul edilebilir: Tablo 1 tam olarak tek creator öğesine izin verir, Info dizgesi listenin tamamını tek parça olarak zaten taşır ve zengin listeyi isteyen biri için özgün paket kaynak dosyanın içindedir
Reddetmeler kasıtlıdır. XMP paketi yokken yöntem EPdfError fırlatır: eksiksiz bir PDF/A kimlikleme ve metadata seti kurmak, PDFium Component ile PDF/A arşiv dosyaları oluşturma yazısında kapsanan SaveAsPdfA'nın işidir. Okunamayan bir paket ya da bozuk bir Info tarihi, hiçbir şey yazılmadan EPdfXmpError fırlatır; XMP'de makul görünen yanlış bir tarih, çıktı vermemekten daha kötüdür. İmzalı belgeler, çağıran AllowSignedDocument = True geçirmediği sürece EPdfError fırlatır; çıktı, yeni metadata'yı artımlı bir güncelleme olarak ekleyen tam bir kayıttır, yani imzalı baytlar değişmeden taşınmaz
Bu kayıt yolunun bir sonucu daha vardır. NormalizePdfAMetadata, her alan zaten eşleşse bile yeni bir dosya yazar, çünkü hiçbir şeyi eşitlemek bile kayıtla biter. Önce InspectPdfAMetadata çağırın ve eşdeğer dosyaları atlayın; binlerce değişmemiş arşivi yeniden yazan bir gece işi, yeni dosya zaman damgalarından başka bir şey kazanmaz. Dosya overload'ı geçici bir dosya üzerinden yazıp hedefi atomik olarak değiştirir. Kaynak dosya, belge yüklü kaldığı sürece açık kalır, dolayısıyla çıktıya farklı bir yol verin
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; // zaten tutarlı, dosyaya dokunma
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 // bozuk Info tarihi ya da okunamayan paket
raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
on E: EPdfError do // AllowSignedDocument olmadan imzalı girdi
raise Exception.CreateFmt('Skipped %s: %s', [Source, E.Message]);
end;
finally
Pdf.Free;
end;
end;
Eşdeğerlik, ISO 19005-1'ın tek bir maddesidir. Normalize edilmiş bir dosya yine de yazı tiplerini, renkleri, saydamlığı ve diğer her PDF/A denetimini geçmek zorundadır; dolayısıyla bir belge uzun süreli depoya gitmeden önce kapıyı ValidatePdfA korumaya devam eder
Belge yüklü değilken bir paketi karşılaştırmak
ComparePdfAInfoAndXmp ile SynchronizePdfAInfoToXmp, FPdfPdfa içinde, hiçbir PDF açık değilken bir TPdfXmpPacket üzerinde çalışan düz işlevlerdir; belge var olmadan önce şablondan XMP dolduran birim testlerine ve işlem hatlarına uyar. SynchronizePdfAInfoToXmp, yeniden yazdığı alanların kümesini döndürür ve nil paketle çağrılırsa EPdfXmpError fırlatır
Tek tuzak Present'tir. Default(TPdfAMetadataValues) ile ilklendirilmiş bir kaydın kümesi boştur, her alan pamsNotRequired bildirir ve karşılaştırma, hangi değerleri doldurduğunuza bakmaksızın geçer. Kaydın dizge alanları değerleri taşır; standardın gerçekte hangisini istediğine yalnızca Present karar verir
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 hangi alanların zorunlu olduğuna karar verir; yalnızca değerler yok sayılır
Info.Present := [pamfTitle, pamfAuthor, pamfModDate];
Packet := TPdfXmpPacket.Parse(TFile.ReadAllText(TemplateFile, TEncoding.UTF8));
try
Changed := SynchronizePdfAInfoToXmp(Info, Packet);
// uyuşmaz bir xmp:ModifyDate, 2026-08-27T01:36:59Z olur;
// uyuşmaz bir dc:creator, tek öğeli bir rdf:Seq olur
if Changed <> [] then
TFile.WriteAllBytes(TemplateFile, Packet.ToUtf8);
finally
Packet.Free;
end;
end;
Aynı şablonu ardından ComparePdfAInfoAndXmp'ten geçirmek boş bir MismatchFields döndürmelidir; bu, şablonları yeniden üreten bir derleme adımı için ucuz bir doğrulamadır
Diğer sistemlerin düzenlemeyi sürdürdüğü arşivler alan alan savrulur ve alan bazlı rapor hangisinin oynadığını gösterir. InspectPdfAMetadata, NormalizePdfAMetadata ve diğer PDF/A doğrulama ile dönüştürme araçları, Delphi ve C++Builder için PDFium Component'in parçasıdır