Techninis straipsnis

Kelių pasirinkimų PDF laukai FDF ir XFDF apsukose (Delphi)

HotPDF perkelia kelių pasirinkimų sąrašo dėžutės reikšmes pro FDF ir XFDF, išlaikydamas lauko reikšmę masyvu nuo pradžios iki galo. Nuo 2.755.0 versijos ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF ir ExportLoadedFormToXFDF rašo kiekvieną pasirinktą parinktį kaip atskirą FDF eilutę arba XFDF <value> elementą, o atitinkami importo metodai kiekvieną reikšmę patikrina pagal lauko parinktis ir perstato /I pasirinkimų indeksus dar prieš ką nors keisdami. Niekas pakelyje nesuklijuojama į vieną eilutę

Gedimą, kurį tai taiso, nesunku atkurti. Paimkite užsakymo formą su kelių pasirinkimų sąrašo dėžute produkto parinkčių, leiskite vartotojui pasirinkti dvi iš jų, eksportuokite formos duomenis vidinei sistemai, tada importuokite redaguotą failą atgal į PDF. Iki šio pakeitimo sąrašo dėžutė sugrįždavo tuščia arba neteisinga. Priežastis ta, kad viena eksporto reikšmių turėjo eilutės lūžį, o senasis kelias buvo suplojęs pasirinkimus į vieną eilutėmis atskirtą eilutę. Iš tos eilutės sugrąžinti kelis pasirinkimus niekada nebuvo patikima, o su eksporto reikšme, kuri pati turi eilutės lūžį, tai apskritai negali veikti

Kodėl kelių pasirinkimų reikšmių suklijavimas eilutės lūžiais sugadina apsuką?

Suklijavus pasirinkimus į vieną eilutę, išmetamos ribos tarp reikšmių, o reikšmė gali turėti tą patį skirtuką, tad joks importuotojas negali eilutės teisingai vėl suskaidyti. ISO 32000-1 §12.7.4.4 leidžia choice lauko /V įrašui būti arba viena teksto eilute, arba teksto eilučių masyvu, o sąrašo dėžutė su MultiSelect vėliavėle (22 /Ff bitas) naudoja masyvo formą, tik paimta daugiau nei viena parinktis. Tas pats skyrius apibrėžia /I kaip 0-based parinkčių indeksų masyvą didėjančia tvarka, kurį peržiūros programos naudoja atskirti dviem parinktims, atsitiktinai dalijantiems eksporto reikšmę. HotPDF skaliarinis getteris GetFormFieldValue skaito tik eilutės formą, tad masyvas pro jį nuleisdavo eksportą iki tuščios eilutės, o senasis XFDF importas pakartotinius <value> elementus suklijuodavo su LF. Įsivaizduokite parinktį, eksportuotą kaip Deep, eilutės pabaiga, Blue: suklijavus Deep\nBlue\nRed gali būti du pasirinkimai arba trys, ir failas neduoda jokio būdo sužinoti, kuris. Pataisa buvo visiškai atsisakyti skaliaro apsukos viduryje

Senoji HotPDF kelių pasirinkimų apsuka, kurioje dvi paimtos sąrašo dėžutės parinktys, viena su įtaikytu eilutės lūžiu, skaliarinio GetFormFieldValue kelio suplojamos į vieną eilutę Deep, eilutės pabaiga, Blue, eilutės pabaiga, Red, kurią tolesni skaitytuvai gali perskaityti ir kaip du pasirinkimus, ir kaip tris
Kelių pasirinkimų reikšmių suklijavimas į vieną eilutę sunaikina reikšmių ribas, o eksporto reikšmė, pati turinti eilutės lūžį, padaro suplojtą formą dviprasmišką

Ką turi eksportuoti FDF ir XFDF failai?

