HotPDF skickar multi-select-listrutevärden på rundtur genom FDF och XFDF genom att hålla fältvärdet som en array från början till slut. Sedan version 2.755.0 skriver ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF och ExportLoadedFormToXFDF varje vald option som sin egen FDF-sträng eller sitt eget XFDF-<value>-element, och de matchande importmetoderna kontrollerar varje värde mot fältalternativen och bygger om /I-urvalsindexen innan de ändrar något. Ingenting limmas ihop till en sträng på vägen
Felet den här fixen åtgärdar är lätt att reproducera. Ta ett orderformulär med en multi-select-listruta av produktalternativ, låt en användare välja två av dem, exportera formulärdatan till ett backoffice-system och importera sedan den redigerade filen tillbaka till PDF:en. Före den här ändringen kom listrutan tillbaka tom eller fel. Orsaken är att ett av exportvärdena innehöll en radbrytning, och den gamla vägen hade plattat ut markeringarna till en enda radseparerad sträng. Att få ut flera markeringar ur den strängen var aldrig pålitligt, och med ett exportvärde som själv innehåller en radbrytning kan det inte fungera alls
Varför förstör sammanfogning av multi-select-värden med radbrytningar rundturen?
Att sammanfoga markeringarna till en sträng kastar bort gränserna mellan värden, och ett värde kan innehålla avgränsaren, så ingen importör kan dela strängen korrekt igen. ISO 32000-1 §12.7.4.4 tillåter att /V-posten i ett valfält antingen är en enskild textsträng eller en array av textsträngar, och en lista med MultiSelect-flaggan (bit 22 i /Ff) använder arrayformen så snart fler än en option är vald. Samma avsnitt definierar /I som en array av 0-baserade optionindex i stigande ordning, vilken viewers använder för att skilja åt två options som råkar dela ett exportvärde. I HotPDF läser skalärgettern GetFormFieldValue bara strängformen, så att köra en array genom den degraderade exporten till en tom sträng, och den gamla XFDF-importen sammanfogade upprepade <value>-element med LF. Föreställ dig en option exporterad som Deep, radmatning, Blue: efter sammanfogning kunde Deep\nBlue\nRed vara två markeringar eller tre, och filen ger inget sätt att veta vilken. Fixen var att sluta använda en skalär mitt i rundturen helt och hållet
Vad innehåller de exporterade FDF- och XFDF-filerna?
HotPDF skriver ett multi-select-värde som en typad array i FDF och som ett <value>-element per markering i XFDF, så gränserna förblir synliga på disk. I FDF behåller varje post stavningen den hade i käll-PDF:en: hexadecimala strängar går ut som hex, och literala strängar eskaperas av en enda hjälpfunktion som gör CR och LF till \r och \n. I XFDF bär roten xml:space="preserve" som ISO 19444-1 kräver, vilket betyder att alla whitespace inuti ett textelement räknas som data. HotPDF skriver därför starttaggen, den eskaperade texten och sluttaggen för varje <value> i ett stycke, håller indrag utanför elementet och kodar CR, LF och TAB som teckenreferenser så att en XML-parser som tillämpar radslutsnormalisering inte kan ändra de ursprungliga bytena
<!-- FDF: en typad array per fält -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>
<!-- XFDF: en <value> per markering -->
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="options">
<value>Deep
Blue</value>
<value>Red</value>
</field>
</fields>
</xfdf>
Två exportkantfall är värda att känna till innan du skriver anropskoden. För det första bygger ExportLoadedFormToFDF hela FDF-bodyn i minnet innan den skapar målfilen (fixat i 2.755.1), så ett värde som inte kan exporteras, som en array som håller något annat än strängar, kastar utan att avkorta en befintlig fil. För det andra är en tom markering på en lista som också erbjuder ett exportvärde i form av tom sträng tvetydig i XFDF, eftersom <value/> kan betyda att ingenting är valt eller att det tomma alternativet är valt. ExportLoadedFormToXFDF kastar i det fallet i stället för att gissa, och den kastar innan målfilen öppnas. FDF har ingen sådan tvetydighet, eftersom /V [] och /V [()] är olika. Båda FDF-exportörerna hoppar också över widget-only-terminaler utan /T-namn, i linje med XFDF-exportören, för att ingen importör någonsin kunde matcha de posterna tillbaka till ett fält
var
Pdf: THotPDF;
Written: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
begin
// MultiSelect-listrutor skrivs som /V [(...) (...)]
Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
try
Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
except
on E: Exception do
// Tom markering plus ett tomt exportalternativ: XFDF kan inte skilja
// dem åt, och den befintliga .xfdf-filen lämnas orörd
ShowMessage('XFDF export refused: ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
end;
Hur validerar HotPDF ett multi-select-värde vid import?
HotPDF accepterar en importerad array bara när målet är ett valfält med MultiSelect-flaggan satt och varje värde i arrayen matchar ett exportvärde i fältets /Opt-array. Varje optionsplats kan användas en gång, så en lista med två alternativ som delar exportvärdet b accepterar [<62> <62>] som två åtskilda markeringar och avvisar en tredje b. Den återbyggda /I följer /Opt-ordningen och inte ordningen på de inkommande värdena, eftersom §12.7.4.4 kräver stigande index. HotPDF bygger den nya /V och /I som frikopplade objekt och tilldelar dem först efter att varje värde passerat valideringen, så ett avvisat värde lämnar aldrig efter sig en halv array eller inaktuella index. Kopian skrivs till fältet som importeras i stället för till en delad anfadersarray, hexstavelser som anländer från FDF förblir hex genom sparandet, och fält vars beräkningar beror på listrutan markeras för omräkning. Om du bara behöver sätta ett enskilt värde går att sätta ett formulärfältvärde i en inläst PDF genom den skalära vägen, som av design inte hanterar flera markeringar
Vissa andra verktyg skriver rena ASCII-exportvärden som hex-strängar utan byteordningsmarkör, till exempel <416272>, och exporterar sedan XFDF genom att skriva ut de hexsiffrorna som text. En strikt literal jämförelse på vägen tillbaka fallerar, och importen avbryts. Version 2.755.1 lägger till ett omförsök: när ett värde inte matchar någon option avkodar HPDFHexSpellingText texten som en hex-payload och jämför resultatet igen. Omförsöket gäller bara indata som annars hade kastat, så det ändrar aldrig ett värde som redan matchade. Samma release lät också den skalära och arrayvägen använda samma Unicode-avkodare, som förstår PDFDocEncoding, UTF-16 med antingen byteordningsmarkör och UTF-8. Före dess kunde ett logiskt värde matcha på en väg och fallera på den andra i dokument som blandade kodningar
Varför kan en giltig FDF-fil ändå tappa fält under tolkningen?
En FDF-skanner som inte spårar hexadecimala strängar kan kapa en fältordbok itu när ett hexvärde slutar precis intill ordboksterminatorn. I << /T (region) /V <416273>>> stänger första > hex-strängen, men en naiv skanner läser den tillsammans med nästa > som slutet på ordboken och tappar fältet tyst. Filnivå-FDF-importören höll reda på huruvida den befann sig inuti en hex-sträng, och i 2.755.1 gör array- och ordboksskannerna bakom ImportLoadedInterchangeFromFDF detsamma. En andra fråga gäller indirekta referenser. En FDF-fil är ett litet PDF-syntaxdokument med egen objektnummerering (ISO 32000-1 §12.7.7), så ett värde som /V [11 0 R] syftar på objekt 11 i FDF-filen, inte objekt 11 i PDF:en du fyller. Den förenklade FDF-parsern i HotPDF löser inte upp referenser inuti filen, så den avvisar en sådan array i stället för att läsa vad objekt 11 råkar vara i måldokumentet
Fil-, ström- och XFDF-importer rapporterar fel på olika sätt
De tre importvägarna validerar på samma sätt men rapporterar fel olika, och det är värt att välja en med flit. ImportLoadedFormFromFDF hoppar över varje fält som inte klarar valideringen och returnerar antalet fält den faktiskt tillämpade, så ett lägre antal än förväntat är det enda tecknet på ett problem. ImportLoadedInterchangeFromFDF och ImportLoadedFormFromXFDF kastar vid det första avvisade fältet. Varje fält committas på egen hand, så fält som processats före undantaget behåller sina nya värden. Behandla ingen av dem som en transaktion över hela utbytesfilen: om du behöver all-eller-inget-beteende, kassera det inlästa dokumentet när ett undantag inträffar i stället för att spara 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
// Bara fält; ett värde utanför /Opt eller ett icke-multiselect-mål kastar
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;
Utöka XFDF-callbacks utan att bryta befintliga anropare
Arraystödet i den lägre XFDF-uniten bor i en separat post, THPDFXFDFArrayAccess, och i nya overloads av HPDFXFDFExportFields och HPDFXFDFImportFields, inte i extra fält tillagda i slutet av den befintliga THPDFXFDFAccess-posten. Orsaken är binär kompatibilitet. Kod som fyller THPDFXFDFAccess som en lokal variabel sätter ofta bara de platser den känner till och rensar aldrig resten, så en ny funktionspekare tillagd i den posten skulle innehålla stackskräp, och biblioteket skulle ta den för en riktig callback. Med en separat post behåller gamla anropare den gamla layouten och de gamla overloaderna, och de overloaderna skickar internt en hel-nil arraypost. Den ursprungliga skalära importoverloaden sammanfogar fortfarande upprepade värden med LF av kompatibilitetsskäl, och bara den arraymedvetna overloaden håller dem isär. När du binder din egen datalagring, börja från Default(THPDFXFDFArrayAccess). Returnera True från GetFormFieldValueArray för varje lista-värdefält, inklusive ett utan något valt, och False för att falla tillbaka till den skalära callbacken
uses HPDFXFDF;
// Vanlig funktionspekare, inte "of object": Context bär 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); // dina befintliga skalärbindningar
ArrayAccess := Default(THPDFXFDFArrayAccess); // varje oanvänd plats är nil
ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;
Multi-select-utbyte fungerar på listrutor som redan finns och har MultiSelect-biten satt i /Ff. För hur valfält och deras flaggbitar skapas från början, se att lägga till ListBox och andra AcroForm-fält i en inläst PDF. För kommentarsmarkering som går genom XFDF:ens <annots>-träd, se XFDF-annoteringsimport och -export i HotPDF. Den fullständiga API-referensen och testnedladdningen finns på sidan för HotPDF Delphi PDF-komponenten