مقاله فنی

تبادل داده‌های فرم FDF، XFDF و XFA در دلفی

losLab PDF Library داده‌های فرم را به سه روش مختلف به داخل و خارج یک PDF منتقل می‌کند: FDF و XFDF برای مقادیر فیلدهای AcroForm، همان دو فرمت برای نظرات annotation، و یک بستهٔ کامل XFA XDP که مستقیماً درون فرم نوشته می‌شود. ExportFormDataToXFDF، ImportFormDataFromXFDF، ImportAnnotationsFromFDF، ExportAnnotationsToXFDF و SetXFAFromString نقاط ورودی هستند و هر یک از آن‌ها یک نسخهٔ فایلی و یک نسخهٔ رشته‌ای دارد

دلیل وجود این‌همه متد این است که داده‌های فرم PDF یک چیز واحد نیستند. یک AcroForm پرشده مقادیر فیلد دارد، ممکن است نظرات بازبینی را نیز حمل کند، و یک فرم قدیمی XFA یک توصیف کامل برنامهٔ XML را در خود جای می‌دهد که هیچ ربطی به آن دو ندارد. losLab PDF Library عمداً این سه دغدغه را روی APIهای جداگانه نگه می‌دارد، چون ادغام آن‌ها مدل دادهٔ نادرستی را دست‌کم به دو مورد از آن‌ها تحمیل می‌کرد. تصمیم‌گیری دربارهٔ اینکه به کدام خانواده نیاز دارید نخستین انتخاب طراحی است، و معمولاً همان لحظه‌ای که بدانید سیستم دریافت‌کننده در سمت دیگر واقعاً چه چیزی مصرف می‌کند مشخص می‌شود

تفاوت میان داده‌های فرم FDF، XFDF و XFA چیست؟

FDF و XFDF اطلاعات یکسانی را در دو نحو متفاوت حمل می‌کنند، و XFA دنیایی جداگانه است. FDF یک سند مینیاتوری با نحو PDF است (ISO 32000-2 §12.7.8): آرایه‌ای از /Fields متشکل از دیکشنری‌های << /T (name) /V (value) >>، که رشته‌ها دقیقاً به همان شکلی escape شده‌اند که رشته‌های literal درون یک PDF escape می‌شوند. XFDF شکل XML همان داده است (ISO 19444)، یک درخت <fields> که Acrobat و بیشتر بک‌اندهای فرم به‌طور بومی می‌خوانند و می‌نویسند. XFA هیچ‌کدام از این دو نیست: یک قالب XML Forms Architecture به‌همراه داده‌هایش است که به‌صورت یک XML Data Package (XDP) ذخیره می‌شود و PDF آن را از /AcroForm/XFA ارجاع می‌دهد. هرگاه روی مقادیر فیلد به تعامل‌پذیری نیاز دارید FDF یا XFDF را انتخاب کنید، و تنها زمانی سراغ XFA بروید که سندی را نگهداری می‌کنید که از ابتدا به‌صورت یک فرم XFA نوشته شده است

در دل FDF و XFDF، losLab PDF Library یک خط تفکیک دوم می‌کشد: مقادیر فیلد در برابر نظرات annotation. خانوادهٔ form-data (ExportFormDataToFDF، ImportFormDataFromFDF، ExportFormDataToXFDF، ImportFormDataFromXFDF) زیردرخت /Fields را می‌خواند و می‌نویسد و به چیز دیگری دست نمی‌زند. خانوادهٔ annotation (ExportAnnotationsToFDF، ImportAnnotationsFromFDF، ExportAnnotationsToXFDF، ImportAnnotationsFromXFDF) در عوض زیردرخت /Annots را می‌خواند و می‌نویسد، که همان چیزی است که Acrobat از Export Comments منظور دارد. این دو هرگز هم‌پوشانی ندارند، پس صدور مقادیر فیلد نظرات بازبینی پراکنده را جارو نمی‌کند، و درون‌ریزی نظرات مقادیری را که کاربر از پیش تایپ کرده مختل نمی‌کند. اینکه یک ویجت هنگام کلیک‌شدن یا بازمحاسبه‌شدن چه کاری انجام می‌دهد دغدغهٔ سومی است که در یادداشت همراه دربارهٔ کنش‌های تعاملی فرم و JavaScript پوشش داده شده است

چگونه در Delphi یک فرم PDF را از FDF یا XFDF پر می‌کنید؟

سند را بارگذاری کنید، یک متد import را فراخوانی کنید، و ذخیره کنید. 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;

