Τεχνικό Άρθρο

Συγχώνευση φορμών PDF στη Delphi: Κανόνες διπλότυπων πεδίων

Η PDF Library for Delphi συγχωνεύει δύο έγγραφα AcroForm με ρητή πολιτική για πεδία που μοιράζονται όνομα. Η MergeDocumentEx δέχεται το αναγνωριστικό πηγαίου εγγράφου και μία από τρεις στρατηγικές: η dfsReject αρνείται τη συγχώνευση, η dfsMerge κρατά το κοινό όνομα και συγχρονίζει τιμές, και η dfsAutoNumber μετονομάζει τα εισερχόμενα πεδία ντετερμινιστικά. Η σάρωση ονομάτων συμβαίνει πριν μετατοπιστεί οποιοσδήποτε αριθμός αντικειμένου, οπότε μια απορριφθείσα συγχώνευση αφήνει και τα δύο έγγραφα πλήρως χρησιμοποιήσιμα

Όποιος έχει συναρμολογήσει ένα πακέτο αίτησης PDF έχει πέσει πάνω σε αυτό. Τρεις φόρμες, καθεμιά με ένα πεδίο που ονομάζεται Signature ή Date ή Total, συγχωνεύονται σε ένα αρχείο. Σε ένα AcroForm, το πλήρως προσδιορισμένο όνομα πεδίου είναι η ταυτότητα του πεδίου, οπότε δύο πεδία με το ίδιο όνομα δεν είναι καθόλου δύο πεδία: η συμπλήρωση του ενός συμπληρώνει και το άλλο, και μια υπογραφή που εφαρμόζεται στο ένα καλύπτει ένα εύρος που κανείς δεν σκόπευε

Γιατί η σύγκρουση ονομάτων αποφασίζεται πριν τη συγχώνευση;

Η παλαιότερη MergeDocument συνενώνει τους δύο πίνακες ριζικών πεδίων AcroForm και δεν προσφέρει καμία επιλογή. Χειρότερα, όταν το αποτέλεσμα είναι μη χρησιμοποιήσιμο, η ανακάλυψη συμβαίνει αφού οι αριθμοί αντικειμένων έχουν αναριθμηθεί και τα δέντρα σελίδων έχουν ραφτεί μαζί, κάτι που αφήνει τον καλούντα να κρατά ένα έγγραφο σε κατάσταση στην οποία κανένα από τα πρωτότυπα δεν βρισκόταν

Η MergeDocumentEx αντιστρέφει τη σειρά. Συλλέγει τα ονόματα πεδίων ανώτατου επιπέδου και από τα δύο έγγραφα, τα συγκρίνει, και εφαρμόζει τη στρατηγική πριν κινηθεί οτιδήποτε. Μια απόρριψη είναι επομένως καθαρή μη ενέργεια: το έγγραφο-στόχος παραμένει ανέγγιχτο, το πηγαίο έγγραφο παραμένει ανέγγιχτο, και τα δύο παραμένουν ανοιχτά και χρησιμοποιήσιμα, κάτι που η δοκιμή συγχώνευσης επαληθεύει διαβάζοντας μια τιμή πεδίου πίσω από την πηγή μετά από μια απορριφθείσα συγχώνευση

Η σύγκριση χρησιμοποιεί ένα ταξινομημένο σύνολο ονομάτων με διάκριση πεζών-κεφαλαίων, οπότε το κόστος είναι ανάλογο του συνδυασμένου αριθμού πεδίων επί έναν λογαριθμικό παράγοντα αντί για το γινόμενο των δύο αριθμών. Η διάκριση πεζών-κεφαλαίων είναι η σωστή επιλογή εδώ επειδή τα ονόματα πεδίων PDF κάνουν διάκριση πεζών-κεφαλαίων· η αναδίπλωσή τους θα συγχώνευε πεδία που η προδιαγραφή αντιμετωπίζει ως διακριτά

Οι τρεις στρατηγικές, και πότε καθεμιά είναι σωστή

