Műszaki cikk

PDF-űrlapok egyesítése Delphiben: duplikált mezőszabályok

A PDF Library for Delphi explicit szabályzattal egyesít két AcroForm-dokumentumot az azonos nevű mezőkre vonatkozóan. A MergeDocumentEx a forrásdokumentum-azonosítót és három stratégia egyikét várja: a dfsReject elutasítja az egyesítést, a dfsMerge megtartja a megosztott nevet és szinkronizálja az értékeket, a dfsAutoNumber pedig determinisztikusan átnevezi a beérkező mezőket. A névvizsgálat azelőtt történik, hogy bármely objektumszám elmozdulna, így egy elutasított egyesítés mindkét dokumentumot teljesen használhatóan hagyja

Bárki, aki már összeállított egy PDF-jelentkezési csomagot, belefutott ebbe. Három űrlap, mindegyikben egy Signature, Date vagy Total nevű mező, egyetlen fájlba egyesítve. Egy AcroForm-ban a teljesen minősített mezőnév a mező azonossága, így két azonos nevű mező egyáltalán nem két mező: az egyik kitöltése kitölti a másikat is, egy az egyikre alkalmazott aláírás pedig olyan hatókört fed le, amelyet senki nem szándékozott

Miért a mezőnévütközés dől el az egyesítés előtt?

A régebbi MergeDocument egyszerűen összefűzi a két AcroForm gyökér mezőtömbjét, és nem kínál választást. Ami rosszabb, amikor az eredmény használhatatlan, a felfedezés azután történik, hogy az objektumszámokat újraszámozták, és az oldalfákat összevarrták, ami a hívónak egy olyan állapotú dokumentumot hagy a kezében, amilyenben egyik eredeti sem volt

A MergeDocumentEx megfordítja a sorrendet. Összegyűjti a felső szintű mezőneveket mindkét dokumentumból, összehasonlítja őket, és a stratégiát azelőtt alkalmazza, hogy bármi elmozdulna. Egy elutasítás ezért egy tiszta no-op: a céldokumentum érintetlen, a forrásdokumentum érintetlen, mindkettő nyitva és használható marad, amit az egyesítési teszt egy elutasított egyesítés után a forrásból visszaolvasott mezőértékkel ellenőriz

Az összehasonlítás egy rendezett, kis- és nagybetűt megkülönböztető névhalmazt használ, így a költség a kombinált mezőszám és egy logaritmikus tényező szorzatával arányos, nem a két szám szorzatával. A kis- és nagybetű-érzékenység itt a helyes választás, mert a PDF-mezőnevek kis- és nagybetűérzékenyek; ha hajtogatnánk őket, az egyesítené azokat a mezőket, amelyeket a specifikáció eltérőnek kezel

A három stratégia, és mikor melyik a helyes

A dfsReject az a stratégia olyan automatizált folyamatokhoz, amelyek nem állíthatnak elő kétértelmű dokumentumokat. Az egyesítés nullát ad vissza, a LastErrorCode pedig 705-öt jelent, egy dedikált kódot, hogy a duplikált nevek megkülönböztethetők legyenek minden más egyesítési hibától, és egy konkrét orvosláshoz irányíthatók legyenek, általában a mezők előzetes átnevezéséhez

A dfsMerge szándékosan megtartja a megosztott nevet, és szinkronizálja a célértéket és az alapértelmezett értéket a forrásmezőbe, így egy megfelelőségi néző a több widgetet egyetlen logikailag elnevezett mezőként kezeli, ami szabványos AcroForm-viselkedés egy több widget-annotációval rendelkező mezőnél. Amit nem tesz meg, hogy különböző mezőszótárakat egyetlen objektumba olvasztana. Minden mező megtartja a saját oldalkapcsolatát, megjelenését és akcióit, mert az összevonásuk csendben eldobná a beérkező dokumentumhoz tartozó formázást és viselkedést

