diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 20308296806..3109d357158 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2989,14 +2989,22 @@ throws PermissionDeniedException, EXistException, XPathException rs.setAbbreviated(true); } - } else { + } else if (rightStep instanceof VariableReference) { rightStep.setPrimaryAxis(Constants.DESCENDANT_SELF_AXIS); - if(rightStep instanceof VariableReference) { - rightStep = new SimpleStep(context, Constants.DESCENDANT_SELF_AXIS, rightStep); - path.replaceLastExpression(rightStep); - } else if (rightStep instanceof FilteredExpression) - ((FilteredExpression)rightStep).setAbbreviated(true); - + rightStep = new SimpleStep(context, Constants.DESCENDANT_SELF_AXIS, rightStep); + path.replaceLastExpression(rightStep); + } else if (rightStep instanceof FilteredExpression) { + rightStep.setPrimaryAxis(Constants.DESCENDANT_SELF_AXIS); + ((FilteredExpression)rightStep).setAbbreviated(true); + } else { + // For other non-LocationStep expressions (e.g., PathExpr wrapping + // parenthesized expressions like //(@x) or //(a | b)), insert an + // explicit descendant-or-self::node() step. We must NOT call + // setPrimaryAxis here because it would corrupt inner axes (e.g., + // overwriting an attribute axis in //(@x)). + LocationStep descStep = new LocationStep(context, Constants.DESCENDANT_SELF_AXIS, new TypeTest(Type.NODE)); + path.replaceLastExpression(descStep); + path.add(rightStep); } } )? diff --git a/exist-core/src/test/java/org/exist/xquery/DescendantOrSelfWithNonLocationStepTest.java b/exist-core/src/test/java/org/exist/xquery/DescendantOrSelfWithNonLocationStepTest.java new file mode 100644 index 00000000000..ec3803e2695 --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/DescendantOrSelfWithNonLocationStepTest.java @@ -0,0 +1,100 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.xquery; + +import org.exist.test.ExistXmldbEmbeddedServer; +import org.junit.ClassRule; +import org.junit.Test; +import org.xmldb.api.base.ResourceSet; +import org.xmldb.api.base.XMLDBException; +import org.xmldb.api.modules.XQueryService; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for // (descendant-or-self) with non-LocationStep expressions. + * + * The XPath abbreviation // expands to /descendant-or-self::node()/. When + * the right-hand side is a LocationStep, the tree walker can merge the axis. + * When it is a non-LocationStep expression (parenthesized expression, variable + * reference, filtered expression), we must insert an explicit + * descendant-or-self::node() step instead. + */ +public class DescendantOrSelfWithNonLocationStepTest { + + @ClassRule + public static final ExistXmldbEmbeddedServer existEmbeddedServer = + new ExistXmldbEmbeddedServer(false, true, true); + + private ResourceSet execute(final String xquery) throws XMLDBException { + final XQueryService xqs = existEmbeddedServer.getRoot().getService(XQueryService.class); + return xqs.query(xquery); + } + + @Test + public void parenthesizedAttribute() throws XMLDBException { + // //(@x) should find all @x attributes at any depth + final ResourceSet result = execute( + "let $doc := \n" + + "return count($doc//(@x))"); + assertEquals("3", result.getResource(0).getContent().toString()); + } + + @Test + public void parenthesizedAttributeUnion() throws XMLDBException { + // //(@x | @y) should find all @x and @y attributes at any depth + final ResourceSet result = execute( + "let $doc := \n" + + "return count($doc//(@x | @y))"); + assertEquals("4", result.getResource(0).getContent().toString()); + } + + @Test + public void parenthesizedElementUnion() throws XMLDBException { + // //(b | c) should find elements at any depth, including direct children + final ResourceSet result = execute( + "let $doc := \n" + + "return count($doc//(b | c))"); + assertEquals("3", result.getResource(0).getContent().toString()); + } + + @Test + public void parenthesizedUnionWithFollowingAxis() throws XMLDBException { + // //(north | near-south)/preceding-sibling::comment() should work + final ResourceSet result = execute( + "let $doc :=\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "return count($doc//(north | near-south)/preceding-sibling::comment())"); + assertEquals("3", result.getResource(0).getContent().toString()); + } +}