Teknisk artikel

XFA SOM-stirepræsentationer i Delphi: Dynamiske formulardata

losLab PDF Library læser og skriver gentagne dataknuder i dynamiske XFA-formularer via XFA 3.3 SOM-stirepræsentationer: GetXFAFormFieldValue og SetXFAFormFieldValue accepterer rødderne $data, $record og !data, [*]-forekomst-jokere, efterkommere og jokertegn for børn, egenskaben parent, klasseselektorerne #dataGroup/#dataValue samt simple prædikater i FormCalc-stil, så et Delphi-program kan opdatere hver fakturalinje i en statslig selvangivelse med et enkelt kald. Denne artikel er referencen for adressering; for flytning af hele datasæt mellem filer, se den tilhørende artikel om dataudveksling af FDF-, XFDF- og XFA-formulardata

Problemet opstår i det øjeblik, en formular holder op med at være flad. En momserklæring eller toldangivelse opbygget som en dynamisk XFA-formular har ikke tolv felter med navne fra Total_Price_1 til Total_Price_12; den har én Detail-underformular erklæret én gang i skabelonens DOM og instansieret så mange gange, som dataene kræver. Værdierne lever i et andet træ, nemlig Data DOM'en inde i datasets-pakken, hvor hver linjepost is et gentaget <Detail>-element under <Receipt>. Flade feltnavne kan ikke adressere "prisen på den tredje linje" eller "hver linje over 200"; det er præcis den opgave, som Scripting Object Model-kapitlet i Adobe XFA 3.3-specifikationen giver til SOM-udtryk, og det er den syntaks, som losLab PDF Library's API'er til XFA-feltværdier taler

Hvordan adresserer du XFA-dataknuder fra Delphi?

Enhver datasti starter fra en rod, og losLab PDF Library accepterer de standardiserede stavemåder i flæng: $data er XFA 3.3-kortformen af xfa.datasets.data, !data er kortformen med rod i xfa.datasets, og i pakker, der ikke bruger postbehandling, opløses $record til den ydre datapost, det første element under dataknuden. Skabelonside-API'er som SetXFAFormFieldAccess accepterer $template og xfa.template på samme måde. Segmenter kan være punktum-separerede eller skråstreg-separerede ($data.Receipt.Tax eller $data/Receipt/Tax), hvilket er vigtigt, fordi GetXFAFormFieldNames opregner felter som skråstreg-separerede stier, der fødes direkte tilbage i værdi- og skabelon-API'erne. Forekomstindekser er nulbaserede i henhold til specifikationen, så Detail[0] er den første række. To undvigeregler holder usædvanlige navne adresserbare: I en punktum-separeret sti angiver \. et bogstaveligt punktum (Line\.Item), mens skråstreg-separerede stier behandler punktummer som almindelige tegn. Forretningsdata, der bærer sit eget XML-navnerumspræfiks, matches af det lokale navn, så <m:Receipt> still answers to 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;

Hvordan udfylder du gentagne rækker i en XFA-formular programmatisk?

Jokeren for forekomst [*] er batch-værktøjet. Hvor et numerisk indeks vælger én søskende, vælger [*] alle søskende med samme navn, så $data.Receipt.Detail[*].Total_Price adresserer prisfeltet på alle linjeposter på én gang. Ved en læsning returnerer GetXFAFormFieldValue værdierne af alle matchende knuder sammenføjet med en |-separator; ved en skrivning opdaterer SetXFAFormFieldValue hver matchende knude med den samme værdi og returnerer 1 ved succes. En sti, der ikke matcher noget, læses tilbage som en tom streng, hvilket er den hurtige måde at undersøge, om en gren eksisterer, før du skriver i 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;

Efterkommere, jokere for børn og forældreselektorer

