Έχετε δημιουργήσει ένα τιμολόγιο Factur-X και κάθε έλεγχος του container περνά. Ο catalog φέρει έναν πίνακα /AF, το δέντρο ονομάτων EmbeddedFiles οδηγεί στη σωστή προδιαγραφή αρχείου, το ενσωματωμένο factur-x.xml έχει το σωστό /AFRelationship με τιμή Alternative, και η ενσωματωμένη ValidateFacturXInvoice επιστρέφει 1. Έπειτα περνάτε το ίδιο αρχείο από το veraPDF, τον ελεγκτή αναφοράς που χρησιμοποιούν οι φορολογικές πύλες, και αυτό αποφαίνεται ότι ολόκληρο το έγγραφο δεν είναι έγκυρο PDF/A-3. Η δομή είναι σωστή. Το πρόβλημα είναι τα μεταδεδομένα, και η αποτυχία αυτή είναι από τις πιο εύκολες να παραβλεφθούν σε ολόκληρη τη ροή εργασίας ηλεκτρονικής τιμολόγησης
Αξίζει να κατανοήσετε πλήρως τον λόγο, γιατί εξηγεί μια κατηγορία ελαττωμάτων PDF/A που δεν έχει καμία σχέση με την ορατή σελίδα ή το συνημμένο και έχει να κάνει αποκλειστικά με το πώς το XMP περιγράφει τον εαυτό του. Αυτή είναι η παγίδα που κρύβεται πίσω από έναν πράσινο έλεγχο container
Οι τέσσερις ιδιότητες που κάνουν το αρχείο να αποτύχει
Ένα τιμολόγιο Factur-X γράφει τέσσερις προσαρμοσμένες ιδιότητες στο πακέτο XMP του, ώστε το λογισμικό που ακολουθεί να μπορεί να διαβάσει το προφίλ του τιμολογίου χωρίς να αναλύσει το ενσωματωμένο XML. Βρίσκονται στον χώρο ονομάτων Factur-X κάτω από το πρόθεμα fx: fx:DocumentFileName, fx:DocumentType, fx:Version και fx:ConformanceLevel. Είναι ακριβώς τα μεταδεδομένα που χρειάζεται ένας αναγνώστης για να γνωρίζει ότι αυτό το PDF μεταφέρει ένα τιμολόγιο EN 16931 με όνομα factur-x.xml στην έκδοση 1.0
Καμία από αυτές τις τέσσερις ιδιότητες δεν ανήκει σε κάποιο σχήμα XMP που προκαθορίζει το PDF/A. Τα σχήματα Dublin Core, XMP Basic, PDF και αναγνώρισης PDF/A είναι γνωστά σε έναν συμμορφούμενο αναγνώστη, αλλά το fx: δεν είναι. Όταν το veraPDF διατρέχει το XMP και φτάνει σε μια ιδιότητα της οποίας τον χώρο ονομάτων δεν αναγνωρίζει, αναζητά μια δήλωση που θα του έλεγε τι σημαίνει η ιδιότητα. Αν αυτή η δήλωση απουσιάζει, αναφέρει αποτυχία ως προς τη ρήτρα 6.6.2.3.1 του ISO 19005-3, η οποία απαιτεί κάθε ιδιότητα που δεν προέρχεται από προκαθορισμένο σχήμα να περιγράφεται σε ένα σχήμα επέκτασης PDF/A. Τέσσερις αδήλωτες ιδιότητες, τέσσερις τρόποι να απορριφθεί το αρχείο, και κανένας από αυτούς δεν είναι ορατός σε έναν έλεγχο container
Γιατί το PDF/A απορρίπτει μια γυμνή προσαρμοσμένη ιδιότητα
Ο κανόνας μοιάζει σχολαστικός μέχρι να θυμηθείτε σε τι χρησιμεύει το PDF/A. Η μορφή υπάρχει ώστε ένα αρχείο να μπορεί να ανοιχτεί και να γίνει κατανοητό δεκαετίες από τώρα, από λογισμικό στο οποίο κανείς δεν εξήγησε ποτέ τις συμβάσεις του 2026. Ένας συμμορφούμενος αναγνώστης αναμένεται να βγάλει νόημα από το έγγραφο με βάση μόνο το ίδιο το έγγραφο, χωρίς κανένα εξωτερικό μητρώο να συμβουλευτεί
Τα προσαρμοσμένα μεταδεδομένα αθετούν αυτή την υπόσχεση, εκτός αν το αρχείο μεταφέρει τη δική του περιγραφή. Με μια γυμνή ιδιότητα fx:ConformanceLevel, ένας μελλοντικός αναγνώστης δεν μπορεί να γνωρίζει σε ποιο URI χώρου ονομάτων δεσμεύεται το πρόθεμα fx, αν η τιμή είναι κείμενο, ημερομηνία ή ακέραιος, ή αν η ιδιότητα περιγράφει το ίδιο το έγγραφο ή κάποιον εξωτερικό πόρο. Ο μηχανισμός σχήματος επέκτασης PDF/A κλείνει αυτό το κενό. Επιτρέπει στο αρχείο να δηλώσει, σε μια σταθερή δομή XMP, τον χώρο ονομάτων, το πρόθεμα και, για κάθε ιδιότητα, έναν τύπο τιμής και μια κατηγορία internal ή external. Μόλις υπάρξει αυτή η δήλωση, η ιδιότητα γίνεται αυτοπεριγραφική και η ρήτρα 6.6.2.3.1 ικανοποιείται. Χωρίς αυτήν, ο επικυρωτής δεν έχει άλλη επιλογή από το να θεωρήσει την ιδιότητα ακατανόητη και να απορρίψει το αρχείο. Η διάκριση της κατηγορίας έχει σημασία εδώ: ιδιότητες τιμολογίου όπως αυτές περιγράφουν δεδομένα που προέρχονται έξω από τον επεξεργαστή PDF, οπότε δηλώνονται ως external και όχι internal
Τι περιέχει η δήλωση του σχήματος επέκτασης
Η δήλωση είναι ένα rdf:Description μέσα στο πακέτο XMP που χρησιμοποιεί τους τρεις χώρους ονομάτων που ορίζει η AIIM: pdfaExtension, pdfaSchema και pdfaProperty. Μέσα σε ένα bag pdfaExtension:schemas βρίσκεται μία καταχώριση σχήματος που ονομάζει το σχήμα Factur-X, δίνει τα pdfaSchema:namespaceURI και pdfaSchema:prefix του, και έπειτα απαριθμεί τις τέσσερις ιδιότητες σε μια ακολουθία pdfaSchema:property. Κάθε ιδιότητα φέρει ένα όνομα, ένα pdfaProperty:valueType με τιμή Text και ένα pdfaProperty:category με τιμή external. Η ενδεικτική σήμανση παρακάτω δείχνει τη μορφή αυτού του μπλοκ
<rdf:Description rdf:about=""
xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">
<pdfaExtension:schemas>
<rdf:Bag>
<rdf:li rdf:parseType="Resource">
<pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>
<pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</pdfaSchema:namespaceURI>
<pdfaSchema:prefix>fx</pdfaSchema:prefix>
<pdfaSchema:property>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<pdfaProperty:name>DocumentFileName</pdfaProperty:name>
<pdfaProperty:valueType>Text</pdfaProperty:valueType>
<pdfaProperty:category>external</pdfaProperty:category>
<pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>
</rdf:li>
<!-- DocumentType, Version, ConformanceLevel declared the same way -->
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
</rdf:Description>
Το URI χώρου ονομάτων και το πρόθεμα δεν είναι σταθερές συμβολοσειρές. Ακολουθούν το προφίλ. Ένα έγγραφο Factur-X χρησιμοποιεί το urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# με το πρόθεμα fx, ενώ ένα αρχείο ZUGFeRD 2.0 που επιλέγεται μέσω του zugferd-invoice.xml αντιστοιχεί σε διαφορετικό URI κάτω από το δικό του όνομα σχήματος. Το σχήμα επέκτασης πρέπει να δηλώνει το ίδιο URI χώρου ονομάτων που χρησιμοποιεί πράγματι το μπλοκ ιδιοτήτων, αλλιώς ο επικυρωτής και πάλι δεν μπορεί να συνδέσει τα δύο. Η PDF Library for Delphi παράγει και τις δύο τιμές από το όνομα αρχείου και την έκδοση που περνάτε, οπότε η δήλωση και το μπλοκ ιδιοτήτων συμφωνούν πάντα
Πώς η βοηθητική μέθοδος γράφει και τα δύο μισά μαζί
Στην PDF Library for Delphi δεν συναρμολογείτε αυτό το XML με το χέρι. Θέτετε το έγγραφο σε λειτουργία PDF/A-3 και καλείτε μία μέθοδο. Το πρώτο που πρέπει να διευθετηθεί είναι η σημαία συμμόρφωσης, επειδή το Factur-X απαιτεί PDF/A-3. Η κλήση SetPDFAMode(7) επιλέγει το επίπεδο PDF/A-3u, το οποίο θέτει το pdfaid:part σε 3 και το pdfaid:conformance σε U στο σχήμα αναγνώρισης. Το πακέτο XMP φέρει τώρα το σωστό part και conformance πριν προστεθούν οποιαδήποτε μεταδεδομένα τιμολογίου
var
FileID: Integer;
begin
PDF.SetPDFAMode(7); // PDF/A-3u: pdfaid:part=3, conformance=U
PDF.NewDocument;
// σχεδιάστε εδώ τη σελίδα τιμολογίου για ανθρώπινη ανάγνωση
FileID := PDF.AddFacturXAssociatedFileFromString(
InvoiceXML, // ακατέργαστα bytes XML σε UTF-8
'EN16931', // ConformanceLevel
'factur-x.xml', // όνομα ενσωματωμένου αρχείου
'Factur-X invoice XML', // κείμενο /Desc
'Alternative', // /AFRelationship
'1.0', // έκδοση προφίλ
''); // προαιρετικός κωδικός χώρας
if FileID = 0 then
Exit; // όχι PDF/A-3, ή αναντιστοιχία XML/προφίλ
PDF.SaveToFile('factur-x.pdf');
end;
Μία μόνο κλήση της AddFacturXAssociatedFileFromString κάνει τη δουλειά που έλειπε από το αρχείο που αποτύγχανε. Ενσωματώνει το XML ως συσχετισμένο αρχείο PDF/A-3 με τη σχέση που ονομάσατε, και καταγράφει τις τέσσερις ιδιότητες fx μαζί με το όνομα σχήματος, το URI χώρου ονομάτων και το πρόθεμα για το επιλεγμένο προφίλ. Όταν το έγγραφο αποθηκεύεται, ένα εσωτερικό βήμα με το όνομα ApplyFacturXMetadata εισάγει τόσο το μπλοκ ιδιοτήτων όσο και την αντίστοιχη δήλωση pdfaExtension:schemas στο πακέτο XMP, οπότε οι προσαρμοσμένες ιδιότητες φτάνουν ήδη περιγραμμένες. Η μέθοδος επιστρέφει 0 αν το έγγραφο δεν βρίσκεται σε λειτουργία PDF/A-3 ή αν το XML δεν ταιριάζει με το δηλωμένο προφίλ, που είναι η ίδια δικλείδα που εμποδίζει εξαρχής ένα κακοσχηματισμένο τιμολόγιο να φτάσει στο αρχείο
Το τυφλό σημείο που ο έλεγχος container δεν μπορεί να δει
Αυτό είναι το σημείο που πρέπει να ονομαστεί καθαρά, γιατί είναι ο λόγος που το σφάλμα κρύβεται. Η ValidateFacturXInvoice ελέγχει το container. Επιβεβαιώνει ότι ο catalog έχει καταχώριση /AF, ότι υπάρχει το δέντρο ονομάτων EmbeddedFiles, ότι υπάρχει το XML του τιμολογίου, ότι το όνομα του ενσωματωμένου αρχείου ταιριάζει με το προφίλ, ότι το αναγνωριστικό οδηγίας στο XML συμφωνεί με το επίπεδο συμμόρφωσης, και ότι το /AFRelationship είναι από αυτά που επιτρέπει το PDF/A-3. Πρόκειται για πραγματικούς ελέγχους που πιάνουν πραγματικά ελαττώματα. Η GetFacturXValidationIssues τα αναφέρει ονομαστικά, με αναγνωριστικά όπως MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship και InvalidFileNameProfile
Αυτό που δεν ελέγχει είναι αν το σχήμα επέκτασης XMP υπάρχει και είναι σωστό. Ένα αρχείο του οποίου το container είναι άψογο αλλά οι ιδιότητες fx είναι αδήλωτες περνά κάθε έλεγχο ζητημάτων και επιστρέφει 1, επειδή τίποτα σε αυτή τη λίστα δεν επιθεωρεί το μπλοκ pdfaExtension:schemas. Ακριβώς γι' αυτό ένα τιμολόγιο χτισμένο με το χέρι, ή ένα που παρήχθη από μια αλυσίδα επεξεργασίας η οποία έγραψε το μπλοκ ιδιοτήτων χωρίς τη δήλωση, μπορεί να περάσει αβίαστα τον ενσωματωμένο επικυρωτή και παρ' όλα αυτά να αποτύχει στο veraPDF ως προς τη ρήτρα 6.6.2.3.1. Ο επικυρωτής container και ο επικυρωτής μεταδεδομένων PDF/A απαντούν σε διαφορετικές ερωτήσεις, και μόνο ο πλήρης ελεγκτής PDF/A απαντά στη δεύτερη
Ανάγνωση των ζητημάτων ώστε να ξέρετε ποιο επίπεδο χάλασε
Επειδή τα δύο επίπεδα αποτυγχάνουν ανεξάρτητα, η σωστή διαγνωστική συνήθεια είναι να διαβάζετε πρώτα τα ζητήματα του container και να αντιμετωπίζετε ένα καθαρό αποτέλεσμα ως δήλωση μόνο για το container, ποτέ για τα μεταδεδομένα PDF/A. Εκτελέστε την ενσωματωμένη επικύρωση, συλλέξτε τη λίστα ζητημάτων και ενεργήστε βάσει αυτής πριν καταφύγετε σε εξωτερικό εργαλείο
var
Issues: WideString;
begin
if PDF.ValidateFacturXInvoice = 0 then
begin
Issues := PDF.GetFacturXValidationIssues('|');
// αναγνωριστικά επιπέδου container, για παράδειγμα:
// MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
// ConformanceGuidelineMismatch, InvalidAFRelationship
WriteLn('Container issues: ', Issues);
end
else
WriteLn('Container OK; verify XMP extension schema with a PDF/A checker.');
end;
Όταν αυτή η κλήση επιστρέφει ένα όνομα ζητήματος, το σφάλμα βρίσκεται στο container και το μήνυμα σάς λέει σε ποιο μέρος. Όταν επιστρέφει καθαρό αποτέλεσμα και το veraPDF εξακολουθεί να απορρίπτει το αρχείο, το σφάλμα είναι σχεδόν πάντα το σχήμα επέκτασης XMP, και η διόρθωση είναι να αφήσετε την AddFacturXAssociatedFileFromString να γράψει τα μεταδεδομένα αντί να κατασκευάζετε μόνοι σας το μπλοκ ιδιοτήτων. Το να κρατάτε τις δύο ερωτήσεις χωριστές στο μυαλό σας είναι αυτό που μετατρέπει μια αινιγματική απόρριψη σε διάγνωση μίας γραμμής: τα προβλήματα του container αναδύονται μέσω της λίστας ζητημάτων, τα προβλήματα δήλωσης σχήματος αναδύονται μόνο μέσω ενός επικυρωτή PDF/A, και η σύγχυση των δύο είναι αυτό που αφήνει το σφάλμα να κρύβεται
Η ευρύτερη εικόνα συμμόρφωσης PDF/A και PDF/UA, μαζί με το πώς να εκτελέσετε ένα πέρασμα preflight πριν ένα αρχείο φύγει από το build σας, καλύπτεται στον οδηγό preflight για PDF/A και PDF/UA. Αν το τιμολόγιό σας πρέπει επιπλέον να είναι προσβάσιμο, το δέντρο δομής στο οποίο βασίζονται το PDF/A-3a και το tagged PDF είναι το θέμα του άρθρου για την προσβασιμότητα tagged PDF. Ο χειρισμός σχήματος επέκτασης που περιγράφεται εδώ διατίθεται ως μέρος της βιβλιοθήκης PDF για Delphi, της PDF Library for Delphi, μαζί με την υποστήριξη προφίλ Factur-X, ZUGFeRD και XRechnung που τεκμηριώνεται σε όλο αυτό το blog