Tehnički članak

Multi-select PDF polja u FDF i XFDF round tripu (Delphi)

HotPDF provlači vrednosti multi-select list boxa kroz FDF i XFDF držeći vrednost polja kao niz od početka do kraja. Od verzije 2.755.0, ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF i ExportLoadedFormToXFDF upisuju svaku izabranu opciju kao sopstveni FDF string ili XFDF <value> element, a odgovarajuće metode uvoza proveravaju svaku vrednost naspram opcija polja i ponovo grade /I indekse izbora pre nego što išta promene. Ništa se usput ne zalepljuje u jedan string

Kvar koji ovo popravlja se lako reprodukuje. Uzmite narudžbenicu sa multi-select list boxom opcija proizvoda, pustite korisnika da izabere dve, izvezite podatke formulara za back-office sistem, pa uvezite obrađen fajl nazad u PDF. Pre ove promene list box se vraćao prazan ili pogrešan. Razlog je što je jedna od izvoznih vrednosti sadržala prelaz reda, a stara putanja je bila ispljoštila izbore u jedan string razdvojen redovima. Izvlačenje višestrukih izbora iz tog stringa nikada nije bilo pouzdano, a sa izvoznom vrednošću koja sama sadrži prelaz reda uopšte ne može da radi

Zašto spajanje multi-select vrednosti prelazima reda kvari round trip?

Spajanje izbora u jedan string baca u vetar granice između vrednosti, a vrednost može sadržati separator, pa nijedan uvoznik ne može da razdeli string nazad ispravno. ISO 32000-1 §12.7.4.4 dozvoljava da /V unos choice polja bude ili jedan tekstualni string ili niz tekstualnih stringova, i list box sa MultiSelect flagom (bit 22 od /Ff) koristi oblik niza čim je izabrano više od jedne opcije. Isti odeljak definiše /I kao niz indeksa opcija sa bazom nula u rastućem redosledu, koje pregledači koriste da razdvoje dve opcije kojima se slučajno poklopi izvozna vrednost. U HotPDF-u skalarni getter GetFormFieldValue čita samo oblik stringa, pa je provođenje niza kroz njega degradiralo izvoz u prazan string, a stari XFDF uvoz je spajao ponovljene <value> elemente sa LF. Zamislite opciju izvezenu kao Deep, prelaz reda, Blue: posle spajanja, Deep\nBlue\nRed može biti dva izbora ili tri, i fajl ne daje način da se zna koje. Popravka je bila da se scalar potpuno prestane koristiti usred round trip-a

Stari HotPDF multi-select round trip gde dve izabrane opcije list boxa, jedna sa ugrađenim prelazom reda, ispljošti skalarna GetFormFieldValue putanja u jedan string Deep, prelaz reda, Blue, prelaz reda, Red, koji nizvodni čitači mogu parsirati ili kao dva izbora ili kao tri
Spajanje multi-select vrednosti u jedan string uništava granice vrednosti, a izvozna vrednost koja sama sadrži prelaz reda čini ispljošteni oblik dvosmislenim

Šta sadrže izvezeni FDF i XFDF fajlovi?

HotPDF upisuje multi-select vrednost kao tipiziran niz u FDF-u i kao po jedan <value> element po izboru u XFDF-u, pa granice ostaju vidljive na disku. U FDF-u svaka stavka zadržava pravopis koji je imala u izvornom PDF-u: heksadecimalni stringovi izlaze kao hex, a literalni stringovi beže se jednim helperom koji CR i LF pretvara u \r i \n. U XFDF-u koren nosi xml:space="preserve" kako zahteva ISO 19444-1, što znači da svaki prazan znak unutar tekstualnog elementa računa se kao podatak. HotPDF zato upisuje početnu oznaku, bežeći tekst i završnu oznaku svakog <value> u jednom komadu, drži uvlačenje van elementa, i enkoduje CR, LF i TAB kao karakter reference da XML parser koji primenjuje normalizaciju krajeva redova ne može da promeni originalne bajtove

Oblici izvoza koje HotPDF upisuje za multi-select list box od 2.755.0: FDF nosi jedan tipiziran niz po polju sa /V [(Deep prelaz reda Blue) (Red)] i heks region vrednošću, dok XFDF nosi po jedan value element po izboru pod xml:space preserve pa se prazni znakovi računaju kao podatak
Granice ostaju vidljive na disku: FDF drži svaki izbor kao sopstvenu stavku niza a XFDF upisuje svaki u odvojen value element, pa nijedan uvoznik ne mora da pogađa
<!-- FDF: jedan tipiziran niz po polju -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>

