HotPDF stuurt multi-select-listbox-waarden een round trip door FDF en XFDF door de veldwaarde van begin tot eind als array te houden. Sinds versie 2.755.0 schrijven ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF en ExportLoadedFormToXFDF elke geselecteerde optie als zijn eigen FDF-string of XFDF-<value>-element, en de bijpassende importmethoden toetsen elke waarde aan de veldopties en bouwen de /I-selectie-indices opnieuw op voordat ze iets veranderen. Onderweg wordt er niets aan elkaar geplakt
Het falen dat dit fixt is makkelijk te reproduceren. Neem een orderformulier met een multi-select-listbox met productopties, laat een gebruiker er twee aanvinken, exporteer de formulierdata voor een backoffice-systeem, en importeer het bewerkte bestand terug in de PDF. Vóór deze wijziging kwam de listbox leeg of fout terug. De reden was dat één van de exportwaarden een regeleinde bevatte, en het oude pad de selecties had afgeplat tot één regeleinde-gescheiden string. Meerdere selecties weer uit die string halen was nooit betrouwbaar, en met een exportwaarde die zelf een regeleinde bevat kan het helemaal niet werken
Waarom breekt het samenvoegen van multi-select-waarden met regeleinden de round trip?
De selecties in één string samenvoegen gooit de grenzen tussen waarden weg, en een waarde kan de scheider bevatten, dus geen enkele importer kan de string weer correct splitten. ISO 32000-1 §12.7.4.4 staat toe dat de /V-entry van een keuzeveld een enkele text string is of een array van text strings, en een listbox met de MultiSelect-flag (bit 22 van /Ff) gebruikt de arrayvorm zodra meer dan één optie is gekozen. Dezelfde sectie definieert /I als een array van 0-based optie-indices in oplopende volgorde, waarmee viewers twee opties die toevallig dezelfde exportwaarde delen uit elkaar houden. In HotPDF leest de scalaire getter GetFormFieldValue alleen de stringvorm, dus een array erdoorheen halen degradeerde de export tot een lege string, en de oude XFDF-import plakte herhaalde <value>-elementen aan elkaar met LF. Stel een optie geëxporteerd als Deep, line feed, Blue: na het samenvoegen kan Deep\nBlue\nRed twee selecties zijn of drie, en het bestand zegt op geen enkele manier welke. De fix was om middenin de round trip helemaal te stoppen met een scalar
Wat bevatten de geëxporteerde FDF- en XFDF-bestanden?
HotPDF schrijft een multi-select-waarde als getypeerde array in FDF en als één <value>-element per selectie in XFDF, zodat de grenzen op schijf zichtbaar blijven. In FDF houdt elk item de spelling die het in de bron-PDF had: hexadecimale strings gaan als hex naar buiten, en literal strings worden geëscapeerd door één helper die CR en LF omzet naar \r en \n. In XFDF draagt de root xml:space="preserve" zoals ISO 19444-1 vereist, wat betekent dat elke witruimte binnen een text-element als data telt. HotPDF schrijft daarom de starttag, de geëscapeerde tekst en de eindtag van elke <value> in één stuk, houdt inspringing buiten het element, en codeert CR, LF en TAB als character references zodat een XML-parser die line-ending-normalisatie toepast de originele bytes niet kan veranderen
<!-- FDF: één getypeerde array per veld -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>
<!-- XFDF: één <value> per selectie -->
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="options">
<value>Deep
Blue</value>
<value>Red</value>
</field>
</fields>
</xfdf>
Twee exportrandgevallen zijn de moeite van het weten waard voordat u de aanroepende code schrijft. Ten eerste bouwt ExportLoadedFormToFDF de complete FDF-body in het geheugen op voordat hij het doelbestand aanmaakt (gefixt in 2.755.1), dus een waarde die niet kan worden geëxporteerd, zoals een array met iets anders dan strings, gooit een exception zonder een bestaand bestand af te kappen. Ten tweede is een lege selectie op een listbox die ook een lege-string-exportwaarde aanbiedt dubbelzinnig in XFDF, want <value/> kan betekenen dat er niets is geselecteerd of dat de lege optie is geselecteerd. ExportLoadedFormToXFDF gooit in dat geval liever dan gokken, en hij gooit voordat het doelbestand wordt geopend. FDF heeft die dubbelzinnigheid niet, want /V [] en /V [()] zijn onderscheiden. Allebei de FDF-exporters slaan bovendien widget-only-terminals zonder /T-naam over, net als de XFDF-exporter, want geen enkele importer kan die entries ooit terugkoppelen naar een veld
var
Pdf: THotPDF;
Written: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
begin
// Multi-select-listboxen worden weggeschreven als /V [(...) (...)]
Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
try
Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
except
on E: Exception do
// Lege selectie plus een lege exportoptie: XFDF kan ze niet
// uit elkaar houden, en het bestaande .xfdf-bestand blijft onaangeroerd
ShowMessage('XFDF export refused: ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
end;
Hoe valideert HotPDF een multi-select-waarde bij import?
HotPDF accepteert een geïmporteerde array alleen wanneer het doel een keuzeveld is met de MultiSelect-flag gezet en elke waarde in de array matcht met een exportwaarde in de /Opt-array van het veld. Elke optieslot kan één keer worden gebruikt, dus een lijst met twee opties die de exportwaarde b delen accepteert [<62> <62>] als twee onderscheiden selecties en verwerpt een derde b. De herbouwde /I volgt de /Opt-volgorde en niet de volgorde van de inkomende waarden, want §12.7.4.4 eist oplopende indices. HotPDF bouwt de nieuwe /V en /I als losse objecten en wijst ze pas toe nadat elke waarde de validatie heeft doorstaan, dus een verworpen waarde laat nooit een halve array of verouderde indices achter. De kopie wordt weggeschreven naar het veld dat wordt geïmporteerd en niet in een gedeelde ancestor-array, hex-spellingen die uit FDF komen blijven hex door de save heen, en velden waarvan de berekeningen van de listbox afhangen worden gemarkeerd voor herberekening. Hoeft u maar één waarde te zetten, dan loopt één formulierveldwaarde zetten in een geladen PDF via het scalaire pad, dat van ontwerp geen meervoudige selecties aankan
Sommige andere tools schrijven kale ASCII-exportwaarden als hex-strings zonder byte order mark, bijvoorbeeld <416272>, en exporteren daarna XFDF door die hex-cijfers als tekst weg te schrijven. Een strenge literal-vergelijking op de terugweg faalt, en de import breekt af. Versie 2.755.1 voegt één retry toe: als een waarde met geen enkele optie matcht, decodeert HPDFHexSpellingText de tekst als hex-payload en vergelijkt het resultaat opnieuw. De retry geldt alleen voor invoer die anders een exception zou hebben opgeleverd, dus hij verandert nooit een waarde die al matchte. Dezelfde release liet ook het scalaire en het array-pad dezelfde Unicode-decoder gebruiken, die PDFDocEncoding, UTF-16 met welke byte order mark dan ook en UTF-8 begrijpt. Daarvoor kon één logische waarde op het ene pad matchen en op het andere falen in documenten die coderingen mixten
Waarom kan een geldig FDF-bestand toch velden verliezen tijdens het parsen?
Een FDF-scanner die hexadecimale strings niet bijhoudt kan een velddictionary doormidden kappen wanneer een hex-waarde precies naast de dictionary-terminator eindigt. In << /T (region) /V <416273>>> sluit de eerste > de hex-string af, maar een naïeve scanner leest haar samen met de volgende > als het einde van de dictionary en laat het veld geruisloos vallen. De FDF-importer op bestandsniveau hield al bij of hij binnen een hex-string zat, en in 2.755.1 doen de array- en dictionary-scanners achter ImportLoadedInterchangeFromFDF datzelfde. Een tweede kwestie betreft indirecte referenties. Een FDF-bestand is een klein PDF-syntaxdocument met zijn eigen objectnummering (ISO 32000-1 §12.7.7), dus een waarde zoals /V [11 0 R] verwijst naar object 11 van het FDF-bestand, niet naar object 11 van de PDF die u aan het vullen bent. De vereenvoudigde FDF-parser in HotPDF lost referenties binnen het bestand niet op, dus hij verwerpt zo'n array in plaats van te lezen wat object 11 toevallig in het doeldocument is
Bestands-, stream- en XFDF-imports melden fouten op verschillende manieren
De drie importroutes valideren op dezelfde manier maar melden falen verschillend, en het is de moeite waard er bewust één te kiezen. ImportLoadedFormFromFDF slaat elk veld dat de validatie niet haalt over en geeft het aantal velden terug dat hij wel heeft toegepast, dus een lager dan verwacht aantal is het enige teken van een probleem. ImportLoadedInterchangeFromFDF en ImportLoadedFormFromXFDF gooien bij het eerste verworpen veld. Elk veld wordt los vastgelegd, dus velden die vóór de exception zijn verwerkt houden hun nieuwe waarden. Beschouw geen van alle als een transactie over het hele uitwisselingsbestand: heeft u alles-of-niets-gedrag nodig, gooi dan het geladen document weg wanneer een exception optreedt in plaats van het op te slaan
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
// Alleen velden; een waarde buiten /Opt of een niet-multi-select-doel gooit
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;
De XFDF-callbacks uitbreiden zonder bestaande aanroepers te breken
De array-ondersteuning in de XFDF-unit op lager niveau woont in een apart record, THPDFXFDFArrayAccess, en in nieuwe overloads van HPDFXFDFExportFields en HPDFXFDFImportFields, niet in extra velden achteraan het bestaande THPDFXFDFAccess-record. De reden is binaire compatibiliteit. Code die THPDFXFDFAccess als lokale variabele vult zet vaak alleen de slots die hij kent en wist de rest nooit, dus een nieuwe functiepointer in dat record zou stackrommel bevatten, en de library zou hem voor een echte callback aanzien. Met een apart record houden oude aanroepers de oude layout en de oude overloads, en die overloads geven intern een array-record van alleen nil's door. De originele scalaire import-overload plakt herhaalde waarden voor compatibiliteit nog steeds met LF aan elkaar, en alleen de array-aware overload houdt ze uit elkaar. Bindt u uw eigen data store, begin dan bij Default(THPDFXFDFArrayAccess). Geef True terug uit GetFormFieldValueArray voor elk veld met lijstwaarde, ook eentje waar niets is geselecteerd, en False om terug te vallen op de scalaire callback
uses HPDFXFDF;
// Kale functiepointer, niet "of object": Context draagt uw eigen 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); // uw bestaande scalaire bindingen
ArrayAccess := Default(THPDFXFDFArrayAccess); // elke ongebruikte slot is nil
ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;
Multi-select-uitwisseling werkt op listboxen die al bestaan en de MultiSelect-bit gezet hebben in /Ff. Hoe keuzevelden en hun flag-bits in eerste instantie worden aangemaakt leest u in ListBox- en andere AcroForm-velden toevoegen aan een geladen PDF. Voor commentaarmarkup die door de <annots>-boom van XFDF gaat, zie XFDF-annotatie-import en -export in HotPDF. De volledige API-referentie en de trial-download staan op de HotPDF Delphi PDF component-pagina