Technical Article

XFA SOM Path Expressions in Delphi: Dynamic Form Data

losLab PDF Library čita i piše ponavljajuće čvorove podataka u dinamičkim XFA obrascima putem XFA 3.3 SOM izraza putanje: GetXFAFormFieldValue i SetXFAFormFieldValue prihvaćaju korijene $data, $record i !data, zamjenske znakove za pojavljivanja [*], selektore potomaka i zamjenske znakove za djecu, svojstvo parent, selektore klasa #dataGroup/#dataValue te jednostavne predikate u FormCalc stilu, tako da Delphi program može ažurirati svaku stavku računa u državnom poreznom obrascu jednim pozivom. Ovaj je članak referenca za adresiranje; za premještanje cijelih skupova podataka između datoteka pogledajte prateći članak o razmjeni podataka FDF, XFDF i XFA obrazaca

Problem se javlja onog trenutka kada obrazac prestane biti ravan (flat). Prijava PDV-a ili carinska deklaracija izgrađena kao dinamički XFA obrazac nema dvanaest polja nazvanih od Total_Price_1 do Total_Price_12; it ima jedan podobrazac Detail koji je jednom deklariran u DOM-u predloška i instanciran onoliko puta koliko podaci to zahtijevaju. Vrijednosti žive u drugom stablu, Data DOM-u unutar paketa skupova podataka (datasets), gdje je svaka stavka ponovljeni element <Detail> pod elementom <Receipt>. Jednostavni nazivi polja ne mogu adresirati 'cijenu u trećem retku' ili 'svaki redak iznad 200'; to je točno zadatak koji poglavlje Scripting Object Model specifikacije Adobe XFA 3.3 dodjeljuje SOM izrazima, i to je sintaksa koju razumiju losLab PDF Library XFA API-ji za vrijednosti polja

Kako adresirati XFA čvorove podataka iz Delphija?

Svaka putanja datasets počinje od korijena, a losLab PDF Library ravnopravno prihvaća standardne načine pisanja: $data je skraćeni oblik XFA 3.3 za xfa.datasets.data, !data je skraćeni oblik s korijenom u xfa.datasets, a u paketima koji ne koriste obradu zapisa $record se razrješava u vanjski zapis podataka, odnosno prvi element pod čvorom podataka. API-ji s predloška kao što je SetXFAFormFieldAccess na isti način prihvaćaju $template i xfa.template. Segmenti mogu biti odvojeni točkom ili kosom crtom ($data.Receipt.Tax ili $data/Receipt/Tax), što je važno jer GetXFAFormFieldNames nabraja polja kao putanje odvojene kosom crtom koje se izravno prosljeđuju natrag u API-je za vrijednosti i predloške. Indeksi pojavljivanja temelje se na nuli prema specifikaciji, pa je Detail[0] prvi redak. Dva pravila izbjegavanja (escaping) omogućuju adresiranje neobičnih naziva: u putanji odvojenoj točkom \. označava doslovnu točku (Line\.Item), kosom crtom odvojene putanje tretiraju točke kao obične znakove. Poslovni podaci koji nose vlastiti prefiks XML prostora naziva (namespace) uparuju se prema lokalnom nazivu, tako da <m:Receipt> i dalje odgovara 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;

Kako programski popuniti ponavljajuće retke u XFA obrascu?

Zamjenski znak za pojavljivanja [*] je alat za skupnu obradu. Tamo gdje brojčani indeks odabire jednog srodnika (sibling), [*] odabire sve srodnike s istim nazivom, tako da $data.Receipt.Detail[*].Total_Price adresira polje cijene na svakoj stavci odjednom. Prilikom čitanja, GetXFAFormFieldValue vraća vrijednosti svih odgovarajućih čvorova spojenih separatorom |; prilikom pisanja, SetXFAFormFieldValue ažurira svaki odgovarajući čvor istom vrijednošću i vraća 1 u slučaju uspjeha. Putanja koja ne odgovara ničemu vraća se kao prazan niz, što je jednostavan način za provjeru postoji li grana prije pisanja u nju

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;

Selektori potomaka, zamjenskih znakova za djecu i roditelja