Η dfsReject είναι η στρατηγική για αυτοματοποιημένους αγωγούς που δεν πρέπει να παράγουν διφορούμενα έγγραφα. Η συγχώνευση επιστρέφει μηδέν και το LastErrorCode αναφέρει 705, έναν αφιερωμένο κωδικό ώστε τα διπλότυπα ονόματα να μπορούν να ξεχωρίσουν από κάθε άλλη αποτυχία συγχώνευσης και να δρομολογηθούν σε μια συγκεκριμένη θεραπεία, συνήθως μετονομασία πεδίων ανάντη

Η dfsMerge κρατά το κοινό όνομα σκόπιμα και συγχρονίζει την τιμή στόχου και την προεπιλεγμένη τιμή στο πηγαίο πεδίο, ώστε ένα συμμορφούμενο πρόγραμμα προβολής να αντιμετωπίζει τα αρκετά widget ως ένα λογικά επώνυμο πεδίο, που είναι τυπική συμπεριφορά AcroForm για ένα πεδίο με πολλαπλές σχολιάσεις widget. Αυτό που δεν κάνει είναι να διπλώσει διαφορετικά λεξικά πεδίων σε ένα μοναδικό αντικείμενο. Κάθε πεδίο κρατά τη δική του συσχέτιση σελίδας, εμφάνιση και ενέργειες, επειδή η σύμπτυξή τους θα πετούσε σιωπηλά μορφοποίηση και συμπεριφορά που ανήκει στο εισερχόμενο έγγραφο

Η dfsAutoNumber μετονομάζει εισερχόμενα διπλότυπα προσαρτώντας ένα αριθμητικό επίθημα ξεκινώντας από _2 και παίρνοντας το πρώτο ελεύθερο. Το αποτέλεσμα είναι αναπαράξιμο: εξαρτάται μόνο από τα παρόντα ονόματα, ποτέ από αριθμούς αντικειμένων πεδίων, οπότε η συγχώνευση του ίδιου ζεύγους εγγράφων δύο φορές αποδίδει τα ίδια ονόματα και τις δύο φορές. Εκείνη η ιδιότητα έχει σημασία όταν κώδικας κατάντη, μια εισαγωγή FDF ή μια αντιστοίχιση βάσης δεδομένων αναφέρεται σε πεδία κατά όνομα

uses
  PDFlibrary;

var
  Lib: TPDFlib;
  TargetDoc, SourceDoc: Integer;
begin
  Lib := TPDFlib.Create;
  try
    TargetDoc := Lib.SelectedDocument;
    Lib.LoadFromFile('application-part1.pdf', '');

    SourceDoc := Lib.NewDocument;
    Lib.LoadFromFile('application-part2.pdf', '');

    Lib.SelectDocument(TargetDoc);
    if Lib.MergeDocumentEx(SourceDoc, dfsReject) = 0 then
    begin
      if Lib.LastErrorCode = 705 then
      begin
        // Και τα δύο έγγραφα είναι ακόμη ανέπαφα - επαναλάβετε με πολιτική
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

    Lib.SaveToFile('application-complete.pdf');
  finally
    Lib.Free;
  end;
end;

Προσέξτε το μοτίβο δύο βημάτων σε εκείνον τον κώδικα, που είναι δυνατό μόνο επειδή η απόρριψη δεν είναι καταστροφική. Δοκιμάστε πρώτα την αυστηρή πολιτική, επιθεωρήστε το σφάλμα, έπειτα αποφασίστε. Με μια συγχώνευση που αποτυγχάνει στα μισά, η εναλλακτική θα έπρεπε να ξεκινήσει από την αρχή ξαναφορτώνοντας και τα δύο αρχεία

Πώς φαίνεται η συγχωνευμένη φόρμα μετά

Υπό την dfsMerge, ένα πεδίο-στόχος με όνομα Shared που φέρει "Target value" και ένα πηγαίο πεδίο με το ίδιο όνομα παράγουν δύο πεδία, και τα δύο επώνυμα Shared, και τα δύο να αναφέρουν την τιμή στόχου, επειδή η τιμή στόχου και η προεπιλεγμένη τιμή συγχρονίζονται στο εισερχόμενο πεδίο. Αυτή είναι η προβλεπόμενη σημασιολογία για ένα κοινό όνομα: ένα λογικό πεδίο, αρκετά widget, μία τιμή

