مقال تقني

دمج نماذج PDF في Delphi: قواعد الحقول المكرَّرة

تدمج PDF Library for Delphi مستندَي AcroForm بسياسة صريحة للحقول التي تتشارك اسمًا. تأخذ MergeDocumentEx معرّف مستند المصدر وواحدة من ثلاث استراتيجيات: dfsReject ترفض الدمج، وdfsMerge تُبقي على الاسم المشترك وتُزامن القيم، وdfsAutoNumber تُعيد تسمية الحقول الواردة بشكل حتمي. ويحدث فحص الأسماء قبل أن تتحرك أي أرقام كائنات، بحيث يترك دمج مرفوض كلا المستندين قابلَين للاستخدام بالكامل

وأي شخص جمّع حزمة طلب PDF واجه هذا. ثلاثة نماذج، لكل منها حقل باسم Signature أو Date أو Total، تُدمَج في ملف واحد. وفي AcroForm، اسم الحقل المؤهَّل بالكامل هو هوية الحقل، لذا فإن حقلين بالاسم نفسه ليسا حقلين إطلاقًا: ملء أحدهما يملأ الآخر، وتوقيع يُطبَّق على أحدهما يُغطّي نطاقًا لم يقصده أحد

لماذا يُبتّ في تصادم الأسماء قبل الدمج؟

تُسلسِل MergeDocument الأقدم مصفوفتَي الحقول الجذرية لـ AcroForm ولا تُقدِّم أي خيار. والأسوأ من ذلك، حين تكون النتيجة غير قابلة للاستخدام، يحدث الاكتشاف بعد إعادة ترقيم أرقام الكائنات ودمج أشجار الصفحات، ما يترك المستدعي ممسكًا بمستند في حالة لم يكن عليها أي من الأصلين

وتعكس MergeDocumentEx هذا الترتيب. فهي تجمع أسماء الحقول في المستوى الأعلى من كلا المستندين، وتقارنها، وتطبق الاستراتيجية قبل أن يتحرك أي شيء. لذا فإن الرفض عملية لا أثر لها بنظافة: يبقى مستند الهدف دون لمس، ويبقى مستند المصدر دون لمس، ويظل كلاهما مفتوحًا وقابلًا للاستخدام، وهو ما يتحقق منه اختبار الدمج بقراءة قيمة حقل من المصدر مجددًا بعد دمج مرفوض

وتستخدم المقارنة مجموعة أسماء مُرتَّبة وحساسة لحالة الأحرف، بحيث تكون التكلفة متناسبة مع عدد الحقول المجمَّع مضروبًا في عامل لوغاريتمي بدلًا من حاصل ضرب العددين. والحساسية لحالة الأحرف هي الخيار الصحيح هنا لأن أسماء حقول PDF حساسة لحالة الأحرف؛ وطيّها كان سيدمج حقولًا تعاملها المواصفة كحقول متمايزة

الاستراتيجيات الثلاث، ومتى تصلح كل منها

dfsReject هي الاستراتيجية لخطوط الأنابيب الآلية التي يجب ألا تُنتج مستندات غامضة. يُعيد الدمج صفرًا وتُبلغ LastErrorCode عن 705، وهو رمز مخصَّص بحيث يمكن تمييز الأسماء المكرَّرة عن كل فشل دمج آخر وتوجيهها إلى علاج محدد، عادة إعادة تسمية الحقول في المنبع

تُبقي dfsMerge على الاسم المشترك عمدًا وتُزامن قيمة الهدف والقيمة الافتراضية في حقل المصدر، بحيث يعامل عارض متوافق العناصر التفاعلية العديدة كحقل واحد مسمّى منطقيًا، وهو السلوك القياسي لـ AcroForm بالنسبة إلى حقل بتعليقات عنصر تفاعلي متعددة. وما لا تفعله هو دمج قواميس حقول مختلفة في كائن واحد. فيحتفظ كل حقل بارتباطه الخاص بالصفحة، ومظهره، وإجراءاته، لأن دمجها كان سيُسقِط بصمت تنسيقًا وسلوكًا ينتمي إلى المستند الوارد

تُعيد dfsAutoNumber تسمية النسخ المكرَّرة الواردة بإلحاق لاحقة رقمية تبدأ من _2 وأخذ أول لاحقة حرة. والنتيجة قابلة لإعادة الإنتاج: فهي تعتمد فقط على الأسماء الموجودة، لا على أرقام كائنات الحقول أبدًا، بحيث يُنتج دمج الزوج نفسه من المستندات مرتين الأسماء نفسها في كلتا المرتين. وهذه الخاصية مهمة حين يُشير كود لاحق، أو استيراد FDF، أو تخطيط قاعدة بيانات إلى الحقول بالاسم

uses
  PDFlibrary;