Tri strukturna selektora pokrivaju slučajeve u kojima znate naziv polja, ali ne i njegovu točnu dubinu. Selektor potomaka .. podudara se na bilo kojoj dubini ispod trenutnog čvora: $data..Total_Price pronalazi prvi Total_Price bilo gdje pod korijenom podataka, a pisanje kroz putanju potomka ažurira taj prvi podudarni čvor. Uočite granicu: indeks pojavljivanja nakon podudaranja potomka ne prelazi na susjedne grane, pa ako se $data..Total_Price[0] razrješava unutar prvog retka Detail, $data..Total_Price[1] vraća praznu vrijednost umjesto skoka na sljedeći redak; koristite Detail[*] kada želite sve. Zamjenski znak za djecu .* podudara se sa svakim izravnim djetetom i omogućuje filtriranje preostalih segmenata: $data.Receipt.*.Total_Price doseže Total_Price unutar svake dječje grane Receipt bez odabira istoimenog sažetka koji se nalazi izravno na samom Receipt. Konačno, svojstvo parent penje se za jednu razinu, što XFA 3.3 namjerno odvaja od ..: $data.Receipt.Detail[1].parent.Tax počinje na drugoj stavci, penje se do Receipt i završava na njegovoj sestrinskoj vrijednosti Tax

Selektori klasa i predikati: #dataGroup, #dataValue, .[expression]

Sintaksa #class adresira Data DOM čvorove prema klasi objekta umjesto prema nazivu, što je praktičan način kada XML oznake sadrže znakove koje je nezgodno pisati kao nazive skripti. losLab PDF Library mapira #dataGroup na elemente koji imaju djecu elemente, a #dataValue na listne (leaf) elemente bez djece elemenata, a oba se kombiniraju s numeričkim pojavljivanjima ili [*] pojavljivanjima: $data.Receipt.#dataGroup[0].#dataValue[0] odabire prvu vrijednost unutar prve grupe pod Receipt. Selektor predikata .[expression] filtrira srodnike istog naziva prema sadržaju. Podržani predikati su jednostavne usporedbe vrijednosti djeteta s literalom, s relacijskim operatorima kao što su >, < i <=; kada predikat odgovara većem broju redaka, čitanja se vraćaju spojena znakom |, a pisanja ažuriraju svako podudaranje

// 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]');

Koje značajke XFA SOM-a nisu podržane?

Podrška za SOM u losLab PDF knjižnici ograničena je na datasets i putanje polja predložaka, a granice su eksplicitne, a ne po principu najboljeg truda. Njihovo poznavanje unaprijed štedi vrijeme otklanjanja pogrešaka za putanju koja tiho vraća prazan niz

  • Predikati ne izvršavaju proizvoljan FormCalc ili JavaScript: nema poziva funkcija (predikat contains(...) vraća praznu vrijednost), nema složenih logičkih izraza, već samo jedna usporedba child op literal
  • $record radi samo u paketima koji ne koriste obradu zapisa; straničenje zapisa dataWindow i rotacija grupa zapisa nisu implementirani
  • Razrješavanje se vrši izravno prema Data DOM-u i DOM-u predloška; relativno razrješavanje Form DOM-a, spojeni prikaz koji preglednik gradi u vremenu izvršavanja, ne izvodi se
  • Semantika učitavanja atributa Data DOM-a se ne primjenjuje; knjižnica čita paket datasets kao sirovi XML, pa putanje adresiraju elemente, a ne atribute promovirane u čvorove

Ove granice rijetko stvaraju probleme u poslovima skupnog popunjavanja, jer se podacima pristupa prema strukturi, a ne prema logici skripte. Kada se to ipak dogodi, rješenje je rad na razini cijelog paketa: pročitajte cijeli datasets XML, transformirajte ga u Delphiju i zapišite natrag

Pisanje cijelog paketa pomoću SetXFAFromString

SetXFAFromString je ta ulazna točka na razini paketa: instalira potpuni XDP niz, predložak i datasets zajedno, stvarajući AcroForm spremnik u novom dokumentu kada on još ne postoji, te prihvaća pakete kodirane u UTF-16 s oznakama redoslijeda bajtova (BOM) kao i u UTF-8. Uobičajeni produkcijski oblik je držati XDP izdan od strane agencije kao fiksni predložak, učitati ga s SetXFAFromString, a zatim pokrenuti pozive SetXFAFormFieldValue s SOM adresama za promjenjive vrijednosti pojedinačnih računa prije spremanja. Budući da je XFA jedan od dva modela obrazaca koje PDF može nositi, klasična AcroForm strana ima vlastitu priču o automatizaciji, pokrivenu u članku o interaktivnim radnjama obrazaca i JavaScriptu

API-ji za vrijednosti XFA polja, nabrajanje i pakete prikazani ovdje isporučuju se u losLab PDF knjižnici za Delphi, C# i VB.NET; stranica proizvoda sadrži cjelovitu referencu za rukovanje obrascima