HotPDF tar multi-select listeboks-verdier gjennom FDF og XFDF i rundtur ved å beholde feltverdien som en array fra ende til annen. Siden versjon 2.755.0 skriver ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF og ExportLoadedFormToXFDF hvert valgte alternativ som sin egen FDF-streng eller XFDF <value>-element, og de matchende importmetodene sjekker hver verdi mot feltalternativene og bygger /I-valgindeksene på nytt før de endrer noe som helst. Ingenting limes sammen til én streng underveis
Feilen dette fikser, er lett å reprodusere. Ta et bestillingsskjema med en multi-select listeboks med produktalternativer, la en bruker velge to av dem, eksporter skjemadataene for et backoffice-system, og importer så den redigerte filen tilbake i PDF-en. Før denne endringen kom listeboksen tilbake tom eller feil. Grunnen er at én av eksportverdiene inneholdt et linjeskift, og den gamle stien hadde flattet ut valgene til én enkelt linjeseparert streng. Å få flere valg ut av den strengen var aldri pålitelig, og med en eksportverdi som selv inneholder et linjeskift, kan det ikke virke i det hele tatt
Hvorfor ødelegger sammenslåing av multi-select verdier med linjeskift rundturen?
Å slå valgene sammen til én streng kaster bort grensene mellom verdiene, og en verdi kan inneholde skilletegnet, så ingen importør kan splitte strengen tilbake korrekt. ISO 32000-1 §12.7.4.4 tillater at /V-oppføringen til et valgfelt er enten en enkelt tekststreng eller en array av tekststrenger, og en listeboks med MultiSelect-flagget (bit 22 av /Ff) bruker arrayformen så snart mer enn ett alternativ er valgt. Samme avsnitt definerer /I som en array av nullbaserte alternatindeksene i stigende rekkefølge, som visningsprogrammer bruker for å skille to alternativer som tilfeldigvis deler en eksportverdi. I HotPDF leser skalar-getteren GetFormFieldValue bare strengformen, så å kjøre en array gjennom den degraderte eksporten til en tom streng, og den gamle XFDF-importen slo gjentatte <value>-elementer sammen med LF. Tenk deg et alternativ eksportert som Deep, linjemating, Blue: etter sammenslåing kunne Deep\nBlue\nRed være to valg eller tre, og filen gir ingen måte å vite hvilket på. Fiksen var å slutte å bruke en skalar midt i rundturen helt
Hva inneholder de eksporterte FDF- og XFDF-filene?
HotPDF skriver en multi-select verdi som et typet array i FDF og som ett <value>-element per valg i XFDF, så grensene forblir synlige på disk. I FDF beholder hvert element stavemåten det hadde i kilde-PDF-en: heksadesimale strenger går ut som heks, og literale strenger escapes av én enkelt hjelper som gjør CR og LF om til \r og \n. I XFDF bærer roten xml:space="preserve" slik ISO 19444-1 krever, noe som betyr at all whitespace inne i et tekst-element teller som data. HotPDF skriver derfor start-taggen, den escapete teksten og sluttaggen til hver <value> i ett stykke, holder innrykk utenfor elementet, og enkode CR, LF og TAB som tegnreferanser slik at en XML-parser som anvender linjeslutt-normalisering, ikke kan endre de opprinnelige bytene
<!-- FDF: ett typet array per felt -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>
<!-- XFDF: ett <value> per 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-kanttilfeller er verdt å kjenne før du skriver kallkoden. For det første bygger ExportLoadedFormToFDF den komplette FDF-bodyen i minnet før den oppretter målfilen (fikset i 2.755.1), så en verdi som ikke kan eksporteres, som en array som holder noe annet enn strenger, reiser uten å avkorte en eksisterende fil. For det andre er et tomt valg på en listeboks som også tilbyr en tom-streng eksportverdi tvetydig i XFDF, fordi <value/> kunne bety at ingenting er valgt eller at det tomme alternativet er valgt. ExportLoadedFormToXFDF reiser i det tilfellet i stedet for å gjette, og den reiser før målfilen åpnes. FDF har ingen slik tvetydighet, siden /V [] og /V [()] er distinkte. Begge FDF-eksportører hopper også over kun-widget-terminaler som ikke har noe /T-navn, i tråd med XFDF-eksportøren, fordi ingen importør noen gang kunne matche de oppføringene tilbake 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 listebokser skrives som /V [(...) (...)]
Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
try
Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
except
on E: Exception do
// Tomt valg pluss et tomt eksportalternativ: XFDF kan ikke skille
// dem, og den eksisterende .xfdf-filen lar være urørt
ShowMessage('XFDF export refused: ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
end;
Hvordan validerer HotPDF en multi-select verdi ved import?
HotPDF godtar en importert array bare når målet er et valgfelt med MultiSelect-flagget satt og hver verdi i arrayen matcher en eksportverdi i feltets /Opt-array. Hvert alternativhull kan brukes én gang, så en liste med to alternativer som deler eksportverdien b, godtar [<62> <62>] som to distinkte valg og avviser en tredje b. Det gjenoppbygde /I-en følger /Opt-rekkefølgen og ikke rekkefølgen på de innkommende verdiene, slik §12.7.4.4 krever stigende indekser. HotPDF bygger den nye /V-en og /I-en som frakoblede objekter og tildeler dem først etter at hver verdi har bestått validering, så en avvist verdi etterlater aldri halvparten av en array eller foreldede indekser. Kopien skrives til feltet som importeres snarere enn inn i en delt forfedre-array, heksstavemåter som ankommer fra FDF forblir heks gjennom lagringen, og felt hvis beregninger avhenger av listeboksen, merkes for omberegning. Hvis du bare trenger å sette én enkelt verdi, går å sette én skjemafeltverdi i en lastet PDF gjennom den skalare stien, som av design ikke håndterer flere valg
Noen andre verktøy skriver rene ASCII-eksportverdier som heksstrenger uten byteordensmarkør, for eksempel <416272>, og eksporterer så XFDF ved å skrive de heksadesimale sifrene ut som tekst. En streng literal sammenligning på veien tilbake feiler, og importen avbryter. Versjon 2.755.1 legger til én retry: når en verdi ikke matcher noe alternativ, dekoder HPDFHexSpellingText teksten som en heks-last og sammenligner resultatet igjen. Retryen gjelder bare input som ellers ville ha reist, så den endrer aldri en verdi som allerede matchet. Samme utgivelse fikk også skalar- og array-stiene til å bruke samme Unicode-dekoder, som forstår PDFDocEncoding, UTF-16 med vilkårlig byteordensmarkør og UTF-8. Før det kunne én logisk verdi matche på én sti og feile på den andre i dokumenter som blandet enkodninger
Hvorfor kan en gyldig FDF-fil fortsatt miste felt under parsing?
En FDF-skanner som ikke sporer heksadesimale strenger, kan klippe en feltordbok i to når en heksverdi slutter rett ved ordbokterminatoren. I << /T (region) /V <416273>>> lukker den første >-en heksstrengen, men en naiv skanner leser den sammen med neste > som slutten på ordboken og mister feltet stille. Filnivå FDF-importøren fulgte allerede med på om den var inne i en heksstreng, og i 2.755.1 gjør array- og ordbokskannerne bak ImportLoadedInterchangeFromFDF det samme. Et annet problem gjelder indirekte referanser. En FDF-fil er et lite PDF-syntaks-dokument med sin egen objektnummerering (ISO 32000-1 §12.7.7), så en verdi som /V [11 0 R] viser til objekt 11 i FDF-filen, ikke objekt 11 i PDF-en du fyller ut. Den forenklede FDF-parseren i HotPDF løser ikke referanser inne i filen, så den avviser en slik array i stedet for å lese hva objekt 11 tilfeldigvis er i måldokumentet
Fil-, strøm- og XFDF-importer rapporterer feil forskjellig
De tre importrutene validerer på samme måte, men rapporterer feil forskjellig, og det er verdt å velge én med hensikt. ImportLoadedFormFromFDF hopper over ethvert felt som feiler validering og returnerer antallet felt den faktisk anvendte, så et antall lavere enn forventet er eneste tegn på et problem. ImportLoadedInterchangeFromFDF og ImportLoadedFormFromXFDF reiser ved det første avviste feltet. Hvert felt committes på egen hånd, så felt behandlet før unntaket beholder sine nye verdier. Behandle ingen av disse som en transaksjon over hele utvekslingsfilen: hvis du trenger alt-eller-ingen-oppførsel, forkast det lastede dokumentet når et unntak oppstår i stedet for å lagre 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
// Bare felt; en verdi utenfor /Opt eller et ikke-multi-select mål reiser
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;
Å utvide XFDF-callbackene uten å knekke eksisterende kallere
Array-støtten i XFDF-uniten på lavere nivå bor i en egen post, THPDFXFDFArrayAccess, og i nye overloads av HPDFXFDFExportFields og HPDFXFDFImportFields, ikke i ekstra felt lagt til på slutten av den eksisterende THPDFXFDFAccess-posten. Grunnen er binær kompatibilitet. Kode som fyller THPDFXFDFAccess som en lokal variabel, setter ofte bare hullene den kjenner og tømmer aldri resten, så en ny funksjonspeker lagt til den posten ville inneholde stakk-søppel, og biblioteket ville ta den for en ekte callback. Med en egen post beholder gamle kallere den gamle layouten og de gamle overloadene, og de overloadene sender et all-nil array-record internt. Den opprinnelige skalare import-overloaden slår fortsatt sammen gjentatte verdier med LF for kompatibilitet, og bare den array-bevisste overloaden holder dem fra hverandre. Når du binder din egen datalager, start fra Default(THPDFXFDFArrayAccess). Returner True fra GetFormFieldValueArray for ethvert liste-verdi felt, inkludert ett uten noe valgt, og False for å falle tilbake til den skalare callbacken
uses HPDFXFDF;
// Ren funksjonspeker, ikke «of object»: Context bærer din egen lagring
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 skalar-bindinger
ArrayAccess := Default(THPDFXFDFArrayAccess); // hvert ubrukt hull er nil
ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;
Multi-select-utveksling virker på listebokser som allerede finnes og har MultiSelect-biten satt i /Ff. For hvordan valgfelt og deres flaggbiter opprettes i utgangspunktet, se å legge til ListBox og andre AcroForm-felt i en lastet PDF. For kommentarmarkup som går gjennom <annots>-treet i XFDF, se XFDF-annotering import og eksport i HotPDF. Den fullstendige API-referansen og prøveversjonsnedlastingen ligger på HotPDF Delphi PDF-komponent-siden