Be able to validate XR 2.0 and 1.2

This commit is contained in:
Jochen Stärk
2020-08-06 15:59:06 +02:00
parent 662307c0ba
commit 382435d0ba
12 changed files with 435 additions and 239699 deletions

View File

@@ -34,17 +34,17 @@ import org.xml.sax.InputSource;
public class XMLValidator extends Validator {
private static final Logger LOGGER = LoggerFactory.getLogger(XMLValidator.class.getCanonicalName()); // log output
// is
// ignored for the
// time being
private static final Logger LOGGER = LoggerFactory.getLogger(XMLValidator.class.getCanonicalName()); // log output
// is
// ignored for the
// time being
protected String zfXML = "";
protected String filename = "";
int firedRules = 0;
int failedRules = 0;
boolean disableNotices=false;
ISchematronResource aResSCH = null;
protected String zfXML = "";
protected String filename = "";
int firedRules = 0;
int failedRules = 0;
boolean disableNotices = false;
ISchematronResource aResSCH = null;
public XMLValidator(ValidationContext ctx) {
@@ -52,162 +52,163 @@ public class XMLValidator extends Validator {
}
public void setFilename(String name) throws IrrecoverableValidationError { // from XML Filename
filename = name;
// file existence must have been checked before
filename = name;
// file existence must have been checked before
try {
zfXML = new String(Files.readAllBytes(Paths.get(name)));
} catch (IOException e) {
try {
zfXML = new String(Files.readAllBytes(Paths.get(name)));
} catch (IOException e) {
ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(9)
.setPart(EPart.fx);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
vri.setStacktrace(sw.toString());
context.addResultItem(vri);
}
}
ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(9)
.setPart(EPart.fx);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
vri.setStacktrace(sw.toString());
context.addResultItem(vri);
}
}
public void setStringContent(String xml) {
zfXML = xml;
}
public void setStringContent(String xml) {
zfXML = xml;
}
public static boolean matchesURI(String uri1, String uri2) {
return (uri1.equals(uri2) || uri1.startsWith(uri2 + "#"));
}
public static boolean matchesURI(String uri1, String uri2) {
return (uri1.equals(uri2) || uri1.startsWith(uri2 + "#"));
}
/***
* don't report notices in validation report
*/
public void disableNotices() {
disableNotices=true;
disableNotices = true;
}
/***
*
* @param xmlString
* @param overrideProfileCheck
* if set to true, all ZF2 files will be checked against EN16931
* schematron, since no other schematron is available
* @return
*/
@Override
public void validate() throws IrrecoverableValidationError {
long startXMLTime = Calendar.getInstance().getTimeInMillis();
firedRules = 0;
failedRules = 0;
/***
*
* @param xmlString
* @param overrideProfileCheck
* if set to true, all ZF2 files will be checked against EN16931
* schematron, since no other schematron is available
* @return
*/
@Override
public void validate() throws IrrecoverableValidationError {
long startXMLTime = Calendar.getInstance().getTimeInMillis();
firedRules = 0;
failedRules = 0;
ByteArrayInputStream xmlByteInputStream = new ByteArrayInputStream(zfXML.getBytes(StandardCharsets.UTF_8));
ByteArrayInputStream xmlByteInputStream = new ByteArrayInputStream(zfXML.getBytes(StandardCharsets.UTF_8));
if (zfXML.isEmpty()) {
ValidationResultItem res = new ValidationResultItem(ESeverity.exception,
"XML data not found in " + filename
+ ": did you specify a pdf or xml file and does the xml file contain an embedded XML file?")
.setSection(3);
context.addResultItem(res);
if (zfXML.isEmpty()) {
ValidationResultItem res = new ValidationResultItem(ESeverity.exception,
"XML data not found in " + filename
+ ": did you specify a pdf or xml file and does the xml file contain an embedded XML file?")
.setSection(3);
context.addResultItem(res);
} else {
} else {
// final ISchematronResource aResSCH =
// SchematronResourceSCH.fromFile (new File("ZUGFeRD_1p0.scmt"));
// ... DOES work but is highly deprecated (and rightly so) because
// it takes 30-40min,
// final ISchematronResource aResSCH =
// SchematronResourceSCH.fromFile (new File("ZUGFeRD_1p0.scmt"));
// ... DOES work but is highly deprecated (and rightly so) because
// it takes 30-40min,
try {
ESeverity XrechnungSeverity=ESeverity.notice;
/***
* private static final String VALID_SCHEMATRON = "test-sch/valid01.sch";
* private static final String VALID_XMLINSTANCE = "test-xml/valid01.xml";
*
* @Test public void testWriteValid () throws Exception { final Document aDoc =
* SchematronResourceSCH.fromClassPath (VALID_SCHEMATRON)
* .applySchematronValidation (new ClassPathResource (VALID_XMLINSTANCE));
*
*/
try {
ESeverity XrechnungSeverity = ESeverity.notice;
/***
* private static final String VALID_SCHEMATRON = "test-sch/valid01.sch";
* private static final String VALID_XMLINSTANCE = "test-xml/valid01.xml";
*
* @Test public void testWriteValid () throws Exception { final Document aDoc =
* SchematronResourceSCH.fromClassPath (VALID_SCHEMATRON)
* .applySchematronValidation (new ClassPathResource (VALID_XMLINSTANCE));
*
*/
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true); // otherwise we can not act namespace independently, i.e. use
// document.getElementsByTagNameNS("*",...
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true); // otherwise we can not act namespace independently, i.e. use
// document.getElementsByTagNameNS("*",...
DocumentBuilder db = dbf.newDocumentBuilder();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(xmlByteInputStream);
Document doc = db.parse(xmlByteInputStream);
Element root = doc.getDocumentElement();
Element root = doc.getDocumentElement();
NodeList ndList;
NodeList ndList;
// rootNode = document.getDocumentElement();
// ApplicableSupplyChainTradeSettlement
// rootNode = document.getDocumentElement();
// ApplicableSupplyChainTradeSettlement
// Create XPathFactory object
XPathFactory xpathFactory = XPathFactory.newInstance();
// Create XPathFactory object
XPathFactory xpathFactory = XPathFactory.newInstance();
// Create XPath object
XPath xpath = xpathFactory.newXPath();
XPathExpression expr = xpath.compile(
"//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/*[local-name()=\"ID\"]/text()");
// evaluate expression result on XML document
ndList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
// Create XPath object
XPath xpath = xpathFactory.newXPath();
XPathExpression expr = xpath.compile(
"//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/*[local-name()=\"ID\"]/text()");
// evaluate expression result on XML document
ndList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int bookingIndex = 0; bookingIndex < ndList.getLength(); bookingIndex++) {
Node booking = ndList.item(bookingIndex);
// if there is a attribute in the tag number:value
// urn:ferd:CrossIndustryDocument:invoice:1p0:extended
// setForeignReference(booking.getTextContent());
for (int bookingIndex = 0; bookingIndex < ndList.getLength(); bookingIndex++) {
Node booking = ndList.item(bookingIndex);
// if there is a attribute in the tag number:value
// urn:ferd:CrossIndustryDocument:invoice:1p0:extended
// setForeignReference(booking.getTextContent());
context.setProfile(booking.getNodeValue());
}
boolean isMiniumum = false;
boolean isBasic = false;
boolean isBasicWithoutLines = false;
boolean isEN16931 = false;
context.setProfile(booking.getNodeValue());
}
boolean isMiniumum = false;
boolean isBasic = false;
boolean isBasicWithoutLines = false;
boolean isEN16931 = false;
boolean isExtended = false;
boolean isXRechnung = false;
String xsltFilename = null;
// urn:ferd:CrossIndustryDocument:invoice:1p0:extended,
// urn:ferd:CrossIndustryDocument:invoice:1p0:comfort,
// urn:ferd:CrossIndustryDocument:invoice:1p0:basic,
// urn:ferd:CrossIndustryDocument:invoice:1p0:comfort,
// urn:ferd:CrossIndustryDocument:invoice:1p0:basic,
// urn:cen.eu:en16931:2017
// urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:basic
if (root.getNodeName().equalsIgnoreCase("rsm:CrossIndustryInvoice")) { // ZUGFeRD 2.0 or Factur-X
context.setVersion("2");
// urn:cen.eu:en16931:2017
// urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:basic
if (root.getNodeName().equalsIgnoreCase("rsm:CrossIndustryInvoice")) { // ZUGFeRD 2.0 or Factur-X
context.setVersion("2");
isMiniumum = context.getProfile().contains("minimum");
isBasic = context.getProfile().contains("basic");
isBasicWithoutLines = context.getProfile().contains("basicwl");
if (isBasicWithoutLines) {
isBasic = false;// basicwl also contains the string basic...
}
isEN16931 = matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:en16931")
|| matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017");
isMiniumum = context.getProfile().contains("minimum");
isBasic = context.getProfile().contains("basic");
isBasicWithoutLines = context.getProfile().contains("basicwl");
if (isBasicWithoutLines) {
isBasic = false;// basicwl also contains the string basic...
}
isEN16931 = matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:en16931")
|| matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017");
isExtended = context.getProfile().contains("extended");
isXRechnung = context.getProfile().contains("xrechnung");
if ((isExtended)||(isXRechnung)) {
isEN16931 = false;// the uri for extended is urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended and thus contains en16931...
}
if (isMiniumum) {
LOGGER.debug("is Minimum");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/MINIMUM/FACTUR-X_MINIMUM.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_MINIMUM.xslt";
} else if (isBasicWithoutLines) {
LOGGER.debug("is Basic/WL");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/BASIC-WL/FACTUR-X_BASIC-WL.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_BASIC-WL.xslt";
} else if (isBasic) {
LOGGER.debug("is Basic");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/BASIC/FACTUR-X_BASIC.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_BASIC.xslt";
} else if (isEN16931) {
LOGGER.debug("is EN16931");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/EN16931/FACTUR-X_EN16931.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_EN16931.xslt";
} else if (isExtended) {
if ((isExtended) || (isXRechnung)) {
isEN16931 = false;// the uri for extended is urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended and thus contains en16931...
}
if (isMiniumum) {
LOGGER.debug("is Minimum");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/MINIMUM/FACTUR-X_MINIMUM.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_MINIMUM.xslt";
} else if (isBasicWithoutLines) {
LOGGER.debug("is Basic/WL");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/BASIC-WL/FACTUR-X_BASIC-WL.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_BASIC-WL.xslt";
} else if (isBasic) {
LOGGER.debug("is Basic");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/BASIC/FACTUR-X_BASIC.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_BASIC.xslt";
} else if (isEN16931) {
LOGGER.debug("is EN16931");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/EN16931/FACTUR-X_EN16931.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_EN16931.xslt";
} else if (isExtended) {
LOGGER.debug("is EXTENDED");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/EXTENDED/FACTUR-X_EXTENDED.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_EXTENDED.xslt";
@@ -215,155 +216,162 @@ public class XMLValidator extends Validator {
LOGGER.debug("is XRechnung");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf2/EXTENDED/FACTUR-X_EXTENDED.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZF_211/FACTUR-X_EN16931.xslt";
XrechnungSeverity=ESeverity.error;
XrechnungSeverity = ESeverity.error;
} /*
* ISchematronResource aResSCH = SchematronResourceXSLT.fromFile(new File(
* "/Users/jstaerk/workspace/ZUV/src/main/resources/ZUGFeRDSchematronStylesheet.xsl"
* ));
*/
* ISchematronResource aResSCH = SchematronResourceXSLT.fromFile(new File(
* "/Users/jstaerk/workspace/ZUV/src/main/resources/ZUGFeRDSchematronStylesheet.xsl"
* ));
*/
// takes around 10 Seconds. //
// http://www.bentoweb.org/refs/TCDL2.0/tsdtf_schematron.html // explains that
// this xslt can be created using sth like
// saxon java net.sf.saxon.Transform -o tcdl2.0.tsdtf.sch.tmp.xsl -s
// tcdl2.0.tsdtf.sch iso_svrl.xsl
// takes around 10 Seconds. //
// http://www.bentoweb.org/refs/TCDL2.0/tsdtf_schematron.html // explains that
// this xslt can be created using sth like
// saxon java net.sf.saxon.Transform -o tcdl2.0.tsdtf.sch.tmp.xsl -s
// tcdl2.0.tsdtf.sch iso_svrl.xsl
} else { // ZUGFeRD 1.0
context.setVersion("1");
//
if ((!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
.setSection(25).setPart(EPart.fx));
}
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf1/ZUGFeRD1p0.xsd", 18, EPart.fx);
} else { // ZUGFeRD 1.0
context.setVersion("1");
//
if ((!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
.setSection(25).setPart(EPart.fx));
}
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "zf1/ZUGFeRD1p0.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZUGFeRD_1p0.xslt";
}
if (context.getVersion().equals("2")) {
if ((!matchesURI(context.getProfile(), "urn:factur-x.eu:1p0:minimum"))
&& (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:minimum"))
&& (!matchesURI(context.getProfile(), "urn:factur-x.eu:1p0:basicwl"))
&& (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:basicwl"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended"))) {
context.addResultItem(
new ValidationResultItem(ESeverity.error, "Unsupported profile type " + context.getProfile())
.setSection(25).setPart(EPart.fx));
xsltFilename = "/xslt/ZUGFeRD_1p0.xslt";
}
if (context.getVersion().equals("2")) {
if ((!matchesURI(context.getProfile(), "urn:factur-x.eu:1p0:minimum"))
&& (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:minimum"))
&& (!matchesURI(context.getProfile(), "urn:factur-x.eu:1p0:basicwl"))
&& (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:basicwl"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended"))
&& (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended"))) {
context.addResultItem(
new ValidationResultItem(ESeverity.error, "Unsupported profile type " + context.getProfile())
.setSection(25).setPart(EPart.fx));
}
} else /** v1 */ {//urn:ferd:invoice:rc:comfort
if ((!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
.setSection(25).setPart(EPart.fx));
}
} else /** v1 */ {//urn:ferd:invoice:rc:comfort
if ((!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
&& (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
.setSection(25).setPart(EPart.fx));
}
}
}
}
// main schematron validation
validateSchematron(zfXML, xsltFilename, 4, ESeverity.error);
// main schematron validation
validateSchematron(zfXML, xsltFilename, 4, ESeverity.error);
if (context.getVersion().equals("2")
&& (isEN16931 || isXRechnung)) {
//additionally validate against CEN
validateSchematron(zfXML, "/xslt/cii16931schematron/EN16931-CII-validation.xslt", 24, ESeverity.error);
if (!disableNotices||XrechnungSeverity!=ESeverity.notice) {
if (context.getVersion().equals("2")
&& (isEN16931 || isXRechnung)) {
//additionally validate against CEN
validateSchematron(zfXML, "/xslt/cii16931schematron/EN16931-CII-validation.xslt", 24, ESeverity.error);
if (!disableNotices || XrechnungSeverity != ESeverity.notice) {
validateXR(zfXML, XrechnungSeverity);
}
}
}
} catch (IrrecoverableValidationError er) {
throw er;
} catch (Exception e) {
ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(22)
.setPart(EPart.fx);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
vri.setStacktrace(sw.toString());
context.addResultItem(vri);
}
} catch (IrrecoverableValidationError er) {
throw er;
} catch (Exception e) {
ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(22)
.setPart(EPart.fx);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
vri.setStacktrace(sw.toString());
context.addResultItem(vri);
}
}
long endTime = Calendar.getInstance().getTimeInMillis();
}
long endTime = Calendar.getInstance().getTimeInMillis();
context.addCustomXML("<info><version>" + ((context.getVersion() != null) ? context.getVersion() : "invalid")
+ "</version><profile>" + ((context.getProfile() != null) ? context.getProfile() : "invalid") +
"</profile><validator version=\"" + XMLValidator.class.getPackage().getImplementationVersion() + "\"></validator><rules><fired>" + firedRules + "</fired><failed>" + failedRules + "</failed></rules>" + "<duration unit='ms'>" + (endTime - startXMLTime) + "</duration></info>");
context.addCustomXML("<info><version>" + ((context.getVersion() != null) ? context.getVersion() : "invalid")
+ "</version><profile>" + ((context.getProfile() != null) ? context.getProfile() : "invalid") +
"</profile><validator version=\"" + XMLValidator.class.getPackage().getImplementationVersion() + "\"></validator><rules><fired>" + firedRules + "</fired><failed>" + failedRules + "</failed></rules>" + "<duration unit='ms'>" + (endTime - startXMLTime) + "</duration></info>");
}
}
public void validateXR(String xml, ESeverity errorImpact) throws IrrecoverableValidationError {
public void validateXR(String xml, ESeverity errorImpact) throws IrrecoverableValidationError {
validateSchematron(xml, "/xslt/XR/XRechnung-CII-validation.xslt",27, errorImpact);
//Guideline ID=urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 or
if (xml.contains(":xrechnung_1.")) {
// urn:cen.eu:en16931:2017#compliant#urn:xoev-dede:kosit:standard:xrechnung_2.0#conformant#urn:xoev-de:kosit:extension:xrechnung_2.0
validateSchematron(xml, "/xslt/XR_12/XRechnung-CII-validation.xslt", 27, errorImpact);
} else {
// urn:cen.eu:en16931:2017#compliant#urn:xoev-dede:kosit:standard:xrechnung_2.0#conformant#urn:xoev-de:kosit:extension:xrechnung_2.0
validateSchematron(xml, "/xslt/XR_20/XRechnung-CII-validation.xslt", 27, errorImpact);
}
}
}
public void validateSchematron(String xml, String xsltFilename, int section, ESeverity severity) throws IrrecoverableValidationError {
ISchematronResource aResSCH = null;
aResSCH = SchematronResourceXSLT.fromClassPath(xsltFilename);
if (aResSCH != null) {
if (!aResSCH.isValidSchematron()) {
throw new IllegalArgumentException(xsltFilename + " is invalid Schematron!");
}
public void validateSchematron(String xml, String xsltFilename, int section, ESeverity severity) throws IrrecoverableValidationError {
ISchematronResource aResSCH = null;
aResSCH = SchematronResourceXSLT.fromClassPath(xsltFilename);
if (aResSCH != null) {
if (!aResSCH.isValidSchematron()) {
throw new IllegalArgumentException(xsltFilename + " is invalid Schematron!");
}
SchematronOutputType sout;
try {
sout = aResSCH
.applySchematronValidationToSVRL(new StreamSource(new StringReader(xml)));
} catch (Exception e) {
throw new IrrecoverableValidationError(e.getMessage());
}
SchematronOutputType sout;
try {
sout = aResSCH
.applySchematronValidationToSVRL(new StreamSource(new StringReader(xml)));
} catch (Exception e) {
throw new IrrecoverableValidationError(e.getMessage());
}
List<Object> failedAsserts = sout.getActivePatternAndFiredRuleAndFailedAssert();
if (failedAsserts.size() > 0) {
for (Object object : failedAsserts) {
if (object instanceof FailedAssert) {
List<Object> failedAsserts = sout.getActivePatternAndFiredRuleAndFailedAssert();
if (failedAsserts.size() > 0) {
for (Object object : failedAsserts) {
if (object instanceof FailedAssert) {
FailedAssert failedAssert = (FailedAssert) object;
LOGGER.info("FailedAssert ", failedAssert);
FailedAssert failedAssert = (FailedAssert) object;
LOGGER.info("FailedAssert ", failedAssert);
context.addResultItem(new ValidationResultItem(severity, SVRLHelper.getAsString(failedAssert.getText()))
.setLocation(failedAssert.getLocation()).setCriterion(failedAssert.getTest()).setSection(section)
.setPart(EPart.fx));
failedRules++;
} else if (object instanceof FiredRule) {
firedRules++;
}
}
context.addResultItem(new ValidationResultItem(severity, SVRLHelper.getAsString(failedAssert.getText()))
.setLocation(failedAssert.getLocation()).setCriterion(failedAssert.getTest()).setSection(section)
.setPart(EPart.fx));
failedRules++;
} else if (object instanceof FiredRule) {
firedRules++;
}
}
}
if (firedRules == 0) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "No rules matched, XML to minimal?").setSection(26)
.setPart(EPart.fx));
}
if (firedRules == 0) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "No rules matched, XML to minimal?").setSection(26)
.setPart(EPart.fx));
}
// for (String currentString : sout.getText()) {
// schematronValidationString += "<output>" + currentString + "</output>";
// }
}
// for (String currentString : sout.getText()) {
// schematronValidationString += "<output>" + currentString + "</output>";
// }
// schematronValidationString += new SVRLMarshaller ().getAsString (sout);
// returns the complete SVRL
// schematronValidationString += new SVRLMarshaller ().getAsString (sout);
// returns the complete SVRL
}
}
}
}
public int getFiredRules() {
return firedRules;
}
public int getFiredRules() {
return firedRules;
}
public int getFailedRules() {
return failedRules;
}
public int getFailedRules() {
return failedRules;
}
}