A dfsAutoNumber a beérkező duplikátumokat egy _2-nél kezdődő numerikus utótag hozzáfűzésével nevezi át, az első szabadot választva. Az eredmény reprodukálható: csak a jelen lévő nevektől függ, soha a mező objektumszámaitól, így ugyanazon dokumentumpár kétszeri egyesítése mindkétszer ugyanazokat a neveket adja. Ez a tulajdonság akkor számít, amikor egy downstream kód, egy FDF-import vagy egy adatbázis-leképezés név szerint hivatkozik mezőkre

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
        // Mindkét dokumentum még mindig sértetlen - próbáld újra egy szabályzattal
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

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

Figyeld meg a kétlépéses mintát abban a kódban, amely csak azért lehetséges, mert az elutasítás nem romboló jellegű. Próbáld ki előbb a szigorú szabályzatot, vizsgáld meg a hibát, majd dönts. Egy félúton elbukó egyesítéssel a visszaesésnek elölről kellene kezdenie mindkét fájl újratöltésével

Hogyan néz ki az egyesített űrlap utána?

A dfsMerge alatt egy Shared nevű, „Target value” értéket hordozó célmező és egy ugyanilyen nevű forrásmező két mezőt eredményez, mindkettő Shared néven, mindkettő a célértéket jelenti, mert a célérték és az alapértelmezett érték szinkronizálva van a beérkező mezőbe. Ez a szándékolt szemantika egy megosztott névhez: egy logikai mező, több widget, egy érték

A dfsAutoNumber alatt ugyanaz a bemenet Shared-et és Shared_2-t ad, mint különálló mezőket, független értékekkel. A kettő közötti választáshoz egyetlen kérdést tegyél fel: az egyik vezérlő kitöltésének ki kell-e töltenie a másikat is? Egy aláíró nevéhez, amely a csomag minden részén ismétlődik, igen, és a dfsMerge a helyes. Egy olyan összeghez, amely minden űrlapon mást jelent, nem, és az automatikus számozás a helyes

// Egy egyesítés után soroltasd fel, mit kaptál valójában
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Gyakorlati megjegyzések űrlapcsomagok összeállításához

A sikeres egyesítés elfogyasztja a forrásdokumentumot: az eltávolításra kerül a könyvtár dokumentumlistájáról, ezért csökken a DocumentCount kettőről egyre. A forrásazonosítót ezután ne használd tovább. A dokumentumverzió a kettő közül a magasabbra emelkedik, így egy PDF 2.0 űrlap egy 1.7-es dokumentumba egyesítése 2.0-s fájlt ad

A sorrend számít a neveknél. Az A-nak B-be egyesítése és a B-nek A-ba egyesítése eltérő automatikusan számozott eredményeket ad, mivel az egyesítést végző dokumentum megtartja a neveit változatlanul. Ha egy csomagnak van kanonikus elsődleges űrlapja, azt tedd meg célnak

Az aláírásmezők saját megfontolást érdemelnek. Egy egyesítés előtt alkalmazott aláírás csak azt a revíziót fedi le, amelyet aláírt, így az egyesítés a gyakorlati értelemben érvényteleníti azt, hogy a fájl megváltozott az aláírás óta. Előbb állítsd össze, majd írd alá az összeállított dokumentumot, ahelyett hogy aláírt részeket egyesítenél. Amikor az egyesítés oldaltartalomról szól, nem űrlapokról, a gyors PDF-egyesítés bájt-hivatkozás-eltolással ismertetett gyorsabb útvonal a jobb eszköz

Végül a csomag adatoldalát az egyesítéssel együtt tervezd meg. Ha a mezőértékek egy külső rendszerből érkeznek, döntsd el, hogy az a rendszer név szerint címzi-e a mezőket, mielőtt automatikus számozást választanál, mert a Shared_2 nem fog egyezni egy olyan leképezéssel, amely Shared-et vár. Az import- és exportformátumokat az FDF-, XFDF- és XFA-űrlapadat-csere ismerteti, az átnevezés által szintén érintett mezőszintű szkriptviselkedést pedig az interaktív űrlapakciók és JavaScript ismerteti

Az űrlap-egyesítés, az adatcsere és az aláírás ugyanabban a könyvtárban fut Delphihez, C++Builderhez és Free Pascalhoz; a teljes funkciólista a PDF Library for Delphi oldalán található