HotPDF kelių pasirinkimų reikšmę rašo kaip tipizuotą masyvą FDF ir kaip po vieną <value> elementą kiekvienam pasirinkimui XFDF, tad ribos diske lieka matomos. FDF kiekvienas elementas išlaiko rašybą, kurią turėjo šaltinio PDF: šešioliktainės eilutės išeina kaip hex, o literalinės eilutės pabėgamos vienu pagalbininku, kuris CR ir LF paverčia \r ir \n. XFDF šaknis neša xml:space="preserve", kaip reikalauja ISO 19444-1, o tai reiškia, kad bet kokie tarpo simboliai teksto elemento viduje skaičiuojasi kaip duomenys. HotPDF todėl rašo kiekvieno <value> pradžios žymę, pabėgtą tekstą ir pabaigos žymę vienu gabalu, įtrauką laiko už elemento ribų, o CR, LF ir TAB užkoduoja kaip simbolių nuorodas, kad XML analizatorius, taikantis eilutės pabaigos normalizaciją, negalėtų pakeisti originalių baitų

Eksporto formos, kurias HotPDF rašo kelių pasirinkimų sąrašo dėžutei nuo 2.755.0: FDF neša po vieną tipizuotą masyvą laukui su /V [(Deep eilutės lūžis Blue) (Red)] ir hex regiono reikšme, o XFDF neša po vieną value elementą pasirinkimui po xml:space preserve, todėl tarpo simboliai skaičiuojasi kaip duomenys
Ribos diske lieka matomos: FDF kiekvieną pasirinkimą laiko kaip savą masyvo elementą, o XFDF kiekvieną rašo atskirame value elemente, tad jokiam importuotojui nereikia spėlioti
<!-- FDF: po vieną tipizuotą masyvą laukui -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>

<!-- XFDF: po vieną <value> pasirinkimui -->
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
  <fields>
    <field name="options">
      <value>Deep&#xA;Blue</value>
      <value>Red</value>
    </field>
  </fields>
</xfdf>

Du eksporto kraštiniai atvejai verti žinoti, dar nerašant kviečiančiojo kodo. Pirma, ExportLoadedFormToFDF pilną FDF turinį sukonstruoja atmintyje dar prieš kurdamas taikinio failą (pataisyta 2.755.1), tad reikšmė, kurios negalima eksportuoti, pavyzdžiui, masyvas, laikantis ką kitą nei eilutes, kelia išimtį netrumpindama egzistuojančio failo. Antra, tuščias pasirinkimas sąrašo dėžutėje, kuri kartu siūlo ir tuščios eilutės eksporto reikšmę, XFDF yra dviprasmiškas, nes <value/> gali reikšti, kad nepasirinkta niekas, arba kad pasirinkta tuščioji parinktis. ExportLoadedFormToXFDF tą atvejį kelia išimtimi, vietoj to, kad spėliotų, ir kelia ją dar prieš atverdamas taikinio failą. FDF tokios dviprasmybės neturi, nes /V [] ir /V [()] skiriasi. Abu FDF eksportuotojai taip pat praleidžia tik-valdiklio terminalinius laukus be /T vardo, sutapdami su XFDF eksportuotoju, nes joks importuotojas tų įrašų negalėtų sugrąžinti į lauką

var
  Pdf: THotPDF;
  Written: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
    begin
      // Kelių pasirinkimų sąrašo dėžutės rašomos kaip /V [(...) (...)]
      Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
      try
        Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
      except
        on E: Exception do
          // Tuščias pasirinkimas plius tuščia eksporto parinktis: XFDF jų
          // atskirti negali, o egzistuojantis .xfdf failas lieka neliestas
          ShowMessage('XFDF export refused: ' + E.Message);
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Kaip HotPDF patikrina kelių pasirinkimų reikšmę importuodama?

HotPDF importuotą masyvą priima tik tada, kai taikinys yra choice laukas su įjungta MultiSelect vėliavėle ir kiekviena masyvo reikšmė sutampa su eksporto reikšme lauko /Opt masyve. Kiekviena parinkčių vieta panaudojama kartą, tad sąrašas su dviem parinktimis, dalijančiomis eksporto reikšmę b, priima [<62> <62>] kaip du atskirus pasirinkimus ir atmeta trečią b. Perstatyta /I seka /Opt tvarką, o ne gaunamų reikšmių tvarką, nes §12.7.4.4 reikalauja didėjančių indeksų. HotPDF naują /V ir /I stato kaip atsietus objektus ir priskiria juos tik po to, kai kiekviena reikšmė praėjo patikrą, tad atmesta reikšmė niekada nepalieka pusės masyvo ar pasenusių indeksų. Kopija rašoma į importuojamą lauką, o ne į bendrą protėvio masyvą, hex rašybos, atkeliaujančios iš FDF, lieka hex per įrašymą, o laukai, kurių skaičiavimai priklauso nuo sąrašo dėžutės, pažymimi perskaičiavimui. Jei reikia nustatyti tik vieną reikšmę, vienos formos lauko reikšmės nustatymas įkeltame PDF eina skaliariniu keliu, kuris pagal projektą neapsimato kelių pasirinkimų

