Tekninen artikkeli

PDF/A-3-laajennusskeemat Factur-X XMP:lle Delphissä

Olet rakentanut Factur-X-laskun ja jokainen säiliötarkistus menee läpi. Catalog-objekti sisältää /AF-taulukon, EmbeddedFiles-nimipuu ratkeaa oikeaan tiedostomäärittelyyn, upotetulla factur-x.xml-tiedostolla on oikea /AFRelationship-arvo Alternative, ja sisäänrakennettu ValidateFacturXInvoice palauttaa arvon 1. Sitten ajat saman tiedoston veraPDF:n läpi, jota veroportaalit käyttävät viitetarkistimena, ja se tuomitsee koko asiakirjan epäkelvoksi PDF/A-3:ksi. Rakenne on oikea. Metatieto on ongelma, ja tämä virhe on yksi helpoimmista missata koko verkkolaskutyönkulussa

Syy on syytä ymmärtää kokonaisuudessaan, koska se selittää sellaisten PDF/A-virheiden luokan, jolla ei ole mitään tekemistä näkyvän sivun tai liitteen kanssa, vaan se liittyy täysin siihen, miten XMP kuvailee itseään. Tämä on ansa, joka piileskelee vihreän säiliötarkistuksen takana

Neljä ominaisuutta, jotka kaatavat tiedoston

Factur-X-lasku kirjoittaa neljä mukautettua ominaisuutta (custom property) XMP-pakettiinsa, jotta myöhemmän vaiheen ohjelmistot (downstream software) voivat lukea laskuprofiilin jäsentämättä upotettua XML:ää. Ne elävät Factur-X-nimiavaruudessa etuliitteen fx alla: fx:DocumentFileName, fx:DocumentType, fx:Version ja fx:ConformanceLevel. Ne ovat täsmälleen sitä metatietoa, jota lukija tarvitsee tietääkseen, että tämä PDF kantaa mukanaan EN 16931 -laskua nimeltä factur-x.xml versiossa 1.0

Yksikään noista neljästä ominaisuudesta ei kuulu mihinkään PDF/A:n ennalta määrittelemään XMP-skeemaan. Vaatimustenmukainen lukija tuntee Dublin Coren, XMP Basicin, PDF:n ja PDF/A:n tunnistusskeemat, mutta fx: ei ole niille tuttu. Kun veraPDF käy läpi XMP:tä ja saavuttaa ominaisuuden, jonka nimiavaruutta se ei tunnista, se etsii julistusta, joka kertoisi sille, mitä ominaisuus tarkoittaa. Jos tämä julistus puuttuu, se raportoi virheestä ISO 19005-3 -lauseketta 6.6.2.3.1 vastaan, joka edellyttää, että jokainen ominaisuus, jota ei ole vedetty ennalta määritellystä skeemasta, on kuvailtava PDF/A-laajennusskeemassa (extension schema). Neljä julistamatonta ominaisuutta tarkoittaa neljää tapaa, joilla tiedosto voidaan hylätä, eikä yksikään niistä näy säiliötarkistuksessa

Miksi PDF/A kieltää paljaan mukautetun ominaisuuden

Sääntö vaikuttaa pikkutarkalta, kunnes muistat, mihin PDF/A on tarkoitettu. Formaatti on olemassa siksi, että tiedosto voidaan avata ja ymmärtää vuosikymmenten päästä ohjelmistolla, jolle ei koskaan kerrottu vuoden 2026 käytännöistä. Vaatimustenmukaisen lukijan odotetaan ymmärtävän asiakirjan pelkästään itse asiakirjasta käsin, ilman että sen tarvitsee konsultoida mitään ulkoista rekisteriä

