Technikai cikk

XFA SOM útvonalkifejezések Delphi-ben: Dinamikus űrlapadatok

A losLab PDF Library az XFA 3.3 SOM útvonalkifejezésein keresztül olvassa és írja az ismétlődő adatcsomópontokat a dinamikus XFA-űrlapokban: a GetXFAFormFieldValue és a SetXFAFormFieldValue elfogadja a $data, $record és !data gyökereket, a [*] előfordulási helyettesítő karaktereket, a leszármazott- és gyermek-helyettesítő választókat, a parent tulajdonságot, a #dataGroup/#dataValue osztályválasztókat, valamint az egyszerű FormCalc-stílusú predikátumokat, így egy Delphi program egyetlen hívással frissítheti egy kormányzati adóűrlap minden számlasorát. Ez a cikk a címzési referenciát mutatja be; a teljes adatkészletek fájlok közötti mozgatásához tekintse meg a társcikket az FDF, XFDF és XFA űrlapadat-cseréről.

A probléma abban a pillanatban jelentkezik, amikor az űrlap már nem sík (flat) felépítésű. Egy dinamikus XFA-űrlapként felépített áfabevallásnak vagy vámszámlának nincs tizenkét különálló mezője Total_Price_1-től Total_Price_12-ig; ehelyett egyetlen Detail alűrlapja (subform) van, amelyet egyszer deklarálnak a sablon-DOM-ban, és annyiszor példányosítanak, ahányszor az adatok megkövetelik. Az értékek egy második fában, a datasets csomagon belüli Adat-DOM-ban (Data DOM) élnek, ahol minden egyes tétel egy ismétlődő <Detail> elem a <Receipt> alatt. A sík mezőnevek nem képesek megcímezni „a harmadik sor árát” vagy „minden 200 feletti sort”; ez pontosan az a feladat, amelyet az Adobe XFA 3.3 specifikáció Scripting Object Model fejezete a SOM-kifejezéseknek tulajdonít, és ez az a szintaxis, amelyet a losLab PDF Library XFA mezőérték API-jai is használnak.

Hogyan címezhetők meg az XFA adatcsomópontok Delphi-ben?

Minden adatkészlet-útvonal (datasets path) egy gyökérből indul, és a losLab PDF Library a szabványos írásmódokat felcserélhetően elfogadja: a $data az xfa.datasets.data kifejezés XFA 3.3 szerinti rövidített formája, a !data az xfa.datasets gyökerű rövidített forma, a rekordfeldolgozást nem használó csomagokban pedig a $record a külső adatrekordra, az adatcsomópont alatti első elemre oldódik fel. Sablon-oldali API-k, mint például a SetXFAFormFieldAccess, ugyanígy elfogadják a $template és xfa.template címeket. A szegmensek lehetnek ponttal vagy perjellel elválasztva ($data.Receipt.Tax vagy $data/Receipt/Tax), ami azért fontos, mert a GetXFAFormFieldNames perjellel elválasztott útvonalként sorolja fel a mezőket, amelyek közvetlenül visszatáplálhatók az érték- és sablon-API-kba. Az előfordulási indexek a specifikáció szerint nulla alapúak, így a Detail[0] az első sort jelenti. Két elfedési (escaping) szabály teszi lehetővé a szokatlan nevek megcímzését: a ponttal elválasztott útvonalakban a \. szó szerinti pontot jelöl (Line\.Item), míg a perjellel elválasztott útvonalak a pontokat közönséges karakterként kezelik. A saját XML névtér-prefixszel rendelkező üzleti adatokat a helyi név (local name) alapján illeszti a program, így a <m:Receipt> továbbra is a Receipt névre válaszol.

