Olet rakentanut Factur-X-laskun, ja jokainen säilön (container) tarkistus menee läpi. Luettelo (catalog) sisältää /AF-taulukon, EmbeddedFiles-nimipuu (name tree) ratkeaa oikeaan tiedostomääritykseen, upotetulla factur-x.xml:llä on oikea /AFRelationship, joka on Alternative, ja sisäänrakennettu ValidateFacturXInvoice palauttaa arvon 1. Sitten ajat saman tiedoston veraPDF:n, viitetarkistimen, jota veroportaalit käyttävät, läpi, ja se päättelee, ettei koko asiakirja ole kelvollinen PDF/A-3. Rakenne on oikea. Metatieto on ongelma, ja vika on yksi koko sähköisen laskutuksen työnkulun helpoimmista jättää huomaamatta
Syy on syytä ymmärtää kokonaisuudessaan, koska se selittää PDF/A-vikojen luokan, jolla ei ole mitään tekemistä näkyvän sivun tai liitteen kanssa ja kaikkea tekemistä sen kanssa, kuinka XMP kuvailee itseään. Tämä on ansa, joka piiloutuu vihreän säiliötarkistuksen (container check) taakse
Neljä ominaisuutta, jotka hylkäävät tiedoston
Factur-X-lasku kirjoittaa neljä mukautettua ominaisuutta XMP-pakettiinsa, jotta loppupään (downstream) ohjelmisto voi lukea laskun profiilin jäsentämättä upotettua XML:ää. Ne elävät Factur-X-nimiavaruudessa fx-etuliitteen alla: fx:DocumentFileName, fx:DocumentType, fx:Version ja fx:ConformanceLevel. Ne ovat juuri sitä metatietoa, jota lukija tarvitsee tietääkseen, että tämä PDF kantaa EN 16931 -laskua nimeltä factur-x.xml versiossa 1.0
Mikään näistä neljästä ominaisuudesta ei ole osa mitään XMP-skeemaa, jonka PDF/A ennalta määrittelee. Dublin Core, XMP Basic, PDF ja PDF/A -tunnistusskeemat (identification schemas) ovat tuttuja vaatimustenmukaiselle lukijalle, mutta fx: ei ole. Kun veraPDF käy läpi XMP:tä ja saavuttaa ominaisuuden, jonka nimiavaruutta se ei tunnista, se etsii ilmoitusta (declaration), joka kertoisi sille, mitä ominaisuus tarkoittaa. Jos tämä ilmoitus puuttuu, se raportoi virheestä ISO 19005-3 -lausekkeen 6.6.2.3.1 mukaisesti, joka vaatii, että jokainen ominaisuus, jota ei ole vedetty ennalta määritellystä skeemasta, on kuvattava PDF/A-laajennusskeemassa (extension schema). Neljä ilmoittamatonta ominaisuutta, neljä tapaa, joilla tiedosto voidaan hylätä, eikä yksikään niistä näy säilön (container) tarkistukselle
Miksi PDF/A kieltäytyy pelkästä mukautetusta ominaisuudesta
Sääntö näyttää pedanttiselta, kunnes muistat, mitä varten PDF/A on olemassa. Formaatti on olemassa siksi, että tiedosto voidaan avata ja ymmärtää vuosikymmenien kuluttua ohjelmistoilla, joille ei koskaan kerrottu vuoden 2026 käytännöistä (conventions). Vaatimustenmukaisen lukijan odotetaan ymmärtävän asiakirjan yksinomaan asiakirjasta käsin, ilman ulkoista rekisteriä, jota konsultoida
Mukautettu metatieto rikkoo tämän lupauksen, ellei tiedosto sisällä omaa kuvaustaan. Pelkän fx:ConformanceLevel-ominaisuuden perusteella tuleva lukija ei voi tietää nimiavaruuden URI:a, johon fx-etuliite sitoutuu, onko arvo tekstiä, päivämäärä vai kokonaisluku, vai kuvaako ominaisuus itse asiakirjaa vai jotain ulkoista resurssia. PDF/A-laajennusskeemamekanismi (extension schema mechanism) sulkee tämän aukon. Sen avulla tiedosto voi ilmoittaa kiinteässä XMP-rakenteessa nimiavaruuden, etuliitteen ja jokaiselle ominaisuudelle arvotyypin ja kategorian internal (sisäinen) tai external (ulkoinen). Kun tämä ilmoitus on läsnä, ominaisuus kuvailee itse itsensä, ja lauseke 6.6.2.3.1 täyttyy. Ilman sitä validaattorilla ei ole muuta vaihtoehtoa kuin käsitellä ominaisuutta käsittämättömänä ja hylätä tiedosto. Luokkaerottelulla (category distinction) on tässä merkitystä: näiden kaltaiset laskuominaisuudet kuvaavat tietoa, joka tulee PDF-prosessorin ulkopuolelta, joten ne on ilmoitettu luokkana external (ulkoinen) eikä internal (sisäinen)
Mitä laajennusskeeman (extension schema) ilmoitus sisältää
Ilmoitus on rdf:Description XMP-paketissa, joka käyttää kolmea AIIM:n määrittelemää nimiavaruutta: pdfaExtension, pdfaSchema ja pdfaProperty. pdfaExtension:schemas -pussin (bag) sisällä on yksi skeemamerkintä, joka nimeää Factur-X-skeeman, antaa sille pdfaSchema:namespaceURI ja pdfaSchema:prefix, ja listaa sitten neljä ominaisuutta pdfaSchema:property-sekvenssissä. Jokaisella ominaisuudella on nimi, pdfaProperty:valueType-arvona Text ja pdfaProperty:category-kategoriana external. Alla oleva havainnollistava merkintä (markup) näyttää tämän 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 (namespace) URI ja etuliite (prefix) eivät ole kiinteitä merkkijonoja. Ne noudattavat profiilia. Factur-X-asiakirja käyttää urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#-URIa fx-etuliitteellä, kun taas ZUGFeRD 2.0 -tiedosto, joka on valittu zugferd-invoice.xml-tiedoston kautta, ratkeaa eri URI:in oman skeemanimensä alla. Laajennusskeeman (extension schema) on ilmoitettava sama nimiavaruus-URI, jota ominaisuuslohko (property block) todellisuudessa käyttää, tai validaattori ei silti voi yhdistää näitä kahta. PDFlibPas johtaa molemmat arvot välittämästäsi tiedostonimestä ja versiosta, joten ilmoitus (declaration) ja ominaisuuslohko ovat aina yhtä mieltä
Kuinka apuri (helper) kirjoittaa molemmat puolikkaat yhteen
PDFlibPas-kirjastossa sinun ei tarvitse koota tätä XML:ää käsin. Laitat asiakirjan PDF/A-3-tilaan ja kutsut yhtä metodia. Ensimmäinen asia, joka on ratkaistava, on vaatimustenmukaisuuslippu (conformance flag), koska Factur-X vaatii PDF/A-3:n. Kutsulla SetPDFAMode(7) valitaan PDF/A-3u-taso, joka asettaa pdfaid:part arvoon 3 ja pdfaid:conformance arvoon U tunnistusskeemassa (identification schema). XMP-paketti kantaa nyt oikeaa osaa (part) ja vaatimustenmukaisuutta 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;
Yksi kutsu AddFacturXAssociatedFileFromString-metodiin tekee työn, joka hylätyltä tiedostolta puuttui. Se upottaa XML:n PDF/A-3:n liitetyksi tiedostoksi nimeämälläsi suhteella (relationship), ja se tallentaa neljä fx-ominaisuutta yhdessä skeeman nimen, nimiavaruus-URI:n ja etuliitteen (prefix) kanssa valitulle profiilille. Kun asiakirja on tallennettu, sisäinen vaihe nimeltä ApplyFacturXMetadata injektoi sekä ominaisuuslohkon että vastaavan pdfaExtension:schemas -ilmoituksen XMP-pakettiin, joten mukautetut ominaisuudet saapuvat valmiiksi kuvailtuina. Metodi palauttaa 0, jos asiakirja ei ole PDF/A-3-tilassa tai jos XML ei vastaa ilmoitettua profiilia, mikä on sama varotoimi (guard), joka estää väärin muotoiltua laskua päätymästä tiedostoon ylipäätään
Sokea piste, jota säilötarkistus (container check) ei näe
Tämä on se osa, joka on syytä nimetä selvästi, koska se on syy siihen, miksi bugi piiloutuu. ValidateFacturXInvoice tarkistaa säilön (container). Se vahvistaa, että luettelossa (catalog) on /AF-merkintä, EmbeddedFiles-nimipuu (name tree) on läsnä, laskun XML on olemassa, upotetun tiedoston nimi vastaa profiilia, XML:n ohje-ID on yhdenmukainen vaatimustenmukaisuustason (conformance level) kanssa, ja /AFRelationship on sellainen, jonka PDF/A-3 sallii. Ne ovat todellisia tarkistuksia ja ne nappaavat todellisia vikoja. GetFacturXValidationIssues raportoi ne nimellä, tunnisteilla kuten MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship ja InvalidFileNameProfile
Mitä se ei tarkista, on se, onko XMP-laajennusskeema läsnä ja oikein. Tiedosto, jonka säilö (container) on virheetön, mutta jonka fx-ominaisuuksia ei ole ilmoitettu, läpäisee jokaisen vikatarkistuksen (issue check) ja palauttaa arvon 1, koska mikään siinä luettelossa ei tarkista pdfaExtension:schemas -lohkoa. Se on juuri syy siihen, miksi käsin rakennettu lasku, tai sellainen, joka on tuotettu liukuhihnalta (pipeline), joka kirjoitti ominaisuuslohkon (property block) ilman ilmoitusta (declaration), voi purjehtia sisäänrakennetun validaattorin läpi ja silti hylätä veraPDF:ssä lausekkeessa 6.6.2.3.1. Säilövalidaattori (container validator) ja PDF/A-metadatavalidaattori vastaavat eri kysymyksiin, ja vain täysi PDF/A-tarkistaja vastaa toiseen
Vikojen (issues) lukeminen, jotta tiedät, mikä kerros hajosi
Koska nämä kaksi kerrosta epäonnistuvat toisistaan riippumatta, oikea vianmääritystapa (diagnostic habit) on lukea säiliöviat (container issues) ensin ja käsitellä puhdasta tulosta lausuntona vain säilöstä (container), ei koskaan PDF/A-metadatasta. Suorita sisäänrakennettu validointi, kerää vikalista ja toimi sen perusteella, ennen kuin tartut ulkoiseen työkaluun
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äilössä (container), ja viesti kertoo sinulle, missä osassa. Kun se palauttaa puhtaan tuloksen ja veraPDF silti hylkää tiedoston, vika on lähes aina XMP-laajennusskeemassa, ja korjaus on antaa AddFacturXAssociatedFileFromString -metodin 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äilöongelmat (container problems) nousevat pintaan vikalistan (issue list) kautta, skeemojen ilmoitusongelmat (schema-declaration problems) nousevat pintaan vain PDF/A-validaattorin kautta, ja näiden kahden sekoittaminen on se, mikä antaa bugin piiloutua
Laajempi PDF/A ja PDF/UA -vaatimustenmukaisuuskuva (conformance picture), mukaan lukien se, kuinka suorittaa esitarkistus (preflight pass) ennen kuin tiedosto jättää koontisi (build), on käsitelty läpikäynnissä pdflibpas-pdfa-pdfua-preflight-delphi.html, PDF/A- ja PDF/UA-esitarkistus (preflight). Jos laskusi on myös oltava saavutettava, rakennepuu (structure tree), josta PDF/A-3a ja tagattu PDF riippuvat, on artikkelin pdflibpas-tagged-pdf-accessibility-structure.html, tagattu PDF -saavutettavuus, aihe. Tässä kuvattu laajennusskeeman (extension schema) käsittely toimitetaan osana PDFlibPas Delphi PDF Library -kirjastoa Factur-X, ZUGFeRD ja XRechnung -profiilituen ohella, joka on dokumentoitu tässä blogissa