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
// Both documents are still intact - retry with a policy
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