Tekninen artikkeli

XFA SOM -polkulausekkeet Delphissä: Dynaaminen lomaketieto

losLab PDF Library lukee ja kirjoittaa toistuvia tietosolmuja dynaamisissa XFA-lomakkeissa XFA 3.3 SOM -polkulausekkeilla: GetXFAFormFieldValue ja SetXFAFormFieldValue hyväksyvät juuret $data, $record ja !data, toistokerrointen yleismerkit [*], jälkeläis- ja lapsi-valitsimet, parent-ominaisuuden, #dataGroup/#dataValue -luokkavalitsimet sekä yksinkertaiset FormCalc-tyyliset predikaatit. Siten Delphi-ohjelma voi päivittää virallisen verolomakkeen jokaisen laskurivin yhdellä kutsulla. Tämä artikkeli toimii osoitteiden määrityksen viitteenä; jos haluat siirtää kokonaisia tietojoukkoja tiedostojen välillä, katso kumppaniartikkeli FDF-, XFDF- ja XFA-lomaketietojen vaihdosta

Ongelma ilmenee heti, kun lomake lakkaa olemasta litteä. Dynaamisena XFA-lomakkeena rakennetussa arvonlisäveroilmoituksessa tai tulli-ilmoituksessa ei ole kahtatoista kenttää nimeltään Total_Price_1Total_Price_12; siinä on yksi Detail-alilomake, joka on ilmoitettu kerran mallipohjan DOM-rakenteessa ja josta luodaan niin monta esiintymää kuin tiedot vaativat. Arvot sijaitsevat toisessa puussa, eli datasets-paketin sisällä olevassa Data DOM -rakenteessa, jossa kukin rivi on toistuva <Detail>-elementti kohdassa <Receipt>. Litteät kenttien nimet eivät kykene osoittamaan "kolmannen rivin hintaan" tai "jokaiseen yli 200 arvoiseen riviin". Tämä on juuri se tehtävä, jonka Adobe XFA 3.3 -spesifikaation Scripting Object Model -luku antaa SOM-lausekkeille, ja se on syntaksi, jota losLab PDF Libraryn XFA-kenttäarvojen rajapinnat puhuvat

Miten osoitat XFA-tietosolmuja Delphistä?

Jokainen datasets-polku alkaa juuresta, ja losLab PDF Library hyväksyy standardit kirjoitusasut vaihtokelpoisesti: $data on XFA 3.3:n lyhyt muoto ilmaisulle xfa.datasets.data, !data on lyhyt muoto, jonka juurena on xfa.datasets, ja paketeissa, joissa ei käytetä tietueiden käsittelyä, $record ratkeaa ulommaksi tietueeksi eli ensimmäiseksi elementti tietosolmun alla. Mallipohjan puoleiset rajapinnat kuten SetXFAFormFieldAccess hyväksyvät $template- ja xfa.template-muodot samalla tavoin. Segmentit voivat olla pisteillä tai vino-viivoilla erotettuja ($data.Receipt.Tax tai $data/Receipt/Tax). Tällä on merkitystä, koska GetXFAFormFieldNames luettelee kentät vino-viivoilla erotettuina polkuina, jotka syötetään suoraan takaisin arvo- ja mallipohjarajapintoihin. Esiintymien indeksit ovat nollapohjaisia spesifikaation mukaisesti, joten Detail[0] on ensimmäinen rivi. Kaksi suojamerkkisääntöä pitää erikoiset nimet osoitettavina: pisteillä erotetussa polussa \. tarkoittaa kirjaimellista pistettä (Line\.Item), kun taas vino-viivoilla erotetuissa poluissa pisteitä käsitellään tavallisina merkkeinä. Yritystieto, joka kantaa omaa XML-nimiavaruuden etuliitettään, täsmätään paikallisen nimen perusteella, joten <m:Receipt> vastaa silti nimeen Receipt

