Η losLab PDF Library διαβάζει και γράφει επαναλαμβανόμενους κόμβους δεδομένων σε δυναμικές φόρμες XFA μέσω εκφράσεων διαδρομής XFA 3.3 SOM: οι GetXFAFormFieldValue και SetXFAFormFieldValue δέχονται τις ρίζες $data, $record και !data, χαρακτήρες μπαλαντέρ εμφάνιση [*], επιλογείς απογόνων και μπαλαντέρ παιδιών, την ιδιότητα parent, επιλογείς κλάσεων #dataGroup/#dataValue, και απλά κατηγορήματα στυλ FormCalc, έτσι ώστε ένα πρόγραμμα Delphi να μπορεί να ενημερώσει κάθε γραμμή τιμολογίου σε μια κρατική φορολογική φόρμα με μία κλήση. Αυτό το άρθρο αποτελεί την αναφορά διευθυνσιοδότησης· για τη μετακίνηση ολόκληρων συνόλων δεδομένων μεταξύ αρχείων, δείτε το συνοδευτικό άρθρο για την ανταλλαγή δεδομένων φόρμας FDF, XFDF και XFA
Το πρόβλημα εμφανίζεται τη στιγμή που μια φόρμα παύει να είναι επίπεδη (flat). Μια δήλωση ΦΠΑ ή τελωνείου που έχει κατασκευαστεί ως δυναμική φόρμα XFA δεν έχει δώδεκα πεδία με ονόματα από Total_Price_1 έως Total_Price_12· έχει μία υποφόρμα Detail που δηλώνεται μία φορά στο template DOM και δημιουργείται όσες φορές απαιτούν τα δεδομένα. Οι τιμές ζουν σε ένα δεύτερο δέντρο, το Data DOM μέσα στο πακέτο datasets, όπου κάθε στοιχείο γραμμής είναι ένα επαναλαμβανόμενο στοιχείο <Detail> κάτω από το <Receipt>. Τα επίπεδα ονόματα πεδίων δεν μπορούν να διευθυνσιοδοτήσουν "την τιμή στην τρίτη γραμμή" ή "κάθε γραμμή πάνω από 200"· αυτή είναι ακριβώς η δουλειά που αναθέτει το κεφάλαιο Scripting Object Model των προδιαγραφών Adobe XFA 3.3 στις εκφράσεις SOM, και είναι η σύνταξη που μιλούν τα API τιμών πεδίων XFA της losLab PDF Library
Πώς διευθυνσιοδοτείτε τους κόμβους δεδομένων XFA από το Delphi;
Κάθε διαδρομή datasets ξεκινά από μια ρίζα (root), και η losLab PDF Library δέχεται τις τυπικές γραφές εναλλακτικά: το $data είναι η σύντομη μορφή XFA 3.3 του xfa.datasets.data, το !data είναι η σύντομη μορφή με ρίζα το xfa.datasets, και σε πακέτα που δεν χρησιμοποιούν επεξεργασία εγγραφών το $record επιλύεται στην εξωτερική εγγραφή δεδομένων, το πρώτο στοιχείο κάτω από τον κόμβο δεδομένων. Τα API από την πλευρά του προτύπου, όπως το SetXFAFormFieldAccess, δέχονται τα $template και xfa.template με τον ίδιο τρόπο. Τα τμήματα (segments) μπορεί να διαχωρίζονται με τελεία ή με κάθετο ($data.Receipt.Tax ή $data/Receipt/Tax), κάτι που έχει σημασία επειδή το GetXFAFormFieldNames απαριθμεί τα πεδία ως διαδρομές διαχωρισμένες με κάθετο που τροφοδοτούνται απευθείας πίσω στα API τιμών και προτύπων. Οι δείκτες εμφάνισης (occurrence indexes) βασίζονται στο μηδέν σύμφωνα με την προδιαγραφή, οπότε το Detail[0] είναι η πρώτη σειρά. Δύο κανόνες διαφυγής (escaping) διατηρούν τα ασυνήθιστα ονόματα διευθυνσιοδοτήσιμα: σε μια διαδρομή που διαχωρίζεται με τελεία, το \. υποδηλώνει μια κυριολεκτική τελεία (Line\.Item), ενώ οι διαδρομές που διαχωρίζονται με κάθετο αντιμετωπίζουν τις τελείες ως απλούς χαρακτήρες. Τα επιχειρηματικά δεδομένα που φέρουν το δικό τους πρόθεμα χώρου ονομάτων XML (XML namespace prefix) αντιστοιχίζονται με βάση το τοπικό όνομα (local name), οπότε το <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 σε περίπτωση επιτυχίας. Μια διαδρομή που δεν ταιριάζει με τίποτα επιστρέφει μια κενή συμβολοσειρά, η οποία είναι ο απλός τρόπος για να διερευνήσετε εάν υπάρχει ένας κλάδος (branch) πριν γράψετε σε αυτόν
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;
Επιλογείς απογόνων, μπαλαντέρ παιδιών και γονέων
Τρεις δομικοί επιλογείς καλύπτουν τις περιπτώσεις όπου γνωρίζετε το όνομα του πεδίου αλλά όχι το ακριβές βάθος του. Ο επιλογέας απογόνων (descendant selector) .. ταιριάζει σε οποιοδήποτε βάθος κάτω από τον τρέχοντα κόμβο: το $data..Total_Price βρίσκει το πρώτο Total_Price οπουδήποτε κάτω από τη ρίζα των δεδομένων, και μια εγγραφή μέσω μιας διαδρομής απογόνων ενημερώνει αυτόν τον πρώτο κόμβο που ταιριάζει. Σημειώστε το όριο: ένας δείκτης εμφάνισης μετά από μια αντιστοίχιση απογόνου δεν μεταβαίνει σε αδελφούς κλάδους, οπότε αν το $data..Total_Price[0] επιλύεται μέσα στην πρώτη σειρά Detail, το $data..Total_Price[1] επιστρέφει κενό αντί να μεταπηδήσει στην επόμενη σειρά· χρησιμοποιήστε το Detail[*] όταν τα θέλετε όλα. Το μπαλαντέρ παιδιών (child wildcard) .* ταιριάζει με κάθε άμεσο παιδί και επιτρέπει στα υπόλοιπα τμήματα να φιλτράρουν: το $data.Receipt.*.Total_Price φτάνει στο Total_Price μέσα σε κάθε παιδικό κλάδο του Receipt χωρίς να επιλέγει επίσης ένα πεδίο σύνοψης με το ίδιο όνομα που βρίσκεται απευθείας στο ίδιο το Receipt. Τέλος, η ιδιότητα parent ανεβαίνει ένα επίπεδο, το οποίο το XFA 3.3 διαχωρίζει σκόπιμα από το ..: το $data.Receipt.Detail[1].parent.Tax ξεκινά από το δεύτερο στοιχείο γραμμής, ανεβαίνει στο Receipt και καταλήγει στην αδελφή τιμή Tax
Επιλογείς κλάσεων και κατηγορήματα: #dataGroup, #dataValue, .[expression]
Η σύνταξη #class απευθύνεται σε κόμβους Data DOM με βάση την κλάση αντικειμένου αντί για το όνομα, η οποία είναι η πρακτική οδός όταν οι ετικέτες XML περιέχουν χαρακτήρες που είναι δύσκολο να γραφτούν ως ονόματα σεναρίων (script names). Η losLab PDF Library αντιστοιχεί το #dataGroup σε στοιχεία που έχουν παιδιά στοιχεία και το #dataValue σε στοιχεία φύλλου (leaf elements) χωρίς παιδιά στοιχεία, και τα δύο συνδυάζονται με αριθμητικές εμφανίσεις ή εμφανίσεις [*]: το $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 και template, και τα όρια είναι ρητά αντί για βέλτιστης προσπάθειας. Η εκ των προτέρων γνώση τους εξοικονομεί χρόνο από τον εντοπισμό σφαλμάτων σε μια διαδρομή που επιστρέφει σιωπηλά μια κενή συμβολοσειρά
- Τα κατηγορήματα δεν εκτελούν αυθαίρετο FormCalc ή JavaScript: καθόλου κλήσεις συναρτήσεων (ένα κατηγόρημα
contains(...)επιστρέφει κενό), καθόλου σύνθετες λογικές εκφράσεις, μόνο μία απλή σύγκρισηchild op literal - Το
$recordλειτουργεί μόνο σε πακέτα χωρίς επεξεργασία εγγραφών· η σελιδοποίηση εγγραφών dataWindow και η περιστροφή ομάδων εγγραφών δεν έχουν υλοποιηθεί - Η επίλυση γίνεται απευθείας έναντι του Data DOM και του template DOM· η σχετική επίλυση Form DOM, η συγχωνευμένη προβολή που δημιουργεί ένα πρόγραμμα προβολής κατά το χρόνο εκτέλεσης, δεν εκτελείται
- Οι σημασιολογίες φόρτωσης χαρακτηριστικών (attribute-loading) του Data DOM δεν εφαρμόζονται· η βιβλιοθήκη διαβάζει το πακέτο datasets ως ακατέργαστο XML, οπότε οι διαδρομές απευθύνονται σε στοιχεία, όχι σε χαρακτηριστικά που έχουν προαχθεί σε κόμβους
Αυτά τα όρια σπάνια δημιουργούν προβλήματα στις εργασίες μαζικής συμπλήρωσης, επειδή η συμπλήρωση απευθύνεται στα δεδομένα βάσει δομής και όχι βάσει σεναρίων λογικής. Όταν συμβαίνει αυτό, η διέξοδος διαφυγής είναι το επίπεδο του πακέτου: διαβάστε ολόκληρο το XML των datasets, μετασχηματίστε το στο Delphi και γράψτε το πίσω
Εγγραφή ολόκληρου του πακέτου με το SetXFAFromString
Το SetXFAFromString είναι αυτό το σημείο εισόδου σε επίπεδο πακέτου: εγκαθιστά μια πλήρη συμβολοσειρά XDP, πρότυπο και datasets μαζί, δημιουργώντας τον περιέκτη AcroForm σε ένα νέο έγγραφο όταν δεν υπάρχει ακόμη, και δέχεται πακέτα κωδικοποιημένα σε UTF-16 με σήμανση σειράς byte (byte-order marks) καθώς και σε UTF-8. Ένα κοινό μοτίβο παραγωγής είναι να διατηρείτε το εκδοθέν από τον οργανισμό XDP ως σταθερά (fixture), να το φορτώνετε με το SetXFAFromString, και στη συνέχεια να εκτελείτε κλήσεις SetXFAFormFieldValue με διευθύνσεις SOM για τις μεταβλητές τιμές ανά τιμολόγιο πριν από την αποθήκευση. Δεδομένου ότι το XFA είναι ένα από τα δύο μοντέλα φορμών που μπορεί να φέρει ένα PDF, η κλασική πλευρά AcroForm έχει τη δική της ιστορία, η οποία καλύπτεται στο άρθρο για τις διαδραστικές ενέργειες φόρμας και τη JavaScript
Τα API τιμών πεδίων, απαρίθμησης και πακέτων XFA που εμφανίζονται εδώ αποστέλλονται στη losLab PDF Library για Delphi, C# και VB.NET· η σελίδα του προϊόντος περιέχει την πλήρη αναφορά χειρισμού φορμών