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
Š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
<!-- 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
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
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