Techninis straipsnis

PDF formų sujungimas Delphi aplinkoje: pasikartojančių laukų taisyklės

PDF Library for Delphi sujungia du AcroForm dokumentus su aiškia politika laukams, kurie dalijasi tuo pačiu pavadinimu. MergeDocumentEx priima šaltinio dokumento identifikatorių ir vieną iš trijų strategijų: dfsReject atsisako sujungimo, dfsMerge palieka bendrą pavadinimą ir sinchronizuoja reikšmes, o dfsAutoNumber deterministiškai pervadina atkeliaujančius laukus. Pavadinimų patikra vyksta prieš bet kokiam objekto numeriui pasislenkant, todėl atmestas sujungimas palieka abu dokumentus visiškai naudojamus

Kiekvienas, kas sudarė PDF aplikacijos paketą, su tuo susidūrė. Trys formos, kiekvienoje po lauką pavadinimu Signature, Date ar Total, sujungiamos į vieną failą. AcroForm formate pilnas kvalifikuotas lauko pavadinimas yra lauko tapatybė, todėl du laukai su tuo pačiu pavadinimu iš tikrųjų nėra du laukai: vieno užpildymas užpildo kitą, o vienam pritaikytas parašas apima apimtį, kurios niekas nenumatė

Kodėl pavadinimo susikirtimas nusprendžiamas prieš sujungimą?

Senesnis MergeDocument sujungia abiejų AcroForm šaknies laukų masyvus ir nesiūlo jokio pasirinkimo. Kas dar blogiau, kai rezultatas netinkamas naudoti, tai paaiškėja jau po to, kai objektų numeriai pernumeruoti ir puslapių medžiai susiūti, todėl kviečiantysis lieka su dokumentu tokioje būsenoje, kokioje nebuvo nė vienas iš originalų

MergeDocumentEx apverčia šią tvarką. Ji surenka aukščiausio lygio laukų pavadinimus iš abiejų dokumentų, palygina juos ir pritaiko strategiją prieš bet kam pasislenkant. Todėl atmetimas yra švarus niekinis veiksmas: tikslinis dokumentas nepaliestas, šaltinio dokumentas nepaliestas, abu lieka atidaryti ir naudojami – tai patikrina sujungimo testas, perskaitydamas lauko reikšmę atgal iš šaltinio po atmesto sujungimo

Palyginimas naudoja surikiuotą, didžiąsias ir mažąsias raides skiriantį pavadinimų rinkinį, todėl kaina proporcinga bendram laukų skaičiui, padaugintam iš logaritminio faktoriaus, o ne dviejų skaičių sandaugai. Didžiųjų ir mažųjų raidžių skyrimas čia teisingas pasirinkimas, nes PDF laukų pavadinimai skiria raidžių dydį; jų sulyginimas sujungtų laukus, kuriuos specifikacija laiko atskirais

Trys strategijos, ir kada kiekviena teisinga

dfsReject yra strategija automatizuotiems konvejeriams, kurie neturi sukurti dviprasmiškų dokumentų. Sujungimas grąžina nulį, o LastErrorCode praneša 705 – specialų kodą, kad pasikartojantys pavadinimai galėtų būti atskirti nuo bet kokio kito sujungimo gedimo ir nukreipti į konkretų sprendimą, dažniausiai laukų pervadinimą prieš tai

dfsMerge sąmoningai palieka bendrą pavadinimą ir sinchronizuoja tikslinę reikšmę bei numatytąją reikšmę į šaltinio lauką, todėl standartus atitinkanti peržiūros priemonė kelis valdiklius traktuoja kaip vieną logiškai pavadintą lauką – tai standartinė AcroForm elgsena laukui su keliomis valdiklio anotacijomis. Ko ji nedaro – tai nesujungia skirtingų laukų žodynų į vieną objektą. Kiekvienas laukas išlaiko savo puslapio susiejimą, išvaizdą ir veiksmus, nes jų sujungimas tyliai atmestų formatavimą ir elgseną, priklausančią atkeliaujančiam dokumentui

