Articol Tehnic

Scheme de extensie PDF/A-3 pentru XMP Factur-X în Delphi

Ați construit o factură Factur-X și toate verificările containerului trec. Catalogul conține un array /AF, arborele de nume EmbeddedFiles se rezolvă la specificația corectă de fișier, factur-x.xml încorporat are /AFRelationship corect de Alternative, iar ValidateFacturXInvoice integrat returnează 1. Apoi rulați același fișier prin veraPDF, verificatorul de referință pe care îl folosesc portalurile fiscale, și acesta decide că întregul document nu este un PDF/A-3 valid. Structura este corectă. Metadatele sunt problema, iar eșecul este unul dintre cele mai ușor de ratat din întregul flux de lucru al facturii electronice

Motivul merită înțeles complet, deoarece explică o clasă de defect PDF/A care nu are nimic de-a face cu pagina vizibilă sau atașamentul și totul de-a face cu modul în care XMP se descrie pe sine. Aceasta este capcana care se ascunde în spatele unei verificări verzi de container

Cele patru proprietăți care fac fișierul să eșueze

O factură Factur-X scrie patru proprietăți personalizate în pachetul său XMP astfel încât software-ul din aval să poată citi profilul facturii fără a analiza XML-ul încorporat. Acestea trăiesc în spațiul de nume Factur-X sub prefixul fx: fx:DocumentFileName, fx:DocumentType, fx:Version, și fx:ConformanceLevel. Acestea sunt exact metadatele de care un cititor are nevoie pentru a ști că acest PDF conține o factură EN 16931 numită factur-x.xml la versiunea 1.0

Niciuna dintre acele patru proprietăți nu face parte din nicio schemă XMP pe care PDF/A o predefinește. Schemele Dublin Core, XMP Basic, PDF și de identificare PDF/A sunt cunoscute unui cititor conform, dar fx: nu este. Când veraPDF parcurge XMP-ul și ajunge la o proprietate al cărei spațiu de nume nu îl recunoaște, caută o declarație care să îi spună ce înseamnă proprietatea. Dacă declarația este absentă, raportează un eșec față de clauza 6.6.2.3.1 din ISO 19005-3, care cere ca fiecare proprietate care nu provine dintr-o schemă predefinită să fie descrisă într-o schemă de extensie PDF/A. Patru proprietăți nedeclarate, patru moduri prin care fișierul poate fi respins, și niciunul dintre ele nu este vizibil pentru o verificare de container

De ce PDF/A refuză o proprietate personalizată neînsoțită

Regula pare pedantă până când vă amintiți pentru ce este PDF/A. Formatul există astfel încât un fișier să poată fi deschis și înțeles peste decenii, de software care nu a fost niciodată informat despre convențiile din 2026. Un cititor conform se așteaptă să înțeleagă documentul numai din document, fără niciun registru extern de consultat

Metadatele personalizate rup acea promisiune dacă fișierul nu poartă propria sa descriere. Dat o proprietate fx:ConformanceLevel neînsoțită, un cititor viitor nu poate cunoaște URI-ul spațiului de nume la care se leagă prefixul fx, dacă valoarea este text sau o dată sau un întreg, sau dacă proprietatea descrie documentul însuși sau o resursă externă. Mecanismul schemei de extensie PDF/A închide acel decalaj. Permite fișierului să declare, într-o structură XMP fixă, spațiul de nume, prefixul și pentru fiecare proprietate un tip de valoare și o categorie de internal sau external. Odată ce declarația este prezentă, proprietatea este auto-descriptivă, și clauza 6.6.2.3.1 este satisfăcută. Fără ea, validatorul nu are altă opțiune decât să trateze proprietatea ca de neînțeles și să eșueze fișierul. Distincția de categorie contează aici: proprietățile de factură precum acestea descriu date care provin din afara procesatorului PDF, deci sunt declarate external mai degrabă decât internal

Ce conține declarația schemei de extensie

Declarația este un rdf:Description în pachetul XMP care folosește cele trei spații de nume definite de AIIM: pdfaExtension, pdfaSchema, și pdfaProperty. În interiorul unui bag pdfaExtension:schemas se află o intrare de schemă care numește schema Factur-X, îi dă pdfaSchema:namespaceURI și pdfaSchema:prefix, și apoi listează cele patru proprietăți într-o secvență pdfaSchema:property. Fiecare proprietate poartă un nume, un pdfaProperty:valueType de Text, și un pdfaProperty:category de external. Marcajul ilustrativ de mai jos arată forma acelui bloc

<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-ul spațiului de nume și prefixul nu sunt șiruri fixe. Urmează profilul. Un document Factur-X folosește urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# cu prefixul fx, în timp ce un fișier ZUGFeRD 2.0 selectat prin zugferd-invoice.xml se rezolvă la un URI diferit sub propriul său nume de schemă. Schema de extensie trebuie să declare același URI de spațiu de nume pe care blocul de proprietăți îl folosește efectiv, sau validatorul tot nu poate conecta cele două. PDFlibPas derivă ambele valori din numele fișierului și versiunea pe care le transmiteți, deci declarația și blocul de proprietăți sunt întotdeauna de acord

Cum helper-ul scrie ambele jumătăți împreună

