Validate electronic invoices with Saxon
When you receive an electronic invoice in the formats ZUGFeRD, Factur-X, X-Rechnung or UBL, you may need to validate the XML file. You can use our DynaPDF functions to extract the XML from the ZUGFeRD invoice. Once you have the XML, you can do some automated validation. We leverage the XSL and XSD files coming with the ZUGFeRD 2.3 download. They provide a way to check both the structure with the XSD file as well as the content with the XSL file. The XSL file is based on the schematron file with all the business rules, but converted to a stylesheet for using it with the XSLT function. While the schema check makes sure you have the right nodes in the right places, the business rules define what can go in a field and how fields are related.

Step 1: Load Saxon
First load the Saxon library. You download the libraries either from our website or directly from Saxonica website. We suggest to copy the Saxon library to the same folder as the plugin. Then you only need to pass the library file name to load it.
# Load Saxon in file ZUGFeRD Validation
Set Variable [ $path ; Value: "" ]
# you may need to put in your path or have the libraries in the folder for the plugin
If [ MBS("IsMacOS") ]
Set Variable [ $path ; Value: "libsaxon-eec-12.5.0.dylib" ]
Else If [ MBS("IsWindows") ]
Set Variable [ $path ; Value: "libsaxon-eec-12.5.0.dylib" ]
Else If [ MBS("IsLinux") ]
Set Variable [ $path ; Value: "libsaxon-eec-12.5.0.so" ]
End If
#
Set Variable [ $r ; Value: MBS( "Saxon.Load"; $Path ) ]
If [ MBS("IsError") ]
Show Custom Dialog [ "Failed to load saxon library." ; $r ]
Else
Set Variable [ $r ; Value: MBS( "Saxon.SetLicense"; "Christian Schmitz"; "Saxon-EEV"; 202601; 1081079443) ]
Show Custom Dialog [ "saxon loaded" ; MBS("Saxon.Version") ]
End If
Step 2: LibXML Validation
If you don't use our Saxon functions yet, you may use the validation functions coming with our XML functions. There we read the XSD file to get the schema XML. We make sure we set the current directory, so the referenced files are found. And then we call XML.Validate to let the validation run through. We either get back OK or the error messages from LibXML. This provides a quick check for the schema.
# LibXML Validation in file ZUGFeRD Validation
# Validate the structure of the XML with LibXML (no Saxon)
Set Variable [ $schemaPath ; Value: MBS("Path.AddPathComponent"; ZUGFeRD Validation::Folder; "Factur-X_1.07.2_EN16931.xsd") ]
#
If [ MBS("Files.FileExists"; $schemaPath) ≠ 1 ]
Show Custom Dialog [ "You miss the XSD file." ; $schemaPath ]
Exit Script [ Text Result: ]
End If
#
Set Variable [ $schemaText ; Value: MBS("Text.ReadTextFile"; $schemaPath; "utf-8") ]
#
# set folder value
Set Variable [ $r ; Value: MBS("Process.SetCurrentDirectory"; ZUGFeRD Validation::Folder) ]
#
# validate the XML against the schema
Set Variable [ $result ; Value: MBS("XML.Validate"; ZUGFeRD Validation::Invoice XML; $schemaText; 0) ]
Set Field [ ZUGFeRD Validation::Result ; $result ]
#
#
Set Field [ ZUGFeRD Validation::HTML ; "" ]
Set Field [ ZUGFeRD Validation::Report ; "" ]
Step 3: Saxon Validation
We do the same schema validation as above, but this time using Saxon functions. First we make sure Saxon was loaded. Then we load the schema file, set the directory to find the related files and rin the validation. The Saxon.Validate function can return OK or an error. But more details are available as a XML based report with Saxon.ValidationReport function. We prepared a XML stylesheet to convert the XML from the report to HTML and show it in web viewer. This needs a Saxon EEV license from us.
# Saxon Validate in file ZUGFeRD Validation
# Validate the structure of the XML with Saxon
If [ MBS("Saxon.IsLoaded") = 0 ]
Perform Script [ Specified: From list ; “Load Saxon” ; Parameter: ]
End If
#
Set Variable [ $schemaPath ; Value: MBS("Path.AddPathComponent"; ZUGFeRD Validation::Folder; "Factur-X_1.07.2_EN16931.xsd") ]
#
If [ MBS("Files.FileExists"; $schemaPath) ≠ 1 ]
Show Custom Dialog [ "You miss the XSD file." ; $schemaPath ]
Exit Script [ Text Result: ]
End If
#
Set Variable [ $schemaText ; Value: MBS("Text.ReadTextFile"; $schemaPath; "utf-8") ]
#
# set folder value
Set Variable [ $r ; Value: MBS( "Saxon.SetCurrentWorkingDirectory"; ZUGFeRD Validation::Folder ) ]
#
# run validation
Set Variable [ $r ; Value: MBS( "Saxon.Validate"; ZUGFeRD Validation::Invoice XML; $schemaText; 2 ) ]
Set Field [ ZUGFeRD Validation::Result ; $r ]
#
# there is also a report as XML
Set Variable [ $report ; Value: MBS("Saxon.ValidationReport") ]
Set Field [ ZUGFeRD Validation::Report ; MBS("Saxon.ValidationReport") ]
#
# convert report to html
Set Variable [ $html ; Value: MBS( "Saxon.XSLT"; $report; ZUGFeRD Validation::HTML XSLT2 ) ]
If [ MBS("IsError") ]
Show Custom Dialog [ "Failed to produce html" ; $html ]
Else
Set Field [ ZUGFeRD Validation::HTML ; $html ]
End If
Step 4: Validate content with business rules
With ZUGFeRD we get the Schematron file with the business rules and the matching XML stylesheet for use with Saxon.XSLT function. We load the XSL file from the data folder. We can use XML.Query function to quickly make a XPath 1.0 query or Saxon.XPathQuery for XPath 1.0 to 3.1 to lookup how many assertions we have. Currently over 400 rules are defined. We perform the transformation and get back a XML file. There you can lookup how many asserts are in the report. We prepared a XSLT to convert the report to a HTML and show that in a web viewer. For the report to show up before our dialog box reports the number of errors, we need to have a small pause. The pause allows the web viewer to show the content. This needs a Saxon PE or EEV license from us.