Mukautettu metatieto rikkoo tämän lupauksen, ellei tiedosto kanna mukanaan omaa kuvaustaan. Jos annetaan paljas fx:ConformanceLevel-ominaisuus, tulevaisuuden lukija ei voi tietää, mihin nimiavaruuden URI:in fx-etuliite sitoutuu, onko arvo tekstiä, päivämäärä vai kokonaisluku, tai kuvaileeko ominaisuus itse asiakirjaa vai jotain ulkoista resurssia. PDF/A:n laajennusskeemamekanismi sulkee tämän aukon. Se antaa tiedoston julistaa kiinteässä XMP-rakenteessa nimiavaruuden, etuliitteen ja jokaiselle ominaisuudelle arvotyypin ja kategorian, joka on joko internal tai external. Kun tämä julistus on paikallaan, ominaisuus on itseään kuvaileva ja lauseke 6.6.2.3.1 on täytetty. Ilman sitä validaattorilla ei ole muuta vaihtoehtoa kuin käsitellä ominaisuutta käsittämättömänä ja hylätä tiedosto. Kategoriaerottelulla on tässä merkitystä: tällaiset laskuominaisuudet kuvaavat tietoa, joka tulee PDF-prosessorin ulkopuolelta, joten ne julistetaan arvoksi external eikä internal

Mitä laajennusskeemajulistus sisältää

Julistus on rdf:Description XMP-paketissa, joka käyttää kolmea AIIM:n määrittelemää nimiavaruutta: pdfaExtension, pdfaSchema ja pdfaProperty. pdfaExtension:schemas-bagin sisällä istuu yksi skeemamerkintä, joka nimeää Factur-X-skeeman, antaa sen pdfaSchema:namespaceURI:n ja pdfaSchema:prefix:n, ja listaa sitten neljä ominaisuutta pdfaSchema:property-sekvenssissä. Jokainen ominaisuus kantaa mukanaan nimeä, pdfaProperty:valueType-tyyppiä Text, ja pdfaProperty:category-kategoriaa external. Alla oleva havainnollistava merkkaus näyttää tuon lohkon muodon

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

Nimiavaruuden URI ja etuliite eivät ole kiinteitä merkkijonoja. Ne seuraavat profiilia. Factur-X-asiakirja käyttää arvoa urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# fx-etuliitteen kanssa, kun taas zugferd-invoice.xml:n kautta valittu ZUGFeRD 2.0 -tiedosto ratkeaa eri URI:in oman skeemanimensä alla. Laajennusskeeman on julistettava sama nimiavaruuden URI, jota ominaisuuslohko (property block) todellisuudessa käyttää, tai validaattori ei edelleenkään voi yhdistää niitä toisiinsa. PDFlibPas johtaa molemmat arvot antamastasi tiedoston nimestä ja versiosta, joten julistus ja ominaisuuslohko ovat aina yhtä mieltä

Kuinka apufunktio kirjoittaa molemmat puoliskot yhdessä

PDFlibPas-kirjastossa sinun ei tarvitse koota tuota XML:ää käsin. Asetat asiakirjan PDF/A-3-tilaan ja kutsut yhtä metodia. Ensimmäinen selvitettävä asia on vaatimustenmukaisuuslippu, koska Factur-X vaatii PDF/A-3:n. Kutsun SetPDFAMode(7) tekeminen valitsee PDF/A-3u-tason, joka asettaa tunnistusskeemassa pdfaid:part-arvoksi 3 ja pdfaid:conformance-arvoksi U. XMP-paketti kantaa nyt oikeaa osaa ja vaatimustenmukaisuutta jo ennen kuin yhtäkään laskun metatietoa lisätään

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;

Yksittäinen kutsu AddFacturXAssociatedFileFromString-metodille tekee työn, joka epäonnistuvasta tiedostosta puuttui. Se upottaa XML:n PDF/A-3-liitetiedostona nimeämälläsi suhteella (relationship), ja se tallentaa neljä fx-ominaisuutta yhdessä valitun profiilin skeemanimen, nimiavaruuden URI:n ja etuliitteen kanssa. Kun asiakirja tallennetaan, sisäinen vaihe nimeltä ApplyFacturXMetadata injektoi sekä ominaisuuslohkon että vastaavan pdfaExtension:schemas-julistuksen XMP-pakettiin, joten mukautetut ominaisuudet saapuvat jo valmiiksi kuvailtuina. Metodi palauttaa arvon 0, jos asiakirja ei ole PDF/A-3-tilassa tai jos XML ei vastaa julistettua profiilia, mikä on sama suojamekanismi, joka estää epämuodostunutta laskua ylipäänsä päätymästä tiedostoon

