Технічна стаття

Взаємообмін даними форм FDF, XFDF та XFA в Delphi

losLab PDF Library переміщує дані форм в і з PDF трьома різними способами: FDF та XFDF для значень полів AcroForm, ті самі два формати для коментарів анотацій, а також цілий пакет 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) >>, у якому рядки екрануються точно так само, як і літеральні рядки всередині PDF. 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 проводить другу межу: значення полів проти коментарів анотацій. Сімейство функцій для роботи з даними форм (ExportFormDataToFDF, ImportFormDataFromFDF, ExportFormDataToXFDF, ImportFormDataFromXFDF) читає та записує піддерево /Fields і не чіпає більше нічого. Натомість сімейство функцій анотацій (ExportAnnotationsToFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF, ImportAnnotationsFromXFDF) працює з піддеревом /Annots, що в термінології Acrobat відповідає експорту коментарів (Export Comments). Ці дві сфери ніколи не перетинаються, тому експорт значень полів не захопить випадкові нотатки рецензентів, а імпорт коментарів не змінить значення, які користувач уже ввів. Те, що віджет робить при натисканні або перерахунку, є третьою окремою темою, розглянутою в супутній статті про інтерактивні дії форм та JavaScript.

Як заповнити форму PDF з FDF або XFDF в Delphi?

Як заповнити форму PDF з FDF або XFDF в Delphi?

Завантажте документ, викличте один із методів імпорту та збережіть його. ImportFormDataFromXFDF та ImportFormDataFromFDF аналізують вхідні дані форми, зіставляють кожен запис із полем AcroForm за його повним ім'ям, встановлюють значення та повертають кількість фактично оновлених полів. Обидва методи оновлюють лише ті поля, які вже існують у цільовому PDF; жоден із них не створює нових полів для імен, які не визначені у формі, що захищає вашу форму від несананційованого розширення через випадкові або шкідливі файли даних.

var
  Lib: TPDFlib;
  FieldsSet: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('application-blank.pdf', '');
    // XFDF produced by a case-management system, already UTF-8 on disk
    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 (символи з діакритичними знаками, знаки валют, азіатські адреси). Якщо ви створюєте XFDF самостійно, оголошуйте та записуйте його в 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 reassembles; Skills fills the /V + /I arrays;
    // Notes gets V from <value> and RV from <value-richtext>
    Lib.ImportFormDataFromXFDFString(XFDF);
    Lib.SaveToFile('intake-filled.pdf');
  finally
    Lib.Free;
  end;
end;

Як передавати коментарі анотацій в обидва боки через FDF або XFDF?

Коментарі передаються через сімейство функцій для анотацій, і підтримувана підмножина навмисно є невеликою. ExportAnnotationsToXFDF та ImportAnnotationsFromXFDF, разом зі своїми аналогами для FDF, переносять текстову розмітку разом із полями, які дійсно коректно експортуються та імпортуються: підтип анотації, її прямокутник, її індекс сторінки (починаючи з 0), автора в T, тему в Subj, звичайний вміст у Contents та колір, який відображає трійку PDF /C DeviceRGB в атрибут XFDF #RRGGBB і навпаки. Імпорт приймає лише відомі імена елементів та пропускає будь-які записи, сторінка яких виходить за межі діапазону або прямокутник яких відсутній, тому відредагований вручну або сторонній XFDF не зможе додати некоректну анотацію в документ. Варто чітко зазначити те, що ця підмножина наразі не підтримує: формати розширеного тексту (RC), спливаючі вікна, quadpoints, рукописні списки (ink lists), вершини та запечені потоки оформлення (appearance streams) наразі знаходяться поза межами підтримки, тому геометрія виділення та кастомне оформлення штампів не збережуться при цьому підході. Щоб підтвердити, що саме було перенесено, пройдіться по анотаціях сторінок або ширшому дереву елементів, описаному в статті про пошук тексту та перерахування елементів сторінки.

var
  Src, Dest: TPDFlib;
  Xfdf: WideString;
begin
  Src := TPDFlib.Create;
  Dest := TPDFlib.Create;
  try
    Src.LoadFromFile('reviewed.pdf', '');
    Xfdf := Src.ExportAnnotationsToXFDFString;   // <annots> subset only

    Dest.LoadFromFile('clean-copy.pdf', '');     // pages must line up by 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> з його шаблонами та пакетами наборів даних, і зберігає його як потік, на який посилається /AcroForm/XFA. Бібліотека losLab PDF Library створює контейнер AcroForm за запитом, якщо в документі його немає, тому вам не потрібно додавати тимчасове поле лише для того, щоб прив'язати XFA. Вона також відкидає будь-який розібраний стан XFA, який зберігався до цього, щоб наступне зчитування відображало щойно записаний пакет, а не застарілий кеш. Оскільки пакет XFA є XML і не обов'язково має кодування UTF-8, бібліотека визначає маркер порядку байтів (BOM) UTF-16 та коректно декодує пакет перед синтаксичним аналізом, що критично для пакетів, створених інструментами, які за замовчуванням використовують UTF-16.

Після розміщення пакета методи GetXFAFormFieldValue та SetXFAFormFieldValue звертаються до окремих полів через їхній шлях Scripting Object Model (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);      // creates the AcroForm container if none exists
    // read back through the data DOM with an SOM path
    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, довідник якої містить повний список параметрів для кожної точки входу експорту та імпорту.