HotPDF provodi multi-select list box vrijednosti kroz FDF i XFDF round trip držeći vrijednost polja kao polje od početka do kraja. Od verzije 2.755.0, ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF i ExportLoadedFormToXFDF zapisuju svaku odabranu opciju kao vlastiti FDF string ili XFDF <value> element, a odgovarajuće metode uvoza provjeravaju svaku vrijednost protiv opcija polja i ponovno izgrade /I indekse selekcije prije nego išta promijene. Ništa se usput ne zalijepi u jedan string
Kvar koji ovo popravlja lako se reproducira. Uzmite narudžbenicu s multi-select list boxom opcija proizvoda, pustite korisnika da odabere dvije, izvezite podatke obrasca za back-office sustav, pa uvezite uredenu datoteku natrag u PDF. Prije ove promjene list box se vratio prazan ili kriv. Razlog je što je jedna od export vrijednosti sadržavala line break, a stara putanja izravnala je selekcije u jedan string odvojen redovima. Izvlačenje višestrukih selekcija iz tog stringa nikad nije bilo pouzdano, a s export vrijednošću koja sama sadrži line break ne može raditi uopće
Zašto spajanje multi-select vrijednosti line breakovima kvari round trip?
Spajanjem selekcija u jedan string bacaju se granice među vrijednostima, a vrijednost može sadržavati separator, pa nijedan importer ne može string ispravno razdvojiti natrag. ISO 32000-1 §12.7.4.4 dopušta da /V unos choice polja bude ili jedan tekstualni string ili polje tekstualnih stringova, i list box s MultiSelect flagom (bit 22 od /Ff) koristi poljnu formu čim je odabrano više od jedne opcije. Ista sekcija definira /I kao polje indeksa opcija od nule u uzlaznom redoslijedu, koje preglednici koriste da razlikuju dvije opcije koje slučajno dijele export vrijednost. U HotPDF-u skalarski getter GetFormFieldValue čita samo string formu, pa je propuštanje polja kroz njega degradiralo izvoz u prazan string, a stari XFDF uvoz spajao je ponovljene <value> elemente s LF. Zamislite opciju koja je izvezena kao Deep, line feed, Blue: nakon spajanja Deep\nBlue\nRed mogu biti dvije selekcije ili tri, i datoteka ne daje načina da se zna koje. Popravak je bio prestati u potpunosti koristiti scalar usred round tripa
Što sadrže izvezeni FDF i XFDF?
HotPDF zapisuje multi-select vrijednost kao tipizirano polje u FDF-u i kao po jedan <value> element po selekciji u XFDF-u, pa granice ostaju vidljive na disku. U FDF-u svaki zadržava pravopis koji je imao u izvornom PDF-u: heksadecimalni stringovi izlaze kao hex, a literalni stringovi izlaze escapirani jednim helperom koji CR i LF pretvara u \r i \n. U XFDF-u korijen nosi xml:space="preserve" kako traži ISO 19444-1, što znači da svaki whitespace unutar tekstualnog elementa računa kao podatak. HotPDF zato zapiše početnu oznaku, escapirani tekst i završnu oznaku svakog <value> u jednom komadu, uvlačenje drži izvan elementa, a CR, LF i TAB kodira kao character reference da XML parser koji primjenjuje line-ending normalizaciju ne može promijeniti izvorne bajtove
<!-- FDF: jedno tipizirano polje po polju -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>
<!-- XFDF: jedan <value> po selekciji -->
<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 rubna slučaja izvoza vrijedna su poznavanja prije nego napišete pozivajući kod. Prvo, ExportLoadedFormToFDF gradi cijelo FDF tijelo u memoriji prije nego stvori ciljnu datoteku (popravljeno u 2.755.1), pa vrijednost koja se ne može izvesti, poput polja koje drži nešto osim stringova, baca iznimku bez skraćivanja postojeće datoteke. Drugo, prazna selekcija na list boxu koji nudi i export vrijednost praznog stringa dvosmislena je u XFDF-u, jer <value/> može značiti da ništa nije odabrano ili da je odabrana prazna opcija. ExportLoadedFormToXFDF u tom slučaju baca iznimku umjesto da nagađa, i baca je prije nego se ciljna datoteka otvori. FDF nema takvu dvosmislenost, jer su /V [] i /V [()] različiti. Oba FDF exportera također preskaču widget-only terminale bez /T imena, u skladu s XFDF exporterom, jer ih nijedan importer 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 zapisuju se kao /V [(...) (...)]
Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
try
Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
except
on E: Exception do
// Prazna selekcija plus prazna export opcija: XFDF ne zna
// razlikovati ih, a postojeća .xfdf datoteka ostaje netaknuta
ShowMessage('XFDF export refused: ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
end;
Kako HotPDF validira multi-select vrijednost pri uvozu?
HotPDF prima uvezeno polje samo kad je cilj choice polje s postavljenim MultiSelect flagom i kad se svaka vrijednost u polju poklapa s export vrijednošću u /Opt polju tog polja. Svaki se slot opcija može iskoristiti jednom, pa lista s dvije opcije koje dijele export vrijednost b prima [<62> <62>] kao dvije različite selekcije, a odbija treći b. Ponovno izgrađeni /I prati redoslijed /Opt, a ne redoslijed dolaznih vrijednosti, jer §12.7.4.4 traži uzlazne indekse. HotPDF gradi novi /V i /I kao odvojene objekte i dodjeljuje ih tek kad je svaka vrijednost prošla validaciju, pa odbijena vrijednost nikad ne ostavlja pola polja ni zastarjele indekse. Kopija se piše u polje koje se uvozi, a ne u zajednički predak, hex pravopisi koji stignu iz FDF-a ostaju hex kroz spremanje, i polja čiji proračuni ovise o list boxu označavaju se za preračun. Ako trebate postaviti samo jednu vrijednost, postavljanje jedne vrijednosti polja obrasca u učitanom PDF-u ide skalarskom putanjom, koja po dizajnu ne rukuje višestrukim selekcijama
Neki drugi alati pišu obične ASCII export vrijednosti kao hex stringove bez byte order marka, na primjer <416272>, pa XFDF izvoze ispisujući te hex znamenke kao tekst. Stroga usporedba literalnih stringova na povratku pada, i uvoz prekine. Verzija 2.755.1 dodaje jedan retry: kad vrijednost ne poklapa ni jednu opciju, HPDFHexSpellingText dekodira tekst kao hex payload i usporedi rezultat ponovno. Retry se primjenjuje samo na ulaz koji bi inače bacio iznimku, pa nikad ne mijenja vrijednost koja se već poklopila. Isto izdanje također je natjeralo skalarsku i poljnu putanju da koriste isti Unicode dekoder, koji razumije PDFDocEncoding, UTF-16 s bilo kojim byte order markom i UTF-8. Prije toga jedna logička vrijednost mogla se poklopiti na jednoj putanji i pasti na drugoj u dokumentima koji miješaju kodiranja
Zašto valjana FDF datoteka i dalje može izgubiti polja pri parsiranju?
FDF skener koji ne prati heksadecimalne stringove može presjeći rječnik polja na pola kad hex vrijednost završi tik uz terminator rječnika. U << /T (region) /V <416273>>> prvi > zatvara hex string, ali naivan skener čita ga zajedno sa sljedećim > kao kraj rječnika i tiho ispusti polje. FDF importer na razini datoteke već je vodio računa o tome je li unutar hex stringa, i u 2.755.1 poljni i rječnički skeneri iza ImportLoadedInterchangeFromFDF rade isto. Drugo pitanje tiče se indirektnih referenci. FDF datoteka mali je dokument PDF sintakse s vlastitim numeriranjem objekata (ISO 32000-1 §12.7.7), pa vrijednost poput /V [11 0 R] upućuje na objekt 11 FDF datoteke, a ne na objekt 11 PDF-a koji ispunjavate. Pojednostavljeni FDF parser u HotPDF-u ne razrješava reference unutar datoteke, pa takvo polje odbija umjesto da čita što god objekt 11 slučajno jest u ciljnom dokumentu
Uvoz iz datoteke, streama i XFDF-a javlja greške različito
Tri rute uvoza validiraju jednako, ali kvarove javljaju različito, i vrijedi jednu namjerno odabrati. ImportLoadedFormFromFDF preskače svako polje koje padne validaciju i vraća broj polja koje je ipak primijenio, pa je brojač niži od očekivanog jedini znak problema. ImportLoadedInterchangeFromFDF i ImportLoadedFormFromXFDF bacaju iznimku na prvom odbijenom polju. Svako se polje potvrđuje samostalno, pa polja obrađena prije iznimke čuvaju svoje nove vrijednosti. Ne tretirajte nijedno od ovih kao transakciju nad cijelom exchange datotekom: treba li vam sve-ili-ništa ponašanje, odbacite učitani dokument kad se iznimka dogodi umjesto da ga spremite
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; vrijednost izvan /Opt ili cilj bez multi-selecta baca iznimku
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;
Širenje XFDF callbackova bez lomljenja postojećih pozivatelja
Podrška polja u nižoj XFDF uniti živi u zasebnom zapisu, THPDFXFDFArrayAccess, i u novim overloadima HPDFXFDFExportFields i HPDFXFDFImportFields, a ne u dodatnim poljima dodanim na kraj postojećeg zapisa THPDFXFDFAccess. Razlog je binarna kompatibilnost. Kod koji puni THPDFXFDFAccess kao lokalnu varijablu često postavi samo slotove koje poznaje i nikad ne očisti ostatak, pa bi novi pokazivač funkcije dodan tom zapisu sadržavao smeće sa stacka, i biblioteka bi ga uzela za pravi callback. Sa zasebnim zapisom stari pozivatelji zadržavaju stari raspored i stare overloade, a ti overloadi interno predaju zapis polja sa svim-nil. Izvorni skalarski import overload i dalje spaja ponovljene vrijednosti s LF radi kompatibilnosti, i samo overload svjestan polja drži ih razdvojene. Kad vezujete vlastiti data store, počnite od Default(THPDFXFDFArrayAccess). Vraćajte True iz GetFormFieldValueArray za svako polje s vrijednošću liste, uključujući jedno u kojem ništa nije odabrano, i False za povratak na skalarski callback
uses HPDFXFDF;
// Obični pokazivač funkcije, ne "of object": Context nosi vaš vlastiti 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ša postojeća skalarska vezivanja
ArrayAccess := Default(THPDFXFDFArrayAccess); // svaki nekorišteni slot je nil
ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;
Multi-select exchange radi na list boxovima koji već postoje i imaju postavljen MultiSelect bit u /Ff. Za to kako se choice polja i njihovi flag bitovi uopće stvaraju, pogledajte dodavanje ListBox i ostalih AcroForm polja u učitani PDF. Za komentarski markup koji prolazi kroz <annots> stablo XFDF-a, pogledajte XFDF uvoz i izvoz anotacija u HotPDF-u. Potpuna API referenca i probno preuzimanje su na stranici HotPDF Delphi PDF komponente