دو جزئیات تعیین می‌کنند که آیا داده‌های غیر‌بدیهی این سفر را سالم پشت سر می‌گذارند یا نه. اولی escaping است: FDF مقادیر را به‌صورت رشته‌های literal در PDF ذخیره می‌کند، پس مقداری که شامل یک پرانتز، یک بک‌اسلش یا یک بایت غیرقابل‌چاپ باشد در قالب escaping هشت‌هشتی تعریف‌شده در ISO 32000-2 §7.3.4.2 پیچیده می‌رسد، و losLab PDF Library هنگام import آن escaping را معکوس می‌کند تا پرانتزها و اسلش‌ها به همان کاراکترهای literalی که کاربر تایپ کرده بازگردند. دومی رمزگذاری است: متدهای مبتنی بر فایل، XFDF و FDF را به‌صورت UTF-8 می‌نویسند و می‌خوانند، که همان چیزی است که اعلان XFDF وعده می‌دهد و همان چیزی است که هر مقدار فیلد غیر‌ASCII (نامی با نشانهٔ تلفظ، نماد ارز، نشانی CJK) برای رفت‌وبرگشت بدون خرابی به آن نیاز دارد. اگر خودتان XFDF را می‌سازید، UTF-8 را اعلان کنید و UTF-8 بنویسید، تا import با شما هم‌داستان بماند

فیلدهای سلسله‌مراتبی، انتخاب‌های چندمقداری و متن غنی

نام‌های فیلد سلسله‌مراتبی نخستین جایی هستند که یک صادرکنندهٔ ساده‌انگارانه در آن می‌شکند. AcroForm یک فیلد تودرتو را با یک عنوان کامل نقطه‌دار مانند Applicant.FullName آدرس‌دهی می‌کند، اما XFDF آن رشتهٔ نقطه‌دار را در یک ویژگی name واحد نمی‌گذارد؛ ISO 19444 آن را تودرتو می‌کند، به‌شکل <field name="Applicant"><field name="FullName">. losLab PDF Library هنگام export عنوان نقطه‌دار را به عناصر تودرتوی <field> تقسیم می‌کند و هنگام import عناصر تودرتو را دوباره به عنوان کامل بازمی‌سازد، پس این دو جهت متقارن می‌مانند. هنگام export همچنین فیلدهای والد غیرپایانی را نادیده می‌گیرد، چون یک گرهٔ والد در AcroForm فقط سطح نام‌گذاری را حمل می‌کند و خودش مقداری ندارد؛ صدور آن یک <value></value> خالی تولید می‌کرد که با فرم واقعی مطابقت نمی‌داشت. جعبه‌های فهرست چندگزینشی دومین تله هستند: یک فیلد choice می‌تواند هم‌زمان چند مقدار انتخاب‌شده داشته باشد، که XFDF آن را به‌صورت عناصر <value> تکرارشونده و FDF آن را به‌صورت یک آرایهٔ /V بیان می‌کند، و losLab PDF Library تنها زمانی یک فیلد را به چند مقدار تقسیم می‌کند که واقعاً یک انتخاب چندگزینشی باشد، پس یک فیلد متنی چندخطی معمولی خطوطش را حفظ می‌کند به‌جای آنکه به مقادیر جعلی خرد شود

متن غنی سومین مورد است، و همان چیزی که مردم بیشتر از همه در آن اشتباه می‌کنند. یک فیلد دارای قالب‌بندی نشانه‌گذاری خود را در مدخل RV به‌صورت یک زیردرخت XHTML ذخیره می‌کند، و XFDF آن را در <value-richtext> حمل می‌کند. losLab PDF Library آن زیردرخت را به‌صورت یک قطعهٔ XML زنده می‌نویسد نه به‌صورت escape‌شده، پس یک ابزار پایین‌دستی متن غنی واقعی را می‌خواند نه رشته‌ای از تگ‌های قابل‌مشاهده؛ هنگام import زیردرخت خام RV را نگه می‌دارد و همچنان <value> ساده را روی V اعمال می‌کند. جایی که یک فیلد هر دو را ارائه می‌دهد، مقدار ساده برای V برنده می‌شود و متن غنی در کنار آن در RV سوار می‌ماند، که همان قاعدهٔ تعامل‌پذیری است که مانع می‌شود یک import متن غنی خاموشانه مقداری را بازنویسی کند که ابزار دیگری از پیش تنظیم کرده. هنگامی که متن غنی شما برای انتقال ساختار سند به فناوری کمکی وجود دارد، همان‌گونه با آن رفتار کنید که با ترتیب خواندنِ مطرح‌شده در یادداشت دربارهٔ 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 را از <value> و RV را از <value-richtext> می‌گیرد
    Lib.ImportFormDataFromXFDFString(XFDF);
    Lib.SaveToFile('intake-filled.pdf');
  finally
    Lib.Free;
  end;
end;

چگونه نظرات annotation را به‌صورت FDF یا XFDF رفت‌وبرگشت می‌دهید؟

