diff --git a/.github/workflows/automate-projects.yml b/.github/workflows/automate-projects.yml
index 3c61af0..ec3166d 100644
--- a/.github/workflows/automate-projects.yml
+++ b/.github/workflows/automate-projects.yml
@@ -14,14 +14,14 @@ jobs:
steps:
- name: add-new-issues-to-organization-based-project-column
if: github.event_name == 'issues' && github.event.action == 'opened'
- uses: alex-page/github-project-automation-plus@v0.8.1
+ uses: alex-page/github-project-automation-plus@v0.8.2
with:
project: CoMPAS Issues Overview Board
column: To do
repo-token: ${{ secrets.ORG_GITHUB_ACTION_SECRET }}
- name: add-new-pull-request-to-organization-based-project-column
if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.action == 'opened'
- uses: alex-page/github-project-automation-plus@v0.8.1
+ uses: alex-page/github-project-automation-plus@v0.8.2
with:
project: CoMPAS Pull Request Overview Board
column: To do
diff --git a/.github/workflows/build-project.yml b/.github/workflows/build-project.yml
index ba64df9..07d5ade 100644
--- a/.github/workflows/build-project.yml
+++ b/.github/workflows/build-project.yml
@@ -47,7 +47,7 @@ jobs:
java-version: '17'
- name: Create custom Maven Settings.xml
- uses: whelk-io/maven-settings-xml-action@v20
+ uses: whelk-io/maven-settings-xml-action@v21
with:
output_file: custom_maven_settings.xml
servers: '[{ "id": "github-packages-compas", "username": "OWNER", "password": "${{ secrets.GITHUB_TOKEN }}" }]'
diff --git a/.github/workflows/release-project.yml b/.github/workflows/release-project.yml
index c160f8f..32e8a75 100644
--- a/.github/workflows/release-project.yml
+++ b/.github/workflows/release-project.yml
@@ -53,7 +53,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Create custom Maven Settings.xml
- uses: whelk-io/maven-settings-xml-action@v20
+ uses: whelk-io/maven-settings-xml-action@v21
with:
output_file: custom_maven_settings.xml
servers: '[{ "id": "github-packages-compas", "username": "OWNER", "password": "${{ secrets.GITHUB_TOKEN }}" }]'
diff --git a/.github/workflows/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml
index b6ca6a0..16e07c2 100644
--- a/.github/workflows/sonarcloud-analysis.yml
+++ b/.github/workflows/sonarcloud-analysis.yml
@@ -57,7 +57,7 @@ jobs:
-Dsonar.projectKey=com-pas_compas-scl-validator \
-Dsonar.organization=com-pas )"
- name: Create custom Maven Settings.xml
- uses: whelk-io/maven-settings-xml-action@v20
+ uses: whelk-io/maven-settings-xml-action@v21
with:
output_file: custom_maven_settings.xml
servers: '[{ "id": "github-packages-compas", "username": "OWNER", "password": "${{ secrets.GITHUB_TOKEN }}" }]'
diff --git a/pom.xml b/pom.xml
index 2ed57be..32fbd32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@ SPDX-License-Identifier: Apache-2.0
0.0.4
0.9.3
- 2.13.0.Final
+ 2.13.3.Final
2.0.3
0.9.1
@@ -105,6 +105,17 @@ SPDX-License-Identifier: Apache-2.0
${compas.core.version}
+
+ xerces
+ xerces
+ 2.4.0
+
+
+ xerces
+ xercesImpl
+ 2.12.2
+
+
org.eclipse.microprofile.openapi
microprofile-openapi-api
diff --git a/validator/pom.xml b/validator/pom.xml
index 6479642..dac8e86 100644
--- a/validator/pom.xml
+++ b/validator/pom.xml
@@ -36,6 +36,15 @@ SPDX-License-Identifier: Apache-2.0
microprofile-openapi-api
+
+ xerces
+ xerces
+
+
+ xerces
+ xercesImpl
+
+
org.slf4j
slf4j-api
@@ -52,6 +61,11 @@ SPDX-License-Identifier: Apache-2.0
junit-jupiter-engine
test
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
com.openpojo
openpojo
diff --git a/validator/src/main/java/org/lfenergy/compas/scl/validator/exception/SclValidatorErrorCode.java b/validator/src/main/java/org/lfenergy/compas/scl/validator/exception/SclValidatorErrorCode.java
index 8b2075e..58eac4b 100644
--- a/validator/src/main/java/org/lfenergy/compas/scl/validator/exception/SclValidatorErrorCode.java
+++ b/validator/src/main/java/org/lfenergy/compas/scl/validator/exception/SclValidatorErrorCode.java
@@ -11,6 +11,7 @@ public class SclValidatorErrorCode {
public static final String NO_SCL_ELEMENT_FOUND_ERROR_CODE = "SVS-0001";
public static final String LOADING_SCL_FILE_ERROR_CODE = "SVS-0002";
public static final String LOADING_XSD_FILE_ERROR_CODE = "SVS-0003";
+ public static final String CREATE_XPATH_ELEMENT_ERROR_CODE = "SVS-0004";
public static final String WEBSOCKET_DECODER_ERROR_CODE = "SVS-0100";
public static final String WEBSOCKET_ENCODER_ERROR_CODE = "SVS-0101";
diff --git a/validator/src/main/java/org/lfenergy/compas/scl/validator/model/ValidationError.java b/validator/src/main/java/org/lfenergy/compas/scl/validator/model/ValidationError.java
index aa57341..a308ed6 100644
--- a/validator/src/main/java/org/lfenergy/compas/scl/validator/model/ValidationError.java
+++ b/validator/src/main/java/org/lfenergy/compas/scl/validator/model/ValidationError.java
@@ -39,6 +39,12 @@ public class ValidationError {
namespace = SCL_VALIDATOR_SERVICE_V1_NS_URI)
private Integer columnNumber;
+ @Schema(description = "The XPath expression to find the element where the validation error occurred",
+ example = "/SCL/Substation[1]/VoltageLevel[1]/Bay[5]")
+ @XmlElement(name = "XPath",
+ namespace = SCL_VALIDATOR_SERVICE_V1_NS_URI)
+ private String xpath;
+
public String getMessage() {
return message;
}
@@ -70,4 +76,12 @@ public Integer getColumnNumber() {
public void setColumnNumber(Integer columnNumber) {
this.columnNumber = columnNumber;
}
+
+ public String getXpath() {
+ return xpath;
+ }
+
+ public void setXpath(String xPath) {
+ this.xpath = xPath;
+ }
}
diff --git a/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/SclInfo.java b/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/SclInfo.java
index 59fec48..035bd10 100644
--- a/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/SclInfo.java
+++ b/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/SclInfo.java
@@ -3,58 +3,18 @@
// SPDX-License-Identifier: Apache-2.0
package org.lfenergy.compas.scl.validator.xsd;
-import org.lfenergy.compas.scl.validator.exception.SclValidatorException;
-
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.events.StartElement;
-import javax.xml.stream.events.XMLEvent;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-import static org.lfenergy.compas.scl.validator.exception.SclValidatorErrorCode.LOADING_SCL_FILE_ERROR_CODE;
-import static org.lfenergy.compas.scl.validator.util.StaxUtil.getAttributeValue;
-import static org.lfenergy.compas.scl.validator.util.StaxUtil.isElement;
+import org.w3c.dom.Document;
public class SclInfo {
- private static final String SCL_ELEMENT_NAME = "SCL";
-
- private String version = null;
- private String revision = null;
- private String release = null;
-
- public SclInfo(String sclData) {
- try (var fis = new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8))) {
- var xmlInputFactory = getXMLInputFactory();
- var reader = xmlInputFactory.createXMLEventReader(fis);
-
- while (reader.hasNext()) {
- processEvent(reader.nextEvent());
- }
- } catch (IOException | XMLStreamException exp) {
- throw new SclValidatorException(LOADING_SCL_FILE_ERROR_CODE, "Error loading SCL File", exp);
- }
- }
-
- private void processEvent(XMLEvent nextEvent) {
- if (nextEvent.isStartElement()) {
- processStartElement(nextEvent.asStartElement());
- }
- }
-
- private void processStartElement(StartElement element) {
- if (isElement(element, SCL_ELEMENT_NAME)) {
- version = getAttributeValue(element, "version");
- revision = getAttributeValue(element, "revision");
- release = getAttributeValue(element, "release");
- }
- }
-
- private XMLInputFactory getXMLInputFactory() {
- var xmlInputFactory = XMLInputFactory.newInstance();
- xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
- return xmlInputFactory;
+ private String version;
+ private String revision;
+ private String release;
+
+ public SclInfo(Document doc) {
+ var sclElement = doc.getDocumentElement();
+ version = sclElement.getAttribute("version");
+ revision = sclElement.getAttribute("revision");
+ release = sclElement.getAttribute("release");
}
public String getSclVersion() {
diff --git a/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandler.java b/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandler.java
index df8535c..cba4c86 100644
--- a/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandler.java
+++ b/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandler.java
@@ -3,66 +3,71 @@
// SPDX-License-Identifier: Apache-2.0
package org.lfenergy.compas.scl.validator.xsd;
+import org.apache.xerces.impl.Constants;
+import org.lfenergy.compas.scl.validator.exception.SclValidatorException;
import org.lfenergy.compas.scl.validator.model.ValidationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
+import javax.xml.validation.Validator;
import java.util.List;
+import static org.lfenergy.compas.scl.validator.exception.SclValidatorErrorCode.CREATE_XPATH_ELEMENT_ERROR_CODE;
+
public class XSDErrorHandler implements ErrorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(XSDErrorHandler.class);
public static final String DEFAULT_PREFIX = "XSD/";
public static final String DEFAULT_RULE_NAME = DEFAULT_PREFIX + "general";
- private List errorList;
+ private final Validator validator;
+ private final List errorList;
- public XSDErrorHandler(List errorList) {
+ public XSDErrorHandler(Validator validator, List errorList) {
+ this.validator = validator;
this.errorList = errorList;
}
@Override
- public void warning(SAXParseException exception) {
+ public void warning(SAXParseException exception) throws SAXException {
var validationError = createValidationError(exception);
errorList.add(validationError);
- LOGGER.debug("XSD Validation - warning: '{}' (Line number {}, Column number {})",
+ LOGGER.debug("XSD Validation - warning: '{}' (XPath {})",
validationError.getMessage(),
- validationError.getLineNumber(),
- validationError.getColumnNumber());
+ validationError.getXpath());
}
@Override
- public void error(SAXParseException exception) {
+ public void error(SAXParseException exception) throws SAXException {
var validationError = createValidationError(exception);
errorList.add(validationError);
- LOGGER.debug("XSD Validation - error: '{}' (Line number {}, Column number {})",
+ LOGGER.debug("XSD Validation - error: '{}' (XPath {})",
validationError.getMessage(),
- validationError.getLineNumber(),
- validationError.getColumnNumber());
+ validationError.getXpath());
}
@Override
- public void fatalError(SAXParseException exception) {
+ public void fatalError(SAXParseException exception) throws SAXException {
var validationError = createValidationError(exception);
errorList.add(validationError);
- LOGGER.debug("XSD Validation - fatal error, stopping: '{}' (Line number {}, Column number {})",
+ LOGGER.debug("XSD Validation - fatal error, stopping: '{}' (XPath {})",
validationError.getMessage(),
- validationError.getLineNumber(),
- validationError.getColumnNumber());
+ validationError.getXpath());
}
- private ValidationError createValidationError(SAXParseException exception) {
+ private ValidationError createValidationError(SAXParseException exception) throws SAXException {
var validationError = new ValidationError();
var xsdMessage = exception.getMessage();
validationError.setMessage(getMessage(xsdMessage));
validationError.setRuleName(getRuleName(xsdMessage));
- validationError.setLineNumber(exception.getLineNumber());
- validationError.setColumnNumber(exception.getColumnNumber());
+ validationError.setXpath(getXPath(getCurrentNode()));
return validationError;
}
@@ -93,4 +98,36 @@ String getMessage(String xsdMessage) {
}
return message;
}
+
+ String getXPath(Node node) {
+ if (node != null) {
+ var parent = node.getParentNode();
+ if (parent != null && parent != node.getOwnerDocument()) {
+ return getXPath(parent) + "/" + node.getNodeName() + "[" + getIndex(parent, node) + "]";
+ }
+ return "/" + node.getNodeName();
+ }
+ return null;
+ }
+
+ int getIndex(Node parent, Node child) {
+ var children = parent.getChildNodes();
+ var index = 0;
+ for (int i = 0; i < children.getLength(); i++) {
+ var listItem = children.item(i);
+ if (listItem.getNodeName().equals(child.getNodeName())) {
+ index++;
+ if (listItem == child) {
+ return index;
+ }
+ }
+ }
+ throw new SclValidatorException(CREATE_XPATH_ELEMENT_ERROR_CODE, "Error determining index of child element");
+ }
+
+ private Node getCurrentNode() throws SAXException {
+ // Get prop "http://apache.org/xml/properties/dom/current-element-node"
+ // See https://xerces.apache.org/xerces2-j/properties.html#dom.current-element-node
+ return (Node) validator.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.CURRENT_ELEMENT_NODE_PROPERTY);
+ }
}
diff --git a/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDValidator.java b/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDValidator.java
index d2e6302..16aca48 100644
--- a/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDValidator.java
+++ b/validator/src/main/java/org/lfenergy/compas/scl/validator/xsd/XSDValidator.java
@@ -3,47 +3,67 @@
// SPDX-License-Identifier: Apache-2.0
package org.lfenergy.compas.scl.validator.xsd;
+import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
import org.lfenergy.compas.scl.validator.exception.SclValidatorException;
import org.lfenergy.compas.scl.validator.model.ValidationError;
import org.lfenergy.compas.scl.validator.xsd.resourceresolver.ResourceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.xml.sax.InputSource;
+import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
-import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
import java.util.List;
+import static org.lfenergy.compas.scl.validator.exception.SclValidatorErrorCode.LOADING_SCL_FILE_ERROR_CODE;
import static org.lfenergy.compas.scl.validator.exception.SclValidatorErrorCode.LOADING_XSD_FILE_ERROR_CODE;
public class XSDValidator {
private static final Logger LOGGER = LoggerFactory.getLogger(XSDValidator.class);
private final Validator validator;
- private final String sclData;
+ private final Document doc;
public XSDValidator(List errorList, String sclData) {
- this.sclData = sclData;
+ try {
+ // Load the SCL XML as Document with Xerces. This way Xerces is able to return the Element which is
+ // related to the Validation Error that occurred. The Element will be converted to a generic XPath string.
+ var dbf = DocumentBuilderFactory.newInstance(
+ DocumentBuilderFactoryImpl.class.getName(),
+ XSDValidator.class.getClassLoader());
+ dbf.setNamespaceAware(true);
+
+ this.doc = dbf.newDocumentBuilder().parse(
+ new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8)));
+ } catch (SAXException | IOException | ParserConfigurationException exception) {
+ throw new SclValidatorException(LOADING_SCL_FILE_ERROR_CODE, exception.getMessage());
+ }
- var info = new SclInfo(sclData);
+ // Use the Document to retrieve the SCL Schema Version
+ var info = new SclInfo(this.doc);
var sclVersion = info.getSclVersion();
try {
+ // Retrieve both SCL and CoMPAS XSD Files to be used for XSD Validation.
var sclXsd = new StreamSource(getClass().getClassLoader().getResourceAsStream("xsd/SCL" + sclVersion + "/SCL.xsd"));
var compasXsd = new StreamSource(getClass().getClassLoader().getResourceAsStream("xsd/SCL_CoMPAS.xsd"));
var factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
factory.setResourceResolver(new ResourceResolver(sclVersion));
var schema = factory.newSchema(new Source[]{sclXsd, compasXsd});
+
validator = schema.newValidator();
- validator.setErrorHandler(new XSDErrorHandler(errorList));
+ validator.setErrorHandler(new XSDErrorHandler(validator, errorList));
} catch (SAXException exception) {
throw new SclValidatorException(LOADING_XSD_FILE_ERROR_CODE, exception.getMessage());
}
@@ -51,7 +71,7 @@ public XSDValidator(List errorList, String sclData) {
public void validate() {
try {
- SAXSource source = new SAXSource(new InputSource(new StringReader(sclData)));
+ var source = new DOMSource(doc.getDocumentElement());
validator.validate(source);
} catch (IOException | SAXException exception) {
LOGGER.error("[XSD validation] Exception: {}", exception.getMessage());
diff --git a/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/SclInfoTest.java b/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/SclInfoTest.java
index 8009032..8addbff 100644
--- a/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/SclInfoTest.java
+++ b/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/SclInfoTest.java
@@ -4,42 +4,43 @@
package org.lfenergy.compas.scl.validator.xsd;
import org.junit.jupiter.api.Test;
-import org.lfenergy.compas.scl.validator.exception.SclValidatorException;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
-import java.io.File;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
-import java.nio.file.Files;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.lfenergy.compas.scl.validator.exception.SclValidatorErrorCode.LOADING_SCL_FILE_ERROR_CODE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
class SclInfoTest {
@Test
- void constructor_WhenCalledWithInvalidSclFile_ThenExceptionThrownDuringConstruction() throws IOException {
- var scdFile = new File(getClass().getResource("/scl/invalid.scd").getFile());
-
- var path = scdFile.toPath();
- var content = Files.readString(path);
-
- var exception = assertThrows(SclValidatorException.class, () -> new SclInfo(content));
- assertEquals(LOADING_SCL_FILE_ERROR_CODE, exception.getErrorCode());
- }
-
- @Test
- void getSclVersion_WhenCalledWithValidSclFile_ThenSclVersionFromFileReturned() throws IOException {
- var scdFile = new File(getClass().getResource("/scl/example.scd").getFile());
- var sclInfo = new SclInfo(Files.readString(scdFile.toPath()));
+ void getSclVersion_WhenCalledWithValidSclFile_ThenSclVersionFromFileReturned()
+ throws IOException, ParserConfigurationException, SAXException {
+ var document = getDocument("/scl/example.scd");
+ var sclInfo = new SclInfo(document);
assertNotNull(sclInfo.getSclVersion());
assertEquals("2007B4", sclInfo.getSclVersion());
}
@Test
- void getSclVersion_WhenCalledWithSclFileWithMissingVersion_ThenSclVersionFromFileReturned() throws IOException {
- var scdFile = new File(getClass().getResource("/scl/validation/example-with-missing-version.scd").getFile());
- var sclInfo = new SclInfo(Files.readString(scdFile.toPath()));
+ void getSclVersion_WhenCalledWithSclFileWithMissingVersion_ThenSclVersionFromFileReturned()
+ throws IOException, ParserConfigurationException, SAXException {
+ var document = getDocument("/scl/validation/example-with-missing-version.scd");
+ var sclInfo = new SclInfo(document);
assertNotNull(sclInfo.getSclVersion());
assertEquals("", sclInfo.getSclVersion());
}
+
+ private Document getDocument(String filename) throws IOException, ParserConfigurationException, SAXException {
+ try (var inputStream = getClass().getResourceAsStream(filename)) {
+ assertNotNull(inputStream);
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ return dbf.newDocumentBuilder().parse(inputStream);
+ }
+ }
}
diff --git a/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandlerTest.java b/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandlerTest.java
index b1532d5..1909a0e 100644
--- a/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandlerTest.java
+++ b/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDErrorHandlerTest.java
@@ -3,61 +3,99 @@
// SPDX-License-Identifier: Apache-2.0
package org.lfenergy.compas.scl.validator.xsd;
+import org.apache.xerces.impl.Constants;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.lfenergy.compas.scl.validator.exception.SclValidatorException;
import org.lfenergy.compas.scl.validator.model.ValidationError;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.validation.Validator;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.lfenergy.compas.scl.validator.exception.SclValidatorErrorCode.CREATE_XPATH_ELEMENT_ERROR_CODE;
import static org.lfenergy.compas.scl.validator.xsd.XSDErrorHandler.DEFAULT_PREFIX;
import static org.lfenergy.compas.scl.validator.xsd.XSDErrorHandler.DEFAULT_RULE_NAME;
+import static org.mockito.Mockito.*;
+@ExtendWith(MockitoExtension.class)
class XSDErrorHandlerTest {
+ @Mock
+ private Validator validator;
+
private List errorList = new ArrayList<>();
- private XSDErrorHandler handler = new XSDErrorHandler(errorList);
+ private Document doc;
+
+ private XSDErrorHandler handler;
+
+ @BeforeEach
+ void setup() throws IOException, ParserConfigurationException, SAXException {
+ handler = new XSDErrorHandler(validator, errorList);
+
+ try (var inputStream = getClass()
+ .getResourceAsStream("/scl/validation/example-without-xsd-validation-errors.scd")) {
+ assertNotNull(inputStream);
+
+ var dbf = DocumentBuilderFactory.newInstance();
+ doc = dbf.newDocumentBuilder().parse(inputStream);
+ }
+ }
@Test
- void warning_WhenCalled_ThenNewValidationErrorAddedToList() {
- var message = "Some Warning Message";
- var lineNumber = 3;
- var columnNumber = 15;
+ void warning_WhenCalled_ThenNewValidationErrorAddedToList() throws SAXException {
+ var message = "Some warning message";
- handler.warning(new SAXParseException(message, "", "", lineNumber, columnNumber));
+ when(validator.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.CURRENT_ELEMENT_NODE_PROPERTY))
+ .thenReturn(doc.getDocumentElement());
- assertEquals(1, errorList.size());
- assertValidationError(errorList.get(0), message, lineNumber, columnNumber);
+ handler.warning(new SAXParseException(message, null));
+
+ assertValidationError(errorList, message);
}
@Test
- void error_WhenCalled_ThenNewValidationErrorAddedToList() {
- var message = "Some Error Message";
- var lineNumber = 5;
- var columnNumber = 25;
+ void error_WhenCalled_ThenNewValidationErrorAddedToList() throws SAXException {
+ var message = "Some error message";
- handler.error(new SAXParseException(message, "", "", lineNumber, columnNumber));
+ when(validator.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.CURRENT_ELEMENT_NODE_PROPERTY))
+ .thenReturn(doc.getDocumentElement());
- assertEquals(1, errorList.size());
- assertValidationError(errorList.get(0), message, lineNumber, columnNumber);
+ handler.error(new SAXParseException(message, null));
+
+ assertValidationError(errorList, message);
}
@Test
- void fatalError_WhenCalled_ThenNewValidationErrorAddedToList() {
- var message = "Some Error Message";
- var lineNumber = 5;
- var columnNumber = 25;
+ void fatalError_WhenCalled_ThenNewValidationErrorAddedToList() throws SAXException {
+ var message = "Some fatal error message";
- handler.fatalError(new SAXParseException(message, "", "", lineNumber, columnNumber));
+ when(validator.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.CURRENT_ELEMENT_NODE_PROPERTY))
+ .thenReturn(doc.getDocumentElement());
- assertEquals(1, errorList.size());
- assertValidationError(errorList.get(0), message, lineNumber, columnNumber);
+ handler.fatalError(new SAXParseException(message, null));
+
+ assertValidationError(errorList, message);
}
- private void assertValidationError(ValidationError validationError, String message, int lineNumber, int columnNumber) {
- assertEquals(message, validationError.getMessage());
- assertEquals(lineNumber, validationError.getLineNumber());
- assertEquals(columnNumber, validationError.getColumnNumber());
+ private void assertValidationError(List errorList, String message) throws SAXException {
+ assertEquals(1, errorList.size());
+ assertEquals(message, errorList.get(0).getMessage());
+ assertEquals("/SCL", errorList.get(0).getXpath());
+ assertNull(errorList.get(0).getLineNumber());
+ assertNull(errorList.get(0).getColumnNumber());
+
+ verify(validator, times(1)).getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.CURRENT_ELEMENT_NODE_PROPERTY);
}
@Test
@@ -135,4 +173,78 @@ void getMessage_WhenXSDMessageContainsRule_ThenRuleNameIsStrippedFromMessage() {
assertEquals(expectedMessage, message);
}
+
+ @Test
+ void getXPath_WhenPassingNull_ThenNullReturned() {
+ assertNull(handler.getXPath(null));
+ }
+
+ @Test
+ void getXPath_WhenPassingSCLElement_ThenExpectedXPathReturned() {
+ assertEquals("/SCL", handler.getXPath(doc.getDocumentElement()));
+ }
+
+ @Test
+ void getXPath_WhenPassingChildElement_ThenExpectedXPathReturned() {
+ var firstIed = getFirstChild(doc.getDocumentElement(), "IED");
+
+ assertEquals("/SCL/IED[1]", handler.getXPath(firstIed));
+ }
+
+ @Test
+ void getIndex_WhenPassingParentAndChildElement_ThenExpectedIndexReturned() {
+ var scl = doc.getDocumentElement();
+
+ var firstIed = getFirstChild(scl, "IED");
+ assertEquals(1, handler.getIndex(scl, firstIed));
+
+ var firstDataTypeTemplates = getFirstChild(scl, "DataTypeTemplates");
+ assertEquals(1, handler.getIndex(scl, firstDataTypeTemplates));
+ }
+
+ @Test
+ void getIndex_WhenPassingParentAndSecondChildElement_ThenExpectedIndexReturned() {
+ var scl = doc.getDocumentElement();
+ var dataTypeTemplates = getFirstChild(scl, "DataTypeTemplates");
+
+ Node foundChild = null;
+ var children = dataTypeTemplates.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ var child = children.item(i);
+ if ("LNodeType".equals(child.getNodeName())) {
+ if (foundChild == null) {
+ // First child found, so assign it and continue.
+ foundChild = child;
+ continue;
+ }
+ // This will be the second child.
+ foundChild = child;
+ break;
+ }
+ }
+ assertEquals(2, handler.getIndex(dataTypeTemplates, foundChild));
+ }
+
+ @Test
+ void getIndex_WhenPassingInvalidChild_ThenExceptionIsThrown() {
+ var scl = doc.getDocumentElement();
+ var dataTypeTemplates = getFirstChild(scl, "DataTypeTemplates");
+ var lNodeType = getFirstChild(dataTypeTemplates, "LNodeType");
+
+ var exception = assertThrows(SclValidatorException.class, () -> handler.getIndex(scl, lNodeType));
+ assertEquals(CREATE_XPATH_ELEMENT_ERROR_CODE, exception.getErrorCode());
+ }
+
+ private Node getFirstChild(Node parent, String tagName) {
+ Node firstChild = null;
+ var children = parent.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ var child = children.item(i);
+ if (tagName.equals(child.getNodeName())) {
+ firstChild = child;
+ break;
+ }
+ }
+ return firstChild;
+ }
}
\ No newline at end of file
diff --git a/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDValidatorTest.java b/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDValidatorTest.java
index 6f53466..0835359 100644
--- a/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDValidatorTest.java
+++ b/validator/src/test/java/org/lfenergy/compas/scl/validator/xsd/XSDValidatorTest.java
@@ -11,8 +11,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
import static org.lfenergy.compas.scl.validator.exception.SclValidatorErrorCode.LOADING_XSD_FILE_ERROR_CODE;
class XSDValidatorTest {
@@ -21,6 +20,8 @@ void validate_WhenCalledWithSclDataWithoutXsdValidationErrors_ThenNoErrorsAreRet
var errorList = new ArrayList();
try (var inputStream = getClass()
.getResourceAsStream("/scl/validation/example-without-xsd-validation-errors.scd")) {
+ assertNotNull(inputStream);
+
var data = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
new XSDValidator(errorList, data).validate();
@@ -33,6 +34,8 @@ void validate_WhenCalledWithSclDataWithXsdValidationErrors_ThenErrorsAreRetrieve
var errorList = new ArrayList();
try (var inputStream = getClass()
.getResourceAsStream("/scl/validation/example-with-xsd-validation-errors.scd")) {
+ assertNotNull(inputStream);
+
var data = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
new XSDValidator(errorList, data).validate();
@@ -41,8 +44,7 @@ void validate_WhenCalledWithSclDataWithXsdValidationErrors_ThenErrorsAreRetrieve
var error = errorList.get(2);
assertEquals("Attribute 'name' must appear on element 'BDA'.", error.getMessage());
assertEquals("XSD/cvc-complex-type.4", error.getRuleName());
- assertEquals(66, error.getLineNumber());
- assertEquals(45, error.getColumnNumber());
+ assertEquals("/SCL/DataTypeTemplates[1]/DAType[1]/BDA[2]", error.getXpath());
}
}
@@ -51,6 +53,8 @@ void validate_WhenCalledWithSclDataWithCompasXsdValidationErrors_ThenErrorsAreRe
var errorList = new ArrayList();
try (var inputStream = getClass()
.getResourceAsStream("/scl/validation/example-with-compas-validation-errors.scd")) {
+ assertNotNull(inputStream);
+
var data = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
new XSDValidator(errorList, data).validate();
@@ -60,15 +64,13 @@ void validate_WhenCalledWithSclDataWithCompasXsdValidationErrors_ThenErrorsAreRe
assertEquals("Value 'INVALID' is not facet-valid with respect to enumeration '[SSD, IID, ICD, SCD, CID, " +
"SED, ISD, STD]'. It must be a value from the enumeration.", error.getMessage());
assertEquals("XSD/cvc-enumeration-valid", error.getRuleName());
- assertEquals(10, error.getLineNumber());
- assertEquals(57, error.getColumnNumber());
+ assertEquals("/SCL/Private[1]/compas:SclFileType[1]", error.getXpath());
error = errorList.get(2);
assertEquals("Value 'Invalid Label' is not facet-valid with respect to pattern '[A-Za-z][0-9A-Za-z_-]*' " +
"for type 'tCompasLabel'.", error.getMessage());
assertEquals("XSD/cvc-pattern-valid", error.getRuleName());
- assertEquals(12, error.getLineNumber());
- assertEquals(55, error.getColumnNumber());
+ assertEquals("/SCL/Private[1]/compas:Labels[1]/compas:Label[1]", error.getXpath());
}
}
@@ -77,6 +79,8 @@ void validate_WhenCalledWithSclDataContainingInvalidVersion_ThenExceptionIsThrow
var errorList = new ArrayList();
try (var inputStream = getClass()
.getResourceAsStream("/scl/validation/example-with-wrong-version.scd")) {
+ assertNotNull(inputStream);
+
var data = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
var exception = assertThrows(SclValidatorException.class, () -> new XSDValidator(errorList, data));
@@ -89,6 +93,8 @@ void validate_WhenCalledWithSclDataContainingMissingVersion_ThenExceptionIsThrow
var errorList = new ArrayList();
try (var inputStream = getClass()
.getResourceAsStream("/scl/validation/example-with-missing-version.scd")) {
+ assertNotNull(inputStream);
+
var data = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
var exception = assertThrows(SclValidatorException.class, () -> new XSDValidator(errorList, data));