var
  Lib: TPDFlib;
  Tax: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('vat-return.pdf', '');
    // Vastaavat osoitteet samalle tietosolmulle
    Tax := Lib.GetXFAFormFieldValue('Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('xfa.datasets.data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$record.Tax');
    // Vino-viivallinen muoto nollapohjaisella indeksillä
    Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
    Lib.SaveToFile('vat-return-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Miten täytät toistuvia rivejä XFA-lomakkeessa ohjelmallisesti?

Toistokerrointen yleismerkki [*] on eräajotyökalu. Siinä missä numeerinen indeksi valitsee yhden sisaruksen, [*] valitsee kaikki samannimiset sisarukset, joten $data.Receipt.Detail[*].Total_Price osoittaa jokaisen laskurivin hintakenttään kerralla. Lukutilassa GetXFAFormFieldValue palauttaa kaikkien täsmäävien solmujen arvot yhdistettynä |-erottimella; kirjoitustilassa SetXFAFormFieldValue päivittää jokaisen täsmäävän solmun samalla arvolla ja palauttaa arvon 1 onnistuessaan. Polku, joka ei täsmää mihinkään, palauttaa tyhjän merkkijonon, mikä on helppo tapa tutkia haaran olemassaoloa ennen siihen kirjoittamista

var
  Lib: TPDFlib;
  Prices: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('invoice.pdf', '');
    // Kaikki Detail-rivit kerralla: '250.00|60.00'
    Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
    // Nollaa yksi sarake jokaiselta toistuvalta riviltä yhdellä kutsulla
    Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
    Lib.SaveToFile('invoice-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Jälkeläis-, lapsi- ja vanhempivalitsimet

Kolme rakennevalitsinta kattaa tilanteet, joissa tiedät kentän nimen mutta et sen tarkkaa syvyyttä. Jälkeläisvalitsin .. täsmää mihin tahansa syvyyteen nykyisen solmun alla: $data..Total_Price löytää ensimmäisen Total_Price-esiintymän mistä tahansa datajuuren alta, ja jälkeläispolun kautta tehty kirjoitus päivittää kyseisen ensimmäisen täsmäävän solmun. Huomaa rajoitus: indeksimerkintä jälkeläisvalitsimen jälkeen ei siirry sisarushaarojen yli, joten jos $data..Total_Price[0] ratkeaa ensimmäisen Detail-rivin sisälle, $data..Total_Price[1] palauttaa tyhjää sen sijaan, että se hyppäisi seuraavalle riville; käytä muotoa Detail[*], kun haluat valita ne kaikki. Lapsi-yleismerkki .* täsmää jokaiseen suoraan lapseen ja antaa jäljellä olevien segmenttien suodattaa: $data.Receipt.*.Total_Price tavoittaa Total_Price-kentän kunkin Receipt-haaran lapsen sisällä ilman, että valittaisiin samannimistä yhteenvetokenttää, joka sijaitsee suoraan Receipt-solmussa itsessään. Lopuksi parent-ominaisuus nousee yhden tason ylöspäin, minkä XFA 3.3 erottaa tietoisesti valitsimesta ..: $data.Receipt.Detail[1].parent.Tax aloittaa toisesta laskurivistä, nousee Receipt-solmuun ja päätyy sen sisarustietoon Tax

Luokkavalitsimet ja predikaatit: #dataGroup, #dataValue, .[expression]

#class-syntaksi osoittaa Data DOM -solmuja olioluokan perusteella nimen sijaan, mikä on käytännöllistä silloin, kun XML-tagit sisältävät merkkejä, joita on hankala kirjoittaa skriptinimiksi. losLab PDF Library kartoittaa #dataGroup-luokan elementteihin, joilla on lapsielementtejä, ja #dataValue-luokan lehtielementteihin (leaf elements) ilman lapsielementtejä. Molemmat yhdistyvät numeerisiin tai [*]-toistoihin: $data.Receipt.#dataGroup[0].#dataValue[0] valitsee ensimmäisen arvon ensimmäisestä ryhmästä Receipt-solmun alla. Predikaattivalitsin .[expression] suodattaa samannimisiä sisaruksia sisällön mukaan. Tuettuja predikaatteja ovat yksinkertaiset lapsisolmun arvon vertailut literaaliin relaatio-operaattoreilla kuten >, < ja <=. Kun predikaatti täsmää useaan riviin, lukutulokset palautetaan |-eroteltuina ja kirjoitukset päivittävät jokaisen täsmäävän kohteen

// Merkitse jokainen rivi, jonka Total_Price ylittää arvon 200
Lib.SetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');

// Kaikkien pienen arvon rivien kuvaukset, '|'-eroteltuina jos useampi täsmää
Desc := Lib.GetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price <= 200].Description');

// Luokkapohjainen osoitus silloin, kun taginimet eivät sovellu skriptiasuun
Total := Lib.GetXFAFormFieldValue(
  '$data.Receipt.#dataGroup[0].#dataValue[0]');

Mitä XFA SOM -ominaisuuksia ei tueta?

losLab PDF Libraryn SOM-tuki on rajattu datasets- ja template-kenttäpolkuihin, ja rajat ovat selkeät pikemmin kuin parhaaseen tulokseen pyrkivät. Niiden tietäminen etukäteen säästää polkujen virheenetsinnältä, jotka palauttavat hiljaa tyhjän merkkijonon

  • Predikaatit eivät suorita vapaata FormCalc- tai JavaScript-koodia: ei funktiokutsuja (contains(...)-predikaatti palauttaa tyhjää), ei monimutkaisia totuusarvolausekkeita, vaan ainoastaan yksinkertaisen lapsi operaattori literaali -vertailun
  • $record toimii ainoastaan paketeissa, joissa ei käytetä tietueiden käsittelyä; dataWindow-tietuesivutusta ja tietuenryhmien vuorottelua ei ole toteutettu
  • Ratkaisu tehdään suoraan Data DOM- ja template DOM -rakenteita vasten; Form DOM -rakenteen suhteellista ratkaisua eli katseluohjelman ajonaikaisesti luomaa yhdistettyä näkymää ei suoriteta
  • Data DOM -attribuuttien lataussemantiikkaa ei sovelleta; Kirjasto lukee datasets-paketin raakana XML-tietona, joten polut osoittavat elementteihin, eivät solmuiksi ylennettyihin attribuutteihin

Nämä rajat harvoin aiheuttavat ongelmia eräajoissa, sillä täyttöohjelma osoittaa tietoa rakenteen perusteella pikemminkin kuin ohjelmallisella logiikalla. Jos ne kuitenkin aiheuttavat ongelmia, varavaihtoehtona on pakettitaso: lue koko datasets XML, muunna se Delphissä ja kirjoita se takaisin

Koko paketin kirjoittaminen SetXFAFromString-metodilla

SetXFAFromString on pakettitason aloituspiste: se asentaa kokonaisen XDP-merkkijonon (sekä mallipohjan että datasets-tietueen yhdessä) luoden AcroForm-säiliön uuteen asiakirjaan, jos sellaista ei vielä ole. Se hyväksyy UTF-16-koodatut paketit tavujärjestysmerkeillä (BOM) sekä UTF-8-muotoiset paketit. Tavallinen tuotantomalli on pitää viranomaisen antama XDP-tiedosto vakiona, ladata se SetXFAFromString-kutsulla ja suorittaa SOM-osoitetut SetXFAFormFieldValue-kutsut muuttuville laskukohtaisille arvoille ennen tallennusta. Koska XFA on toinen kahdesta PDF-asiakirjan tukemasta lomakemallista, klassisella AcroForm-puolella on oma automaatiotarinansa, jota käsitellään artikkelissa vuorovaikutteisista lomaketoiminnoista ja JavaScriptistä

Tässä esitetyt XFA-kenttäarvo-, luettelo- ja pakettirajapinnat sisältyvät losLab PDF Library -tuotteeseen Delphille, C#:lle ja VB.NET:ille; tuotesivulla on täydellinen lomakkeiden käsittelyviite