نظرات روی خانوادهٔ annotation جابه‌جا می‌شوند، و زیرمجموعهٔ پشتیبانی‌شده عمداً کوچک است. ExportAnnotationsToXFDF و ImportAnnotationsFromXFDF، به‌همراه معادل‌های FDF خود، نشانه‌گذاری سبک‌متنی را با فیلدهایی حمل می‌کنند که واقعاً به‌طور تمیز رفت‌وبرگشت می‌کنند: زیرنوع annotation، مستطیل آن، اندیس صفحهٔ آن (مبتنی بر 0)، نویسنده در T، موضوع در Subj، Contents ساده، و رنگ، که سه‌تایی DeviceRGB مربوط به /C در PDF را به ویژگی #RRGGBB در XFDF و برعکس نگاشت می‌کند. import فقط نام‌های عنصر شناخته‌شده را می‌پذیرد و هر مدخلی را که صفحه‌اش خارج از محدوده باشد یا مستطیلش وجود نداشته باشد نادیده می‌گیرد، پس یک XFDF دستی‌ویرایش‌شده یا بیگانه نمی‌تواند یک annotation نادرست را وارد سند کند. آنچه این زیرمجموعه هنوز حمل نمی‌کند ارزش گفتنِ صریح دارد: محتوای متن غنی (RC)، پاپ‌آپ‌ها، quadpoints، فهرست‌های ink، رأس‌ها و appearance streamهای پخته‌شده فعلاً خارج از محدوده‌اند، پس هندسهٔ highlight و ظاهرهای سفارشی stamp این مسیر را سالم پشت سر نمی‌گذارند. برای تأیید اینکه واقعاً چه چیزی نشسته، annotationهای صفحه یا درخت عنصر گسترده‌تر پوشش‌داده‌شده در جست‌وجوی متن و شمارش عناصر صفحه را بپیمایید

var
  Src, Dest: TPDFlib;
  Xfdf: WideString;
begin
  Src := TPDFlib.Create;
  Dest := TPDFlib.Create;
  try
    Src.LoadFromFile('reviewed.pdf', '');
    Xfdf := Src.ExportAnnotationsToXFDFString;   // فقط زیرمجموعهٔ <annots>

    Dest.LoadFromFile('clean-copy.pdf', '');     // صفحات باید بر اساس index هم‌تراز باشند
    Dest.ImportAnnotationsFromXFDFString(Xfdf);
    Dest.SaveToFile('clean-copy-commented.pdf');
  finally
    Dest.Free;
    Src.Free;
  end;
end;

نوشتن یک بستهٔ کامل XFA XDP با SetXFAFromString

XFA مورد استثناست، و SetXFAFromString راهی است که کل آن را یک‌جا می‌نویسید. این متد یک XML Data Package کامل، سند <xdp:xdp> به‌همراه بسته‌های template و datasets آن را می‌گیرد و آن را به‌صورت جریانی که از /AcroForm/XFA ارجاع داده می‌شود ذخیره می‌کند. losLab PDF Library هنگام نیاز کانتینر AcroForm را می‌سازد اگر سند فاقد آن باشد، پس لازم نیست فقط برای اینکه جایی برای آویزان‌کردن XFA داشته باشید یک فیلد بی‌مصرف اضافه کنید، و هر وضعیت تجزیه‌شدهٔ XFA را که در اختیار داشت دور می‌ریزد تا خواندنِ بعدی همان بستهٔ همین‌الان نوشته‌شده را منعکس کند نه یک کش قدیمی را. از آنجا که یک بستهٔ XFA یک XML است و لزوماً UTF-8 نیست، کتابخانه یک نشانهٔ ترتیب بایت UTF-16 را تشخیص می‌دهد و بسته را پیش از تجزیه به‌درستی رمزگشایی می‌کند، که برای بسته‌های تولیدشده توسط ابزارهایی که به‌طور پیش‌فرض از UTF-16 استفاده می‌کنند اهمیت دارد

پس از قرارگرفتن بسته در جای خود، GetXFAFormFieldValue و SetXFAFormFieldValue فیلدهای منفرد را از طریق مسیر Scripting Object Model آن‌ها آدرس‌دهی می‌کنند. losLab PDF Library هم ریشه‌های استاندارد SOM و هم مسیرهای نسبی برهنه را می‌پذیرد، پس form1.FullName، $data.form1.FullName و xfa.datasets.data.form1.FullName همگی به همان گرهٔ داده حل می‌شوند، و سمت template نیز به همان شکل $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 وجود نداشته باشد کانتینر آن را می‌سازد
    // بازخوانی از طریق data 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 رفت‌وبرگشت می‌کنند، شامل سلسله‌مراتب، چندگزینشی و متن غنی؛ نظرات annotation به‌صورت یک زیرمجموعهٔ نشانه‌گذاری متنی تعریف‌شده با شکاف‌های روشن رفت‌وبرگشت می‌کنند؛ و XFA به‌صورت یک بستهٔ کامل، به‌همراه دسترسی SOM به‌ازای هر فیلد در بالای آن، نوشته و خوانده می‌شود. فرمت را با مصرف‌کننده تطبیق دهید، برای هر چیزی که خودتان دستی می‌سازید UTF-8 را اعلان کنید، و به یاد داشته باشید متدهای import همیشه فقط فیلدها و annotationهایی را لمس می‌کنند که از پیش با سند سازگارند. متدهای form-data، annotation و XFA که در اینجا توصیف شدند بخشی از losLab PDF Library برای Delphi و C++Builder هستند، که مرجع آن فهرست کامل پارامترها را برای هر نقطهٔ ورودی export و import حمل می‌کند