Sokea piste, jota säiliötarkistus ei näe

Tämä on se osa, joka on syytä sanoa suoraan, koska se on syy sille, miksi bugi piileskelee. ValidateFacturXInvoice tarkistaa säiliön. Se varmistaa, että Catalogilla on /AF-merkintä, EmbeddedFiles-nimipuu on olemassa, laskun XML löytyy, upotetun tiedoston nimi vastaa profiilia, XML:n ohjeistus-ID (guideline ID) on yhtä mieltä vaatimustenmukaisuustason kanssa ja /AFRelationship on sellainen, jonka PDF/A-3 sallii. Nämä ovat todellisia tarkistuksia ja ne nappaavat todellisia vikoja. GetFacturXValidationIssues raportoi ne nimeltä, tunnisteilla kuten MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship ja InvalidFileNameProfile

Mitä se ei tarkista on se, onko XMP-laajennusskeema olemassa ja oikein. Tiedosto, jonka säiliö on virheetön, mutta jonka fx-ominaisuuksia ei ole julistettu, läpäisee jokaisen vikatarkistuksen ja palauttaa arvon 1, koska mikään tuolla listalla ei tutki pdfaExtension:schemas-lohkoa. Juuri tästä syystä käsin rakennettu lasku – tai sellainen lasku, jonka on tuottanut ominaisuuslohkon ilman julistusta kirjoittava liukuhihna – voi purjehtia sisäänrakennetun validaattorin läpi ja silti epäonnistua veraPDF:ssä lausekkeen 6.6.2.3.1 osalta. Säiliövalidaattori ja PDF/A-metatietovalidaattori vastaavat eri kysymyksiin, ja vain täysi PDF/A-tarkistin vastaa jälkimmäiseen

Vikojen lukeminen, jotta tiedät mikä kerros hajosi

Koska nämä kaksi kerrosta epäonnistuvat toisistaan riippumatta, oikea diagnosointitapa on lukea säiliön viat ensin ja käsitellä puhdasta tulosta lausuntona vain säiliöstä, ei koskaan PDF/A-metatiedosta. Aja sisäänrakennettu validointi, kerää vikalista ja toimi sen perusteella, ennen kuin kurotat kohti ulkoista työkalua

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;

Kun tuo kutsu palauttaa vian nimen, vika on säiliössä ja viesti kertoo sinulle, missä osassa. Kun se palaa puhtaana ja veraPDF silti hylkää tiedoston, vika on lähes aina XMP-laajennusskeemassa, ja korjaus on antaa AddFacturXAssociatedFileFromString-kutsun kirjoittaa metatiedot sen sijaan, että rakentaisit ominaisuuslohkon itse. Näiden kahden kysymyksen pitäminen erillään omassa mielessäsi on se, mikä muuttaa hämmentävän hylkäyksen yksiriviseksi diagnoosiksi: säiliöongelmat nousevat pintaan vikalistan kautta, skeemajulistusongelmat nousevat pintaan vain PDF/A-validaattorin kautta, ja näiden kahden sekoittaminen keskenään on se, mikä antaa bugin piiloutua

Laajempi PDF/A- ja PDF/UA-vaatimustenmukaisuuden kokonaiskuva, mukaan lukien se, kuinka ajaa preflight-läpivienti ennen kuin tiedosto lähtee koontiversiostasi, on katettu artikkelissa läpikäyntimme PDF/A:n ja PDF/UA:n preflight-tarkistuksista Delphissä. Jos laskusi täytyy olla myös saavutettava, se rakenne (structure tree), josta PDF/A-3a ja tagattu PDF riippuvat, on aiheena artikkelissa tagatun PDF:n saavutettavuus. Tässä kuvailtu laajennusskeemojen käsittely toimitetaan osana PDFlibPas Delphi PDF -kirjastoa niiden Factur-X-, ZUGFeRD- ja XRechnung-profiilitukien rinnalla, joita on dokumentoitu pitkin tätä blogia