مقال تقني

تبادل بيانات نماذج FDF و XFDF و XFA في Delphi

تنقل مكتبة losLab PDF Library بيانات النماذج داخل وخارج ملف PDF بثلاث طرق مختلفة: FDF و XFDF لقيم حقول AcroForm، وهذان التنسيقان نفسهما لتعليقات المراجعة التوضيحية (annotations)، وحزمة XFA XDP كاملة مكتوبة مباشرة في النموذج؛ وتعد ExportFormDataToXFDF و ImportFormDataFromXFDF و ImportAnnotationsFromFDF و ExportAnnotationsToXFDF و SetXFAFromString هي نقاط الدخول، ولكل منها متغير للملف ومتغير للسلسلة النصية

السبب وراء وجود العديد من الطرق هو أن بيانات نماذج PDF ليست شيئًا واحدًا؛ فالنموذج AcroForm المعبأ يحتوي على قيم الحقول، وقد يحمل أيضًا تعليقات المراجعة التوضيحية، بينما يدمج نموذج XFA القديم وصف تطبيق XML كامل لا علاقة له بأي منهما؛ وتحتفظ مكتبة losLab PDF Library بهذه الاهتمامات الثلاثة على واجهات برمجة تطبيقات منفصلة عمدًا، لأن دمجها سيفرض نموذج البيانات الخاطئ على اثنين منها على الأقل؛ ويعد تحديد العائلة التي تحتاجها هو خيار التصميم الأول، وعادة ما يتم تسويته لحظة معرفة ما يستهلكه النظام المستلم على الجانب الآخر بالفعل

ما الفرق بين بيانات نماذج FDF و XFDF و XFA؟

تحمل FDF و XFDF نفس المعلومات بأسلوبين مختلفين، بينما يعد XFA عالمًا منفصلاً؛ فـ FDF عبارة عن مستند مصغر بصيغة PDF (المواصفة ISO 32000-2 الفقرة 12.7.8): مصفوفة /Fields من قواميس << /T (name) /V (value) >>، مع هروب السلاسل النصية تمامًا مثل السلاسل الحرفية داخل ملف الـ PDF؛ أما XFDF فهو شكل XML لنفس البيانات (المواصفة ISO 19444)، وهي شجرة <fields> يقرؤها ويكتبها Acrobat ومعظم خلفيات النماذج بشكل أصيل؛ وXFA ليس أيًا منهما: بل هو قالب بنية نماذج XML مع بياناته، والمخزن كحزمة بيانات XML (اختصارًا XDP) يشير إليها الـ PDF من /AcroForm/XFA؛ فاختر FDF أو XFDF عند التعامل مع قيم الحقول، والجا إلى XFA فقط عند صيانة مستند تم تأليفه كنموذج XFA في المقام الأول

وداخل FDF و XFDF، ترسم مكتبة losLab PDF Library خطًا ثانيًا: قيم الحقول مقابل تعليقات المراجعة التوضيحية؛ حيث تقرأ وتكتب عائلة بيانات النماذج (ExportFormDataToFDF، ImportFormDataFromFDF، ExportFormDataToXFDF، ImportFormDataFromXFDF) الشجرة الفرعية /Fields ولا تلمس أي شيء آخر؛ بينما تقرأ وتكتب عائلة التعليقات التوضيحية (ExportAnnotationsToFDF، ImportAnnotationsFromFDF، ExportAnnotationsToXFDF، ImportAnnotationsFromXFDF) الشجرة الفرعية /Annots بدلاً من ذلك، وهو ما يعنيه Acrobat بتصدير التعليقات؛ ولا يتداخل الاثنان أبدًا، ولذلك فإن تصدير قيم الحقول لن يجمع ملاحظات المراجعة الشاردة، واستيراد التعليقات لن يزعج القيم التي كتبها المستخدم بالفعل؛ وما تفعله الأداة عند النقر عليها أو إعادة حسابها هو اهتمام ثالث مغطى في المقال المصاحب حول إجراءات النماذج التفاعلية وجافا سكريبت

كيف تملأ نموذج PDF من FDF أو XFDF في Delphi؟

