Enhance code quality

This commit is contained in:
Daniel Luckas
2025-07-17 15:51:20 +02:00
parent 7ebaf3af8a
commit 2243f8ce6f
9 changed files with 135 additions and 98 deletions

View File

@@ -11,6 +11,7 @@ import java.util.Arrays;
import java.util.Calendar;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -41,6 +42,7 @@ import org.verapdf.processor.TaskType;
import org.verapdf.processor.plugins.PluginsCollectionConfig;
import org.verapdf.processor.reports.ItemDetails;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -194,9 +196,9 @@ public class PDFValidator extends Validator {
boolean documentTypeValid = false;
for (int i = 0; i < nodes.getLength(); i++) {
if (nodes.item(i).getTextContent().equals("INVOICE") || nodes.item(i).getTextContent().equals("ORDER")
|| nodes.item(i).getTextContent().equals("ORDER_RESPONSE") || nodes.item(i).getTextContent()
.equals("ORDER_CHANGE")) {
Node item = nodes.item(i);
String textContent = item.getTextContent();
if (textContent != null && Set.of("INVOICE", "ORDER", "ORDER_RESPONSE", "ORDER_CHANGE").contains(textContent)) {
documentTypeValid = true;
}
}

View File

@@ -10,6 +10,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
@@ -103,7 +104,7 @@ public class XMLValidator extends Validator {
* @return true if semantically identical
*/
public static boolean matchesURI(String uri1, String uri2) {
return (uri1.equals(uri2) || uri1.startsWith(uri2 + "#"));
return (uri1 != null && (uri2 != null && (uri1.equals(uri2) || uri1.startsWith(uri2 + "#"))));
}
@@ -217,35 +218,41 @@ public class XMLValidator extends Validator {
// urn:cen.eu:en16931:2017
// urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:basic
if (root.getLocalName().equalsIgnoreCase("SCRDMCCBDACIOMessageStructure")) {
String rootLocalName = root.getLocalName();
String contextProfile = context.getProfile();
if ("SCRDMCCBDACIOMessageStructure".equalsIgnoreCase(rootLocalName)) {
context.setGeneration("1");
isOrderX = true;
isBasic = context.getProfile().contains("basic");
isEN16931 = context.getProfile().contains("comfort");
isExtended = context.getProfile().contains("extended");
isBasic = contextProfile.contains("basic");
isEN16931 = contextProfile.contains("comfort");
isExtended = contextProfile.contains("extended");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd", 99, EPart.ox);
xsltFilename = "/xslt/OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B_COMFORT.xslt";
} else if (root.getLocalName().equalsIgnoreCase("SCRDMCCBDACIOMessageStructure")) {
} else if ("SCRDMCCBDACIOMessageStructure".equalsIgnoreCase(rootLocalName)) {
context.setGeneration("1");
isOrderX = true;
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd", 99, EPart.ox);
xsltFilename = "/xslt/OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B_COMFORT.xslt";
} else if (root.getLocalName().equalsIgnoreCase("CrossIndustryInvoice")) { // ZUGFeRD 2.0 or Factur-X
} else if ("CrossIndustryInvoice".equalsIgnoreCase(rootLocalName)) { // ZUGFeRD 2.0 or Factur-X
context.setGeneration("2");
isMiniumum = context.getProfile().contains("minimum");
isBasic = context.getProfile().contains("basic");
isBasicWithoutLines = context.getProfile().contains("basicwl");
isMiniumum = contextProfile.contains("minimum");
isBasic = contextProfile.contains("basic");
isBasicWithoutLines = contextProfile.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");
isEN16931 = Set.of(
"urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:en16931",
"urn:cen.eu:en16931:2017"
)
.stream()
.anyMatch(profile -> matchesURI(contextProfile, profile));
isExtended = context.getProfile().contains("extended");
isXRechnung = context.getProfile().contains("xrechnung");
isExtended = contextProfile.contains("extended");
isXRechnung = contextProfile.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...
@@ -287,13 +294,13 @@ public class XMLValidator extends Validator {
// saxon java net.sf.saxon.Transform -o tcdl2.0.tsdtf.sch.tmp.xsl -s
// tcdl2.0.tsdtf.sch iso_svrl.xsl
} else if (root.getLocalName().equalsIgnoreCase("Invoice") || root.getLocalName().equalsIgnoreCase("CreditNote")) {
} else if ("Invoice".equalsIgnoreCase(rootLocalName) || rootLocalName.equalsIgnoreCase("CreditNote")) {
context.setGeneration("2");
context.setFormat("UBL");
isXRechnung = context.getProfile().contains("xrechnung");
isXRechnung = contextProfile.contains("xrechnung");
// UBL
LOGGER.debug("UBL");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "UBL_21/maindoc/UBL-" + root.getLocalName() + "-2.1.xsd", 18, EPart.fx);
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "UBL_21/maindoc/UBL-" + rootLocalName + "-2.1.xsd", 18, EPart.fx);
xsltFilename = "/xslt/en16931schematron/EN16931-UBL-validation.xslt";
mainSchematronSectionErrorTypeCode = 24;
@@ -305,8 +312,10 @@ public class XMLValidator extends Validator {
XRechnung is a EN16931 subset so the validation vis a vis FACTUR-X_EN16931.xslt=schematron also has to pass
* */
//validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "ZF_211/EN16931/FACTUR-X_EN16931.xsd", 18, EPart.fx);
String xrVersion = context.getProfile().substring(context.getProfile().length() - 3).replace(".", "");
if (!xrVersion.equals("12") && !xrVersion.equals("20") && !xrVersion.equals("21") && !xrVersion.equals("22") && !xrVersion.equals("23") && !xrVersion.equals("30")) {
String xrVersion = contextProfile.substring(contextProfile.length() - 3).replace(".", "");
Set<String> supportedVersions = Set.of("12", "20", "21", "22", "23", "30");
if (!supportedVersions.contains(xrVersion)) {
throw new Exception("Unsupported XR version");
}
LOGGER.debug("is XRechnung v{}", xrVersion);
@@ -316,14 +325,16 @@ public class XMLValidator extends Validator {
}
} else if (root.getLocalName().equalsIgnoreCase("CrossIndustryDocument")) { // ZUGFeRD 1.0
} else if ("CrossIndustryDocument".equalsIgnoreCase(rootLocalName)) { // ZUGFeRD 1.0
context.setGeneration("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));
Set<String> validZF1Profiles = Set.of(
"urn:ferd:CrossIndustryDocument:invoice:1p0:basic",
"urn:ferd:CrossIndustryDocument:invoice:1p0:comfort",
"urn:ferd:CrossIndustryDocument:invoice:1p0:extended"
);
if (validZF1Profiles.stream().noneMatch(profile -> matchesURI(contextProfile, profile))) {
addUnsupportedProfileResultItem();
}
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "ZF_10/ZUGFeRD1p0.xsd", 18, EPart.fx);
@@ -332,41 +343,41 @@ public class XMLValidator extends Validator {
context.addResultItem(new ValidationResultItem(ESeverity.fatal, "Unsupported root element")
.setSection(3).setPart(EPart.fx));
}
if (context.getFormat().equals("CII")) {
if (context.getGeneration().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));
if ("CII".equals(context.getFormat())) {
if ("2".equals(context.getGeneration())) {
Set<String> validZF2Profiles = Set.of(
"urn:factur-x.eu:1p0:minimum",
"urn:zugferd.de:2p0:minimum",
"urn:factur-x.eu:1p0:basicwl",
"urn:zugferd.de:2p0:basicwl",
"urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic",
"urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic",
"urn:cen.eu:en16931:2017",
"urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended",
"urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended"
);
if (validZF2Profiles.stream().noneMatch(profile -> matchesURI(contextProfile, profile))) {
addUnsupportedProfileResultItem();
}
} else /** v1 */ {
if (isOrderX) {
//order-x 1.0
if ((!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:basic"))
&& (!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:comfort"))
&& (!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:extended"))) {
//zf 1.0
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
.setSection(25).setPart(EPart.fx));
if(Set.of(
"urn:order-x.eu:1p0:basic",
"urn:order-x.eu:1p0:comfort",
"urn:order-x.eu:1p0:extended"
).stream().noneMatch(profile -> matchesURI(contextProfile, profile))) {
addUnsupportedProfileResultItem();
}
} else 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"))) {
//zf 1.0
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
.setSection(25).setPart(EPart.fx));
} else if (Set.of(
"urn:ferd:CrossIndustryDocument:invoice:1p0:basic",
"urn:ferd:CrossIndustryDocument:invoice:1p0:comfort",
"urn:ferd:CrossIndustryDocument:invoice:1p0:extended"
).stream().noneMatch(profile -> matchesURI(contextProfile, profile))) {
//zf 1.0
addUnsupportedProfileResultItem();
}
}
}
@@ -377,7 +388,7 @@ public class XMLValidator extends Validator {
}
if (context.getFormat().equals("CII") && (context.getGeneration().equals("2"))) {
if ("CII".equals(context.getFormat()) && ("2".equals(context.getGeneration()))) {
if (isXRechnung) {
//additionally validate against CEN, the CEN rules are part of the ZF Schematron anyway
@@ -408,10 +419,30 @@ public class XMLValidator extends Validator {
}
final long endTime = Calendar.getInstance().getTimeInMillis();
context.addCustomXML("<info><version>" + ((context.getGeneration() != null) ? context.getGeneration() : "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(getInfoXml(endTime, startXMLTime));
}
private void addUnsupportedProfileResultItem() throws IrrecoverableValidationError {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type " + context.getProfile())
.setSection(25).setPart(EPart.fx));
}
private String getInfoXml(long endTime, long startXMLTime) {
String generation = context.getGeneration() != null ? context.getGeneration() : "invalid";
String profile = context.getProfile() != null ? context.getProfile() : "invalid";
String validatorVersion = XMLValidator.class.getPackage().getImplementationVersion();
long duration = endTime - startXMLTime;
return String.format(
"<info>" +
"<version>%s</version>" +
"<profile>%s</profile>" +
"<validator version=\"%s\"></validator>" +
"<rules><fired>%d</fired><failed>%d</failed></rules>" +
"<duration unit=\"ms\">%d</duration>" +
"</info>",
generation, profile, validatorVersion, firedRules, failedRules, duration
);
}
private void checkArithmetics(ValidationContext context) {
@@ -514,7 +545,7 @@ public class XMLValidator extends Validator {
if (defaultSeverity == ESeverity.notice) {
severity = defaultSeverity;
} else if (currentFailNode.getAttributes().getNamedItem("flag") != null
&& currentFailNode.getAttributes().getNamedItem("flag").getNodeValue().equals("warning")) {
&& "warning".equals(currentFailNode.getAttributes().getNamedItem("flag").getNodeValue())) {
// the XR issues warnings with flag=warning
severity = ESeverity.warning;
} else {