dfsAutoNumber pervadina atkeliaujančius pasikartojimus, pridėdama skaitinį priesagą, prasidedančią nuo _2 ir imdama pirmą laisvą. Rezultatas atkuriamas: jis priklauso tik nuo esamų pavadinimų, niekada nuo laukų objektų numerių, todėl tos pačios dviejų dokumentų poros sujungimas du kartus abu kartus duoda tuos pačius pavadinimus. Ši savybė svarbi, kai tolesnis kodas, FDF importas ar duomenų bazės susiejimas kreipiasi į laukus pagal pavadinimą

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
        // Abu dokumentai vis dar nepaliesti - bandome dar kartą su politika
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

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

Atkreipkite dėmesį į dviejų žingsnių šabloną tame kode – jis įmanomas tik todėl, kad atmetimas yra nedestruktyvus. Pirmiausia išbandykite griežtą politiką, patikrinkite klaidą, tada nuspręskite. Su sujungimu, kuris nepavyksta pusiaukelėje, atsarginis variantas turėtų pradėti iš naujo, perkraudamas abu failus

Kaip atrodo sujungta forma po to

Naudojant dfsMerge, tikslinis laukas pavadinimu Shared, nešantis „Target value“, ir šaltinio laukas su tuo pačiu pavadinimu duoda du laukus, abu pavadintus Shared, abu pranešančius tikslinę reikšmę, nes tikslinė reikšmė ir numatytoji reikšmė sinchronizuojamos į atkeliaujantį lauką. Būtent tokia semantika ir numatyta bendram pavadinimui: vienas loginis laukas, keli valdikliai, viena reikšmė

Naudojant dfsAutoNumber, ta pati įvestis duoda Shared ir Shared_2 kaip atskirus laukus su nepriklausomomis reikšmėmis. Rinkitės tarp dviejų, užduodami vieną klausimą: ar vieno valdiklio užpildymas turėtų užpildyti ir kitą? Pasirašančiojo vardui, pasikartojančiam kiekvienoje paketo dalyje – taip, ir dfsMerge teisingas. Bendrai sumai, kuri kiekvienoje formoje reiškia kažką kitą – ne, ir automatinis numeravimas teisingas

// Po sujungimo išvardykite, ką iš tikrųjų gavote
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Praktinės pastabos formos paketų surinkimui

Sėkmingas sujungimas suvartoja šaltinio dokumentą: jis pašalinamas iš bibliotekos dokumentų sąrašo, todėl DocumentCount sumažėja nuo dviejų iki vieno. Po to nebenaudokite šaltinio identifikatoriaus. Dokumento versija pakeliama iki aukštesnės iš dviejų, todėl PDF 2.0 formos sujungimas su 1.7 dokumentu duoda 2.0 failą

Pavadinimams svarbi tvarka. A sujungimas su B ir B sujungimas su A duoda skirtingus automatiškai sunumeruotus rezultatus, nes sujungimą atliekantis dokumentas išlaiko savo pavadinimus nepakitusius. Kai pakete yra kanoninė pagrindinė forma, padarykite ją tiksline

Parašo laukai nusipelno atskiro apsvarstymo. Parašas, pritaikytas prieš sujungimą, apima tik tą redakciją, kurią jis pasirašė, todėl sujungimas jį praktine prasme padaro negaliojantį, nes failas pasikeitė nuo pasirašymo. Pirmiausia surinkite, o tada pasirašykite surinktą dokumentą, o ne sujunkite pasirašytas dalis. Kai sujungimas susijęs su puslapio turiniu, o ne su formomis, greitesnis kelias, aprašytas straipsnyje greitas PDF sujungimas su baitų nuorodų poslinkiu, yra geresnis įrankis

Galiausiai, planuokite paketo duomenų pusę kartu su sujungimu. Jei lauko reikšmės atkeliauja iš išorinės sistemos, prieš pasirenkant automatinį numeravimą nuspręskite, ar ta sistema kreipiasi į laukus pagal pavadinimą, nes Shared_2 neatitiks susiejimo, kuris tikisi Shared. Importo ir eksporto formatai aprašyti straipsnyje FDF, XFDF ir XFA formos duomenų mainai, o lauko lygio scenarijų elgsena, kurią taip pat gali paveikti pervadinimas, aprašyta straipsnyje interaktyvūs formos veiksmai ir JavaScript

Formų sujungimas, duomenų mainai ir pasirašymas veikia toje pačioje bibliotekoje Delphi, C++Builder ir Free Pascal aplinkoms; pilnas funkcijų sąrašas pateikiamas PDF Library for Delphi puslapyje