losLab PDF Library läser och skriver upprepande datanoder i dynamiska XFA-formulär via XFA 3.3 SOM-sökvägsuttryck: GetXFAFormFieldValue och SetXFAFormFieldValue accepterar rötterna $data, $record och !data, [*]-förekomstjokrar, avkomling- och barnjokerselektorer, egenskapen parent, klassselektorerna #dataGroup/#dataValue samt enkla predikat i FormCalc-stil, så att ett Delphi-program kan uppdatera varje fakturarad i en statlig skattedeklaration med ett enda anrop. Den här artikeln är en referens för adressering; för att flytta hela datauppsättningar mellan filer, se artikeln om datautbyte för FDF-, XFDF- och XFA-formulär
Problemet uppstår så fort ett formulär upphör att vara platt. En momsdeklaration eller tullredovisning som byggts som ett dynamiskt XFA-formulär har inte tolv fält namngivna Total_Price_1 till Total_Price_12; den har ett Detail-underformulär som deklarerats en gång i mallens DOM och instansierats så många gånger som datamängden kräver. Värdena lever i ett annat träd, Data DOM i datasets-paketet, där varje radobjekt är ett upprepat <Detail>-element under <Receipt>. Platta fältnamn kan inte adressera "priset på den tredje raden" eller "varje rad över 200"; det är precis det jobb som kapitlet Scripting Object Model i Adobe XFA 3.3-specifikationen ger till SOM-uttryck, och det är den syntax som losLab PDF Librarys API:er för XFA-fältvärden talar
Hur adresserar du XFA-datanoder från Delphi?
Varje sökväg i datasets startar från en rot, och losLab PDF Library accepterar standardskrivningarna omväxlande: $data är kortformen i XFA 3.3 för xfa.datasets.data, !data är kortformen med bas i xfa.datasets och i paket som inte använder posthantering (record processing) motsvarar $record den yttre dataposten (det första elementet under datanoden). API:er på mall-sidan som SetXFAFormFieldAccess accepterar $template och xfa.template på samma sätt. Segment kan vara punktavgränsade eller snedstrecksavgränsade ($data.Receipt.Tax eller $data/Receipt/Tax), vilket har betydelse eftersom GetXFAFormFieldNames räknar upp fält som snedstrecksavgränsade sökvägar som kan matas direkt tillbaka till värde- och mall-API:er. Förekomstindex är nollbaserade enligt specifikationen, så Detail[0] är den första raden. Två maskeringsregler gör att ovanliga namn förblir adresserbara: i en punktavgränsad sökväg betecknar \. en bokstavlig punkt (Line\.Item), medan snedstrecksavgränsade sökvägar behandlar punkter som vanliga tecken. Företagsdata med egna XML-namnrymdsprefix matchas med lokalt namn, så <m:Receipt> svarar fortfarande på 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;
Hur fyller du upprepade rader i ett XFA-formulär programmatiskt?
Jokern [*] för förekomst är verktyget för batch-hantering. Där ett numeriskt index väljer ett syskonelement, väljer [*] alla syskon med samma namn, så $data.Receipt.Detail[*].Total_Price adresserar prisfältet på varje radobjekt samtidigt. Vid läsning returnerar GetXFAFormFieldValue värdena för alla matchande noder sammanfogade med en |-separator; vid skrivning uppdaterar SetXFAFormFieldValue varje matchande nod med samma värde och returnerar 1 vid framgång. En sökväg som inte matchar något läses tillbaka som en tom sträng, vilket är ett enkelt sätt att undersöka om en gren existerar innan man skriver till den
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;
Avkomling-, barnjoker- och föräldraselektorer
Tre strukturella selektorer täcker fallen där du känner till fältnamnet men inte dess exakta djup. Avkomlingselektorn .. matchar på valfritt djup under den aktuella noden: $data..Total_Price hittar den första Total_Price var som helst under dataroten, och en skrivning via en avkomlingssökväg uppdaterar den första matchande noden. Observera gränsen: ett förekomstindex efter en avkomlingsmatchning går inte över till syskongrenar, så om $data..Total_Price[0] resolves inside the first Detail row, $data..Total_Price[1] returnerar tomt istället för att hoppa till nästa rad; använd Detail[*] när du vill ha alla. Barnjokern .* matchar varje direkt underordnat objekt och låter resterande segment filtrera: $data.Receipt.*.Total_Price når Total_Price inuti varje underordnad gren av Receipt utan att också välja ett sammanfattningsfält med samma namn som ligger direkt på själva Receipt. Slutligen stiger egenskapen parent upp en nivå, vilket XFA 3.3 avsiktligt skiljer från ..: $data.Receipt.Detail[1].parent.Tax startar vid det andra radobjektet, går upp till Receipt och landar på dess syskonvärde Tax
Klassselektorer och predikat: #dataGroup, #dataValue, .[expression]
Syntaxen #class adresserar Data DOM-noder via objektklass istället för via namn, vilket är det praktiska valet när XML taggar innehåller tecken som är svåra att stava som skriptnamn. losLab PDF Library mappar #dataGroup till element som har underordnade element och #dataValue till lövelement utan underordnade element, och båda kan kombineras med numeriska förekomster eller förekomster med [*]: $data.Receipt.#dataGroup[0].#dataValue[0] väljer det första värdet inuti den första gruppen under Receipt. Predikatselektorn .[expression] filtrerar syskon med samma namn baserat på innehåll. De predikat som stöds är enkla jämförelser av ett underordnat värde mot en literal, med relationsoperatorer som >, < och <=; när ett predikat matchar flera rader returneras läsningar sammanfogade med | och skrivningar uppdaterar varje matchning
// 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]');
Vilka XFA SOM-funktioner stöds inte?
SOM-stödet i losLab PDF Library är begränsat till datasets och mallfältssökvägar, och gränserna är explicita snarare än gjorda efter bästa förmåga. Att känna till dem i förväg sparar tid vid felsökning av sökvägar som tyst returnerar en tom sträng
- Predikat kör inte godtycklig FormCalc eller JavaScript: inga funktionsanrop (ett
contains(...)-predikat returnerar tomt), inga sammansatta booleska uttryck, endast en jämförelse av typenchild op literal $recordfungerar endast i paket utan posthantering (non-record-processing); record paging för dataWindow och postgruppsrotation är inte implementerade- Upplösning sker direkt mot Data DOM och mall-DOM; relativ upplösning för Form DOM, den sammanslagna vy som en visare bygger vid körtid, utförs inte
- Semantik för attributinläsning i Data DOM tillämpas inte; biblioteket läser datasets-paketet som rå XML, så sökvägar adresserar element, inte attribut som upphöjts till noder
Dessa begränsningar är sällan ett hinder vid batch-fyllning, eftersom fyllningsverktyget adresserar data baserat på struktur snarare än via skriptlogik. När de ändå gör det är nödutgången att arbeta på paketnivå: läs hela XML-koden för datasets, transformera den i Delphi och skriv tillbaka den
Skriva hela paketet med SetXFAFromString
SetXFAFromString är denna ingångspunkt på paketnivå: funktionen installerar en komplett XDP-sträng (mall och datasets tillsammans) och skapar AcroForm-behållaren i ett nytt dokument om ingen sådan finns än. Den accepterar både UTF-16-kodade paket med byte-ordningsmärken (BOM) och UTF-8. Ett vanligt produktionsmönster är att spara det av myndigheten utfärdade XDP-paketet som en fix struktur, ladda det med SetXFAFromString och sedan köra SOM-adresserade SetXFAFormFieldValue-anrop för de föränderliga värdena per faktura innan du sparar. Eftersom XFA is one of two form models a PDF can carry, den klassiska AcroForm-sidan har sin egen automatiseringshistoria, vilken behandlas i artikeln om interaktiva formuläråtgärder och JavaScript
De API:er för XFA-fältvärden, uppräkning och paket som visas här ingår i losLab PDF Library för Delphi, C# och VB.NET; produktsidan innehåller den fullständiga referensen för formulärhantering