var
  Lib: TPDFlib;
  TargetDoc, SourceDoc: Integer;
begin
  Lib := TPDFlib.Create;
  try
    TargetDoc := Lib.SelectedDocument;
    Lib.LoadFromFile('application-part1.pdf', '');

    SourceDoc := Lib.NewDocument;
    Lib.LoadFromFile('application-part2.pdf', '');

    Lib.SelectDocument(TargetDoc);
    if Lib.MergeDocumentEx(SourceDoc, dfsReject) = 0 then
    begin
      if Lib.LastErrorCode = 705 then
      begin
        // كلا المستندين لا يزالان سليمَين - أعِد المحاولة بسياسة
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

    Lib.SaveToFile('application-complete.pdf');
  finally
    Lib.Free;
  end;
end;

لاحظ نمط الخطوتين في ذلك الكود، وهو ممكن فقط لأن الرفض غير مُدمِّر. جرّب السياسة الصارمة أولًا، وافحص الخطأ، ثم قرر. أما مع دمج يفشل في منتصف الطريق، فسيتعين على الحل البديل البدء من جديد بإعادة تحميل كلا الملفين

كيف يبدو النموذج المدمج بعد ذلك

في ظل dfsMerge، حقل هدف باسم Shared يحمل "Target value" وحقل مصدر بالاسم نفسه يُنتجان حقلين، كلاهما باسم Shared، وكلاهما يُبلغ عن قيمة الهدف، لأن قيمة الهدف والقيمة الافتراضية تُزامَنان في الحقل الوارد. وهذه هي الدلالة المقصودة لاسم مشترك: حقل منطقي واحد، وعناصر تفاعلية عدة، وقيمة واحدة

وفي ظل dfsAutoNumber، ينتج المُدخَل نفسه Shared وShared_2 كحقلين منفصلين بقيم مستقلة. اختر بين الاثنين بطرح سؤال واحد: هل يجب أن يملأ ملء عنصر تحكم واحد الآخر؟ بالنسبة إلى اسم موقِّع مكرَّر على كل جزء من حزمة، الجواب نعم، وdfsMerge هو الصحيح. وبالنسبة إلى إجمالي يعني شيئًا مختلفًا في كل نموذج، الجواب لا، والترقيم التلقائي هو الصحيح

// بعد الدمج، عدّد ما حصلت عليه فعليًا
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

ملاحظات عملية لتجميع حزم النماذج

يستهلك الدمج الناجح مستند المصدر: إذ يُزال من قائمة مستندات المكتبة، ولهذا ينخفض DocumentCount من اثنين إلى واحد. لا تستمر في استخدام معرّف المصدر بعد ذلك. وتُرفَع نسخة المستند إلى الأعلى بين الاثنتين، بحيث يُنتج دمج نموذج PDF 2.0 في مستند 1.7 ملف 2.0

والترتيب مهم بالنسبة إلى الأسماء. فدمج A في B ودمج B في A يُنتجان نتائج ترقيم تلقائي مختلفة، إذ يحتفظ المستند الذي يقوم بالدمج بأسمائه دون تغيير. وحين تحتوي الحزمة على نموذج أساسي قانوني، اجعله هو الهدف

وتستحق حقول التوقيع اعتبارًا خاصًا بها. فتوقيع طُبِّق قبل الدمج يُغطّي فقط المراجعة التي وقَّعها، لذا فإن الدمج يُبطله بالمعنى العملي المتمثل في أن الملف قد تغيّر منذ التوقيع. اجمّع أولًا ووقِّع المستند المُجمَّع، بدلًا من دمج أجزاء موقَّعة. وحين يكون الدمج متعلقًا بمحتوى الصفحة لا بالنماذج، فإن المسار الأسرع الموصوف في دمج PDF السريع بإزاحة المراجع بالبايت هو الأداة الأفضل

وأخيرًا، خطِّط لجانب البيانات في الحزمة جنبًا إلى جنب مع الدمج. إذا وصلت قيم الحقول من نظام خارجي، قرر ما إذا كان ذلك النظام يُعالج الحقول بالاسم قبل اختيار الترقيم التلقائي، لأن Shared_2 لن يطابق تخطيطًا يتوقع Shared. وتُغطّى صيغ الاستيراد والتصدير في تبادل بيانات نماذج FDF وXFDF وXFA، وسلوك البرمجة النصية على مستوى الحقل الذي يمكن أن يتأثر أيضًا بإعادة التسمية مُغطّى في إجراءات النماذج التفاعلية وJavaScript

وتعمل عمليات دمج النماذج، وتبادل البيانات، والتوقيع في المكتبة نفسها لـ Delphi وC++Builder وFree Pascal؛ وتوجد قائمة الميزات الكاملة على صفحة PDF Library for Delphi