Tre strukturelle selektorer dækker de tilfælde, hvor du kender feltnavnet, men ikke dets nøjagtige dybde. Efterkommerselektoren .. matcher på enhver dybde under den aktuelle knude: $data..Total_Price finder den første Total_Price hvor som helst under dataroden, og en skrivning gennem en efterkommersti opdaterer den første matchende knude. Bemærk grænsen: Et forekomstindeks efter et efterkommermatch træder ikke på tværs af søskendegrene, så hvis $data..Total_Price[0] løses inde i den første Detail-række, returnerer $data..Total_Price[1] tomt i stedet for at hoppe til den næste række; brug Detail[*], når du ønsker dem alle. Jokeren for børn .* matcher alle direkte børn og lader de resterende segmenter filtrere: $data.Receipt.*.Total_Price når Total_Price inde i hver barnegren af Receipt uden også at vælge et opsummeringsfelt med samme navn, der sidder direkte på selve Receipt. Endelig klatrer egenskaben parent ét niveau op, hvilket XFA 3.3 bevidst adskiller fra ..: $data.Receipt.Detail[1].parent.Tax starter ved den anden linjepost, går op to Receipt og lander på dens søskendes Tax-værdi

Klasseselektorer og prædikater: #dataGroup, #dataValue, .[udtryk]

Syntaksen #class adresserer Data DOM-knuder efter objektklasse i stedet for navn, hvilket er den praktiske vej, når XML-tags indeholder tegn, der er svære at stave som scriptnavne. losLab PDF Library kortlægger #dataGroup to elementer, der har elementbørn, og #dataValue til bladelementer uden elementbørn, og begge kombineres med numeriske forekomster eller [*]-forekomster: $data.Receipt.#dataGroup[0].#dataValue[0] vælger den første værdi i den første gruppe under Receipt. Prædikatselektoren .[expression] filtrerer søskende med samme navn efter indhold. Understøttede prædikater er simple sammenligninger af en barneværdi mod en literal, med relationelle operatorer som f.eks. >, < og <=; når et prædikat matcher flere rækker, kommer læsninger tilbage |-sammenføjet, og skrivninger opdaterer hvert match

// 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]');

Hvilke XFA SOM-funktioner understøttes ikke?

SOM-understøttelsen i losLab PDF Library er afgrænset til datasets- og skabelonfeltstier, og grænserne er eksplicitte frem for at gøre det så godt som muligt. At kende dem på forhånd sparer dig for at fejlsøge en sti, der lydløst returnerer en tom streng

  • Prædikater udfører ikke vilkårlig FormCalc eller JavaScript: Ingen funktionskald (et contains(...)-prædikat returnerer tomt), ingen sammensatte boolske udtryk, kun en enkelt child op literal-sammenligning
  • $record fungerer kun i pakker uden postbehandling; dataWindow-postpaginering og postgrupperotation er ikke implementeret
  • Opløsning sker mod Data DOM og skabelon-DOM direkte; Form DOM-relativ opløsning, den flettede visning en fremviser bygger under kørsel, udføres ikke
  • Data DOM-attributindlæsningssemantik anvendes ikke; biblioteket læser datasets-pakken som rå XML, så stier adresserer elementer, ikke attributter forfremmet til knuder

Disse grænser bider sjældent i batchudfyldningsarbejde, fordi en udfylder adresserer data efter struktur frem for efter scriptet logik. Når de gør det, er bagdøren pakkeniveauet: Læs hele datasets-XML'en, transformer den i Delphi, og skriv den tilbage

Skrivning af hele pakken med SetXFAFromString

SetXFAFromString er dette indgangspunkt på pakkeniveau: Den installerer en komplet XDP-streng, skabelon og datasets sammen, og opretter AcroForm-containeren i et nyt dokument, når der endnu ikke findes en, og den accepterer UTF-16-kodede pakker med byte-order-mærker såvel som UTF-8. En almindelig produktionsform er at beholde den myndighedsudstedte XDP som en fast del, indlæse den med SetXFAFromString og derefter køre SOM-adresserede SetXFAFormFieldValue-kald for de flygtige værdier pr. faktura før lagring. Da XFA er en af to formularmodeller, en PDF kan bære, har den klassiske AcroForm side sin egen automatiseringshistorie, dækket i artiklen om interaktive formularhandlinger og JavaScript

De XFA-feltværdier, opregnings- og pakke-API'er, der vises her, leveres i losLab PDF Library til Delphi, C# og VB.NET; produktsiden indeholder den fulde formularhåndteringsreference