قم بتحميل المستند، واستدعِ دالة استيراد واحدة، ثم احفظ؛ حيث تقوم كل من ImportFormDataFromXFDF و ImportFormDataFromFDF بتحليل بيانات النموذج الواردة، ومطابقة كل إدخال مع حقل AcroForm باسمه المؤهل بالكامل، وتعيين القيمة، وإرجاع عدد الحقول التي تم تحديثها بالفعل؛ وتقوم كلتا الطريقتين بتحديث الحقول الموجودة بالفعل في ملف PDF المستهدف فقط، ولا تبتكر أي منهما حقلاً لاسم لا يحدده النموذج، مما يمنع ملف بيانات شارد أو ضار من تنمية نموذجك بصمت

var
  Lib: TPDFlib;
  FieldsSet: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('application-blank.pdf', '');
    // XFDF تم إنتاجه بواسطة نظام إدارة الحالات، وهو بالفعل UTF-8 على القرص
    FieldsSet := Lib.ImportFormDataFromXFDF('applicant-1042.xfdf');
    if FieldsSet > 0 then
      Lib.SaveToFile('application-filled.pdf');
  finally
    Lib.Free;
  end;
end;

هناك تفصيلان يحددان ما إذا كانت البيانات غير التافهة تنجو من الرحلة؛ الأول هو الهروب: يخزن FDF القيم كسلاسل حرفية لـ PDF، ولذلك فإن القيمة التي تحتوي على قوس أو مائلة عكسية أو بايت غير قابل للطباعة تصل مغلفة بالهروب الثماني المحدد في المواصفة ISO 32000-2 الفقرة 7.3.4.2، وتقوم مكتبة losLab PDF Library بعكس هذا الهروب عند الاستيراد بحيث تعود الأقواس والمائلات كالأحرف الحرفية التي كتبها المستخدم؛ والثاني هو الترميز: تكتب وتقرأ الطرق المعتمدة على الملفات ملفات XFDF و FDF كـ UTF-8، وهو ما يعلنه تصريح XFDF وما تحتاجه أي قيمة حقل غير ASCII (اسم يحتوي على علامات تشكيل، أو رمز عملة، أو عنوان CJK) للقيام بدورة كاملة دون تلف؛ وإذا قمت ببناء XFDF بنفسك، فصرح بـ UTF-8 واكتب UTF-8، وسيتوافق الاستيراد معك

الحقول الهرمية، والخيارات متعددة القيم، والنصوص المنسقة

أسماء الحقول الهرمية هي أول مكان ينكسر فيه المصدر البسيط؛ حيث يعنون AcroForm حقلاً متداخلاً بعنوان كامل منقط مثل Applicant.FullName، ولكن XFDF لا يضع تلك السلسلة المنقطة في سمة اسم واحدة؛ بل تعششها مواصفة ISO 19444 بهذا الشكل: <field name="Applicant"><field name="FullName">؛ وتقوم مكتبة losLab PDF Library بتقسيم العنوان المنقط إلى عناصر <field> متداخلة عند التصدير وإعادة تجميع العناصر المتداخلة إلى العنوان الكامل عند الاستيراد، بحيث تظل الطريقتان متماثلتين؛ وتتخطى أيضًا عند التصدير حقول الآباء غير النهائية، لأن عقدة الأب في AcroForm تحمل فقط مستوى التسمية وليس لها قيمة خاصة بها، وإصدارها سينتج <value></value> فارغًا لا يطابق النموذج الحقيقي؛ وصناديق القوائم متعددة التحديد هي المصيدة الثانية: فيمكن لحقل الاختيار حمل عدة قيم محددة في نفس الوقت، وهو ما يعبر عنه XFDF كعناصر <value> متكررة ويعبر عنه FDF كمصفوفة /V، وتقوم مكتبة losLab PDF Library بتقسيم الحقل إلى قيم متعددة فقط عندما يكون اختيارًا متعدد التحديد حقًا، بحيث يحتفظ حقل النص العادي متعدد الأسطر بفواصل أسطره بدلاً من التفتت إلى قيم وهمية

