Odborný článok

Multi-select polia PDF v round tripe FDF a XFDF (Delphi)

HotPDF prevezie hodnoty multi-select list boxov cez FDF a XFDF tak, že hodnotu poľa drží ako pole od začiatku do konca. Od verzie 2.755.0 ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF a ExportLoadedFormToXFDF zapisujú každú vybranú voľbu ako vlastný FDF reťazec alebo XFDF element <value> a zodpovedajúce importné metódy kontrolujú každú hodnotu proti voľbám poľa a znovu stavajú výberové indexy /I, skôr než niečo zmenia. Nič sa cestou nezlepí do jedného reťazca

Zlyhanie, ktoré to opravuje, sa dá ľahko zreprodukovať. Vezmite objednávkový formulár s multi-select list boxom produktových volieb, nechajte používateľa vybrať dve z nich, exportujte dáta formulára pre back-office systém a potom importujte upravený súbor späť do PDF. Pred touto zmenou sa list box vrátil prázdny alebo zlý. Dôvodom bolo, že jedna z exportných hodnôt obsahovala koniec riadku a stará cesta spláštila výbery do jedného reťazca oddeľovaného riadkami. Dostať viacnásobné výbery z toho reťazca späť bolo vždy nespoľahlivé a s exportnou hodnotou, ktorá sama obsahuje koniec riadku, to nemôže fungovať vôbec

Prečo spájanie hodnôt multi-select koncami riadkov rozbije round trip?

Spojenie výberov do jedného reťazca vyhodí hranice medzi hodnotami a hodnota môže obsahovať oddeľovač, takže žiadny importer nedokáže reťazec správne rozdeliť späť. ISO 32000-1 §12.7.4.4 dovoľuje, aby bola položka /V choice poľa buď jediný textový reťazec, alebo pole textových reťazcov, a list box s flagom MultiSelect (bit 22 /Ff) používa poľovú formu, akonáhle sa vyberie viac než jedna voľba. Tá istá sekcia definuje /I ako pole indexov volieb od nuly vo vzostupnom poradí, čo prehliadače používajú na odlíšenie dvoch volieb, ktoré náhodou zdieľajú exportnú hodnotu. V HotPDF skalárny getter GetFormFieldValue číta len reťazcovú formu, takže pole ňou prepnuté znehodnotilo export na prázdny reťazec a starý XFDF import spájal opakované elementy <value> s LF. Predstavte si voľbu exportovanú ako Deep, koniec riadku, Blue: po spojení môže byť Deep\nBlue\nRed dva výbery alebo tri a súbor nedáva spôsob, ako zistiť ktoré. Opravou bolo prestať používať skalár v strede round tripu úplne

Starý round trip multi-select v HotPDF, keď dve vybrané voľby list boxu, jedna obsahujúca vložený koniec riadku, splášti skalárna cesta GetFormFieldValue do jediného reťazca Deep, koniec riadku, Blue, koniec riadku, Red, ktorý si downstream čítače dokážu prečítať buď ako dva výbery, alebo ako tri
Spojenie hodnôt multi-select do jedného reťazca ničí hranice hodnôt a exportná hodnota, ktorá sama obsahuje koniec riadku, robí spláštenú formu nejednoznačnou

Čo obsahujú exportované súbory FDF a XFDF?

HotPDF zapisuje hodnotu multi-select ako typované pole vo FDF a ako jeden element <value> na výber v XFDF, takže hranice ostávajú na disku viditeľné. Vo FDF si každá položka drží zapísanie, ktoré mala v zdrojovom PDF: hexadecimálne reťazce idú von ako hex a literal stringy escapuje jediný helper, ktorý mení CR a LF na \r a \n. V XFDF nesie koreň xml:space="preserve", ako vyžaduje ISO 19444-1, čo znamená, že akékoľvek biele miesta vnútri textového elementu sa počítajú ako dáta. HotPDF preto zapisuje otváraciu značku, escapovaný text a uzatváraciu značku každého <value> v jednom kuse, drží odsadenie mimo elementu a kóduje CR, LF a TAB ako character referencie, takže XML parser aplikujúci normalizáciu koncov riadkov nemôže zmeniť pôvodné bajty

Exportné tvary, ktoré HotPDF zapisuje pre multi-select list box od 2.755.0: FDF nesie jedno typované pole na field s /V [(Deep koniec riadku Blue) (Red)] a hex hodnotu regiónu, kým XFDF nesie jeden element value na výber pod xml:space preserve, takže biele miesta sa počítajú ako dáta
Hranice ostávajú na disku viditeľné: FDF drží každý výber ako vlastnú položku poľa a XFDF zapisuje každý v osobitnom elemente value, takže žiadny importer nemusí hádať
<!-- FDF: jedno typované pole na field -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>

