Technical Article

PDF/A-3 plėtinių schemos Factur-X XMP metaduomenims Delphi aplinkoje

Jūs sukūrėte Factur-X sąskaitą faktūrą, ir kiekvienas konteinerio patikrinimas praeina sėkmingai. Kataloge yra /AF masyvas, EmbeddedFiles vardų medis (name tree) nukreipia į teisingą failo specifikaciją, įterptas factur-x.xml turi teisingą /AFRelationship reikšmę Alternative, o integruota ValidateFacturXInvoice funkcija grąžina 1. Tada tą patį failą paleidžiate per veraPDF – etaloninį tikrintuvą (reference checker), kurį naudoja mokesčių portalai, – ir jis nusprendžia, kad visas dokumentas nėra galiojantis PDF/A-3. Struktūra yra teisinga. Problema yra metaduomenys, ir šią klaidą praleisti yra bene lengviausia visoje elektroninių sąskaitų faktūrų darbo eigoje

Priežastį verta suprasti iki galo, nes ji paaiškina PDF/A defektų klasę, kuri neturi nieko bendro su matomu puslapiu ar priedu, bet yra visiškai susijusi su tuo, kaip XMP aprašo save. Tai yra spąstai, kurie slepiasi už žalio (sėkmingo) konteinerio patikrinimo

Keturios savybės, dėl kurių failas atmetamas

Factur-X sąskaita faktūra įrašo keturias pasirinktines (custom) savybes į savo XMP paketą, kad tolesnė programinė įranga galėtų nuskaityti sąskaitos faktūros profilį neanalizuodama (without parsing) įterpto XML. Jos gyvena Factur-X vardų srityje (namespace) su fx prefiksu: fx:DocumentFileName, fx:DocumentType, fx:Version ir fx:ConformanceLevel. Tai yra būtent tie metaduomenys, kurių skaitytuvui reikia, kad žinotų, jog šis PDF neša EN 16931 sąskaitą faktūrą, pavadintą factur-x.xml, 1.0 versijos

Nė viena iš tų keturių savybių nėra dalis jokios XMP schemos, kurią iš anksto apibrėžia (predefines) PDF/A. Dublin Core, XMP Basic, PDF ir PDF/A identifikavimo schemos yra žinomos atitiktį palaikančiam skaitytuvui (conforming reader), tačiau fx: tokia nėra. Kai veraPDF eina per XMP ir pasiekia savybę, kurios vardų srities jis neatpažįsta, jis ieško deklaracijos, kuri pasakytų, ką ta savybė reiškia. Jei tokios deklaracijos nėra, jis praneša apie klaidą (failure) pagal ISO 19005-3 6.6.2.3.1 sąlygą (clause), kuri reikalauja, kad kiekviena savybė, neimta iš iš anksto apibrėžtos schemos, būtų aprašyta PDF/A plėtinio schemoje (extension schema). Keturios nedeklaruotos savybės – keturi būdai atsirasti failo atmetimui, ir nė vienas iš jų nėra matomas konteinerio patikrinimui

Kodėl PDF/A atsisako plikos (bare) pasirinktinės savybės

Ši taisyklė atrodo pedantiška, kol neprisimeni, kam skirtas PDF/A. Šis formatas egzistuoja tam, kad failą būtų galima atidaryti ir suprasti po kelių dešimtmečių, naudojant programinę įrangą, kuriai niekada nebuvo papasakota apie 2026 m. konvencijas. Tikimasi, kad atitiktį palaikantis skaitytuvas supras dokumentą remdamasis vien tik dokumentu, nesikreipdamas į jokį išorinį registrą