النص المنسق (rich text) هو الثالث، وهو ما يخطئ فيه الناس غالباً؛ حيث يخزن الحقل ذو التنسيق وسومه في إدخال RV كشجرة فرعية XHTML، ويحمله XFDF في <value-richtext>؛ وتكتب مكتبة losLab PDF Library تلك الشجرة كجزء XML حي بدلاً من الهروب منها، بحيث تقرأ الأداة التالية نصًا منسقًا حقيقيًا بدلاً من سلسلة من الوسوم المرئية؛ وتحتفظ عند الاستيراد بشجرة RV الخام وتطبق القيمة البسيطة <value> على V؛ وحيث يقدم الحقل كليهما، تفوز القيمة البسيطة لـ V ويركب النص المنسق بجوارها في RV، وهي قاعدة التشغيل البيني التي تمنع استيراد النص المنسق من إعادة كتابة قيمة قامت أداة أخرى بتعيينها بالفعل بصمت؛ وعندما يتواجد نصك المنسق لنقل هيكل المستند إلى التقنيات المساعدة، عامله بنفس الطريقة التي تعامل بها ترتيب القراءة الموضح في المقال المصاحب حول ملفات PDF المميزة وهيكل إمكانية الوصول

var
  Lib: TPDFlib;
const
  XFDF =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">' +
    '<fields>' +
    '  <field name="Applicant">' +
    '    <field name="FullName"><value>Alice Example</value></field>' +
    '  </field>' +
    '  <field name="Skills">' +
    '    <value>Delphi</value><value>PDF</value>' +
    '  </field>' +
    '  <field name="Notes">' +
    '    <value>See attachment</value>' +
    '    <value-richtext><body><p>See <b>attachment</b></p></body></value-richtext>' +
    '  </field>' +
    '</fields></xfdf>';
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('intake.pdf', '');
    // يتجمع Applicant.FullName؛ تملأ المهارات (Skills) مصفوفات /V + /I؛
    // تحصل الملاحظات (Notes) على V من  و RV من 
    Lib.ImportFormDataFromXFDFString(XFDF);
    Lib.SaveToFile('intake-filled.pdf');
  finally
    Lib.Free;
  end;
end;

كيف تقوم بدورة كاملة لتعليقات المراجعة كـ FDF أو XFDF؟

تنتقل التعليقات في عائلة التعليقات التوضيحية، والمجموعة الفرعية المدعومة صغيرة عمدًا؛ حيث تحمل ExportAnnotationsToXFDF و ImportAnnotationsFromXFDF، مع نظرائهما لـ FDF، وسوم نمط النص مع الحقول التي تدور دورة كاملة بنظافة: النوع الفرعي للتعليق التوضيحي، مستطيله، فهرس صفحته المبني على الصفر، المؤلف في T، الموضوع في Subj، المحتويات البسيطة Contents، واللون، والذي يطابق ثلاثية /C DeviceRGB للـ PDF مع سمة #RRGGBB لـ XFDF؛ ويقبل الاستيراد أسماء العناصر المعروفة فقط ويتخطى أي إدخال صفحته خارج النطاق أو مستطيله مفقود، بحيث لا يمكن لملف XFDF معدل يدويًا أو خارجي إسقاط تعليق توضيحي تالف في المستند؛ وما لا تحمله المجموعة الفرعية يستحق الذكر بوضوح: المحتويات المنسقة (RC), النوافذ المنبثقة، النقاط الرباعية (quadpoints)، قوائم الحبر، الرؤوس ودفق المظهر المخبوزة هي خارج النطاق في الوقت الحالي، ولذلك فإن هندسة التمييز ومظاهر الأختام المخصصة لن تنجو في هذا المسار؛ ولتأكيد ما هبط بالفعل، مر عبر تعليقات الصفحة التوضيحية أو شجرة العناصر الأوسع المغطاة في البحث عن النصوص وتعداد عناصر الصفحة

var
  Src, Dest: TPDFlib;
  Xfdf: WideString;
begin
  Src := TPDFlib.Create;
  Dest := TPDFlib.Create;
  try
    Src.LoadFromFile('reviewed.pdf', '');
    Xfdf := Src.ExportAnnotationsToXFDFString;   // مجموعة  الفرعية فقط

    Dest.LoadFromFile('clean-copy.pdf', '');     // يجب أن تتطابق الصفحات بالفهرس
    Dest.ImportAnnotationsFromXFDFString(Xfdf);
    Dest.SaveToFile('clean-copy-commented.pdf');
  finally
    Dest.Free;
    Src.Free;
  end;
