HotPDF duce valorile de listă multi-select la dus-întors prin FDF și XFDF păstrând valoarea câmpului ca tablou de la un capăt la altul. Din versiunea 2.755.0, ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF și ExportLoadedFormToXFDF scriu fiecare opțiune selectată ca propriul șir FDF sau propriul element <value> XFDF, iar metodele de import potrivite verifică fiecare valoare împotriva opțiunilor câmpului și reconstruiesc indicii de selecție /I înainte să schimbe ceva. Nimic nu e lipit într-un singur șir pe drum
Eșecul reparat e ușor de reprodus. Luați un formular de comandă cu o listă multi-select de opțiuni de produs, lăsați un utilizator să aleagă două dintre ele, exportați datele formularului pentru un sistem de back-office, apoi importați fișierul editat înapoi în PDF. Înainte de această schimbare, lista revenea goală sau greșită. Motivul e că una dintre valorile de export conținea un line break, iar vechea cale aplatizase selecțiile într-un singur șir despărțit prin linii. Extragea mai multor selecții din acel șir n-a fost niciodată de încredere, iar cu o valoare de export care conține ea însăși un line break nici nu poate funcționa
De ce unirea valorilor multi-select cu line break-uri strică dus-întorsul?
Unirea selecțiilor într-un singur șir aruncă granițele dintre valori, iar o valoare poate conține separatorul, deci niciun importator nu poate despică șirul înapoi corect. ISO 32000-1 §12.7.4.4 permite intrării /V a unui câmp choice să fie fie un singur șir de text, fie un tablou de șiruri de text, iar o listă cu flag-ul MultiSelect (bitul 22 din /Ff) folosește forma de tablou de îndată ce mai mult de o opțiune e aleasă. Aceeași secțiune definește /I ca tablou de indici de opțiuni bazate pe zero, în ordine crescătoare, pe care vizualizatoarele îl folosesc ca să deosebească două opțiuni care se întâmplă să partajeze o valoare de export. În HotPDF, getter-ul scalar GetFormFieldValue citește doar forma de șir, deci trecerea unui tablou prin el degrada exportul la un șir gol, iar vechiul import XFDF uni elementele <value> repetate cu LF. Imaginați-vă o opțiune exportată ca Deep, line feed, Blue: după unire, Deep\nBlue\nRed putea fi două selecții sau trei, iar fișierul nu dă niciun mijloc de a ști care. Reparația a fost să nu se mai folosească deloc un scalar în mijlocul dus-întorsului
Ce conțin fișierele FDF și XFDF exportate?
HotPDF scrie o valoare multi-select ca tablou tipizat în FDF și ca un element <value> per selecție în XFDF, deci granițele rămân vizibile pe disc. În FDF, fiecare element păstrează scrierea pe care o avea în PDF-ul sursă: șirurile hexazecimale pleacă ca hex, iar șirurile literale sunt escape-uite de un singur helper care transformă CR și LF în \r și \n. În XFDF, rădăcina cară xml:space="preserve" așa cum cere ISO 19444-1, ceea ce înseamnă că orice spațiu alb din interiorul unui element de text contează ca date. HotPDF scrie deci tag-ul de deschidere, textul escape-uit și tag-ul de închidere ale fiecărui <value> într-o singură bucată, ține indentarea în afara elementului și encodhează CR, LF și TAB ca referințe de caractere, astfel încât un parser XML care aplică normalizarea de sfârșit de linie să nu poată schimba octeții originali
<!-- FDF: un tablou tipizat per câmp -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>
<!-- XFDF: un <value> per selecție -->
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="options">
<value>Deep
Blue</value>
<value>Red</value>
</field>
</fields>
</xfdf>
Două cazuri limită de export merită știute înainte să scrieți codul apelant. Întâi, ExportLoadedFormToFDF construiește tot body-ul FDF în memorie înainte să creeze fișierul țintă (reparat în 2.755.1), deci o valoare care nu poate fi exportată, precum un tablou care ține altceva decât șiruri, ridică excepție fără să trunchieze un fișier existent. Al doilea, o selecție goală pe o listă care oferă și o valoare de export de tip șir gol e ambiguă în XFDF, pentru că <value/> ar putea însemna că nu e selectat nimic sau că opțiunea goală e selectată. ExportLoadedFormToXFDF ridică excepție în cazul acela în loc să ghicească, și ridică înainte ca fișierul țintă să fie deschis. FDF n-o are pe aceasta, căci /V [] și /V [()] sunt distincte. Ambii exportatori FDF sără și terminalele doar-widget fără nume /T, la fel ca exportatorul XFDF, pentru că niciun importator nu ar putea vreodată să potrivească acele intrări înapoi la un câmp
var
Pdf: THotPDF;
Written: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
begin
// Listele multi-select sunt scrise ca /V [(...) (...)]
Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
try
Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
except
on E: Exception do
// Selecție goală plus o opțiune de export goală: XFDF nu le poate
// deosebi, iar fișierul .xfdf existent rămâne neatins
ShowMessage('XFDF export refused: ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
end;
Cum validează HotPDF o valoare multi-select la import?
HotPDF acceptă un tablou importat doar când ținta e un câmp choice cu flag-ul MultiSelect setat și fiecare valoare din tablou se potrivește cu o valoare de export din tabloul /Opt al câmpului. Fiecare slot de opțiune poate fi folosit o singură dată, deci o listă cu două opțiuni care partajează valoarea de export b acceptă [<62> <62>] ca două selecții distincte și respinge un al treilea b. /I-ul reconstruit urmează ordinea din /Opt, nu ordinea valorilor primite, căci §12.7.4.4 cere indici crescători. HotPDF construiește noul /V și noul /I ca obiecte detașate și îi atribuie doar după ce fiecare valoare a trecut validarea, deci o valoare respinsă nu lasă niciodată în urmă jumătate de tablou sau indici perimați. Copia e scrisă câmpului care se importă, nu unui tablou strămoș partajat, scrierile hexazecimale sosite din FDF rămân hex prin salvare, iar câmpurile ale căror calcule depind de listă sunt marcate pentru recalculare. Dacă aveți nevoie doar să setați o singură valoare, setarea unei valori de câmp de formular într-un PDF încărcat trece prin calea scalară, care din proiectare nu tratează selecții multiple
Câteva alte unelte scriu valori de export ASCII simple ca șiruri hexazecimale fără byte order mark, de exemplu <416272>, și apoi exportă XFDF scriind acele cifre hexazecimale ca text. O comparație literală strictă la drumul înapoi eșuează, iar importul se întrerupe. Versiunea 2.755.1 adaugă un singur retry: când o valoare nu se potrivește cu nicio opțiune, HPDFHexSpellingText decodează textul ca payload hexazecimal și compară rezultatul din nou. Retry-ul se aplică doar input-ului care altfel ar fi ridicat excepție, deci nu schimbă niciodată o valoare care se potrosea deja. Aceeași versiune a mai făcut ca calea scalară și cea de tablou să folosească același decodor Unicode, care înțelege PDFDocEncoding, UTF-16 cu oricare byte order mark și UTF-8. Înainte de asta, o valoare logică putea să se potrivească pe o cale și să eșueze pe cealaltă în documente care amestecau codări
De ce un fișier FDF valid poate pierde totuși câmpuri la parsare?
Un scanner FDF care nu urmărește șirurile hexazecimale poate tăia un dicționar de câmp în două când o valoare hex se termină chiar lângă terminatorul dicționarului. În << /T (region) /V <416273>>>, primul > închide șirul hexazecimal, dar un scanner naiv îl citește împreună cu următorul > drept sfârșit al dicționarului și lasă câmpul să dispară în tăcere. Importatorul FDF la nivel de fișier ținea deja evidența dacă se afla în interiorul unui șir hexazecimal, iar în 2.755.1 scanerele de tablouri și dicționare din spatele lui ImportLoadedInterchangeFromFDF fac la fel. O a doua problemă privește referințele indirecte. Un fișier FDF e un mic document cu sintaxă PDF cu propria lui numerotare de obiecte (ISO 32000-1 §12.7.7), deci o valoare precum /V [11 0 R] se referă la obiectul 11 din fișierul FDF, nu la obiectul 11 din PDF-ul pe care îl completați. Parser-ul FDF simplificat din HotPDF nu rezolvă referințele în interiorul fișierului, deci respinge un asemenea tablou în loc să citească orice ar fi obiectul 11 în documentul țintă
Importurile din fișier, stream și XFDF raportează erorile diferit
Cele trei rute de import validează la fel, dar raportează eșecurile diferit, și merită să alegi una în cunoștință de cauză. ImportLoadedFormFromFDF sare orice câmp care pică la validare și întoarce numărul de câmpuri pe care le-a aplicat totuși, deci un cont mai mic decât cel așteptat e singurul semn de problemă. ImportLoadedInterchangeFromFDF și ImportLoadedFormFromXFDF ridică excepție la primul câmp respins. Fiecare câmp e comis pe cont propriu, deci câmpurile procesate înainte de excepție își păstrează valorile noi. Nu tratați niciuna dintre ele ca o tranzacție peste tot fișierul de schimb: dacă aveți nevoie de comportament tot-sau-nimic, aruncați documentul încărcat când apare o excepție în loc să îl salvați
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
// Doar câmpuri; o valoare în afara /Opt sau o țintă non-multi-select ridică excepție
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;
Extinderea callback-urilor XFDF fără a strica apelanții existenți
Suportul de tablouri din unitatea XFDF de nivel mai jos trăiește într-un record separat, THPDFXFDFArrayAccess, și în suprascrieri noi ale lui HPDFXFDFExportFields și HPDFXFDFImportFields, nu în câmpuri suplimentare adăugate la coada recordului existent THPDFXFDFAccess. Motivul e compatibilitatea binară. Codul care umple un THPDFXFDFAccess ca variabilă locală setează adesea doar sloturile pe care le cunoaște și nu golește restul, deci un nou pointer de funcție adăugat la recordul acela ar conține gunoi de pe stivă, iar biblioteca l-ar lua drept callback adevărat. Cu un record separat, apelanții vechi păstrează vechiul layout și vechile suprascrieri, iar aceste suprascrieri pasați intern un record de tablou all-nil. Vechea suprascriere de import scalară unește în continuare valorile repetate cu LF pentru compatibilitate, iar doar suprascrierea conștientă de tablouri le ține separate. Când legați propriul vostru magazin de date, porniți de la Default(THPDFXFDFArrayAccess). Întoarceți True din GetFormFieldValueArray pentru orice câmp cu valoare de tip listă, inclusiv unul fără nimic selectat, și False ca să reveniți la callback-ul scalar
uses HPDFXFDF;
// Pointer de funcție simplu, nu "of object": Context cară propriul vostru 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); // legăturile voastre scalare existente
ArrayAccess := Default(THPDFXFDFArrayAccess); // fiecare slot nefolosit e nil
ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;
Schimbul multi-select funcționează pe liste care există deja și au bitul MultiSelect setat în /Ff. Pentru cum se creează câmpurile choice și biții lor de flag de la bun început, vedeți adăugarea de câmpuri ListBox și alte câmpuri AcroForm într-un PDF încărcat. Pentru markup-ul de comentarii care trece prin arborele <annots> al XFDF, vedeți importul și exportul de annotații XFDF în HotPDF. Referința completă a API-ului și descărcarea de probă stau pe pagina HotPDF Delphi PDF component