Υπό την dfsAutoNumber, η ίδια είσοδος παράγει Shared και Shared_2 ως ξεχωριστά πεδία με ανεξάρτητες τιμές. Επιλέξτε ανάμεσα στα δύο ρωτώντας μία μόνο ερώτηση: πρέπει η συμπλήρωση ενός στοιχείου ελέγχου να συμπληρώνει και το άλλο; Για ένα όνομα υπογράφοντα που επαναλαμβάνεται σε κάθε μέρος ενός πακέτου, ναι, και η dfsMerge είναι σωστή. Για ένα σύνολο που σημαίνει κάτι διαφορετικό σε κάθε φόρμα, όχι, και η αυτόματη αρίθμηση είναι σωστή

// Μετά από μια συγχώνευση, απαριθμήστε τι πραγματικά λάβατε
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Πρακτικές σημειώσεις για τη συναρμολόγηση πακέτων φορμών

Η επιτυχημένη συγχώνευση καταναλώνει το πηγαίο έγγραφο: αφαιρείται από τη λίστα εγγράφων της βιβλιοθήκης, γι' αυτό το DocumentCount πέφτει από δύο σε ένα. Μη συνεχίσετε να χρησιμοποιείτε το αναγνωριστικό πηγής μετά. Η έκδοση εγγράφου ανεβαίνει στην υψηλότερη από τις δύο, οπότε η συγχώνευση μιας φόρμας PDF 2.0 σε ένα έγγραφο 1.7 αποδίδει αρχείο 2.0

Η σειρά έχει σημασία για τα ονόματα. Η συγχώνευση του A στο B και η συγχώνευση του B στο A παράγουν διαφορετικά αυτόματα αριθμημένα αποτελέσματα, αφού το έγγραφο που κάνει τη συγχώνευση κρατά τα δικά του ονόματα αμετάβλητα. Όταν ένα πακέτο έχει μια κανονική κύρια φόρμα, κάντε εκείνη τον στόχο

Τα πεδία υπογραφής αξίζουν τη δική τους σκέψη. Μια υπογραφή που εφαρμόστηκε πριν από μια συγχώνευση καλύπτει μόνο την αναθεώρηση που υπέγραψε, οπότε η συγχώνευση την ακυρώνει με την πρακτική έννοια ότι το αρχείο έχει αλλάξει από την υπογραφή. Συναρμολογήστε πρώτα και υπογράψτε το συναρμολογημένο έγγραφο, αντί να συγχωνεύετε υπογεγραμμένα μέρη. Όταν η συγχώνευση αφορά περιεχόμενο σελίδας αντί για φόρμες, η ταχύτερη διαδρομή που περιγράφεται στη γρήγορη συγχώνευση PDF με μετατόπιση αναφορών byte είναι το καλύτερο εργαλείο

Τέλος, σχεδιάστε την πλευρά δεδομένων του πακέτου μαζί με τη συγχώνευση. Αν οι τιμές πεδίων φτάνουν από εξωτερικό σύστημα, αποφασίστε αν εκείνο το σύστημα απευθύνεται σε πεδία κατά όνομα πριν επιλέξετε αυτόματη αρίθμηση, επειδή το Shared_2 δεν θα ταιριάξει με μια αντιστοίχιση που περιμένει Shared. Μορφές εισαγωγής και εξαγωγής καλύπτονται στην ανταλλαγή δεδομένων φορμών FDF, XFDF και XFA, και συμπεριφορά δέσμης ενεργειών σε επίπεδο πεδίου που μπορεί επίσης να επηρεαστεί από μετονομασία καλύπτεται στις διαδραστικές ενέργειες φόρμας και JavaScript

Η συγχώνευση φορμών, η ανταλλαγή δεδομένων και η υπογραφή εκτελούνται στην ίδια βιβλιοθήκη για Delphi, C++Builder και Free Pascal· η πλήρης λίστα χαρακτηριστικών βρίσκεται στη σελίδα της PDF Library for Delphi