diff --git a/rewrite-xml/src/main/antlr/XPathLexer.g4 b/rewrite-xml/src/main/antlr/XPathLexer.g4 new file mode 100644 index 0000000000..dbc9b3800c --- /dev/null +++ b/rewrite-xml/src/main/antlr/XPathLexer.g4 @@ -0,0 +1,106 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * XPath lexer for a limited subset of XPath expressions. + * Supports absolute and relative paths, wildcards, predicates, + * attribute access, and common XPath functions. + */ +lexer grammar XPathLexer; + +// Whitespace +WS : [ \t\r\n]+ -> skip ; + +// Path separators +SLASH : '/' ; +DOUBLE_SLASH : '//' ; +AXIS_SEP : '::' ; + +// Brackets +LBRACKET : '[' ; +RBRACKET : ']' ; +LPAREN : '(' ; +RPAREN : ')' ; + +// Operators +AT : '@' ; +DOTDOT : '..' ; // Must come before DOT for proper lexing +DOT : '.' ; +COMMA : ',' ; +EQUALS : '=' ; +NOT_EQUALS : '!=' ; +LTE : '<=' ; // Must come before LT for proper lexing +GTE : '>=' ; // Must come before GT for proper lexing +LT : '<' ; +GT : '>' ; +WILDCARD : '*' ; + +// Numbers +NUMBER : [0-9]+ ('.' [0-9]+)? ; + +// Logical operators (for predicate conditions) +AND : 'and' ; +OR : 'or' ; + +// XPath functions +LOCAL_NAME : 'local-name' ; +NAMESPACE_URI : 'namespace-uri' ; + +// String literals +STRING_LITERAL + : '\'' (~['])* '\'' + | '"' (~["])* '"' + ; + +// NCName (Non-Colonized Name) - XML name without colons +// QName (Qualified Name) - NCName with optional prefix +// QNAME must come before NCNAME to match longer token first +QNAME + : NCNAME_CHARS ':' NCNAME_CHARS + ; + +NCNAME + : NCNAME_CHARS + ; + +fragment NCNAME_CHARS + : NAME_START_CHAR NAME_CHAR* + ; + +fragment NAME_START_CHAR + : [a-zA-Z_] + | '\u00C0'..'\u00D6' + | '\u00D8'..'\u00F6' + | '\u00F8'..'\u02FF' + | '\u0370'..'\u037D' + | '\u037F'..'\u1FFF' + | '\u200C'..'\u200D' + | '\u2070'..'\u218F' + | '\u2C00'..'\u2FEF' + | '\u3001'..'\uD7FF' + | '\uF900'..'\uFDCF' + | '\uFDF0'..'\uFFFD' + ; + +fragment NAME_CHAR + : NAME_START_CHAR + | '-' + | '.' + | [0-9] + | '\u00B7' + | '\u0300'..'\u036F' + | '\u203F'..'\u2040' + ; diff --git a/rewrite-xml/src/main/antlr/XPathLexer.tokens b/rewrite-xml/src/main/antlr/XPathLexer.tokens new file mode 100644 index 0000000000..b17440a9a7 --- /dev/null +++ b/rewrite-xml/src/main/antlr/XPathLexer.tokens @@ -0,0 +1,33 @@ +WS=1 +SLASH=2 +DOUBLE_SLASH=3 +LBRACKET=4 +RBRACKET=5 +LPAREN=6 +RPAREN=7 +AT=8 +DOT=9 +COMMA=10 +EQUALS=11 +WILDCARD=12 +AND=13 +OR=14 +LOCAL_NAME=15 +NAMESPACE_URI=16 +STRING_LITERAL=17 +QNAME=18 +'/'=2 +'//'=3 +'['=4 +']'=5 +'('=6 +')'=7 +'@'=8 +'.'=9 +','=10 +'='=11 +'*'=12 +'and'=13 +'or'=14 +'local-name'=15 +'namespace-uri'=16 diff --git a/rewrite-xml/src/main/antlr/XPathParser.g4 b/rewrite-xml/src/main/antlr/XPathParser.g4 new file mode 100644 index 0000000000..3563dd797e --- /dev/null +++ b/rewrite-xml/src/main/antlr/XPathParser.g4 @@ -0,0 +1,207 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * XPath parser for a limited subset of XPath expressions. + * + * Supports: + * - Absolute paths: /root/child + * - Relative paths: child/grandchild + * - Descendant-or-self: //element + * - Wildcards: /root/* + * - Attribute access: /root/@attr, /root/element/@* + * - Node type tests: /root/element/text(), /root/comment(), etc. + * - Predicates with conditions: /root/element[@attr='value'] + * - Child element predicates: /root/element[child='value'] + * - Positional predicates: /root/element[1], /root/element[last()] + * - Parenthesized expressions with predicates: (/root/element)[1], (/root/a)[last()] + * - XPath functions: local-name(), namespace-uri(), text(), contains(), position(), last(), etc. + * - Logical operators in predicates: and, or + * - Multiple predicates: /root/element[@attr='value'][local-name()='element'] + * - Top-level function expressions: contains(/root/element, 'value') + * - Boolean expressions: not(contains(...)), string-length(...) > 2 + * - Abbreviated syntax: . (self), .. (parent) + * - Parent axis: parent::node(), parent::element + */ +parser grammar XPathParser; + +options { tokenVocab=XPathLexer; } + +// Entry point for XPath expression +xpathExpression + : booleanExpr + | filterExpr + | absoluteLocationPath + | relativeLocationPath + ; + +// Filter expression - parenthesized path with predicates and optional trailing path: (/root/a)[1]/child +filterExpr + : LPAREN (absoluteLocationPath | relativeLocationPath) RPAREN predicate+ (pathSeparator relativeLocationPath)? + ; + +// Boolean expression (function calls with optional comparison) +booleanExpr + : functionCall comparisonOp comparand + | functionCall + ; + +// Comparison operators +comparisonOp + : EQUALS + | NOT_EQUALS + | LT + | GT + | LTE + | GTE + ; + +// Value to compare against +comparand + : stringLiteral + | NUMBER + ; + +// Absolute path starting with / or // +absoluteLocationPath + : SLASH relativeLocationPath? + | DOUBLE_SLASH relativeLocationPath + ; + +// Relative path (series of steps) +relativeLocationPath + : step (pathSeparator step)* + ; + +// Path separator between steps +pathSeparator + : SLASH + | DOUBLE_SLASH + ; + +// A single step in the path +step + : axisStep predicate* + | nodeTest predicate* + | attributeStep predicate* + | nodeTypeTest + | abbreviatedStep + ; + +// Axis step - explicit axis like parent::node() +axisStep + : axisName AXIS_SEP nodeTest + ; + +// Supported axis names (NCName - no namespace prefix) +axisName + : NCNAME // parent, ancestor, self, child, etc. - validated at runtime + ; + +// Abbreviated step - . or .. +abbreviatedStep + : DOTDOT // parent::node() + | DOT // self::node() + ; + +// Node type test - text(), comment(), node(), processing-instruction() +// Validation of which functions are valid node type tests happens at runtime +nodeTypeTest + : NCNAME LPAREN RPAREN + ; + +// Attribute step (@attr, @ns:attr, or @*) +attributeStep + : AT (QNAME | NCNAME | WILDCARD) + ; + +// Node test (element name, ns:element, or wildcard) +nodeTest + : QNAME + | NCNAME + | WILDCARD + ; + +// Predicate in square brackets +predicate + : LBRACKET predicateExpr RBRACKET + ; + +// Predicate expression (supports and/or) +predicateExpr + : orExpr + ; + +// OR expression (lowest precedence) +orExpr + : andExpr (OR andExpr)* + ; + +// AND expression (higher precedence than OR) +andExpr + : primaryExpr (AND primaryExpr)* + ; + +// Primary expression in a predicate +primaryExpr + : predicateValue comparisonOp comparand // any value expression with comparison + | predicateValue // standalone value (last(), position(), number, boolean) + ; + +// A value-producing expression in a predicate +predicateValue + : functionCall // local-name(), last(), position(), contains(), etc. + | attributeStep // @attr, @* + | relativeLocationPath // bar/baz/text() + | childElementTest // child, * + | NUMBER // positional predicate [1], [2], etc. + ; + +// XPath function call - unified for both top-level and predicate use +// Function names are NCNames (no namespace prefix in standard XPath 1.0) +functionCall + : LOCAL_NAME LPAREN RPAREN + | NAMESPACE_URI LPAREN RPAREN + | NCNAME LPAREN functionArgs? RPAREN + ; + +// Function arguments (comma-separated) +functionArgs + : functionArg (COMMA functionArg)* + ; + +// A single function argument +// Note: functionCall must come before relativeLocationPath +// because both can start with QNAME, but we need to check for '(' to distinguish them +functionArg + : absoluteLocationPath + | functionCall + | relativeLocationPath + | stringLiteral + | NUMBER + ; + +// Child element test in predicate (element name, ns:element, or wildcard) +childElementTest + : QNAME + | NCNAME + | WILDCARD + ; + +// String literal value +stringLiteral + : STRING_LITERAL + ; diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/XPathCompiler.java b/rewrite-xml/src/main/java/org/openrewrite/xml/XPathCompiler.java new file mode 100644 index 0000000000..a16f8d27e2 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/XPathCompiler.java @@ -0,0 +1,1256 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.xml; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.jspecify.annotations.Nullable; +import org.openrewrite.xml.internal.grammar.XPathLexer; +import org.openrewrite.xml.internal.grammar.XPathParser; + +import java.util.List; + +/** + * Parses and compiles XPath expressions into an optimized representation + * for efficient matching against XML cursor positions. + */ +final class XPathCompiler { + + // Step characteristic flags (bitmask) + static final int FLAG_ABSOLUTE_PATH = 1; + static final int FLAG_DESCENDANT_OR_SELF = 1 << 1; + static final int FLAG_HAS_DESCENDANT = 1 << 2; + static final int FLAG_HAS_ABBREVIATED_STEP = 1 << 3; + static final int FLAG_HAS_AXIS_STEP = 1 << 4; + static final int FLAG_HAS_ATTRIBUTE_STEP = 1 << 5; + static final int FLAG_HAS_NODE_TYPE_TEST = 1 << 6; + + // Expression type constants + static final int EXPR_PATH = 0; // Simple path (compiled) + static final int EXPR_BOOLEAN = 1; // Boolean expression (compiled) + static final int EXPR_FILTER = 2; // Filter expression (compiled) + + // Empty steps array for non-path expressions + private static final CompiledStep[] EMPTY_STEPS = new CompiledStep[0]; + + private XPathCompiler() { + // Utility class + } + + /** + * Compile an XPath expression into an optimized representation. + * + * @param expression the XPath expression to compile + * @return the compiled XPath representation + */ + public static CompiledXPath compile(String expression) { + XPathLexer lexer = new XPathLexer(CharStreams.fromString(expression)); + XPathParser parser = new XPathParser(new CommonTokenStream(lexer)); + XPathParser.XpathExpressionContext ctx = parser.xpathExpression(); + return compileSteps(ctx); + } + + /** + * Pre-compile step information from parsed XPath context. + */ + private static CompiledXPath compileSteps(XPathParser.XpathExpressionContext ctx) { + // Determine expression type first + if (ctx.booleanExpr() != null) { + CompiledExpr boolExpr = compileBooleanExpr(ctx.booleanExpr()); + return new CompiledXPath(EMPTY_STEPS, 0, 0, EXPR_BOOLEAN, boolExpr, null); + } else if (ctx.filterExpr() != null) { + CompiledFilterExpr filterExpr = compileFilterExpr(ctx.filterExpr()); + return new CompiledXPath(EMPTY_STEPS, 0, 0, EXPR_FILTER, null, filterExpr); + } + + // Otherwise it's a path expression + XPathParser.RelativeLocationPathContext relPath = null; + int flags = 0; + + if (ctx.absoluteLocationPath() != null) { + XPathParser.AbsoluteLocationPathContext absCtx = ctx.absoluteLocationPath(); + if (absCtx.DOUBLE_SLASH() != null) { + flags |= FLAG_DESCENDANT_OR_SELF; + } else { + flags |= FLAG_ABSOLUTE_PATH; + } + relPath = absCtx.relativeLocationPath(); + } else if (ctx.relativeLocationPath() != null) { + relPath = ctx.relativeLocationPath(); + } + + CompiledStep[] compiledSteps = EMPTY_STEPS; + int compiledElementSteps = 0; + + if (relPath != null) { + // Extract steps + List stepCtxs = relPath.step(); + List separators = relPath.pathSeparator(); + compiledSteps = new CompiledStep[stepCtxs.size()]; + + for (int i = 0; i < stepCtxs.size(); i++) { + boolean isDescendant = false; + if (i > 0 && i - 1 < separators.size()) { + isDescendant = separators.get(i - 1).DOUBLE_SLASH() != null; + } + compiledSteps[i] = CompiledStep.fromStepContext(stepCtxs.get(i), isDescendant); + } + + // Set nextIsBacktrack flag and compute step characteristics + for (int i = 0; i < compiledSteps.length; i++) { + CompiledStep s = compiledSteps[i]; + if (s.isDescendant) flags |= FLAG_HAS_DESCENDANT; + + switch (s.type) { + case ABBREVIATED_DOT: + case ABBREVIATED_DOTDOT: + flags |= FLAG_HAS_ABBREVIATED_STEP; + break; + case AXIS_STEP: + flags |= FLAG_HAS_AXIS_STEP; + break; + case ATTRIBUTE_STEP: + flags |= FLAG_HAS_ATTRIBUTE_STEP; + break; + case NODE_TYPE_TEST: + flags |= FLAG_HAS_NODE_TYPE_TEST; + break; + case NODE_TEST: + compiledElementSteps++; + break; + } + + // Set nextIsBacktrack for lookahead optimization + if (i + 1 < compiledSteps.length && compiledSteps[i + 1].isBacktrack()) { + s.setNextIsBacktrack(); + } + } + } + + // Normalize mid-path parent steps (.. or parent::) to existence predicates + compiledSteps = normalizeParentSteps(compiledSteps); + + // Recompute flags and element count after normalization + flags = recomputeFlags(compiledSteps, flags); + compiledElementSteps = countElementSteps(compiledSteps); + + return new CompiledXPath(compiledSteps, flags, compiledElementSteps, EXPR_PATH, null, null); + } + + /** + * Normalize mid-path parent steps (.. or parent::) into existence predicates. + *

+ * Transforms patterns like: + * - /a/b/c/../e → /a/b[c]/e (b must have child c) + * - /a/b/c/d/../../e → /a/b[c/d]/e (b must have path c/d) + *

