diff --git a/pom.xml b/pom.xml
index b91f7b7..d070c9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,7 +36,7 @@
${java.version}
${java.version}
- 1.0.5
+ 1.2.0
2.0.0-alpha.1
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
index 5b5ec0d..c39abd8 100644
--- 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
@@ -15,9 +15,9 @@
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;
import eu.europa.ted.eforms.sdk.analysis.util.XPathUtils;
-import eu.europa.ted.eforms.sdk.analysis.util.XPathSplitter.StepInfo;
+import eu.europa.ted.eforms.xpath.XPathInfo;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
public class FieldFact implements SdkComponentFact {
private static final long serialVersionUID = -8325643682910825716L;
@@ -120,16 +120,22 @@ public String getXpathRelative() {
return field.getXpathRelative();
}
- public int getXpathRelativeStepCount() {
+ public int getXpathRelativeElementCount() {
if (stepCount == 0 && getXpathRelative() != null) {
- stepCount = XPathSplitter.getStepElementNames(getXpathRelative()).size();
+ XPathInfo xpathInfo = XPathProcessor.parse(getXpathRelative());
+ stepCount = xpathInfo.getSteps().size();
+ if (xpathInfo.isAttribute()) {
+ // we don't want to count attributes
+ stepCount--;
+ }
}
return stepCount;
}
public List getInvalidXpathRelativeSteps() {
- List badSteps = XPathSplitter.getStepElementNames(getXpathRelative()).stream()
- .filter(s -> XPathUtils.isAscendingStep(s))
+ List badSteps = XPathProcessor.parse(getXpathRelative()).getSteps().stream()
+ .map(s -> s.getStepText())
+ .filter(x -> XPathUtils.isAscendingStep(x))
.collect(Collectors.toList());
return badSteps;
@@ -143,8 +149,7 @@ public String getType() {
* Return true if the last step of the relative path corresponds to an XML attribute.
*/
public boolean isAttribute() {
- List steps = XPathSplitter.getSteps(getXpathRelative());
- return steps.get(steps.size() - 1).isAttribute();
+ return XPathProcessor.parse(getXpathRelative()).isAttribute();
}
public String getAttributeOf() {
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
index 1576b42..6eefc95 100644
--- 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
@@ -6,8 +6,8 @@
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;
import eu.europa.ted.eforms.sdk.analysis.util.XPathUtils;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
public class NodeFact implements SdkComponentFact {
private static final long serialVersionUID = -6237630016231337698L;
@@ -80,15 +80,16 @@ public String getXpathRelative() {
return node.getXpathRelative();
}
- public int getXpathRelativeStepCount() {
+ public int getXpathRelativeElementCount() {
if (stepCount == 0 && getXpathRelative() != null) {
- stepCount = XPathSplitter.getStepElementNames(getXpathRelative()).size();
+ stepCount = XPathProcessor.parse(getXpathRelative()).getSteps().size();
}
return stepCount;
}
public List getInvalidXpathRelativeSteps() {
- List badSteps = XPathSplitter.getStepElementNames(getXpathRelative()).stream()
+ List badSteps = XPathProcessor.parse(getXpathRelative()).getSteps().stream()
+ .map(s -> s.getStepText())
.filter(s -> XPathUtils.isAscendingStep(s))
.collect(Collectors.toList());
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
deleted file mode 100644
index adfcb5b..0000000
--- a/src/main/java/eu/europa/ted/eforms/sdk/analysis/util/XPathSplitter.java
+++ /dev/null
@@ -1,219 +0,0 @@
-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.AxisstepContext;
-import eu.europa.ted.efx.xpath.XPath20Parser.FilterexprContext;
-import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext;
-
-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 exitAxisstep(AxisstepContext ctx) {
- if (inPredicateMode()) {
- return;
- }
-
- // When we recognize a step, we add it to the queue if is is empty.
- // If the queue is not empty, and the depth of the new step is not smaller than
- // the depth of the last step in the queue, then this step needs to be added to
- // the queue too.
- // Otherwise, the last step in the queue is a sub-expression of the new step,
- // and we need to
- // replace it in the queue with the new step.
- if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) {
- this.steps.offer(new AxisStepInfo(ctx, this::getInputText));
- } else {
- Interval removedInterval = ctx.getSourceInterval();
- while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) {
- this.steps.removeLast();
- }
- this.steps.offer(new AxisStepInfo(ctx, this::getInputText));
- }
- }
-
- @Override
- public void exitFilterexpr(FilterexprContext ctx) {
- if (inPredicateMode()) {
- return;
- }
-
- // Same logic as for axis steps here (sse exitAxisstep).
- if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) {
- this.steps.offer(new FilterStepInfo(ctx, this::getInputText));
- } else {
- Interval removedInterval = ctx.getSourceInterval();
- while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) {
- this.steps.removeLast();
- }
- this.steps.offer(new FilterStepInfo(ctx, this::getInputText));
- }
- }
-
- @Override
- public void enterPredicate(PredicateContext ctx) {
- this.predicateMode++;
- }
-
- @Override
- public void exitPredicate(PredicateContext ctx) {
- this.predicateMode--;
- }
-
- public class AxisStepInfo extends StepInfo {
-
- public AxisStepInfo(AxisstepContext ctx, Function getInputText) {
- super(ctx.reversestep() != null? getInputText.apply(ctx.reversestep()) : getInputText.apply(ctx.forwardstep()),
- ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval());
- }
- }
-
- public class FilterStepInfo extends StepInfo {
-
- public FilterStepInfo(FilterexprContext ctx, Function getInputText) {
- super(getInputText.apply(ctx.primaryexpr()),
- ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval());
- }
- }
-
- public class StepInfo {
- String stepText;
- List predicates;
- int a;
- int b;
-
- 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/validator/XmlSchemaValidator.java b/src/main/java/eu/europa/ted/eforms/sdk/analysis/validator/XmlSchemaValidator.java
index 60d0b6e..f852756 100644
--- a/src/main/java/eu/europa/ted/eforms/sdk/analysis/validator/XmlSchemaValidator.java
+++ b/src/main/java/eu/europa/ted/eforms/sdk/analysis/validator/XmlSchemaValidator.java
@@ -31,8 +31,9 @@
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.NodeFact;
-import eu.europa.ted.eforms.sdk.analysis.util.XPathSplitter;
import eu.europa.ted.eforms.sdk.analysis.vo.ValidationResult;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
+import eu.europa.ted.eforms.xpath.XPathStep;
/**
* Validates the content of the XSD files in the SDK, and their consistency with other files.
@@ -161,8 +162,7 @@ private void checkNodeRepeatability(XmlStructureNode node) {
* removing any predicate.
*/
private String getFirstElementName(String xpath) {
- List names = XPathSplitter.getStepElementNames(xpath);
- return names.get(0);
+ return XPathProcessor.parse(xpath).getSteps().get(0).getStepText();
}
/**
@@ -170,8 +170,8 @@ private String getFirstElementName(String xpath) {
* removing any predicate.
*/
private String getLastElementName(String xpath) {
- List names = XPathSplitter.getStepElementNames(xpath);
- return names.get(names.size() - 1);
+ List steps = XPathProcessor.parse(xpath).getSteps();
+ return steps.get(steps.size() - 1).getStepText();
}
/**
diff --git a/src/main/resources/eu/europa/ted/eforms/sdk/analysis/drools/fieldsAndNodesRules.drl b/src/main/resources/eu/europa/ted/eforms/sdk/analysis/drools/fieldsAndNodesRules.drl
index ff2dc00..85aa237 100644
--- a/src/main/resources/eu/europa/ted/eforms/sdk/analysis/drools/fieldsAndNodesRules.drl
+++ b/src/main/resources/eu/europa/ted/eforms/sdk/analysis/drools/fieldsAndNodesRules.drl
@@ -153,14 +153,14 @@ end
rule "Every field has XSD sequence order consistent with relative xpath"
when
- /fields[ $f: this, xsdSequenceOrderCount != xpathRelativeStepCount ]
+ /fields[ $f: this, xsdSequenceOrderCount != xpathRelativeElementCount ]
then
results.add(new ValidationResult($f, "Field XSD sequence order is inconsistent with relative XPath.", ValidationStatusEnum.ERROR));
end
rule "Every non-root node has XSD sequence order consistent with relative xpath"
when
- /nodes[ $n: this, parentId != null, xsdSequenceOrderCount != xpathRelativeStepCount ]
+ /nodes[ $n: this, parentId != null, xsdSequenceOrderCount != xpathRelativeElementCount ]
then
results.add(new ValidationResult($n, "Node XSD sequence order is inconsistent with relative XPath.", ValidationStatusEnum.ERROR));
end