var
  Lib: TPDFlib;
  Tax: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('vat-return.pdf', '');
    // Egyenértékű címek ugyanarra az adatcsomópontra
    Tax := Lib.GetXFAFormFieldValue('Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('xfa.datasets.data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$record.Tax');
    // Perjeles forma nulla alapú előfordulási indexszel
    Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
    Lib.SaveToFile('vat-return-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Hogyan tölthetők ki programozottan az ismétlődő sorok egy XFA-űrlapon?

A [*] előfordulási helyettesítő karakter a tömeges műveletek eszköze. Ahol egy numerikus index egyetlen testvérelemet választ ki, ott a [*] az összes azonos nevű testvért kijelöli, így a $data.Receipt.Detail[*].Total_Price egyszerre címzi meg az összes tételsor ármezőjét. Olvasáskor a GetXFAFormFieldValue a megfelelő csomópontok értékeit adja vissza, | elválasztóval összefűzve; íráskor a SetXFAFormFieldValue minden egyező csomópontot ugyanarra az értékre frissít, és siker esetén 1-gyel tér vissza. A semmire sem illeszkedő útvonal üres karakterláncként olvasható be, ami egyszerű módja annak ellenőrzésére, hogy létezik-e egy ág, mielőtt írnánk bele.

var
  Lib: TPDFlib;
  Prices: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('invoice.pdf', '');
    // Minden Detail sor egyszerre: '250.00|60.00'
    Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
    // Egy oszlop alaphelyzetbe állítása az összes ismétlődő sorban egyetlen hívással
    Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
    Lib.SaveToFile('invoice-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Leszármazott-, gyermek-helyettesítő- és szülőválasztók

Három strukturális választó fedi le azokat az eseteket, amikor ismeri a mező nevét, de annak pontos mélységét nem. A leszármazott választó .. az aktuális csomópont alatti bármely mélységben egyezést keres: a $data..Total_Price megtalálja az első Total_Price mezőt bárhol az adatgyökér alatt, és a leszármazott útvonalon keresztül végzett írás ezt az első egyező csomópontot frissíti. Vegye figyelembe a korlátot: a leszármazott egyezés utáni előfordulási index nem lép át a testvérágak között, így ha a $data..Total_Price[0] az első Detail soron belül oldódik fel, a $data..Total_Price[1] üresen tér vissza, ahelyett, hogy a következő sorra ugrana; használja a Detail[*]-ot, ha mindegyiket szeretné elérni. A gyermek-helyettesítő .* minden közvetlen gyermekre illeszkedik, és lehetővé teszi a többi szegmens szűrését: a $data.Receipt.*.Total_Price eléri a Total_Price-ot a Receipt minden egyes gyermekágán belül, anélkül, hogy kijelölne egy azonos nevű összegző mezőt közvetlenül a Receipt-en. Végül a parent tulajdonság egy szinttel feljebb lép, amit az XFA 3.3 szándékosan elkülönít a ..-tól: a $data.Receipt.Detail[1].parent.Tax a második tételnél kezdődik, felmegy a Receipt szintre, és a testvér Tax értéknél köt ki.

Osztályválasztók és predikátumok: #dataGroup, #dataValue, .[kifejezés]

A #class szintaxis az objektumosztály alapján címzi meg a Data DOM csomópontokat a név helyett, ami praktikus megoldás, ha az XML tagek olyan karaktereket tartalmaznak, amelyeket nehézkes lenne szkriptnévként leírni. A losLab PDF Library a #dataGroup-ot a gyermekelemekkel rendelkező elemekre, a #dataValue-t pedig a gyermekelemek nélküli levélelemekre (leaf elements) képezi le, és mindkettő kombinálható numerikus vagy [*] előfordulásokkal: a $data.Receipt.#dataGroup[0].#dataValue[0] kijelöli az első értéket az első csoporton belül a Receipt alatt. A predikátumválasztó .[expression] a tartalom alapján szűri az azonos nevű testvérelemeket. A támogatott predikátumok egy gyermekérték és egy literál egyszerű összehasonlításai olyan relációs operátorokkal, mint a >, < és <=; ha egy predikátum több sorra is illeszkedik, az olvasások |-val összefűzve térnek vissza, az írások pedig minden egyezést frissítenek.

// Megjelöl minden olyan sort, amelynek a Total_Price értéke meghaladja a 200-at
Lib.SetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');

// Az összes alacsony értékű sor leírása, '|'-vel összefűzve, ha több is egyezik
Desc := Lib.GetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price <= 200].Description');

// Osztályalapú címzés, ha a tagek nevei ellenállnak a szkriptes írásmódnak
Total := Lib.GetXFAFormFieldValue(
  '$data.Receipt.#dataGroup[0].#dataValue[0]');

Mely XFA SOM funkciók nem támogatottak?

A losLab PDF Library SOM-támogatása az adatkészletekre és a sablon mezőútvonalakra korlátozódik, és a határok határozottak, nem pedig legjobb törekvés (best-effort) jellegűek. Ezek előzetes ismerete megkíméli Önt egy olyan útvonal hibakeresésétől, amely csendben üres karakterláncot ad vissza.

  • A predikátumok nem hajtanak végre tetszőleges FormCalc vagy JavaScript kódot: nincsenek függvényhívások (a contains(...) predikátum üresen tér vissza), nincsenek összetett logikai kifejezések, csak egyetlen gyermek reláció literál összehasonlítás.
  • A $record csak nem rekordfeldolgozó csomagokban működik; a dataWindow rekord-lapozása és a rekordcsoport-forgatás nincs megvalósítva.
  • A feloldás közvetlenül a Data DOM és a sablon-DOM ellen történik; a Form DOM relatív feloldása, azaz az az összevont nézet, amelyet a megjelenítő futás közben épít fel, nem történik meg.
  • A Data DOM attribútum-betöltési szemantikája nem érvényesül; a könyvtár nyers XML-ként olvassa a datasets csomagot, így az útvonalak elemeket címeznek meg, nem pedig csomóponttá előléptetett attribútumokat.

Ezek a határok ritkán okoznak gondot a tömeges kitöltési munkák során, mert a kitöltő az adatokat a szerkezet, és nem a szkriptelt logika alapján címzi. Amikor mégis problémát okoznak, a kiút a csomagszintű elérés: olvassa be a teljes datasets XML-t, alakítsa át Delphi-ben, majd írja vissza.

A teljes csomag írása a SetXFAFromString segítségével

A SetXFAFromString a csomagszintű belépési pont: telepít egy teljes XDP karakterláncot, sablont és adatkészleteket együtt, létrehozva az AcroForm konténert egy új dokumentumban, ha az még nem létezik, és elfogadja az UTF-16 kódolású, bájtsorrend-jelölővel (BOM) ellátott csomagokat, valamint az UTF-8-at is. Egy gyakori éles környezetbeli folyamat az, hogy az ügynökség által kiadott XDP-t sablonként tároljuk, betöltjük a SetXFAFromString-gel, majd a mentés előtt SOM-címzett SetXFAFormFieldValue hívásokat futtatunk az instabil, számlánként változó értékekhez. Mivel az XFA a két formamodell egyike, amelyet egy PDF hordozhat, a klasszikus AcroForm oldalnak is megvan a maga automatizálási története, amelyet az interaktív űrlapműveletekről és JavaScriptről szóló cikk tárgyal.

Az itt bemutatott XFA mezőérték-, felsorolási és csomag-API-k a Delphi, C# és VB.NET szoftverekhez készült losLab PDF Library szoftverben érhetők el; a termékoldal tartalmazza a teljes űrlapkezelési referenciát.