<!-- XFDF: jeden <value> na výber -->
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
  <fields>
    <field name="options">
      <value>Deep&#xA;Blue</value>
      <value>Red</value>
    </field>
  </fields>
</xfdf>

Dva okrajové prípady exportu stoja za poznanie, skôr než napíšete volajúci kód. Po prvé, ExportLoadedFormToFDF stavia celé telo FDF v pamäti skôr, než vytvorí cieľový súbor (opravené v 2.755.1), takže hodnota, ktorá sa nedá exportovať, ako pole držiace niečo iné než reťazce, vyhodí výnimku bez skrátenia existujúceho súboru. Po druhé, prázdny výber na list boxe, ktorý zároveň ponúka exportnú hodnotu prázdneho reťazca, je v XFDF nejednoznačný, lebo <value/> môže znamenať, že nič nie je vybrané, alebo že je vybraná prázdna voľba. ExportLoadedFormToXFDF v tom prípade vyhodí výnimku namiesto hádania a vyhodí ju skôr, než sa cieľový súbor otvorí. FDF takú nejednoznačnosť nemá, keďže /V [] a /V [()] sú odlišné. Oba FDF exportéry tiež preskočia widget-only koncové body bez mena /T, súdiac s XFDF exportérom, lebo žiadny importer by nikdy nepriradil tie položky späť k poľu

var
  Pdf: THotPDF;
  Written: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
    begin
      // Multi-select list boxy sa zapisujú ako /V [(...) (...)]
      Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
      try
        Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
      except
        on E: Exception do
          // Prázdny výber plus prázdna exportná voľba: XFDF ich nedokáže odlíšiť
          // a existujúci .xfdf súbor zostane nedotknutý
          ShowMessage('XFDF export refused: ' + E.Message);
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Ako HotPDF validuje hodnotu multi-select pri importe?

HotPDF prijme importované pole len vtedy, keď cieľ je choice pole s nastaveným flagom MultiSelect a každá hodnota v poli sedí na exportnú hodnotu v poli /Opt daného poľa. Každý slot voľby sa môže použiť raz, takže zoznam s dvomi voľbami zdieľajúcimi exportnú hodnotu b prijme [<62> <62>] ako dva odlišné výbery a zamietne tretie b. Znovu postavené /I nasleduje poradie /Opt, nie poradie prichádzajúcich hodnôt, keďže §12.7.4.4 vyžaduje vzostupné indexy. HotPDF staví nové /V a /I ako oddelené objekty a priradí ich až po tom, ako prešla validáciu každá hodnota, takže zamietnutá hodnota nikdy nenechá polovicu poľa alebo zradené indexy. Kópia sa zapisuje do poľa, ktoré sa importuje, nie do zdieľaného predka, hex zapísania prichádzajúce z FDF ostávajú hex cez ukladanie a polia, ktorých výpočty závisia od list boxu, sa označia na prepočet. Ak potrebujete nastaviť len jednu hodnotu, nastavenie jednej hodnoty poľa formulára v načítanom PDF ide cez skalárnu cestu, ktorá zámierne nespracúva viacnásobné výbery

Validácia importu pre hodnoty multi-select v HotPDF: cieľ musí byť choice pole s nastaveným MultiSelect v /Ff, každá prichádzajúca hodnota musí sedieť na exportnú hodnotu /Opt s každým slotom použitým raz, /I sa znovu stavia vzostupne v poradí /Opt a oddelené /V a /I sa priradia až po tom, ako prešli všetky hodnoty
Každá prichádzajúca hodnota sa skontroluje proti voľbám poľa, skôr než sa čokoľvek zapíše, takže zamietnutá hodnota nikdy nenechá polovicu poľa alebo zradené výberové indexy

Niektoré iné nástroje zapisujú obyčajné ASCII exportné hodnoty ako hex reťazce bez byte order mark, napríklad <416272>, a potom exportujú XFDF tým, že tie hex číslice vypíšu ako text. Prísne doslovné porovnanie na ceste späť zlyhá a import sa preruší. Verzia 2.755.1 pridáva jedno opakovanie: keď hodnota nesedí na žiadnu voľbu, HPDFHexSpellingText dekóduje text ako hex payload a výsledok porovná znovu. Opakovanie sa týka len vstupu, ktorý by inak vyhodil výnimku, takže nikdy nemení hodnotu, ktorá už sedela. Rovnaké vydanie tiež dalo skalárnej aj poľovej ceste ten istý Unicode dekodér, ktorý rozumie PDFDocEncoding, UTF-16 s ľubovoľným byte order mark a UTF-8. Predtým mohla jedna logická hodnota sedieť na jednej ceste a zlyhať na druhej v dokumentoch miešajúcich kódovania