<!-- XFDF: jedan <value> po izboru -->
<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>

Dva granična slučaja izvoza vredi znati pre nego što napišete pozivajući kod. Prvo, ExportLoadedFormToFDF gradi kompletan FDF u memoriji pre nego što napravi ciljni fajl (popravljeno u 2.755.1), pa vrednost koja se ne može izvesti, poput niza koji drži nešto drugo od stringova, podiže izuzetak bez skraćivanja postojećeg fajla. Drugo, prazan izbor na list boxu koji nudi i izvoznu vrednost praznog stringa je dvosmislen u XFDF-u, jer <value/> može značiti da ništa nije izabrano ili da je izabrana prazna opcija. ExportLoadedFormToXFDF u tom slučaju podiže izuzetak umesto da pogađa, i podiže ga pre nego što se ciljni fajl otvori. FDF nema tu dvosmislenost, jer su /V [] i /V [()] različiti. Oba FDF izvoza takođe preskaču widget-only završetke bez /T imena, u skladu sa XFDF izvozom, jer ih nijedan uvoznik ne bi mogao vratiti na polje

var
  Pdf: THotPDF;
  Written: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
    begin
      // Multi-select list boxovi se upisuju kao /V [(...) (...)]
      Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
      try
        Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
      except
        on E: Exception do
          // Prazan izbor plus prazna izvozna opcija: XFDF ne može da ih
          // razdvoji, a postojeći .xfdf fajl ostaje netaknut
          ShowMessage('XFDF export refused: ' + E.Message);
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Kako HotPDF validira multi-select vrednost pri uvozu?

HotPDF prihvata uvezen niz samo kada je cilj choice polje sa postavljenim MultiSelect flagom i kada se svaka vrednost u nizu poklapa sa izvoznom vrednošću u /Opt nizu polja. Svaki slot opcije može se upotrebiti jednom, pa lista sa dve opcije koje dele izvoznu vrednost b prihvata [<62> <62>] kao dva različita izbora i odbija treći b. Ponovo izgrađeni /I prati redosled /Opt-a, a ne redosled dolazećih vrednosti, jer §12.7.4.4 zahteva rastuće indekse. HotPDF gradi novi /V i /I kao odvojene objekte i dodeljuje ih tek kad je svaka vrednost prošla validaciju, pa odbijena vrednost nikada ne ostavlja pola niza ili zastarele indekse. Kopija se upisuje u polje koje se uvozi, a ne u deljeni predak niz, heks pravopisi koji stignu iz FDF-a ostaju hex kroz čuvanje, a polja čija izračunavanja zavise od list boxa označavaju se za preračunavanje. Ako vam treba samo jedna vrednost, postavljanje jedne vrednosti polja formulara u učitanom PDF-u ide kroz skalarnu putanju, koja po dizajnu ne barata višestruke izbore

Validacija uvoza multi-select vrednosti u HotPDF-u: cilj mora biti choice polje sa MultiSelect postavljenim u /Ff, svaka dolazeća vrednost mora poklopiti /Opt izvoznu vrednost pri čemu se svaki slot koristi jednom, /I se ponovo gradi rastuće u /Opt redosledu, a odvojeni /V i /I dodeljuju se tek kad sve vrednosti prođu
Svaka dolazeća vrednost se proverava naspram opcija polja pre nego što se išta upiše, pa odbijena vrednost nikada ne ostavlja pola niza ili zastarele indekse izbora

Neki drugi alati upisuju obične ASCII izvozne vrednosti kao heks stringove bez byte order marka, na primer <416272>, pa onda izvoze XFDF ispisujući te heks cifre kao tekst. Stroga literalna poredba na povratku pada, i uvoz prekine. Verzija 2.755.1 dodaje jedan retry: kada se vrednost ne poklopi ni sa jednom opcijom, HPDFHexSpellingText dekoduje tekst kao heks payload i ponovo poredi rezultat. Retry se odnosi samo na ulaz koji bi inače podigao izuzetak, pa nikada ne menja vrednost koja se već poklopila. Isto izdanje je takode nateralo skalarnu i nizovnu putanju da koriste isti Unicode dekoder, koji razume PDFDocEncoding, UTF-16 sa bilo kojim byte order markom i UTF-8. Pre toga, jedna logička vrednost mogla je da se poklopi na jednoj putanji i padne na drugoj u dokumentima koji mešaju enkodovanja