# Validate Content with Saxon in file ZUGFeRD Validation
If [ MBS("Saxon.IsLoaded") = 0 ]
Perform Script [ Specified: From list ; “Load Saxon” ; Parameter: ]
End If
#
# We read the XSLT needed to do validation
Set Variable [ $schemaPath ; Value: MBS("Path.AddPathComponent"; ZUGFeRD Validation::Folder; "FACTUR-X_EN16931.xslt") ]
#
If [ MBS("Files.FileExists"; $schemaPath) ≠ 1 ]
Show Custom Dialog [ "You miss the XSLT file." ; $schemaPath ]
Exit Script [ Text Result: ]
End If
Set Variable [ $schemaText ; Value: MBS("Text.ReadTextFile"; $schemaPath; "utf-8") ]
#
# check count if you like to know how many rules you have
Set Variable [ $countRules ; Value: MBS("XML.Query"; $schemaText; "count(//svrl:failed-assert)"; "svrl=http://purl.oclc.org/dsdl/svrl"; 0) ]
#
# we need to set where to find related files
Set Variable [ $r ; Value: MBS( "Saxon.SetCurrentWorkingDirectory"; ZUGFeRD Validation::Folder ) ]
#
# perform check
Set Variable [ $report ; Value: MBS( "Saxon.XSLT"; ZUGFeRD Validation::Invoice XML; $schemaText ) ]
Set Field [ ZUGFeRD Validation::Report ; $report ]
If [ MBS("IsError") ]
Show Custom Dialog [ "Failed to check invoice" ; $report ]
Else
# check count if you like to know how many rules you have
Set Variable [ $countFailed ; Value: MBS("XML.Query"; $report; "count(//svrl:failed-assert)"; "svrl=http://purl.oclc.org/dsdl/svrl"; 0) ]
#
# convert report to html
Set Variable [ $html ; Value: MBS( "Saxon.XSLT"; $report; ZUGFeRD Validation::HTML XSLT1 ) ]
If [ MBS("IsError") ]
Show Custom Dialog [ "Failed to produce html" ; $html ]
Else
Set Field [ ZUGFeRD Validation::HTML ; $html ]
#
Pause/Resume Script [ Duration (seconds): ,1 ]
Show Custom Dialog [ "Check completed." ; $countFailed & " of " & $countRules & " rules failed." ]
End If
End If
Please try this when you have time. We will include the database files (without the data files) with future plugin versions and update them whenever needed. We include the examples with future plugins and you can download the sample here.
See also
- Validate electronic invoices with Saxon
- PDF Rechnung mit oder ohne ZUGFeRD empfangen?
- ZUGFeRD Update (Deutsch)
- ZUGFeRD Update (Englisch)
- ZUGFeRD mit DynaPDF und MBS
- Validating schema for electronic invoices with ZUGFeRD
- DynaPDF Licenses and ZUGFeRD invoices
- The new ZUGFeRD example
- FileMaker with ZUGFeRD 2.0 and Factur-X
