HotPDF round-tripper multi-select list box-værdier gennem FDF og XFDF ved at holde feltværdien som et array hele vejen igennem. Siden version 2.755.0 skriver ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF og ExportLoadedFormToXFDF hver valgte option som sin egen FDF-streng eller sit eget XFDF <value>-element, og de matchende import-metoder tjekker hver værdi mod felt-options og genopbygger /I-valgindekserne, før de ændrer noget. Intet bliver limet sammen til én streng undervejs
Fejlen, dette fikser, er let at reproducere. Tag en ordreformular med en multi-select list box af produktopOptioner, lad en bruger vælge to af dem, eksportér formulardataene til et back-office-system, og importér så den redigerede fil tilbage i PDF'en. Før denne ændring kom list boksen tilbage tom eller forkert. Grunden er, at én af eksportværdierne indeholdt et linjeskift, og den gamle sti havde fladet valgene ud til én enkelt linje-separeret streng. At få flere valg ud af den streng var aldrig pålideligt, og med en eksportværdi, der selv indeholder et linjeskift, kan det slet ikke lade sig gøre
Hvorfor ødelægger sammensætning af multi-select-værdier med linjeskift roundtrippet?
At sætte valgene sammen til én streng smider grænserne mellem værdier væk, og en værdi kan indeholde separatoren, så ingen importer kan splitte strengen korrekt op igen. ISO 32000-1 §12.7.4.4 tillader, at /V-entryen på et choice-felt enten er én enkelt tekststreng eller et array af tekststrenge, og en list box med MultiSelect-flaget (bit 22 i /Ff) bruger array-formen, så snart mere end én option er valgt. Samme sektion definerer /I som et array af 0-baserede option-indekser i stigende rækkefølge, som viewers bruger til at skelne to options, der tilfældigvis deler en eksportværdi. I HotPDF læser den skalære getter GetFormFieldValue kun strengformen, så at køre et array gennem den nedgraderede eksporten til en tom streng, og den gamle XFDF-import satte gentagne <value>-elementer sammen med LF. Forestil dig en option eksporteret som Deep, line feed, Blue: efter sammen sætningen kunne Deep\nBlue\nRed være to valg eller tre, og filen giver ingen måde at vide hvilket. Fixet var at holde op med at bruge en skalar midt i roundtrippet helt
Hvad indeholder de eksporterede FDF- og XFDF-filer?
HotPDF skriver en multi-select-værdi som et typet array i FDF og som ét <value>-element pr. valg i XFDF, så grænserne forbliver synlige på disken. I FDF beholder hvert item den stavemåde, det havde i kilde-PDF'en: hexadecimale strenge går ud som hex, og literale strenge escapes af én enkelt helper, der omskriver CR og LF til \r og \n. I XFDF bærer roden xml:space="preserve", som ISO 19444-1 kræver, hvilket betyder, at al whitespace inde i et tekst-element tæller som data. HotPDF skriver derfor start-tagget, den escapede tekst og slut-tagget af hver <value> i ét stykke, holder indentation uden for elementet og encoder CR, LF og TAB som character references, så en XML-parser, der anvender line-ending-normalisering, ikke kan ændre de originale bytes
<!-- FDF: ét typet array pr. felt -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>
<!-- XFDF: ét <value> pr. valg -->
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="options">
<value>Deep
Blue</value>
<value>Red</value>
</field>
</fields>
</xfdf>
To eksport-edge-cases er værd at kende, før du skriver kaldekoden. For det første bygger ExportLoadedFormToFDF den komplette FDF-krop i hukommelsen, før den opretter målfilen (fikset i 2.755.1), så en værdi, der ikke kan eksporteres, såsom et array, der holder noget andet end strenge, rejser uden at afkorte en eksisterende fil. For det andet er et tomt valg på en list box, der også tilbyder en tom-streng-eksportværdi, tvetydigt i XFDF, for <value/> kunne betyde, at intet er valgt, eller at den tomme option er valgt. ExportLoadedFormToXFDF rejser i det tilfælde i stedet for at gætte, og den rejser, før målfilen åbnes. FDF har ingen sådan tvetydighed, da /V [] og /V [()] er forskellige. Begge FDF-eksportører skipper også widget-only-terminaler uden /T-navn, matchende XFDF-eksportøren, for ingen importer kunne nogensinde matche de entries tilbage til et felt
var
Pdf: THotPDF;
Written: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
begin
// Multi-select list boxes skrives som /V [(...) (...)]
Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
try
Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
except
on E: Exception do
// Tomt valg plus en tom eksport-option: XFDF kan ikke skelne
// dem, og den eksisterende .xfdf-fil efterlades urørt
ShowMessage('XFDF export refused: ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
end;
Hvordan validerer HotPDF en multi-select-værdi ved import?
HotPDF accepterer et importeret array kun, når target er et choice-felt med MultiSelect-flaget sat, og hver værdi i arrayet matcher en eksportværdi i feltets /Opt-array. Hver option-slot kan bruges én gang, så en liste med to options, der deler eksportværdien b, accepterer [<62> <62>] som to adskilte valg og afviser en tredje b. Det genopbyggede /I følger /Opt-rækkefølgen og ikke rækkefølgen af de indkommende værdier, da §12.7.4.4 kræver stigende indekser. HotPDF bygger den nye /V og /I som løsrevne objekter og tildeler dem først, efter hver værdi har bestået valideringen, så en afvist værdi aldrig efterlader et halvt array eller forældede indekser. Kopien skrives til feltet, der importeres, snarere end til et delt ancestor-array, hex-stavemåder, der ankommer fra FDF, forbliver hex gennem gemningen, og felter, hvis beregninger afhænger af list boksen, markeres til genberegning. Behøver du kun at sætte én værdi, går at sætte én formularfeltværdi i en loadet PDF gennem den skalære sti, som af design ikke håndterer flere valg
Nogle andre værktøjer skriver almindelige ASCII-eksportværdier som hex-strenge uden byte order mark, for eksempel <416272>, og eksporterer derefter XFDF ved at skrive de hex-cifre ud som tekst. En streng literal-sammenligning på vejen tilbage fejler, og importen afbrydes. Version 2.755.1 tilføjer én retry: når en værdi ikke matcher nogen option, dekoder HPDFHexSpellingText teksten som en hex-payload og sammenligner resultatet igen. Retry'en gælder kun input, der ellers ville have rejst, så den ændrer aldrig en værdi, der allerede matchede. Samme release fikset også, at den skalære og array-stien bruger samme Unicode-decoder, som forstår PDFDocEncoding, UTF-16 med begge byte order marks og UTF-8. Før det kunne én logisk værdi matche på én sti og fejle på den anden i dokumenter, der blandede encodings
Hvorfor kan en gyldig FDF-fil stadig miste felter under parsing?
En FDF-scanner, der ikke sporer hexadecimale strenge, kan skære en feltdictionary over i halvdele, når en hex-værdi slutter lige ved siden af dictionary-terminatoren. I << /T (region) /V <416273>> lukker den første > hex-strengen, men en naiv scanner læser den sammen med den næste > som enden på dictionaryen og dropper feltet i stilhed. Filniveau-FDF-importeren holdt allerede styr på, om den var inde i en hex-streng, og i 2.755.1 gør array- og dictionary-scannerne bag ImportLoadedInterchangeFromFDF det samme. Et andet problem vedrører indirekte referencer. En FDF-fil er et lille PDF-syntaks-dokument med sin egen objektnummerering (ISO 32000-1 §12.7.7), så en værdi som /V [11 0 R] refererer til objekt 11 i FDF-filen, ikke objekt 11 i den PDF, du udfylder. Den forenklede FDF-parser i HotPDF resolver ikke referencer inde i filen, så den afviser sådan et array i stedet for at læse, hvad objekt 11 nu tilfældigvis er i target-dokumentet
Fil-, stream- og XFDF-importer rapporterer fejl forskelligt
De tre import-ruter validerer på samme måde, men rapporterer fejl forskelligt, og det er værd at vælge én med vilje. ImportLoadedFormFromFDF skipper ethvert felt, der fejler valideringen, og returnerer antallet af felter, den faktisk anvendte, så et antal lavere end forventet er det eneste tegn på et problem. ImportLoadedInterchangeFromFDF og ImportLoadedFormFromXFDF rejser ved det første afviste felt. Hvert felt committes for sig selv, så felter, der er behandlet før exceptionen, beholder deres nye værdier. Behandl ingen af dem som en transaktion over hele exchange-filen: behøver du all-or-nothing-adfærd, så kassér det loadede dokument, når en exception opstår, i stedet for at gemme det
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
// Kun felter; en værdi uden for /Opt eller et ikke-multi-select-target rejser
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;
At udvide XFDF-callbacks uden at bryde eksisterende kaldere
Array-supporten i den lavniveaus XFDF-unit bor i en separat record, THPDFXFDFArrayAccess, og i nye overloads af HPDFXFDFExportFields og HPDFXFDFImportFields, ikke i ekstra felter tilføjet i enden af den eksisterende THPDFXFDFAccess-record. Grunden er binær kompatibilitet. Kode, der fylder THPDFXFDFAccess som en lokal variabel, sætter ofte kun de slots, den kender til, og rydder aldrig resten, så en ny funktionspointer tilføjet til den record ville indeholde stak-skrald, og biblioteket ville tage den for en rigtig callback. Med en separat record beholder gamle kaldere det gamle layout og de gamle overloads, og de overloads giver et all-nil array-record videre internt. Den originale skalære import-overload sætter stadig gentagne værdier sammen med LF af kompatibilitetshensyn, og kun den array-bevidste overload holder dem adskilt. Når du binder din egen datastore, så start fra Default(THPDFXFDFArrayAccess). Returnér True fra GetFormFieldValueArray for ethvert liste-værdi-felt, også ét med intet valgt, og False for at falde tilbage til den skalære callback
uses HPDFXFDF;
// Almindelig funktionspointer, ikke "of object": Context bærer din egen 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); // dine eksisterende skalære bindings
ArrayAccess := Default(THPDFXFDFArrayAccess); // hver ubrugt slot er nil
ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;
Multi-select-udveksling virker på list boxes, der allerede findes og har MultiSelect-bitten sat i /Ff. For hvordan choice-felter og deres flagbitter oprettes i første omgang, se at tilføje ListBox og andre AcroForm-felter til en loadet PDF. For kommentar-markup, der går gennem XFDFs <annots>-træ, se XFDF-annotation-import og -eksport i HotPDF. Den fulde API-reference og trial-download ligger på HotPDF Delphi PDF-komponent-siden