يفحص مكوّن PDFium تكافؤ بيانات Info وXMP الوصفية في PDF/A عبر TPdf.InspectPdfAMetadata ويصلحه عبر TPdf.NormalizePdfAMetadata. تشترط ISO 19005-1 (مصححة بـ Cor.1) أن يحمل كل مدخل من مداخيل Info الثمانية المرتبطة، من Title إلى ModDate، القيمة نفسها التي تحملها خاصية XMP المقابلة، لا أن يوجد فحسب؛ والفحص يقرأ شكل RDF الصحيح، ويطابق نطاقات الأسماء بالـ URI، ويقارن التواريخ بوصفها لحظات
تقرير العيب الذي يفتح هذه المحادثة عادة يبدو بريئًا. نظام إدارة مستندات يختم /ModDate جديدة في قاموس Info عند كل حفظ تزايدي، ويترك حزمة XMP وشأنها، وبعد ستة أشهر يعلّم تدقيق أرشفة آلاف الملفات بوصفها غير مطابقة. كلا التاريخين موجود. إنما توقفا عن الاتفاق عند أول تعديل، وفحص الحضور لم ينتبه قط. وتحريرات العنوان عبر API يكتب في Info وحده، وسلسلة مؤلف مثل Finance; Controlling شققتها أداة ما إلى مدخلين dc:creator، تفشل بالطريقة نفسها
لماذا يرفض PDF/A بيانات وصفية موجودة في الموضعين؟
يرفضه PDF/A لأن ISO 19005-1 §6.7.3 قاعدة قيم لا قاعدة حضور: يربط الجدول 1 ثمانية مفاتيح Info بخاصيات XMP، ومتى حضر مفتاح Info وجب على خاصية XMP المرتبطة أن تحمل قيمة مكافئة. الماسح على مستوى البايتات الموصوف في التحقق المسبق PDF/A عبر مكوّن PDFium لا يؤكد إلا وجود xmp:CreateDate وxmp:ModifyDate (pvaiMissingXmpDates). ومنذ v3.72.0 يشغل TPdf.ValidatePdfA أيضًا مقارنة القيم كاملة ويضيف pvaiInfoXmpValueMismatch إلى مجموعة المشاكل حين توجد حزمة XMP لكنها تخالف Info (وحزمة لا يمكن تحليلها تحسب مخالفة). والحزمة الغائبة تظل تبلغ بوصفها pvaiMissingXmpMetadata، فلا تحسب المشكلتان العيب نفسه مرتين قط
أي شكل RDF تحتاجه كل خاصية XMP مرتبطة؟
لكل ربط من الربوط الثمانية نوع XMP ثابت، والقيمة الصحيحة في الحاوية الخطأ ما تزال تفشل. يبحث ComparePdfAInfoAndXmp في FPdfPdfa.pas عن الخاصيات بربط URI نطاق الاسم، فحزمة تربط http://purl.org/dc/elements/1.1/ ببادئة غير مألوفة تقرأ تمامًا كحزمة تستخدم dc. والأشكال المطلوبة هي:
- Title إلى
dc:titleوSubject إلىdc:description: بديل لغةrdf:Alt، يقارن مقابل مدخلx-defaultخاصته فقط (وتطابق وسيم اللغة بلا حساسية لحالة الأحرف)؛ وAlt بلاx-defaultيحسب غائبًا - Author إلى
dc:creator:rdf:Seqبمدخل نص واحد بالضبط يحمل سلسلة Info كاملة، فقائمة مؤلفين تفصل بينهم فواصل منقوطة تبقى مدخلًا واحدًا - Keywords إلى
pdf:KeywordsوProducer إلىpdf:Producer (نطاق الاسمhttp://ns.adobe.com/pdf/1.3/): خاصيات نص بسيطة - Creator إلى
xmp:CreatorToolوCreationDate إلىxmp:CreateDateوModDate إلىxmp:ModifyDate (نطاق الاسمhttp://ns.adobe.com/xap/1.0/): خاصيات نص بسيطة
<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>
تقارن قيم النص بوصفها متتاليات نقاط ترميز Unicode تامة، بلا قص ولا طي حالة ولا تطبيع. مسافة زائدة في الذيل، أو é مركبة مسبقًا في طرف وe مفككة مع علامة تشكيل في الطرف الآخر، عدم تطابق حقيقي. ويأتي طرف Info دائمًا من فك ترميز PDFium الخاص لنصي PDFDocEncoding وUTF-16 عبر FPDF_GetMetaText، مما يحول دون أن تعيد المكتبة تنفيذ فك ترميز السلاسل فتخطئ فيه خطأ خفيًا؛ وطرف XMP نظيف بقدر البايتات التي أنتجته، ولهذا تهم هنا أيضًا فخاخ صفحات الأكواد التي تفسد بيانات XMP الوصفية تحت Free Pascal
متى يتساوى تاريخ PDF وتاريخ XMP؟
يتساوى تاريخ PDF وتاريخ XMP حين يصفان اللحظة نفسها بدقة الثانية، وبالمعرفة نفسها بالمنطقة الزمنية في الطرفين. يقبل كلا المحللين الدقة المخفضة المشروعة، فـ D:2026 و2026 كلاهما يعني 1 يناير 2026 الساعة 00:00:00. وحين تحمل القيمتان منطقة، تحولان إلى UTC قبل المقارنة: D:20260827093659+08'00' يساوي 2026-08-27T01:36:59Z. وحين لا تحمل أي منهما منطقة، تقارن المكونات المحلية كما كتبت. وحين يملك طرف واحد منطقة فقط، كان الناتج pamsValueMismatch، لأن اختلاق إزاحة سيكون تخمينًا. وكسر الثانية غير الصفري مثل .250 في XMP يفرض عدم تطابق أيضًا، لأن تاريخ PDF لا سبيل له للتعبير عنه وإزالته بالتقريب الصامت سيخفي خلافًا حقيقيًا؛ أما .000 فيقبل. والقيم غير القابلة للتحليل تبلغ منفصلة بوصفها pamsInvalidInfoDate أو pamsInvalidXmpDate
للحضور قاعدته الخاصة. TPdfAMetadataValues.Present مجموعة تمتلئ بمرور قاموس /Info في trailer النشط، وهي تفصل «مفتاح غائب» عن «مفتاح حاضر بسلسلة فارغة». المفتاح الغائب يعطي pamsNotRequired ولا يطلب شيئًا من XMP؛ و/Title () حاضر، فيجب أن تحمل حزمة XMP عنوان x-default فارغًا أيضًا
كيف تفحص بيانات Info وXMP الوصفية قبل الحفظ؟
يعيد TPdf.InspectPdfAMetadata قيمة TPdfAMetadataReport بها TPdfAMetadataComparison واحدة لكل حقل، كل منها تحمل قيمة Info وقيمة XMP وTPdfAMetadataState، فيمكن تفسير فشل دون هندسة عكسية لعلم تحقق واحد. ويلخص MismatchFields مجموعة الفاشل، ويخبرك HasXmpPacket هل وُجدت حزمة، ويحمل XmpParseError رسالة المحلل حين تكون الحزمة موجودة لكن لا يمكن قراءتها
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;
ماذا يغير NormalizePdfAMetadata، وما الذي يرفضه؟
يعامل TPdf.NormalizePdfAMetadata قاموس Info بوصفه مصدر الحقيقة ويعيد كتابة خاصيات XMP التي حقلها هبط في MismatchFields فقط؛ وكل شيء آخر في الحزمة ينجو. يكتب Title وSubject في مدخل x-default بينما تبقى بدائل اللغات الأخرى سليمة، ويصير Author rdf:Seq بمدخل واحد، وتحفظ نطاقات الأسماء المجهولة والخاصيات غير ذات الصلة، وتترك خاصيات XMP للمفاتيح الغائبة في Info بلا مساس. ويكتب تاريخ Info ذو المنطقة بوصفه تاريخ XMP قانونيًا بالـ UTC بلاحقة Z؛ وما لا منطقة له يحفظ مكوناته المحلية. والتحميل الزائد للملفات يحفظ عبر ملف مؤقت واستبدال ذري، وتلحق تحديثة XMP نفسها بوصفها تحديثًا تزايديًا
الرفوف مقصودة. بلا حزمة XMP يرفع الأسلوب EPdfError، لأن بناء مجموعة تعريف PDF/A والبيانات الوصفية كاملة عمل SaveAsPdfA، المغطى في إنشاء ملفات PDF/A للأرشفة عبر مكوّن PDFium. وتاريخ Info مشوه يرفع EPdfXmpError بدل كتابة قيمة خاطئة تبدو مقنعة، ولا يحفظ شيء. والمستندات الموقعة ترفض ما لم يمرر المستدعي AllowSignedDocument = True. والتكافؤ قاعدة واحدة في ISO 19005-1 أيضًا، فالملف المطبّع ليس مطابقًا تلقائيًا
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; // متسق سلفًا، اترك الملف وشأنه
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 // تاريخ Info مشوه أو حزمة غير قابلة للقراءة
raise Exception.CreateFmt('Cannot normalize %s: %s', [Source, E.Message]);
end;
finally
Pdf.Free;
end;
end;
تشغيل المقارنة على حزمة XMP خاصة بك
إن ComparePdfAInfoAndXmp وSynchronizePdfAInfoToXmp دالتان عاديتان في FPdfPdfa تعملان على TPdfXmpPacket دون تحميل أي مستند، وهو ما يناسب اختبارات الوحدة وخطوط المعالجة التي تركب XMP من قالب. والفخ الوحيد هو Present: سجل مهيأ بـ Default(TPdfAMetadataValues) مجموعته فارغة، فتبلغ كل الحقول pamsNotRequired وينجح المقارنة نجاحًا هوائيًا مهما ملأت من قيم
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 أي الحقول إلزامية؛ والقيم وحدها تتجاهل
Info.Present := [pamfTitle, pamfAuthor, pamfModDate];
Packet := TPdfXmpPacket.Parse(TFile.ReadAllText(TemplateFile, TEncoding.UTF8));
try
Changed := SynchronizePdfAInfoToXmp(Info, Packet);
// أصبح xmp:ModifyDate الآن 2026-08-27T01:36:59Z وdc:creator rdf:Seq بمدخل واحد
if Changed <> [] then
TFile.WriteAllBytes(TemplateFile, Packet.ToUtf8);
finally
Packet.Free;
end;
end;
إن كان خط معالجتك يؤرشف مستندات ما تزال أنظمة أخرى تحررها، فاجمع بين مسح ليلي بـ InspectPdfAMetadata وNormalizePdfAMetadata للملفات المنحرفة، وأبق ValidatePdfA بوابة قبل أن يغادر أي شيء إلى التخزين طويل الأمد. والتقرير المكتوب الأنواع ومسار الإصلاح وبقية أدوات PDF/A تأتي في مكوّن PDFium لدلفي وC++Builder