HotPDF importo patikra kelių pasirinkimų reikšmėms: taikinys turi būti choice laukas su MultiSelect /Ff, kiekviena gaunama reikšmė turi sutapti su /Opt eksporto reikšme, kiekvieną vietą panaudojus kartą, /I perstatoma didėjančia /Opt tvarka, o atsieti /V ir /I priskiriami tik visoms reikšmėms praeitus
Kiekviena gaunama reikšmė patikrinama pagal lauko parinktis dar prieš ką nors rašant, tad atmesta reikšmė niekada nepalieka pusės masyvo ar pasenusių pasirinkimų indeksų

Kai kurie kiti įrankiai paprastas ASCII eksporto reikšmes rašo kaip hex eilutes be baitų eiliškumo ženklo, pavyzdžiui <416272>, o tada eksportuoja XFDF, tuos hex skaitmenis išrašydami kaip tekstą. Griežtas literalinis gretinimas kelyje atgal žlunga, ir importas nutrūksta. 2.755.1 versija prideda vieną pakartojimą: kai reikšmė nesutampa su jokia parinktimi, HPDFHexSpellingText tekstą iškoduoja kaip hex turinį ir rezultatą sugretina dar kartą. Pakartojimas taikomas tik įvedimui, kuris kitaip būtų kėlęs išimtį, tad jis niekada nekeičia jau sutampusios reikšmės. Ta pati laida taip pat padarė, kad skaliarinis ir masyvinis keliai naudotų tą patį Unicode dekoderį, suprantantį PDFDocEncoding, UTF-16 su bet kuria baitų eiliškumo žyma ir UTF-8. Iki tol viena loginė reikšmė viename kelyje galėjo sutapti, o kitame žlugti dokumentuose, maišiusiuose koduotes

Kodėl teisėtas FDF failas analizės metu vis tiek gali prarasti laukus?

FDF skeneris, nesekantis šešioliktinėmis eilutėmis, gali perpjauti lauko žodyną pusiau, kai hex reikšmė baigiasi paties žodyno pabaigos ženklo kaimynystėje. << /T (region) /V <416273>>> pirmasis > užveria hex eilutę, bet naivus skeneris jį perskaito kartu su sekančiu > kaip žodyno pabaigą ir tyliai praranda lauką. Failo lygio FDF importuotojas jau sekė, ar yra hex eilutės viduje, o 2.755.1 tą patį daro masyvo ir žodynų skeneriai už ImportLoadedInterchangeFromFDF. Antras klausimas liečia netiesiogines nuorodas. FDF failas yra mažas PDF sintaksės dokumentas su savu objektų numeravimu (ISO 32000-1 §12.7.7), tad reikšmė, tokia kaip /V [11 0 R], nurodo FDF failo objektą 11, o ne jūsų pildomo PDF objektą 11. Supaprastintas HotPDF FDF analizatorius nuorodų faile neišsprendžia, tad tokį masyvą atmeta, vietoj to, kad skaitytų bet kokį objektą 11 taikinio dokumente

Failo, srauto ir XFDF importai klaidas praneša skirtingai

Trys importo keliai tikrina vienodai, bet žlugimus praneša skirtingai, ir verta vieną pasirinkti sąmoningai. ImportLoadedFormFromFDF praleidžia bet kurį lauką, nepraėjusį patikros, ir grąžina taikytų laukų skaičių, tad skaičius, mažesnis už tikėtą, yra vienintelis problemos požymis. ImportLoadedInterchangeFromFDF ir ImportLoadedFormFromXFDF kelia išimtį ties pirmuoju atmestu lauku. Kiekvienas laukas patvirtinamas savarankiškai, tad laukai, apdoroti iki išimties, išlaiko savo naujas reikšmes. Nė vieno iš šių elkitės ne kaip su transakcija per visą apsikeitimo failą: jei reikia visko-arba-nieko elgesio, įvykus išimčiai atminkite įkeltą dokumentą, vietoj to, kad jį įrašytumėte