View File

@@ -0,0 +1,61 @@
<pattern xmlns="http://purl.oclc.org/dsdl/schematron" is-a="model"
id="CII-model">
<param name="BR-DE-01"
value="rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans" />
<param name="BR-DE-02" value="ram:DefinedTradeContact" />
<param name="BR-DE-03" value="ram:CityName[boolean(normalize-space(.))]" />
<param name="BR-DE-04" value="ram:PostcodeCode[boolean(normalize-space(.))]" />
<param name="BR-DE-05"
value="(ram:PersonName,ram:DepartmentName)[boolean(normalize-space(.))]" />
<param name="BR-DE-06"
value="ram:TelephoneUniversalCommunication/ram:CompleteNumber[boolean(normalize-space(.))]" />
<param name="BR-DE-07"
value="ram:EmailURIUniversalCommunication/ram:URIID[boolean(normalize-space(.))]" />
<param name="BR-DE-08" value="ram:CityName[boolean(normalize-space(.))]" />
<param name="BR-DE-09" value="ram:PostcodeCode[boolean(normalize-space(.))]" />
<param name="BR-DE-10" value="ram:CityName[boolean(normalize-space(.))]" />
<param name="BR-DE-11" value="ram:PostcodeCode[boolean(normalize-space(.))]" />
<!-- Für BG-19 wird jedes Informationselement der Gruppe einzeln aufgeführt, der Pfad zu BG-19 (DIRECT DEBIT) zu unspezifisch ist und
sich mit den Pfaden zu BG-17 und BG-18 überschneidet. -->
<param name="BR-DE-13"
value="count((rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount)[1]) + count(rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans/ram:ApplicableTradeSettlementFinancialCard) + count((rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradePaymentTerms/ram:DirectDebitMandateID, rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:CreditorReferenceID, rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans/ram:PayerPartyDebtorFinancialAccount/ram:IBANID)[1]) = 1" />
<param name="BR-DE-14"
value="ram:RateApplicablePercent[boolean(normalize-space(.))]" />
<param name="BR-DE-15"
value="rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerReference[boolean(normalize-space(.))]" />
<param name="BR-DE-16"
value="(rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID='VA' or @schemeID='VAT' or @schemeID='FC'][boolean(normalize-space(.))], rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTaxRepresentativeTradeParty)" />
<param name="BR-DE-17"
value="rsm:ExchangedDocument/ram:TypeCode = ('326', '380', '384', '389', '381', '875', '876', '877')" />
<param name="BR-DE-18"
value="every $line in rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradePaymentTerms/ram:Description/tokenize(.,'(\r\n|\r|\n)') satisfies if(count(tokenize($line,'#')) &gt; 1) then tokenize($line,'#')[1]='' and (tokenize($line,'#')[2]='SKONTO' or tokenize($line,'#')[2]='VERZUG') and string-length(replace(tokenize($line,'#')[3],'TAGE=[0-9]+',''))=0 and string-length(replace(tokenize($line,'#')[4],'PROZENT=[0-9]+\.[0-9]{2}',''))=0 and (tokenize($line,'#')[5]='' and empty(tokenize($line,'#')[6]) or string-length(replace(tokenize($line,'#')[5],'BASISBETRAG=[0-9]+\.[0-9]{2}',''))=0 and tokenize($line,'#')[6]='' and empty(tokenize($line,'#')[7])) else true()" />
<param name="BR-DE-19"
value=" not(ram:TypeCode = '58') or matches(ram:PayeePartyCreditorFinancialAccount/ram:IBANID, '^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{0,30}$') and xs:integer( replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace( upper-case(concat(substring(ram:PayeePartyCreditorFinancialAccount/ram:IBANID,5),substring(ram:PayeePartyCreditorFinancialAccount/ram:IBANID,1,4))) ,'A','10'),'B','11'),'C','12'),'D','13'),'E','14'),'F','15'),'G','16'),'H','17'),'I','18'),'J','19'),'K','20'),'L','21'),'M','22') ,'N','23'),'O','24'),'P','25'),'Q','26'),'R','27'),'S','28'),'T','29'),'U','30'),'V','31'),'W','32'),'X','33'),'Y','34'),'Z','35') ) mod 97 = 1 " />
<param name="BR-DE-20"
value=" not(ram:TypeCode = '59') or matches(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{0,30}$') and xs:integer( replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace( upper-case(concat(substring(ram:PayerPartyDebtorFinancialAccount/ram:IBANID,5),substring(ram:PayerPartyDebtorFinancialAccount/ram:IBANID,1,4))) ,'A','10'),'B','11'),'C','12'),'D','13'),'E','14'),'F','15'),'G','16'),'H','17'),'I','18'),'J','19'),'K','20'),'L','21'),'M','22') ,'N','23'),'O','24'),'P','25'),'Q','26'),'R','27'),'S','28'),'T','29'),'U','30'),'V','31'),'W','32'),'X','33'),'Y','34'),'Z','35') ) mod 97 = 1" />
<param name="BR-DE-21"
value="ram:GuidelineSpecifiedDocumentContextParameter/ram:ID = 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2'" />
<param name="INVOICE" value="//rsm:CrossIndustryInvoice" />
<param name="BG-2_PROCESS_CONTROL"
value="/rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext" />
<param name="BG-4_SELLER"
value="//rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty" />
<param name="BG-5_SELLER_POSTAL_ADDRESS"
value="//rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:PostalTradeAddress" />
<param name="BG-6_SELLER_CONTACT"
value="//rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:DefinedTradeContact" />
<param name="BG-8_BUYER_POSTAL_ADDRESS"
value="//rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty/ram:PostalTradeAddress" />
<param name="BG-15_DELIVER_TO_ADDRESS"
value="//rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:ShipToTradeParty/ram:PostalTradeAddress" />
<param name="BG-16_PAYMENT_INSTRUCTIONS"
value="//rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans" />
<param name="BG-23_VAT_BREAKDOWN"
value="//rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax" />
</pattern>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://purl.oclc.org/dsdl/schematron"
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ccts="urn:un:unece:uncefact:documentation:standard:CoreComponentsTechnicalSpecification:2"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
schemaVersion="2.0.0" queryBinding="xslt2">
<title>Schematron Version 1.3.0 - XRechnung
1.2.2 compatible - CII</title>
<ns prefix="rsm"
uri="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" />
<ns prefix="ccts"
uri="urn:un:unece:uncefact:documentation:standard:CoreComponentsTechnicalSpecification:2" />
<ns prefix="udt"
uri="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" />
<ns prefix="qdt"
uri="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" />
<ns prefix="ram"
uri="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" />
<phase id="XRechnung_model">
<active pattern="CII-model" />
</phase>
<!-- Abstract CEN BII patterns -->
<!-- ========================= -->
<!-- in the original these includes are mistakenly called .sch but that has to be changed manually
since they are no complete schematrons which otherwise conflicts with ph-schematron -->
<include href="abstract/XRechnung-model.part" />
<!-- Data Binding parameters -->
<!-- ======================= -->
<include href="CII/XRechnung-CII-model.part" />
</schema>

