losLab PDF Library čte a zapisuje opakující se datové uzly v dynamických formulářích XFA prostřednictvím cest XFA 3.3 SOM (Scripting Object Model): metody GetXFAFormFieldValue and SetXFAFormFieldValue akceptují kořeny $data, $record a !data, zástupné znaky výskytu [*], selektory potomků a zástupných znaků dětí, vlastnost parent, selektory tříd #dataGroup/#dataValue a jednoduché predikáty ve stylu FormCalc, takže program v Delphi může jedním voláním aktualizovat každý řádek faktury ve státním daňovém formuláři. Tento článek slouží jako referenční příručka pro adresování; informace o přenosu celých datových sad mezi soubory naleznete v doprovodném článku o výměně dat formulářů FDF, XFDF a XFA
Problém se objevuje v okamžiku, kdy formulář přestane být plochý. Přiznání k DPH nebo celní prohlášení vytvořené jako dynamický formulář XFA nemá dvanáct polí s názvy Total_Price_1 až Total_Price_12; má jeden dílčí formulář (subform) Detail deklarovaný jednou v šabloně DOM a vytvořený v tolika instancích, kolik data vyžadují. Hodnoty žijí ve druhém stromu, Data DOM, uvnitř balíčku datasets, kde každá položka řádku představuje opakovaný element <Detail> pod <Receipt>. Ploché názvy polí nedokážou adresovat „cenu na třetím řádku“ nebo „každý řádek nad 200“; to je přesně úkol, který kapitola Scripting Object Model specifikace Adobe XFA 3.3 přiděluje výrazům SOM, a je to syntaxe, kterou hovoří rozhraní API XFA pro hodnoty polí losLab PDF Library
Jak v Delphi adresovat datové uzly XFA?
Každá cesta v datových sadách začíná od kořene a losLab PDF Library přijímá standardní zápisy zaměnitelně: $data je zkrácená forma XFA 3.3 pro xfa.datasets.data, !data je zkrácená forma s kořenem v xfa.datasets a v balíčcích, které nepoužívají zpracování záznamů, se $record vyhodnocuje jako vnější datový záznam, první prvek pod uzlem data. Rozhraní API na straně šablony, jako je SetXFAFormFieldAccess, přijímají $template a xfa.template stejným způsobem. Segmenty mohou být odděleny tečkou nebo lomítkem ($data.Receipt.Tax nebo $data/Receipt/Tax), což je důležité, protože GetXFAFormFieldNames vyjmenovává pole jako cesty oddělené lomítkem, které se předávají přímo zpět do API pro hodnoty a šablony. Indexy výskytů jsou podle specifikace od nuly, takže Detail[0] je první řádek. Dvě pravidla pro uvozování (escaping) udržují adresovatelné i neobvyklé názvy: v cestě oddělené tečkami označuje \. doslovnou tečku (Line\.Item), zatímco cesty oddělené lomítky považují tečky za běžné znaky. Podniková data, která nesou vlastní předponu jmenného prostoru XML, se porovnávají podle lokálního názvu, takže <m:Receipt> stále odpovídá 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;
Jak programově vyplnit opakující se řádky ve formuláři XFA?
Zástupný znak výskytu [*] je nástroj pro hromadné operace. Tam, kde číselný index vybírá jednoho sourozence, [*] vybírá všechny sourozence se stejným názvem, takže $data.Receipt.Detail[*].Total_Price adresuje pole ceny na každé položce řádku najednou. Při čtení vrací GetXFAFormFieldValue hodnoty všech odpovídajících uzlů spojené oddělovačem |; při zápisu aktualizuje SetXFAFormFieldValue každý odpovídající uzel stejnou hodnotou a při úspěchu vrátí 1. Cesta, která neodpovídá ničemu, se přečte jako prázdný řetězec, což představuje snadný způsob, jak před zápisem ověřit, zda větev vůbec 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 potomků, dětských zástupných znaků a rodičů
Tři strukturální selektory pokrývají případy, kdy znáte název pole, ale ne jeho přesnou hloubku. Selektor potomků .. odpovídá v jakékoli hloubce pod aktuálním uzlem: $data..Total_Price najde první Total_Price kdekoli pod kořenem dat a zápis přes cestu potomka tento první odpovídající uzel aktualizuje. Všimněte si omezení: index výskytu po shodě s potomkem nepřechází přes sourozenecké větve, takže pokud se $data..Total_Price[0] vyhodnotí uvnitř prvního řádku Detail, $data..Total_Price[1] vrátí prázdnou hodnotu, místo aby přeskočil na další řádek; chcete-li všechny, použijte Detail[*]. Zástupný znak pro děti .* odpovídá každému přímému potomkovi a umožňuje zbývajícím segmentům filtrovat: $data.Receipt.*.Total_Price dosáhne na Total_Price uvnitř každé podřízené větve Receipt, aniž by zároveň vybral souhrnné pole se stejným názvem umístěné přímo na samotném Receipt. Nakonec vlastnost parent stoupá o jednu úroveň výše, což specifikace XFA 3.3 záměrně odděluje od ..: cesta $data.Receipt.Detail[1].parent.Tax začíná u druhé položky řádku, vystoupá k Receipt a skončí na jeho sourozenecké hodnotě Tax
Selektory tříd a predikáty: #dataGroup, #dataValue, .[expression]
Syntaxe #class adresuje uzly Data DOM podle třídy objektu namísto názvu, což je praktická cesta, když tagy XML obsahují znaky, které se obtížně zapisují jako názvy skriptů. losLab PDF Library mapuje #dataGroup na prvky, které mají podřízené prvky (elementy), a #dataValue na koncové prvky (listy) bez podřízených prvků, přičemž oba lze kombinovat s číselnými výskyty nebo [*]: $data.Receipt.#dataGroup[0].#dataValue[0] vybere první hodnotu uvnitř první skupiny pod Receipt. Selektor predikátu .[expression] filtruje sourozence se stejným názvem podle obsahu. Podporovány jsou jednoduché predikáty porovnání podřízené hodnoty s literálem pomocí relačních operátorů jako >, < a <=; pokud predikát odpovídá více řádkům, vrátí čtení hodnoty spojené symbolem | a zápis aktualizuje všechny shody
// 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]');
Které funkce XFA SOM nejsou podporovány?
Podpora SOM v losLab PDF Library je omezena na sady dat a cesty polí šablony, přičemž hranice jsou definovány explicitně a nikoli s nejlepším úsilím. Jejich znalost předem vám ušetří ladění cesty, která tiše vrací prázdný řetězec
- Predikáty nespouštějí libovolný FormCalc ani JavaScript: žádná volání funkcí (predikát contains(...) vrací prázdnou hodnotu), no compound boolean expressions, pouze jediné porovnání child op literal
- $record funguje pouze v balíčcích bez zpracování záznamů; stránkování záznamů dataWindow a rotace skupin záznamů nejsou implementovány
- Vyhodnocování probíhá přímo proti Data DOM a template DOM; relativní vyhodnocování Form DOM, tedy sloučený pohled, který prohlížeč sestavuje za běhu, se neprovádí
- Sémantika načítání atributů Data DOM se nepoužije; knihovna čte balíček datasets jako surové XML, takže cesty adresují elementy, nikoli atributy povýšené na uzly
Tato omezení se při hromadném vyplňování projevují málokdy, protože plnič adresuje data podle struktury, nikoli podle naskriptované logiky. Pokud na ně narazíte, únikovou cestou je úroveň balíčku: načtěte celé XML datasets, transformujte jej v Delphi a zapište zpět
Zápis celého balíčku pomocí SetXFAFromString
Metoda SetXFAFromString je oním přístupovým bodem na úrovni balíčku: instaluje kompletní řetězec XDP, šablonu a datové sady dohromady, přičemž v novém dokumentu vytvoří kontejner AcroForm, pokud ještě neexistuje, a přijímá balíčky kódované v UTF-16 se značkami pořadí bajtů (BOM) i UTF-8. Běžným produkčním postupem je uchovat úředně vydané XDP jako šablonu, načíst jej pomocí SetXFAFromString a před uložením provést volání SetXFAFormFieldValue adresovaná pomocí SOM pro proměnlivé hodnoty jednotlivých faktur. Vzhledem k tomu, že XFA je jedním ze dvou modelů formulářů, které může PDF obsahovat, klasická strana AcroForm má svůj vlastní příběh automatizace, popsaný v článku o interaktivních akcích formulářů a JavaScriptu
Zde uvedená rozhraní API XFA pro hodnoty polí, výčty a balíčky se dodávají v knihovně losLab PDF Library pro Delphi, C# a VB.NET; produktová stránka obsahuje kompletní referenční příručku pro práci s formuláři