Pasirinktiniai metaduomenys laužo šį pažadą, nebent failas neša savo paties aprašymą. Turėdamas pliką (bare) fx:ConformanceLevel savybę, ateities skaitytuvas negali žinoti, prie kokio vardų srities URI prisiriša fx prefiksas, ar ta reikšmė yra tekstas, data, ar sveikasis skaičius, ir ar savybė aprašo patį dokumentą, ar kokį nors išorinį resursą. PDF/A plėtinių schemų mechanizmas užpildo šią spragą. Jis leidžia failui deklaruoti (fiksuotoje XMP struktūroje) vardų sritį, prefiksą ir kiekvienai savybei – reikšmės tipą bei internal arba external kategoriją. Kai ši deklaracija yra, savybė tampa save aprašančia (self-describing), ir 6.6.2.3.1 sąlyga tenkinama. Be jos validatorius neturi kito pasirinkimo, kaip tik traktuoti savybę kaip nesuprantamą ir atmesti failą. Kategorijų atskyrimas čia yra svarbus: tokios sąskaitų faktūrų savybės aprašo duomenis, gaunamus iš už PDF procesoriaus ribų, todėl jos deklaruojamos kaip external, o ne internal

Ką apima plėtinio schemos deklaracija

Deklaracija yra rdf:Description blokas XMP pakete, kuris naudoja tris AIIM apibrėžtas vardų sritis: pdfaExtension, pdfaSchema ir pdfaProperty. pdfaExtension:schemas maišo (bag) viduje yra vienas schemos įrašas, kuris įvardija Factur-X schemą, nurodo jos pdfaSchema:namespaceURI ir pdfaSchema:prefix, o tada išvardija keturias savybes pdfaSchema:property sekoje. Kiekviena savybė neša pavadinimą, pdfaProperty:valueType lygų Text ir pdfaProperty:category lygų external. Toliau pateiktas iliustracinis ženklinimas (markup) rodo tokio bloko pavidalą

<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>

Vardų srities URI ir prefiksas nėra fiksuotos eilutės. Jos seka pagal profilį. Factur-X dokumentas naudoja urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# su fx prefiksu, tuo tarpu ZUGFeRD 2.0 failas, pasirinktas per zugferd-invoice.xml, nukreipia į kitą URI po savo paties schemos pavadinimu. Plėtinio schema turi deklaruoti tą patį vardų srities URI, kurį faktiškai naudoja savybių blokas, kitaip validatorius vis tiek negalės jų susieti. PDFlibPas abu šiuos dydžius išveda iš jūsų perduodamo failo pavadinimo ir versijos, todėl deklaracija ir savybių blokas visada sutampa

Kaip pagalbininkas (helper) įrašo abi puses kartu

PDFlibPas bibliotekoje jūs nesurenkate šio XML rankiniu būdu. Jūs pervedate dokumentą į PDF/A-3 režimą ir iškviečiate vieną metodą. Pirmasis dalykas, kurį reikia sutvarkyti, yra atitikties žyma (conformance flag), nes Factur-X reikalauja PDF/A-3. Iškvietus SetPDFAMode(7) pasirenkamas PDF/A-3u lygis, kuris nustato identifikavimo schemoje pdfaid:part į 3 ir pdfaid:conformance į U. Dabar XMP paketas neša teisingą dalį ir atitiktį dar prieš pridedant bet kokius sąskaitos faktūros metaduomenis

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;

Vienas AddFacturXAssociatedFileFromString iškvietimas atlieka darbą, kurio trūko atmestam failui. Jis įterpia XML kaip PDF/A-3 susietą failą su jūsų nurodytu ryšiu (relationship) ir įrašo keturias fx savybes kartu su pasirinkto profilio schemos pavadinimu, vardų srities URI ir prefiksu. Išsaugant dokumentą, vidinis žingsnis, pavadintas ApplyFacturXMetadata, įšvirkščia (injects) ir savybių bloką, ir atitinkamą pdfaExtension:schemas deklaraciją į XMP paketą, todėl pasirinktinės savybės atkeliauja jau aprašytos. Metodas grąžina 0, jei dokumentas nėra PDF/A-3 režime arba jei XML neatitinka deklaruoto profilio; tai yra ta pati apsauga (guard), kuri sustabdo neteisingai suformuotos sąskaitos faktūros patekimą į failą