Zašto validan FDF fajl može i dalje da izgubi polja pri parsiranju?

FDF skener koji ne prati heksadecimalne stringove može preseći rečnik polja na pola kad se heks vrednost završi tik uz terminator rečnika. U << /T (region) /V <416273>>> prvi > zatvara heks string, ali naivan skener čita ga zajedno sa sledećim > kao kraj rečnika i tiho baca polje. FDF uvoznik na nivou fajla već je vodio računa da li je unutar heks stringa, i u 2.755.1 skeneri niza i rečnika iza ImportLoadedInterchangeFromFDF rade isto. Drugo pitanje se tiče indirektnih referenci. FDF fajl je mali dokument u PDF sintaksi sa sopstvenim numerisanjem objekata (ISO 32000-1 §12.7.7), pa vrednost poput /V [11 0 R] upućuje na objekat 11 FDF fajla, a ne na objekat 11 PDF-a koji popunjavate. Pojednostavljeni FDF parser u HotPDF-u ne razrešava reference unutar fajla, pa takav niz odbija umesto da čita šta god da je objekat 11 u ciljnom dokumentu

Uvozi iz fajla, streama i XFDF prijavljuju greške drugačije

Tri uvozne rute validiraju isto ali prijavljuju kvarove drugačije, i vredi jednu izabrati namerno. ImportLoadedFormFromFDF preskače svako polje koje padne validaciju i vraća broj polja koje je zaista primenio, pa je broj manji od očekivanog jedini znak problema. ImportLoadedInterchangeFromFDF i ImportLoadedFormFromXFDF podižu izuzetak na prvom odbijenom polju. Svako polje se posvećuje samo za sebe, pa polja obrađena pre izuzetka zadržavaju nove vrednosti. Ni jedan od njih ne tretirajte kao transakciju preko celog fajla razmene: ako treba sve-ili-ništa ponašanje, odbacite učitani dokument kad se izuzetak desi umesto da ga sačuvate

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
      // Samo polja; vrednost van /Opt ili cilj bez multi-selecta podiže izuzetak
      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;

Proširivanje XFDF callbackova bez lomljenja postojećih pozivalaca

Podrška za nizove u nižoj XFDF jedinici živi u odvojenom zapisu, THPDFXFDFArrayAccess, i u novim overloadovima HPDFXFDFExportFields i HPDFXFDFImportFields, a ne u dodatnim poljima dodanim na kraj postojećeg THPDFXFDFAccess zapisa. Razlog je binarna kompatibilnost. Kod koji puni THPDFXFDFAccess kao lokalnu promenljivu često postavlja samo slotove koje zna i nikada ne briše ostatak, pa bi novi pokazivač funkcije dodat tom zapisu sadržao smeće sa steka, i biblioteka bi ga uzela za pravi callback. Sa odvojenim zapisom, stari pozivaoci zadržavaju stari raspored i stare overloadove, a ti overloadovi interno predaju zapis niza sa svim nil. Originalni skalarni uvozni overload i dalje spaja ponovljene vrednosti sa LF radi kompatibilnosti, i samo overload svestan nizova drži ih razdvojene. Kad vezujete sopstveni data store, krenite od Default(THPDFXFDFArrayAccess). Vraćajte True iz GetFormFieldValueArray za svako polje sa vrednošću liste, uključujući i ono u kome ništa nije izabrano, i False za povratak na skalarni callback

uses HPDFXFDF;

// Običan pokazivač funkcije, a ne „of object”: Context nosi vaš sopstveni store
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);             // vaše postojeće skalarne veze
  ArrayAccess := Default(THPDFXFDFArrayAccess); // svaki nekorišćeni slot je nil
  ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
  HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;

Multi-select razmena radi na list boxovima koji već postoje i imaju MultiSelect bit postavljen u /Ff-u. Za to kako se choice polja i njihovi flag bitovi uopšte prave, pogledajte dodavanje ListBox i drugih AcroForm polja u učitani PDF. Za komentar markup koji prolazi kroz <annots> drvo XFDF-a, pogledajte uvoz i izvoz XFDF anotacija u HotPDF-u. Kompletna API referenca i probno preuzimanje su na stranici HotPDF Delphi PDF component-e