Technický článek

Slučování formulářů PDF v Delphi: pravidla pro duplicitní pole

PDF Library for Delphi slučuje dva dokumenty AcroForm s výslovnou politikou pro pole sdílející stejný název. MergeDocumentEx přebírá identifikátor zdrojového dokumentu a jednu ze tří strategií: dfsReject sloučení odmítne, dfsMerge ponechá sdílený název a synchronizuje hodnoty a dfsAutoNumber příchozí pole deterministicky přejmenuje. Kontrola názvů proběhne dřív, než se posunou jakákoli čísla objektů, takže odmítnuté sloučení ponechá oba dokumenty plně použitelné

Kdokoli, kdo sestavoval balíček žádostí PDF, na toto narazil. Tři formuláře, každý s polem nazvaným Signature, Date nebo Total, se sloučí do jednoho souboru. V AcroFormu je plně kvalifikovaný název pole identitou pole, takže dvě pole se stejným názvem nejsou vůbec dvě pole: vyplnění jednoho vyplní i druhé a podpis aplikovaný na jedno pokryje rozsah, který nikdo nezamýšlel

Proč se kolize názvů řeší ještě před sloučením?

Starší MergeDocument spojí obě pole kořenových polí AcroForm a nenabízí žádnou volbu. Horší je, že když je výsledek nepoužitelný, zjištění přijde až poté, co byla čísla objektů přečíslována a stromy stránek sešity, což volajícímu ponechá dokument ve stavu, ve kterém nebyl žádný z originálů

MergeDocumentEx pořadí obrací. Shromáždí názvy polí nejvyšší úrovně z obou dokumentů, porovná je a strategii aplikuje ještě předtím, než se cokoli pohne. Odmítnutí je proto čistý no-op: cílový dokument je nedotčený, zdrojový dokument je nedotčený a oba zůstávají otevřené a použitelné, což test sloučení ověřuje zpětným přečtením hodnoty pole ze zdroje po odmítnutém sloučení

Porovnání používá seřazenou, na velikost písmen citlivou množinu názvů, takže náklady jsou úměrné součtu počtu polí krát logaritmický faktor, ne součinu obou počtů. Citlivost na velikost písmen je zde správná volba, protože názvy polí PDF jsou na velikost písmen citlivé; jejich skládání by sloučilo pole, která specifikace považuje za odlišná

Tři strategie a kdy je která správná

dfsReject je strategie pro automatizované pipeline, které nesmí produkovat nejednoznačné dokumenty. Sloučení vrátí nulu a LastErrorCode nahlásí 705, vyhrazený kód, aby bylo možné duplicitní názvy odlišit od každého jiného selhání sloučení a nasměrovat je ke konkrétní nápravě, obvykle přejmenování polí ve zdroji

dfsMerge záměrně ponechá sdílený název a synchronizuje cílovou hodnotu i výchozí hodnotu do zdrojového pole, takže konformní prohlížeč zachází s několika widgety jako s jedním logicky pojmenovaným polem, což je standardní chování AcroForm pro pole s více anotacemi widgetů. Co nedělá, je slití různých slovníků polí do jediného objektu. Každé pole si ponechává vlastní přiřazení ke stránce, vzhled a akce, protože jejich sloučení by tiše zahodilo formátování a chování, které patří příchozímu dokumentu

dfsAutoNumber přejmenuje příchozí duplicity připojením číselné přípony počínající _2 a použije první volnou. Výsledek je reprodukovatelný: závisí pouze na přítomných názvech, nikdy na číslech objektů polí, takže sloučení stejné dvojice dokumentů dvakrát vydá oba dokumenty se stejnými názvy. Tato vlastnost má význam, když navazující kód, import FDF nebo mapování na databázi odkazuje na pole podle názvu

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
        // Oba dokumenty jsou stále neporušené - zkusíme to znovu s politikou
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

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

Všimněte si dvoukrokového vzoru v tomto kódu, který je možný jen proto, že odmítnutí je nedestruktivní. Nejprve zkuste přísnou politiku, prozkoumejte chybu, pak rozhodněte. U sloučení, které selže napůl cesty, by záložní varianta musela začít znovu opětovným načtením obou souborů

Jak sloučený formulář poté vypadá

Pod dfsMerge vytvoří cílové pole nazvané Shared nesoucí "Target value" a zdrojové pole se stejným názvem dvě pole, obě nazvaná Shared, obě hlásící cílovou hodnotu, protože cílová hodnota i výchozí hodnota jsou synchronizovány do příchozího pole. To je zamýšlená sémantika pro sdílený název: jedno logické pole, několik widgetů, jedna hodnota

Pod dfsAutoNumber stejný vstup vytvoří Shared a Shared_2 jako samostatná pole s nezávislými hodnotami. Mezi oběma vyberte položením jediné otázky: má vyplnění jednoho ovládacího prvku vyplnit i druhý? U jména podepisujícího opakovaného na každé části balíčku ano, a dfsMerge je správně. U celkové částky, která na každém formuláři znamená něco jiného, ne, a automatické číslování je správně

// Po sloučení vypište, co jste skutečně dostali
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Praktické poznámky pro sestavování balíčků formulářů

Úspěšné sloučení spotřebuje zdrojový dokument: je odstraněn ze seznamu dokumentů knihovny, což je důvod, proč DocumentCount klesne ze dvou na jednu. Poté už identifikátor zdroje dál nepoužívejte. Verze dokumentu se zvýší na vyšší z obou, takže sloučení formuláře PDF 2.0 do dokumentu 1.7 vydá soubor verze 2.0

Na pořadí u názvů záleží. Sloučení A do B a sloučení B do A vydá odlišné automaticky číslované výsledky, protože dokument provádějící sloučení si ponechává své názvy nezměněné. Když má balíček kanonický primární formulář, udělejte z něj cíl

Pole podpisu si zaslouží vlastní úvahu. Podpis aplikovaný před sloučením pokrývá pouze revizi, kterou podepsal, takže sloučení jej v praktickém smyslu zneplatní, protože se soubor od podepsání změnil. Nejprve sestavte a podepište až sestavený dokument, místo slučování podepsaných částí. Když jde při sloučení spíš o obsah stránek než o formuláře, je lepším nástrojem rychlejší cesta popsaná v článku rychlé sloučení PDF s posunem bajtových referencí

Nakonec naplánujte datovou stránku balíčku společně se sloučením. Pokud hodnoty polí přicházejí z externího systému, rozhodněte, zda tento systém adresuje pole podle názvu, ještě předtím, než zvolíte automatické číslování, protože Shared_2 nebude odpovídat mapování, které očekává Shared. Formáty importu a exportu popisuje článek výměna dat formulářů FDF, XFDF a XFA, a chování skriptování na úrovni pole, které může přejmenování rovněž ovlivnit, popisuje článek interaktivní akce formulářů a JavaScript

Slučování formulářů, výměna dat a podepisování běží ve stejné knihovně pro Delphi, C++Builder a Free Pascal; kompletní seznam funkcí je na stránce PDF Library for Delphi