Odborný článok

Cesty XFA SOM v Delphi: Dynamické údaje formulárov

Knižnica losLab PDF Library číta a zapisuje opakujúce sa dátové uzly v dynamických formulároch XFA prostredníctvom ciest (path expressions) XFA 3.3 SOM: metódy GetXFAFormFieldValue a SetXFAFormFieldValue akceptujú korene $data, $record a !data, zástupné znaky výskytu [*], selektory potomkov (descendant) a zástupné znaky detí (child-wildcard), vlastnosť parent, triedne selektory #dataGroup/#dataValue a jednoduché predikáty v štýle FormCalc, takže program v Delphi dokáže aktualizovať každý riadok faktúry vo vládnom daňovom formulári jediným volaním. Tento článok slúži ako referenčná príručka adresovania; informácie o prenose celých súborov dát medzi súbormi nájdete v sprievodnom článku o výmene dát formulárov FDF, XFDF a XFA

Problém sa objaví vo chvíli, keď formulár prestane byť plochý. Daňové priznanie k DPH alebo colné vyhlásenie postavené ako dynamický formulár XFA nemá dvanásť polí s názvami Total_Price_1Total_Price_12; má jeden subformulár Detail, ktorý je deklarovaný raz v šablóne DOM a inštancovaný toľkokrát, koľko si vyžadujú dáta. Hodnoty žijú v druhom strome, Data DOM vo vnútri balíka datasets, kde každá položka riadku je opakovaným prvkom <Detail> pod uzlom <Receipt>. Ploché názvy polí nedokážu adresovať „cenu na treťom riadku“ alebo „každý riadok s hodnotou nad 200“; to je presne úloha, ktorú kapitola Scripting Object Model špecifikácie Adobe XFA 3.3 zveruje výrazom SOM, a je to syntax, ktorú používajú API pre hodnoty polí XFA v knižnici losLab PDF Library

Ako adresovať dátové uzly XFA z Delphi?

Každá cesta v datasete začína od koreňa a losLab PDF Library prijíma štandardné zápisy zameniteľne: $data je skrátená forma XFA 3.3 pre xfa.datasets.data, !data je skrátená forma s koreňom v xfa.datasets a v balíkoch, ktoré nepoužívajú spracovanie záznamov, sa $record vyhodnotí ako vonkajší dátový záznam, prvý prvok pod uzlom data. API na strane šablóny, ako napríklad SetXFAFormFieldAccess, prijímajú $template a xfa.template rovnakým spôsobom. Segmenty môžu byť oddelené bodkou alebo lomkou ($data.Receipt.Tax alebo $data/Receipt/Tax), čo je dôležité, pretože GetXFAFormFieldNames enumeruje polia ako cesty oddelené lomkou, ktoré sa dajú priamo použiť v API pre hodnoty a šablóny. Indexy výskytu sú podľa špecifikácie indexované od nuly, takže Detail[0] je prvý riadok. Dve pravidlá pre escaping umožňujú adresovať aj nezvyčajné názvy: v ceste oddelenej bodkou zápis \. označuje doslovnú bodku (Line\.Item), zatiaľ čo cesty oddelené lomkou považujú bodky za bežné znaky. Obchodné dáta, ktoré nesú vlastný prefix XML menného priestoru (namespace prefix), sa porovnávajú podľa lokálneho názvu, takže <m:Receipt> stále reaguje na Receipt