end;

كتابة حزمة XFA XDP كاملة باستخدام SetXFAFromString

XFA هي الاستثناء، و SetXFAFromString هي كيفية كتابة الشيء بأكمله مرة واحدة؛ حيث تأخذ الطريقة حزمة بيانات XML كاملة، مستند <xdp:xdp> مع قالب وحزم مجموعات البيانات الخاصة به، وتخزنها كالدفق المشار إليه من /AcroForm/XFA؛ وتنشئ مكتبة losLab PDF Library حاوية AcroForm عند الطلب عندما لا يحتوي المستند على أي منها، بحيث لا تضطر إلى إضافة حقل وهمي لمجرد منح XFA مكاناً للتعليق، وتتخلص من أي حالة XFA محللة كانت تحتفظ بها بحيث تعكس القراءة اللاحقة الحزمة التي كتبتها للتو بدلاً من التخزين المؤقت القديم؛ ولأن حزمة XFA هي XML وليست بالضرورة UTF-8، تكتشف المكتبة علامة ترتيب البايت (BOM) لـ UTF-16 وتفك ترميز الحزمة بشكل صحيح قبل التحليل، وهو أمر مهم للحزم المنتجة بواسطة أدوات تفترض UTF-16 افتراضياً

وبمجرد أن تصبح الحزمة في مكانها، تخاطب GetXFAFormFieldValue و SetXFAFormFieldValue الحقول الفردية من خلال مسار نموذج كائيات البرمجة النصية (SOM) الخاص بها؛ وتقبل مكتبة losLab PDF Library جذور SOM القياسية بالإضافة إلى المسارات النسبية المجردة، ولذلك فإن form1.FullName و $data.form1.FullName و xfa.datasets.data.form1.FullName تحل جميعها إلى نفس عقدة البيانات، ويقبل جانب القالب $template و xfa.template بنفس الطريقة؛ وتهم هذه السماحية عندما يتم إنشاء مسارات SOM بواسطة نظام آخر يصدر دائماً الصيغة المؤهلة بالكامل

var
  Lib: TPDFlib;
const
  Xdp =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">' +
    '  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">' +
    '    <subform name="form1">' +
    '      <field name="FullName"><ui><textEdit/></ui></field>' +
    '    </subform>' +
    '  </template>' +
    '  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">' +
    '    <xfa:data><form1><FullName>Alice Example</FullName></form1></xfa:data>' +
    '  </xfa:datasets>' +
    '</xdp:xdp>';
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetXFAFromString(Xdp, 0);      // ينشئ حاوية AcroForm إذا لم يكن هناك أي منها
    // القراءة مرة أخرى من خلال بيانات DOM مع مسار SOM
    if Lib.GetXFAFormFieldValue('form1.FullName') = 'Alice Example' then
      Lib.SetXFAFormFieldValue('form1.FullName', 'Bob Example');
    Lib.SaveToFile('xfa-packet.pdf');
  finally
    Lib.Free;
  end;
end;

الملخص الصادق هو أن قيم الحقول تدور دورة كاملة بشكل كامل من خلال FDF و XFDF، بما في ذلك الهيكل الهرمي، والتحديد المتعدد، والنصوص المنسقة؛ وتدور تعليقات المراجعة التوضيحية كمجموعة فرعية محددة لعلامات النصوص مع فجوات واضحة؛ ويتم كتابة وقراءة XFA كحزمة كاملة مع إمكانية الوصول SOM لكل حقل؛ وطابق التنسيق مع المستهلك، وصرح بـ UTF-8 لأي شيء تبنيه يدويًا، وتذكر أن طرق الاستيراد تلمس فقط الحقول والتعليقات التوضيحية التي تناسب المستند بالفعل؛ وإن طرق بيانات النماذج والتعليقات التوضيحية و XFA الموصوفة هنا هي جزء من مكتبة losLab PDF Library لـ Delphi و C++Builder، ويحمل مرجعها قائمة المعلمات الكاملة لكل نقطة دخول للتصدير والاستيراد