+ * Leading parent steps (like ../foo) are left unchanged as they work naturally + * in bottom-up matching. + */ + private static CompiledStep[] normalizeParentSteps(CompiledStep[] steps) { + if (steps.length == 0) { + return steps; + } + + // Quick check: does this path have any parent steps that aren't at the start? + boolean hasNormalizableParent = false; + for (int i = 1; i < steps.length; i++) { + if (steps[i].isBacktrack() && !isLeadingParentStep(steps, i)) { + hasNormalizableParent = true; + break; + } + } + if (!hasNormalizableParent) { + return steps; + } + + // Build normalized step list + java.util.ArrayList result = new java.util.ArrayList<>(); + int i = 0; + + while (i < steps.length) { + // Check if this is a parent step that should be normalized + if (steps[i].isBacktrack() && !isLeadingParentStep(steps, i)) { + // Count consecutive parent steps + int parentCount = 0; + int parentStart = i; + while (i < steps.length && steps[i].isBacktrack()) { + parentCount++; + i++; + } + + // The parentCount element steps before the parent sequence become a predicate + // These steps are at positions: parentStart - parentCount to parentStart - 1 + int predicateStartIdx = parentStart - parentCount; + + if (predicateStartIdx < 0 || predicateStartIdx >= result.size()) { + // Not enough steps to consume - this is an edge case + // Just skip the parent steps (they'll be handled as leading parents) + continue; + } + + // Extract the steps that become the predicate + int stepsToConvert = Math.min(parentCount, result.size() - predicateStartIdx); + CompiledStep[] predicateSteps = new CompiledStep[stepsToConvert]; + for (int j = 0; j < stepsToConvert; j++) { + predicateSteps[j] = result.remove(predicateStartIdx); + } + + // Create the predicate expression + CompiledExpr predicate = createPathPredicate(predicateSteps); + + // Attach predicate to the step now at predicateStartIdx - 1 (the anchor) + if (predicateStartIdx > 0 && predicateStartIdx <= result.size()) { + int anchorIdx = predicateStartIdx - 1; + result.set(anchorIdx, result.get(anchorIdx).withAdditionalPredicate(predicate)); + } + + // Continue processing remaining steps after the parent sequence + } else { + result.add(steps[i]); + i++; + } + } + + return result.toArray(new CompiledStep[0]); + } + + /** + * Check if a parent step at the given index is a "leading" parent step. + * Leading parent steps are those at the start or only preceded by other parent steps. + */ + private static boolean isLeadingParentStep(CompiledStep[] steps, int index) { + for (int i = 0; i < index; i++) { + if (!steps[i].isBacktrack()) { + return false; + } + } + return true; + } + + /** + * Create a predicate expression from a sequence of steps. + * Single step becomes CHILD, multiple steps become PATH. + */ + private static CompiledExpr createPathPredicate(CompiledStep[] steps) { + if (steps.length == 1) { + return CompiledExpr.child(steps[0].name); + } + + // Multiple steps - create PATH expression + CompiledExpr[] childExprs = new CompiledExpr[steps.length]; + for (int i = 0; i < steps.length; i++) { + childExprs[i] = CompiledExpr.child(steps[i].name); + } + return CompiledExpr.path(childExprs, null); + } + + /** + * Recompute flags after normalization (parent steps may have been removed). + */ + private static int recomputeFlags(CompiledStep[] steps, int originalFlags) { + // Keep absolute/descendant-or-self flags from original + int flags = originalFlags & (FLAG_ABSOLUTE_PATH | FLAG_DESCENDANT_OR_SELF); + + for (CompiledStep s : steps) { + if (s.isDescendant) flags |= FLAG_HAS_DESCENDANT; + + switch (s.type) { + case ABBREVIATED_DOT: + case ABBREVIATED_DOTDOT: + flags |= FLAG_HAS_ABBREVIATED_STEP; + break; + case AXIS_STEP: + flags |= FLAG_HAS_AXIS_STEP; + break; + case ATTRIBUTE_STEP: + flags |= FLAG_HAS_ATTRIBUTE_STEP; + break; + case NODE_TYPE_TEST: + flags |= FLAG_HAS_NODE_TYPE_TEST; + break; + } + } + return flags; + } + + /** + * Count element steps (NODE_TEST type). + */ + private static int countElementSteps(CompiledStep[] steps) { + int count = 0; + for (CompiledStep s : steps) { + if (s.type == StepType.NODE_TEST) { + count++; + } + } + return count; + } + + /** + * Compile a top-level boolean expression: functionCall [comparisonOp comparand] + */ + private static CompiledExpr compileBooleanExpr(XPathParser.BooleanExprContext ctx) { + CompiledExpr funcExpr = compileFunctionCall(ctx.functionCall()); + + if (ctx.comparisonOp() != null && ctx.comparand() != null) { + ComparisonOp op = compileComparisonOp(ctx.comparisonOp()); + CompiledExpr comparand = compileComparand(ctx.comparand()); + return CompiledExpr.comparison(funcExpr, op, comparand); + } + + return funcExpr; + } + + /** + * Compile a filter expression: (path)[predicate] [/trailing] + */ + private static CompiledFilterExpr compileFilterExpr(XPathParser.FilterExprContext ctx) { + // Get the path expression (absolute or relative) - first one is inside parentheses + String pathExpr; + if (ctx.absoluteLocationPath() != null) { + pathExpr = ctx.absoluteLocationPath().getText(); + } else if (!ctx.relativeLocationPath().isEmpty()) { + pathExpr = ctx.relativeLocationPath(0).getText(); + } else { + pathExpr = ""; + } + + // Compile predicates + CompiledExpr[] predicates = compilePredicates(ctx.predicate()); + + // Check for trailing path + String trailingPath = null; + boolean trailingIsDescendant = false; + if (ctx.pathSeparator() != null && ctx.relativeLocationPath().size() > 1) { + trailingPath = ctx.relativeLocationPath(1).getText(); + trailingIsDescendant = ctx.pathSeparator().DOUBLE_SLASH() != null; + } + + return new CompiledFilterExpr(pathExpr, predicates, trailingPath, trailingIsDescendant); + } + + /** + * Get name from node test (handles QNAME, NCNAME, or WILDCARD). + */ + static String getNodeTestName(XPathParser.@Nullable NodeTestContext nodeTest) { + if (nodeTest == null) { + return "*"; + } + if (nodeTest.WILDCARD() != null) { + return "*"; + } + return nodeTest.getText(); + } + + /** + * Get name from attribute step (handles QNAME, NCNAME, or WILDCARD). + */ + static String getAttributeStepName(XPathParser.AttributeStepContext attrStep) { + if (attrStep.WILDCARD() != null) { + return "*"; + } + if (attrStep.QNAME() != null) { + return attrStep.QNAME().getText(); + } + if (attrStep.NCNAME() != null) { + return attrStep.NCNAME().getText(); + } + return "*"; + } + + /** + * Get name from child element test (handles QNAME, NCNAME, or WILDCARD). + */ + static String getChildElementTestName(XPathParser.ChildElementTestContext childTest) { + if (childTest.QNAME() != null) { + return childTest.QNAME().getText(); + } + if (childTest.NCNAME() != null) { + return childTest.NCNAME().getText(); + } + return "*"; + } + + // ==================== Predicate Compilation ==================== + + private static final CompiledExpr[] EMPTY_PREDICATES = new CompiledExpr[0]; + + /** + * Compile a list of predicates into CompiledExpr array. + */ + static CompiledExpr[] compilePredicates(@Nullable List predicates) { + if (predicates == null || predicates.isEmpty()) { + return EMPTY_PREDICATES; + } + CompiledExpr[] result = new CompiledExpr[predicates.size()]; + for (int i = 0; i < predicates.size(); i++) { + result[i] = compilePredicate(predicates.get(i)); + } + return result; + } + + /** + * Compile a single predicate. + */ + static CompiledExpr compilePredicate(XPathParser.PredicateContext predicate) { + if (predicate.predicateExpr() == null || predicate.predicateExpr().orExpr() == null) { + return CompiledExpr.unsupported("empty predicate"); + } + return compileOrExpr(predicate.predicateExpr().orExpr()); + } + + /** + * Compile an OR expression (supports: expr or expr or ...) + */ + static CompiledExpr compileOrExpr(XPathParser.OrExprContext orExpr) { + List andExprs = orExpr.andExpr(); + if (andExprs.isEmpty()) { + return CompiledExpr.unsupported("empty or expression"); + } + CompiledExpr result = compileAndExpr(andExprs.get(0)); + for (int i = 1; i < andExprs.size(); i++) { + result = CompiledExpr.or(result, compileAndExpr(andExprs.get(i))); + } + return result; + } + + /** + * Compile an AND expression (supports: expr and expr and ...) + */ + static CompiledExpr compileAndExpr(XPathParser.AndExprContext andExpr) { + List primaries = andExpr.primaryExpr(); + if (primaries.isEmpty()) { + return CompiledExpr.unsupported("empty and expression"); + } + CompiledExpr result = compilePrimaryExpr(primaries.get(0)); + for (int i = 1; i < primaries.size(); i++) { + result = CompiledExpr.and(result, compilePrimaryExpr(primaries.get(i))); + } + return result; + } + + /** + * Compile a primary expression (predicateValue with optional comparison). + */ + static CompiledExpr compilePrimaryExpr(XPathParser.PrimaryExprContext primary) { + if (primary.predicateValue() == null) { + return CompiledExpr.unsupported("missing predicate value"); + } + + CompiledExpr left = compilePredicateValue(primary.predicateValue()); + + // Check for comparison + if (primary.comparisonOp() != null && primary.comparand() != null) { + ComparisonOp op = compileComparisonOp(primary.comparisonOp()); + CompiledExpr right = compileComparand(primary.comparand()); + return CompiledExpr.comparison(left, op, right); + } + + return left; + } + + /** + * Compile a predicate value (function, attribute, child, number). + */ + static CompiledExpr compilePredicateValue(XPathParser.PredicateValueContext pv) { + // Numeric predicate [1], [2], etc. + if (pv.NUMBER() != null) { + try { + int value = Integer.parseInt(pv.NUMBER().getText()); + return CompiledExpr.numeric(value); + } catch (NumberFormatException e) { + return CompiledExpr.unsupported("invalid number: " + pv.NUMBER().getText()); + } + } + + // Function call: position(), last(), local-name(), contains(), etc. + if (pv.functionCall() != null) { + return compileFunctionCall(pv.functionCall()); + } + + // Attribute: @foo, @* + if (pv.attributeStep() != null) { + return CompiledExpr.attribute(getAttributeStepName(pv.attributeStep())); + } + + // Child element test: foo, * + if (pv.childElementTest() != null) { + return CompiledExpr.child(getChildElementTestName(pv.childElementTest())); + } + + // Relative path (e.g., bar/baz/text()) + if (pv.relativeLocationPath() != null) { + return compileRelativePath(pv.relativeLocationPath()); + } + + return CompiledExpr.unsupported("unknown predicate value"); + } + + /** + * Compile a relative path in a predicate (e.g., bar/baz/text()). + */ + static CompiledExpr compileRelativePath(XPathParser.RelativeLocationPathContext relPath) { + List steps = relPath.step(); + if (steps.isEmpty()) { + return CompiledExpr.unsupported("empty path"); + } + + // Check if last step is a node type test (like text()) + XPathParser.StepContext lastStep = steps.get(steps.size() - 1); + FunctionType terminalFunction = null; + int elementStepCount = steps.size(); + + if (lastStep.nodeTypeTest() != null) { + String funcName = lastStep.nodeTypeTest().NCNAME().getText(); + terminalFunction = getFunctionType(funcName); + elementStepCount = steps.size() - 1; + } + + // Single element step with no terminal function -> CHILD + if (elementStepCount == 1 && terminalFunction == null && steps.get(0).nodeTest() != null) { + return CompiledExpr.child(steps.get(0).nodeTest().getText()); + } + + // Build array of child expressions for each element step + CompiledExpr[] pathSteps = new CompiledExpr[elementStepCount]; + for (int i = 0; i < elementStepCount; i++) { + XPathParser.StepContext step = steps.get(i); + if (step.nodeTest() != null) { + pathSteps[i] = CompiledExpr.child(step.nodeTest().getText()); + } else { + // Can't handle complex steps in path + return CompiledExpr.unsupported("complex step in path"); + } + } + + return CompiledExpr.path(pathSteps, terminalFunction); + } + + /** + * Compile a function call. + */ + static CompiledExpr compileFunctionCall(XPathParser.FunctionCallContext fc) { + // Built-in function tokens + if (fc.LOCAL_NAME() != null) { + return CompiledExpr.function(FunctionType.LOCAL_NAME); + } + if (fc.NAMESPACE_URI() != null) { + return CompiledExpr.function(FunctionType.NAMESPACE_URI); + } + + // Named function (NCNAME) + if (fc.NCNAME() != null) { + String funcName = fc.NCNAME().getText(); + FunctionType type = getFunctionType(funcName); + + // Compile arguments if present + CompiledExpr[] args = EMPTY_PREDICATES; + if (fc.functionArgs() != null) { + List argCtxs = fc.functionArgs().functionArg(); + args = new CompiledExpr[argCtxs.size()]; + for (int i = 0; i < argCtxs.size(); i++) { + args[i] = compileFunctionArg(argCtxs.get(i)); + } + } + + return CompiledExpr.function(type, args); + } + + return CompiledExpr.unsupported("unknown function"); + } + + /** + * Compile a function argument. + */ + static CompiledExpr compileFunctionArg(XPathParser.FunctionArgContext arg) { + if (arg.stringLiteral() != null) { + return CompiledExpr.string(stripQuotes(arg.stringLiteral().getText())); + } + if (arg.NUMBER() != null) { + try { + return CompiledExpr.numeric(Integer.parseInt(arg.NUMBER().getText())); + } catch (NumberFormatException e) { + return CompiledExpr.unsupported("invalid number"); + } + } + if (arg.functionCall() != null) { + return compileFunctionCall(arg.functionCall()); + } + // Path arguments (for contains(path, 'str')) + if (arg.relativeLocationPath() != null) { + List steps = arg.relativeLocationPath().step(); + if (steps.size() == 1 && steps.get(0).nodeTest() != null) { + return CompiledExpr.child(steps.get(0).nodeTest().getText()); + } + return CompiledExpr.unsupported("complex path argument"); + } + if (arg.absoluteLocationPath() != null) { + return CompiledExpr.absolutePath(arg.absoluteLocationPath().getText()); + } + return CompiledExpr.unsupported("unknown argument type"); + } + + /** + * Compile a comparand (right side of comparison). + */ + static CompiledExpr compileComparand(XPathParser.ComparandContext comparand) { + if (comparand.stringLiteral() != null) { + return CompiledExpr.string(stripQuotes(comparand.stringLiteral().getText())); + } + if (comparand.NUMBER() != null) { + try { + return CompiledExpr.numeric(Integer.parseInt(comparand.NUMBER().getText())); + } catch (NumberFormatException e) { + return CompiledExpr.unsupported("invalid number"); + } + } + return CompiledExpr.unsupported("unknown comparand"); + } + + /** + * Compile a comparison operator. + */ + static ComparisonOp compileComparisonOp(XPathParser.ComparisonOpContext op) { + if (op.EQUALS() != null) return ComparisonOp.EQ; + if (op.NOT_EQUALS() != null) return ComparisonOp.NE; + if (op.LT() != null) return ComparisonOp.LT; + if (op.LTE() != null) return ComparisonOp.LE; + if (op.GT() != null) return ComparisonOp.GT; + if (op.GTE() != null) return ComparisonOp.GE; + return ComparisonOp.EQ; // default + } + + /** + * Map function name to FunctionType. + */ + static FunctionType getFunctionType(String name) { + switch (name) { + case "position": return FunctionType.POSITION; + case "last": return FunctionType.LAST; + case "local-name": return FunctionType.LOCAL_NAME; + case "namespace-uri": return FunctionType.NAMESPACE_URI; + case "contains": return FunctionType.CONTAINS; + case "starts-with": return FunctionType.STARTS_WITH; + case "ends-with": return FunctionType.ENDS_WITH; + case "string-length": return FunctionType.STRING_LENGTH; + case "substring-before": return FunctionType.SUBSTRING_BEFORE; + case "substring-after": return FunctionType.SUBSTRING_AFTER; + case "count": return FunctionType.COUNT; + case "text": return FunctionType.TEXT; + case "not": return FunctionType.NOT; + default: + throw new IllegalArgumentException("Unsupported XPath function: " + name + "()"); + } + } + + /** + * Strip quotes from string literal. + */ + static String stripQuotes(String s) { + if (s.length() < 2) return s; + char first = s.charAt(0); + if ((first == '\'' || first == '"') && s.charAt(s.length() - 1) == first) { + return s.substring(1, s.length() - 1); + } + return s; + } + + /** + * Compiled XPath expression - holds all pre-compiled information. + * No ANTLR references - fully compiled for efficient matching. + */ + public static final class CompiledXPath { + final CompiledStep[] steps; + final int flags; + final int elementStepCount; + final int exprType; + + // For EXPR_BOOLEAN: compiled boolean expression + final @Nullable CompiledExpr booleanExpr; + + // For EXPR_FILTER: compiled filter expression + final @Nullable CompiledFilterExpr filterExpr; + + CompiledXPath(CompiledStep[] steps, + int flags, + int elementStepCount, + int exprType, + @Nullable CompiledExpr booleanExpr, + @Nullable CompiledFilterExpr filterExpr) { + this.steps = steps; + this.flags = flags; + this.elementStepCount = elementStepCount; + this.exprType = exprType; + this.booleanExpr = booleanExpr; + this.filterExpr = filterExpr; + } + + public boolean isPathExpression() { + return exprType == EXPR_PATH; + } + + public boolean isBooleanExpression() { + return exprType == EXPR_BOOLEAN; + } + + public boolean isFilterExpression() { + return exprType == EXPR_FILTER; + } + + public boolean hasAbsolutePath() { + return (flags & FLAG_ABSOLUTE_PATH) != 0; + } + + public boolean hasDescendantOrSelf() { + return (flags & FLAG_DESCENDANT_OR_SELF) != 0; + } + + public boolean hasDescendant() { + return (flags & FLAG_HAS_DESCENDANT) != 0; + } + } + + /** + * Compiled filter expression: (/path/expr)[predicate]/trailing + */ + public static final class CompiledFilterExpr { + final String pathExpr; // The path inside parentheses + final CompiledExpr[] predicates; // Predicates to apply + final @Nullable String trailingPath; // Optional trailing path after predicates + final boolean trailingIsDescendant; // Is trailing path preceded by //? + + CompiledFilterExpr(String pathExpr, CompiledExpr[] predicates, + @Nullable String trailingPath, boolean trailingIsDescendant) { + this.pathExpr = pathExpr; + this.predicates = predicates; + this.trailingPath = trailingPath; + this.trailingIsDescendant = trailingIsDescendant; + } + } + + /** + * Step types for compiled steps - avoids ANTLR tree navigation during matching. + */ + public enum StepType { + ABBREVIATED_DOT, // . + ABBREVIATED_DOTDOT, // .. + AXIS_STEP, // parent::node(), self::element, child::* + ATTRIBUTE_STEP, // @attr, @* + NODE_TYPE_TEST, // text(), comment(), node(), processing-instruction() + NODE_TEST // element name or * + } + + /** + * Axis types for axis steps. + */ + public enum AxisType { + PARENT, + SELF, + CHILD, + OTHER // Unsupported axis + } + + /** + * Node type test types. + */ + public enum NodeTypeTestType { + TEXT, + COMMENT, + NODE, + PROCESSING_INSTRUCTION, + UNKNOWN + } + + /** + * Fully compiled step information - all data extracted from ANTLR tree. + * No ANTLR context references needed during matching for simple steps. + */ + public static final class CompiledStep { + final StepType type; + final boolean isDescendant; + + // For NODE_TEST: element name (or "*" for wildcard) + // For AXIS_STEP: node test name from axis (e.g., "node" from parent::node()) + // For ATTRIBUTE_STEP: attribute name (or "*" for @*) + final @Nullable String name; + + // For AXIS_STEP only + final AxisType axisType; + + // For NODE_TYPE_TEST only + final NodeTypeTestType nodeTypeTestType; + + // Compiled predicates - no ANTLR references needed during matching + final CompiledExpr[] predicates; + + // Step flags (bitmask) + private static final int STEP_FLAG_NEEDS_POSITION = 1; + private static final int STEP_FLAG_NEXT_IS_BACKTRACK = 1 << 1; + + int flags; + + private CompiledStep(StepType type, boolean isDescendant, @Nullable String name, + AxisType axisType, NodeTypeTestType nodeTypeTestType, + CompiledExpr[] predicates, int flags) { + this.type = type; + this.isDescendant = isDescendant; + this.name = name; + this.axisType = axisType; + this.nodeTypeTestType = nodeTypeTestType; + this.predicates = predicates; + this.flags = flags; + } + + public StepType getType() { + return type; + } + + public boolean isDescendant() { + return isDescendant; + } + + public @Nullable String getName() { + return name; + } + + public AxisType getAxisType() { + return axisType; + } + + public NodeTypeTestType getNodeTypeTestType() { + return nodeTypeTestType; + } + + public CompiledExpr[] getPredicates() { + return predicates; + } + + boolean needsPositionInfo() { + return (flags & STEP_FLAG_NEEDS_POSITION) != 0; + } + + boolean nextIsBacktrack() { + return (flags & STEP_FLAG_NEXT_IS_BACKTRACK) != 0; + } + + void setNextIsBacktrack() { + flags |= STEP_FLAG_NEXT_IS_BACKTRACK; + } + + static CompiledStep fromStepContext(XPathParser.StepContext step, boolean isDescendant) { + // Compile predicates into expressions + CompiledExpr[] predicates = compilePredicates(step.predicate()); + + // Compute flags + int flags = predicatesNeedPosition(predicates) ? STEP_FLAG_NEEDS_POSITION : 0; + + // Abbreviated step: . or .. + if (step.abbreviatedStep() != null) { + if (step.abbreviatedStep().DOTDOT() != null) { + return new CompiledStep(StepType.ABBREVIATED_DOTDOT, isDescendant, null, + AxisType.OTHER, NodeTypeTestType.UNKNOWN, predicates, flags); + } else { + return new CompiledStep(StepType.ABBREVIATED_DOT, isDescendant, null, + AxisType.OTHER, NodeTypeTestType.UNKNOWN, predicates, flags); + } + } + + // Axis step: parent::node(), self::element, etc. + if (step.axisStep() != null) { + XPathParser.AxisStepContext axisStep = step.axisStep(); + String axisName = axisStep.axisName().NCNAME().getText(); + String nodeTestName = getNodeTestName(axisStep.nodeTest()); + + AxisType axisType; + switch (axisName) { + case "parent": + axisType = AxisType.PARENT; + break; + case "self": + axisType = AxisType.SELF; + break; + case "child": + axisType = AxisType.CHILD; + break; + default: + axisType = AxisType.OTHER; + } + + return new CompiledStep(StepType.AXIS_STEP, isDescendant, nodeTestName, + axisType, NodeTypeTestType.UNKNOWN, predicates, flags); + } + + // Attribute step: @attr, @* + if (step.attributeStep() != null) { + String attrName = getAttributeStepName(step.attributeStep()); + return new CompiledStep(StepType.ATTRIBUTE_STEP, isDescendant, attrName, + AxisType.OTHER, NodeTypeTestType.UNKNOWN, predicates, flags); + } + + // Node type test: text(), comment(), node(), processing-instruction() + if (step.nodeTypeTest() != null) { + String functionName = step.nodeTypeTest().NCNAME().getText(); + NodeTypeTestType testType; + switch (functionName) { + case "text": + testType = NodeTypeTestType.TEXT; + break; + case "comment": + testType = NodeTypeTestType.COMMENT; + break; + case "node": + testType = NodeTypeTestType.NODE; + break; + case "processing-instruction": + testType = NodeTypeTestType.PROCESSING_INSTRUCTION; + break; + default: + testType = NodeTypeTestType.UNKNOWN; + } + return new CompiledStep(StepType.NODE_TYPE_TEST, isDescendant, null, + AxisType.OTHER, testType, predicates, flags); + } + + // Node test: element name or * + if (step.nodeTest() != null) { + String nodeName = step.nodeTest().getText(); + return new CompiledStep(StepType.NODE_TEST, isDescendant, nodeName, + AxisType.OTHER, NodeTypeTestType.UNKNOWN, predicates, flags); + } + + // Shouldn't reach here - return a non-matching step + return new CompiledStep(StepType.NODE_TEST, isDescendant, null, + AxisType.OTHER, NodeTypeTestType.UNKNOWN, predicates, flags); + } + + /** + * Check if any compiled predicate needs position/size info. + */ + private static boolean predicatesNeedPosition(CompiledExpr[] predicates) { + for (CompiledExpr pred : predicates) { + if (pred.needsPosition()) { + return true; + } + } + return false; + } + + /** + * Check if this step is a backtrack step (.. or parent::). + */ + boolean isBacktrack() { + return type == StepType.ABBREVIATED_DOTDOT || + (type == StepType.AXIS_STEP && axisType == AxisType.PARENT); + } + + /** + * Create a new step with an additional predicate appended. + */ + CompiledStep withAdditionalPredicate(CompiledExpr predicate) { + CompiledExpr[] newPredicates = new CompiledExpr[predicates.length + 1]; + System.arraycopy(predicates, 0, newPredicates, 0, predicates.length); + newPredicates[predicates.length] = predicate; + int newFlags = flags; + if (predicate.needsPosition()) { + newFlags |= STEP_FLAG_NEEDS_POSITION; + } + return new CompiledStep(type, isDescendant, name, axisType, nodeTypeTestType, newPredicates, newFlags); + } + } + + // ==================== Compiled Expression Types ==================== + + /** + * Comparison operators for predicate expressions. + */ + public enum ComparisonOp { + EQ, // = + NE, // != + LT, // < + LE, // <= + GT, // > + GE // >= + } + + /** + * Function call types we support. + */ + public enum FunctionType { + POSITION, // position() + LAST, // last() + LOCAL_NAME, // local-name() + NAMESPACE_URI, // namespace-uri() + CONTAINS, // contains(str, substr) + STARTS_WITH, // starts-with(str, prefix) + ENDS_WITH, // ends-with(str, suffix) + STRING_LENGTH, // string-length(str) + SUBSTRING_BEFORE, // substring-before(str, delim) + SUBSTRING_AFTER, // substring-after(str, delim) + COUNT, // count(nodeset) + TEXT, // text() + NOT // not(expr) + } + + /** + * Expression types for compiled predicate expressions. + */ + public enum ExprType { + NUMERIC, // [1], [2], etc. - position check + STRING, // 'value' + COMPARISON, // left op right + AND, // expr1 and expr2 + OR, // expr1 or expr2 + FUNCTION, // position(), local-name(), contains(), etc. + ATTRIBUTE, // @name or @* + CHILD, // childName or * (in predicates) + PATH, // Multi-step relative path (e.g., bar/baz/text()) + ABSOLUTE_PATH, // Absolute path like /root/element1 + BOOLEAN, // true/false + } + + /** + * Compiled predicate expression - fully compiled, no ANTLR references. + * Uses a discriminated union pattern for Java 8 compatibility. + */ + public static final class CompiledExpr { + final ExprType type; + + // For NUMERIC + final int numericValue; + + // For STRING + final @Nullable String stringValue; + + // For COMPARISON + final @Nullable CompiledExpr left; + final @Nullable ComparisonOp op; + final @Nullable CompiledExpr right; + + // For FUNCTION + final @Nullable FunctionType functionType; + final CompiledExpr @Nullable [] args; + + // For ATTRIBUTE, CHILD + final @Nullable String name; + + // For BOOLEAN + final boolean booleanValue; + + // Private constructor - use factory methods + private CompiledExpr(ExprType type, int numericValue, @Nullable String stringValue, + @Nullable CompiledExpr left, @Nullable ComparisonOp op, @Nullable CompiledExpr right, + @Nullable FunctionType functionType, CompiledExpr @Nullable [] args, + @Nullable String name, boolean booleanValue) { + this.type = type; + this.numericValue = numericValue; + this.stringValue = stringValue; + this.left = left; + this.op = op; + this.right = right; + this.functionType = functionType; + this.args = args; + this.name = name; + this.booleanValue = booleanValue; + } + + // Factory methods + public static CompiledExpr numeric(int value) { + return new CompiledExpr(ExprType.NUMERIC, value, null, null, null, null, null, null, null, false); + } + + public static CompiledExpr string(String value) { + return new CompiledExpr(ExprType.STRING, 0, value, null, null, null, null, null, null, false); + } + + public static CompiledExpr comparison(CompiledExpr left, ComparisonOp op, CompiledExpr right) { + return new CompiledExpr(ExprType.COMPARISON, 0, null, left, op, right, null, null, null, false); + } + + public static CompiledExpr and(CompiledExpr left, CompiledExpr right) { + return new CompiledExpr(ExprType.AND, 0, null, left, null, right, null, null, null, false); + } + + public static CompiledExpr or(CompiledExpr left, CompiledExpr right) { + return new CompiledExpr(ExprType.OR, 0, null, left, null, right, null, null, null, false); + } + + public static CompiledExpr function(FunctionType type, CompiledExpr... args) { + return new CompiledExpr(ExprType.FUNCTION, 0, null, null, null, null, type, args, null, false); + } + + public static CompiledExpr attribute(@Nullable String name) { + return new CompiledExpr(ExprType.ATTRIBUTE, 0, null, null, null, null, null, null, name, false); + } + + public static CompiledExpr child(@Nullable String name) { + return new CompiledExpr(ExprType.CHILD, 0, null, null, null, null, null, null, name, false); + } + + /** + * Create a PATH expression for multi-step relative paths. + * @param steps Array of CHILD expressions representing each step + * @param terminalFunction Optional function at the end (e.g., text()) + */ + public static CompiledExpr path(CompiledExpr[] steps, @Nullable FunctionType terminalFunction) { + return new CompiledExpr(ExprType.PATH, 0, null, null, null, null, terminalFunction, steps, null, false); + } + + public static CompiledExpr bool(boolean value) { + return new CompiledExpr(ExprType.BOOLEAN, 0, null, null, null, null, null, null, null, value); + } + + /** + * Create an ABSOLUTE_PATH expression for absolute path arguments. + * The path string is stored in stringValue. + */ + public static CompiledExpr absolutePath(String pathExpr) { + return new CompiledExpr(ExprType.ABSOLUTE_PATH, 0, pathExpr, null, null, null, null, null, null, false); + } + + /** + * Throws an exception for unsupported XPath constructs. + * Called at compile time to fail fast with a descriptive message. + */ + public static CompiledExpr unsupported(String description) { + throw new UnsupportedOperationException("Unsupported XPath expression: " + description); + } + + /** + * Check if this expression needs position/size context to evaluate. + */ + public boolean needsPosition() { + switch (type) { + case NUMERIC: + return true; + case FUNCTION: + return functionType == FunctionType.POSITION || functionType == FunctionType.LAST; + case COMPARISON: + case AND: + case OR: + return (left != null && left.needsPosition()) || (right != null && right.needsPosition()); + default: + return false; + } + } + + /** + * Check if this is a wildcard attribute or child expression. + */ + public boolean isWildcard() { + return (type == ExprType.ATTRIBUTE || type == ExprType.CHILD) && + (name == null || "*".equals(name)); + } + + /** + * Check if this expression contains any relative paths (CHILD or PATH types). + * Relative paths should be evaluated from the cursor context, not from root. + * ABSOLUTE_PATH expressions are not considered relative. + */ + public boolean hasRelativePath() { + switch (type) { + case CHILD: + case PATH: + return true; + case COMPARISON: + case AND: + case OR: + return (left != null && left.hasRelativePath()) || + (right != null && right.hasRelativePath()); + case FUNCTION: + if (args != null) { + for (CompiledExpr arg : args) { + if (arg.hasRelativePath()) { + return true; + } + } + } + return false; + default: + return false; + } + } + + /** + * Check if this expression contains only pure absolute paths (starting with / but not //). + * Pure absolute paths like /foo/bar require cursor to be at root. + * Descendant paths like //foo can match at any cursor position. + * Returns true if the expression has at least one ABSOLUTE_PATH and all are pure absolute. + */ + public boolean hasPureAbsolutePath() { + switch (type) { + case ABSOLUTE_PATH: + // Check if path starts with // (descendant) vs / (pure absolute) + return stringValue != null && stringValue.startsWith("/") && !stringValue.startsWith("//"); + case COMPARISON: + case AND: + case OR: + // Both sides must have pure absolute paths (if they have any paths) + boolean leftPure = left == null || !left.hasAnyAbsolutePath() || left.hasPureAbsolutePath(); + boolean rightPure = right == null || !right.hasAnyAbsolutePath() || right.hasPureAbsolutePath(); + boolean hasAny = (left != null && left.hasAnyAbsolutePath()) || + (right != null && right.hasAnyAbsolutePath()); + return hasAny && leftPure && rightPure; + case FUNCTION: + if (args != null) { + boolean anyAbsolute = false; + for (CompiledExpr arg : args) { + if (arg.hasAnyAbsolutePath()) { + anyAbsolute = true; + if (!arg.hasPureAbsolutePath()) { + return false; // Has descendant path + } + } + } + return anyAbsolute; + } + return false; + default: + return false; + } + } + + /** + * Check if this expression contains any ABSOLUTE_PATH expressions. + */ + private boolean hasAnyAbsolutePath() { + switch (type) { + case ABSOLUTE_PATH: + return true; + case COMPARISON: + case AND: + case OR: + return (left != null && left.hasAnyAbsolutePath()) || + (right != null && right.hasAnyAbsolutePath()); + case FUNCTION: + if (args != null) { + for (CompiledExpr arg : args) { + if (arg.hasAnyAbsolutePath()) { + return true; + } + } + } + return false; + default: + return false; + } + } + + public ExprType getType() { + return type; + } + } +} diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java b/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java index 025625ac4d..49eeda46da 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java @@ -17,56 +17,34 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; -import org.openrewrite.internal.StringUtils; -import org.openrewrite.xml.search.FindTags; -import org.openrewrite.xml.trait.Namespaced; +import org.openrewrite.xml.XPathCompiler.*; +import org.openrewrite.xml.tree.Content; import org.openrewrite.xml.tree.Xml; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; -import static java.util.Collections.reverse; +import static org.openrewrite.xml.XPathCompiler.FLAG_ABSOLUTE_PATH; /** * Supports a limited set of XPath expressions, specifically those documented on this page. - * Additionally, supports `local-name()` and `namespace-uri()` conditions, `and`/`or` operators, and chained conditions. + * Additionally, supports `local-name()` and `namespace-uri()` conditions, `and`/`or` operators, chained conditions, + * and abbreviated syntax (`.` for self, `..` for parent within a path). *

* Used for checking whether a visitor's cursor meets a certain XPath expression. - *

- * The "current node" for XPath evaluation is always the root node of the document. As a result, '.' and '..' are not - * recognized. */ +@SuppressWarnings("BooleanMethodIsAlwaysInverted") public class XPathMatcher { - private static final Pattern XPATH_ELEMENT_SPLITTER = Pattern.compile("((?<=/)(?=/)|[^/\\[]|\\[[^]]*])+"); - // Regular expression to support conditional tags like `plugin[artifactId='maven-compiler-plugin']` or foo[@bar='baz'] - private static final Pattern ELEMENT_WITH_CONDITION_PATTERN = Pattern.compile("(@)?([-:\\w]+|\\*)(\\[.+])"); - private static final Pattern CONDITION_PATTERN = Pattern.compile("(\\[.*?])+?"); - private static final Pattern CONDITION_CONJUNCTION_PATTERN = Pattern.compile("(((local-name|namespace-uri|text)\\(\\)|(@)?([-\\w:]+|\\*))\\h*=\\h*[\"'](.*?)[\"'](\\h?(or|and)\\h?)?)+?"); - private final String expression; - private final boolean startsWithSlash; - private final boolean startsWithDoubleSlash; - private final String[] parts; - private final long tagMatchingParts; + @SuppressWarnings("NotNullFieldNotInitialized") + private volatile CompiledXPath compiled; public XPathMatcher(String expression) { this.expression = expression; - startsWithSlash = expression.startsWith("/"); - startsWithDoubleSlash = expression.startsWith("//"); - parts = splitOnXPathSeparator(expression.substring(startsWithDoubleSlash ? 2 : startsWithSlash ? 1 : 0)); - tagMatchingParts = Arrays.stream(parts).filter(part -> !part.isEmpty() && !part.startsWith("@")).count(); - } - - private String[] splitOnXPathSeparator(String input) { - List matches = new ArrayList<>(); - Matcher m = XPATH_ELEMENT_SPLITTER.matcher(input); - while (m.find()) { - matches.add(m.group()); - } - return matches.toArray(new String[0]); } /** @@ -76,269 +54,1393 @@ private String[] splitOnXPathSeparator(String input) { * @return true if the expression matches the cursor, false otherwise */ public boolean matches(Cursor cursor) { - List path = new ArrayList<>(); - for (Cursor c = cursor; c != null; c = c.getParent()) { - if (c.getValue() instanceof Xml.Tag) { - path.add(c.getValue()); - } + // Ensure expression is parsed and steps are compiled + CompiledXPath xpath = compile(); + + // Path expressions use bottom-up matching (consecutive parent steps + // are normalized to predicates at compile time) + if (xpath.isPathExpression() && xpath.steps.length > 0) { + return matchBottomUp(cursor); } - if (startsWithDoubleSlash || !startsWithSlash) { - int pathIndex = 0; - for (int i = parts.length - 1; i >= 0; i--, pathIndex++) { - String part = parts[i]; + // Boolean and filter expressions use specialized evaluation + return matchTopDown(cursor); + } - String partWithCondition = null; - Xml.Tag tagForCondition = null; - boolean conditionIsBefore = false; - if (part.endsWith("]") && i < path.size()) { - int index = part.indexOf("["); - if (index < 0) { - return false; - } - partWithCondition = part; - tagForCondition = path.get(pathIndex); - } else if (i < path.size() && i > 0 && parts[i - 1].endsWith("]")) { - String partBefore = parts[i - 1]; - int index = partBefore.indexOf("["); - if (index < 0) { - return false; - } - if (!partBefore.contains("@")) { - conditionIsBefore = true; - partWithCondition = partBefore; - tagForCondition = path.get(parts.length - i); - } - } else if (part.endsWith(")")) { // is xpath method - // TODO: implement other xpath methods - throw new UnsupportedOperationException("XPath methods are not supported"); + private CompiledXPath compile() { + CompiledXPath result = compiled; + //noinspection ConstantValue + if (result == null) { + synchronized (this) { + result = compiled; + if (result == null) { + compiled = result = XPathCompiler.compile(expression); } + } + } + return result; + } - String partName; - boolean matchedCondition = false; - - Matcher matcher; - if (tagForCondition != null && partWithCondition.endsWith("]") && - (matcher = ELEMENT_WITH_CONDITION_PATTERN.matcher(partWithCondition)).matches()) { - String optionalPartName = matchesElementWithConditionFunction(matcher, tagForCondition, cursor); - if (optionalPartName == null) { - return false; + /** + * Bottom-up matching: work backwards through compiled steps from the cursor. + * This avoids building a path array and fails fast on mismatches. + */ + private boolean matchBottomUp(Cursor cursor) { + CompiledStep[] steps = compiled.steps; + // Early reject: for absolute paths without any //, if cursor depth exceeds element steps, can't match + // e.g., /a/b has 2 element steps, so cursor at depth 3+ can never match + // But /a//b can match at any depth due to the // so we can't apply this optimization + if (compiled.hasAbsolutePath() && !compiled.hasDescendant() && compiled.elementStepCount > 0) { + int depth = 0; + for (Cursor c = cursor; c != null; c = c.getParent()) { + if (c.getValue() instanceof Xml.Tag) { + depth++; + if (depth > compiled.elementStepCount) { + return false; // Too deep } - partName = optionalPartName; - matchedCondition = true; - } else { - partName = null; } + } + } - if (part.startsWith("@")) { - if (!matchedCondition) { - if (!(cursor.getValue() instanceof Xml.Attribute)) { - return false; - } - Xml.Attribute attribute = cursor.getValue(); - if (!attribute.getKeyAsString().equals(part.substring(1)) && !"*".equals(part.substring(1))) { - return false; - } - } + // Get current element + Object cursorValue = cursor.getValue(); - pathIndex--; - continue; + // Match last step against current cursor position + CompiledStep lastStep = steps[steps.length - 1]; + + // Handle attribute matching + if (lastStep.getType() == StepType.ATTRIBUTE_STEP) { + if (!(cursorValue instanceof Xml.Attribute)) { + return false; + } + Xml.Attribute attr = (Xml.Attribute) cursorValue; + if (!matchesName(lastStep.getName(), attr.getKeyAsString())) { + return false; + } + // Check predicates on attribute + if (lastStep.getPredicates().length > 0) { + if (!evaluateAttributePredicatesBottomUp(lastStep.getPredicates(), attr, cursor)) { + return false; } + } + // For attributes, continue matching from parent tag + Cursor parentCursor = cursor.getParent(); + if (parentCursor == null || !(parentCursor.getValue() instanceof Xml.Tag)) { + return false; + } + return matchRemainingStepsBottomUp(parentCursor, steps.length - 2); + } - boolean conditionNotFulfilled = tagForCondition == null || - (!part.equals(partName) && !tagForCondition.getName().equals(partName)); + // Handle node type test (text(), comment(), etc.) + if (lastStep.getType() == StepType.NODE_TYPE_TEST) { + return matchNodeTypeTestBottomUp(lastStep, cursor, steps.length - 2); + } - int idx = part.indexOf("["); - if (idx > 0) { - part = part.substring(0, idx); - } - if (path.size() < i + 1 || - (!(path.get(pathIndex).getName().equals(part)) && !"*".equals(part)) || - conditionIsBefore && conditionNotFulfilled) { + // Handle parent axis (parent::node()) or abbreviated parent (..) as last step + // This means the cursor should be at a parent position, and we need to verify + // the step before this exists as a child + if (lastStep.getType() == StepType.ABBREVIATED_DOTDOT || + (lastStep.getType() == StepType.AXIS_STEP && lastStep.getAxisType() == AxisType.PARENT)) { + return matchParentStepAsLast(lastStep, cursor, steps.length - 2); + } + + // Handle self axis (self::node() or .) as last step + if (lastStep.getType() == StepType.ABBREVIATED_DOT || + (lastStep.getType() == StepType.AXIS_STEP && lastStep.getAxisType() == AxisType.SELF)) { + // . or self:: means current position - cursor must be a tag matching the name test (if any) + if (!(cursorValue instanceof Xml.Tag)) { + return false; + } + Xml.Tag tag = (Xml.Tag) cursorValue; + if (lastStep.getType() == StepType.AXIS_STEP && !matchesElementName(lastStep.getName(), tag.getName())) { + return false; + } + // Continue matching from current position (don't go up) + return matchRemainingStepsBottomUp(cursor, steps.length - 2); + } + + // For element matching, cursor must be a tag + if (!(cursorValue instanceof Xml.Tag)) { + return false; + } + Xml.Tag currentTag = (Xml.Tag) cursorValue; + + // Check last step matches current tag + if (!matchStepAgainstTag(lastStep, currentTag, cursor)) { + return false; + } + + // Match remaining steps going up the cursor chain + Cursor parentCursor = getParentTagCursor(cursor); + return matchRemainingStepsBottomUp(parentCursor, steps.length - 2); + } + + /** + * Handle parent step (.. or parent::) as the last step. + * This means we're at a parent position, and we need to verify the child exists. + */ + private boolean matchParentStepAsLast(CompiledStep parentStep, Cursor cursor, int prevStepIdx) { + Object cursorValue = cursor.getValue(); + if (!(cursorValue instanceof Xml.Tag)) { + return false; + } + Xml.Tag currentTag = (Xml.Tag) cursorValue; + + // Check node test name for parent:: axis + if (parentStep.type == StepType.AXIS_STEP && !matchesElementName(parentStep.name, currentTag.getName())) { + return false; + } + + // If there's a previous step, verify it exists as a child + if (prevStepIdx >= 0) { + CompiledStep prevStep = compiled.steps[prevStepIdx]; + // The previous step should exist as a child of current position + if (prevStep.type == StepType.NODE_TEST && prevStep.name != null) { + if (!hasChildWithName(currentTag, prevStep.name)) { return false; } } + // Continue matching from current position, skipping the child verification step + return matchRemainingStepsBottomUp(cursor, prevStepIdx - 1); + } + + return true; + } + + /** + * Check if a tag has a direct child with the given name. + */ + private boolean hasChildWithName(Xml.Tag parent, String childName) { + return findChildTag(parent, childName) != null; + } + + /** + * Handle .. (parent step) in the middle of a path. + * The step at prevStepIdx should be checked as a child existence test. + */ + private boolean matchParentStepInMiddle(@Nullable Cursor cursor, int prevStepIdx) { + if (cursor == null || !(cursor.getValue() instanceof Xml.Tag)) { + return false; + } + Xml.Tag currentTag = cursor.getValue(); - // we have matched the whole XPath, and it does not start with the root + if (prevStepIdx < 0) { + // No more steps - verify we're at root + if ((compiled.flags & FLAG_ABSOLUTE_PATH) != 0 && !compiled.steps[0].isDescendant) { + Cursor parentCursor = getParentTagCursor(cursor); + return parentCursor == null || !(parentCursor.getValue() instanceof Xml.Tag); + } return true; - } else { - reverse(path); - - // Deal with the two forward slashes in the expression; works, but I'm not proud of it. - if (expression.contains("//") && Arrays.stream(parts).anyMatch(StringUtils::isBlank)) { - int blankPartIndex = Arrays.asList(parts).indexOf(""); - int doubleSlashIndex = expression.indexOf("//"); - - if (path.size() > blankPartIndex && path.size() >= tagMatchingParts) { - Xml.Tag blankPartTag = path.get(blankPartIndex); - String part = parts[blankPartIndex + 1]; - Matcher matcher = ELEMENT_WITH_CONDITION_PATTERN.matcher(part); - if (matcher.matches() ? - matchesElementWithConditionFunction(matcher, blankPartTag, cursor) != null : - Objects.equals(blankPartTag.getName(), part)) { - if (matchesWithoutDoubleSlashesAt(cursor, doubleSlashIndex)) { - return true; - } - // fall-through: maybe we can skip this element and match further down + } + + CompiledStep prevStep = compiled.steps[prevStepIdx]; + + // The previous step should exist as a child (this is the "detour" we took before going up) + if (prevStep.type == StepType.NODE_TEST && prevStep.name != null) { + if (!hasChildWithName(currentTag, prevStep.name)) { + return false; + } + // Skip the child verification step and continue matching + return matchRemainingStepsBottomUp(cursor, prevStepIdx - 1); + } + + // For other step types, just continue (shouldn't normally happen) + return matchRemainingStepsBottomUp(cursor, prevStepIdx - 1); + } + + /** + * Evaluate attribute predicates in bottom-up context. + * Uses compiled expressions - no ANTLR tree traversal needed. + */ + private boolean evaluateAttributePredicatesBottomUp(CompiledExpr[] predicates, + Xml.Attribute attr, Cursor cursor) { + for (CompiledExpr predicate : predicates) { + if (!evaluateExpr(predicate, null, attr, cursor, 1, 1)) { + return false; + } + } + return true; + } + + /** + * Match node type test in bottom-up context. + */ + private boolean matchNodeTypeTestBottomUp(CompiledStep step, Cursor cursor, int nextStepIdx) { + Object cursorValue = cursor.getValue(); + + switch (step.nodeTypeTestType) { + case TEXT: + // text() can match: + // 1. When cursor is on Xml.CharData - continue from parent tag + // 2. When cursor is on a tag that has text content - continue from same position + // (text() verifies content exists, doesn't change traversal level) + if (cursorValue instanceof Xml.CharData) { + Cursor parentCursor = getParentTagCursor(cursor); + return matchRemainingStepsBottomUp(parentCursor, nextStepIdx); + } + if (cursorValue instanceof Xml.Tag) { + Xml.Tag tag = (Xml.Tag) cursorValue; + if (tag.getValue().isPresent()) { + // Tag has text content - continue from SAME position + return matchRemainingStepsBottomUp(cursor, nextStepIdx); } - String newExpression = String.format( - // the // here allows to skip several levels of nested elements - "%s/%s//%s", - expression.substring(0, doubleSlashIndex), - blankPartTag.getName(), - expression.substring(doubleSlashIndex + 2) - ); - return new XPathMatcher(newExpression).matches(cursor); - } else if (path.size() == tagMatchingParts) { - return matchesWithoutDoubleSlashesAt(cursor, doubleSlashIndex); } - } + return false; + + case COMMENT: + if (cursorValue instanceof Xml.Comment) { + Cursor parentCursor = getParentTagCursor(cursor); + return matchRemainingStepsBottomUp(parentCursor, nextStepIdx); + } + return false; + + case NODE: + // node() matches any node + Cursor parentCursor = getParentTagCursor(cursor); + return matchRemainingStepsBottomUp(parentCursor, nextStepIdx); + + case PROCESSING_INSTRUCTION: + if (cursorValue instanceof Xml.ProcessingInstruction) { + Cursor parentCursorPi = getParentTagCursor(cursor); + return matchRemainingStepsBottomUp(parentCursorPi, nextStepIdx); + } + return false; - if (tagMatchingParts > path.size()) { + default: return false; + } + } + + /** + * Recursively match remaining steps going up the cursor chain. + * + * @param cursor Current position in the document (may be null if we've reached root) + * @param stepIdx Index of the step to match (going backwards from end) + * @return true if all remaining steps match + */ + private boolean matchRemainingStepsBottomUp(@Nullable Cursor cursor, int stepIdx) { + // All steps matched - verify root condition + if (stepIdx < 0) { + if ((compiled.flags & FLAG_ABSOLUTE_PATH) != 0) { + // Absolute path (starts with /) - must have reached root (no more tag ancestors) + return cursor == null || !(cursor.getValue() instanceof Xml.Tag); } + // Relative path or descendant path (starts with //) - any position is fine + return true; + } + + CompiledStep step = compiled.steps[stepIdx]; + + // Handle special step types that don't consume parent levels normally + switch (step.type) { + case ABBREVIATED_DOT: + // . means "self" - don't move up, just continue matching from same position + return matchRemainingStepsBottomUp(cursor, stepIdx - 1); - for (int i = 0; i < parts.length; i++) { - String part = parts[i]; + case ABBREVIATED_DOTDOT: + // .. means "parent" - in bottom-up, the step BEFORE .. should be checked + // as a child existence test (not a direct match) + return matchParentStepInMiddle(cursor, stepIdx - 1); - int isAttr = part.startsWith("@") ? 1 : 0; - Xml.Tag tag = i - isAttr < path.size() ? path.get(i - isAttr) : null; - String partName; - boolean matchedCondition = false; + case AXIS_STEP: + return matchAxisStepBottomUp(step, cursor, stepIdx); - Matcher matcher; - if (tag != null && part.endsWith("]") && (matcher = ELEMENT_WITH_CONDITION_PATTERN.matcher(part)).matches()) { - String optionalPartName = matchesElementWithConditionFunction(matcher, tag, cursor); - if (optionalPartName == null) { + case NODE_TYPE_TEST: + // Node type test in the middle of a path + return matchNodeTypeStepBottomUp(step, cursor, stepIdx); + + default: + // Regular element step (NODE_TEST) + break; + } + + // Check how this step relates to the step we just matched (stepIdx + 1) + // The step at stepIdx+1 tells us the relationship via isDescendant + CompiledStep nextStep = compiled.steps[stepIdx + 1]; + + if (nextStep.isDescendant) { + // The step we just matched can be a descendant of current step + // So current step can be ANY ancestor - scan upward with backtracking + Cursor pos = cursor; + while (pos != null && pos.getValue() instanceof Xml.Tag) { + Xml.Tag tag = pos.getValue(); + if (matchStepAgainstTag(step, tag, pos)) { + // Found a candidate - try to match remaining prefix + Cursor nextParent = getParentTagCursor(pos); + if (matchRemainingStepsBottomUp(nextParent, stepIdx - 1)) { + return true; // This candidate worked + } + // Continue scanning for another candidate (backtracking) + } + pos = getParentTagCursor(pos); + } + return false; // No valid candidate found + } else { + // Direct parent relationship - current step must be at cursor exactly + if (cursor == null || !(cursor.getValue() instanceof Xml.Tag)) { + return false; + } + Xml.Tag tag = cursor.getValue(); + if (!matchStepAgainstTag(step, tag, cursor)) { + return false; + } + Cursor nextParent = getParentTagCursor(cursor); + return matchRemainingStepsBottomUp(nextParent, stepIdx - 1); + } + } + + /** + * Match axis step in bottom-up context. + */ + private boolean matchAxisStepBottomUp(CompiledStep step, @Nullable Cursor cursor, int stepIdx) { + switch (step.axisType) { + case PARENT: + // parent::node() or parent::element - similar to .. + // Check node test name first + if (cursor != null && cursor.getValue() instanceof Xml.Tag) { + Xml.Tag tag = cursor.getValue(); + if (!matchesElementName(step.name, tag.getName())) { return false; } - partName = optionalPartName; - matchedCondition = true; - } else { - partName = part; } + // The step before parent:: should be verified as a child + return matchParentStepInMiddle(cursor, stepIdx - 1); - if (part.startsWith("@")) { - if (matchedCondition) { - return true; + case SELF: + // self::node() or self::element - matches current position + // In bottom-up, we need to check the next step's cursor position + // Actually, self:: doesn't consume a level - it's like . with a name test + if (cursor != null && cursor.getValue() instanceof Xml.Tag) { + Xml.Tag tag = cursor.getValue(); + if (!matchesElementName(step.name, tag.getName())) { + return false; } - return cursor.getValue() instanceof Xml.Attribute && - (((Xml.Attribute) cursor.getValue()).getKeyAsString().equals(part.substring(1)) || - "*".equals(part.substring(1))); } + return matchRemainingStepsBottomUp(cursor, stepIdx - 1); - if (path.size() < i + 1 || (tag != null && !tag.getName().equals(partName) && !"*".equals(partName) && !"*".equals(part))) { + case CHILD: + // child:: is the default - same as normal element step + if (cursor == null || !(cursor.getValue() instanceof Xml.Tag)) { return false; } + Xml.Tag tag = cursor.getValue(); + if (!matchStepAgainstTag(step, tag, cursor)) { + return false; + } + return matchRemainingStepsBottomUp(getParentTagCursor(cursor), stepIdx - 1); + + default: + // Unsupported axis + return false; + } + } + + /** + * Match node type test step in bottom-up context (not as last step). + */ + private boolean matchNodeTypeStepBottomUp(CompiledStep step, @Nullable Cursor cursor, int stepIdx) { + // Node type tests in the middle of a path need special handling + switch (step.nodeTypeTestType) { + case NODE: + // node() matches anything - don't consume extra level + return matchRemainingStepsBottomUp(cursor, stepIdx - 1); + + case TEXT: + case COMMENT: + case PROCESSING_INSTRUCTION: + // These typically don't appear in the middle of element paths + // Fall through to default handling + default: + return false; + } + } + + /** + * Get the parent cursor that contains a tag, skipping non-tag nodes. + */ + private @Nullable Cursor getParentTagCursor(@Nullable Cursor cursor) { + if (cursor == null) return null; + Cursor parent = cursor.getParent(); + while (parent != null && !(parent.getValue() instanceof Xml.Tag)) { + if (parent.getValue() instanceof Xml.Document) { + return null; } + parent = parent.getParent(); + } + return parent; + } - return cursor.getValue() instanceof Xml.Tag && path.size() == parts.length; + /** + * Check if a compiled step matches a tag. + */ + private boolean matchStepAgainstTag(CompiledStep step, Xml.Tag tag, Cursor cursor) { + // Handle different step types + switch (step.type) { + case NODE_TEST: + // Element name or wildcard + if (!matchesName(step.name, tag.getName())) { + return false; + } + break; + case ABBREVIATED_DOT: + // . matches current - always true for tags + break; + case ABBREVIATED_DOTDOT: + // .. is handled differently - this shouldn't be called directly + return false; + case NODE_TYPE_TEST: + // text(), comment(), node() - these don't match tags + if (step.nodeTypeTestType != NodeTypeTestType.NODE) { + return false; + } + break; + default: + // Other step types not supported in bottom-up yet + return false; + } + + // Check predicates if present + if (step.predicates.length > 0) { + return evaluatePredicates(step.predicates, tag, cursor); } + + return true; } - private boolean matchesWithoutDoubleSlashesAt(Cursor cursor, int doubleSlashIndex) { - String newExpression = String.format( - "%s/%s", - expression.substring(0, doubleSlashIndex), - expression.substring(doubleSlashIndex + 2) - ); - return new XPathMatcher(newExpression).matches(cursor); + /** + * Evaluate predicates against a tag. + * Uses compiled expressions - no ANTLR tree traversal needed. + */ + private boolean evaluatePredicates(CompiledExpr[] predicates, Xml.Tag tag, Cursor cursor) { + // Calculate position/size once by looking at parent's children + int position = 1; + int size = 1; + Cursor parentCursor = cursor.getParent(); + if (parentCursor != null && parentCursor.getValue() instanceof Xml.Tag) { + Xml.Tag parent = parentCursor.getValue(); + List contents = parent.getContent(); + if (contents != null) { + int count = 0; + for (Content c : contents) { + if (c instanceof Xml.Tag) { + Xml.Tag child = (Xml.Tag) c; + if (child.getName().equals(tag.getName())) { + count++; + if (child == tag) { + position = count; + } + } + } + } + size = count > 0 ? count : 1; + } + } + + for (CompiledExpr predicate : predicates) { + if (!evaluateExpr(predicate, tag, null, cursor, position, size)) { + return false; + } + } + return true; } + // ==================== Compiled Expression Evaluation ==================== + /** - * Checks that the given {@code tag} matches the XPath part represented by {@code matcher}. + * Unified expression evaluation for both tag and attribute contexts. + * Pass the relevant context (tag or attr), with the other as null. + * This is allocation-free - no context objects are created. * - * @param matcher an XPath part matcher for {@link #ELEMENT_WITH_CONDITION_PATTERN} - * @param tag a tag to match - * @param cursor the cursor we are trying to match - * @return the element name specified before the condition of the part - * (either {@code tag.getName()}, {@code "*"} or an attribute name) or {@code null} if the tag did not match - */ - private @Nullable String matchesElementWithConditionFunction(Matcher matcher, Xml.Tag tag, Cursor cursor) { - boolean isAttributeElement = matcher.group(1) != null; - String element = matcher.group(2); - String allConditions = matcher.group(3); - - // Fail quickly if element name doesn't match - if (!isAttributeElement && !tag.getName().equals(element) && !"*".equals(element)) { - return null; + * @param expr the compiled expression to evaluate + * @param tag the tag context (null for attribute-only evaluation) + * @param attr the attribute context (null for tag-only evaluation) + * @param cursor the cursor position + * @param position the 1-based position among siblings + * @param size the total number of siblings + * @return true if the expression evaluates to true + */ + @SuppressWarnings("DataFlowIssue") + private boolean evaluateExpr(CompiledExpr expr, Xml.@Nullable Tag tag, Xml.@Nullable Attribute attr, + Cursor cursor, int position, int size) { + switch (expr.type) { + case NUMERIC: + // Positional predicate: [1], [2], etc. + return position == expr.numericValue; + + case AND: + return evaluateExpr(expr.left, tag, attr, cursor, position, size) && + evaluateExpr(expr.right, tag, attr, cursor, position, size); + + case OR: + return evaluateExpr(expr.left, tag, attr, cursor, position, size) || + evaluateExpr(expr.right, tag, attr, cursor, position, size); + + case COMPARISON: + return evaluateComparison(expr, tag, attr, cursor, position, size); + + case FUNCTION: + return evaluateFunction(expr, tag, attr, cursor, position, size); + + case CHILD: + // Existence check: [childName] - tag context only + return tag != null && hasChildElement(tag, expr.name); + + case PATH: + // Path existence check: [a/b/c] - tag context only + return tag != null && pathExists(tag, expr); + + case ATTRIBUTE: + // Existence check: [@attr] - tag context only + return tag != null && hasAttribute(tag, expr.name); + + case BOOLEAN: + return expr.booleanValue; + + default: + return false; } + } - // check that all conditions match on current element - Matcher conditions = CONDITION_PATTERN.matcher(allConditions); - boolean stillMatchesConditions = true; - while (conditions.find() && stillMatchesConditions) { - String conditionGroup = conditions.group(1); - Matcher condition = CONDITION_CONJUNCTION_PATTERN.matcher(conditionGroup); - boolean orCondition = false; - - while (condition.find() && (stillMatchesConditions || orCondition)) { - boolean matchCurrentCondition = false; - - boolean isAttributeCondition = condition.group(4) != null; - String selector = isAttributeCondition ? condition.group(5) : condition.group(2); - boolean isFunctionCondition = selector.endsWith("()"); - String value = condition.group(6); - String conjunction = condition.group(8); - orCondition = "or".equals(conjunction); - - // invalid conjunction if not 'or' or 'and' - if (!orCondition && conjunction != null && !"and".equals(conjunction)) { - // TODO: throw exception for invalid or unsupported XPath conjunction? - stillMatchesConditions = false; - break; + /** + * Unified comparison evaluation for both tag and attribute contexts. + */ + @SuppressWarnings("DataFlowIssue") + private boolean evaluateComparison(CompiledExpr expr, Xml.@Nullable Tag tag, Xml.@Nullable Attribute attr, + Cursor cursor, int position, int size) { + String leftValue = resolveValue(expr.left, tag, attr, cursor, position, size); + String rightValue = resolveValue(expr.right, tag, attr, cursor, position, size); + + if (leftValue == null || rightValue == null) { + return false; + } + + return compareValues(leftValue, rightValue, expr.op); + } + + /** + * Unified value resolution for both tag and attribute contexts. + */ + private @Nullable String resolveValue(CompiledExpr expr, Xml.@Nullable Tag tag, Xml.@Nullable Attribute attr, + Cursor cursor, int position, int size) { + switch (expr.type) { + case STRING: + return expr.stringValue; + + case NUMERIC: + return String.valueOf(expr.numericValue); + + case CHILD: + // Tag context only + return tag != null ? getChildElementValue(tag, expr.name) : null; + + case ATTRIBUTE: + // In tag context, get attribute from tag + // In attribute context, get value from current attribute or parent tag + if (tag != null) { + return getAttributeValue(tag, expr.name); + } + if (attr != null) { + if (expr.name == null || "*".equals(expr.name)) { + return attr.getValueAsString(); + } + // For named attributes, check parent tag + Cursor parentCursor = cursor.getParent(); + if (parentCursor != null && parentCursor.getValue() instanceof Xml.Tag) { + return getAttributeValue(parentCursor.getValue(), expr.name); + } } + return null; + + case PATH: + // Tag context only + return tag != null ? resolvePathValue(expr, tag) : null; - if (isAttributeCondition) { // [@attr='value'] pattern - for (Xml.Attribute a : tag.getAttributes()) { - if ((a.getKeyAsString().equals(selector) || "*".equals(selector)) && a.getValueAsString().equals(value)) { - matchCurrentCondition = true; - break; + case ABSOLUTE_PATH: + // Resolve path from document root + if (expr.stringValue != null) { + Xml.Tag root = getRootTag(cursor); + if (root != null) { + Set pathMatches = findTagsByPath(root, expr.stringValue); + if (!pathMatches.isEmpty()) { + return pathMatches.iterator().next().getValue().orElse(""); } } - } else if (isFunctionCondition) { // [local-name()='name'] or [text()='value'] pattern - if ("text()".equals(selector)) { - matchCurrentCondition = tag.getValue().map(v -> v.equals(value)).orElse(false); - } else if (isAttributeElement) { - for (Xml.Attribute a : tag.getAttributes()) { - if (matchesElementAndFunction(new Cursor(cursor, a), element, selector, value)) { - matchCurrentCondition = true; - break; - } + } + return ""; + + case FUNCTION: + return resolveFunctionValue(expr, tag, attr, cursor, position, size); + + default: + return null; + } + } + + /** + * Unified function evaluation as boolean for both contexts. + */ + private boolean evaluateFunction(CompiledExpr expr, Xml.@Nullable Tag tag, Xml.@Nullable Attribute attr, + Cursor cursor, int position, int size) { + if (expr.functionType == null) { + return false; + } + + switch (expr.functionType) { + case POSITION: + return position > 0; + + case LAST: + return position == size; + + case NOT: + if (expr.args != null && expr.args.length > 0) { + return !evaluateExpr(expr.args[0], tag, attr, cursor, position, size); + } + return false; + + case CONTAINS: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String substr = resolveValue(expr.args[1], tag, attr, cursor, position, size); + return str != null && substr != null && str.contains(substr); + } + return false; + + case STARTS_WITH: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String prefix = resolveValue(expr.args[1], tag, attr, cursor, position, size); + return str != null && prefix != null && str.startsWith(prefix); + } + return false; + + case ENDS_WITH: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String suffix = resolveValue(expr.args[1], tag, attr, cursor, position, size); + return str != null && suffix != null && str.endsWith(suffix); + } + return false; + + case STRING_LENGTH: + if (expr.args != null && expr.args.length > 0) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + return str != null && !str.isEmpty(); + } + return false; + + case COUNT: + if (expr.args != null && expr.args.length > 0) { + CompiledExpr pathArg = expr.args[0]; + if (pathArg.type == ExprType.ABSOLUTE_PATH && pathArg.stringValue != null) { + Xml.Tag root = getRootTag(cursor); + if (root != null) { + Set matches = findTagsByPath(root, pathArg.stringValue); + return !matches.isEmpty(); } - } else { - matchCurrentCondition = matchesElementAndFunction(cursor, element, selector, value); } - } else { // other [] conditions - for (Xml.Tag t : FindTags.find(tag, selector)) { - if (t.getValue().map(v -> v.equals(value)).orElse(false)) { - matchCurrentCondition = true; - break; + } + return false; + + case TEXT: + // text() as existence check - tag context only + return tag != null && tag.getValue().isPresent() && !tag.getValue().get().trim().isEmpty(); + + case LOCAL_NAME: + case NAMESPACE_URI: + // These return strings, not booleans - but as existence check they're truthy + return true; + + default: + return false; + } + } + + /** + * Unified function value resolution for both contexts. + */ + private @Nullable String resolveFunctionValue(CompiledExpr expr, Xml.@Nullable Tag tag, Xml.@Nullable Attribute attr, + Cursor cursor, int position, int size) { + if (expr.functionType == null) { + return null; + } + + switch (expr.functionType) { + case POSITION: + return String.valueOf(position); + + case LAST: + return String.valueOf(size); + + case LOCAL_NAME: + if (tag != null) { + return localName(tag.getName()); + } + if (attr != null) { + return localName(attr.getKeyAsString()); + } + return null; + + case NAMESPACE_URI: + if (tag != null) { + return resolveNamespaceUri(tag, cursor); + } + if (attr != null) { + return resolveAttributeNamespaceUri(attr, cursor); + } + return null; + + case TEXT: + // Tag context only + return tag != null ? tag.getValue().orElse("") : null; + + case STRING_LENGTH: + if (expr.args != null && expr.args.length > 0) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + return str != null ? String.valueOf(str.length()) : "0"; + } + return "0"; + + case SUBSTRING_BEFORE: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String delim = resolveValue(expr.args[1], tag, attr, cursor, position, size); + String result = substringBefore(str, delim); + return result != null ? result : ""; + } + return ""; + + case SUBSTRING_AFTER: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String delim = resolveValue(expr.args[1], tag, attr, cursor, position, size); + String result = substringAfter(str, delim); + return result != null ? result : ""; + } + return ""; + + case COUNT: + if (expr.args != null && expr.args.length > 0) { + CompiledExpr pathArg = expr.args[0]; + if (pathArg.type == ExprType.ABSOLUTE_PATH && pathArg.stringValue != null) { + Xml.Tag root = getRootTag(cursor); + if (root != null) { + Set matches = findTagsByPath(root, pathArg.stringValue); + return String.valueOf(matches.size()); } } } - // break condition early if first OR condition is fulfilled - if (matchCurrentCondition && orCondition) { - break; + return "0"; + + case CONTAINS: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String substr = resolveValue(expr.args[1], tag, attr, cursor, position, size); + return String.valueOf(str != null && substr != null && str.contains(substr)); + } + return "false"; + + case STARTS_WITH: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String prefix = resolveValue(expr.args[1], tag, attr, cursor, position, size); + return String.valueOf(str != null && prefix != null && str.startsWith(prefix)); + } + return "false"; + + case ENDS_WITH: + if (expr.args != null && expr.args.length >= 2) { + String str = resolveValue(expr.args[0], tag, attr, cursor, position, size); + String suffix = resolveValue(expr.args[1], tag, attr, cursor, position, size); + return String.valueOf(str != null && suffix != null && str.endsWith(suffix)); + } + return "false"; + + default: + return null; + } + } + + /** + * Check if a path exists starting from a tag. + * Used for predicates like [a/b/c] which check existence of a descendant path. + */ + private boolean pathExists(Xml.Tag tag, CompiledExpr pathExpr) { + if (pathExpr.args == null || pathExpr.args.length == 0) { + return true; // Empty path always exists + } + + // Navigate through each step in the path + Xml.Tag current = tag; + for (CompiledExpr step : pathExpr.args) { + if (step.type != ExprType.CHILD) { + return false; // Only CHILD steps supported in path existence check + } + String childName = step.name; + Xml.Tag child = findChildTag(current, childName); + if (child == null) { + return false; + } + current = child; + } + return true; + } + + /** + * Find a child tag with the given name. + */ + private Xml.@Nullable Tag findChildTag(Xml.Tag parent, @Nullable String name) { + List contents = parent.getContent(); + if (contents == null) { + return null; + } + for (Content c : contents) { + if (c instanceof Xml.Tag) { + Xml.Tag child = (Xml.Tag) c; + if (name == null || "*".equals(name) || child.getName().equals(name)) { + return child; } + } + } + return null; + } + + /** + * Resolve a PATH expression value by navigating child elements. + */ + private @Nullable String resolvePathValue(CompiledExpr expr, Xml.Tag tag) { + if (expr.args == null || expr.args.length == 0) { + // No path steps - get text of current element + if (expr.functionType == FunctionType.TEXT) { + return tag.getValue().orElse(""); + } + return null; + } - stillMatchesConditions = matchCurrentCondition; + // Navigate through child elements following the path + Xml.Tag current = tag; + for (int i = 0; i < expr.args.length; i++) { + CompiledExpr step = expr.args[i]; + if (step.type != ExprType.CHILD) { + return null; + } + Xml.Tag child = findChildTag(current, step.name); + if (child == null) { + return null; } + current = child; } - return stillMatchesConditions ? element : null; + // Apply terminal function if present + if (expr.functionType == FunctionType.TEXT) { + return current.getValue().orElse(""); + } + + // Default: return text value of target element + return current.getValue().orElse(""); } - private static boolean matchesElementAndFunction(Cursor cursor, String element, String selector, String value) { - Namespaced namespaced = new Namespaced(cursor); - if (!"*".equals(element) && !Objects.equals(namespaced.getName().orElse(null), element)) { - return false; - } else if ("local-name()".equals(selector)) { - return Objects.equals(namespaced.getLocalName().orElse(null), value); - } else if ("namespace-uri()".equals(selector)) { - Optional nsUri = namespaced.getNamespaceUri(); - return nsUri.isPresent() && nsUri.get().equals(value); + /** + * Compare two values using the given comparison operator. + * Package-private so XPathMatcherVisitor can share this logic. + */ + static boolean compareValues(String left, String right, @Nullable ComparisonOp op) { + if (op == null) { + return left.equals(right); + } + + switch (op) { + case EQ: + return left.equals(right); + case NE: + return !left.equals(right); + case LT: + case LE: + case GT: + case GE: + // Try numeric comparison + try { + double leftNum = Double.parseDouble(left); + double rightNum = Double.parseDouble(right); + switch (op) { + case LT: + return leftNum < rightNum; + case LE: + return leftNum <= rightNum; + case GT: + return leftNum > rightNum; + case GE: + return leftNum >= rightNum; + default: + return false; + } + } catch (NumberFormatException e) { + // Fall back to string comparison + int cmp = left.compareTo(right); + switch (op) { + case LT: + return cmp < 0; + case LE: + return cmp <= 0; + case GT: + return cmp > 0; + case GE: + return cmp >= 0; + default: + return false; + } + } + default: + return false; + } + } + + // ==================== Static String Function Helpers ==================== + + private static @Nullable String substringBefore(@Nullable String str, @Nullable String delim) { + if (str == null || delim == null) { + return null; + } + int idx = str.indexOf(delim); + return idx >= 0 ? str.substring(0, idx) : ""; + } + + private static @Nullable String substringAfter(@Nullable String str, @Nullable String delim) { + if (str == null || delim == null) { + return null; + } + int idx = str.indexOf(delim); + return idx >= 0 ? str.substring(idx + delim.length()) : ""; + } + + private static String localName(String name) { + int colonIdx = name.indexOf(':'); + return colonIdx >= 0 ? name.substring(colonIdx + 1) : name; + } + + // ==================== Instance Helper Methods ==================== + + /** + * Check if tag has a child element with the given name. + */ + private boolean hasChildElement(Xml.Tag tag, @Nullable String name) { + return findChildTag(tag, name) != null; + } + + /** + * Get the text value of a child element. + */ + private @Nullable String getChildElementValue(Xml.Tag tag, @Nullable String name) { + Xml.Tag child = findChildTag(tag, name); + return child != null ? child.getValue().orElse("") : null; + } + + /** + * Check if tag has an attribute with the given name. + */ + private boolean hasAttribute(Xml.Tag tag, @Nullable String name) { + List attrs = tag.getAttributes(); + for (Xml.Attribute attr : attrs) { + if (name == null || "*".equals(name) || attr.getKeyAsString().equals(name)) { + return true; + } } return false; } + + /** + * Get the value of an attribute. + */ + private @Nullable String getAttributeValue(Xml.Tag tag, @Nullable String name) { + List attrs = tag.getAttributes(); + for (Xml.Attribute attr : attrs) { + if (name == null || "*".equals(name) || attr.getKeyAsString().equals(name)) { + return attr.getValueAsString(); + } + } + return null; + } + + /** + * Resolve namespace URI for a tag. + */ + private @Nullable String resolveNamespaceUri(Xml.Tag tag, Cursor cursor) { + String tagName = tag.getName(); + String prefix = ""; + int colonIdx = tagName.indexOf(':'); + if (colonIdx >= 0) { + prefix = tagName.substring(0, colonIdx); + } + return findNamespaceUri(prefix, cursor); + } + + /** + * Resolve namespace URI for an attribute. + */ + private @Nullable String resolveAttributeNamespaceUri(Xml.Attribute attr, Cursor cursor) { + String attrName = attr.getKeyAsString(); + int colonIdx = attrName.indexOf(':'); + if (colonIdx >= 0) { + String prefix = attrName.substring(0, colonIdx); + return findNamespaceUri(prefix, cursor); + } + return ""; + } + + /** + * Find namespace URI for a prefix by walking up the cursor. + */ + private @Nullable String findNamespaceUri(String prefix, Cursor cursor) { + String nsAttr = prefix.isEmpty() ? "xmlns" : "xmlns:" + prefix; + for (Cursor c = cursor; c != null; c = c.getParent()) { + if (c.getValue() instanceof Xml.Tag) { + Xml.Tag t = c.getValue(); + for (Xml.Attribute attr : t.getAttributes()) { + if (attr.getKeyAsString().equals(nsAttr)) { + return attr.getValueAsString(); + } + } + } + } + return prefix.isEmpty() ? "" : null; + } + + /** + * Handle non-path expressions (boolean expressions, filter expressions). + * Path expressions are handled by matchBottomUp. + */ + private boolean matchTopDown(Cursor cursor) { + switch (compiled.exprType) { + case XPathCompiler.EXPR_BOOLEAN: + return matchBooleanExpr(cursor); + case XPathCompiler.EXPR_FILTER: + return matchFilterExpr(cursor); + default: + // Path expressions should go through matchBottomUp + return false; + } + } + + // ==================== Boolean Expression Matching ==================== + + /** + * Match a boolean expression like contains(/root/element, 'value'). + *

+ * For expressions with pure absolute paths (/foo/bar): cursor must be at the root element. + *

+ * For expressions with descendant paths (//foo) or relative paths (foo/bar): + * evaluated from cursor context, matches at any position where the expression is true. + */ + @SuppressWarnings("DataFlowIssue") + private boolean matchBooleanExpr(Cursor cursor) { + if (compiled.booleanExpr == null) { + return false; + } + + // Cursor must be at a tag + if (!(cursor.getValue() instanceof Xml.Tag)) { + return false; + } + Xml.Tag currentTag = cursor.getValue(); + + // Check if expression contains relative paths or descendant paths + // Relative paths (foo/bar) and descendant paths (//foo) can match at any cursor position + // Only pure absolute paths (/foo/bar) require cursor to be at root + if (compiled.booleanExpr.hasRelativePath() || !compiled.booleanExpr.hasPureAbsolutePath()) { + // Relative or descendant paths: evaluate from cursor context at any position + return evaluateExpr(compiled.booleanExpr, currentTag, null, cursor, 1, 1); + } + + // Pure absolute paths only: only match at root element + // Check if cursor is at root (parent is Document or no parent tag) + Cursor parentCursor = cursor.getParent(); + while (parentCursor != null && !(parentCursor.getValue() instanceof Xml.Tag) && + !(parentCursor.getValue() instanceof Xml.Document)) { + parentCursor = parentCursor.getParent(); + } + if (parentCursor == null || !(parentCursor.getValue() instanceof Xml.Document)) { + return false; // Not at root element + } + + // Evaluate the expression from current (root) context + return evaluateExpr(compiled.booleanExpr, currentTag, null, cursor, 1, 1); + } + + // ==================== Filter Expression Matching ==================== + + /** + * Match a filter expression like (/root/a)[1] or (/root/a)[last()]/child. + */ + @SuppressWarnings("DataFlowIssue") + private boolean matchFilterExpr(Cursor cursor) { + if (compiled.filterExpr == null) { + return false; + } + + Xml.Tag root = getRootTag(cursor); + if (root == null) { + return false; + } + + // Find all matching nodes + Set allMatches = findTagsByPath(root, compiled.filterExpr.pathExpr); + if (allMatches.isEmpty()) { + return false; + } + + // Convert to list for positional access + List matchList = new ArrayList<>(allMatches); + int size = matchList.size(); + + // Apply predicates to filter the result set + List filteredMatches = new ArrayList<>(); + for (int i = 0; i < matchList.size(); i++) { + Xml.Tag tag = matchList.get(i); + int position = i + 1; // 1-based + boolean allPredicatesMatch = true; + for (CompiledExpr predicate : compiled.filterExpr.predicates) { + if (!evaluateFilterPredicate(predicate, tag, position, size)) { + allPredicatesMatch = false; + break; + } + } + if (allPredicatesMatch) { + filteredMatches.add(tag); + } + } + + if (filteredMatches.isEmpty()) { + return false; + } + + // Get current tag from cursor + Xml.Tag currentTag = null; + if (cursor.getValue() instanceof Xml.Tag) { + currentTag = cursor.getValue(); + } + if (currentTag == null) { + return false; + } + + // Check if there's a trailing path after the predicates + if (compiled.filterExpr.trailingPath != null) { + Set trailingMatches = new LinkedHashSet<>(); + for (Xml.Tag filteredTag : filteredMatches) { + if (compiled.filterExpr.trailingIsDescendant) { + findDescendants(filteredTag, compiled.filterExpr.trailingPath, trailingMatches); + } else { + trailingMatches.addAll(findDirectChildren(filteredTag, compiled.filterExpr.trailingPath)); + } + } + return trailingMatches.contains(currentTag); + } else { + return filteredMatches.contains(currentTag); + } + } + + /** + * Evaluate a predicate for filter expressions. + */ + @SuppressWarnings("DataFlowIssue") + private boolean evaluateFilterPredicate(CompiledExpr expr, Xml.Tag tag, int position, int size) { + switch (expr.type) { + case NUMERIC: + return position == expr.numericValue; + + case FUNCTION: + if (expr.functionType == FunctionType.LAST) { + return position == size; + } + if (expr.functionType == FunctionType.POSITION) { + return position > 0; + } + return false; + + case COMPARISON: + // For filter predicates, comparisons often involve position()/last() + String leftValue = resolveFilterValue(expr.left, tag, position, size); + String rightValue = resolveFilterValue(expr.right, tag, position, size); + if (leftValue == null || rightValue == null) { + return false; + } + return compareValues(leftValue, rightValue, expr.op); + + default: + return true; // Be permissive for unsupported expressions + } + } + + /** + * Resolve a value for filter predicate evaluation. + */ + @SuppressWarnings("ConstantValue") + private @Nullable String resolveFilterValue(CompiledExpr expr, Xml.Tag tag, int position, int size) { + if (expr == null) return null; + + switch (expr.type) { + case STRING: + return expr.stringValue; + case NUMERIC: + return String.valueOf(expr.numericValue); + case FUNCTION: + if (expr.functionType == FunctionType.POSITION) { + return String.valueOf(position); + } + if (expr.functionType == FunctionType.LAST) { + return String.valueOf(size); + } + if (expr.functionType == FunctionType.LOCAL_NAME) { + return localName(tag.getName()); + } + return null; + default: + return null; + } + } + + // ==================== Tree Traversal Helpers ==================== + + /** + * Get the cursor at the root tag by walking up to the document. + */ + private @Nullable Cursor getRootCursor(Cursor cursor) { + Cursor c = cursor; + while (c.getParent() != null && !(c.getParent().getValue() instanceof Xml.Document)) { + c = c.getParent(); + } + return c.getValue() instanceof Xml.Tag ? c : null; + } + + /** + * Get the root tag from a cursor by walking up to the document. + */ + private Xml.@Nullable Tag getRootTag(Cursor cursor) { + Cursor rootCursor = getRootCursor(cursor); + return rootCursor != null ? (Xml.Tag) rootCursor.getValue() : null; + } + + /** + * Find tags matching a path expression starting from a root tag. + * Supports absolute paths (/a/b), descendant paths (//a), and relative paths (a/b). + */ + private Set findTagsByPath(Xml.Tag startTag, String pathExpr) { + Set result = new LinkedHashSet<>(); + + // Handle descendant-or-self axis (//) + if (pathExpr.startsWith("//")) { + String elementName = pathExpr.substring(2); + if (elementName.contains("/")) { + elementName = elementName.substring(0, elementName.indexOf('/')); + } + findDescendants(startTag, elementName, result); + return result; + } + + // Handle absolute path + boolean isAbsolute = pathExpr.startsWith("/"); + if (isAbsolute) { + pathExpr = pathExpr.substring(1); + } + + String[] steps = pathExpr.split("/"); + if (steps.length == 0) { + return result; + } + + Set currentMatches = new LinkedHashSet<>(); + + if (isAbsolute) { + // First step matches the root element itself + String firstStep = steps[0]; + if ("*".equals(firstStep) || startTag.getName().equals(firstStep)) { + if (steps.length == 1) { + currentMatches.add(startTag); + } else { + currentMatches = findDirectChildren(startTag, steps[1]); + for (int i = 2; i < steps.length; i++) { + Set nextMatches = new LinkedHashSet<>(); + for (Xml.Tag match : currentMatches) { + nextMatches.addAll(findDirectChildren(match, steps[i])); + } + currentMatches = nextMatches; + } + } + } + } else { + // Relative path - start with children + currentMatches = findDirectChildren(startTag, steps[0]); + for (int i = 1; i < steps.length; i++) { + Set nextMatches = new LinkedHashSet<>(); + for (Xml.Tag match : currentMatches) { + nextMatches.addAll(findDirectChildren(match, steps[i])); + } + currentMatches = nextMatches; + } + } + + return currentMatches; + } + + /** + * Find all descendant tags matching the given element name. + */ + private void findDescendants(Xml.Tag tag, String elementName, Set result) { + if ("*".equals(elementName) || tag.getName().equals(elementName)) { + result.add(tag); + } + List contents = tag.getContent(); + if (contents != null) { + for (Content content : contents) { + if (content instanceof Xml.Tag) { + findDescendants((Xml.Tag) content, elementName, result); + } + } + } + } + + /** + * Find direct children matching the given element name or wildcard. + */ + private Set findDirectChildren(Xml.Tag parent, String elementName) { + Set result = new LinkedHashSet<>(); + List contents = parent.getContent(); + if (contents == null) { + return result; + } + for (Content content : contents) { + if (content instanceof Xml.Tag) { + Xml.Tag child = (Xml.Tag) content; + if ("*".equals(elementName) || child.getName().equals(elementName)) { + result.add(child); + } + } + } + return result; + } + + /** + * Check if a name pattern matches an actual element name in an axis context. + * Handles wildcards (*), node() tests, and null patterns. + * Use this for element names where node() is a valid node type test. + * + * @param pattern the XPath name pattern (may be null, "*", "node", or a specific name) + * @param actualName the actual element name to match against + * @return true if the pattern matches the actual name + */ + private static boolean matchesElementName(@Nullable String pattern, String actualName) { + return pattern == null || "*".equals(pattern) || "node".equals(pattern) || actualName.equals(pattern); + } + + /** + * Check if a name pattern matches an actual name (without node() test). + * Handles wildcards (*) and null patterns. + * Use this for attribute names or simple element name tests. + * + * @param pattern the XPath name pattern (may be null, "*", or a specific name) + * @param actualName the actual name to match against + * @return true if the pattern matches the actual name + */ + private static boolean matchesName(@Nullable String pattern, String actualName) { + return pattern == null || "*".equals(pattern) || actualName.equals(pattern); + } } diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLLexer.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLLexer.java index b7047021f4..24e95132b0 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLLexer.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLLexer.java @@ -15,12 +15,14 @@ */ // Generated from ~/git/rewrite/rewrite-xml/src/main/antlr/XMLLexer.g4 by ANTLR 4.13.2 package org.openrewrite.xml.internal.grammar; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.LexerATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class XMLLexer extends Lexer { diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLParser.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLParser.java index 9ffcc3360f..ec71d61207 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLParser.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XMLParser.java @@ -15,16 +15,14 @@ */ // Generated from ~/git/rewrite/rewrite-xml/src/main/antlr/XMLParser.g4 by ANTLR 4.13.2 package org.openrewrite.xml.internal.grammar; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.ParserATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.tree.ParseTreeListener; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; -import org.antlr.v4.runtime.tree.TerminalNode; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class XMLParser extends Parser { @@ -61,22 +59,22 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, null, null, null, null, null, null, null, null, "'?'", null, "'<'", - null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, "'/>'", null, + null, null, null, null, null, null, null, null, null, "'?'", null, "'<'", + null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "'/>'", null, "'%@'", "'%'", "'/'", "'='" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "WS", "COMMENT", "CDATA", "ParamEntityRef", "EntityRef", "CharRef", - "SEA_WS", "UTF_ENCODING_BOM", "QUESTION_MARK", "SPECIAL_OPEN_XML", "OPEN", - "SPECIAL_OPEN", "DTD_OPEN", "JSP_COMMENT", "JSP_DECLARATION", "JSP_EXPRESSION", - "JSP_SCRIPTLET", "TEXT", "DTD_CLOSE", "DTD_SUBSET_OPEN", "DTD_S", "DOCTYPE", - "DTD_SUBSET_CLOSE", "MARKUP_OPEN", "DTS_SUBSET_S", "MARK_UP_CLOSE", "MARKUP_S", - "MARKUP_TEXT", "MARKUP_SUBSET", "PI_S", "PI_TEXT", "CLOSE", "SPECIAL_CLOSE", - "SLASH_CLOSE", "S", "DIRECTIVE_OPEN", "DIRECTIVE_CLOSE", "SLASH", "EQUALS", + null, "WS", "COMMENT", "CDATA", "ParamEntityRef", "EntityRef", "CharRef", + "SEA_WS", "UTF_ENCODING_BOM", "QUESTION_MARK", "SPECIAL_OPEN_XML", "OPEN", + "SPECIAL_OPEN", "DTD_OPEN", "JSP_COMMENT", "JSP_DECLARATION", "JSP_EXPRESSION", + "JSP_SCRIPTLET", "TEXT", "DTD_CLOSE", "DTD_SUBSET_OPEN", "DTD_S", "DOCTYPE", + "DTD_SUBSET_CLOSE", "MARKUP_OPEN", "DTS_SUBSET_S", "MARK_UP_CLOSE", "MARKUP_S", + "MARKUP_TEXT", "MARKUP_SUBSET", "PI_S", "PI_TEXT", "CLOSE", "SPECIAL_CLOSE", + "SLASH_CLOSE", "S", "DIRECTIVE_OPEN", "DIRECTIVE_CLOSE", "SLASH", "EQUALS", "STRING", "Name" }; } diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.interp b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.interp new file mode 100644 index 0000000000..03b70a6e56 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.interp @@ -0,0 +1,101 @@ +token literal names: +null +null +'/' +'//' +'::' +'[' +']' +'(' +')' +'@' +'..' +'.' +',' +'=' +'!=' +'<=' +'>=' +'<' +'>' +'*' +null +'and' +'or' +'local-name' +'namespace-uri' +null +null +null + +token symbolic names: +null +WS +SLASH +DOUBLE_SLASH +AXIS_SEP +LBRACKET +RBRACKET +LPAREN +RPAREN +AT +DOTDOT +DOT +COMMA +EQUALS +NOT_EQUALS +LTE +GTE +LT +GT +WILDCARD +NUMBER +AND +OR +LOCAL_NAME +NAMESPACE_URI +STRING_LITERAL +QNAME +NCNAME + +rule names: +WS +SLASH +DOUBLE_SLASH +AXIS_SEP +LBRACKET +RBRACKET +LPAREN +RPAREN +AT +DOTDOT +DOT +COMMA +EQUALS +NOT_EQUALS +LTE +GTE +LT +GT +WILDCARD +NUMBER +AND +OR +LOCAL_NAME +NAMESPACE_URI +STRING_LITERAL +QNAME +NCNAME +NCNAME_CHARS +NAME_START_CHAR +NAME_CHAR + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 27, 193, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 1, 0, 4, 0, 63, 8, 0, 11, 0, 12, 0, 64, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 4, 19, 112, 8, 19, 11, 19, 12, 19, 113, 1, 19, 1, 19, 4, 19, 118, 8, 19, 11, 19, 12, 19, 119, 3, 19, 122, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 5, 24, 158, 8, 24, 10, 24, 12, 24, 161, 9, 24, 1, 24, 1, 24, 1, 24, 5, 24, 166, 8, 24, 10, 24, 12, 24, 169, 9, 24, 1, 24, 3, 24, 172, 8, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 5, 27, 182, 8, 27, 10, 27, 12, 27, 185, 9, 27, 1, 28, 3, 28, 188, 8, 28, 1, 29, 1, 29, 3, 29, 192, 8, 29, 0, 0, 30, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 0, 57, 0, 59, 0, 1, 0, 6, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 1, 0, 39, 39, 1, 0, 34, 34, 14, 0, 65, 90, 95, 95, 97, 122, 192, 214, 216, 246, 248, 767, 880, 893, 895, 8191, 8204, 8205, 8304, 8591, 11264, 12271, 12289, 55295, 63744, 64975, 65008, 65533, 5, 0, 45, 46, 48, 57, 183, 183, 768, 879, 8255, 8256, 198, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 1, 62, 1, 0, 0, 0, 3, 68, 1, 0, 0, 0, 5, 70, 1, 0, 0, 0, 7, 73, 1, 0, 0, 0, 9, 76, 1, 0, 0, 0, 11, 78, 1, 0, 0, 0, 13, 80, 1, 0, 0, 0, 15, 82, 1, 0, 0, 0, 17, 84, 1, 0, 0, 0, 19, 86, 1, 0, 0, 0, 21, 89, 1, 0, 0, 0, 23, 91, 1, 0, 0, 0, 25, 93, 1, 0, 0, 0, 27, 95, 1, 0, 0, 0, 29, 98, 1, 0, 0, 0, 31, 101, 1, 0, 0, 0, 33, 104, 1, 0, 0, 0, 35, 106, 1, 0, 0, 0, 37, 108, 1, 0, 0, 0, 39, 111, 1, 0, 0, 0, 41, 123, 1, 0, 0, 0, 43, 127, 1, 0, 0, 0, 45, 130, 1, 0, 0, 0, 47, 141, 1, 0, 0, 0, 49, 171, 1, 0, 0, 0, 51, 173, 1, 0, 0, 0, 53, 177, 1, 0, 0, 0, 55, 179, 1, 0, 0, 0, 57, 187, 1, 0, 0, 0, 59, 191, 1, 0, 0, 0, 61, 63, 7, 0, 0, 0, 62, 61, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 62, 1, 0, 0, 0, 64, 65, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 67, 6, 0, 0, 0, 67, 2, 1, 0, 0, 0, 68, 69, 5, 47, 0, 0, 69, 4, 1, 0, 0, 0, 70, 71, 5, 47, 0, 0, 71, 72, 5, 47, 0, 0, 72, 6, 1, 0, 0, 0, 73, 74, 5, 58, 0, 0, 74, 75, 5, 58, 0, 0, 75, 8, 1, 0, 0, 0, 76, 77, 5, 91, 0, 0, 77, 10, 1, 0, 0, 0, 78, 79, 5, 93, 0, 0, 79, 12, 1, 0, 0, 0, 80, 81, 5, 40, 0, 0, 81, 14, 1, 0, 0, 0, 82, 83, 5, 41, 0, 0, 83, 16, 1, 0, 0, 0, 84, 85, 5, 64, 0, 0, 85, 18, 1, 0, 0, 0, 86, 87, 5, 46, 0, 0, 87, 88, 5, 46, 0, 0, 88, 20, 1, 0, 0, 0, 89, 90, 5, 46, 0, 0, 90, 22, 1, 0, 0, 0, 91, 92, 5, 44, 0, 0, 92, 24, 1, 0, 0, 0, 93, 94, 5, 61, 0, 0, 94, 26, 1, 0, 0, 0, 95, 96, 5, 33, 0, 0, 96, 97, 5, 61, 0, 0, 97, 28, 1, 0, 0, 0, 98, 99, 5, 60, 0, 0, 99, 100, 5, 61, 0, 0, 100, 30, 1, 0, 0, 0, 101, 102, 5, 62, 0, 0, 102, 103, 5, 61, 0, 0, 103, 32, 1, 0, 0, 0, 104, 105, 5, 60, 0, 0, 105, 34, 1, 0, 0, 0, 106, 107, 5, 62, 0, 0, 107, 36, 1, 0, 0, 0, 108, 109, 5, 42, 0, 0, 109, 38, 1, 0, 0, 0, 110, 112, 7, 1, 0, 0, 111, 110, 1, 0, 0, 0, 112, 113, 1, 0, 0, 0, 113, 111, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 121, 1, 0, 0, 0, 115, 117, 5, 46, 0, 0, 116, 118, 7, 1, 0, 0, 117, 116, 1, 0, 0, 0, 118, 119, 1, 0, 0, 0, 119, 117, 1, 0, 0, 0, 119, 120, 1, 0, 0, 0, 120, 122, 1, 0, 0, 0, 121, 115, 1, 0, 0, 0, 121, 122, 1, 0, 0, 0, 122, 40, 1, 0, 0, 0, 123, 124, 5, 97, 0, 0, 124, 125, 5, 110, 0, 0, 125, 126, 5, 100, 0, 0, 126, 42, 1, 0, 0, 0, 127, 128, 5, 111, 0, 0, 128, 129, 5, 114, 0, 0, 129, 44, 1, 0, 0, 0, 130, 131, 5, 108, 0, 0, 131, 132, 5, 111, 0, 0, 132, 133, 5, 99, 0, 0, 133, 134, 5, 97, 0, 0, 134, 135, 5, 108, 0, 0, 135, 136, 5, 45, 0, 0, 136, 137, 5, 110, 0, 0, 137, 138, 5, 97, 0, 0, 138, 139, 5, 109, 0, 0, 139, 140, 5, 101, 0, 0, 140, 46, 1, 0, 0, 0, 141, 142, 5, 110, 0, 0, 142, 143, 5, 97, 0, 0, 143, 144, 5, 109, 0, 0, 144, 145, 5, 101, 0, 0, 145, 146, 5, 115, 0, 0, 146, 147, 5, 112, 0, 0, 147, 148, 5, 97, 0, 0, 148, 149, 5, 99, 0, 0, 149, 150, 5, 101, 0, 0, 150, 151, 5, 45, 0, 0, 151, 152, 5, 117, 0, 0, 152, 153, 5, 114, 0, 0, 153, 154, 5, 105, 0, 0, 154, 48, 1, 0, 0, 0, 155, 159, 5, 39, 0, 0, 156, 158, 8, 2, 0, 0, 157, 156, 1, 0, 0, 0, 158, 161, 1, 0, 0, 0, 159, 157, 1, 0, 0, 0, 159, 160, 1, 0, 0, 0, 160, 162, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 162, 172, 5, 39, 0, 0, 163, 167, 5, 34, 0, 0, 164, 166, 8, 3, 0, 0, 165, 164, 1, 0, 0, 0, 166, 169, 1, 0, 0, 0, 167, 165, 1, 0, 0, 0, 167, 168, 1, 0, 0, 0, 168, 170, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 170, 172, 5, 34, 0, 0, 171, 155, 1, 0, 0, 0, 171, 163, 1, 0, 0, 0, 172, 50, 1, 0, 0, 0, 173, 174, 3, 55, 27, 0, 174, 175, 5, 58, 0, 0, 175, 176, 3, 55, 27, 0, 176, 52, 1, 0, 0, 0, 177, 178, 3, 55, 27, 0, 178, 54, 1, 0, 0, 0, 179, 183, 3, 57, 28, 0, 180, 182, 3, 59, 29, 0, 181, 180, 1, 0, 0, 0, 182, 185, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 56, 1, 0, 0, 0, 185, 183, 1, 0, 0, 0, 186, 188, 7, 4, 0, 0, 187, 186, 1, 0, 0, 0, 188, 58, 1, 0, 0, 0, 189, 192, 3, 57, 28, 0, 190, 192, 7, 5, 0, 0, 191, 189, 1, 0, 0, 0, 191, 190, 1, 0, 0, 0, 192, 60, 1, 0, 0, 0, 11, 0, 64, 113, 119, 121, 159, 167, 171, 183, 187, 191, 1, 6, 0, 0] \ No newline at end of file diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.java new file mode 100644 index 0000000000..ac46095e15 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.java @@ -0,0 +1,264 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /Users/knut/git/openrewrite/rewrite/rewrite-xml/src/main/antlr/XPathLexer.g4 by ANTLR 4.13.2 +package org.openrewrite.xml.internal.grammar; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) +public class XPathLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + WS=1, SLASH=2, DOUBLE_SLASH=3, AXIS_SEP=4, LBRACKET=5, RBRACKET=6, LPAREN=7, + RPAREN=8, AT=9, DOTDOT=10, DOT=11, COMMA=12, EQUALS=13, NOT_EQUALS=14, + LTE=15, GTE=16, LT=17, GT=18, WILDCARD=19, NUMBER=20, AND=21, OR=22, LOCAL_NAME=23, + NAMESPACE_URI=24, STRING_LITERAL=25, QNAME=26, NCNAME=27; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE" + }; + + private static String[] makeRuleNames() { + return new String[] { + "WS", "SLASH", "DOUBLE_SLASH", "AXIS_SEP", "LBRACKET", "RBRACKET", "LPAREN", + "RPAREN", "AT", "DOTDOT", "DOT", "COMMA", "EQUALS", "NOT_EQUALS", "LTE", + "GTE", "LT", "GT", "WILDCARD", "NUMBER", "AND", "OR", "LOCAL_NAME", "NAMESPACE_URI", + "STRING_LITERAL", "QNAME", "NCNAME", "NCNAME_CHARS", "NAME_START_CHAR", + "NAME_CHAR" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, "'/'", "'//'", "'::'", "'['", "']'", "'('", "')'", "'@'", + "'..'", "'.'", "','", "'='", "'!='", "'<='", "'>='", "'<'", "'>'", "'*'", + null, "'and'", "'or'", "'local-name'", "'namespace-uri'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "WS", "SLASH", "DOUBLE_SLASH", "AXIS_SEP", "LBRACKET", "RBRACKET", + "LPAREN", "RPAREN", "AT", "DOTDOT", "DOT", "COMMA", "EQUALS", "NOT_EQUALS", + "LTE", "GTE", "LT", "GT", "WILDCARD", "NUMBER", "AND", "OR", "LOCAL_NAME", + "NAMESPACE_URI", "STRING_LITERAL", "QNAME", "NCNAME" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + public XPathLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "XPathLexer.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + public static final String _serializedATN = + "\u0004\u0000\u001b\u00c1\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002"+ + "\u0001\u0007\u0001\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002"+ + "\u0004\u0007\u0004\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002"+ + "\u0007\u0007\u0007\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002"+ + "\u000b\u0007\u000b\u0002\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e"+ + "\u0002\u000f\u0007\u000f\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011"+ + "\u0002\u0012\u0007\u0012\u0002\u0013\u0007\u0013\u0002\u0014\u0007\u0014"+ + "\u0002\u0015\u0007\u0015\u0002\u0016\u0007\u0016\u0002\u0017\u0007\u0017"+ + "\u0002\u0018\u0007\u0018\u0002\u0019\u0007\u0019\u0002\u001a\u0007\u001a"+ + "\u0002\u001b\u0007\u001b\u0002\u001c\u0007\u001c\u0002\u001d\u0007\u001d"+ + "\u0001\u0000\u0004\u0000?\b\u0000\u000b\u0000\f\u0000@\u0001\u0000\u0001"+ + "\u0000\u0001\u0001\u0001\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+ + "\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0001\u0005\u0001"+ + "\u0005\u0001\u0006\u0001\u0006\u0001\u0007\u0001\u0007\u0001\b\u0001\b"+ + "\u0001\t\u0001\t\u0001\t\u0001\n\u0001\n\u0001\u000b\u0001\u000b\u0001"+ + "\f\u0001\f\u0001\r\u0001\r\u0001\r\u0001\u000e\u0001\u000e\u0001\u000e"+ + "\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u0010\u0001\u0010\u0001\u0011"+ + "\u0001\u0011\u0001\u0012\u0001\u0012\u0001\u0013\u0004\u0013p\b\u0013"+ + "\u000b\u0013\f\u0013q\u0001\u0013\u0001\u0013\u0004\u0013v\b\u0013\u000b"+ + "\u0013\f\u0013w\u0003\u0013z\b\u0013\u0001\u0014\u0001\u0014\u0001\u0014"+ + "\u0001\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0016\u0001\u0016"+ + "\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016"+ + "\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017\u0001\u0017"+ + "\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017"+ + "\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0018"+ + "\u0001\u0018\u0005\u0018\u009e\b\u0018\n\u0018\f\u0018\u00a1\t\u0018\u0001"+ + "\u0018\u0001\u0018\u0001\u0018\u0005\u0018\u00a6\b\u0018\n\u0018\f\u0018"+ + "\u00a9\t\u0018\u0001\u0018\u0003\u0018\u00ac\b\u0018\u0001\u0019\u0001"+ + "\u0019\u0001\u0019\u0001\u0019\u0001\u001a\u0001\u001a\u0001\u001b\u0001"+ + "\u001b\u0005\u001b\u00b6\b\u001b\n\u001b\f\u001b\u00b9\t\u001b\u0001\u001c"+ + "\u0003\u001c\u00bc\b\u001c\u0001\u001d\u0001\u001d\u0003\u001d\u00c0\b"+ + "\u001d\u0000\u0000\u001e\u0001\u0001\u0003\u0002\u0005\u0003\u0007\u0004"+ + "\t\u0005\u000b\u0006\r\u0007\u000f\b\u0011\t\u0013\n\u0015\u000b\u0017"+ + "\f\u0019\r\u001b\u000e\u001d\u000f\u001f\u0010!\u0011#\u0012%\u0013\'"+ + "\u0014)\u0015+\u0016-\u0017/\u00181\u00193\u001a5\u001b7\u00009\u0000"+ + ";\u0000\u0001\u0000\u0006\u0003\u0000\t\n\r\r \u0001\u000009\u0001\u0000"+ + "\'\'\u0001\u0000\"\"\u000e\u0000AZ__az\u00c0\u00d6\u00d8\u00f6\u00f8\u02ff"+ + "\u0370\u037d\u037f\u1fff\u200c\u200d\u2070\u218f\u2c00\u2fef\u3001\u8000"+ + "\ud7ff\u8000\uf900\u8000\ufdcf\u8000\ufdf0\u8000\ufffd\u0005\u0000-.0"+ + "9\u00b7\u00b7\u0300\u036f\u203f\u2040\u00c6\u0000\u0001\u0001\u0000\u0000"+ + "\u0000\u0000\u0003\u0001\u0000\u0000\u0000\u0000\u0005\u0001\u0000\u0000"+ + "\u0000\u0000\u0007\u0001\u0000\u0000\u0000\u0000\t\u0001\u0000\u0000\u0000"+ + "\u0000\u000b\u0001\u0000\u0000\u0000\u0000\r\u0001\u0000\u0000\u0000\u0000"+ + "\u000f\u0001\u0000\u0000\u0000\u0000\u0011\u0001\u0000\u0000\u0000\u0000"+ + "\u0013\u0001\u0000\u0000\u0000\u0000\u0015\u0001\u0000\u0000\u0000\u0000"+ + "\u0017\u0001\u0000\u0000\u0000\u0000\u0019\u0001\u0000\u0000\u0000\u0000"+ + "\u001b\u0001\u0000\u0000\u0000\u0000\u001d\u0001\u0000\u0000\u0000\u0000"+ + "\u001f\u0001\u0000\u0000\u0000\u0000!\u0001\u0000\u0000\u0000\u0000#\u0001"+ + "\u0000\u0000\u0000\u0000%\u0001\u0000\u0000\u0000\u0000\'\u0001\u0000"+ + "\u0000\u0000\u0000)\u0001\u0000\u0000\u0000\u0000+\u0001\u0000\u0000\u0000"+ + "\u0000-\u0001\u0000\u0000\u0000\u0000/\u0001\u0000\u0000\u0000\u00001"+ + "\u0001\u0000\u0000\u0000\u00003\u0001\u0000\u0000\u0000\u00005\u0001\u0000"+ + "\u0000\u0000\u0001>\u0001\u0000\u0000\u0000\u0003D\u0001\u0000\u0000\u0000"+ + "\u0005F\u0001\u0000\u0000\u0000\u0007I\u0001\u0000\u0000\u0000\tL\u0001"+ + "\u0000\u0000\u0000\u000bN\u0001\u0000\u0000\u0000\rP\u0001\u0000\u0000"+ + "\u0000\u000fR\u0001\u0000\u0000\u0000\u0011T\u0001\u0000\u0000\u0000\u0013"+ + "V\u0001\u0000\u0000\u0000\u0015Y\u0001\u0000\u0000\u0000\u0017[\u0001"+ + "\u0000\u0000\u0000\u0019]\u0001\u0000\u0000\u0000\u001b_\u0001\u0000\u0000"+ + "\u0000\u001db\u0001\u0000\u0000\u0000\u001fe\u0001\u0000\u0000\u0000!"+ + "h\u0001\u0000\u0000\u0000#j\u0001\u0000\u0000\u0000%l\u0001\u0000\u0000"+ + "\u0000\'o\u0001\u0000\u0000\u0000){\u0001\u0000\u0000\u0000+\u007f\u0001"+ + "\u0000\u0000\u0000-\u0082\u0001\u0000\u0000\u0000/\u008d\u0001\u0000\u0000"+ + "\u00001\u00ab\u0001\u0000\u0000\u00003\u00ad\u0001\u0000\u0000\u00005"+ + "\u00b1\u0001\u0000\u0000\u00007\u00b3\u0001\u0000\u0000\u00009\u00bb\u0001"+ + "\u0000\u0000\u0000;\u00bf\u0001\u0000\u0000\u0000=?\u0007\u0000\u0000"+ + "\u0000>=\u0001\u0000\u0000\u0000?@\u0001\u0000\u0000\u0000@>\u0001\u0000"+ + "\u0000\u0000@A\u0001\u0000\u0000\u0000AB\u0001\u0000\u0000\u0000BC\u0006"+ + "\u0000\u0000\u0000C\u0002\u0001\u0000\u0000\u0000DE\u0005/\u0000\u0000"+ + "E\u0004\u0001\u0000\u0000\u0000FG\u0005/\u0000\u0000GH\u0005/\u0000\u0000"+ + "H\u0006\u0001\u0000\u0000\u0000IJ\u0005:\u0000\u0000JK\u0005:\u0000\u0000"+ + "K\b\u0001\u0000\u0000\u0000LM\u0005[\u0000\u0000M\n\u0001\u0000\u0000"+ + "\u0000NO\u0005]\u0000\u0000O\f\u0001\u0000\u0000\u0000PQ\u0005(\u0000"+ + "\u0000Q\u000e\u0001\u0000\u0000\u0000RS\u0005)\u0000\u0000S\u0010\u0001"+ + "\u0000\u0000\u0000TU\u0005@\u0000\u0000U\u0012\u0001\u0000\u0000\u0000"+ + "VW\u0005.\u0000\u0000WX\u0005.\u0000\u0000X\u0014\u0001\u0000\u0000\u0000"+ + "YZ\u0005.\u0000\u0000Z\u0016\u0001\u0000\u0000\u0000[\\\u0005,\u0000\u0000"+ + "\\\u0018\u0001\u0000\u0000\u0000]^\u0005=\u0000\u0000^\u001a\u0001\u0000"+ + "\u0000\u0000_`\u0005!\u0000\u0000`a\u0005=\u0000\u0000a\u001c\u0001\u0000"+ + "\u0000\u0000bc\u0005<\u0000\u0000cd\u0005=\u0000\u0000d\u001e\u0001\u0000"+ + "\u0000\u0000ef\u0005>\u0000\u0000fg\u0005=\u0000\u0000g \u0001\u0000\u0000"+ + "\u0000hi\u0005<\u0000\u0000i\"\u0001\u0000\u0000\u0000jk\u0005>\u0000"+ + "\u0000k$\u0001\u0000\u0000\u0000lm\u0005*\u0000\u0000m&\u0001\u0000\u0000"+ + "\u0000np\u0007\u0001\u0000\u0000on\u0001\u0000\u0000\u0000pq\u0001\u0000"+ + "\u0000\u0000qo\u0001\u0000\u0000\u0000qr\u0001\u0000\u0000\u0000ry\u0001"+ + "\u0000\u0000\u0000su\u0005.\u0000\u0000tv\u0007\u0001\u0000\u0000ut\u0001"+ + "\u0000\u0000\u0000vw\u0001\u0000\u0000\u0000wu\u0001\u0000\u0000\u0000"+ + "wx\u0001\u0000\u0000\u0000xz\u0001\u0000\u0000\u0000ys\u0001\u0000\u0000"+ + "\u0000yz\u0001\u0000\u0000\u0000z(\u0001\u0000\u0000\u0000{|\u0005a\u0000"+ + "\u0000|}\u0005n\u0000\u0000}~\u0005d\u0000\u0000~*\u0001\u0000\u0000\u0000"+ + "\u007f\u0080\u0005o\u0000\u0000\u0080\u0081\u0005r\u0000\u0000\u0081,"+ + "\u0001\u0000\u0000\u0000\u0082\u0083\u0005l\u0000\u0000\u0083\u0084\u0005"+ + "o\u0000\u0000\u0084\u0085\u0005c\u0000\u0000\u0085\u0086\u0005a\u0000"+ + "\u0000\u0086\u0087\u0005l\u0000\u0000\u0087\u0088\u0005-\u0000\u0000\u0088"+ + "\u0089\u0005n\u0000\u0000\u0089\u008a\u0005a\u0000\u0000\u008a\u008b\u0005"+ + "m\u0000\u0000\u008b\u008c\u0005e\u0000\u0000\u008c.\u0001\u0000\u0000"+ + "\u0000\u008d\u008e\u0005n\u0000\u0000\u008e\u008f\u0005a\u0000\u0000\u008f"+ + "\u0090\u0005m\u0000\u0000\u0090\u0091\u0005e\u0000\u0000\u0091\u0092\u0005"+ + "s\u0000\u0000\u0092\u0093\u0005p\u0000\u0000\u0093\u0094\u0005a\u0000"+ + "\u0000\u0094\u0095\u0005c\u0000\u0000\u0095\u0096\u0005e\u0000\u0000\u0096"+ + "\u0097\u0005-\u0000\u0000\u0097\u0098\u0005u\u0000\u0000\u0098\u0099\u0005"+ + "r\u0000\u0000\u0099\u009a\u0005i\u0000\u0000\u009a0\u0001\u0000\u0000"+ + "\u0000\u009b\u009f\u0005\'\u0000\u0000\u009c\u009e\b\u0002\u0000\u0000"+ + "\u009d\u009c\u0001\u0000\u0000\u0000\u009e\u00a1\u0001\u0000\u0000\u0000"+ + "\u009f\u009d\u0001\u0000\u0000\u0000\u009f\u00a0\u0001\u0000\u0000\u0000"+ + "\u00a0\u00a2\u0001\u0000\u0000\u0000\u00a1\u009f\u0001\u0000\u0000\u0000"+ + "\u00a2\u00ac\u0005\'\u0000\u0000\u00a3\u00a7\u0005\"\u0000\u0000\u00a4"+ + "\u00a6\b\u0003\u0000\u0000\u00a5\u00a4\u0001\u0000\u0000\u0000\u00a6\u00a9"+ + "\u0001\u0000\u0000\u0000\u00a7\u00a5\u0001\u0000\u0000\u0000\u00a7\u00a8"+ + "\u0001\u0000\u0000\u0000\u00a8\u00aa\u0001\u0000\u0000\u0000\u00a9\u00a7"+ + "\u0001\u0000\u0000\u0000\u00aa\u00ac\u0005\"\u0000\u0000\u00ab\u009b\u0001"+ + "\u0000\u0000\u0000\u00ab\u00a3\u0001\u0000\u0000\u0000\u00ac2\u0001\u0000"+ + "\u0000\u0000\u00ad\u00ae\u00037\u001b\u0000\u00ae\u00af\u0005:\u0000\u0000"+ + "\u00af\u00b0\u00037\u001b\u0000\u00b04\u0001\u0000\u0000\u0000\u00b1\u00b2"+ + "\u00037\u001b\u0000\u00b26\u0001\u0000\u0000\u0000\u00b3\u00b7\u00039"+ + "\u001c\u0000\u00b4\u00b6\u0003;\u001d\u0000\u00b5\u00b4\u0001\u0000\u0000"+ + "\u0000\u00b6\u00b9\u0001\u0000\u0000\u0000\u00b7\u00b5\u0001\u0000\u0000"+ + "\u0000\u00b7\u00b8\u0001\u0000\u0000\u0000\u00b88\u0001\u0000\u0000\u0000"+ + "\u00b9\u00b7\u0001\u0000\u0000\u0000\u00ba\u00bc\u0007\u0004\u0000\u0000"+ + "\u00bb\u00ba\u0001\u0000\u0000\u0000\u00bc:\u0001\u0000\u0000\u0000\u00bd"+ + "\u00c0\u00039\u001c\u0000\u00be\u00c0\u0007\u0005\u0000\u0000\u00bf\u00bd"+ + "\u0001\u0000\u0000\u0000\u00bf\u00be\u0001\u0000\u0000\u0000\u00c0<\u0001"+ + "\u0000\u0000\u0000\u000b\u0000@qwy\u009f\u00a7\u00ab\u00b7\u00bb\u00bf"+ + "\u0001\u0006\u0000\u0000"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.tokens b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.tokens new file mode 100644 index 0000000000..68cbcd6cc2 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathLexer.tokens @@ -0,0 +1,49 @@ +WS=1 +SLASH=2 +DOUBLE_SLASH=3 +AXIS_SEP=4 +LBRACKET=5 +RBRACKET=6 +LPAREN=7 +RPAREN=8 +AT=9 +DOTDOT=10 +DOT=11 +COMMA=12 +EQUALS=13 +NOT_EQUALS=14 +LTE=15 +GTE=16 +LT=17 +GT=18 +WILDCARD=19 +NUMBER=20 +AND=21 +OR=22 +LOCAL_NAME=23 +NAMESPACE_URI=24 +STRING_LITERAL=25 +QNAME=26 +NCNAME=27 +'/'=2 +'//'=3 +'::'=4 +'['=5 +']'=6 +'('=7 +')'=8 +'@'=9 +'..'=10 +'.'=11 +','=12 +'='=13 +'!='=14 +'<='=15 +'>='=16 +'<'=17 +'>'=18 +'*'=19 +'and'=21 +'or'=22 +'local-name'=23 +'namespace-uri'=24 diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.interp b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.interp new file mode 100644 index 0000000000..2097a48dc8 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.interp @@ -0,0 +1,91 @@ +token literal names: +null +null +'/' +'//' +'::' +'[' +']' +'(' +')' +'@' +'..' +'.' +',' +'=' +'!=' +'<=' +'>=' +'<' +'>' +'*' +null +'and' +'or' +'local-name' +'namespace-uri' +null +null +null + +token symbolic names: +null +WS +SLASH +DOUBLE_SLASH +AXIS_SEP +LBRACKET +RBRACKET +LPAREN +RPAREN +AT +DOTDOT +DOT +COMMA +EQUALS +NOT_EQUALS +LTE +GTE +LT +GT +WILDCARD +NUMBER +AND +OR +LOCAL_NAME +NAMESPACE_URI +STRING_LITERAL +QNAME +NCNAME + +rule names: +xpathExpression +filterExpr +booleanExpr +comparisonOp +comparand +absoluteLocationPath +relativeLocationPath +pathSeparator +step +axisStep +axisName +abbreviatedStep +nodeTypeTest +attributeStep +nodeTest +predicate +predicateExpr +orExpr +andExpr +primaryExpr +predicateValue +functionCall +functionArgs +functionArg +childElementTest +stringLiteral + + +atn: +[4, 1, 27, 218, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 1, 0, 1, 0, 1, 0, 1, 0, 3, 0, 57, 8, 0, 1, 1, 1, 1, 1, 1, 3, 1, 62, 8, 1, 1, 1, 1, 1, 4, 1, 66, 8, 1, 11, 1, 12, 1, 67, 1, 1, 1, 1, 1, 1, 3, 1, 73, 8, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 80, 8, 2, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 86, 8, 4, 1, 5, 1, 5, 3, 5, 90, 8, 5, 1, 5, 1, 5, 3, 5, 94, 8, 5, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 100, 8, 6, 10, 6, 12, 6, 103, 9, 6, 1, 7, 1, 7, 1, 8, 1, 8, 5, 8, 109, 8, 8, 10, 8, 12, 8, 112, 9, 8, 1, 8, 1, 8, 5, 8, 116, 8, 8, 10, 8, 12, 8, 119, 9, 8, 1, 8, 1, 8, 5, 8, 123, 8, 8, 10, 8, 12, 8, 126, 9, 8, 1, 8, 1, 8, 3, 8, 130, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 5, 17, 158, 8, 17, 10, 17, 12, 17, 161, 9, 17, 1, 18, 1, 18, 1, 18, 5, 18, 166, 8, 18, 10, 18, 12, 18, 169, 9, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 176, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 3, 20, 183, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 194, 8, 21, 1, 21, 3, 21, 197, 8, 21, 1, 22, 1, 22, 1, 22, 5, 22, 202, 8, 22, 10, 22, 12, 22, 205, 9, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 3, 23, 212, 8, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 0, 0, 26, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 0, 4, 1, 0, 13, 18, 1, 0, 2, 3, 1, 0, 10, 11, 2, 0, 19, 19, 26, 27, 224, 0, 56, 1, 0, 0, 0, 2, 58, 1, 0, 0, 0, 4, 79, 1, 0, 0, 0, 6, 81, 1, 0, 0, 0, 8, 85, 1, 0, 0, 0, 10, 93, 1, 0, 0, 0, 12, 95, 1, 0, 0, 0, 14, 104, 1, 0, 0, 0, 16, 129, 1, 0, 0, 0, 18, 131, 1, 0, 0, 0, 20, 135, 1, 0, 0, 0, 22, 137, 1, 0, 0, 0, 24, 139, 1, 0, 0, 0, 26, 143, 1, 0, 0, 0, 28, 146, 1, 0, 0, 0, 30, 148, 1, 0, 0, 0, 32, 152, 1, 0, 0, 0, 34, 154, 1, 0, 0, 0, 36, 162, 1, 0, 0, 0, 38, 175, 1, 0, 0, 0, 40, 182, 1, 0, 0, 0, 42, 196, 1, 0, 0, 0, 44, 198, 1, 0, 0, 0, 46, 211, 1, 0, 0, 0, 48, 213, 1, 0, 0, 0, 50, 215, 1, 0, 0, 0, 52, 57, 3, 4, 2, 0, 53, 57, 3, 2, 1, 0, 54, 57, 3, 10, 5, 0, 55, 57, 3, 12, 6, 0, 56, 52, 1, 0, 0, 0, 56, 53, 1, 0, 0, 0, 56, 54, 1, 0, 0, 0, 56, 55, 1, 0, 0, 0, 57, 1, 1, 0, 0, 0, 58, 61, 5, 7, 0, 0, 59, 62, 3, 10, 5, 0, 60, 62, 3, 12, 6, 0, 61, 59, 1, 0, 0, 0, 61, 60, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 65, 5, 8, 0, 0, 64, 66, 3, 30, 15, 0, 65, 64, 1, 0, 0, 0, 66, 67, 1, 0, 0, 0, 67, 65, 1, 0, 0, 0, 67, 68, 1, 0, 0, 0, 68, 72, 1, 0, 0, 0, 69, 70, 3, 14, 7, 0, 70, 71, 3, 12, 6, 0, 71, 73, 1, 0, 0, 0, 72, 69, 1, 0, 0, 0, 72, 73, 1, 0, 0, 0, 73, 3, 1, 0, 0, 0, 74, 75, 3, 42, 21, 0, 75, 76, 3, 6, 3, 0, 76, 77, 3, 8, 4, 0, 77, 80, 1, 0, 0, 0, 78, 80, 3, 42, 21, 0, 79, 74, 1, 0, 0, 0, 79, 78, 1, 0, 0, 0, 80, 5, 1, 0, 0, 0, 81, 82, 7, 0, 0, 0, 82, 7, 1, 0, 0, 0, 83, 86, 3, 50, 25, 0, 84, 86, 5, 20, 0, 0, 85, 83, 1, 0, 0, 0, 85, 84, 1, 0, 0, 0, 86, 9, 1, 0, 0, 0, 87, 89, 5, 2, 0, 0, 88, 90, 3, 12, 6, 0, 89, 88, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 94, 1, 0, 0, 0, 91, 92, 5, 3, 0, 0, 92, 94, 3, 12, 6, 0, 93, 87, 1, 0, 0, 0, 93, 91, 1, 0, 0, 0, 94, 11, 1, 0, 0, 0, 95, 101, 3, 16, 8, 0, 96, 97, 3, 14, 7, 0, 97, 98, 3, 16, 8, 0, 98, 100, 1, 0, 0, 0, 99, 96, 1, 0, 0, 0, 100, 103, 1, 0, 0, 0, 101, 99, 1, 0, 0, 0, 101, 102, 1, 0, 0, 0, 102, 13, 1, 0, 0, 0, 103, 101, 1, 0, 0, 0, 104, 105, 7, 1, 0, 0, 105, 15, 1, 0, 0, 0, 106, 110, 3, 18, 9, 0, 107, 109, 3, 30, 15, 0, 108, 107, 1, 0, 0, 0, 109, 112, 1, 0, 0, 0, 110, 108, 1, 0, 0, 0, 110, 111, 1, 0, 0, 0, 111, 130, 1, 0, 0, 0, 112, 110, 1, 0, 0, 0, 113, 117, 3, 28, 14, 0, 114, 116, 3, 30, 15, 0, 115, 114, 1, 0, 0, 0, 116, 119, 1, 0, 0, 0, 117, 115, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 130, 1, 0, 0, 0, 119, 117, 1, 0, 0, 0, 120, 124, 3, 26, 13, 0, 121, 123, 3, 30, 15, 0, 122, 121, 1, 0, 0, 0, 123, 126, 1, 0, 0, 0, 124, 122, 1, 0, 0, 0, 124, 125, 1, 0, 0, 0, 125, 130, 1, 0, 0, 0, 126, 124, 1, 0, 0, 0, 127, 130, 3, 24, 12, 0, 128, 130, 3, 22, 11, 0, 129, 106, 1, 0, 0, 0, 129, 113, 1, 0, 0, 0, 129, 120, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 129, 128, 1, 0, 0, 0, 130, 17, 1, 0, 0, 0, 131, 132, 3, 20, 10, 0, 132, 133, 5, 4, 0, 0, 133, 134, 3, 28, 14, 0, 134, 19, 1, 0, 0, 0, 135, 136, 5, 27, 0, 0, 136, 21, 1, 0, 0, 0, 137, 138, 7, 2, 0, 0, 138, 23, 1, 0, 0, 0, 139, 140, 5, 27, 0, 0, 140, 141, 5, 7, 0, 0, 141, 142, 5, 8, 0, 0, 142, 25, 1, 0, 0, 0, 143, 144, 5, 9, 0, 0, 144, 145, 7, 3, 0, 0, 145, 27, 1, 0, 0, 0, 146, 147, 7, 3, 0, 0, 147, 29, 1, 0, 0, 0, 148, 149, 5, 5, 0, 0, 149, 150, 3, 32, 16, 0, 150, 151, 5, 6, 0, 0, 151, 31, 1, 0, 0, 0, 152, 153, 3, 34, 17, 0, 153, 33, 1, 0, 0, 0, 154, 159, 3, 36, 18, 0, 155, 156, 5, 22, 0, 0, 156, 158, 3, 36, 18, 0, 157, 155, 1, 0, 0, 0, 158, 161, 1, 0, 0, 0, 159, 157, 1, 0, 0, 0, 159, 160, 1, 0, 0, 0, 160, 35, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 162, 167, 3, 38, 19, 0, 163, 164, 5, 21, 0, 0, 164, 166, 3, 38, 19, 0, 165, 163, 1, 0, 0, 0, 166, 169, 1, 0, 0, 0, 167, 165, 1, 0, 0, 0, 167, 168, 1, 0, 0, 0, 168, 37, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 170, 171, 3, 40, 20, 0, 171, 172, 3, 6, 3, 0, 172, 173, 3, 8, 4, 0, 173, 176, 1, 0, 0, 0, 174, 176, 3, 40, 20, 0, 175, 170, 1, 0, 0, 0, 175, 174, 1, 0, 0, 0, 176, 39, 1, 0, 0, 0, 177, 183, 3, 42, 21, 0, 178, 183, 3, 26, 13, 0, 179, 183, 3, 12, 6, 0, 180, 183, 3, 48, 24, 0, 181, 183, 5, 20, 0, 0, 182, 177, 1, 0, 0, 0, 182, 178, 1, 0, 0, 0, 182, 179, 1, 0, 0, 0, 182, 180, 1, 0, 0, 0, 182, 181, 1, 0, 0, 0, 183, 41, 1, 0, 0, 0, 184, 185, 5, 23, 0, 0, 185, 186, 5, 7, 0, 0, 186, 197, 5, 8, 0, 0, 187, 188, 5, 24, 0, 0, 188, 189, 5, 7, 0, 0, 189, 197, 5, 8, 0, 0, 190, 191, 5, 27, 0, 0, 191, 193, 5, 7, 0, 0, 192, 194, 3, 44, 22, 0, 193, 192, 1, 0, 0, 0, 193, 194, 1, 0, 0, 0, 194, 195, 1, 0, 0, 0, 195, 197, 5, 8, 0, 0, 196, 184, 1, 0, 0, 0, 196, 187, 1, 0, 0, 0, 196, 190, 1, 0, 0, 0, 197, 43, 1, 0, 0, 0, 198, 203, 3, 46, 23, 0, 199, 200, 5, 12, 0, 0, 200, 202, 3, 46, 23, 0, 201, 199, 1, 0, 0, 0, 202, 205, 1, 0, 0, 0, 203, 201, 1, 0, 0, 0, 203, 204, 1, 0, 0, 0, 204, 45, 1, 0, 0, 0, 205, 203, 1, 0, 0, 0, 206, 212, 3, 10, 5, 0, 207, 212, 3, 42, 21, 0, 208, 212, 3, 12, 6, 0, 209, 212, 3, 50, 25, 0, 210, 212, 5, 20, 0, 0, 211, 206, 1, 0, 0, 0, 211, 207, 1, 0, 0, 0, 211, 208, 1, 0, 0, 0, 211, 209, 1, 0, 0, 0, 211, 210, 1, 0, 0, 0, 212, 47, 1, 0, 0, 0, 213, 214, 7, 3, 0, 0, 214, 49, 1, 0, 0, 0, 215, 216, 5, 25, 0, 0, 216, 51, 1, 0, 0, 0, 21, 56, 61, 67, 72, 79, 85, 89, 93, 101, 110, 117, 124, 129, 159, 167, 175, 182, 193, 196, 203, 211] \ No newline at end of file diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.java new file mode 100644 index 0000000000..6e3c5cc3c1 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.java @@ -0,0 +1,2040 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /Users/knut/git/openrewrite/rewrite/rewrite-xml/src/main/antlr/XPathParser.g4 by ANTLR 4.13.2 +package org.openrewrite.xml.internal.grammar; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) +public class XPathParser extends Parser { + static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + WS=1, SLASH=2, DOUBLE_SLASH=3, AXIS_SEP=4, LBRACKET=5, RBRACKET=6, LPAREN=7, + RPAREN=8, AT=9, DOTDOT=10, DOT=11, COMMA=12, EQUALS=13, NOT_EQUALS=14, + LTE=15, GTE=16, LT=17, GT=18, WILDCARD=19, NUMBER=20, AND=21, OR=22, LOCAL_NAME=23, + NAMESPACE_URI=24, STRING_LITERAL=25, QNAME=26, NCNAME=27; + public static final int + RULE_xpathExpression = 0, RULE_filterExpr = 1, RULE_booleanExpr = 2, RULE_comparisonOp = 3, + RULE_comparand = 4, RULE_absoluteLocationPath = 5, RULE_relativeLocationPath = 6, + RULE_pathSeparator = 7, RULE_step = 8, RULE_axisStep = 9, RULE_axisName = 10, + RULE_abbreviatedStep = 11, RULE_nodeTypeTest = 12, RULE_attributeStep = 13, + RULE_nodeTest = 14, RULE_predicate = 15, RULE_predicateExpr = 16, RULE_orExpr = 17, + RULE_andExpr = 18, RULE_primaryExpr = 19, RULE_predicateValue = 20, RULE_functionCall = 21, + RULE_functionArgs = 22, RULE_functionArg = 23, RULE_childElementTest = 24, + RULE_stringLiteral = 25; + private static String[] makeRuleNames() { + return new String[] { + "xpathExpression", "filterExpr", "booleanExpr", "comparisonOp", "comparand", + "absoluteLocationPath", "relativeLocationPath", "pathSeparator", "step", + "axisStep", "axisName", "abbreviatedStep", "nodeTypeTest", "attributeStep", + "nodeTest", "predicate", "predicateExpr", "orExpr", "andExpr", "primaryExpr", + "predicateValue", "functionCall", "functionArgs", "functionArg", "childElementTest", + "stringLiteral" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, "'/'", "'//'", "'::'", "'['", "']'", "'('", "')'", "'@'", + "'..'", "'.'", "','", "'='", "'!='", "'<='", "'>='", "'<'", "'>'", "'*'", + null, "'and'", "'or'", "'local-name'", "'namespace-uri'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "WS", "SLASH", "DOUBLE_SLASH", "AXIS_SEP", "LBRACKET", "RBRACKET", + "LPAREN", "RPAREN", "AT", "DOTDOT", "DOT", "COMMA", "EQUALS", "NOT_EQUALS", + "LTE", "GTE", "LT", "GT", "WILDCARD", "NUMBER", "AND", "OR", "LOCAL_NAME", + "NAMESPACE_URI", "STRING_LITERAL", "QNAME", "NCNAME" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + @Override + public String getGrammarFileName() { return "XPathParser.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public ATN getATN() { return _ATN; } + + public XPathParser(TokenStream input) { + super(input); + _interp = new ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @SuppressWarnings("CheckReturnValue") + public static class XpathExpressionContext extends ParserRuleContext { + public BooleanExprContext booleanExpr() { + return getRuleContext(BooleanExprContext.class,0); + } + public FilterExprContext filterExpr() { + return getRuleContext(FilterExprContext.class,0); + } + public AbsoluteLocationPathContext absoluteLocationPath() { + return getRuleContext(AbsoluteLocationPathContext.class,0); + } + public RelativeLocationPathContext relativeLocationPath() { + return getRuleContext(RelativeLocationPathContext.class,0); + } + public XpathExpressionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_xpathExpression; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterXpathExpression(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitXpathExpression(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitXpathExpression(this); + else return visitor.visitChildren(this); + } + } + + public final XpathExpressionContext xpathExpression() throws RecognitionException { + XpathExpressionContext _localctx = new XpathExpressionContext(_ctx, getState()); + enterRule(_localctx, 0, RULE_xpathExpression); + try { + setState(56); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,0,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(52); + booleanExpr(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(53); + filterExpr(); + } + break; + case 3: + enterOuterAlt(_localctx, 3); + { + setState(54); + absoluteLocationPath(); + } + break; + case 4: + enterOuterAlt(_localctx, 4); + { + setState(55); + relativeLocationPath(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FilterExprContext extends ParserRuleContext { + public TerminalNode LPAREN() { return getToken(XPathParser.LPAREN, 0); } + public TerminalNode RPAREN() { return getToken(XPathParser.RPAREN, 0); } + public AbsoluteLocationPathContext absoluteLocationPath() { + return getRuleContext(AbsoluteLocationPathContext.class,0); + } + public List relativeLocationPath() { + return getRuleContexts(RelativeLocationPathContext.class); + } + public RelativeLocationPathContext relativeLocationPath(int i) { + return getRuleContext(RelativeLocationPathContext.class,i); + } + public List predicate() { + return getRuleContexts(PredicateContext.class); + } + public PredicateContext predicate(int i) { + return getRuleContext(PredicateContext.class,i); + } + public PathSeparatorContext pathSeparator() { + return getRuleContext(PathSeparatorContext.class,0); + } + public FilterExprContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_filterExpr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterFilterExpr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitFilterExpr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitFilterExpr(this); + else return visitor.visitChildren(this); + } + } + + public final FilterExprContext filterExpr() throws RecognitionException { + FilterExprContext _localctx = new FilterExprContext(_ctx, getState()); + enterRule(_localctx, 2, RULE_filterExpr); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(58); + match(LPAREN); + setState(61); + _errHandler.sync(this); + switch (_input.LA(1)) { + case SLASH: + case DOUBLE_SLASH: + { + setState(59); + absoluteLocationPath(); + } + break; + case AT: + case DOTDOT: + case DOT: + case WILDCARD: + case QNAME: + case NCNAME: + { + setState(60); + relativeLocationPath(); + } + break; + default: + throw new NoViableAltException(this); + } + setState(63); + match(RPAREN); + setState(65); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(64); + predicate(); + } + } + setState(67); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( _la==LBRACKET ); + setState(72); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==SLASH || _la==DOUBLE_SLASH) { + { + setState(69); + pathSeparator(); + setState(70); + relativeLocationPath(); + } + } + + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class BooleanExprContext extends ParserRuleContext { + public FunctionCallContext functionCall() { + return getRuleContext(FunctionCallContext.class,0); + } + public ComparisonOpContext comparisonOp() { + return getRuleContext(ComparisonOpContext.class,0); + } + public ComparandContext comparand() { + return getRuleContext(ComparandContext.class,0); + } + public BooleanExprContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_booleanExpr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterBooleanExpr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitBooleanExpr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitBooleanExpr(this); + else return visitor.visitChildren(this); + } + } + + public final BooleanExprContext booleanExpr() throws RecognitionException { + BooleanExprContext _localctx = new BooleanExprContext(_ctx, getState()); + enterRule(_localctx, 4, RULE_booleanExpr); + try { + setState(79); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,4,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(74); + functionCall(); + setState(75); + comparisonOp(); + setState(76); + comparand(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(78); + functionCall(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ComparisonOpContext extends ParserRuleContext { + public TerminalNode EQUALS() { return getToken(XPathParser.EQUALS, 0); } + public TerminalNode NOT_EQUALS() { return getToken(XPathParser.NOT_EQUALS, 0); } + public TerminalNode LT() { return getToken(XPathParser.LT, 0); } + public TerminalNode GT() { return getToken(XPathParser.GT, 0); } + public TerminalNode LTE() { return getToken(XPathParser.LTE, 0); } + public TerminalNode GTE() { return getToken(XPathParser.GTE, 0); } + public ComparisonOpContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_comparisonOp; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterComparisonOp(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitComparisonOp(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitComparisonOp(this); + else return visitor.visitChildren(this); + } + } + + public final ComparisonOpContext comparisonOp() throws RecognitionException { + ComparisonOpContext _localctx = new ComparisonOpContext(_ctx, getState()); + enterRule(_localctx, 6, RULE_comparisonOp); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(81); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 516096L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ComparandContext extends ParserRuleContext { + public StringLiteralContext stringLiteral() { + return getRuleContext(StringLiteralContext.class,0); + } + public TerminalNode NUMBER() { return getToken(XPathParser.NUMBER, 0); } + public ComparandContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_comparand; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterComparand(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitComparand(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitComparand(this); + else return visitor.visitChildren(this); + } + } + + public final ComparandContext comparand() throws RecognitionException { + ComparandContext _localctx = new ComparandContext(_ctx, getState()); + enterRule(_localctx, 8, RULE_comparand); + try { + setState(85); + _errHandler.sync(this); + switch (_input.LA(1)) { + case STRING_LITERAL: + enterOuterAlt(_localctx, 1); + { + setState(83); + stringLiteral(); + } + break; + case NUMBER: + enterOuterAlt(_localctx, 2); + { + setState(84); + match(NUMBER); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class AbsoluteLocationPathContext extends ParserRuleContext { + public TerminalNode SLASH() { return getToken(XPathParser.SLASH, 0); } + public RelativeLocationPathContext relativeLocationPath() { + return getRuleContext(RelativeLocationPathContext.class,0); + } + public TerminalNode DOUBLE_SLASH() { return getToken(XPathParser.DOUBLE_SLASH, 0); } + public AbsoluteLocationPathContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_absoluteLocationPath; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterAbsoluteLocationPath(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitAbsoluteLocationPath(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitAbsoluteLocationPath(this); + else return visitor.visitChildren(this); + } + } + + public final AbsoluteLocationPathContext absoluteLocationPath() throws RecognitionException { + AbsoluteLocationPathContext _localctx = new AbsoluteLocationPathContext(_ctx, getState()); + enterRule(_localctx, 10, RULE_absoluteLocationPath); + int _la; + try { + setState(93); + _errHandler.sync(this); + switch (_input.LA(1)) { + case SLASH: + enterOuterAlt(_localctx, 1); + { + setState(87); + match(SLASH); + setState(89); + _errHandler.sync(this); + _la = _input.LA(1); + if ((((_la) & ~0x3f) == 0 && ((1L << _la) & 201854464L) != 0)) { + { + setState(88); + relativeLocationPath(); + } + } + + } + break; + case DOUBLE_SLASH: + enterOuterAlt(_localctx, 2); + { + setState(91); + match(DOUBLE_SLASH); + setState(92); + relativeLocationPath(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class RelativeLocationPathContext extends ParserRuleContext { + public List step() { + return getRuleContexts(StepContext.class); + } + public StepContext step(int i) { + return getRuleContext(StepContext.class,i); + } + public List pathSeparator() { + return getRuleContexts(PathSeparatorContext.class); + } + public PathSeparatorContext pathSeparator(int i) { + return getRuleContext(PathSeparatorContext.class,i); + } + public RelativeLocationPathContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_relativeLocationPath; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterRelativeLocationPath(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitRelativeLocationPath(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitRelativeLocationPath(this); + else return visitor.visitChildren(this); + } + } + + public final RelativeLocationPathContext relativeLocationPath() throws RecognitionException { + RelativeLocationPathContext _localctx = new RelativeLocationPathContext(_ctx, getState()); + enterRule(_localctx, 12, RULE_relativeLocationPath); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(95); + step(); + setState(101); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==SLASH || _la==DOUBLE_SLASH) { + { + { + setState(96); + pathSeparator(); + setState(97); + step(); + } + } + setState(103); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PathSeparatorContext extends ParserRuleContext { + public TerminalNode SLASH() { return getToken(XPathParser.SLASH, 0); } + public TerminalNode DOUBLE_SLASH() { return getToken(XPathParser.DOUBLE_SLASH, 0); } + public PathSeparatorContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_pathSeparator; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterPathSeparator(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitPathSeparator(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitPathSeparator(this); + else return visitor.visitChildren(this); + } + } + + public final PathSeparatorContext pathSeparator() throws RecognitionException { + PathSeparatorContext _localctx = new PathSeparatorContext(_ctx, getState()); + enterRule(_localctx, 14, RULE_pathSeparator); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(104); + _la = _input.LA(1); + if ( !(_la==SLASH || _la==DOUBLE_SLASH) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StepContext extends ParserRuleContext { + public AxisStepContext axisStep() { + return getRuleContext(AxisStepContext.class,0); + } + public List predicate() { + return getRuleContexts(PredicateContext.class); + } + public PredicateContext predicate(int i) { + return getRuleContext(PredicateContext.class,i); + } + public NodeTestContext nodeTest() { + return getRuleContext(NodeTestContext.class,0); + } + public AttributeStepContext attributeStep() { + return getRuleContext(AttributeStepContext.class,0); + } + public NodeTypeTestContext nodeTypeTest() { + return getRuleContext(NodeTypeTestContext.class,0); + } + public AbbreviatedStepContext abbreviatedStep() { + return getRuleContext(AbbreviatedStepContext.class,0); + } + public StepContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_step; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterStep(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitStep(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitStep(this); + else return visitor.visitChildren(this); + } + } + + public final StepContext step() throws RecognitionException { + StepContext _localctx = new StepContext(_ctx, getState()); + enterRule(_localctx, 16, RULE_step); + int _la; + try { + setState(129); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,12,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(106); + axisStep(); + setState(110); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==LBRACKET) { + { + { + setState(107); + predicate(); + } + } + setState(112); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(113); + nodeTest(); + setState(117); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==LBRACKET) { + { + { + setState(114); + predicate(); + } + } + setState(119); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + break; + case 3: + enterOuterAlt(_localctx, 3); + { + setState(120); + attributeStep(); + setState(124); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==LBRACKET) { + { + { + setState(121); + predicate(); + } + } + setState(126); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + break; + case 4: + enterOuterAlt(_localctx, 4); + { + setState(127); + nodeTypeTest(); + } + break; + case 5: + enterOuterAlt(_localctx, 5); + { + setState(128); + abbreviatedStep(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class AxisStepContext extends ParserRuleContext { + public AxisNameContext axisName() { + return getRuleContext(AxisNameContext.class,0); + } + public TerminalNode AXIS_SEP() { return getToken(XPathParser.AXIS_SEP, 0); } + public NodeTestContext nodeTest() { + return getRuleContext(NodeTestContext.class,0); + } + public AxisStepContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_axisStep; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterAxisStep(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitAxisStep(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitAxisStep(this); + else return visitor.visitChildren(this); + } + } + + public final AxisStepContext axisStep() throws RecognitionException { + AxisStepContext _localctx = new AxisStepContext(_ctx, getState()); + enterRule(_localctx, 18, RULE_axisStep); + try { + enterOuterAlt(_localctx, 1); + { + setState(131); + axisName(); + setState(132); + match(AXIS_SEP); + setState(133); + nodeTest(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class AxisNameContext extends ParserRuleContext { + public TerminalNode NCNAME() { return getToken(XPathParser.NCNAME, 0); } + public AxisNameContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_axisName; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterAxisName(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitAxisName(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitAxisName(this); + else return visitor.visitChildren(this); + } + } + + public final AxisNameContext axisName() throws RecognitionException { + AxisNameContext _localctx = new AxisNameContext(_ctx, getState()); + enterRule(_localctx, 20, RULE_axisName); + try { + enterOuterAlt(_localctx, 1); + { + setState(135); + match(NCNAME); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class AbbreviatedStepContext extends ParserRuleContext { + public TerminalNode DOTDOT() { return getToken(XPathParser.DOTDOT, 0); } + public TerminalNode DOT() { return getToken(XPathParser.DOT, 0); } + public AbbreviatedStepContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_abbreviatedStep; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterAbbreviatedStep(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitAbbreviatedStep(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitAbbreviatedStep(this); + else return visitor.visitChildren(this); + } + } + + public final AbbreviatedStepContext abbreviatedStep() throws RecognitionException { + AbbreviatedStepContext _localctx = new AbbreviatedStepContext(_ctx, getState()); + enterRule(_localctx, 22, RULE_abbreviatedStep); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(137); + _la = _input.LA(1); + if ( !(_la==DOTDOT || _la==DOT) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class NodeTypeTestContext extends ParserRuleContext { + public TerminalNode NCNAME() { return getToken(XPathParser.NCNAME, 0); } + public TerminalNode LPAREN() { return getToken(XPathParser.LPAREN, 0); } + public TerminalNode RPAREN() { return getToken(XPathParser.RPAREN, 0); } + public NodeTypeTestContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_nodeTypeTest; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterNodeTypeTest(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitNodeTypeTest(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitNodeTypeTest(this); + else return visitor.visitChildren(this); + } + } + + public final NodeTypeTestContext nodeTypeTest() throws RecognitionException { + NodeTypeTestContext _localctx = new NodeTypeTestContext(_ctx, getState()); + enterRule(_localctx, 24, RULE_nodeTypeTest); + try { + enterOuterAlt(_localctx, 1); + { + setState(139); + match(NCNAME); + setState(140); + match(LPAREN); + setState(141); + match(RPAREN); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class AttributeStepContext extends ParserRuleContext { + public TerminalNode AT() { return getToken(XPathParser.AT, 0); } + public TerminalNode QNAME() { return getToken(XPathParser.QNAME, 0); } + public TerminalNode NCNAME() { return getToken(XPathParser.NCNAME, 0); } + public TerminalNode WILDCARD() { return getToken(XPathParser.WILDCARD, 0); } + public AttributeStepContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_attributeStep; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterAttributeStep(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitAttributeStep(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitAttributeStep(this); + else return visitor.visitChildren(this); + } + } + + public final AttributeStepContext attributeStep() throws RecognitionException { + AttributeStepContext _localctx = new AttributeStepContext(_ctx, getState()); + enterRule(_localctx, 26, RULE_attributeStep); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(143); + match(AT); + setState(144); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 201850880L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class NodeTestContext extends ParserRuleContext { + public TerminalNode QNAME() { return getToken(XPathParser.QNAME, 0); } + public TerminalNode NCNAME() { return getToken(XPathParser.NCNAME, 0); } + public TerminalNode WILDCARD() { return getToken(XPathParser.WILDCARD, 0); } + public NodeTestContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_nodeTest; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterNodeTest(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitNodeTest(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitNodeTest(this); + else return visitor.visitChildren(this); + } + } + + public final NodeTestContext nodeTest() throws RecognitionException { + NodeTestContext _localctx = new NodeTestContext(_ctx, getState()); + enterRule(_localctx, 28, RULE_nodeTest); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(146); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 201850880L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PredicateContext extends ParserRuleContext { + public TerminalNode LBRACKET() { return getToken(XPathParser.LBRACKET, 0); } + public PredicateExprContext predicateExpr() { + return getRuleContext(PredicateExprContext.class,0); + } + public TerminalNode RBRACKET() { return getToken(XPathParser.RBRACKET, 0); } + public PredicateContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_predicate; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterPredicate(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitPredicate(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitPredicate(this); + else return visitor.visitChildren(this); + } + } + + public final PredicateContext predicate() throws RecognitionException { + PredicateContext _localctx = new PredicateContext(_ctx, getState()); + enterRule(_localctx, 30, RULE_predicate); + try { + enterOuterAlt(_localctx, 1); + { + setState(148); + match(LBRACKET); + setState(149); + predicateExpr(); + setState(150); + match(RBRACKET); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PredicateExprContext extends ParserRuleContext { + public OrExprContext orExpr() { + return getRuleContext(OrExprContext.class,0); + } + public PredicateExprContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_predicateExpr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterPredicateExpr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitPredicateExpr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitPredicateExpr(this); + else return visitor.visitChildren(this); + } + } + + public final PredicateExprContext predicateExpr() throws RecognitionException { + PredicateExprContext _localctx = new PredicateExprContext(_ctx, getState()); + enterRule(_localctx, 32, RULE_predicateExpr); + try { + enterOuterAlt(_localctx, 1); + { + setState(152); + orExpr(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class OrExprContext extends ParserRuleContext { + public List andExpr() { + return getRuleContexts(AndExprContext.class); + } + public AndExprContext andExpr(int i) { + return getRuleContext(AndExprContext.class,i); + } + public List OR() { return getTokens(XPathParser.OR); } + public TerminalNode OR(int i) { + return getToken(XPathParser.OR, i); + } + public OrExprContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_orExpr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterOrExpr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitOrExpr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitOrExpr(this); + else return visitor.visitChildren(this); + } + } + + public final OrExprContext orExpr() throws RecognitionException { + OrExprContext _localctx = new OrExprContext(_ctx, getState()); + enterRule(_localctx, 34, RULE_orExpr); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(154); + andExpr(); + setState(159); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==OR) { + { + { + setState(155); + match(OR); + setState(156); + andExpr(); + } + } + setState(161); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class AndExprContext extends ParserRuleContext { + public List primaryExpr() { + return getRuleContexts(PrimaryExprContext.class); + } + public PrimaryExprContext primaryExpr(int i) { + return getRuleContext(PrimaryExprContext.class,i); + } + public List AND() { return getTokens(XPathParser.AND); } + public TerminalNode AND(int i) { + return getToken(XPathParser.AND, i); + } + public AndExprContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_andExpr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterAndExpr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitAndExpr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitAndExpr(this); + else return visitor.visitChildren(this); + } + } + + public final AndExprContext andExpr() throws RecognitionException { + AndExprContext _localctx = new AndExprContext(_ctx, getState()); + enterRule(_localctx, 36, RULE_andExpr); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(162); + primaryExpr(); + setState(167); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==AND) { + { + { + setState(163); + match(AND); + setState(164); + primaryExpr(); + } + } + setState(169); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PrimaryExprContext extends ParserRuleContext { + public PredicateValueContext predicateValue() { + return getRuleContext(PredicateValueContext.class,0); + } + public ComparisonOpContext comparisonOp() { + return getRuleContext(ComparisonOpContext.class,0); + } + public ComparandContext comparand() { + return getRuleContext(ComparandContext.class,0); + } + public PrimaryExprContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_primaryExpr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterPrimaryExpr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitPrimaryExpr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitPrimaryExpr(this); + else return visitor.visitChildren(this); + } + } + + public final PrimaryExprContext primaryExpr() throws RecognitionException { + PrimaryExprContext _localctx = new PrimaryExprContext(_ctx, getState()); + enterRule(_localctx, 38, RULE_primaryExpr); + try { + setState(175); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,15,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(170); + predicateValue(); + setState(171); + comparisonOp(); + setState(172); + comparand(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(174); + predicateValue(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PredicateValueContext extends ParserRuleContext { + public FunctionCallContext functionCall() { + return getRuleContext(FunctionCallContext.class,0); + } + public AttributeStepContext attributeStep() { + return getRuleContext(AttributeStepContext.class,0); + } + public RelativeLocationPathContext relativeLocationPath() { + return getRuleContext(RelativeLocationPathContext.class,0); + } + public ChildElementTestContext childElementTest() { + return getRuleContext(ChildElementTestContext.class,0); + } + public TerminalNode NUMBER() { return getToken(XPathParser.NUMBER, 0); } + public PredicateValueContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_predicateValue; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterPredicateValue(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitPredicateValue(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitPredicateValue(this); + else return visitor.visitChildren(this); + } + } + + public final PredicateValueContext predicateValue() throws RecognitionException { + PredicateValueContext _localctx = new PredicateValueContext(_ctx, getState()); + enterRule(_localctx, 40, RULE_predicateValue); + try { + setState(182); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,16,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(177); + functionCall(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(178); + attributeStep(); + } + break; + case 3: + enterOuterAlt(_localctx, 3); + { + setState(179); + relativeLocationPath(); + } + break; + case 4: + enterOuterAlt(_localctx, 4); + { + setState(180); + childElementTest(); + } + break; + case 5: + enterOuterAlt(_localctx, 5); + { + setState(181); + match(NUMBER); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FunctionCallContext extends ParserRuleContext { + public TerminalNode LOCAL_NAME() { return getToken(XPathParser.LOCAL_NAME, 0); } + public TerminalNode LPAREN() { return getToken(XPathParser.LPAREN, 0); } + public TerminalNode RPAREN() { return getToken(XPathParser.RPAREN, 0); } + public TerminalNode NAMESPACE_URI() { return getToken(XPathParser.NAMESPACE_URI, 0); } + public TerminalNode NCNAME() { return getToken(XPathParser.NCNAME, 0); } + public FunctionArgsContext functionArgs() { + return getRuleContext(FunctionArgsContext.class,0); + } + public FunctionCallContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_functionCall; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterFunctionCall(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitFunctionCall(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitFunctionCall(this); + else return visitor.visitChildren(this); + } + } + + public final FunctionCallContext functionCall() throws RecognitionException { + FunctionCallContext _localctx = new FunctionCallContext(_ctx, getState()); + enterRule(_localctx, 42, RULE_functionCall); + int _la; + try { + setState(196); + _errHandler.sync(this); + switch (_input.LA(1)) { + case LOCAL_NAME: + enterOuterAlt(_localctx, 1); + { + setState(184); + match(LOCAL_NAME); + setState(185); + match(LPAREN); + setState(186); + match(RPAREN); + } + break; + case NAMESPACE_URI: + enterOuterAlt(_localctx, 2); + { + setState(187); + match(NAMESPACE_URI); + setState(188); + match(LPAREN); + setState(189); + match(RPAREN); + } + break; + case NCNAME: + enterOuterAlt(_localctx, 3); + { + setState(190); + match(NCNAME); + setState(191); + match(LPAREN); + setState(193); + _errHandler.sync(this); + _la = _input.LA(1); + if ((((_la) & ~0x3f) == 0 && ((1L << _la) & 261623308L) != 0)) { + { + setState(192); + functionArgs(); + } + } + + setState(195); + match(RPAREN); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FunctionArgsContext extends ParserRuleContext { + public List functionArg() { + return getRuleContexts(FunctionArgContext.class); + } + public FunctionArgContext functionArg(int i) { + return getRuleContext(FunctionArgContext.class,i); + } + public List COMMA() { return getTokens(XPathParser.COMMA); } + public TerminalNode COMMA(int i) { + return getToken(XPathParser.COMMA, i); + } + public FunctionArgsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_functionArgs; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterFunctionArgs(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitFunctionArgs(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitFunctionArgs(this); + else return visitor.visitChildren(this); + } + } + + public final FunctionArgsContext functionArgs() throws RecognitionException { + FunctionArgsContext _localctx = new FunctionArgsContext(_ctx, getState()); + enterRule(_localctx, 44, RULE_functionArgs); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(198); + functionArg(); + setState(203); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==COMMA) { + { + { + setState(199); + match(COMMA); + setState(200); + functionArg(); + } + } + setState(205); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FunctionArgContext extends ParserRuleContext { + public AbsoluteLocationPathContext absoluteLocationPath() { + return getRuleContext(AbsoluteLocationPathContext.class,0); + } + public FunctionCallContext functionCall() { + return getRuleContext(FunctionCallContext.class,0); + } + public RelativeLocationPathContext relativeLocationPath() { + return getRuleContext(RelativeLocationPathContext.class,0); + } + public StringLiteralContext stringLiteral() { + return getRuleContext(StringLiteralContext.class,0); + } + public TerminalNode NUMBER() { return getToken(XPathParser.NUMBER, 0); } + public FunctionArgContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_functionArg; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterFunctionArg(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitFunctionArg(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitFunctionArg(this); + else return visitor.visitChildren(this); + } + } + + public final FunctionArgContext functionArg() throws RecognitionException { + FunctionArgContext _localctx = new FunctionArgContext(_ctx, getState()); + enterRule(_localctx, 46, RULE_functionArg); + try { + setState(211); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,20,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(206); + absoluteLocationPath(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(207); + functionCall(); + } + break; + case 3: + enterOuterAlt(_localctx, 3); + { + setState(208); + relativeLocationPath(); + } + break; + case 4: + enterOuterAlt(_localctx, 4); + { + setState(209); + stringLiteral(); + } + break; + case 5: + enterOuterAlt(_localctx, 5); + { + setState(210); + match(NUMBER); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ChildElementTestContext extends ParserRuleContext { + public TerminalNode QNAME() { return getToken(XPathParser.QNAME, 0); } + public TerminalNode NCNAME() { return getToken(XPathParser.NCNAME, 0); } + public TerminalNode WILDCARD() { return getToken(XPathParser.WILDCARD, 0); } + public ChildElementTestContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_childElementTest; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterChildElementTest(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitChildElementTest(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitChildElementTest(this); + else return visitor.visitChildren(this); + } + } + + public final ChildElementTestContext childElementTest() throws RecognitionException { + ChildElementTestContext _localctx = new ChildElementTestContext(_ctx, getState()); + enterRule(_localctx, 48, RULE_childElementTest); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(213); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 201850880L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StringLiteralContext extends ParserRuleContext { + public TerminalNode STRING_LITERAL() { return getToken(XPathParser.STRING_LITERAL, 0); } + public StringLiteralContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_stringLiteral; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).enterStringLiteral(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof XPathParserListener ) ((XPathParserListener)listener).exitStringLiteral(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof XPathParserVisitor ) return ((XPathParserVisitor)visitor).visitStringLiteral(this); + else return visitor.visitChildren(this); + } + } + + public final StringLiteralContext stringLiteral() throws RecognitionException { + StringLiteralContext _localctx = new StringLiteralContext(_ctx, getState()); + enterRule(_localctx, 50, RULE_stringLiteral); + try { + enterOuterAlt(_localctx, 1); + { + setState(215); + match(STRING_LITERAL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static final String _serializedATN = + "\u0004\u0001\u001b\u00da\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004"+ + "\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007"+ + "\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b"+ + "\u0002\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007"+ + "\u000f\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011\u0002\u0012\u0007"+ + "\u0012\u0002\u0013\u0007\u0013\u0002\u0014\u0007\u0014\u0002\u0015\u0007"+ + "\u0015\u0002\u0016\u0007\u0016\u0002\u0017\u0007\u0017\u0002\u0018\u0007"+ + "\u0018\u0002\u0019\u0007\u0019\u0001\u0000\u0001\u0000\u0001\u0000\u0001"+ + "\u0000\u0003\u00009\b\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0003"+ + "\u0001>\b\u0001\u0001\u0001\u0001\u0001\u0004\u0001B\b\u0001\u000b\u0001"+ + "\f\u0001C\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0001I\b\u0001\u0001"+ + "\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0003\u0002P\b"+ + "\u0002\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0003\u0004V\b"+ + "\u0004\u0001\u0005\u0001\u0005\u0003\u0005Z\b\u0005\u0001\u0005\u0001"+ + "\u0005\u0003\u0005^\b\u0005\u0001\u0006\u0001\u0006\u0001\u0006\u0001"+ + "\u0006\u0005\u0006d\b\u0006\n\u0006\f\u0006g\t\u0006\u0001\u0007\u0001"+ + "\u0007\u0001\b\u0001\b\u0005\bm\b\b\n\b\f\bp\t\b\u0001\b\u0001\b\u0005"+ + "\bt\b\b\n\b\f\bw\t\b\u0001\b\u0001\b\u0005\b{\b\b\n\b\f\b~\t\b\u0001\b"+ + "\u0001\b\u0003\b\u0082\b\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001\n\u0001"+ + "\n\u0001\u000b\u0001\u000b\u0001\f\u0001\f\u0001\f\u0001\f\u0001\r\u0001"+ + "\r\u0001\r\u0001\u000e\u0001\u000e\u0001\u000f\u0001\u000f\u0001\u000f"+ + "\u0001\u000f\u0001\u0010\u0001\u0010\u0001\u0011\u0001\u0011\u0001\u0011"+ + "\u0005\u0011\u009e\b\u0011\n\u0011\f\u0011\u00a1\t\u0011\u0001\u0012\u0001"+ + "\u0012\u0001\u0012\u0005\u0012\u00a6\b\u0012\n\u0012\f\u0012\u00a9\t\u0012"+ + "\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0003\u0013"+ + "\u00b0\b\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014"+ + "\u0003\u0014\u00b7\b\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015"+ + "\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0003\u0015"+ + "\u00c2\b\u0015\u0001\u0015\u0003\u0015\u00c5\b\u0015\u0001\u0016\u0001"+ + "\u0016\u0001\u0016\u0005\u0016\u00ca\b\u0016\n\u0016\f\u0016\u00cd\t\u0016"+ + "\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0003\u0017"+ + "\u00d4\b\u0017\u0001\u0018\u0001\u0018\u0001\u0019\u0001\u0019\u0001\u0019"+ + "\u0000\u0000\u001a\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ + "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02\u0000\u0004\u0001\u0000\r\u0012"+ + "\u0001\u0000\u0002\u0003\u0001\u0000\n\u000b\u0002\u0000\u0013\u0013\u001a"+ + "\u001b\u00e0\u00008\u0001\u0000\u0000\u0000\u0002:\u0001\u0000\u0000\u0000"+ + "\u0004O\u0001\u0000\u0000\u0000\u0006Q\u0001\u0000\u0000\u0000\bU\u0001"+ + "\u0000\u0000\u0000\n]\u0001\u0000\u0000\u0000\f_\u0001\u0000\u0000\u0000"+ + "\u000eh\u0001\u0000\u0000\u0000\u0010\u0081\u0001\u0000\u0000\u0000\u0012"+ + "\u0083\u0001\u0000\u0000\u0000\u0014\u0087\u0001\u0000\u0000\u0000\u0016"+ + "\u0089\u0001\u0000\u0000\u0000\u0018\u008b\u0001\u0000\u0000\u0000\u001a"+ + "\u008f\u0001\u0000\u0000\u0000\u001c\u0092\u0001\u0000\u0000\u0000\u001e"+ + "\u0094\u0001\u0000\u0000\u0000 \u0098\u0001\u0000\u0000\u0000\"\u009a"+ + "\u0001\u0000\u0000\u0000$\u00a2\u0001\u0000\u0000\u0000&\u00af\u0001\u0000"+ + "\u0000\u0000(\u00b6\u0001\u0000\u0000\u0000*\u00c4\u0001\u0000\u0000\u0000"+ + ",\u00c6\u0001\u0000\u0000\u0000.\u00d3\u0001\u0000\u0000\u00000\u00d5"+ + "\u0001\u0000\u0000\u00002\u00d7\u0001\u0000\u0000\u000049\u0003\u0004"+ + "\u0002\u000059\u0003\u0002\u0001\u000069\u0003\n\u0005\u000079\u0003\f"+ + "\u0006\u000084\u0001\u0000\u0000\u000085\u0001\u0000\u0000\u000086\u0001"+ + "\u0000\u0000\u000087\u0001\u0000\u0000\u00009\u0001\u0001\u0000\u0000"+ + "\u0000:=\u0005\u0007\u0000\u0000;>\u0003\n\u0005\u0000<>\u0003\f\u0006"+ + "\u0000=;\u0001\u0000\u0000\u0000=<\u0001\u0000\u0000\u0000>?\u0001\u0000"+ + "\u0000\u0000?A\u0005\b\u0000\u0000@B\u0003\u001e\u000f\u0000A@\u0001\u0000"+ + "\u0000\u0000BC\u0001\u0000\u0000\u0000CA\u0001\u0000\u0000\u0000CD\u0001"+ + "\u0000\u0000\u0000DH\u0001\u0000\u0000\u0000EF\u0003\u000e\u0007\u0000"+ + "FG\u0003\f\u0006\u0000GI\u0001\u0000\u0000\u0000HE\u0001\u0000\u0000\u0000"+ + "HI\u0001\u0000\u0000\u0000I\u0003\u0001\u0000\u0000\u0000JK\u0003*\u0015"+ + "\u0000KL\u0003\u0006\u0003\u0000LM\u0003\b\u0004\u0000MP\u0001\u0000\u0000"+ + "\u0000NP\u0003*\u0015\u0000OJ\u0001\u0000\u0000\u0000ON\u0001\u0000\u0000"+ + "\u0000P\u0005\u0001\u0000\u0000\u0000QR\u0007\u0000\u0000\u0000R\u0007"+ + "\u0001\u0000\u0000\u0000SV\u00032\u0019\u0000TV\u0005\u0014\u0000\u0000"+ + "US\u0001\u0000\u0000\u0000UT\u0001\u0000\u0000\u0000V\t\u0001\u0000\u0000"+ + "\u0000WY\u0005\u0002\u0000\u0000XZ\u0003\f\u0006\u0000YX\u0001\u0000\u0000"+ + "\u0000YZ\u0001\u0000\u0000\u0000Z^\u0001\u0000\u0000\u0000[\\\u0005\u0003"+ + "\u0000\u0000\\^\u0003\f\u0006\u0000]W\u0001\u0000\u0000\u0000][\u0001"+ + "\u0000\u0000\u0000^\u000b\u0001\u0000\u0000\u0000_e\u0003\u0010\b\u0000"+ + "`a\u0003\u000e\u0007\u0000ab\u0003\u0010\b\u0000bd\u0001\u0000\u0000\u0000"+ + "c`\u0001\u0000\u0000\u0000dg\u0001\u0000\u0000\u0000ec\u0001\u0000\u0000"+ + "\u0000ef\u0001\u0000\u0000\u0000f\r\u0001\u0000\u0000\u0000ge\u0001\u0000"+ + "\u0000\u0000hi\u0007\u0001\u0000\u0000i\u000f\u0001\u0000\u0000\u0000"+ + "jn\u0003\u0012\t\u0000km\u0003\u001e\u000f\u0000lk\u0001\u0000\u0000\u0000"+ + "mp\u0001\u0000\u0000\u0000nl\u0001\u0000\u0000\u0000no\u0001\u0000\u0000"+ + "\u0000o\u0082\u0001\u0000\u0000\u0000pn\u0001\u0000\u0000\u0000qu\u0003"+ + "\u001c\u000e\u0000rt\u0003\u001e\u000f\u0000sr\u0001\u0000\u0000\u0000"+ + "tw\u0001\u0000\u0000\u0000us\u0001\u0000\u0000\u0000uv\u0001\u0000\u0000"+ + "\u0000v\u0082\u0001\u0000\u0000\u0000wu\u0001\u0000\u0000\u0000x|\u0003"+ + "\u001a\r\u0000y{\u0003\u001e\u000f\u0000zy\u0001\u0000\u0000\u0000{~\u0001"+ + "\u0000\u0000\u0000|z\u0001\u0000\u0000\u0000|}\u0001\u0000\u0000\u0000"+ + "}\u0082\u0001\u0000\u0000\u0000~|\u0001\u0000\u0000\u0000\u007f\u0082"+ + "\u0003\u0018\f\u0000\u0080\u0082\u0003\u0016\u000b\u0000\u0081j\u0001"+ + "\u0000\u0000\u0000\u0081q\u0001\u0000\u0000\u0000\u0081x\u0001\u0000\u0000"+ + "\u0000\u0081\u007f\u0001\u0000\u0000\u0000\u0081\u0080\u0001\u0000\u0000"+ + "\u0000\u0082\u0011\u0001\u0000\u0000\u0000\u0083\u0084\u0003\u0014\n\u0000"+ + "\u0084\u0085\u0005\u0004\u0000\u0000\u0085\u0086\u0003\u001c\u000e\u0000"+ + "\u0086\u0013\u0001\u0000\u0000\u0000\u0087\u0088\u0005\u001b\u0000\u0000"+ + "\u0088\u0015\u0001\u0000\u0000\u0000\u0089\u008a\u0007\u0002\u0000\u0000"+ + "\u008a\u0017\u0001\u0000\u0000\u0000\u008b\u008c\u0005\u001b\u0000\u0000"+ + "\u008c\u008d\u0005\u0007\u0000\u0000\u008d\u008e\u0005\b\u0000\u0000\u008e"+ + "\u0019\u0001\u0000\u0000\u0000\u008f\u0090\u0005\t\u0000\u0000\u0090\u0091"+ + "\u0007\u0003\u0000\u0000\u0091\u001b\u0001\u0000\u0000\u0000\u0092\u0093"+ + "\u0007\u0003\u0000\u0000\u0093\u001d\u0001\u0000\u0000\u0000\u0094\u0095"+ + "\u0005\u0005\u0000\u0000\u0095\u0096\u0003 \u0010\u0000\u0096\u0097\u0005"+ + "\u0006\u0000\u0000\u0097\u001f\u0001\u0000\u0000\u0000\u0098\u0099\u0003"+ + "\"\u0011\u0000\u0099!\u0001\u0000\u0000\u0000\u009a\u009f\u0003$\u0012"+ + "\u0000\u009b\u009c\u0005\u0016\u0000\u0000\u009c\u009e\u0003$\u0012\u0000"+ + "\u009d\u009b\u0001\u0000\u0000\u0000\u009e\u00a1\u0001\u0000\u0000\u0000"+ + "\u009f\u009d\u0001\u0000\u0000\u0000\u009f\u00a0\u0001\u0000\u0000\u0000"+ + "\u00a0#\u0001\u0000\u0000\u0000\u00a1\u009f\u0001\u0000\u0000\u0000\u00a2"+ + "\u00a7\u0003&\u0013\u0000\u00a3\u00a4\u0005\u0015\u0000\u0000\u00a4\u00a6"+ + "\u0003&\u0013\u0000\u00a5\u00a3\u0001\u0000\u0000\u0000\u00a6\u00a9\u0001"+ + "\u0000\u0000\u0000\u00a7\u00a5\u0001\u0000\u0000\u0000\u00a7\u00a8\u0001"+ + "\u0000\u0000\u0000\u00a8%\u0001\u0000\u0000\u0000\u00a9\u00a7\u0001\u0000"+ + "\u0000\u0000\u00aa\u00ab\u0003(\u0014\u0000\u00ab\u00ac\u0003\u0006\u0003"+ + "\u0000\u00ac\u00ad\u0003\b\u0004\u0000\u00ad\u00b0\u0001\u0000\u0000\u0000"+ + "\u00ae\u00b0\u0003(\u0014\u0000\u00af\u00aa\u0001\u0000\u0000\u0000\u00af"+ + "\u00ae\u0001\u0000\u0000\u0000\u00b0\'\u0001\u0000\u0000\u0000\u00b1\u00b7"+ + "\u0003*\u0015\u0000\u00b2\u00b7\u0003\u001a\r\u0000\u00b3\u00b7\u0003"+ + "\f\u0006\u0000\u00b4\u00b7\u00030\u0018\u0000\u00b5\u00b7\u0005\u0014"+ + "\u0000\u0000\u00b6\u00b1\u0001\u0000\u0000\u0000\u00b6\u00b2\u0001\u0000"+ + "\u0000\u0000\u00b6\u00b3\u0001\u0000\u0000\u0000\u00b6\u00b4\u0001\u0000"+ + "\u0000\u0000\u00b6\u00b5\u0001\u0000\u0000\u0000\u00b7)\u0001\u0000\u0000"+ + "\u0000\u00b8\u00b9\u0005\u0017\u0000\u0000\u00b9\u00ba\u0005\u0007\u0000"+ + "\u0000\u00ba\u00c5\u0005\b\u0000\u0000\u00bb\u00bc\u0005\u0018\u0000\u0000"+ + "\u00bc\u00bd\u0005\u0007\u0000\u0000\u00bd\u00c5\u0005\b\u0000\u0000\u00be"+ + "\u00bf\u0005\u001b\u0000\u0000\u00bf\u00c1\u0005\u0007\u0000\u0000\u00c0"+ + "\u00c2\u0003,\u0016\u0000\u00c1\u00c0\u0001\u0000\u0000\u0000\u00c1\u00c2"+ + "\u0001\u0000\u0000\u0000\u00c2\u00c3\u0001\u0000\u0000\u0000\u00c3\u00c5"+ + "\u0005\b\u0000\u0000\u00c4\u00b8\u0001\u0000\u0000\u0000\u00c4\u00bb\u0001"+ + "\u0000\u0000\u0000\u00c4\u00be\u0001\u0000\u0000\u0000\u00c5+\u0001\u0000"+ + "\u0000\u0000\u00c6\u00cb\u0003.\u0017\u0000\u00c7\u00c8\u0005\f\u0000"+ + "\u0000\u00c8\u00ca\u0003.\u0017\u0000\u00c9\u00c7\u0001\u0000\u0000\u0000"+ + "\u00ca\u00cd\u0001\u0000\u0000\u0000\u00cb\u00c9\u0001\u0000\u0000\u0000"+ + "\u00cb\u00cc\u0001\u0000\u0000\u0000\u00cc-\u0001\u0000\u0000\u0000\u00cd"+ + "\u00cb\u0001\u0000\u0000\u0000\u00ce\u00d4\u0003\n\u0005\u0000\u00cf\u00d4"+ + "\u0003*\u0015\u0000\u00d0\u00d4\u0003\f\u0006\u0000\u00d1\u00d4\u0003"+ + "2\u0019\u0000\u00d2\u00d4\u0005\u0014\u0000\u0000\u00d3\u00ce\u0001\u0000"+ + "\u0000\u0000\u00d3\u00cf\u0001\u0000\u0000\u0000\u00d3\u00d0\u0001\u0000"+ + "\u0000\u0000\u00d3\u00d1\u0001\u0000\u0000\u0000\u00d3\u00d2\u0001\u0000"+ + "\u0000\u0000\u00d4/\u0001\u0000\u0000\u0000\u00d5\u00d6\u0007\u0003\u0000"+ + "\u0000\u00d61\u0001\u0000\u0000\u0000\u00d7\u00d8\u0005\u0019\u0000\u0000"+ + "\u00d83\u0001\u0000\u0000\u0000\u00158=CHOUY]enu|\u0081\u009f\u00a7\u00af"+ + "\u00b6\u00c1\u00c4\u00cb\u00d3"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.tokens b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.tokens new file mode 100644 index 0000000000..68cbcd6cc2 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParser.tokens @@ -0,0 +1,49 @@ +WS=1 +SLASH=2 +DOUBLE_SLASH=3 +AXIS_SEP=4 +LBRACKET=5 +RBRACKET=6 +LPAREN=7 +RPAREN=8 +AT=9 +DOTDOT=10 +DOT=11 +COMMA=12 +EQUALS=13 +NOT_EQUALS=14 +LTE=15 +GTE=16 +LT=17 +GT=18 +WILDCARD=19 +NUMBER=20 +AND=21 +OR=22 +LOCAL_NAME=23 +NAMESPACE_URI=24 +STRING_LITERAL=25 +QNAME=26 +NCNAME=27 +'/'=2 +'//'=3 +'::'=4 +'['=5 +']'=6 +'('=7 +')'=8 +'@'=9 +'..'=10 +'.'=11 +','=12 +'='=13 +'!='=14 +'<='=15 +'>='=16 +'<'=17 +'>'=18 +'*'=19 +'and'=21 +'or'=22 +'local-name'=23 +'namespace-uri'=24 diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserBaseListener.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserBaseListener.java new file mode 100644 index 0000000000..3082575b03 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserBaseListener.java @@ -0,0 +1,367 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /Users/knut/git/openrewrite/rewrite/rewrite-xml/src/main/antlr/XPathParser.g4 by ANTLR 4.13.2 +package org.openrewrite.xml.internal.grammar; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +/** + * This class provides an empty implementation of {@link XPathParserListener}, + * which can be extended to create a listener which only needs to handle a subset + * of the available methods. + */ +@SuppressWarnings("CheckReturnValue") +public class XPathParserBaseListener implements XPathParserListener { + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterXpathExpression(XPathParser.XpathExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitXpathExpression(XPathParser.XpathExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFilterExpr(XPathParser.FilterExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFilterExpr(XPathParser.FilterExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterBooleanExpr(XPathParser.BooleanExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitBooleanExpr(XPathParser.BooleanExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterComparisonOp(XPathParser.ComparisonOpContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitComparisonOp(XPathParser.ComparisonOpContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterComparand(XPathParser.ComparandContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitComparand(XPathParser.ComparandContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterAbsoluteLocationPath(XPathParser.AbsoluteLocationPathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitAbsoluteLocationPath(XPathParser.AbsoluteLocationPathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterRelativeLocationPath(XPathParser.RelativeLocationPathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitRelativeLocationPath(XPathParser.RelativeLocationPathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPathSeparator(XPathParser.PathSeparatorContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPathSeparator(XPathParser.PathSeparatorContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterStep(XPathParser.StepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitStep(XPathParser.StepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterAxisStep(XPathParser.AxisStepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitAxisStep(XPathParser.AxisStepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterAxisName(XPathParser.AxisNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitAxisName(XPathParser.AxisNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterAbbreviatedStep(XPathParser.AbbreviatedStepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitAbbreviatedStep(XPathParser.AbbreviatedStepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterNodeTypeTest(XPathParser.NodeTypeTestContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitNodeTypeTest(XPathParser.NodeTypeTestContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterAttributeStep(XPathParser.AttributeStepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitAttributeStep(XPathParser.AttributeStepContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterNodeTest(XPathParser.NodeTestContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitNodeTest(XPathParser.NodeTestContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPredicate(XPathParser.PredicateContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPredicate(XPathParser.PredicateContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPredicateExpr(XPathParser.PredicateExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPredicateExpr(XPathParser.PredicateExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterOrExpr(XPathParser.OrExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitOrExpr(XPathParser.OrExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterAndExpr(XPathParser.AndExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitAndExpr(XPathParser.AndExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPrimaryExpr(XPathParser.PrimaryExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPrimaryExpr(XPathParser.PrimaryExprContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPredicateValue(XPathParser.PredicateValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPredicateValue(XPathParser.PredicateValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFunctionCall(XPathParser.FunctionCallContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFunctionCall(XPathParser.FunctionCallContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFunctionArgs(XPathParser.FunctionArgsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFunctionArgs(XPathParser.FunctionArgsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFunctionArg(XPathParser.FunctionArgContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFunctionArg(XPathParser.FunctionArgContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterChildElementTest(XPathParser.ChildElementTestContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitChildElementTest(XPathParser.ChildElementTestContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterStringLiteral(XPathParser.StringLiteralContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitStringLiteral(XPathParser.StringLiteralContext ctx) { } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitTerminal(TerminalNode node) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitErrorNode(ErrorNode node) { } +} \ No newline at end of file diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserBaseVisitor.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserBaseVisitor.java new file mode 100644 index 0000000000..06243552d8 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserBaseVisitor.java @@ -0,0 +1,212 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /Users/knut/git/openrewrite/rewrite/rewrite-xml/src/main/antlr/XPathParser.g4 by ANTLR 4.13.2 +package org.openrewrite.xml.internal.grammar; +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; + +/** + * This class provides an empty implementation of {@link XPathParserVisitor}, + * which can be extended to create a visitor which only needs to handle a subset + * of the available methods. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +@SuppressWarnings("CheckReturnValue") +public class XPathParserBaseVisitor extends AbstractParseTreeVisitor implements XPathParserVisitor { + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitXpathExpression(XPathParser.XpathExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFilterExpr(XPathParser.FilterExprContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitBooleanExpr(XPathParser.BooleanExprContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitComparisonOp(XPathParser.ComparisonOpContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitComparand(XPathParser.ComparandContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAbsoluteLocationPath(XPathParser.AbsoluteLocationPathContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitRelativeLocationPath(XPathParser.RelativeLocationPathContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPathSeparator(XPathParser.PathSeparatorContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStep(XPathParser.StepContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAxisStep(XPathParser.AxisStepContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAxisName(XPathParser.AxisNameContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAbbreviatedStep(XPathParser.AbbreviatedStepContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNodeTypeTest(XPathParser.NodeTypeTestContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAttributeStep(XPathParser.AttributeStepContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNodeTest(XPathParser.NodeTestContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPredicate(XPathParser.PredicateContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPredicateExpr(XPathParser.PredicateExprContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitOrExpr(XPathParser.OrExprContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAndExpr(XPathParser.AndExprContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPrimaryExpr(XPathParser.PrimaryExprContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPredicateValue(XPathParser.PredicateValueContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFunctionCall(XPathParser.FunctionCallContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFunctionArgs(XPathParser.FunctionArgsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFunctionArg(XPathParser.FunctionArgContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitChildElementTest(XPathParser.ChildElementTestContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStringLiteral(XPathParser.StringLiteralContext ctx) { return visitChildren(ctx); } +} \ No newline at end of file diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserListener.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserListener.java new file mode 100644 index 0000000000..d7b5e6b45e --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserListener.java @@ -0,0 +1,285 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /Users/knut/git/openrewrite/rewrite/rewrite-xml/src/main/antlr/XPathParser.g4 by ANTLR 4.13.2 +package org.openrewrite.xml.internal.grammar; +import org.antlr.v4.runtime.tree.ParseTreeListener; + +/** + * This interface defines a complete listener for a parse tree produced by + * {@link XPathParser}. + */ +public interface XPathParserListener extends ParseTreeListener { + /** + * Enter a parse tree produced by {@link XPathParser#xpathExpression}. + * @param ctx the parse tree + */ + void enterXpathExpression(XPathParser.XpathExpressionContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#xpathExpression}. + * @param ctx the parse tree + */ + void exitXpathExpression(XPathParser.XpathExpressionContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#filterExpr}. + * @param ctx the parse tree + */ + void enterFilterExpr(XPathParser.FilterExprContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#filterExpr}. + * @param ctx the parse tree + */ + void exitFilterExpr(XPathParser.FilterExprContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#booleanExpr}. + * @param ctx the parse tree + */ + void enterBooleanExpr(XPathParser.BooleanExprContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#booleanExpr}. + * @param ctx the parse tree + */ + void exitBooleanExpr(XPathParser.BooleanExprContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#comparisonOp}. + * @param ctx the parse tree + */ + void enterComparisonOp(XPathParser.ComparisonOpContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#comparisonOp}. + * @param ctx the parse tree + */ + void exitComparisonOp(XPathParser.ComparisonOpContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#comparand}. + * @param ctx the parse tree + */ + void enterComparand(XPathParser.ComparandContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#comparand}. + * @param ctx the parse tree + */ + void exitComparand(XPathParser.ComparandContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#absoluteLocationPath}. + * @param ctx the parse tree + */ + void enterAbsoluteLocationPath(XPathParser.AbsoluteLocationPathContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#absoluteLocationPath}. + * @param ctx the parse tree + */ + void exitAbsoluteLocationPath(XPathParser.AbsoluteLocationPathContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#relativeLocationPath}. + * @param ctx the parse tree + */ + void enterRelativeLocationPath(XPathParser.RelativeLocationPathContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#relativeLocationPath}. + * @param ctx the parse tree + */ + void exitRelativeLocationPath(XPathParser.RelativeLocationPathContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#pathSeparator}. + * @param ctx the parse tree + */ + void enterPathSeparator(XPathParser.PathSeparatorContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#pathSeparator}. + * @param ctx the parse tree + */ + void exitPathSeparator(XPathParser.PathSeparatorContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#step}. + * @param ctx the parse tree + */ + void enterStep(XPathParser.StepContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#step}. + * @param ctx the parse tree + */ + void exitStep(XPathParser.StepContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#axisStep}. + * @param ctx the parse tree + */ + void enterAxisStep(XPathParser.AxisStepContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#axisStep}. + * @param ctx the parse tree + */ + void exitAxisStep(XPathParser.AxisStepContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#axisName}. + * @param ctx the parse tree + */ + void enterAxisName(XPathParser.AxisNameContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#axisName}. + * @param ctx the parse tree + */ + void exitAxisName(XPathParser.AxisNameContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#abbreviatedStep}. + * @param ctx the parse tree + */ + void enterAbbreviatedStep(XPathParser.AbbreviatedStepContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#abbreviatedStep}. + * @param ctx the parse tree + */ + void exitAbbreviatedStep(XPathParser.AbbreviatedStepContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#nodeTypeTest}. + * @param ctx the parse tree + */ + void enterNodeTypeTest(XPathParser.NodeTypeTestContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#nodeTypeTest}. + * @param ctx the parse tree + */ + void exitNodeTypeTest(XPathParser.NodeTypeTestContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#attributeStep}. + * @param ctx the parse tree + */ + void enterAttributeStep(XPathParser.AttributeStepContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#attributeStep}. + * @param ctx the parse tree + */ + void exitAttributeStep(XPathParser.AttributeStepContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#nodeTest}. + * @param ctx the parse tree + */ + void enterNodeTest(XPathParser.NodeTestContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#nodeTest}. + * @param ctx the parse tree + */ + void exitNodeTest(XPathParser.NodeTestContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#predicate}. + * @param ctx the parse tree + */ + void enterPredicate(XPathParser.PredicateContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#predicate}. + * @param ctx the parse tree + */ + void exitPredicate(XPathParser.PredicateContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#predicateExpr}. + * @param ctx the parse tree + */ + void enterPredicateExpr(XPathParser.PredicateExprContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#predicateExpr}. + * @param ctx the parse tree + */ + void exitPredicateExpr(XPathParser.PredicateExprContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#orExpr}. + * @param ctx the parse tree + */ + void enterOrExpr(XPathParser.OrExprContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#orExpr}. + * @param ctx the parse tree + */ + void exitOrExpr(XPathParser.OrExprContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#andExpr}. + * @param ctx the parse tree + */ + void enterAndExpr(XPathParser.AndExprContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#andExpr}. + * @param ctx the parse tree + */ + void exitAndExpr(XPathParser.AndExprContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#primaryExpr}. + * @param ctx the parse tree + */ + void enterPrimaryExpr(XPathParser.PrimaryExprContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#primaryExpr}. + * @param ctx the parse tree + */ + void exitPrimaryExpr(XPathParser.PrimaryExprContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#predicateValue}. + * @param ctx the parse tree + */ + void enterPredicateValue(XPathParser.PredicateValueContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#predicateValue}. + * @param ctx the parse tree + */ + void exitPredicateValue(XPathParser.PredicateValueContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#functionCall}. + * @param ctx the parse tree + */ + void enterFunctionCall(XPathParser.FunctionCallContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#functionCall}. + * @param ctx the parse tree + */ + void exitFunctionCall(XPathParser.FunctionCallContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#functionArgs}. + * @param ctx the parse tree + */ + void enterFunctionArgs(XPathParser.FunctionArgsContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#functionArgs}. + * @param ctx the parse tree + */ + void exitFunctionArgs(XPathParser.FunctionArgsContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#functionArg}. + * @param ctx the parse tree + */ + void enterFunctionArg(XPathParser.FunctionArgContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#functionArg}. + * @param ctx the parse tree + */ + void exitFunctionArg(XPathParser.FunctionArgContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#childElementTest}. + * @param ctx the parse tree + */ + void enterChildElementTest(XPathParser.ChildElementTestContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#childElementTest}. + * @param ctx the parse tree + */ + void exitChildElementTest(XPathParser.ChildElementTestContext ctx); + /** + * Enter a parse tree produced by {@link XPathParser#stringLiteral}. + * @param ctx the parse tree + */ + void enterStringLiteral(XPathParser.StringLiteralContext ctx); + /** + * Exit a parse tree produced by {@link XPathParser#stringLiteral}. + * @param ctx the parse tree + */ + void exitStringLiteral(XPathParser.StringLiteralContext ctx); +} \ No newline at end of file diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserVisitor.java b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserVisitor.java new file mode 100644 index 0000000000..f130256329 --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/internal/grammar/XPathParserVisitor.java @@ -0,0 +1,184 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /Users/knut/git/openrewrite/rewrite/rewrite-xml/src/main/antlr/XPathParser.g4 by ANTLR 4.13.2 +package org.openrewrite.xml.internal.grammar; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by {@link XPathParser}. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +public interface XPathParserVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by {@link XPathParser#xpathExpression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitXpathExpression(XPathParser.XpathExpressionContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#filterExpr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFilterExpr(XPathParser.FilterExprContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#booleanExpr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitBooleanExpr(XPathParser.BooleanExprContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#comparisonOp}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitComparisonOp(XPathParser.ComparisonOpContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#comparand}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitComparand(XPathParser.ComparandContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#absoluteLocationPath}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAbsoluteLocationPath(XPathParser.AbsoluteLocationPathContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#relativeLocationPath}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitRelativeLocationPath(XPathParser.RelativeLocationPathContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#pathSeparator}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPathSeparator(XPathParser.PathSeparatorContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#step}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStep(XPathParser.StepContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#axisStep}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAxisStep(XPathParser.AxisStepContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#axisName}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAxisName(XPathParser.AxisNameContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#abbreviatedStep}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAbbreviatedStep(XPathParser.AbbreviatedStepContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#nodeTypeTest}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNodeTypeTest(XPathParser.NodeTypeTestContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#attributeStep}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAttributeStep(XPathParser.AttributeStepContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#nodeTest}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNodeTest(XPathParser.NodeTestContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#predicate}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPredicate(XPathParser.PredicateContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#predicateExpr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPredicateExpr(XPathParser.PredicateExprContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#orExpr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitOrExpr(XPathParser.OrExprContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#andExpr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAndExpr(XPathParser.AndExprContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#primaryExpr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPrimaryExpr(XPathParser.PrimaryExprContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#predicateValue}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPredicateValue(XPathParser.PredicateValueContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#functionCall}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFunctionCall(XPathParser.FunctionCallContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#functionArgs}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFunctionArgs(XPathParser.FunctionArgsContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#functionArg}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFunctionArg(XPathParser.FunctionArgContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#childElementTest}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitChildElementTest(XPathParser.ChildElementTestContext ctx); + /** + * Visit a parse tree produced by {@link XPathParser#stringLiteral}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStringLiteral(XPathParser.StringLiteralContext ctx); +} \ No newline at end of file diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/AddCommentToXmlTagTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/AddCommentToXmlTagTest.java index 9d3622c3dd..8dce9772b8 100755 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/AddCommentToXmlTagTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/AddCommentToXmlTagTest.java @@ -30,7 +30,7 @@ void addCommentToDependencyBlock() { rewriteRun( spec -> spec.recipe( new AddCommentToXmlTag( - "/project/dependencies/", + "/project/dependencies", " Comment text " ) ), diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java index 006668c71c..74d6512aab 100755 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java @@ -15,16 +15,13 @@ */ package org.openrewrite.xml; -import org.junit.jupiter.api.Disabled; +import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; import org.openrewrite.Issue; import org.openrewrite.SourceFile; -import org.openrewrite.TreeVisitor; -import org.openrewrite.marker.SearchResult; import org.openrewrite.xml.tree.Xml; -import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; @@ -115,51 +112,48 @@ class XPathMatcherTest { @Test void matchAbsolute() { - assertThat(match("/dependencies/dependency", xmlDoc)).isTrue(); - assertThat(match("/dependencies/*/artifactId", xmlDoc)).isTrue(); - assertThat(match("/dependencies/*", xmlDoc)).isTrue(); - assertThat(match("/dependencies//dependency", xmlDoc)).isTrue(); - assertThat(match("/dependencies//dependency//groupId", xmlDoc)).isTrue(); + assertThat(matchCount("/dependencies/dependency", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies/*/artifactId", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies/*", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies//dependency", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies//dependency//groupId", xmlDoc)).isEqualTo(1); // negative matches - assertThat(match("/dependencies/dne", xmlDoc)).isFalse(); - assertThat(match("/dependencies//dne", xmlDoc)).isFalse(); - assertThat(match("/dependencies//dependency//dne", xmlDoc)).isFalse(); + assertThat(matchCount("/dependencies/dne", xmlDoc)).isEqualTo(0); + assertThat(matchCount("/dependencies//dne", xmlDoc)).isEqualTo(0); + assertThat(matchCount("/dependencies//dependency//dne", xmlDoc)).isEqualTo(0); } @Test void matchAbsoluteAttribute() { - assertThat(match("/dependencies/dependency/artifactId/@scope", xmlDoc)).isTrue(); - assertThat(match("/dependencies/dependency/artifactId/@scope", xmlDoc)).isTrue(); - assertThat(match("/dependencies/dependency/artifactId/@*", xmlDoc)).isTrue(); - assertThat(match("/dependencies/dependency/groupId/@*", xmlDoc)).isFalse(); - assertThat(match("/dependencies//dependency//@scope", xmlDoc)).isTrue(); - assertThat(match("/dependencies//dependency//artifactId//@scope", xmlDoc)).isTrue(); - assertThat(match("/dependencies//dependency//@*", xmlDoc)).isTrue(); - assertThat(match("/dependencies//dependency//artifactId//@*", xmlDoc)).isTrue(); + assertThat(matchCount("/dependencies/dependency/artifactId/@scope", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies/dependency/artifactId/@*", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies/dependency/groupId/@*", xmlDoc)).isEqualTo(0); + assertThat(matchCount("/dependencies//dependency//@scope", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies//dependency//artifactId//@scope", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies//dependency//@*", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies//dependency//artifactId//@*", xmlDoc)).isEqualTo(2); // negative matches - assertThat(match("/dependencies/dependency/artifactId/@dne", xmlDoc)).isFalse(); - assertThat(match("/dependencies/dependency/artifactId/@dne", xmlDoc)).isFalse(); - assertThat(match("/dependencies//dependency//@dne", xmlDoc)).isFalse(); - assertThat(match("/dependencies//dependency//artifactId//@dne", xmlDoc)).isFalse(); - + assertThat(matchCount("/dependencies/dependency/artifactId/@dne", xmlDoc)).isEqualTo(0); + assertThat(matchCount("/dependencies//dependency//@dne", xmlDoc)).isEqualTo(0); + assertThat(matchCount("/dependencies//dependency//artifactId//@dne", xmlDoc)).isEqualTo(0); } @Test void matchRelative() { - assertThat(match("dependencies", xmlDoc)).isTrue(); - assertThat(match("dependency", xmlDoc)).isTrue(); - assertThat(match("//dependency", xmlDoc)).isTrue(); - assertThat(match("dependency/*", xmlDoc)).isTrue(); - assertThat(match("dne", xmlDoc)).isFalse(); + assertThat(matchCount("dependencies", xmlDoc)).isEqualTo(1); + assertThat(matchCount("dependency", xmlDoc)).isEqualTo(2); + assertThat(matchCount("//dependency", xmlDoc)).isEqualTo(2); + assertThat(matchCount("dependency/*", xmlDoc)).isEqualTo(3); // 1 groupId + 2 artifactId + assertThat(matchCount("dne", xmlDoc)).isEqualTo(0); } @Test void matchRelativeAttribute() { - assertThat(match("dependency/artifactId/@scope", xmlDoc)).isTrue(); - assertThat(match("dependency/artifactId/@*", xmlDoc)).isTrue(); - assertThat(match("//dependency/artifactId/@scope", xmlDoc)).isTrue(); + assertThat(matchCount("dependency/artifactId/@scope", xmlDoc)).isEqualTo(2); + assertThat(matchCount("dependency/artifactId/@*", xmlDoc)).isEqualTo(2); + assertThat(matchCount("//dependency/artifactId/@scope", xmlDoc)).isEqualTo(2); } @Test @@ -308,18 +302,176 @@ void namespaceMatchFunctions() { } @Test - @Disabled - void otherUncoveredXpathFunctions() { - // Other common XPath functions - assertThat(match("contains(/root/element1, 'content1')", namespacedXml)).isTrue(); - assertThat(match("not(contains(/root/element1, 'content1'))", namespacedXml)).isFalse(); - assertThat(match("string-length(/root/element1) > 2", namespacedXml)).isTrue(); - assertThat(match("starts-with(/root/element1, 'content1')", namespacedXml)).isTrue(); - assertThat(match("ends-with(/root/element1, 'content1')", namespacedXml)).isTrue(); - assertThat(match("substring-before(/root/element1, '1') = 'content'", namespacedXml)).isTrue(); - assertThat(match("substring-after(/root/element1, 'content') = '1'", namespacedXml)).isTrue(); - assertThat(match("/root/element1/text()", namespacedXml)).isTrue(); - assertThat(match("count(/root/*)", namespacedXml)).isTrue(); + void matchTextNodeTypeTest() { + // text() node type test works in path steps + assertThat(match("/root/element1/text()", namespacedXml)).isTrue(); + assertThat(match("//element1/text()", namespacedXml)).isTrue(); + assertThat(match("/root/ns2:element2/text()", namespacedXml)).isTrue(); + // parent has child elements, not direct text content + assertThat(match("/root/parent/text()", namespacedXml)).isFalse(); + } + + @Test + void matchContainsFunction() { + // Basic contains - positive cases + assertThat(match("contains(/root/element1, 'content1')", namespacedXml)).isTrue(); + assertThat(match("contains(/root/element1, 'content')", namespacedXml)).isTrue(); + assertThat(match("contains(/root/element1, '1')", namespacedXml)).isTrue(); + assertThat(match("contains(/root/element1, 'ent')", namespacedXml)).isTrue(); + + // Basic contains - negative cases + assertThat(match("contains(/root/element1, 'notfound')", namespacedXml)).isFalse(); + assertThat(match("contains(/root/element1, 'CONTENT1')", namespacedXml)).isFalse(); // case sensitive + assertThat(match("contains(/root/element1, '')", namespacedXml)).isTrue(); // empty string is always contained + + // Contains with different elements + assertThat(match("contains(/root/ns2:element2, 'content2')", namespacedXml)).isTrue(); + assertThat(match("contains(/root/parent/element3, 'content3')", namespacedXml)).isTrue(); + + // Contains with non-existent path + assertThat(match("contains(/root/nonexistent, 'anything')", namespacedXml)).isFalse(); + } + + @Test + void matchContainsInPredicate() { + // contains() in predicate with child element - matches dependency with groupId containing 'openrewrite' + assertThat(match("/dependencies/dependency[contains(groupId, 'openrewrite')]", xmlDoc)).isTrue(); + assertThat(match("/dependencies/dependency[contains(groupId, 'rewrite')]", xmlDoc)).isTrue(); + assertThat(match("/dependencies/dependency[contains(artifactId, 'rewrite')]", xmlDoc)).isTrue(); + + // negative cases + assertThat(match("/dependencies/dependency[contains(groupId, 'notfound')]", xmlDoc)).isFalse(); + assertThat(match("/dependencies/dependency[contains(artifactId, 'notfound')]", xmlDoc)).isFalse(); + + // with descendant axis + assertThat(match("//dependency[contains(groupId, 'openrewrite')]", xmlDoc)).isTrue(); + assertThat(match("//dependency[contains(artifactId, 'xml')]", xmlDoc)).isTrue(); + } + + @Test + void matchNotFunction() { + // not() with contains + assertThat(match("not(contains(/root/element1, 'content1'))", namespacedXml)).isFalse(); + assertThat(match("not(contains(/root/element1, 'notfound'))", namespacedXml)).isTrue(); + + // not() with starts-with + assertThat(match("not(starts-with(/root/element1, 'content'))", namespacedXml)).isFalse(); + assertThat(match("not(starts-with(/root/element1, 'xyz'))", namespacedXml)).isTrue(); + + // Double negation + assertThat(match("not(not(contains(/root/element1, 'content1')))", namespacedXml)).isTrue(); + } + + @Test + void matchStringLengthFunction() { + // string-length with comparisons + assertThat(match("string-length(/root/element1) > 0", namespacedXml)).isTrue(); + assertThat(match("string-length(/root/element1) > 2", namespacedXml)).isTrue(); + assertThat(match("string-length(/root/element1) > 100", namespacedXml)).isFalse(); + + assertThat(match("string-length(/root/element1) < 100", namespacedXml)).isTrue(); + assertThat(match("string-length(/root/element1) < 5", namespacedXml)).isFalse(); + + assertThat(match("string-length(/root/element1) = 8", namespacedXml)).isTrue(); // "content1" = 8 chars + assertThat(match("string-length(/root/element1) = 7", namespacedXml)).isFalse(); + + assertThat(match("string-length(/root/element1) >= 8", namespacedXml)).isTrue(); + assertThat(match("string-length(/root/element1) >= 9", namespacedXml)).isFalse(); + + assertThat(match("string-length(/root/element1) <= 8", namespacedXml)).isTrue(); + assertThat(match("string-length(/root/element1) <= 7", namespacedXml)).isFalse(); + + assertThat(match("string-length(/root/element1) != 7", namespacedXml)).isTrue(); + assertThat(match("string-length(/root/element1) != 8", namespacedXml)).isFalse(); + + // string-length of non-existent path + assertThat(match("string-length(/root/nonexistent) = 0", namespacedXml)).isTrue(); + } + + @Test + void matchStartsWithFunction() { + assertThat(match("starts-with(/root/element1, 'content')", namespacedXml)).isTrue(); + assertThat(match("starts-with(/root/element1, 'con')", namespacedXml)).isTrue(); + assertThat(match("starts-with(/root/element1, 'c')", namespacedXml)).isTrue(); + assertThat(match("starts-with(/root/element1, '')", namespacedXml)).isTrue(); + assertThat(match("starts-with(/root/element1, 'content1')", namespacedXml)).isTrue(); + + assertThat(match("starts-with(/root/element1, 'ontent')", namespacedXml)).isFalse(); + assertThat(match("starts-with(/root/element1, '1')", namespacedXml)).isFalse(); + assertThat(match("starts-with(/root/element1, 'Content')", namespacedXml)).isFalse(); // case sensitive + } + + @Test + void matchEndsWithFunction() { + assertThat(match("ends-with(/root/element1, '1')", namespacedXml)).isTrue(); + assertThat(match("ends-with(/root/element1, 'ent1')", namespacedXml)).isTrue(); + assertThat(match("ends-with(/root/element1, 'content1')", namespacedXml)).isTrue(); + assertThat(match("ends-with(/root/element1, '')", namespacedXml)).isTrue(); + + assertThat(match("ends-with(/root/element1, 'content')", namespacedXml)).isFalse(); + assertThat(match("ends-with(/root/element1, '2')", namespacedXml)).isFalse(); + assertThat(match("ends-with(/root/element1, 'Content1')", namespacedXml)).isFalse(); // case sensitive + } + + @Test + void matchSubstringFunctions() { + // substring-before + assertThat(match("substring-before(/root/element1, '1') = 'content'", namespacedXml)).isTrue(); + assertThat(match("substring-before(/root/element1, 'tent') = 'con'", namespacedXml)).isTrue(); + assertThat(match("substring-before(/root/element1, 'c') = ''", namespacedXml)).isTrue(); // nothing before first char + assertThat(match("substring-before(/root/element1, 'notfound') = ''", namespacedXml)).isTrue(); // delimiter not found + + // substring-after + assertThat(match("substring-after(/root/element1, 'content') = '1'", namespacedXml)).isTrue(); + assertThat(match("substring-after(/root/element1, 'con') = 'tent1'", namespacedXml)).isTrue(); + assertThat(match("substring-after(/root/element1, '1') = ''", namespacedXml)).isTrue(); // nothing after last char + assertThat(match("substring-after(/root/element1, 'notfound') = ''", namespacedXml)).isTrue(); // delimiter not found + } + + @Test + void matchCountFunction() { + // TODO: count() with wildcard paths like count(/root/*) requires special handling + // to count all matching nodes rather than evaluating path to text content. + // For now, count() with a path that has text content returns 1 (truthy) + assertThat(match("count(/root/element1)", namespacedXml)).isTrue(); // has content, so count >= 1 + assertThat(match("count(/root/element1) > 0", namespacedXml)).isTrue(); + + // count of non-existent returns 0 + assertThat(match("count(/root/nonexistent) = 0", namespacedXml)).isTrue(); + } + + @Test + void matchNestedFunctionCalls() { + // Nested function calls + assertThat(match("not(not(contains(/root/element1, 'content1')))", namespacedXml)).isTrue(); + assertThat(match("not(not(not(contains(/root/element1, 'content1'))))", namespacedXml)).isFalse(); + + // contains with substring result + assertThat(match("contains(substring-after(/root/element1, 'con'), 'tent')", namespacedXml)).isTrue(); + } + + @Test + void matchFunctionWithDescendantPath() { + // Using // descendant axis in function arguments + assertThat(match("contains(//element1, 'content1')", namespacedXml)).isTrue(); + assertThat(match("contains(//element3, 'content3')", namespacedXml)).isTrue(); + assertThat(match("string-length(//element1) = 8", namespacedXml)).isTrue(); + } + + @Test + void matchBooleanExpressionWithRelativePath() { + // Relative paths in boolean expressions are evaluated from the cursor's context element + // contains(element1, 'content1') matches at because it has a child containing 'content1' + assertThat(matchCount("contains(element1, 'content1')", namespacedXml)).isEqualTo(1); // matches at + + // xmlDoc: org.openrewrite...... + // contains(groupId, 'openrewrite') matches at elements that have child containing 'openrewrite' + assertThat(matchCount("contains(groupId, 'openrewrite')", xmlDoc)).isEqualTo(1); // matches at first + assertThat(matchCount("contains(groupId, 'notfound')", xmlDoc)).isEqualTo(0); // no matches + + // Absolute paths: only match at root element + // contains(/dependencies/dependency/groupId, 'openrewrite') - matches only at (root) + assertThat(matchCount("contains(/dependencies/dependency/groupId, 'openrewrite')", xmlDoc)).isEqualTo(1); } @Test @@ -421,12 +573,24 @@ void matchTextFunctionCondition() { bar notBar + + two + + + + three + + """ ).toList().getFirst(); // text() predicate should only match element with specific text content + assertThat(match("/test/one[two/three/text()='three']", xml)).isTrue(); + assertThat(match("/test/one[two/three/text()='notthree']", xml)).isFalse(); + assertThat(match("/test/one[two/text()='two']", xml)).isTrue(); + assertThat(match("/test/one[two/text()='nottwo']", xml)).isFalse(); assertThat(match("/test/foo[text()='bar']", xml)).isTrue(); assertThat(match("/test/foo[text()='notBar']", xml)).isTrue(); assertThat(match("/test/foo[text()='nonexistent']", xml)).isFalse(); @@ -503,24 +667,209 @@ void matchConditionsWithConjunctions() { assertThat(match("//*[local-name()='element4' or local-name()='dne' and namespace-uri()='http://www.example.com/namespaceX']", namespacedXml)).isTrue(); } - private boolean match(String xpath, SourceFile x) { + private boolean match(@Language("xpath") String xpath, SourceFile x) { + return matchCount(xpath, x) > 0; + } + + private int matchCount(String xpath, SourceFile x) { XPathMatcher matcher = new XPathMatcher(xpath); - return !TreeVisitor.collect(new XmlVisitor<>() { + return (new XmlVisitor() { @Override - public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + public Xml visitTag(Xml.Tag tag, AtomicInteger ctx) { if (matcher.matches(getCursor())) { - return SearchResult.found(tag); + ctx.incrementAndGet(); } return super.visitTag(tag, ctx); } @Override - public Xml visitAttribute(Xml.Attribute attribute, ExecutionContext ctx) { + public Xml visitAttribute(Xml.Attribute attribute, AtomicInteger ctx) { if (matcher.matches(getCursor())) { - return SearchResult.found(attribute); + ctx.incrementAndGet(); } return super.visitAttribute(attribute, ctx); } - }, x, new ArrayList<>()).isEmpty(); + }).reduce(x, new AtomicInteger()).get(); + } + + @Test + void matchNodeTypeTests() { + // text() node type test - matches elements with text content + assertThat(match("/root/element1/text()", namespacedXml)).isTrue(); + assertThat(match("//element1/text()", namespacedXml)).isTrue(); + assertThat(match("/root/parent/text()", namespacedXml)).isFalse(); // parent has child elements, not direct text + } + + @Test + void matchPositionalPredicates() { + // xmlDoc has two elements under + // [1] selects the first dependency + assertThat(matchCount("/dependencies/dependency[1]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[2]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[3]", xmlDoc)).isEqualTo(0); // only 2 dependencies + + // [last()] selects the last element + assertThat(matchCount("/dependencies/dependency[last()]", xmlDoc)).isEqualTo(1); + + // position() function + assertThat(matchCount("/dependencies/dependency[position()=1]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[position()=2]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[position()=3]", xmlDoc)).isEqualTo(0); + + // Combining positional with other predicates + // The first dependency has groupId, the second doesn't + assertThat(matchCount("/dependencies/dependency[1]/groupId", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[2]/groupId", xmlDoc)).isEqualTo(0); + + // position() with comparison operators + assertThat(matchCount("/dependencies/dependency[position()>1]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[position()<2]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[position()>=1]", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies/dependency[position()<=2]", xmlDoc)).isEqualTo(2); + } + + @Test + void matchPositionalWithOtherConditions() { + // Combining positional predicates with attribute/element conditions + // First dependency's artifactId has scope="compile", second has scope="test" + assertThat(matchCount("/dependencies/dependency[1]/artifactId[@scope='compile']", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[2]/artifactId[@scope='test']", xmlDoc)).isEqualTo(1); + assertThat(matchCount("/dependencies/dependency[1]/artifactId[@scope='test']", xmlDoc)).isEqualTo(0); + } + + @Test + void matchParenthesizedPathExpressions() { + // Parenthesized path expressions apply predicates to the entire result set + // (/dependencies/dependency)[1] - first dependency from the entire document + assertThat(matchCount("(/dependencies/dependency)[1]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("(/dependencies/dependency)[2]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("(/dependencies/dependency)[3]", xmlDoc)).isEqualTo(0); + + // (/dependencies/dependency)[last()] - last dependency + assertThat(matchCount("(/dependencies/dependency)[last()]", xmlDoc)).isEqualTo(1); + + // With position() function + assertThat(matchCount("(/dependencies/dependency)[position()=1]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("(/dependencies/dependency)[position()=2]", xmlDoc)).isEqualTo(1); + + // Descendant axis in parenthesized expression + assertThat(matchCount("(//dependency)[1]", xmlDoc)).isEqualTo(1); + assertThat(matchCount("(//dependency)[last()]", xmlDoc)).isEqualTo(1); + } + + @Test + void matchAdvancedFilterExpressions() { + // (/path/expr)[predicate]/trailing - filter expression with trailing path + assertThat(matchCount("(/project/build/plugins/plugin)[1]/groupId", pomXml1)).isEqualTo(1); + assertThat(matchCount("(/project/build/plugins/plugin)[1]/artifactId", pomXml1)).isEqualTo(1); + assertThat(matchCount("(/project/build/plugins/plugin)[1]/configuration", pomXml1)).isEqualTo(1); + assertThat(matchCount("(/project/build/plugins/plugin)[1]/configuration/source", pomXml1)).isEqualTo(1); + + // Test with attribute access in trailing path (not yet supported) + // assertThat(matchCount("(/dependencies/dependency)[1]/artifactId/@scope", xmlDoc)).isEqualTo(1); + } + + @Test + void matchAbbreviatedSyntax() { + // . means self (current node) + assertThat(matchCount("/dependencies/./dependency", xmlDoc)).isEqualTo(2); + assertThat(matchCount("/dependencies/dependency/.", xmlDoc)).isEqualTo(2); + + // .. means parent + assertThat(matchCount("/dependencies/dependency/groupId/..", xmlDoc)).isEqualTo(1); // only first dependency has groupId + assertThat(matchCount("/dependencies/dependency/groupId/../artifactId", xmlDoc)).isEqualTo(1); + + // Multiple parent references + assertThat(matchCount("/dependencies/dependency/groupId/../../dependency", xmlDoc)).isEqualTo(2); + } + + @Test + void matchParentAxis() { + // parent::node() - explicit parent axis with node() test + assertThat(match("/dependencies/dependency/groupId/parent::node()", xmlDoc)).isTrue(); + assertThat(match("/dependencies/dependency/groupId/parent::dependency", xmlDoc)).isTrue(); + + // parent with specific element name + assertThat(match("/dependencies/dependency/parent::dependencies", xmlDoc)).isTrue(); + + // self::node() - explicit self axis + assertThat(match("/dependencies/self::node()", xmlDoc)).isTrue(); + assertThat(match("/dependencies/self::dependencies", xmlDoc)).isTrue(); + + // Combined with other path steps + assertThat(match("/dependencies/dependency/groupId/parent::dependency/artifactId", xmlDoc)).isTrue(); + } + + @Test + void matchDescendantWithChildPath() { + // //plugins/plugin - descendant axis followed by child axis + // This is used by MavenPlugin matcher + assertThat(match("//plugins/plugin", pomXml1)).isTrue(); + assertThat(match("//plugins/plugin/groupId", pomXml1)).isTrue(); + assertThat(match("//plugins/plugin/artifactId", pomXml1)).isTrue(); + assertThat(match("//plugins/plugin/configuration", pomXml1)).isTrue(); + assertThat(match("//plugins/plugin/configuration/source", pomXml1)).isTrue(); + + // With pluginManagement + assertThat(match("//plugins/plugin", pomXml2)).isTrue(); + assertThat(match("//pluginManagement/plugins/plugin", pomXml2)).isTrue(); + + // Negative cases + assertThat(match("//plugins/nonexistent", pomXml1)).isFalse(); + assertThat(match("//nonexistent/plugin", pomXml1)).isFalse(); + } + + @Test + void matchDescendantStartingWithRootElement() { + // //project/build/... - descendant axis where first step matches root element + // This pattern is used by RemoveXmlTag and should match when root is + assertThat(match("//project/build", pomXml1)).isTrue(); + assertThat(match("//project/build/plugins", pomXml1)).isTrue(); + assertThat(match("//project/build/plugins/plugin", pomXml1)).isTrue(); + assertThat(match("//project/build/pluginManagement/plugins/plugin", pomXml2)).isTrue(); + + // Should not match if the path doesn't exist + assertThat(match("//project/build/pluginManagement", pomXml1)).isFalse(); + assertThat(match("//project/nonexistent", pomXml1)).isFalse(); } + + @Test + void matchRootElement() { + // Single-step absolute path should match the root element + assertThat(matchCount("/project", pomXml1)).isEqualTo(1); + assertThat(matchCount("/dependencies", xmlDoc)).isEqualTo(1); + + // /project/parent should match the parent element + SourceFile pomWithParent = new XmlParser().parse( + """ + + + com.example + + 1.0 + + """ + ).toList().getFirst(); + assertThat(matchCount("/project/parent", pomWithParent)).isEqualTo(1); + assertThat(matchCount("/project", pomWithParent)).isEqualTo(1); + } + + @Test + void matchRelativePathFromContext() { + // Relative paths should match based on suffix, allowing match anywhere in document + // This is important for ChangeTagValue which uses paths like "version" + assertThat(match("version", pomXml1)).isTrue(); + assertThat(match("groupId", pomXml1)).isTrue(); + assertThat(match("artifactId", pomXml1)).isTrue(); + + // Multi-step relative paths + assertThat(match("configuration/source", pomXml1)).isTrue(); + assertThat(match("plugin/configuration", pomXml1)).isTrue(); + assertThat(match("plugins/plugin", pomXml1)).isTrue(); + + // Negative cases - element names that don't exist + assertThat(match("nonexistent", pomXml1)).isFalse(); + assertThat(match("configuration/nonexistent", pomXml1)).isFalse(); + } + }