var
  Lib: TPDFlib;
  Tax: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('vat-return.pdf', '');
    // Equivalent addresses for the same data node
    Tax := Lib.GetXFAFormFieldValue('Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('xfa.datasets.data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$record.Tax');
    // Slash form with a zero-based occurrence index
    Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
    Lib.SaveToFile('vat-return-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Ako programovo vyplniť opakujúce sa riadky vo formulári XFA?

Zástupný znak výskytu [*] je hromadný nástroj. Tam, kde číselný index vyberá jedného súrodenca, [*] vyberá všetkých súrodencov s rovnakým názvom, takže $data.Receipt.Detail[*].Total_Price adresuje pole ceny na každom riadku položky naraz. Pri čítaní metóda GetXFAFormFieldValue vracia hodnoty všetkých zodpovedajúcich uzlov spojené separátorom |; pri zápise metóda SetXFAFormFieldValue aktualizuje každý zodpovedajúci uzol rovnakou hodnotou a v prípade úspechu vráti 1. Cesta, ktorá sa nezhoduje s ničím, sa načíta ako prázdny reťazec, čo je jednoduchý spôsob, ako pred zápisom overiť, či daná vetva existuje

var
  Lib: TPDFlib;
  Prices: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('invoice.pdf', '');
    // All Detail rows at once: '250.00|60.00'
    Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
    // Reset one column across every repeated row in a single call
    Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
    Lib.SaveToFile('invoice-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Selektory potomkov, detských zástupných znakov a rodičov

Tri štrukturálne selektory pokrývajú prípady, keď poznáte názov poľa, ale nie jeho presnú hĺbku. Selektor potomkov (descendant selector) .. hľadá v akejkoľvek hĺbke pod aktuálnym uzlom: $data..Total_Price nájde prvý Total_Price kdekoľvek pod koreňom data a zápis cez cestu potomka aktualizuje tento prvý nájdený uzol. Pozor na obmedzenie: index výskytu po zhode potomka neprechádza cez vetvy súrodencov, takže ak sa $data..Total_Price[0] vyhodnotí vo vnútri prvého riadku Detail, cesta $data..Total_Price[1] vráti prázdnu hodnotu namiesto toho, aby skočila na ďalší riadok; ak chcete všetky, použite Detail[*]. Detský zástupný znak .* sa zhoduje s každým priamym dieťaťom a umožňuje filtrovanie zostávajúcich segmentov: $data.Receipt.*.Total_Price dosiahne Total_Price vo vnútri každej detskej vetvy Receipt bez toho, aby vybral súhrnné pole s rovnakým názvom nachádzajúce sa priamo na samotnom Receipt. Nakoniec vlastnosť parent stúpa o úroveň vyššie, čo XFA 3.3 zámerne oddeľuje od ..: cesta $data.Receipt.Detail[1].parent.Tax začína na druhej položke riadku, stúpa k Receipt a končí na jeho súrodeneckej hodnote Tax

Triedne selektory a predikáty: #dataGroup, #dataValue, .[výraz]

Syntax #trieda adresuje uzly Data DOM podľa triedy objektu a nie podľa názvu, čo je praktická cesta, ak XML tagy obsahujú znaky, ktoré sa ťažko zapisujú ako názvy skriptov. losLab PDF Library mapuje #dataGroup na prvky, ktoré majú podelementy (deti), a #dataValue na koncové prvky (listy) bez podelementov, pričom oba sa kombinujú s číselnými výskytmi alebo výskytmi [*]: $data.Receipt.#dataGroup[0].#dataValue[0] vyberie prvú hodnotu v prvej skupine pod Receipt. Selektor predikátu .[výraz] filtruje súrodencov s rovnakým názvom podľa obsahu. Podporované predikáty sú jednoduché porovnania hodnoty dieťaťa s literálom pomocou relačných operátorov ako >, < a <=; ak predikát vyhovuje viacerým riadkom, čítania sa vracajú spojené znakom | a zápisy aktualizujú každý vyhovujúci riadok

// Flag every line whose Total_Price exceeds 200
Lib.SetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');

// Descriptions of all low-value lines, '|'-joined when several match
Desc := Lib.GetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price <= 200].Description');

// Class-based addressing when tag names resist script spelling
Total := Lib.GetXFAFormFieldValue(
  '$data.Receipt.#dataGroup[0].#dataValue[0]');

Ktoré funkcie XFA SOM nie sú podporované?

Podpora SOM v losLab PDF Library je obmedzená na datasety a cesty šablón polí, pričom tieto hranice sú pevne dané a nie len orientačné. Ich znalosť vopred vám ušetrí čas pri ladení cesty, ktorá potichu vracia prázdny reťazec

  • Predikáty nevykonávajú ľubovoľný FormCalc ani JavaScript: žiadne volania funkcií (predikát s contains(...) vráti prázdnu hodnotu), žiadne zložité logické výrazy, iba jedno porovnanie v tvare: dieťa operator literál
  • Koreň $record funguje len v balíkoch bez spracovania záznamov; stránkovanie záznamov dataWindow a rotácia skupín záznamov nie sú implementované
  • Vyhodnocovanie prebieha priamo voči Data DOM a template DOM; relatívne vyhodnocovanie Form DOM, teda zlúčené zobrazenie, ktoré prehliadač zostavuje za behu, sa nevykonáva
  • Sémantika načítania atribútov Data DOM sa neaplikuje; knižnica číta balík datasets ako čisté XML, takže cesty adresujú elementy, nie atribúty povýšené na uzly

Tieto obmedzenia málokedy prekážajú pri hromadnom vypĺňaní formulárov, pretože vypĺňací program adresuje dáta podľa štruktúry a nie podľa skriptovanej logiky. Ak sa tak stane, únikovou cestou je úroveň celého balíka: načítajte celý XML datasets, transformujte ho v Delphi a zapíšte ho späť

Zápis celého balíka pomocou SetXFAFromString

Metóda SetXFAFromString je týmto vstupným bodom na úrovni balíka: nainštaluje kompletný reťazec XDP, šablónu a datasets spoločne, pričom vytvorí kontajner AcroForm v novom dokumente, ak ešte neexistuje, a prijíma balíky kódované v UTF-16 so značkami poradia bajtov (BOM) ako aj v UTF-8. Bežným produkčným modelom je ponechať si agentúrou vydaný súbor XDP ako fixný základ, načítať ho pomocou SetXFAFromString, potom spustiť volania SetXFAFormFieldValue s adresovaním SOM pre premenlivé hodnoty faktúry a nakoniec súbor uložiť. Keďže XFA is one of two form models a PDF can carry, the classic AcroForm side has its own automation story, covered in the article on interactive form actions and JavaScript

API pre hodnoty polí XFA, enumeráciu a prácu s balíkmi zobrazené v tomto článku sa dodávajú v knižnici losLab PDF Library pre Delphi, C# a VB.NET; produktová stránka obsahuje kompletnú referenčnú príručku spracovania formulárov