losLab PDF Library чете и записва повтарящи се възли от данни в динамични XFA формуляри чрез XFA 3.3 SOM (Scripting Object Model) изрази за пътища: GetXFAFormFieldValue и SetXFAFormFieldValue приемат корените $data, $record и !data, заместващи знаци за повторение [*], селектори за наследници и заместващи знаци за деца, свойството parent, класови селектори #dataGroup/#dataValue и прости предикати в стил FormCalc, така че програма на Delphi може да актуализира всеки ред от фактура в правителствен данъчен формуляр с едно извикване. Тази статия е справочник за адресирането; за преместване на цели набори от данни между файлове вижте придружаващата статия за обмен на данни от формуляри във формати FDF, XFDF и XFA
Проблемът възниква в момента, в който формулярът спре да бъде плосък. Декларация по ДДС или митническа декларация, изградена като динамичен XFA формуляр, няма дванадесет полета, наречени от Total_Price_1 до Total_Price_12; тя има една подобласт Detail, декларирана веднъж в шаблона DOM и инстанцирана толкова пъти, колкото изискват данните. Стойностите живеят във второ дърво – Data DOM вътре в пакета datasets, където всеки артикул е повтарящ се елемент <Detail> под <Receipt>. Плоските имена на полета не могат да адресират „цената на третия ред“ или „всеки ред над 200“; това е точно задачата, която разделът Scripting Object Model на спецификацията Adobe XFA 3.3 дава на SOM изразите, и това е синтаксисът, когото говорят приложните програмни интерфейсове (API) за стойности на XFA полета в losLab PDF Library
Как се адресират XFA възли от данни в Delphi?
Всеки път в datasets започва от корен, като losLab PDF Library приема стандартните изписвания взаимозаменяемо: $data е кратката форма в XFA 3.3 за xfa.datasets.data, !data е кратката форма с корен в xfa.datasets, а в пакети, които не използват обработка на записи (record processing), $record съвпада с външния запис от данни – първия елемент под възела за данни. Приложните програмни интерфейсове (API) от страната на шаблона като SetXFAFormFieldAccess приемат $template и xfa.template по същия начин. Сегментите могат да бъдат разделени с точки или наклонени черти ($data.Receipt.Tax или $data/Receipt/Tax), което е важно, тъй като GetXFAFormFieldNames изброява полетата като пътища, разделени с наклонени черти, които се подават директно обратно в API-тата за стойности и шаблони. Индексите за повторение са нулево-базирани според спецификацията, така че Detail[0] е първият ред. Две правила за екраниране поддържат необичайните имена адресируеми: в път, разделен с точка, \. означава буквална точка (Line\.Item), докато пътищата с наклонени черти третират точките като обикновени символи. Бизнес данните, които носят собствен префикс на XML пространство от имена, се съпоставят по локално име, така че <m:Receipt> все още съответства на 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;
Как се попълват програмно повтарящи се редове в XFA формуляр?
Заместващият знак за повторение [*] е инструментът за групова обработка. Докато числовият индекс избира един съседен елемент, [*] избира всички съседи с едно и също име, така че $data.Receipt.Detail[*].Total_Price адресира полето за цена във всеки един ред наведнъж. При четене, GetXFAFormFieldValue връща стойностите на всички съвпадащи възли, обединени с разделител |; при запис, SetXFAFormFieldValue актуализира всеки съвпадащ възел с една и съща стойност и връща 1 при успех. Път, който не съвпада с нищо, се чете като празен низ, което е лесният начин да проверите дали даден клон съществува, преди да пишете в него
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;
Селектори за наследници, заместващи знаци за деца и родителски селектори
Три структурни селектора покриват случаите, в които знаете името на полето, но не и точната му дълбочина. Селекторът за наследници .. намира съвпадение на всякаква дълбочина под текущия възел: $data..Total_Price намира първото Total_Price навсякъде под корена за данни, а записът през път за наследник актуализира този първи съвпадащ възел. Обърнете внимание на ограничението: индексът за повторение след съвпадение за наследник не преминава през съседни клонове, така че ако $data..Total_Price[0] сочи към първия ред на Detail, $data..Total_Price[1] връща празен низ, вместо да прескочи на следващия ред; използвайте Detail[*], ако искате всички тях. Заместващият знак за дете .* съвпада с всяко пряко дете и позволява филтриране по следващите сегменти: $data.Receipt.*.Total_Price достига до Total_Price във всеки детски клон на Receipt, без да избира обобщаващо поле със същото име, намиращо се директно в самия Receipt. И накрая, свойството parent се изкачва с едно ниво нагоре, което XFA 3.3 умишлено разделя от ..: $data.Receipt.Detail[1].parent.Tax започва от втория артикул, изкачва се до Receipt и попада върху съседната стойност Tax
Класови селектори и предикати: #dataGroup, #dataValue, .[израз]
Синтаксисът #class адресира Data DOM възли по клас на обекта вместо по име, което е практическият път, когато XML таговете съдържат символи, които са неудобни за изписване като имена в скрипт. losLab PDF Library съпоставя #dataGroup към елементи, които имат деца-елементи, и #dataValue към крайни (leaf) елементи без деца-елементи, като и двата вида се комбинират с числови или [*] повторения: $data.Receipt.#dataGroup[0].#dataValue[0] избира първата стойност в първата група под Receipt. Селекторът с предикат .[expression] филтрира съседни елементи с едно и също име по тяхното съдържание. Поддържаните предикати са прости сравнения на стойност на дете с литерал, с релационни оператори като >, < и <=; когато даден предикат намери съвпадение в няколко реда, четенето връща резултати, разделени с |, а записът актуализира всяко съвпадение
// 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]');
Кои функции на XFA SOM не се поддържат?
Поддръжката на SOM в losLab PDF Library е ограничена до datasets и пътища на полета в шаблони, като границите са ясни, а не на принципа на най-добрите усилия. Знанието за тях предварително спестява дебъгването на път, който безшумно връща празен низ
- Предикатите не изпълняват произволен FormCalc или JavaScript: без извиквания на функции (предикат
contains(...)връща празен низ), без сложни булеви изрази, а само единично сравнениеchild op literal $recordработи само в пакети без обработка на записи; странирането на записи в dataWindow и ротацията на групи от записи не са внедрени- Разделителната способност е директно спрямо Data DOM и шаблонния DOM; относителното разрешаване на Form DOM (смесеният изглед, който четецът изгражда при изпълнение) не се извършва
- Семантиката за зареждане на атрибути в Data DOM не се прилага; библиотеката чете пакета datasets какъвто е, като суров XML, така че пътищата адресират елементи, а не атрибути, превърнати във възли
Тези ограничения рядко пречат при задачи по групово попълване, тъй като попълващият код адресира данните по структура, а не по логика на скрипт. Когато обаче възпрепятстват работата, спасителният изход е на ниво пакет: прочетете целия datasets XML, трансформирайте го в Delphi и го запишете обратно
Записване на целия пакет с SetXFAFromString
Методът SetXFAFromString е входната точка на ниво пакет: той инсталира пълен XDP низ (шаблон и datasets заедно), създавайки контейнера AcroForm в нов документ, ако такъв все още не съществува, и приема както UTF-16 кодирани пакети с маркери за подредба на байтовете (BOM), така и UTF-8. Общ производствен шаблон е да запазите издадения от агенцията XDP файл като шаблон, да го заредите с SetXFAFromString, след което да изпълните SOM-адресирани повиквания на SetXFAFormFieldValue за променливите стойности на конкретната фактура преди запис. Тъй като XFA е един от двата модела на формуляри, които PDF може да носи, класическата страна на AcroForm има своя собствена история на автоматизация, разгледана в статията за интерактивни действия с формуляри и JavaScript
Приложните програмни интерфейсове (API) за стойности на XFA полета, изброяване и пакети, показани тук, се доставят в losLab PDF Library за Delphi, C# и VB.NET; продуктовата страница съдържа пълното справочно ръководство за работа с формуляри