var
  Pdf: THotPDF;
  Source: TMemoryStream;
  Status: AnsiString;
  Info: THPDFFDFInterchangeInfo;
begin
  Pdf := THotPDF.Create(nil);
  Source := TMemoryStream.Create;
  try
    Source.LoadFromFile('order-form-reviewed.fdf');
    if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
    try
      // Tik laukai; reikšmė už /Opt arba ne-kelių-pasirinkimų taikinys kelia išimtį
      if Pdf.ImportLoadedInterchangeFromFDF(Source, True, False, Status, Info) then
        Pdf.SaveLoadedDocument('order-form-filled.pdf');
    except
      on E: Exception do
        ShowMessage('Import rejected, nothing saved: ' + E.Message);
    end;
  finally
    Source.Free;
    Pdf.Free;
  end;
end;

XFDF callback plėtimas nesulaužant egzistuojančių kviečiančiųjų

Masyvų palaikymas žemesnio lygio XFDF unite gyvena atskirame įraše THPDFXFDFArrayAccess ir naujuose HPDFXFDFExportFields bei HPDFXFDFImportFields overloaduose, o ne papildomuose laukuose, pridėtuose prie egzistuojančio THPDFXFDFAccess įrašo galo. Priežastis – dvejetainis suderinamumas. Kodas, pildantis THPDFXFDFAccess kaip vietinį kintamąjį, dažnai nustato tik žinomas vietas ir niekada neišvalo kitų, tad nauja funkcijos rodyklė, pridėta į tą įrašą, turėtų steko šiukšlių, o biblioteka imtų ją kaip tikrą callback. Su atskiru įrašu senieji kviečiantieji išlaiko seną išdėstymą ir senus overloadus, o tie overloadai viduje perduoda visur nulinį masyvų įrašą. Originalus skaliarinio importo overloadas suderinamumui vis dar suklijuoja pakartotines reikšmes su LF, ir tik masyvus atpažįstantis overloadas jas laiko atskirtas. Susiedami savą duomenų saugyklą, pradėkite nuo Default(THPDFXFDFArrayAccess). Grąžinkite True iš GetFormFieldValueArray bet kuriam sąrašinės reikšmės laukui, įskaitant tuščią, ir False, norėdami sugrįžti į skaliarinį callback

uses HPDFXFDF;

// Paprasta funkcijos rodyklė, ne „of object“: Context neša jūsų pačių saugyklą
function StoreGetSelections(Context: Pointer; FieldIndex: Integer;
  out Values: THPDFXFDFValueArray): Boolean;
begin
  Result := TFormStore(Context).IsListField(FieldIndex);
  if Result then
    Values := TFormStore(Context).Selections(FieldIndex);
end;

procedure ExportStore(Store: TFormStore; out Bytes: TBytes);
var
  Access: THPDFXFDFAccess;
  ArrayAccess: THPDFXFDFArrayAccess;
begin
  Access := MakeStoreAccess(Store);             // jūsų egzistuojantys skaliariniai susiejimai
  ArrayAccess := Default(THPDFXFDFArrayAccess); // kiekviena nepanaudota vieta yra nil
  ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
  HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;

Kelių pasirinkimų apsikeitimas veikia ant sąrašo dėžučių, kurios jau egzistuoja ir turi uždėtą MultiSelect bitą /Ff. Kaip choice laukai ir jų vėliavėlių bitai iš viso kuriami, žiūrėkite ListBox ir kitų AcroForm laukų pridėjime į įkeltą PDF. Komentarų ženklinimui, einančiam pro XFDF <annots> medį, žiūrėkite XFDF anotacijų importą ir eksportą HotPDF. Pilna API atskaita ir bandomasis atsisiuntimas – HotPDF Delphi PDF komponento puslapyje