+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
column;
+
+ /**
+ * Gets the value of the column property.
+ *
+ *
+ * This accessor method returns a reference to the live list, not a snapshot. Therefore any
+ * modification you make to the returned list will be present inside the Jakarta XML Binding
+ * object. This is why there is not a set
method for the column property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ *
+ * getColumn().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list {@link ColumnSet.Column }
+ *
+ *
+ */
+ public List getColumn() {
+ if (column == null) {
+ column = new ArrayList<>();
+ }
+ return this.column;
+ }
+
+
+ /**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="Data">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="Lang" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * <attribute name="Id" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="Use" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {"shortName", "data"})
+ public static class Column {
+
+ @XmlElement(name = "ShortName", required = true)
+ protected String shortName;
+ @XmlElement(name = "Data", required = true)
+ protected ColumnSet.Column.Data data;
+ @XmlAttribute(name = "Id", required = true)
+ protected String id;
+ @XmlAttribute(name = "Use", required = true)
+ protected String use;
+
+ /**
+ * Gets the value of the shortName property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getShortName() {
+ return shortName;
+ }
+
+ /**
+ * Sets the value of the shortName property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setShortName(String value) {
+ this.shortName = value;
+ }
+
+ /**
+ * Gets the value of the data property.
+ *
+ * @return possible object is {@link ColumnSet.Column.Data }
+ *
+ */
+ public ColumnSet.Column.Data getData() {
+ return data;
+ }
+
+ /**
+ * Sets the value of the data property.
+ *
+ * @param value allowed object is {@link ColumnSet.Column.Data }
+ *
+ */
+ public void setData(ColumnSet.Column.Data value) {
+ this.data = value;
+ }
+
+ /**
+ * Gets the value of the id property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the id property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setId(String value) {
+ this.id = value;
+ }
+
+ /**
+ * Gets the value of the use property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getUse() {
+ return use;
+ }
+
+ /**
+ * Sets the value of the use property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setUse(String value) {
+ this.use = value;
+ }
+
+
+ /**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="Lang" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "")
+ public static class Data {
+
+ @XmlAttribute(name = "Lang", required = true)
+ protected String lang;
+ @XmlAttribute(name = "Type", required = true)
+ protected String type;
+
+ /**
+ * Gets the value of the lang property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getLang() {
+ return lang;
+ }
+
+ /**
+ * Sets the value of the lang property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setLang(String value) {
+ this.lang = value;
+ }
+
+ /**
+ * Gets the value of the type property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the value of the type property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setType(String value) {
+ this.type = value;
+ }
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/Identification.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/Identification.java
new file mode 100644
index 0000000..b80a83a
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/Identification.java
@@ -0,0 +1,354 @@
+package eu.europa.ted.eforms.sdk.analysis.domain.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="LongName" maxOccurs="unbounded">
+ * <complexType>
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="Identifier" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ * </element>
+ * <element name="Version" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="CanonicalUri" type="{http://www.w3.org/2001/XMLSchema}anyType"/>
+ * <element name="CanonicalVersionUri" type="{http://www.w3.org/2001/XMLSchema}anyType"/>
+ * <element name="Agency">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="LongName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "shortName",
+ "longName",
+ "version",
+ "canonicalUri",
+ "canonicalVersionUri",
+ "agency"
+})
+@XmlRootElement(name = "Identification")
+public class Identification {
+
+ @XmlElement(name = "ShortName", required = true)
+ protected String shortName;
+ @XmlElement(name = "LongName", required = true)
+ protected List longName;
+ @XmlElement(name = "Version", required = true)
+ protected String version;
+ @XmlElement(name = "CanonicalUri", required = true)
+ protected Object canonicalUri;
+ @XmlElement(name = "CanonicalVersionUri", required = true)
+ protected Object canonicalVersionUri;
+ @XmlElement(name = "Agency", required = true)
+ protected Identification.Agency agency;
+
+ /**
+ * Gets the value of the shortName property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getShortName() {
+ return shortName;
+ }
+
+ /**
+ * Sets the value of the shortName property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setShortName(String value) {
+ this.shortName = value;
+ }
+
+ /**
+ * Gets the value of the longName property.
+ *
+ *
+ * This accessor method returns a reference to the live list, not a snapshot. Therefore any
+ * modification you make to the returned list will be present inside the Jakarta XML Binding
+ * object. This is why there is not a set
method for the longName property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ *
+ * getLongName().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list {@link Identification.LongName }
+ *
+ *
+ */
+ public List getLongName() {
+ if (longName == null) {
+ longName = new ArrayList<>();
+ }
+ return this.longName;
+ }
+
+ /**
+ * Gets the value of the version property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the value of the version property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setVersion(String value) {
+ this.version = value;
+ }
+
+ /**
+ * Gets the value of the canonicalUri property.
+ *
+ * @return possible object is {@link Object }
+ *
+ */
+ public Object getCanonicalUri() {
+ return canonicalUri;
+ }
+
+ /**
+ * Sets the value of the canonicalUri property.
+ *
+ * @param value allowed object is {@link Object }
+ *
+ */
+ public void setCanonicalUri(Object value) {
+ this.canonicalUri = value;
+ }
+
+ /**
+ * Gets the value of the canonicalVersionUri property.
+ *
+ * @return possible object is {@link Object }
+ *
+ */
+ public Object getCanonicalVersionUri() {
+ return canonicalVersionUri;
+ }
+
+ /**
+ * Sets the value of the canonicalVersionUri property.
+ *
+ * @param value allowed object is {@link Object }
+ *
+ */
+ public void setCanonicalVersionUri(Object value) {
+ this.canonicalVersionUri = value;
+ }
+
+ /**
+ * Gets the value of the agency property.
+ *
+ * @return possible object is {@link Identification.Agency }
+ *
+ */
+ public Identification.Agency getAgency() {
+ return agency;
+ }
+
+ /**
+ * Sets the value of the agency property.
+ *
+ * @param value allowed object is {@link Identification.Agency }
+ *
+ */
+ public void setAgency(Identification.Agency value) {
+ this.agency = value;
+ }
+
+
+ /**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="LongName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {"shortName", "longName"})
+ public static class Agency {
+
+ @XmlElement(name = "ShortName", required = true)
+ protected String shortName;
+ @XmlElement(name = "LongName", required = true)
+ protected String longName;
+
+ /**
+ * Gets the value of the shortName property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getShortName() {
+ return shortName;
+ }
+
+ /**
+ * Sets the value of the shortName property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setShortName(String value) {
+ this.shortName = value;
+ }
+
+ /**
+ * Gets the value of the longName property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getLongName() {
+ return longName;
+ }
+
+ /**
+ * Sets the value of the longName property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setLongName(String value) {
+ this.longName = value;
+ }
+
+ }
+
+
+ /**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="Identifier" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {"value"})
+ public static class LongName {
+
+ @XmlValue
+ protected String value;
+ @XmlAttribute(name = "Identifier")
+ protected String identifier;
+
+ /**
+ * Gets the value of the value property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the value property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value of the identifier property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ /**
+ * Sets the value of the identifier property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setIdentifier(String value) {
+ this.identifier = value;
+ }
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/Properties.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/Properties.java
new file mode 100644
index 0000000..6a878dd
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/Properties.java
@@ -0,0 +1,170 @@
+package eu.europa.ted.eforms.sdk.analysis.domain.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="comment" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="entry" maxOccurs="unbounded">
+ * <complexType>
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {"comment", "entry"})
+@XmlRootElement(name = "properties")
+public class Properties {
+
+ @XmlElement(required = true)
+ protected String comment;
+ @XmlElement(required = true)
+ protected List entry;
+
+ /**
+ * Gets the value of the comment property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getComment() {
+ return comment;
+ }
+
+ /**
+ * Sets the value of the comment property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setComment(String value) {
+ this.comment = value;
+ }
+
+ /**
+ * Gets the value of the entry property.
+ *
+ *
+ * This accessor method returns a reference to the live list, not a snapshot. Therefore any
+ * modification you make to the returned list will be present inside the Jakarta XML Binding
+ * object. This is why there is not a set
method for the entry property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ *
+ * getEntry().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list {@link Properties.Entry }
+ *
+ *
+ */
+ public List getEntry() {
+ if (entry == null) {
+ entry = new ArrayList();
+ }
+ return this.entry;
+ }
+
+
+ /**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {"value"})
+ public static class Entry {
+
+ @XmlValue
+ protected String value;
+ @XmlAttribute(name = "key", required = true)
+ protected String key;
+
+ /**
+ * Gets the value of the value property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the value property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value of the key property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Sets the value of the key property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setKey(String value) {
+ this.key = value;
+ }
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/SimpleCodeList.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/SimpleCodeList.java
new file mode 100644
index 0000000..3ee9641
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/domain/xml/SimpleCodeList.java
@@ -0,0 +1,230 @@
+package eu.europa.ted.eforms.sdk.analysis.domain.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="Row" maxOccurs="unbounded">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="Value" maxOccurs="unbounded">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="SimpleValue" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </sequence>
+ * <attribute name="ColumnRef" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {"row"})
+@XmlRootElement(name = "SimpleCodeList")
+public class SimpleCodeList {
+ @XmlElement(name = "Row", required = true)
+ protected List row;
+
+ /**
+ * Gets the value of the row property.
+ *
+ *
+ * This accessor method returns a reference to the live list, not a snapshot. Therefore any
+ * modification you make to the returned list will be present inside the Jakarta XML Binding
+ * object. This is why there is not a set
method for the row property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ *
+ * getRow().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list {@link SimpleCodeList.Row }
+ *
+ *
+ */
+ public List getRow() {
+ if (row == null) {
+ row = new ArrayList<>();
+ }
+ return this.row;
+ }
+
+
+ /**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="Value" maxOccurs="unbounded">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="SimpleValue" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </sequence>
+ * <attribute name="ColumnRef" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {"value"})
+ public static class Row {
+
+ @XmlElement(name = "Value", required = true)
+ protected List value;
+
+ /**
+ * Gets the value of the value property.
+ *
+ *
+ * This accessor method returns a reference to the live list, not a snapshot. Therefore any
+ * modification you make to the returned list will be present inside the Jakarta XML Binding
+ * object. This is why there is not a set
method for the value property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ *
+ * getValue().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list {@link SimpleCodeList.Row.Value }
+ *
+ *
+ */
+ public List getValue() {
+ if (value == null) {
+ value = new ArrayList<>();
+ }
+ return this.value;
+ }
+
+
+ /**
+ *
+ * Java class for anonymous complex type.
+ *
+ *
+ * The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="SimpleValue" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </sequence>
+ * <attribute name="ColumnRef" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {"simpleValue"})
+ public static class Value {
+
+ @XmlElement(name = "SimpleValue", required = true)
+ protected String simpleValue;
+ @XmlAttribute(name = "ColumnRef", required = true)
+ protected String columnRef;
+
+ /**
+ * Gets the value of the simpleValue property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getSimpleValue() {
+ return simpleValue;
+ }
+
+ /**
+ * Sets the value of the simpleValue property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setSimpleValue(String value) {
+ this.simpleValue = value;
+ }
+
+ /**
+ * Gets the value of the columnRef property.
+ *
+ * @return possible object is {@link String }
+ *
+ */
+ public String getColumnRef() {
+ return columnRef;
+ }
+
+ /**
+ * Sets the value of the columnRef property.
+ *
+ * @param value allowed object is {@link String }
+ *
+ */
+ public void setColumnRef(String value) {
+ this.columnRef = value;
+ }
+ }
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/RuleUnit.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/RuleUnit.java
new file mode 100644
index 0000000..b0ec336
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/RuleUnit.java
@@ -0,0 +1,11 @@
+package eu.europa.ted.eforms.sdk.analysis.drools;
+
+import java.util.List;
+import org.drools.ruleunits.api.RuleUnitData;
+import org.kie.api.definition.rule.Rule;
+
+public interface RuleUnit extends RuleUnitData {
+ List getFiredRules();
+
+ RuleUnit addFiredRule(Rule rule);
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/RulesRunner.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/RulesRunner.java
new file mode 100644
index 0000000..a623b25
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/RulesRunner.java
@@ -0,0 +1,38 @@
+package eu.europa.ted.eforms.sdk.analysis.drools;
+
+import java.util.Arrays;
+import org.apache.commons.lang3.ArrayUtils;
+import org.drools.ruleunits.api.RuleUnitInstance;
+import org.drools.ruleunits.api.RuleUnitProvider;
+import org.drools.ruleunits.api.conf.RuleConfig;
+import org.kie.api.runtime.rule.Match;
+import org.kie.internal.event.rule.RuleEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RulesRunner {
+ private static final Logger logger = LoggerFactory.getLogger(RulesRunner.class);
+
+ private RulesRunner() {}
+
+ public static T execute(final T unit, String... rulesFilter) {
+ RuleConfig rc = RuleUnitProvider.get().newRuleConfig();
+
+ rc.getRuleEventListeners().add(new RuleEventListener() {
+ @Override
+ public void onAfterMatchFire(Match match) {
+ RuleEventListener.super.onAfterMatchFire(match);
+ unit.addFiredRule(match.getRule());
+ }
+ });
+
+ try (RuleUnitInstance instance = RuleUnitProvider.get().createRuleUnitInstance(unit, rc)) {
+ logger.info("Run query. Rules are also fired");
+
+ instance.fire((Match match) -> ArrayUtils.isEmpty(rulesFilter)
+ || Arrays.asList(rulesFilter).contains(match.getRule().getName()));
+
+ return instance.ruleUnitData();
+ }
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/SdkUnit.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/SdkUnit.java
new file mode 100644
index 0000000..94bba6c
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/drools/SdkUnit.java
@@ -0,0 +1,193 @@
+package eu.europa.ted.eforms.sdk.analysis.drools;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.drools.ruleunits.api.DataStore;
+import org.kie.api.definition.rule.Rule;
+
+import eu.europa.ted.eforms.sdk.analysis.fact.CodelistFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.CodelistsIndexFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.DocumentTypeFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.FieldFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.FieldsAndNodesMetadataFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.LabelFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.NodeFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.NoticeTypeFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.NoticeTypesIndexFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.SvrlReportFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.ViewTemplateFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.XmlNoticeFact;
+import eu.europa.ted.eforms.sdk.analysis.vo.SdkMetadata;
+import eu.europa.ted.eforms.sdk.analysis.vo.ValidationResult;
+
+public class SdkUnit implements RuleUnit {
+ private Path sdkRoot;
+
+ private DataStore codelists;
+ private DataStore codelistsIndex;
+ private DataStore documentTypes;
+ private DataStore xmlNotices;
+ private DataStore fieldsAndNodesMetadata;
+ private DataStore fields;
+ private DataStore labels;
+ private DataStore nodes;
+ private DataStore noticeTypesIndex;
+ private DataStore noticeTypes;
+ private DataStore viewTemplates;
+ private DataStore svrlReports;
+
+ private SdkMetadata sdkMetadata;
+
+ // Global variable to store validations results
+ private final Set results = new HashSet<>();
+
+ private final List firedRules = new ArrayList<>();
+
+ public SdkUnit() {
+ // Default constructor
+ }
+
+ public Path getSdkRoot() {
+ return sdkRoot;
+ }
+
+ public SdkUnit setSdkRoot(Path sdkRoot) {
+ this.sdkRoot = sdkRoot;
+ return this;
+ }
+
+ public DataStore getCodelists() {
+ return codelists;
+ }
+
+ public SdkUnit setCodelists(DataStore codelists) {
+ this.codelists = codelists;
+ return this;
+ }
+
+ public DataStore getCodelistsIndex() {
+ return codelistsIndex;
+ }
+
+ public SdkUnit setCodelistsIndex(DataStore codelistsIndex) {
+ this.codelistsIndex = codelistsIndex;
+ return this;
+ }
+
+ public DataStore getDocumentTypes() {
+ return documentTypes;
+ }
+
+ public SdkUnit setDocumentTypes(DataStore documentTypes) {
+ this.documentTypes = documentTypes;
+ return this;
+ }
+
+ public DataStore getFieldsAndNodesMetadata() {
+ return fieldsAndNodesMetadata;
+ }
+
+ public SdkUnit setFieldsAndNodesMetadata(DataStore metadata) {
+ this.fieldsAndNodesMetadata = metadata;
+ return this;
+ }
+
+ public DataStore getFields() {
+ return fields;
+ }
+
+ public SdkUnit setFields(DataStore fields) {
+ this.fields = fields;
+ return this;
+ }
+
+ public DataStore getLabels() {
+ return labels;
+ }
+
+ public SdkUnit setLabels(DataStore labels) {
+ this.labels = labels;
+ return this;
+ }
+
+ public DataStore getNodes() {
+ return nodes;
+ }
+
+ public SdkUnit setNodes(DataStore nodes) {
+ this.nodes = nodes;
+ return this;
+ }
+
+ public DataStore getNoticeTypesIndex() {
+ return noticeTypesIndex;
+ }
+
+ public SdkUnit setNoticeTypesIndex(DataStore noticeTypesIndex) {
+ this.noticeTypesIndex = noticeTypesIndex;
+ return this;
+ }
+
+ public DataStore getNoticeTypes() {
+ return noticeTypes;
+ }
+
+ public SdkUnit setNoticeTypes(DataStore noticeTypes) {
+ this.noticeTypes = noticeTypes;
+ return this;
+ }
+
+ public DataStore getViewTemplates() {
+ return viewTemplates;
+ }
+
+ public SdkUnit setViewTemplates(DataStore viewTemplates) {
+ this.viewTemplates = viewTemplates;
+ return this;
+ }
+
+ public DataStore getXmlNotices() {
+ return xmlNotices;
+ }
+
+ public SdkUnit setXmlNotices(DataStore xmlExamples) {
+ this.xmlNotices = xmlExamples;
+ return this;
+ }
+
+ public DataStore getSvrlReports() {
+ return svrlReports;
+ }
+
+ public SdkUnit setSvrlReports(DataStore svrlReports) {
+ this.svrlReports = svrlReports;
+ return this;
+ }
+
+ public SdkMetadata getSdkMetadata() {
+ return sdkMetadata;
+ }
+
+ public SdkUnit setSdkMetadata(SdkMetadata sdkMetadata) {
+ this.sdkMetadata = sdkMetadata;
+ return this;
+ }
+
+ public Set getResults() {
+ return results;
+ }
+
+ @Override
+ public List getFiredRules() {
+ return firedRules;
+ }
+
+ @Override
+ public SdkUnit addFiredRule(Rule rule) {
+ firedRules.add(rule);
+ return this;
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/efx/mock/MarkupGeneratorMock.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/efx/mock/MarkupGeneratorMock.java
new file mode 100644
index 0000000..8b834ba
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/efx/mock/MarkupGeneratorMock.java
@@ -0,0 +1,70 @@
+package eu.europa.ted.eforms.sdk.analysis.efx.mock;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import eu.europa.ted.efx.interfaces.MarkupGenerator;
+import eu.europa.ted.efx.model.Expression;
+import eu.europa.ted.efx.model.Expression.PathExpression;
+import eu.europa.ted.efx.model.Expression.StringExpression;
+import eu.europa.ted.efx.model.Markup;
+
+public class MarkupGeneratorMock implements MarkupGenerator {
+ @Override
+ public Markup renderVariableExpression(Expression valueReference) {
+ return new Markup(String.format("eval(%s)", valueReference.script));
+ }
+
+ @Override
+ public Markup renderLabelFromKey(StringExpression key) {
+ return new Markup(String.format("label(%s)", key.script));
+ }
+
+ @Override
+ public Markup renderLabelFromExpression(Expression expression) {
+ return new Markup(String.format("label(%s)", expression.script));
+ }
+
+ @Override
+ public Markup renderFreeText(String freeText) {
+ return new Markup(String.format("text('%s')", freeText));
+ }
+
+ @Override
+ public Markup composeFragmentDefinition(String name, String number, Markup content) {
+ return this.composeFragmentDefinition(name, number, content, new LinkedHashSet<>());
+ }
+
+ public Markup composeFragmentDefinition(String name, String number, Markup content,
+ Set parameters) {
+ if (StringUtils.isBlank(number)) {
+ return new Markup(String.format("let %s(%s) -> { %s }", name,
+ parameters.stream().collect(Collectors.joining(", ")), content.script));
+ }
+ return new Markup(String.format("let %s(%s) -> { #%s: %s }", name,
+ parameters.stream().collect(Collectors.joining(", ")), number, content.script));
+ }
+
+ @Override
+ public Markup renderFragmentInvocation(String name, PathExpression context) {
+ return this.renderFragmentInvocation(name, context, new LinkedHashSet<>());
+ }
+
+ public Markup renderFragmentInvocation(String name, PathExpression context,
+ Set> variables) {
+ return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name,
+ variables.stream()
+ .map(v -> String.format("%s:%s", v.getLeft(), v.getRight()))
+ .collect(Collectors.joining(", "))));
+ }
+
+ @Override
+ public Markup composeOutputFile(List body, List templates) {
+ return new Markup(String.format("%s\n%s",
+ templates.stream().map(t -> t.script).collect(Collectors.joining("\n")),
+ body.stream().map(t -> t.script).collect(Collectors.joining("\n"))));
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/enums/ValidationStatusEnum.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/enums/ValidationStatusEnum.java
new file mode 100644
index 0000000..a7a5e2a
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/enums/ValidationStatusEnum.java
@@ -0,0 +1,5 @@
+package eu.europa.ted.eforms.sdk.analysis.enums;
+
+public enum ValidationStatusEnum {
+ OK, WARNING, ERROR;
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/CodelistFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/CodelistFact.java
new file mode 100644
index 0000000..f9a14ac
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/CodelistFact.java
@@ -0,0 +1,88 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.codelist.Codelist;
+import eu.europa.ted.eforms.sdk.analysis.domain.xml.ColumnSet.Column;
+import eu.europa.ted.eforms.sdk.analysis.domain.xml.SimpleCodeList.Row;
+import eu.europa.ted.eforms.sdk.analysis.domain.xml.SimpleCodeList.Row.Value;
+
+public class CodelistFact implements SdkComponentFact {
+ private static final long serialVersionUID = 597836162298039219L;
+
+ private Codelist codelist;
+
+ public CodelistFact(Codelist codelist) {
+ this.codelist = codelist;
+ }
+
+ public String getParentId() {
+ return codelist.getParentId();
+ }
+
+ public List getCodes() {
+ return codelist.getCodes();
+ }
+
+ public boolean isTailored() {
+ return codelist.getParentId() != null;
+ }
+
+ public List getColumnDefinitions() {
+ return codelist.getColumnDefinitions();
+ }
+
+ public List getRows() {
+ return codelist.getRows();
+ }
+
+ public String getFilename() {
+ return codelist.getFilename();
+ }
+
+ public List getDuplicateCodes() {
+ Set set = new HashSet();
+ return getCodes().stream().filter(c -> !set.add(c)).collect(Collectors.toList());
+ }
+
+ public String getExpectedFilename() {
+ String basename;
+ if (getParentId() == null) {
+ basename = getId();
+ } else {
+ basename = getParentId() + "_" + getId();
+ }
+ return basename + ".gc";
+ }
+
+ public Set getInvalidColumnRefs() {
+ Set result = new HashSet<>();
+
+ // All column identifiers in the column definitions
+ Set colIds = getColumnDefinitions().stream().map(Column::getId).collect(Collectors.toSet());
+
+ getRows().forEach(r -> {
+ // Find any column ref that is not in colIds
+ result.addAll(r.getValue().stream()
+ .map(Value::getColumnRef)
+ .filter(v -> !colIds.contains(v))
+ .collect(Collectors.toSet())
+ );
+ });
+
+ return result;
+ }
+
+ @Override
+ public String getId() {
+ return codelist.getId();
+ }
+
+ @Override
+ public String getTypeName() {
+ return "codelist";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/CodelistsIndexFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/CodelistsIndexFact.java
new file mode 100644
index 0000000..f02c8cd
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/CodelistsIndexFact.java
@@ -0,0 +1,30 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.codelist.CodelistsIndex;
+import eu.europa.ted.eforms.sdk.analysis.domain.codelist.CodelistsIndexItem;
+
+public class CodelistsIndexFact implements SdkComponentFact {
+ private CodelistsIndex codelistIndex;
+
+ public CodelistsIndexFact(CodelistsIndex codelistIndex) {
+ this.codelistIndex = codelistIndex;
+ }
+
+ public List getIndexItems() {
+ return codelistIndex.getCodelists();
+ }
+
+ @Override
+ public String getId() {
+ return StringUtils.EMPTY;
+ }
+
+ @Override
+ public String getTypeName() {
+ return "codelistIndex";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/DocumentTypeFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/DocumentTypeFact.java
new file mode 100644
index 0000000..9e150fd
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/DocumentTypeFact.java
@@ -0,0 +1,37 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
+import eu.europa.ted.eforms.sdk.analysis.domain.noticetype.DocumentType;
+
+public class DocumentTypeFact implements SdkComponentFact {
+ private static final long serialVersionUID = 2293703290220188078L;
+
+ private DocumentType documentType;
+
+ public DocumentTypeFact(DocumentType documentType) {
+ this.documentType = documentType;
+ }
+
+ public String getSchemaLocation() {
+ return documentType.getSchemaLocation();
+ }
+
+ public boolean schemaLocationExists(Path sdkRoot) {
+ return Files
+ .exists(Path.of(Optional.ofNullable(sdkRoot).orElse(Path.of(StringUtils.EMPTY)).toString(),
+ documentType.getSchemaLocation()));
+ }
+
+ @Override
+ public String getId() {
+ return documentType.getId();
+ }
+
+ @Override
+ public String getTypeName() {
+ return "documentType";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/FieldFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/FieldFact.java
new file mode 100644
index 0000000..b9f6763
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/FieldFact.java
@@ -0,0 +1,258 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.StringUtils;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.field.AbstractConstraint;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.AbstractFieldProperty;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.BooleanConstraint;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.Field;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.FieldPrivacy;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.XmlElementPosition;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.XmlStructureNode;
+import eu.europa.ted.eforms.sdk.analysis.util.XPathSplitter;
+
+public class FieldFact implements SdkComponentFact {
+ private static final long serialVersionUID = -8325643682910825716L;
+
+ private Field field;
+
+ private int stepCount = 0;
+
+ public FieldFact(Field field) {
+ this.field = field;
+ }
+
+ public XmlStructureNode getParent() {
+ return field.getParentNode();
+ }
+
+ public String getParentId() {
+ return field.getParentNodeId();
+ }
+
+ public List getXsdSequenceOrder() {
+ return field.getXsdSequenceOrder();
+ }
+
+ public int getXsdSequenceOrderCount() {
+ return getXsdSequenceOrder() == null ? 0 : getXsdSequenceOrder().size();
+ }
+
+ public boolean hasAncestor(String ancestorNodeId) {
+ if (StringUtils.isBlank(ancestorNodeId)) {
+ return false;
+ }
+
+ XmlStructureNode currentAncestor = field.getParentNode();
+
+ while (currentAncestor != null) {
+ if (currentAncestor.getId().equals(ancestorNodeId)) {
+ return true;
+ }
+
+ currentAncestor = currentAncestor.getParent();
+ }
+
+ return false;
+ }
+
+ public BooleanConstraint getForbiddenConstraintWithoutCondition() {
+ if (field.getForbidden() != null) {
+ List constraints = field.getForbidden().getConstraints();
+
+ if (constraints != null) {
+ return constraints.stream()
+ .filter(
+ (BooleanConstraint constraint) -> StringUtils.isBlank(constraint.getCondition()))
+ .findFirst()
+ .orElse(null);
+ }
+ }
+
+ return null;
+ }
+
+ /*
+ * Return the constraint representing when the field is always mandatory.
+ * This is the "mandatory" constraint without a condition, but also without any notice
+ * subtype for which the field can be forbidden.
+ */
+ public BooleanConstraint getAlwaysMandatoryConstraint() {
+ if (field.getMandatory() == null) {
+ return null;
+ }
+
+ List constraints = field.getMandatory().getConstraints();
+
+ if (constraints == null) {
+ return null;
+ }
+
+ // the one mandatory constraint with no condition
+ BooleanConstraint mandatory = constraints.stream()
+ .filter(
+ (BooleanConstraint constraint) -> StringUtils.isBlank(constraint.getCondition()))
+ .findFirst()
+ .orElse(null);
+
+ if (mandatory != null && field.getForbidden() != null) {
+ // If the field can be forbidden for a notice subtype, then it's not always mandatory,
+ // So we remove those notice subtypes from the constraint.
+ mandatory.getNoticeTypes().removeAll(field.getForbidden().getAllNoticeTypeIds());
+ }
+
+ return mandatory;
+ }
+
+ public String getXpathAbsolute() {
+ return field.getXpathAbsolute();
+ }
+
+ public String getXpathRelative() {
+ return field.getXpathRelative();
+ }
+
+ public int getXpathRelativeStepCount() {
+ if (stepCount == 0 && getXpathRelative() != null) {
+ stepCount = XPathSplitter.getStepElementNames(getXpathRelative()).size();
+ }
+ return stepCount;
+ }
+
+ public String getType() {
+ return field.getType();
+ }
+
+ /*
+ * Return a stream of the dynamic properties of the field
+ */
+ private Stream, ?>> getDynamicProperties() {
+ return Stream.of(field.getRepeatable(), field.getForbidden(), field.getMandatory(),
+ field.getCodeList(), field.getPattern(), field.getRangeNumeric(), field.getAssertion(),
+ field.getInChangeNotice(), field.getInContinueProcedure());
+ }
+
+ /**
+ * Return the notices types referenced in all properties of the field.
+ */
+ public Set getAllNoticeTypes() {
+ Set noticeTypes = new HashSet<>();
+
+ // Go over all dynamic properties and collect referenced notice types
+ getDynamicProperties()
+ .forEach(property -> {
+ if (property != null) {
+ noticeTypes.addAll(property.getAllNoticeTypeIds());
+ }
+ });
+
+ return noticeTypes;
+ }
+
+ public String getPrivacyCode() {
+ if (field.getPrivacy() != null) {
+ return field.getPrivacy().getCode().getLiteral();
+ } else {
+ return null;
+ }
+ }
+
+ public String getUnpublishedFieldId() {
+ if (field.getPrivacy() != null) {
+ return field.getPrivacy().getUnpublishedFieldId();
+ } else {
+ return null;
+ }
+ }
+
+ /*
+ * Return the various field identifiers indicated in the "privacy" property.
+ */
+ public Set getReferencedPrivacyFieldIds() {
+ Set fieldReferences = new HashSet<>();
+
+ FieldPrivacy privacy = field.getPrivacy();
+
+ if (privacy != null) {
+ fieldReferences = Stream.of(privacy.getUnpublishedFieldId(), privacy.getReasonCodeFieldId(),
+ privacy.getReasonDescriptionFieldId(), privacy.getPublicationDateFieldId())
+ .collect(Collectors.toSet());
+ }
+
+ return fieldReferences;
+ }
+
+ /**
+ * Returns the list of ancestors, starting from the field: parent node, then grand-parent, etc.
+ * If the structure is incorrect and a node is in its ancestor, this will put the node in the
+ * list and return it, to avoid going into an infinite loop.
+ */
+ public List getAncestors() {
+ List result = new ArrayList<>();
+ XmlStructureNode currentNode = getParent();
+
+ while (currentNode != null) {
+ if (result.contains(currentNode)) {
+ // A node is its own ancestor. Add it to the list of ancestors,
+ // but break to avoid going into an infinite loop.
+ result.add(currentNode);
+ break;
+ }
+ result.add(currentNode);
+
+ currentNode = currentNode.getParent();
+ }
+
+ return result;
+ }
+
+ public List getAllRepeatableAncestors() {
+ List result;
+
+ result = this.getAncestors().stream().filter(n -> n.isRepeatable()).collect(Collectors.toList());
+
+ return result;
+ }
+
+ public String getCodelistId() {
+ if (field.getCodeList() != null) {
+ return field.getCodeList().getValue().getId();
+ }
+ return null;
+ }
+
+ /*
+ * Return all label identifiers referenced by a field
+ */
+ public Set getAllLabelIds() {
+ Set labelIds = new HashSet<>();
+
+ // Go through every property that can contain a reference to a label
+ // Dynamic properties can reference a label, but it is not currently not used.
+ getDynamicProperties().forEach(property -> {
+ if (property != null) {
+ labelIds.add(property.getMessage());
+ // Constraints can also reference a label
+ labelIds.addAll(property.getAllConstraintsLabelIds());
+ }
+ });
+
+ return labelIds;
+ }
+
+ @Override
+ public String getId() {
+ return field.getId();
+ }
+
+ @Override
+ public String getTypeName() {
+ return "field";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/FieldsAndNodesMetadataFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/FieldsAndNodesMetadataFact.java
new file mode 100644
index 0000000..d7bd6b3
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/FieldsAndNodesMetadataFact.java
@@ -0,0 +1,27 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import org.apache.commons.lang3.StringUtils;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.EFormsTrackableEntity;
+
+public class FieldsAndNodesMetadataFact implements SdkComponentFact {
+ private EFormsTrackableEntity metadata;
+
+ public FieldsAndNodesMetadataFact(EFormsTrackableEntity metadata) {
+ this.metadata = metadata;
+ }
+
+ public String getSdkVersion() {
+ return metadata.getSdkVersion();
+ }
+
+ @Override
+ public String getId() {
+ return StringUtils.EMPTY;
+ }
+
+ @Override
+ public String getTypeName() {
+ return "fieldsAndNodesMetadata";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/LabelFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/LabelFact.java
new file mode 100644
index 0000000..26ddca3
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/LabelFact.java
@@ -0,0 +1,23 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.Label;
+
+public class LabelFact implements SdkComponentFact {
+ private static final long serialVersionUID = -8325643682910825716L;
+
+ private Label label;
+
+ public LabelFact(Label label) {
+ this.label = label;
+ }
+
+ @Override
+ public String getId() {
+ return label.getId();
+ }
+
+ @Override
+ public String getTypeName() {
+ return "label";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NodeFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NodeFact.java
new file mode 100644
index 0000000..ac47ccc
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NodeFact.java
@@ -0,0 +1,97 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.field.XmlElementPosition;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.XmlStructureNode;
+import eu.europa.ted.eforms.sdk.analysis.util.XPathSplitter;
+
+public class NodeFact implements SdkComponentFact {
+ private static final long serialVersionUID = -6237630016231337698L;
+
+ private XmlStructureNode node;
+
+ private int stepCount = 0;
+
+ public NodeFact(XmlStructureNode node) {
+ this.node = node;
+ }
+
+ public String getParentId() {
+ return node.getParentId();
+ }
+
+ public XmlStructureNode getParent() {
+ return node.getParent();
+ }
+
+ public List getXsdSequenceOrder() {
+ return node.getXsdSequenceOrder();
+ }
+
+ public int getXsdSequenceOrderCount() {
+ return getXsdSequenceOrder() == null ? 0 : getXsdSequenceOrder().size();
+ }
+
+ public boolean isRepeatable() {
+ return node.isRepeatable();
+ }
+
+ /**
+ * Returns the list of ancestors, starting from the node: the node parent, then grand-parent, etc.
+ * If the structure is incorrect and a node is in its ancestor, this will put the node in the
+ * list and return it, to avoid going into an infinite loop.
+ */
+ public List getAncestors() {
+ List result = new ArrayList<>();
+ XmlStructureNode currentNode = node.getParent();
+
+ while (currentNode != null) {
+ if (result.contains(currentNode)) {
+ // A node is its own ancestor. Add it to the list of ancestors,
+ // but break to avoid going into an infinite loop.
+ result.add(currentNode);
+ break;
+ }
+ result.add(currentNode);
+
+ currentNode = currentNode.getParent();
+ }
+
+ return result;
+ }
+
+ public XmlStructureNode getFirstRepeatableAncestor() {
+ XmlStructureNode result = new XmlStructureNode();
+
+ result = this.getAncestors().stream().filter(n -> n.isRepeatable()).findFirst().orElse(result);
+
+ return result;
+ }
+
+ public String getXpathAbsolute() {
+ return node.getXpathAbsolute();
+ }
+
+ public String getXpathRelative() {
+ return node.getXpathRelative();
+ }
+
+ public int getXpathRelativeStepCount() {
+ if (stepCount == 0 && getXpathRelative() != null) {
+ stepCount = XPathSplitter.getStepElementNames(getXpathRelative()).size();
+ }
+ return stepCount;
+ }
+
+ @Override
+ public String getId() {
+ return node.getId();
+ }
+
+ @Override
+ public String getTypeName() {
+ return "node";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NoticeTypeFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NoticeTypeFact.java
new file mode 100644
index 0000000..24ac7b6
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NoticeTypeFact.java
@@ -0,0 +1,93 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.collections4.CollectionUtils;
+import eu.europa.ted.eforms.sdk.analysis.domain.noticetype.NoticeType;
+import eu.europa.ted.eforms.sdk.analysis.domain.noticetype.NoticeTypeContent;
+import eu.europa.ted.eforms.sdk.analysis.domain.noticetype.enums.NoticeTypeContentType;
+
+public class NoticeTypeFact implements SdkComponentFact {
+ private static final long serialVersionUID = 8298379634322672452L;
+
+ private NoticeType noticeType;
+
+ public NoticeTypeFact(NoticeType noticeType) {
+ this.noticeType = noticeType;
+ }
+
+ public String getSdkVersion() {
+ return noticeType.getSdkVersion();
+ }
+
+ public Set getMetadataFieldsIds() {
+ return noticeType.getMetadata().stream()
+ .filter((NoticeTypeContent content) -> NoticeTypeContentType.FIELD == content
+ .getContentTypeEnum())
+ .map(NoticeTypeContent::getId)
+ .collect(Collectors.toSet());
+ }
+
+ public Set getContentFieldIds() {
+ return noticeType.getContent()
+ .stream()
+ .flatMap(NoticeTypeContent::flattened)
+ .filter((NoticeTypeContent content) -> NoticeTypeContentType.FIELD == content
+ .getContentTypeEnum())
+ .map(NoticeTypeContent::getId)
+ .collect(Collectors.toSet());
+ }
+
+ public Set getFieldIds() {
+ return new HashSet<>(CollectionUtils.union(getMetadataFieldsIds(), getContentFieldIds()));
+ }
+
+ public Set getNodeIds() {
+ return noticeType.getContent()
+ .stream()
+ .flatMap(NoticeTypeContent::flattened)
+ .filter((NoticeTypeContent content) -> NoticeTypeContentType.GROUP == content
+ .getContentTypeEnum())
+ .map(NoticeTypeContent::getNodeId)
+ .collect(Collectors.toSet());
+ }
+
+ public Set getLabelIds() {
+ return noticeType.getContent()
+ .stream()
+ .flatMap(NoticeTypeContent::flattened)
+ .map(NoticeTypeContent::getLabel)
+ .collect(Collectors.toSet());
+ }
+
+ private Set findContentByType(NoticeTypeContentType type) {
+ if (type == null) {
+ return Collections.emptySet();
+ }
+ return noticeType.getContent()
+ .stream()
+ .flatMap(NoticeTypeContent::flattened)
+ .filter((NoticeTypeContent content) -> type == content.getContentTypeEnum())
+ .collect(Collectors.toSet());
+ }
+
+ public Set getGroups() {
+ return findContentByType(NoticeTypeContentType.GROUP);
+ }
+
+ public Set getFields() {
+ return findContentByType(NoticeTypeContentType.FIELD);
+ }
+
+ @Override
+ public String getId() {
+ return noticeType.getNoticeId();
+ }
+
+ @Override
+ public String getTypeName() {
+ return "noticeType";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NoticeTypesIndexFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NoticeTypesIndexFact.java
new file mode 100644
index 0000000..fff2538
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/NoticeTypesIndexFact.java
@@ -0,0 +1,54 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import eu.europa.ted.eforms.sdk.analysis.domain.noticetype.DocumentType;
+import eu.europa.ted.eforms.sdk.analysis.domain.noticetype.NoticeSubTypeForIndex;
+import eu.europa.ted.eforms.sdk.analysis.domain.noticetype.NoticeTypesForIndex;
+
+public class NoticeTypesIndexFact implements SdkComponentFact {
+ private static final long serialVersionUID = 780561935375885850L;
+
+ private NoticeTypesForIndex noticeTypesForIndex;
+
+ public NoticeTypesIndexFact(NoticeTypesForIndex noticeTypesForIndex) {
+ this.noticeTypesForIndex = noticeTypesForIndex;
+ }
+
+ public String getSdkVersion() {
+ return noticeTypesForIndex.getSdkVersion();
+ }
+
+ public Set getLabelIds() {
+ return noticeTypesForIndex.getNoticeSubTypes().stream()
+ .map(NoticeSubTypeForIndex::getLabelId)
+ .collect(Collectors.toSet());
+ }
+
+ public Set getViewTemplateIds() {
+ return noticeTypesForIndex.getNoticeSubTypes().stream()
+ .flatMap(
+ (NoticeSubTypeForIndex noticeSubType) -> noticeSubType.getViewTemplateIds().stream())
+ .collect(Collectors.toSet());
+ }
+
+ public List getNoticeSubTypes() {
+ return noticeTypesForIndex.getNoticeSubTypes();
+ }
+
+ public List getDocumentTypes() {
+ return noticeTypesForIndex.getDocumentTypes();
+ }
+
+ @Override
+ public String getId() {
+ return StringUtils.EMPTY;
+ }
+
+ @Override
+ public String getTypeName() {
+ return "noticeTypesIndex";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/SdkComponentFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/SdkComponentFact.java
new file mode 100644
index 0000000..ccebdf9
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/SdkComponentFact.java
@@ -0,0 +1,8 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import java.io.Serializable;
+import java.lang.reflect.Type;
+import eu.europa.ted.eforms.sdk.analysis.Identifiable;
+
+public interface SdkComponentFact extends Identifiable, Type {
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/SvrlReportFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/SvrlReportFact.java
new file mode 100644
index 0000000..6f0bb9c
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/SvrlReportFact.java
@@ -0,0 +1,37 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import org.apache.commons.lang3.Validate;
+import eu.europa.ted.eforms.sdk.analysis.domain.SvrlReport;
+
+public class SvrlReportFact implements SdkComponentFact {
+ private static final long serialVersionUID = 2253226277261559611L;
+
+ private final SvrlReport svrlReport;
+
+ public SvrlReportFact(SvrlReport svrlReport) {
+ Validate.notNull(svrlReport, "SvrlReport is");
+ this.svrlReport = svrlReport;
+ }
+
+ public int getErrorCount() {
+ return svrlReport.getErrorCount();
+ }
+
+ /**
+ * Returns true if the report is for an XML notice that is intended to be valid.
+ */
+ public boolean shouldBeValid() {
+ return !svrlReport.getFilename().startsWith("INVALID");
+ }
+
+ @Override
+ public String getId() {
+ int extensionIndex = svrlReport.getFilename().lastIndexOf(".");
+ return svrlReport.getFilename().substring(0, extensionIndex);
+ }
+
+ @Override
+ public String getTypeName() {
+ return "svrlReport";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/ViewTemplateFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/ViewTemplateFact.java
new file mode 100644
index 0000000..871e621
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/ViewTemplateFact.java
@@ -0,0 +1,23 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.view.index.TedefoViewTemplateIndex;
+
+public class ViewTemplateFact implements SdkComponentFact {
+ private static final long serialVersionUID = 591669979811179909L;
+
+ private TedefoViewTemplateIndex viewTemplate;
+
+ public ViewTemplateFact(TedefoViewTemplateIndex viewTemplate) {
+ this.viewTemplate = viewTemplate;
+ }
+
+ @Override
+ public String getId() {
+ return viewTemplate.getId();
+ }
+
+ @Override
+ public String getTypeName() {
+ return "viewTemplate";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/XmlNoticeFact.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/XmlNoticeFact.java
new file mode 100644
index 0000000..6dcd9ec
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/fact/XmlNoticeFact.java
@@ -0,0 +1,35 @@
+package eu.europa.ted.eforms.sdk.analysis.fact;
+
+import eu.europa.ted.eforms.sdk.analysis.domain.XmlNotice;
+
+public class XmlNoticeFact implements SdkComponentFact {
+ private static final long serialVersionUID = 7260419598936482554L;
+
+ private final XmlNotice xmlNotice;
+
+ public XmlNoticeFact(XmlNotice xmlNotice) {
+ this.xmlNotice = xmlNotice;
+ }
+
+ public String getCustomizationId() {
+ return xmlNotice.getCustomizationId();
+ }
+
+ /**
+ * Returns true if the XML notice is intended to be a valid notice.
+ */
+ public boolean shouldBeValid() {
+ return !xmlNotice.getFilename().startsWith("INVALID");
+ }
+
+ @Override
+ public String getId() {
+ int extensionIndex = xmlNotice.getFilename().lastIndexOf(".");
+ return xmlNotice.getFilename().substring(0, extensionIndex);
+ }
+
+ @Override
+ public String getTypeName() {
+ return "xmlNotice";
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/SdkMetadataParser.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/SdkMetadataParser.java
new file mode 100644
index 0000000..9b079fc
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/SdkMetadataParser.java
@@ -0,0 +1,38 @@
+package eu.europa.ted.eforms.sdk.analysis.util;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import eu.europa.ted.eforms.sdk.analysis.vo.SdkMetadata;
+
+/**
+ * Utility class to load metadata on the SDK from its POM file.
+ */
+public class SdkMetadataParser {
+ private static final Logger logger = LoggerFactory.getLogger(SdkMetadataParser.class);
+
+ private SdkMetadataParser() {}
+
+ public static SdkMetadata loadSdkMetadata(Path sdkRoot) throws IOException {
+ return loadSdkMetadataFromPomFile(sdkRoot.resolve("pom.xml"));
+ }
+
+ private static SdkMetadata loadSdkMetadataFromPomFile(Path pomFilePath) throws IOException {
+ logger.debug("Loading SDK metadata from file [{}]", pomFilePath);
+
+ MavenXpp3Reader reader = new MavenXpp3Reader();
+ Model model = null;
+ try {
+ model = reader.read(Files.newInputStream(pomFilePath));
+ } catch (XmlPullParserException e) {
+ throw new IOException("Error reading POM from " + pomFilePath, e);
+ }
+
+ return new SdkMetadata(model.getVersion());
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XPathSplitter.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XPathSplitter.java
new file mode 100644
index 0000000..493c0c6
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XPathSplitter.java
@@ -0,0 +1,173 @@
+package eu.europa.ted.eforms.sdk.analysis.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.misc.Interval;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+import eu.europa.ted.efx.xpath.XPath20BaseListener;
+import eu.europa.ted.efx.xpath.XPath20Lexer;
+import eu.europa.ted.efx.xpath.XPath20Parser;
+import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext;
+import eu.europa.ted.efx.xpath.XPath20Parser.StepexprContext;;
+
+public class XPathSplitter extends XPath20BaseListener {
+
+ private final CharStream inputStream;
+ private final LinkedList steps = new LinkedList<>();
+
+ public XPathSplitter(CharStream inputStream) {
+ this.inputStream = inputStream;
+ }
+
+ /**
+ * Parses the given xpath and returns a list containing a {@link StepInfo} for
+ * each step that the XPath is comprised of.
+ */
+ public static List getSteps(String xpath) {
+
+ final CharStream inputStream = CharStreams.fromString(xpath);
+ final XPath20Lexer lexer = new XPath20Lexer(inputStream);
+ final CommonTokenStream tokens = new CommonTokenStream(lexer);
+ final XPath20Parser parser = new XPath20Parser(tokens);
+ final ParseTree tree = parser.xpath();
+
+ final ParseTreeWalker walker = new ParseTreeWalker();
+ final XPathSplitter xpathParser = new XPathSplitter(inputStream);
+ walker.walk(xpathParser, tree);
+
+ return xpathParser.steps;
+ }
+
+ /**
+ * Parses the given xpath and returns a list containing the element name for
+ * each step that the XPath is comprised of.
+ * A step corresponding to an attribute is ignored, and predicates are removed from each element.
+ * So for "a/b/ns:foo[x = y]/@attr" this will return ("a", "b", "ns:foo")
+ */
+ public static List getStepElementNames(String xpath) {
+ return getSteps(xpath).stream()
+ .filter(s -> !s.isAttribute())
+ .map(StepInfo::getElementName)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Helper method that returns the input text that matched a parser rule context. It is useful
+ * because {@link ParserRuleContext#getText()} omits whitespace and other lexer tokens in the
+ * HIDDEN channel.
+ *
+ * @param context
+ * @return
+ */
+ private String getInputText(ParserRuleContext context) {
+ return this.inputStream
+ .getText(new Interval(context.start.getStartIndex(), context.stop.getStopIndex()));
+ }
+
+ int predicateMode = 0;
+
+ private Boolean inPredicateMode() {
+ return predicateMode > 0;
+ }
+
+ @Override
+ public void exitStepexpr(StepexprContext ctx) {
+ if (!inPredicateMode()) {
+ this.steps.offer(new StepInfo(ctx, this::getInputText));
+ }
+ }
+
+ @Override
+ public void enterPredicate(PredicateContext ctx) {
+ this.predicateMode++;
+ }
+
+ @Override
+ public void exitPredicate(PredicateContext ctx) {
+ this.predicateMode--;
+ }
+
+ public class StepInfo {
+ String stepText;
+ List predicates;
+ int a;
+ int b;
+
+ public StepInfo(StepexprContext ctx, Function getInputText) {
+ this.stepText = getInputText.apply(ctx.step());
+ this.predicates =
+ ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList());
+ }
+
+ protected StepInfo(String stepText, List predicates, Interval interval) {
+ this.stepText = stepText;
+ this.predicates = predicates;
+ this.a = interval.a;
+ this.b = interval.b;
+ }
+
+ public String getElementName() {
+ return stepText;
+ }
+
+ public Boolean isAttribute() {
+ return this.stepText.startsWith("@");
+ }
+
+ public Boolean isVariableStep() {
+ return this.stepText.startsWith("$");
+ }
+
+ public String getPredicateText() {
+ return String.join("", this.predicates);
+ }
+
+ public Boolean isTheSameAs(final StepInfo contextStep) {
+
+ // First check the step texts are the different.
+ if (!Objects.equals(contextStep.stepText, this.stepText)) {
+ return false;
+ }
+
+ // If one of the two steps has more predicates that the other,
+ if (this.predicates.size() != contextStep.predicates.size()) {
+ // then the steps are the same is the path has no predicates
+ // or all the predicates of the path are also found in the context.
+ return this.predicates.isEmpty() || contextStep.predicates.containsAll(this.predicates);
+ }
+
+ // If there are no predicates then the steps are the same.
+ if (this.predicates.isEmpty()) {
+ return true;
+ }
+
+ // If there is only one predicate in each step, then we can do a quick comparison.
+ if (this.predicates.size() == 1) {
+ return Objects.equals(contextStep.predicates.get(0), this.predicates.get(0));
+ }
+
+ // Both steps contain multiple predicates.
+ // We need to compare them one by one.
+ // First we make a copy so that we can sort them without affecting the original lists.
+ List pathPredicates = new ArrayList<>(this.predicates);
+ List contextPredicates = new ArrayList<>(contextStep.predicates);
+ Collections.sort(pathPredicates);
+ Collections.sort(contextPredicates);
+ return pathPredicates.equals(contextPredicates);
+ }
+
+ public Boolean isPartOf(Interval interval) {
+ return this.a >= interval.a && this.b <= interval.b;
+ }
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XmlDataExtractor.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XmlDataExtractor.java
new file mode 100644
index 0000000..2a6bd29
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XmlDataExtractor.java
@@ -0,0 +1,110 @@
+package eu.europa.ted.eforms.sdk.analysis.util;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathNodes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+import eu.europa.ted.eforms.sdk.analysis.domain.SvrlReport;
+import eu.europa.ted.eforms.sdk.analysis.domain.XmlNotice;
+
+/**
+ * Extract some specific information from an XML file, by applying some XPaths. This for XML files
+ * that we do not need or want to load as a whole.
+ */
+public class XmlDataExtractor {
+ private static final Logger logger = LoggerFactory.getLogger(XmlDataExtractor.class);
+
+ private static final String XPATH_NOTICE_CUSTOMIZATIONID = "/*/cbc:CustomizationID/text()";
+ private static final String XPATH_SVRL_ERRORS =
+ "/svrl:schematron-output/svrl:failed-assert[@role='ERROR']";
+
+ private XmlDataExtractor() {}
+
+ public static XmlNotice loadXmlNoticeFile(Path xmlFilePath)
+ throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
+ logger.debug("Loading XML notice [{}]", xmlFilePath);
+
+ Document xmlDoc = loadXmlDocument(xmlFilePath);
+
+ String filename = xmlFilePath.getFileName().toString();
+
+ XPath xpath = getXPath();
+ String customizationId =
+ xpath.evaluateExpression(XPATH_NOTICE_CUSTOMIZATIONID, xmlDoc, String.class);
+
+ return new XmlNotice(filename, customizationId.trim());
+ }
+
+ public static SvrlReport loadSvrlReportFile(Path svrlReportPath)
+ throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
+ logger.debug("Loading SVRL report [{}]", svrlReportPath);
+
+ Document xmlDoc = loadXmlDocument(svrlReportPath);
+
+ String filename = svrlReportPath.getFileName().toString();
+
+ XPath xpath = getXPath();
+ XPathNodes nodes = xpath.evaluateExpression(XPATH_SVRL_ERRORS, xmlDoc, XPathNodes.class);
+ int errorCount = nodes.size();
+
+ return new SvrlReport(filename, errorCount);
+ }
+
+ private static XPath getXPath() {
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ xpath.setNamespaceContext(new SimpleNamespaceContext());
+ return xpath;
+ }
+
+ private static Document loadXmlDocument(Path xmlFilePath)
+ throws ParserConfigurationException, SAXException, IOException {
+ final DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
+ // Options for security
+ dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ dbf.setXIncludeAware(false);
+ // For the XPaths to work
+ dbf.setNamespaceAware(true);
+
+ return dbf.newDocumentBuilder().parse(xmlFilePath.toFile());
+ }
+
+ private static final class SimpleNamespaceContext implements NamespaceContext {
+ private Map nsMap;
+
+ public SimpleNamespaceContext() {
+ nsMap = new HashMap();
+ // Only need the namespace prefixes used in the XPath expressions
+ nsMap.put("cbc", "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2");
+ nsMap.put("svrl", "http://purl.oclc.org/dsdl/svrl");
+ }
+
+ @Override
+ public Iterator getPrefixes(String namespaceURI) {
+ return null;
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ return null;
+ }
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ return nsMap.get(prefix);
+ }
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XmlParser.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XmlParser.java
new file mode 100644
index 0000000..caacd8b
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XmlParser.java
@@ -0,0 +1,57 @@
+package eu.europa.ted.eforms.sdk.analysis.util;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Source;
+import javax.xml.transform.sax.SAXSource;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class XmlParser {
+ private static final Logger logger = LoggerFactory.getLogger(XmlParser.class);
+
+ private XmlParser() {
+
+ }
+
+ public static T loadXmlFile(Class clazz, Path xmlFilePath)
+ throws IOException, JAXBException, SAXException, ParserConfigurationException {
+ logger.debug("Loading contents of type [{} from XML file [{}]", clazz.getName(), xmlFilePath);
+
+ if (!Files.isRegularFile(xmlFilePath)) {
+ throw new FileNotFoundException(xmlFilePath.toString());
+ }
+
+ final JAXBContext context = JAXBContext.newInstance(clazz);
+
+ // Disable XXE
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+
+ // Do unmarshall operation
+ Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(),
+ new InputSource(Files.newBufferedReader(xmlFilePath)));
+
+ Unmarshaller unmarshaller = context.createUnmarshaller();
+
+ @SuppressWarnings("unchecked")
+ final T result = (T) unmarshaller.unmarshal(xmlSource);
+ Validate.notNull(result,
+ MessageFormat.format("No data was loaded from XML file [{0}]", xmlFilePath));
+
+ return result;
+ }
+}
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/analysis/validator/EfxValidator.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/validator/EfxValidator.java
new file mode 100644
index 0000000..e4556b5
--- /dev/null
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/validator/EfxValidator.java
@@ -0,0 +1,165 @@
+package eu.europa.ted.eforms.sdk.analysis.validator;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import eu.europa.ted.eforms.sdk.ComponentFactory;
+import eu.europa.ted.eforms.sdk.SdkConstants.SdkResource;
+import eu.europa.ted.eforms.sdk.SdkVersion;
+import eu.europa.ted.eforms.sdk.analysis.SdkLoader;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.Field;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.StringConstraint;
+import eu.europa.ted.eforms.sdk.analysis.domain.field.StringProperty;
+import eu.europa.ted.eforms.sdk.analysis.domain.view.index.TedefoViewTemplateIndex;
+import eu.europa.ted.eforms.sdk.analysis.efx.mock.MarkupGeneratorMock;
+import eu.europa.ted.eforms.sdk.analysis.enums.ValidationStatusEnum;
+import eu.europa.ted.eforms.sdk.analysis.fact.FieldFact;
+import eu.europa.ted.eforms.sdk.analysis.fact.ViewTemplateFact;
+import eu.europa.ted.eforms.sdk.analysis.util.SdkMetadataParser;
+import eu.europa.ted.eforms.sdk.analysis.vo.SdkMetadata;
+import eu.europa.ted.eforms.sdk.analysis.vo.ValidationResult;
+import eu.europa.ted.efx.EfxTranslator;
+import eu.europa.ted.efx.exceptions.ThrowingErrorListener;
+import eu.europa.ted.efx.interfaces.MarkupGenerator;
+import eu.europa.ted.efx.interfaces.ScriptGenerator;
+import eu.europa.ted.efx.interfaces.SymbolResolver;
+import eu.europa.ted.efx.interfaces.TranslatorDependencyFactory;
+
+/**
+ * Validates EFX expressions and templates
+ */
+public class EfxValidator implements Validator {
+ private static final Logger logger = LoggerFactory.getLogger(EfxValidator.class);
+
+ private final Path sdkRoot;
+ private final String sdkVersion;
+
+ private final SdkLoader sdkLoader;
+ private final TranslatorDependencyFactory dependencyFactory;
+
+ private final Set results;
+
+ public EfxValidator(final Path sdkRoot) throws IOException {
+ this.sdkRoot = Validate.notNull(sdkRoot, "Undefined SDK root path");
+
+ if (!Files.isDirectory(sdkRoot)) {
+ throw new FileNotFoundException(sdkRoot.toString());
+ }
+
+ final SdkMetadata sdkMetadata = SdkMetadataParser.loadSdkMetadata(sdkRoot);
+ this.sdkVersion = new SdkVersion(sdkMetadata.getVersion()).toStringWithoutPatch();
+
+ this.dependencyFactory = new DependencyFactory(sdkRoot);
+
+ this.sdkLoader = new SdkLoader(Path.of(sdkRoot.toString()));
+
+ this.results = new HashSet<>();
+ }
+
+ @Override
+ public Validator validate() throws Exception {
+ return validateTemplates().validateExpressions();
+ }
+
+ public EfxValidator validateTemplates() throws IOException {
+ final Set viewTemplates = sdkLoader.getViewTemplates();
+
+ viewTemplates.forEach((TedefoViewTemplateIndex template) -> {
+ final Path templatePath = Path.of(sdkRoot.toString(),
+ SdkResource.VIEW_TEMPLATES.getPath().toString(), template.getFilename());
+
+ logger.debug("Compiling template [{}] using file [{}]", template.getId(), templatePath);
+
+ try {
+ EfxTranslator.translateTemplate(dependencyFactory, sdkVersion, templatePath);
+ } catch (Exception e) {
+ results.add(new ValidationResult(new ViewTemplateFact(template), e.getMessage(),
+ ValidationStatusEnum.ERROR));
+ }
+ });
+
+ return this;
+ }
+
+ public EfxValidator validateExpressions() throws IOException {
+ final List fields = sdkLoader.getFieldsAndNodes().getFields();
+
+ fields.stream()
+ .forEach((Field field) -> getExpressions(field)
+ .forEach((String expression) -> {
+ logger.debug("Translating expression [{}] of assertion constraint of field [{}]",
+ expression, field.getId());
+
+ try {
+ EfxTranslator.translateExpression(dependencyFactory, sdkVersion, expression);
+ } catch (Exception e) {
+ results.add(new ValidationResult(new FieldFact(field), e.getMessage(),
+ ValidationStatusEnum.ERROR));
+ }
+ }));
+
+ return this;
+ }
+
+ private Set getExpressions(final Field field) {
+ return Optional.ofNullable(field)
+ .map(Field::getAssertion)
+ .map(StringProperty::getConstraints)
+ .map((List