Tehnički članak

XFA SOM izrazi putanja u Delphi-ju: Dinamički podaci obrazaca

losLab PDF biblioteka čita i piše ponavljajuće čvorove podataka u dinamičkim XFA obrascima preko XFA 3.3 SOM (Scripting Object Model) izraza putanja: metode GetXFAFormFieldValue i SetXFAFormFieldValue prihvataju korene $data, $record i !data, džoker oznake za pojavljivanje [*], selektore potomaka i džokere za decu, svojstvo parent, selektore klasa #dataGroup/#dataValue i jednostavne predikate u stilu FormCalc-a, tako da Delphi program može ažurirati svaki red fakture u državnom poreskom obrascu jednim pozivom. Ovaj članak je referenca za adresiranje, a za premeštanje čitavih skupova podataka između datoteka pogledajte prateći članak o razmeni podataka FDF, XFDF i XFA obrazaca

Problem se pojavljuje onog trenutka kada obrazac prestane da bude ravan (flat). Poreska prijava ili carinska deklaracija napravljena kao dinamički XFA obrazac nema dvanaest polja nazvanih od Total_Price_1 do Total_Price_12, već ima jedan podobrazac Detail koji je jednom deklarisan u DOM-u šablona (template DOM) i instanciran onoliko puta koliko podaci zahtevaju. Vrednosti žive u drugom stablu, DOM-u podataka (Data DOM) unutar paketa skupova podataka (datasets packet), gde je svaka stavka ponovljeni element <Detail> ispod elementa <Receipt>. Ravna imena polja ne mogu adresirati "cenu u trećem redu" ili "svaki red preko 200". To je upravo posao koji poglavlje Scripting Object Model specifikacije Adobe XFA 3.3 dodeljuje SOM izrazima, i to je sintaksa koju govore XFA API-ji za vrednosti polja u losLab PDF biblioteci

Kako adresirati XFA čvorove podataka iz Delphi-ja?

Svaka putanja skupa podataka (datasets path) počinje od korena, a losLab PDF biblioteka naizmenično prihvata standardne zapise: $data je skraćeni oblik za xfa.datasets.data iz specifikacije XFA 3.3, !data je skraćeni oblik sa korenom na xfa.datasets, a u paketima koji ne koriste obradu zapisa (record processing), $record se razrešava u spoljni zapis podataka, odnosno prvi element ispod čvora podataka. API-ji sa strane šablona, kao što je SetXFAFormFieldAccess, na isti način prihvataju $template i xfa.template. Segmenti mogu biti razdvojeni tačkom ili kosom crtom ($data.Receipt.Tax ili $data/Receipt/Tax), što je važno jer GetXFAFormFieldNames enumeriše polja kao putanje razdvojene kosim crtama koje se direktno vraćaju nazad u API-je za vrednosti i šablone. Indeksi pojavljivanja (occurrence indexes) su u bazi od 0 prema specifikaciji, tako da je Detail[0] prvi red. Dva pravila izbegavanja (escaping) omogućavaju adresiranje neobičnih naziva: u putanji razdvojenoj tačkama \. označava doslovnu tačku (Line\.Item), dok putanje razdvojene kosom crtom tretiraju tačke kao obične znakove. Poslovni podaci koji nose sopstveni prefiks XML prostora imena (namespace prefix) se uparuju po 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 redove u XFA obrascu?

Džoker oznaka za pojavljivanje [*] je alat za grupne operacije. Tamo gde numerički indeks bira jednog srodnika (sibling), [*] bira sve srodnike istog imena, tako da $data.Receipt.Detail[*].Total_Price adresira polje cene na svakoj stavci odjednom. Prilikom čitanja, GetXFAFormFieldValue vraća vrednosti svih odgovarajućih čvorova spojenih separatorom |. Prilikom pisanja, SetXFAFormFieldValue ažurira svaki odgovarajući čvor istom vrednošću i vraća 1 u slučaju uspeha. Putanja koja ne pronađe ništa čita se kao prazan string, što je jeftin način da proverite da li grana postoji pre nego što pišete 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, džokeri za decu i roditelje