Prečo môže platný súbor FDF stratiť polia počas parsovania?

FDF skener, ktorý nesleduje hexadecimálne reťazce, môže prerezať slovník poľa na polovicu, keď hex hodnota končí tesne pri terminátore slovníka. V << /T (region) /V <416273>>> prvé > zatvára hex reťazec, ale naivný skener ho prečíta spolu s ďalším > ako koniec slovníka a potichu zahodí pole. Importér FDF na úrovni súboru už sledoval, či je vnútri hex reťazca, a v 2.755.1 robia to isté skenery poľa a slovníka za ImportLoadedInterchangeFromFDF. Druhá vec sa týka nepriamych referencií. Súbor FDF je malý dokument v PDF syntaxi s vlastným číslovaním objektov (ISO 32000-1 §12.7.7), takže hodnota ako /V [11 0 R] odkazuje na objekt 11 súboru FDF, nie na objekt 11 PDF, ktoré vypĺňate. Zjednodušený FDF parser v HotPDF nerieši referencie vnútri súboru, takže také pole zamietne namiesto čítania čohokoľvek, čo je objekt 11 náhodou v cieľovom dokumente

Importy súboru, streamu a XFDF hlásia chyby rozdielne

Tri importné cesty validujú rovnako, ale hlásia zlyhania rozdielne a stojí za to si vybrať jednu zámerne. ImportLoadedFormFromFDF preskočí každé pole, ktoré neprejde validáciou, a vráti počet polí, ktoré naozaj aplikovala, takže nižší než očakávaný počet je jediným znakom problému. ImportLoadedInterchangeFromFDF a ImportLoadedFormFromXFDF vyhodí výnimku pri prvom zamietnutom poli. Každé pole sa commitne svojsky, takže polia spracované pred výnimkou si držia nové hodnoty. Nespoliehajte sa na žiadnu z nich ako na transakciu cez celý výmenový súbor: ak potrebujete správanie všetko-alebo-nič, zahoďte načítaný dokument, keď nastane výnimka, namiesto jeho uloženia

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
      // Len polia; hodnota mimo /Opt alebo necieľ multi-select vyhodí výnimku
      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;

Rozširovanie XFDF callbackov bez rozbíjania existujúcich volajúcich

Podpora polí v nižšej XFDF jednotke býva v osobitnom zázname THPDFXFDFArrayAccess a v nových overloadoch HPDFXFDFExportFields a HPDFXFDFImportFields, nie v ďalších poliach pridaných na koniec existujúceho záznamu THPDFXFDFAccess. Dôvodom je binárna kompatibilita. Kód, ktorý plní THPDFXFDFAccess ako lokálnu premennú, často nastaví len sloty, ktoré pozná, a zvyšok nikdy nevynuluje, takže nový funkčný pointer pridaný do toho záznamu by obsahoval odpad zo zásobníka a knižnica by ho brala ako reálny callback. S osobitným záznamom si starí volajúci nechajú staré rozloženie a staré overloady a tie overloady podávajú interne záznam poľa všetko-nil. Pôvodný skalárny importný overload stále spája opakované hodnoty s LF kvôli kompatibilite a len overload znaly pole ich drží rozdelené. Keď viažete vlastné dátové úložisko, začnite od Default(THPDFXFDFArrayAccess). Vracajte True z GetFormFieldValueArray pre každé pole s hodnotou zoznamu, vrátane takéhoto s ničím nevybraným, a False pre spadnutie na skalárny callback

uses HPDFXFDF;

// Holý pointer na funkciu, nie „of object“: Context nesie vaše vlastné úložisko
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);             // vaše existujúce skalárne viazania
  ArrayAccess := Default(THPDFXFDFArrayAccess); // každý nepoužitý slot je nil
  ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
  HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;

Multi-select výmena funguje na list boxoch, ktoré už existujú a majú bit MultiSelect nastavený v /Ff. Ako sa choice polia a ich flagové bity vytvárajú na začiatku, popisuje článok o pridávaní ListBox a ďalších polí AcroForm do načítaného PDF. Komentárové značenie idúce cez strom <annots> XFDF popisuje import a export anotácií XFDF v HotPDF. Plná API referencia a skúšobné stiahnutie sú na stránke HotPDF Delphi PDF component