View File

@@ -0,0 +1,59 @@
<pattern xmlns="http://purl.oclc.org/dsdl/schematron" abstract="true" id="model">
<rule context="$INVOICE">
<assert test="$BR-DE-01" flag="fatal" id="BR-DE-1"
>[BR-DE-1] Eine Rechnung (INVOICE) muss Angaben zu "PAYMENT INSTRUCTIONS" (BG-16) enthalten.</assert>
<assert test="$BR-DE-13" flag="fatal" id="BR-DE-13"
>[BR-DE-13] In der Rechnung müssen Angaben zu genau einer der drei Gruppen "CREDIT TRANSFER" (BG-17), "PAYMENT CARD INFORMATION" (BG-18) oder "DIRECT DEBIT" (BG-19) übermittelt werden.</assert>
<assert test="$BR-DE-15" flag="fatal" id="BR-DE-15"
>[BR-DE-15] Das Element "Buyer reference" (BT-10) muss übermittelt werden.</assert>
<assert test="$BR-DE-16" flag="fatal" id="BR-DE-16"
>[BR-DE-16] In der Rechnung muss mindestens eines der Elemente "Seller VAT identifier" (BT-31), "Seller tax registration identifier" (BT-32) oder "SELLER TAX REPRESENTATIVE PARTY" (BG-11) übermittelt werden.</assert>
<assert test="$BR-DE-17" flag="warning" id="BR-DE-17" >[BR-DE-17] Mit dem Element "Invoice type code" (BT-3) sollen ausschließlich folgende Codes aus der Codeliste UNTDID 1001 übermittelt werden: 326 (Partial invoice), 380 (Commercial invoice), 384 (Corrected invoice), 389 (Self-billed invoice) und 381 (Credit note),875 (Partial construction invoice), 876 (Partial final construction invoice), 877 (Final construction invoice).</assert>
<assert test="$BR-DE-18" flag="fatal" id="BR-DE-18"
>[BR-DE-18] Die Informationen zur Gewährung von Skonto oder zur Berechnung von Verzugszinsen müssen wie folgt im Element "Payment terms" (BT-20) übermittelt werden: Anzugeben ist im ersten Segment "SKONTO" oder "VERZUG", im zweiten "TAGE=n", im dritten "PROZENT=n". Prozentzahlen sind ohne Vorzeichen sowie mit Punkt getrennt von zwei Nachkommastellen anzugeben. Liegt dem zu berechnenden Betrag nicht BT-115, "fälliger Betrag" zugrunde, sondern nur ein Teil des fälligen Betrags der Rechnung, ist der Grundwert zur Berechnung von Skonto oder Verzugszins als viertes Segment "BASISBETRAG=n" gemäß dem semantischen Datentypen Amount anzugeben. Jeder Eintrag beginnt mit einer #, die Segmente sind mit einer # getrennt und eine Zeile schließt mit einer # ab. Am Ende einer vollständigen Skonto oder Verzugsangabe muss ein XML-konformer Zeilenumbruch folgen. Alle Angaben zur Gewährung von Skonto oder zur Berechnung von Verzugszinsen müssen in Großbuchstaben gemacht werden. Zusätzliches Whitespace (Leerzeichen, Tabulatoren oder Zeilenumbrüche) ist nicht zulässig. Andere Zeichen oder Texte als in den oberen Vorgaben genannt sind nicht zulässig.</assert>
</rule>
<rule context="$BG-2_PROCESS_CONTROL">
<assert test="$BR-DE-21" flag="warning" id="BR-DE-21"
>[BR-DE-21] Das Element "Specification identifier" (BT-24) soll syntaktisch der Kennung des Standards XRechnung entsprechen.</assert>
</rule>
<rule context="$BG-4_SELLER">
<assert test="$BR-DE-02" flag="fatal" id="BR-DE-2"
>[BR-DE-2] Die Gruppe "SELLER CONTACT" (BG-6) muss übermittelt werden.</assert>
</rule>
<rule context="$BG-5_SELLER_POSTAL_ADDRESS">
<assert test="$BR-DE-03" flag="fatal" id="BR-DE-3"
>[BR-DE-3] Das Element "Seller city" (BT-37) muss übermittelt werden.</assert>
<assert test="$BR-DE-04" flag="fatal" id="BR-DE-4"
>[BR-DE-4] Das Element "Seller post code" (BT-38) muss übermittelt werden.</assert>
</rule>
<rule context="$BG-6_SELLER_CONTACT">
<assert test="$BR-DE-05" flag="fatal" id="BR-DE-5"
>[BR-DE-5] Das Element "Seller contact point" (BT-41) muss übermittelt werden.</assert>
<assert test="$BR-DE-06" flag="fatal" id="BR-DE-6"
>[BR-DE-6] Das Element "Seller contact telephone number" (BT-42) muss übermittelt werden.</assert>
<assert test="$BR-DE-07" flag="fatal" id="BR-DE-7"
>[BR-DE-7] Das Element "Seller contact email address" (BT-43) muss übermittelt werden.</assert>
</rule>
<rule context="$BG-8_BUYER_POSTAL_ADDRESS">
<assert test="$BR-DE-08" flag="fatal" id="BR-DE-8"
>[BR-DE-8] Das Element "Buyer city" (BT-52) muss übermittelt werden.</assert>
<assert test="$BR-DE-09" flag="fatal" id="BR-DE-9"
>[BR-DE-9] Das Element "Buyer post code" (BT-53) muss übermittelt werden.</assert>
</rule>
<rule context="$BG-15_DELIVER_TO_ADDRESS">
<assert test="$BR-DE-10" flag="fatal" id="BR-DE-10"
>[BR-DE-10] Das Element "Deliver to city" (BT-77) muss übermittelt werden, wenn die Gruppe "DELIVER TO ADDRESS" (BG-15) übermittelt wird.</assert>
<assert test="$BR-DE-11" flag="fatal" id="BR-DE-11"
>[BR-DE-11] Das Element "Deliver to post code" (BT-78) muss übermittelt werden, wenn die Gruppe "DELIVER TO ADDRESS" (BG-15) übermittelt wird.</assert>
</rule>
<rule context="$BG-16_PAYMENT_INSTRUCTIONS">
<assert test="$BR-DE-19" flag="warning" id="BR-DE-19"
>[BR-DE-19] "Payment account identifier" (BT-84) soll eine korrekte IBAN enthalten, wenn in "Payment means type code" (BT-81) mit dem Code 58 SEPA als Zahlungsmittel gefordert wird.</assert>
<assert test="$BR-DE-20" flag="warning" id="BR-DE-20"
>[BR-DE-20] "Debited account identifier" (BT-91) soll eine korrekte IBAN enthalten, wenn in "Payment means type code" (BT-81) mit dem Code 59 SEPA als Zahlungsmittel gefordert wird.</assert>
</rule>
<rule context="$BG-23_VAT_BREAKDOWN">
<assert test="$BR-DE-14" flag="fatal" id="BR-DE-14"
>[BR-DE-14] Das Element "VAT category rate" (BT-119) muss übermittelt werden.</assert>
</rule>
</pattern>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet xmlns:svrl="http://purl.oclc.org/dsdl/svrl" xmlns:ccts="urn:un:unece:uncefact:documentation:standard:CoreComponentsTechnicalSpecification:2" xmlns:iso="http://purl.oclc.org/dsdl/schematron" xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:saxon="http://saxon.sf.net/" xmlns:schold="http://www.ascc.net/xml/schematron" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<!--Implementers: please note that overriding process-prolog or process-root is
the preferred method for meta-stylesheets to use where possible. -->

View File

@@ -191,4 +191,25 @@ public class XMLValidatorTest extends ResourceCase {
}
public void testXRValidation() {
ValidationContext ctx = new ValidationContext(null);
XMLValidator xv = new XMLValidator(ctx);
XPathEngine xpath = new JAXPXPathEngine();
File tempFile = getResourceAsFile("validXRv2.xml");
try {
xv.setFilename(tempFile.getAbsolutePath());
xv.validate();
Source source = Input.fromString("<validation>" + xv.getXMLResult() + "</validation>").build();
String content = xpath.evaluate("/validation/summary/@status", source);
assertEquals("valid", content);
} catch (IrrecoverableValidationError e) {
// ignore, will be in XML output anyway
}
}
}