In org.mustangproject.validator.XMLValidator.validateSchematron(), the severity has to be determined for every node of the validation result. Otherwise, all schematron assertions tagged as "fatal" will be treated as warnings after the first warning.
ZUV
ZUV (ZUgferd+VeraPDF) is an open-source e-invoice validator for the ZUGFeRD/Factur-X standard.
It checks both PDF/A-3 compliance (based on VeraPDF) and ZUGFeRD version 1 respectively 2/2.1 XMLs for correctness. The XML check is done by validating against the official ZUGFeRD schematron file for v1 and v2.1 and additionally the EN16931 UN/CEFACT SCRDM v16B uncoupled schematron from the CEN.
Build
In the pom.xml directory compile the jar with ./mvnw clean package
To prepare the schematron files they have to be converted to XSLT files, which is done with a XSLT transformation
itself.
Get Saxon and the XSLT to convert schematron to XSLT
and run
java -jar ./saxon9he.jar -o:minimum.xsl -s:zugferd2p0_basicwl_minimum.sch ./schematron_to_xslt/iso-schematron-xslt2/iso_svrl_for_xslt1.xsl
to do so.
To create a new en16931 run
git clone https://github.com/ConnectingEurope/eInvoicing-EN16931.git mvn -f pom-xslt.xml package
Run
To check a file for ZUGFeRD conformance use
java -jar ZUV-0.9.0.jar --action validate -f <filename of ZUGFeRD PDF.pdf>
You can provide either the complete PDF which will be checked for XML and PDF correctness, or just a XML file, which of course will only be checked for XML correctness.
Output
A valid file looks like this
<?xml version="1.0" encoding="UTF-8"?>
<validation filename="Facture_UE_MINIMUM.pdf" datetime="2020-03-23 12:34:54">
<pdf>
<report>
<buildInformation>
<releaseDetails id="core" version="1.12.1" buildDate="2018-05-08T18:57:00+02:00"/>
<releaseDetails id="validation-model" version="1.12.1" buildDate="2018-05-08T20:39:00+02:00"/>
</buildInformation>
<jobs>
<job>
<item size="101181">
<name>/Users/jstaerk/workspace/zugferd/helper_files/../foreign_samples/fx/Facture_UE_MINIMUM.pdf</name>
</item>
<validationReport profileName="PDF/A-3B validation profile" statement="PDF file is compliant with Validation Profile requirements." isCompliant="true">
<details passedRules="123" failedRules="0" passedChecks="11082" failedChecks="0"/>
</validationReport>
<duration start="1584963295227" finish="1584963296646">00:00:01.419</duration>
</job>
</jobs>
<batchSummary totalJobs="1" failedToParse="0" encrypted="0">
<validationReports compliant="1" nonCompliant="0" failedJobs="0">1</validationReports>
<featureReports failedJobs="0">0</featureReports>
<repairReports failedJobs="0">0</repairReports>
<duration start="1584963294997" finish="1584963296679">00:00:01.682</duration>
</batchSummary>
</report>
<info>
<signature>Factur/X Python</signature>
<duration unit="ms">2361</duration>
</info>
<summary status="valid"/>
</pdf>
<xml>
<info>
<version>2</version>
<profile>urn:factur-x.eu:1p0:minimum</profile>
<validator version="0.8.4-RESTRICTED"/>
<rules>
<fired>26</fired>
<failed>0</failed>
</rules>
<duration unit="ms">2772</duration>
</info>
<summary status="valid"/>
</xml>
<summary status="valid"/>
</validation>
Invalid files with errors e.g. in the XML part can look like this
<?xml version="1.0" encoding="UTF-8"?>
<validation filename="Facture_UE_EXTENDED.pdf" datetime="2020-03-23 12:34:43">
<pdf>
<report>
<buildInformation>
<releaseDetails id="core" version="1.12.1" buildDate="2018-05-08T18:57:00+02:00"/>
<releaseDetails id="validation-model" version="1.12.1" buildDate="2018-05-08T20:39:00+02:00"/>
</buildInformation>
<jobs>
<job>
<item size="109172">
<name>/Users/jstaerk/workspace/zugferd/helper_files/../foreign_samples/fx/Facture_UE_EXTENDED.pdf</name>
</item>
<validationReport profileName="PDF/A-3B validation profile" statement="PDF file is compliant with Validation Profile requirements." isCompliant="true">
<details passedRules="123" failedRules="0" passedChecks="11082" failedChecks="0"/>
</validationReport>
<duration start="1584963283448" finish="1584963284790">00:00:01.342</duration>
</job>
</jobs>
<batchSummary totalJobs="1" failedToParse="0" encrypted="0">
<validationReports compliant="1" nonCompliant="0" failedJobs="0">1</validationReports>
<featureReports failedJobs="0">0</featureReports>
<repairReports failedJobs="0">0</repairReports>
<duration start="1584963283223" finish="1584963284825">00:00:01.602</duration>
</batchSummary>
</report>
<info>
<signature>Factur/X Python</signature>
<duration unit="ms">2296</duration>
</info>
<summary status="valid"/>
</pdf>
<xml>
<info>
<version>2</version>
<profile>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</profile>
<validator version="0.8.4-RESTRICTED"/>
<rules>
<fired>97</fired>
<failed>1</failed>
</rules>
<duration unit="ms">8546</duration>
</info>
<messages>
<error type="4" location="/*[local-name()='CrossIndustryInvoice' and namespace-uri()='urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100']/*[local-name()='SupplyChainTradeTransaction' and namespace-uri()='urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100']/*[local-name()='ApplicableHeaderTradeSettlement' and namespace-uri()='urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100']/*[local-name()='ApplicableTradeTax' and namespace-uri()='urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100']/*[local-name()='CategoryCode' and namespace-uri()='urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100']" criterion="(/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString) or (../../ram:BillingSpecifiedPeriod/ram:StartDateTime) or (../../ram:BillingSpecifiedPeriod/ram:EndDateTime)">In an Invoice with a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Intra-community supply" the Actual delivery date (BT-72) or the Invoicing period (BG-14) shall not be blank.</error>
</messages>
<summary status="invalid"/>
</xml>
<messages></messages>
<summary status="invalid"/>
</validation>
Embed
Feel free to embed this into your java software, send me a PR to use it as a library, or exec it and parse it's output to put on the web.
For exec, you might try something like
exec('java -Dfile.encoding=UTF-8 -jar /path/to/ZUV-0.9.0.jar --action validate -f '.escapeshellarg($uploadfile).' 2>/dev/null', $output);
- Redirecting stderr away (some logging messages might otherwise disturb XML well formedness)
- Escaping any file names in case you use original file names at all (apart from security concerns please take into account that they might contain spaces)
- Signal java to use UTF-8 even when otherwise it would not: You might run into trouble with XML files starting with a BOM otherwise and when you exec, keep in mind that you lose all env vars.
License
Permissive Open Source APL2, see LICENSE
Codes
| section | meaning |
|---|---|
| 1 | file not found |
| 2 | additional data schema validation fails |
| 3 | xml data not found |
| 4 | schematron rule failed |
| 5 | file too small |
| 6 | VeraPDFException |
| 7 | IOException PDF |
| 8 | File does not look like PDF nor XML (contains neither %PDF nor <?xml) |
| 9 | IOException XML |
| 11 | XMP Metadata: ConformanceLevel not found |
| 12 | XMP Metadata: ConformanceLevel contains invalid value |
| 13 | XMP Metadata: DocumentType not found |
| 14 | XMP Metadata: DocumentType invalid |
| 15 | XMP Metadata: Version not found |
| 16 | XMP Metadata: Version contains invalid value |
| 18 | schema validation failed |
| 19 | XMP Metadata: DocumentFileName contains invalid value |
| 20 | not a pdf |
| 21 | XMP Metadata: DocumentFileName not found") |
| 22 | generic XML validation exception |
| 23 | Not a PDF/A-3 |
| 24 | Issues in CEN EN16931 Schematron Check |
| 25 | Unsupported profile type |
| 26 | No rules matched, XML to minimal? |
History
see the Mustangproject history file
Authors
Jochen Staerk "Mustangproject Chief ZUGFeRD amatuer" jochen@zugferd.org