Tri strukturna selektora pokrivaju slučajeve kada znate naziv polja, ali ne i njegovu tačnu dubinu. Selektor potomaka .. pronalazi podudaranja na bilo kojoj dubini ispod trenutnog čvora: $data..Total_Price pronalazi prvi Total_Price bilo gde ispod korena podataka, a pisanje preko putanje potomka ažurira taj prvi odgovarajući čvor. Obratite pažnju na granicu: indeks pojavljivanja nakon podudaranja potomka ne prelazi na srodne grane, pa ako se $data..Total_Price[0] razreši unutar prvog reda Detail, $data..Total_Price[1] vraća praznu vrednost umesto da skoči na sledeći red. Koristite Detail[*] kada želite sve njih. Džoker za decu .* uparuje svako direktno dete i omogućava preostalim segmentima da vrše filtriranje: $data.Receipt.*.Total_Price dopire do Total_Price unutar svake pod-grane elementa Receipt, bez selektovanja zbirnog polja istog imena koje se nalazi direktno na samom elementu Receipt. Konačno, svojstvo parent se penje za jedan nivo naviše, što specifikacija XFA 3.3 namerno odvaja od ..: putanja $data.Receipt.Detail[1].parent.Tax počinje na drugoj stavci, penje se do elementa Receipt i sleće na srodnu vrednost Tax

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

Sintaksa #class adresira čvorove DOM-a podataka po klasi objekta umesto po imenu, što je praktičan put kada XML tagovi sadrže znakove koje je nezgodno pisati kao nazive u skripti. losLab PDF biblioteka mapira #dataGroup na elemente koji imaju elemente-decu, a #dataValue na list-elemente (leaf elements) bez dece, i oba se kombinuju sa numeričkim ili [*] pojavljivanjima: $data.Receipt.#dataGroup[0].#dataValue[0] bira prvu vrednost unutar prve grupe ispod elementa Receipt. Selektor predikata .[izraz] filtrira srodnike istog imena po sadržaju. Podržani predikati su jednostavna poređenja vrednosti deteta sa doslovnom vrednošću, sa relacionim operatorima kao što su >, < i <=. Kada predikat odgovara većem broju redova, čitanje vraća vrednosti spojene znakom |, a pisanje ažurira 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 funkcije XFA SOM-a nisu podržane?

Podrška za SOM u losLab PDF biblioteci je ograničena na skupove podataka (datasets) i putanje polja šablona, a granice su eksplicitne, a ne zasnovane na najboljem trudu. Poznavanje ovih granica unapred štedi vreme pri debagovanju putanje koja tiho vraća prazan string

  • Predikati ne izvršavaju proizvoljan FormCalc ili JavaScript: nema poziva funkcija (predikat contains(...) vraća praznu vrednost), nema složenih logičkih izraza, već samo jedno poređenje oblika dete operator literal
  • Koren $record radi samo u paketima koji ne koriste obradu zapisa. Paginacija zapisa dataWindow i rotacija grupa zapisa nisu implementirane
  • Razrešavanje se vrši direktno u odnosu na DOM podataka i DOM šablona. Relativno razrešavanje u DOM-u obrasca (Form DOM), odnosno spojeni prikaz koji pregledač gradi u vreme izvršavanja, se ne obavlja
  • Semantika učitavanja atributa DOM-a podataka se ne primenjuje. Biblioteka čita paket skupova podataka kao sirovi XML, pa putanje adresiraju elemente, a ne atribute promovisane u čvorove

Ove granice retko ometaju rad kod grupnog popunjavanja, jer program za popunjavanje adresira podatke po strukturi, a ne po logici skripte. Kada se to desi, izlaz u nuždi je rad na nevou paketa: pročitajte ceo XML skup podataka, transformišite ga u Delphi-ju i upišite ga nazad

Upisivanje čitavog paketa pomoću SetXFAFromString

Metoda SetXFAFromString je ta ulazna tačka na nivou paketa: ona instalira kompletan XDP string, šablon i skupove podataka zajedno, kreirajući AcroForm kontejner u novom dokumentu kada on još ne postoji, i prihvata pakete kodirane u UTF-16 sa oznakama reda bajtova (BOM), kao i u UTF-8. Uobičajeni produkcijski obrazac je da zadržite službeno izdat XDP kao test podatak (fixture), učitate ga pomoću SetXFAFromString, a zatim izvršite pozive SetXFAFormFieldValue sa SOM adresiranjem za promenljive vrednosti po fakturi pre čuvanja. Pošto je XFA jedan od dva modela obrazaca koje PDF može da nosi, klasična AcroForm strana ima sopstvenu priču o automatizaciji, što je pokriveno u članku o interaktivnim akcijama obrazaca i JavaScript-u

Prikazani XFA API-ji za vrednosti polja, enumeraciju i pakete isporučuju se u losLab PDF biblioteci za Delphi, C# i VB.NET, a stranica proizvoda sadrži kompletnu referencu za rukovanje obrascima