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