Akloji zona, kurios konteinerio patikrinimas negali pamatyti

Šią dalį reikia įvardyti aiškiai, nes būtent dėl jos slepiasi klaida (bug). ValidateFacturXInvoice patikrina konteinerį. Jis patvirtina, kad kataloge yra /AF įrašas, EmbeddedFiles vardų medis egzistuoja, sąskaitos faktūros XML yra, įterpto failo pavadinimas atitinka profilį, XML esantis gairių (guideline) ID atitinka atitikties lygį, o /AFRelationship yra toks, kokį leidžia PDF/A-3. Tai yra tikri patikrinimai ir jie sugauna tikrus defektus. GetFacturXValidationIssues praneša apie juos pagal pavadinimą, naudodamas tokius identifikatorius kaip MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship ir InvalidFileNameProfile

Ko jis netikrina, tai ar XMP plėtinio schema yra ir ar ji teisinga. Failas, kurio konteineris yra nepriekaištingas, bet kurio fx savybės nėra deklaruotos, sėkmingai praeina visus klaidų patikrinimus ir grąžina 1, nes niekas iš to sąrašo netikrina pdfaExtension:schemas bloko. Būtent todėl rankiniu būdu sukurta sąskaita faktūra arba pagaminta tokio proceso (pipeline), kuris įrašė savybių bloką be deklaracijos, gali lengvai praplaukti pro integruotą validatorių ir vis tiek patirti nesėkmę veraPDF pagal 6.6.2.3.1 sąlygą. Konteinerio validatorius ir PDF/A metaduomenų validatorius atsako į skirtingus klausimus, ir tik pilnas PDF/A tikrintuvas (checker) atsako į antrąjį

Kaip skaityti klaidų (issues) ataskaitas, kad žinotumėte, kuris sluoksnis sugedo

Kadangi šie du sluoksniai lūžta (fail) nepriklausomai vienas nuo kito, teisingas diagnostikos įprotis yra pirmiausia perskaityti konteinerio klaidas (issues) ir traktuoti švarų (clean) rezultatą tik kaip teiginį apie konteinerį, o ne apie PDF/A metaduomenis. Paleiskite integruotą validavimą, surinkite klaidų sąrašą ir atitinkamai reaguokite prieš imdamiesi išorinio įrankio

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;

Kai šis iškvietimas grąžina klaidos pavadinimą, kaltas konteineris, o pranešimas nurodo, kuri jo dalis. Kai jis grąžina švarų rezultatą, bet veraPDF vis tiek atmeta failą, kalta beveik visada yra XMP plėtinio schema, o sprendimas (fix) yra leisti AddFacturXAssociatedFileFromString įrašyti metaduomenis, užuot konstravus savybių bloką pačiam. Šių dviejų klausimų atskyrimas savo galvoje yra tai, kas verčia gluminantį atmetimą vienos eilutės diagnoze: konteinerio problemos iškyla per klaidų sąrašą, schemos deklaracijos problemos iškyla tik per PDF/A validatorių, o šių dviejų dalykų supainiojimas leidžia klaidai pasislėpti

Platesnis PDF/A ir PDF/UA atitikties (conformance) vaizdas, įskaitant tai, kaip paleisti preflight patikrinimą prieš failui paliekant jūsų generavimo sistemą (build), aprašytas PDF/A ir PDF/UA preflight Delphi aplinkoje peržiūroje. Jei jūsų sąskaita faktūra taip pat turi būti prieinama (accessible), struktūros medis, nuo kurio priklauso PDF/A-3a ir žymėtas PDF (tagged PDF), aptariamas žymėto PDF prieinamumo straipsnyje. Čia aprašytas plėtinių schemų tvarkymas pateikiamas kaip PDFlibPas Delphi PDF Library dalis, kartu su Factur-X, ZUGFeRD ir XRechnung profilių palaikymu, dokumentuotu visame šiame tinklaraštyje