În PDFlibPas nu asamblați acel XML manual. Puneți documentul în modul PDF/A-3 și apelați o singură metodă. Primul lucru de stabilit este indicatorul de conformitate, deoarece Factur-X necesită PDF/A-3. Apelarea SetPDFAMode(7) selectează nivelul PDF/A-3u, care setează pdfaid:part la 3 și pdfaid:conformance la U în schema de identificare. Pachetul XMP poartă acum partea corectă și conformitatea înainte ca orice metadate de factură să fie adăugate

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(7);            // PDF/A-3u: pdfaid:part=3, conformance=U
  PDF.NewDocument;
  // draw the human-readable invoice page here

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // raw UTF-8 XML bytes
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // embedded file name
    'Factur-X invoice XML',      // /Desc text
    'Alternative',               // /AFRelationship
    '1.0',                       // profile version
    '');                         // optional country code
  if FileID = 0 then
    Exit;                        // not PDF/A-3, or XML/profile mismatch

  PDF.SaveToFile('factur-x.pdf');
end;

Un singur apel la AddFacturXAssociatedFileFromString face munca pe care fișierul eșuat o lipsa. Încorporează XML-ul ca fișier asociat PDF/A-3 cu relația pe care ați denumit-o, și înregistrează cele patru proprietăți fx împreună cu numele schemei, URI-ul spațiului de nume și prefixul pentru profilul ales. Când documentul este salvat, un pas intern numit ApplyFacturXMetadata injectează atât blocul de proprietăți, cât și declarația corespunzătoare pdfaExtension:schemas în pachetul XMP, astfel proprietățile personalizate ajung deja descrise. Metoda returnează 0 dacă documentul nu se află în modul PDF/A-3 sau dacă XML-ul nu corespunde profilului declarat, care este aceeași gardă care împiedică o factură malformată să ajungă la fișier în primul rând

Punctul orb pe care verificarea containerului nu îl poate vedea

Aceasta este partea de denumit clar, deoarece este motivul pentru care bug-ul se ascunde. ValidateFacturXInvoice verifică containerul. Confirmă că catalogul are o intrare /AF, că arborele de nume EmbeddedFiles este prezent, că XML-ul facturii există, că numele fișierului încorporat corespunde profilului, că ID-ul ghidului din XML este de acord cu nivelul de conformitate, și că /AFRelationship este unul pe care PDF/A-3 îl permite. Acestea sunt verificări reale și prind defecte reale. GetFacturXValidationIssues le raportează după nume, cu identificatori precum MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship, și InvalidFileNameProfile

Ceea ce nu verifică este dacă schema de extensie XMP este prezentă și corectă. Un fișier al cărui container este impecabil, dar ale cărui proprietăți fx sunt nedeclarate, trece fiecare verificare de problemă și returnează 1, deoarece nimic din acea listă nu inspectează blocul pdfaExtension:schemas. Acesta este exact motivul pentru care o factură construită manual, sau una produsă de un pipeline care a scris blocul de proprietăți fără declarație, poate trece prin validatorul integrat și totuși eșua în veraPDF la clauza 6.6.2.3.1. Validatorul containerului și validatorul de metadate PDF/A răspund la întrebări diferite, și numai verificatorul PDF/A complet răspunde la a doua

Citirea problemelor pentru a ști care strat s-a defectat

Deoarece cele două straturi eșuează independent, obiceiul diagnostic corect este să citiți mai întâi problemele containerului și să tratați un rezultat curat ca o afirmație despre container numai, niciodată despre metadatele PDF/A. Rulați validarea integrată, colectați lista de probleme și acționați conform acesteia înainte de a apela la un instrument extern

var
  Issues: WideString;
begin
  if PDF.ValidateFacturXInvoice = 0 then
  begin
    Issues := PDF.GetFacturXValidationIssues('|');
    // container-level identifiers, for example:
    //   MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
    //   ConformanceGuidelineMismatch, InvalidAFRelationship
    WriteLn('Container issues: ', Issues);
  end
  else
    WriteLn('Container OK; verify XMP extension schema with a PDF/A checker.');
end;

Când acel apel returnează un nume de problemă, defectul este în container și mesajul vă spune care parte. Când returnează curat și veraPDF totuși respinge fișierul, defectul este aproape întotdeauna schema de extensie XMP, iar remedierea este să lăsați AddFacturXAssociatedFileFromString să scrie metadatele în loc să construiți blocul de proprietăți dvs. Păstrarea celor două întrebări separate în propria minte este ceea ce transformă o respingere desconcertantă într-un diagnostic de o linie: problemele containerului apar prin lista de probleme, problemele de declarare a schemei apar numai printr-un validator PDF/A, iar confundarea celor două este ceea ce lasă bug-ul să se ascundă

Imaginea mai largă de conformitate PDF/A și PDF/UA, inclusiv cum să rulați un pas de preflight înainte ca un fișier să părăsească build-ul dvs., este acoperită în ghidul de preflight PDF/A și PDF/UA. Dacă factura dvs. trebuie să fie și accesibilă, arborele de structură de care depind PDF/A-3a și PDF-ul etichetat este subiectul articolului privind accesibilitatea PDF-ului etichetat. Gestionarea schemei de extensie descrisă aici este livrată ca parte din PDFlibPas Delphi PDF Library alături de suportul de profil Factur-X, ZUGFeRD și XRechnung documentat pe acest blog