[dna-commits] DNA SVN: r1285 - in trunk: dna-jcr/src/main/java/org/jboss/dna/jcr and 3 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Sat Oct 3 00:07:44 EDT 2009


Author: rhauch
Date: 2009-10-03 00:07:41 -0400 (Sat, 03 Oct 2009)
New Revision: 1285

Added:
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
Modified:
   trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java
Log:
DNA-467 Added XPath parser with associated unit tests, though this doesn't yet convert the parsed XPath AST into a query using the Abstract Query Model

Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java	2009-10-02 17:35:34 UTC (rev 1284)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java	2009-10-03 04:07:41 UTC (rev 1285)
@@ -437,7 +437,7 @@
      * 
      * @return the current token's position; never null
      * @throws IllegalStateException if this method was called before the stream was {@link #start() started}
-     * @throws NoSuchElementException if there is no previous token
+     * @throws NoSuchElementException if there is no next token
      */
     public Position nextPosition() {
         return currentToken().position();
@@ -1047,7 +1047,7 @@
         ListIterator<Token> iter = tokens.listIterator(tokenIterator.previousIndex());
         if (!iter.hasNext()) return false;
         Token token = iter.next();
-        if (currentExpectedType != ANY_TYPE || currentToken().type() != currentExpectedType) return false;
+        if (currentExpectedType != ANY_TYPE && currentToken().type() != currentExpectedType) return false;
         for (int nextExpectedType : expectedTypeForNextTokens) {
             if (!iter.hasNext()) return false;
             token = iter.next();
@@ -1366,6 +1366,22 @@
         boolean isNextValidXmlCharacter();
 
         /**
+         * Determine if the next character on the sream is a {@link XmlCharacters#isValidName(int) valid XML NCName character}.
+         * This method does <i>not</i> advance the stream.
+         * 
+         * @return true if there is a {@link #next() next} character and it is a valid XML Name character, or false otherwise
+         */
+        boolean isNextValidXmlNameCharacter();
+
+        /**
+         * Determine if the next character on the sream is a {@link XmlCharacters#isValidNcName(int) valid XML NCName character}.
+         * This method does <i>not</i> advance the stream.
+         * 
+         * @return true if there is a {@link #next() next} character and it is a valid XML NCName character, or false otherwise
+         */
+        boolean isNextValidXmlNcNameCharacter();
+
+        /**
          * Determine if the next character on the sream is the supplied value. This method does <i>not</i> advance the stream.
          * 
          * @param c the character value to compare to the next character on the stream
@@ -1882,6 +1898,26 @@
             int nextIndex = lastIndex + 1;
             return nextIndex <= maxIndex && XmlCharacters.isValid(content[nextIndex]);
         }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.common.text.TokenStream.CharacterStream#isNextValidXmlNameCharacter()
+         */
+        public boolean isNextValidXmlNameCharacter() {
+            int nextIndex = lastIndex + 1;
+            return nextIndex <= maxIndex && XmlCharacters.isValidName(content[nextIndex]);
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.common.text.TokenStream.CharacterStream#isNextValidXmlNcNameCharacter()
+         */
+        public boolean isNextValidXmlNcNameCharacter() {
+            int nextIndex = lastIndex + 1;
+            return nextIndex <= maxIndex && XmlCharacters.isValidNcName(content[nextIndex]);
+        }
     }
 
     /**

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java	2009-10-03 04:07:41 UTC (rev 1285)
@@ -0,0 +1,1132 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you 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.
+ * 
+ * JBoss DNA 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.xpath;
+
+import java.util.List;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
+import org.jboss.dna.graph.query.model.Operator;
+
+/**
+ * 
+ */
+public class XPath {
+
+    public static enum NodeComparisonOperator {
+        IS,
+        PRECEDES,
+        FOLLOWS;
+    }
+
+    public static interface Component {
+    }
+
+    public static abstract class UnaryComponent implements Component {
+        protected final Component wrapped;
+
+        public UnaryComponent( Component wrapped ) {
+            this.wrapped = wrapped;
+        }
+    }
+
+    public static class Negation extends UnaryComponent {
+        public Negation( Component wrapped ) {
+            super(wrapped);
+        }
+
+        public Component getNegated() {
+            return wrapped;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "-" + wrapped;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof Negation) {
+                Negation that = (Negation)obj;
+                return this.wrapped.equals(that.wrapped);
+            }
+            return false;
+        }
+    }
+
+    public static abstract class BinaryComponent implements Component {
+        private final Component left;
+        private final Component right;
+
+        public BinaryComponent( Component left,
+                                Component right ) {
+            this.left = left;
+            this.right = right;
+        }
+
+        /**
+         * @return left
+         */
+        public Component getLeft() {
+            return left;
+        }
+
+        /**
+         * @return right
+         */
+        public Component getRight() {
+            return right;
+        }
+    }
+
+    public static class Comparison extends BinaryComponent {
+        private final Operator operator;
+
+        public Comparison( Component left,
+                           Operator operator,
+                           Component right ) {
+            super(left, right);
+            this.operator = operator;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getLeft() + " " + operator + " " + getRight();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof Comparison) {
+                Comparison that = (Comparison)obj;
+                if (this.operator != that.operator) return false;
+                return this.getLeft().equals(that.getLeft()) && this.getRight().equals(that.getRight());
+            }
+            return false;
+        }
+    }
+
+    public static class NodeComparison extends BinaryComponent {
+        private final NodeComparisonOperator operator;
+
+        public NodeComparison( Component left,
+                               NodeComparisonOperator operator,
+                               Component right ) {
+            super(left, right);
+            this.operator = operator;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getLeft() + " " + operator + " " + getRight();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof NodeComparison) {
+                NodeComparison that = (NodeComparison)obj;
+                if (this.operator != that.operator) return false;
+                return this.getLeft().equals(that.getLeft()) && this.getRight().equals(that.getRight());
+            }
+            return false;
+        }
+    }
+
+    public static class Add extends BinaryComponent {
+        public Add( Component left,
+                    Component right ) {
+            super(left, right);
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getLeft() + " + " + getRight();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof Add) {
+                Add that = (Add)obj;
+                return this.getLeft().equals(that.getLeft()) && this.getRight().equals(that.getRight());
+            }
+            return false;
+        }
+    }
+
+    public static class Subtract extends BinaryComponent {
+        public Subtract( Component left,
+                         Component right ) {
+            super(left, right);
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getLeft() + " - " + getRight();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof Subtract) {
+                Subtract that = (Subtract)obj;
+                return this.getLeft().equals(that.getLeft()) && this.getRight().equals(that.getRight());
+            }
+            return false;
+        }
+    }
+
+    public static class And extends BinaryComponent {
+        public And( Component left,
+                    Component right ) {
+            super(left, right);
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getLeft() + " and " + getRight();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof And) {
+                And that = (And)obj;
+                return this.getLeft().equals(that.getLeft()) && this.getRight().equals(that.getRight());
+            }
+            return false;
+        }
+    }
+
+    public static class Union extends BinaryComponent {
+        public Union( Component left,
+                      Component right ) {
+            super(left, right);
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getLeft() + " union " + getRight();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof Union) {
+                Union that = (Union)obj;
+                return this.getLeft().equals(that.getLeft()) && this.getRight().equals(that.getRight());
+            }
+            return false;
+        }
+    }
+
+    public static class Or extends BinaryComponent {
+        public Or( Component left,
+                   Component right ) {
+            super(left, right);
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getLeft() + " or " + getRight();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof Or) {
+                Or that = (Or)obj;
+                return this.getLeft().equals(that.getLeft()) && this.getRight().equals(that.getRight());
+            }
+            return false;
+        }
+    }
+
+    public static class ContextItem implements Component {
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            return obj == this || obj instanceof ContextItem;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return ".";
+        }
+    }
+
+    public static class Literal implements Component {
+        private final String value;
+
+        public Literal( String value ) {
+            this.value = value;
+        }
+
+        /**
+         * @return value
+         */
+        public String getValue() {
+            return value;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof Literal) {
+                return this.value.equals(((Literal)obj).value);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+    public static class FunctionCall implements Component {
+        private final NameTest name;
+        private final List<Component> arguments;
+
+        public FunctionCall( NameTest name,
+                             List<Component> arguments ) {
+            assert name != null;
+            assert arguments != null;
+            this.name = name;
+            this.arguments = arguments;
+        }
+
+        /**
+         * @return name
+         */
+        public NameTest getName() {
+            return name;
+        }
+
+        /**
+         * @return arguments
+         */
+        public List<Component> getParameters() {
+            return arguments;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof FunctionCall) {
+                FunctionCall that = (FunctionCall)obj;
+                return this.name.equals(that.name) && this.arguments.equals(that.arguments);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return name + "(" + asString(arguments, ",") + ")";
+        }
+    }
+
+    protected static String asString( Iterable<?> components,
+                                      String delimiter ) {
+        StringBuilder sb = new StringBuilder();
+        for (Object component : components) {
+            if (sb.length() != 0) sb.append(delimiter);
+            sb.append(component);
+        }
+        return sb.toString();
+    }
+
+    public static class PathExpression implements Component {
+        private final List<StepExpression> steps;
+        private final boolean relative;
+
+        public PathExpression( boolean relative,
+                               List<StepExpression> steps ) {
+            this.steps = steps;
+            this.relative = relative;
+        }
+
+        /**
+         * @return relative
+         */
+        public boolean isRelative() {
+            return relative;
+        }
+
+        /**
+         * @return steps
+         */
+        public List<StepExpression> getSteps() {
+            return steps;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof PathExpression) {
+                PathExpression that = (PathExpression)obj;
+                return this.relative == that.relative && this.steps.equals(that.steps);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return (relative ? "" : "/") + asString(steps, "/");
+        }
+    }
+
+    public static interface StepExpression extends Component {
+    }
+
+    public static class FilterStep implements StepExpression {
+        private final Component primaryExpression;
+        private final List<Component> predicates;
+
+        public FilterStep( Component primaryExpression,
+                           List<Component> predicates ) {
+            assert primaryExpression != null;
+            assert predicates != null;
+            this.primaryExpression = primaryExpression;
+            this.predicates = predicates;
+        }
+
+        /**
+         * @return nodeTest
+         */
+        public Component getPrimaryExpression() {
+            return primaryExpression;
+        }
+
+        /**
+         * @return predicates
+         */
+        public List<Component> getPredicates() {
+            return predicates;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof FilterStep) {
+                FilterStep that = (FilterStep)obj;
+                return this.primaryExpression.equals(that.primaryExpression) && this.predicates.equals(that.predicates);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return primaryExpression + (predicates.isEmpty() ? "" : predicates.toString());
+        }
+    }
+
+    public static class DescendantOrSelf implements StepExpression {
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            return obj == this || obj instanceof DescendantOrSelf;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "descendant-or-self::node()";
+        }
+    }
+
+    public static class AxisStep implements StepExpression {
+        private final NodeTest nodeTest;
+        private final List<Component> predicates;
+
+        public AxisStep( NodeTest nodeTest,
+                         List<Component> predicates ) {
+            assert nodeTest != null;
+            assert predicates != null;
+            this.nodeTest = nodeTest;
+            this.predicates = predicates;
+        }
+
+        /**
+         * @return nodeTest
+         */
+        public NodeTest getNodeTest() {
+            return nodeTest;
+        }
+
+        /**
+         * @return predicates
+         */
+        public List<Component> getPredicates() {
+            return predicates;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof AxisStep) {
+                AxisStep that = (AxisStep)obj;
+                return this.nodeTest.equals(that.nodeTest) && this.predicates.equals(that.predicates);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return nodeTest + (predicates.isEmpty() ? "" : predicates.toString());
+        }
+    }
+
+    public static class ParenthesizedExpression implements Component {
+        private final Component wrapped;
+
+        public ParenthesizedExpression() {
+            this.wrapped = null;
+        }
+
+        public ParenthesizedExpression( Component wrapped ) {
+            this.wrapped = wrapped; // may be null
+        }
+
+        /**
+         * @return wrapped
+         */
+        public Component getWrapped() {
+            return wrapped;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof ParenthesizedExpression) {
+                ParenthesizedExpression that = (ParenthesizedExpression)obj;
+                return this.wrapped.equals(that.wrapped);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "(" + wrapped + ")";
+        }
+    }
+
+    public static interface NodeTest extends Component {
+    }
+
+    public static interface KindTest extends NodeTest {
+    }
+
+    public static class NameTest implements NodeTest {
+        private final String prefixTest;
+        private final String localTest;
+
+        public NameTest( String prefixTest,
+                         String localTest ) {
+            this.prefixTest = prefixTest;
+            this.localTest = localTest;
+        }
+
+        /**
+         * @return prefixTest
+         */
+        public String getPrefixTest() {
+            return prefixTest;
+        }
+
+        /**
+         * @return localTest
+         */
+        public String getLocalTest() {
+            return localTest;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof NameTest) {
+                NameTest that = (NameTest)obj;
+                return ObjectUtil.isEqualWithNulls(this.prefixTest, that.prefixTest)
+                       && ObjectUtil.isEqualWithNulls(this.localTest, that.localTest);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            String local = localTest != null ? localTest : "*";
+            return prefixTest == null ? local : (prefixTest + ":" + local);
+        }
+    }
+
+    public static class AttributeNameTest implements NodeTest {
+        private final NodeTest nodeTest;
+
+        public AttributeNameTest( NodeTest nodeTest ) {
+            this.nodeTest = nodeTest;
+        }
+
+        /**
+         * @return nodeTest
+         */
+        public NodeTest getNodeTest() {
+            return nodeTest;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see XPath.NameTest#toString()
+         */
+        @Override
+        public String toString() {
+            return "@" + nodeTest;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof AttributeNameTest) {
+                AttributeNameTest that = (AttributeNameTest)obj;
+                return this.nodeTest.equals(that.nodeTest);
+            }
+            return false;
+        }
+
+    }
+
+    public static class AnyKindTest implements KindTest {
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            return obj == this || obj instanceof AnyKindTest;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "node()";
+        }
+    }
+
+    public static class TextTest implements KindTest {
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            return obj == this || obj instanceof TextTest;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "text()";
+        }
+    }
+
+    public static class CommentTest implements KindTest {
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            return obj == this || obj instanceof CommentTest;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "comment()";
+        }
+    }
+
+    public static class ProcessingInstructionTest implements KindTest {
+        private final String nameOrStringLiteral;
+
+        public ProcessingInstructionTest( String nameOrStringLiteral ) {
+            this.nameOrStringLiteral = nameOrStringLiteral;
+        }
+
+        /**
+         * @return nameOrStringLiteral
+         */
+        public String getNameOrStringLiteral() {
+            return nameOrStringLiteral;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof ProcessingInstructionTest) {
+                ProcessingInstructionTest that = (ProcessingInstructionTest)obj;
+                return this.nameOrStringLiteral.equals(that.nameOrStringLiteral);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "processing-instruction(" + nameOrStringLiteral + ")";
+        }
+    }
+
+    public static class DocumentTest implements KindTest {
+        private KindTest elementOrSchemaElementTest;
+
+        public DocumentTest( ElementTest elementTest ) {
+            CheckArg.isNotNull(elementTest, "elementTest");
+            this.elementOrSchemaElementTest = elementTest;
+        }
+
+        public DocumentTest( SchemaElementTest schemaElementTest ) {
+            CheckArg.isNotNull(schemaElementTest, "schemaElementTest");
+            this.elementOrSchemaElementTest = schemaElementTest;
+        }
+
+        /**
+         * @return elementOrSchemaElementTest
+         */
+        public ElementTest getElementTest() {
+            return elementOrSchemaElementTest instanceof ElementTest ? (ElementTest)elementOrSchemaElementTest : null;
+        }
+
+        /**
+         * @return elementOrSchemaElementTest
+         */
+        public SchemaElementTest getSchemaElementTest() {
+            return elementOrSchemaElementTest instanceof SchemaElementTest ? (SchemaElementTest)elementOrSchemaElementTest : null;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof DocumentTest) {
+                DocumentTest that = (DocumentTest)obj;
+                return this.elementOrSchemaElementTest.equals(that.elementOrSchemaElementTest);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "document-node(" + elementOrSchemaElementTest + ")";
+        }
+    }
+
+    public static class AttributeTest implements KindTest {
+        private final NameTest attributeNameOrWildcard;
+        private final NameTest typeName;
+
+        public AttributeTest( NameTest attributeNameOrWildcard,
+                              NameTest typeName ) {
+            this.attributeNameOrWildcard = attributeNameOrWildcard;
+            this.typeName = typeName;
+        }
+
+        /**
+         *@return the attribute name, which may be a wilcard
+         */
+        public NameTest getAttributeName() {
+            return attributeNameOrWildcard;
+        }
+
+        /**
+         * @return typeName
+         */
+        public NameTest getTypeName() {
+            return typeName;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof AttributeTest) {
+                AttributeTest that = (AttributeTest)obj;
+                return ObjectUtil.isEqualWithNulls(this.typeName, that.typeName)
+                       && ObjectUtil.isEqualWithNulls(this.attributeNameOrWildcard, that.attributeNameOrWildcard);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "attribute(" + attributeNameOrWildcard + (typeName != null ? "," + typeName : "") + ")";
+        }
+    }
+
+    public static class ElementTest implements KindTest {
+        private final NameTest elementNameOrWildcard;
+        private final NameTest typeName;
+
+        public ElementTest( NameTest elementNameOrWildcard,
+                            NameTest typeName ) {
+            this.elementNameOrWildcard = elementNameOrWildcard;
+            this.typeName = typeName;
+        }
+
+        /**
+         *@return the element name, which may be a wilcard
+         */
+        public NameTest getElementName() {
+            return elementNameOrWildcard;
+        }
+
+        /**
+         * @return typeName
+         */
+        public NameTest getTypeName() {
+            return typeName;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof ElementTest) {
+                ElementTest that = (ElementTest)obj;
+                return ObjectUtil.isEqualWithNulls(this.typeName, that.typeName)
+                       && ObjectUtil.isEqualWithNulls(this.elementNameOrWildcard, that.elementNameOrWildcard);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "element(" + elementNameOrWildcard + (typeName != null ? "," + typeName : "") + ")";
+        }
+    }
+
+    public static class SchemaElementTest implements KindTest {
+        private final NameTest elementDeclarationName;
+
+        public SchemaElementTest( NameTest elementDeclarationName ) {
+            this.elementDeclarationName = elementDeclarationName;
+        }
+
+        /**
+         *@return the element declaration name, which will be a qualified name
+         */
+        public NameTest getElementDeclarationName() {
+            return elementDeclarationName;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof SchemaElementTest) {
+                SchemaElementTest that = (SchemaElementTest)obj;
+                return this.elementDeclarationName.equals(that.elementDeclarationName);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "schema-element(" + elementDeclarationName + ")";
+        }
+    }
+
+    public static class SchemaAttributeTest implements KindTest {
+        private final NameTest attributeDeclarationName;
+
+        public SchemaAttributeTest( NameTest attributeDeclarationName ) {
+            this.attributeDeclarationName = attributeDeclarationName;
+        }
+
+        /**
+         *@return the attribute declaration name, which will be a qualified name
+         */
+        public NameTest getAttributeDeclarationName() {
+            return attributeDeclarationName;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof SchemaAttributeTest) {
+                SchemaAttributeTest that = (SchemaAttributeTest)obj;
+                return this.attributeDeclarationName.equals(that.attributeDeclarationName);
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "schema-attribute(" + attributeDeclarationName + ")";
+        }
+    }
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java	2009-10-03 04:07:41 UTC (rev 1285)
@@ -0,0 +1,812 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you 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.
+ * 
+ * JBoss DNA 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.xpath;
+
+import static org.jboss.dna.common.text.TokenStream.ANY_VALUE;
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.dna.common.CommonI18n;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.text.Position;
+import org.jboss.dna.common.text.TokenStream;
+import org.jboss.dna.common.text.TokenStream.CharacterStream;
+import org.jboss.dna.common.text.TokenStream.Tokenizer;
+import org.jboss.dna.common.text.TokenStream.Tokens;
+import org.jboss.dna.common.xml.XmlCharacters;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.property.ValueFormatException;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.parse.QueryParser;
+import org.jboss.dna.jcr.xpath.XPath.Add;
+import org.jboss.dna.jcr.xpath.XPath.And;
+import org.jboss.dna.jcr.xpath.XPath.AnyKindTest;
+import org.jboss.dna.jcr.xpath.XPath.AttributeNameTest;
+import org.jboss.dna.jcr.xpath.XPath.AttributeTest;
+import org.jboss.dna.jcr.xpath.XPath.AxisStep;
+import org.jboss.dna.jcr.xpath.XPath.CommentTest;
+import org.jboss.dna.jcr.xpath.XPath.Comparison;
+import org.jboss.dna.jcr.xpath.XPath.Component;
+import org.jboss.dna.jcr.xpath.XPath.ContextItem;
+import org.jboss.dna.jcr.xpath.XPath.DescendantOrSelf;
+import org.jboss.dna.jcr.xpath.XPath.DocumentTest;
+import org.jboss.dna.jcr.xpath.XPath.ElementTest;
+import org.jboss.dna.jcr.xpath.XPath.FilterStep;
+import org.jboss.dna.jcr.xpath.XPath.FunctionCall;
+import org.jboss.dna.jcr.xpath.XPath.KindTest;
+import org.jboss.dna.jcr.xpath.XPath.Literal;
+import org.jboss.dna.jcr.xpath.XPath.NameTest;
+import org.jboss.dna.jcr.xpath.XPath.Negation;
+import org.jboss.dna.jcr.xpath.XPath.NodeComparison;
+import org.jboss.dna.jcr.xpath.XPath.NodeComparisonOperator;
+import org.jboss.dna.jcr.xpath.XPath.NodeTest;
+import org.jboss.dna.jcr.xpath.XPath.Or;
+import org.jboss.dna.jcr.xpath.XPath.ParenthesizedExpression;
+import org.jboss.dna.jcr.xpath.XPath.PathExpression;
+import org.jboss.dna.jcr.xpath.XPath.ProcessingInstructionTest;
+import org.jboss.dna.jcr.xpath.XPath.SchemaAttributeTest;
+import org.jboss.dna.jcr.xpath.XPath.SchemaElementTest;
+import org.jboss.dna.jcr.xpath.XPath.StepExpression;
+import org.jboss.dna.jcr.xpath.XPath.Subtract;
+import org.jboss.dna.jcr.xpath.XPath.TextTest;
+import org.jboss.dna.jcr.xpath.XPath.Union;
+
+/**
+ * 
+ */
+public class XPathParser implements QueryParser {
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(java.lang.String, org.jboss.dna.graph.ExecutionContext)
+     */
+    public QueryCommand parseQuery( String query,
+                                    ExecutionContext context ) {
+        Tokenizer tokenizer = new XPathTokenizer(false); // skip comments
+        TokenStream tokens = new TokenStream(query, tokenizer, true).start(); // case sensitive!!
+        Component result = new Parser(context).parseXPath(tokens);
+        System.out.println(query);
+        System.out.println(" --> " + result);
+        return null;
+    }
+
+    protected static class Parser {
+        private final ExecutionContext context;
+
+        protected Parser( ExecutionContext context ) {
+            this.context = context;
+        }
+
+        protected Component parseXPath( TokenStream tokens ) {
+            return parseExpr(tokens);
+        }
+
+        protected Component parseExpr( TokenStream tokens ) {
+            Component result = parseExprSingle(tokens);
+            if (tokens.matches(',')) {
+                throw new ParsingException(tokens.nextPosition(), "Multiple XPath expressions are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseExprSingle( TokenStream tokens ) {
+            if (tokens.matches("for", "$", ANY_VALUE, "IN")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'for' expressions are not supported");
+            }
+            if (tokens.matches("some", "$", ANY_VALUE, "IN")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'some' expressions are not supported");
+            }
+            if (tokens.matches("every", "$", ANY_VALUE, "IN")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'every' expressions are not supported");
+            }
+            if (tokens.matches("if", "(", ANY_VALUE, "IN")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath if-then-else expressions are not supported");
+            }
+            return parseOrExpr(tokens);
+        }
+
+        protected Component parseOrExpr( TokenStream tokens ) {
+            Component result = parseAndExpr(tokens);
+            while (tokens.canConsume("or")) {
+                result = new Or(result, parseInstanceofExpr(tokens));
+            }
+            return result;
+        }
+
+        protected Component parseAndExpr( TokenStream tokens ) {
+            Component result = parseInstanceofExpr(tokens);
+            while (tokens.canConsume("and")) {
+                result = new And(result, parseInstanceofExpr(tokens));
+            }
+            return result;
+        }
+
+        protected Component parseInstanceofExpr( TokenStream tokens ) {
+            Component result = parseTreatExpr(tokens);
+            if (tokens.matches("instance", "of")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'instance of' expressions are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseTreatExpr( TokenStream tokens ) {
+            Component result = parseCastableExpr(tokens);
+            if (tokens.matches("treat", "as")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'treat as' expressions are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseCastableExpr( TokenStream tokens ) {
+            Component result = parseCastExpr(tokens);
+            if (tokens.matches("castable", "as")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'castable as' expressions are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseCastExpr( TokenStream tokens ) {
+            Component result = parseComparisonExpr(tokens);
+            if (tokens.matches("cast", "as")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'cast as' expressions are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseComparisonExpr( TokenStream tokens ) {
+            Component result = parseRangeExpr(tokens);
+            // General comparison is optional ...
+            Operator operator = parseGeneralComp(tokens);
+            if (operator == null) parseValueComp(tokens);
+            if (operator != null) {
+                return new Comparison(result, operator, parseRangeExpr(tokens));
+            }
+            NodeComparisonOperator nodeComp = parseNodeComp(tokens);
+            if (nodeComp != null) {
+                return new NodeComparison(result, nodeComp, parseRangeExpr(tokens));
+            }
+            return result;
+        }
+
+        protected Component parseValueComp( TokenStream tokens ) {
+            if (tokens.matchesAnyOf("eq", "ne", "lt", "le", "gt")) {
+                throw new ParsingException(tokens.nextPosition(),
+                                           "XPath value comparisons using 'eq', 'ne', 'lt', 'le', or 'gt' are not supported");
+            }
+            return null;
+        }
+
+        protected NodeComparisonOperator parseNodeComp( TokenStream tokens ) {
+            if (tokens.matches("is") || tokens.matches("<", "<") || tokens.matches(">", ">")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath 'is', '<<' and '>>' expressions are not supported");
+            }
+            return null;
+        }
+
+        protected Component parseRangeExpr( TokenStream tokens ) {
+            Component result = parseAdditiveExpr(tokens);
+            if (tokens.matches("to")) {
+                throw new ParsingException(tokens.nextPosition(), "XPath range expressions with 'to' are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseAdditiveExpr( TokenStream tokens ) {
+            Component result = parseMultiplicativeExpr(tokens);
+            while (true) {
+                if (tokens.canConsume("+")) {
+                    result = new Add(result, parseMultiplicativeExpr(tokens));
+                } else if (tokens.canConsume("-")) {
+                    result = new Subtract(result, parseMultiplicativeExpr(tokens));
+                } else {
+                    break; // no more additions
+                }
+            }
+            return result;
+        }
+
+        protected Component parseMultiplicativeExpr( TokenStream tokens ) {
+            Component result = parseUnaryExpr(tokens);
+            if (tokens.matchesAnyOf("+", "div", "idiv", "mod")) {
+                throw new ParsingException(tokens.nextPosition(),
+                                           "XPath multiplicative expressions using '+', 'div', 'idiv', or 'mod' are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseUnaryExpr( TokenStream tokens ) {
+            boolean negative = false;
+            // Technically more than one +/- are allowed by the spec
+            while (tokens.matchesAnyOf("+", "-")) {
+                if (tokens.canConsume("-")) negative = true;
+                tokens.canConsume("+");
+            }
+            Component result = parseUnionExpr(tokens);
+            return negative ? new Negation(result) : result;
+        }
+
+        protected Component parseUnionExpr( TokenStream tokens ) {
+            Component result = parseIntersectExceptExpr(tokens);
+            while (true) {
+                if (tokens.canConsumeAnyOf("union", "|")) {
+                    result = new Union(result, parseIntersectExceptExpr(tokens));
+                } else {
+                    break; // no more
+                }
+            }
+            return result;
+        }
+
+        protected Component parseIntersectExceptExpr( TokenStream tokens ) {
+            Component result = parseValueExpr(tokens);
+            if (tokens.matchesAnyOf("intersect", "except")) {
+                throw new ParsingException(tokens.nextPosition(),
+                                           "XPath multiplicative expressions using '+', 'div', 'idiv', or 'mod' are not supported");
+            }
+            return result;
+        }
+
+        protected Component parseValueExpr( TokenStream tokens ) {
+            return parsePathExpr(tokens);
+        }
+
+        protected PathExpression parsePathExpr( TokenStream tokens ) {
+            boolean relative = true;
+            boolean prependDependentOrSelf = false;
+            if (tokens.canConsume('/')) {
+                if (tokens.canConsume('/')) {
+                    if (!tokens.hasNext()) {
+                        // See http://www.w3.org/XML/2007/qt-errata/xpath20-errata.html#E3
+                        throw new ParsingException(tokens.previousPosition(), "'//' is not a valid XPath expression");
+                    }
+                    prependDependentOrSelf = true;
+                }
+                relative = false;
+            }
+            PathExpression result = new PathExpression(relative, parseRelativePathExpr(tokens).getSteps());
+            if (prependDependentOrSelf) {
+                result.getSteps().add(0, new DescendantOrSelf());
+            }
+            return result;
+        }
+
+        protected PathExpression parseRelativePathExpr( TokenStream tokens ) {
+            List<StepExpression> steps = new ArrayList<StepExpression>();
+            steps.add(parseStepExpr(tokens));
+            while (tokens.canConsume('/')) {
+                if (tokens.canConsume('/')) {
+                    steps.add(new DescendantOrSelf());
+                }
+                if (tokens.hasNext()) {
+                    steps.add(parseStepExpr(tokens));
+                }
+            }
+            return new PathExpression(true, steps);
+        }
+
+        protected StepExpression parseStepExpr( TokenStream tokens ) {
+            if (tokens.matches('(') || tokens.matches('.') || tokens.matches(XPathTokenizer.QUOTED_STRING)
+                || tokens.matches(ANY_VALUE, "(") || tokens.matches(ANY_VALUE, ":", ANY_VALUE, "(")) {
+                // We know its a filter expression (though literals don't fit this pattern) ...
+                return parseFilterExpr(tokens);
+            }
+            AxisStep result = parseAxisStep(tokens);
+            if (result != null) return result;
+            // It must be the remaining kind of filter expression ...
+            return parseFilterExpr(tokens);
+        }
+
+        protected AxisStep parseAxisStep( TokenStream tokens ) {
+            NodeTest nodeTest = null;
+            if (tokens.canConsume('@')) {
+                // Abbreviated forward step with an attribute...
+                nodeTest = new AttributeNameTest(parseNodeTest(tokens));
+            } else if (tokens.matches('*')) {
+                // Abbreviated forward step with an wildcard element ...
+                nodeTest = parseNodeTest(tokens);
+
+            } else if (tokens.matches("child", ":", ":") || tokens.matches("attribute", ":", ":")
+                       || tokens.matches("self", ":", ":") || tokens.matches("descendant", ":", ":")
+                       || tokens.matches("descendant-or-self", ":", ":") || tokens.matches("following-sibling", ":", ":")
+                       || tokens.matches("following", ":", ":") || tokens.matches("namespace", ":", ":")) {
+                // No non-abbreviated forward steps allowed
+                throw new ParsingException(
+                                           tokens.nextPosition(),
+                                           "XPath non-abbreviated forward steps (e.g., 'child::', 'attribute::', 'self::', 'descendant::', 'descendant-or-self::', 'following-sibling::', 'following::', or 'namespace::') are not supported");
+            } else if (tokens.matches("..")) {
+                // No abbreviated reverse steps allowed ...
+                throw new ParsingException(tokens.nextPosition(),
+                                           "XPath abbreviated reverse steps (e.g., '..') are not supported");
+            } else if (tokens.matches("parent", ":", ":") || tokens.matches("ancestor-or-self", ":", ":")
+                       || tokens.matches("preceding-sibling", ":", ":") || tokens.matches("preceding", ":", ":")
+                       || tokens.matches("ancestor", ":", ":")) {
+                // No non-abbreviated reverse steps allowed ...
+                throw new ParsingException(
+                                           tokens.nextPosition(),
+                                           "XPath non-abbreviated reverse steps (e.g., 'parent::', 'ancestor::', 'ancestor-or-self::', 'preceding-or-sibling::', or 'preceding::') are not supported");
+            } else if (tokens.matches(ANY_VALUE, ":", ANY_VALUE)
+                       && tokens.matches(XPathTokenizer.NAME, XPathTokenizer.SYMBOL, XPathTokenizer.NAME)) {
+                // This is probably a forward step with a (qualified) name test ...
+                nodeTest = parseQName(tokens);
+            } else if (tokens.matches(XPathTokenizer.NAME)) {
+                // This is probably a forward step with an unqualified name test ...
+                nodeTest = parseNodeTest(tokens);
+            } else {
+                return null;
+            }
+
+            // Parse the predicates
+            List<Component> predicates = parsePredicates(tokens);
+            return new AxisStep(nodeTest, predicates);
+        }
+
+        protected List<Component> parsePredicates( TokenStream tokens ) {
+            List<Component> predicates = new ArrayList<Component>();
+            while (tokens.canConsume('[')) {
+                predicates.add(parseExpr(tokens));
+                tokens.consume(']');
+            }
+            return predicates;
+        }
+
+        protected FilterStep parseFilterExpr( TokenStream tokens ) {
+            Component primaryExpr = parsePrimaryExpr(tokens);
+            List<Component> predicates = parsePredicates(tokens);
+            return new FilterStep(primaryExpr, predicates);
+        }
+
+        protected Component parsePrimaryExpr( TokenStream tokens ) {
+            if (tokens.matches('(')) {
+                return parseParenthesizedExpr(tokens);
+            }
+            if (tokens.matches('.')) {
+                return parseContextItemExpr(tokens);
+            }
+            if (tokens.matches(XPathTokenizer.QUOTED_STRING)) {
+                return parseStringLiteral(tokens);
+            }
+            if (tokens.matches(ANY_VALUE, "(") || tokens.matches(ANY_VALUE, ":", ANY_VALUE, "(")) {
+                return parseFunctionCall(tokens);
+            }
+            return parseNumericLiteral(tokens);
+        }
+
+        protected ContextItem parseContextItemExpr( TokenStream tokens ) {
+            tokens.consume('.');
+            return new ContextItem();
+        }
+
+        protected ParenthesizedExpression parseParenthesizedExpr( TokenStream tokens ) {
+            tokens.consume('(');
+            if (tokens.canConsume(')')) {
+                return new ParenthesizedExpression();
+            }
+            Component expr = parseExpr(tokens);
+            tokens.consume(')');
+            return new ParenthesizedExpression(expr);
+        }
+
+        protected Literal parseNumericLiteral( TokenStream tokens ) {
+            Position pos = tokens.nextPosition();
+            String sign = "";
+            if (tokens.canConsume('-')) sign = "-";
+            else if (tokens.canConsume('+')) sign = "";
+
+            // Try to parse this value as a number ...
+            ValueFactory<String> stringFactory = context.getValueFactories().getStringFactory();
+            String number = tokens.consume();
+            if (number.indexOf(".") != -1) {
+                String value = sign + number;
+                if (value.endsWith("e") && (tokens.matches('+') || tokens.matches('-'))) {
+                    // There's more to the number ...
+                    value = value + tokens.consume() + tokens.consume(); // +/-EXP
+                }
+                try {
+                    // Convert to a double and then back to a string to get canonical form ...
+                    String canonical = stringFactory.create(context.getValueFactories().getDoubleFactory().create(value));
+                    return new Literal(canonical);
+                } catch (ValueFormatException e) {
+                    String msg = GraphI18n.expectingLiteralAndUnableToParseAsDouble.text(value, pos.getLine(), pos.getColumn());
+                    throw new ParsingException(pos, msg);
+                }
+            }
+            // try to parse an a long ...
+            String value = sign + number;
+            try {
+                // Convert to a long and then back to a string to get canonical form ...
+                String canonical = stringFactory.create(context.getValueFactories().getLongFactory().create(value));
+                return new Literal(canonical);
+            } catch (ValueFormatException e) {
+                String msg = GraphI18n.expectingLiteralAndUnableToParseAsLong.text(value, pos.getLine(), pos.getColumn());
+                throw new ParsingException(pos, msg);
+            }
+        }
+
+        protected Literal parseStringLiteral( TokenStream tokens ) {
+            boolean removeQuotes = tokens.matches(XPathTokenizer.QUOTED_STRING);
+            String value = tokens.consume();
+            if (removeQuotes) value = removeQuotes(value);
+            return new Literal(value);
+        }
+
+        protected FunctionCall parseFunctionCall( TokenStream tokens ) {
+            NameTest name = parseQName(tokens);
+            tokens.consume("(");
+            List<Component> args = new ArrayList<Component>();
+            if (!tokens.matches(')')) {
+                do {
+                    args.add(parseExprSingle(tokens));
+                } while (tokens.canConsume(","));
+                tokens.consume(")");
+            }
+            return new FunctionCall(name, args);
+        }
+
+        protected Operator parseGeneralComp( TokenStream tokens ) {
+            if (tokens.canConsume("!", "=")) return Operator.NOT_EQUAL_TO;
+            if (tokens.canConsume("=")) return Operator.EQUAL_TO;
+            if (tokens.canConsume("<", "=")) return Operator.LESS_THAN_OR_EQUAL_TO;
+            if (tokens.canConsume(">", "=")) return Operator.GREATER_THAN_OR_EQUAL_TO;
+            if (tokens.canConsume("<")) return Operator.LESS_THAN;
+            if (tokens.canConsume(">")) return Operator.GREATER_THAN;
+            return null;
+        }
+
+        protected NodeTest parseNodeTest( TokenStream tokens ) {
+            KindTest kind = parseKindTest(tokens);
+            if (kind != null) return kind;
+            return parseNameTest(tokens);
+        }
+
+        protected NameTest parseNameTest( TokenStream tokens ) {
+            NameTest wildcard = parseWildcard(tokens);
+            if (wildcard != null) return wildcard;
+            return parseQName(tokens);
+        }
+
+        protected NameTest parseQName( TokenStream tokens ) {
+            String firstPart = parseNCName(tokens);
+            if (tokens.canConsume(':')) {
+                String secondPart = tokens.consume();
+                return new NameTest(firstPart, secondPart);
+            }
+            return new NameTest(null, firstPart);
+        }
+
+        protected String parseNCName( TokenStream tokens ) {
+            String name = tokens.consume();
+            if (!XmlCharacters.isValidNcName(name)) {
+                throw new ParsingException(tokens.previousPosition(), "Expected valid NCName but found " + name);
+            }
+            return name;
+        }
+
+        protected NameTest parseWildcard( TokenStream tokens ) {
+            if (tokens.canConsume('*')) {
+                if (tokens.canConsume(':')) {
+                    if (tokens.canConsume('*')) {
+                        return new NameTest(null, null);
+                    }
+                    String localName = tokens.consume();
+                    return new NameTest(null, localName);
+                }
+                return new NameTest(null, null);
+            }
+            if (tokens.matches(XPathTokenizer.NAME, XPathTokenizer.SYMBOL, XPathTokenizer.SYMBOL)
+                && tokens.matches(TokenStream.ANY_VALUE, ":", "*")) {
+                String prefix = tokens.consume();
+                tokens.consume(':');
+                tokens.consume('*');
+                return new NameTest(prefix, null);
+            }
+            return null;
+        }
+
+        protected NameTest parseItemType( TokenStream tokens ) {
+            return parseQName(tokens);
+        }
+
+        protected NameTest parseAtomicType( TokenStream tokens ) {
+            return parseQName(tokens);
+        }
+
+        protected KindTest parseKindTest( TokenStream tokens ) {
+            KindTest result = parseAnyKindTest(tokens);
+            if (result == null) result = parseDocumentTest(tokens);
+            if (result == null) result = parseElementTest(tokens);
+            if (result == null) result = parseAttributeTest(tokens);
+            if (result == null) result = parseSchemaElementTest(tokens);
+            if (result == null) result = parseSchemaAttributeTest(tokens);
+            if (result == null) result = parsePITest(tokens);
+            if (result == null) result = parseCommentTest(tokens);
+            if (result == null) result = parseTextTest(tokens);
+            return result;
+        }
+
+        protected AnyKindTest parseAnyKindTest( TokenStream tokens ) {
+            if (tokens.canConsume("node", "(", ")")) {
+                return new AnyKindTest();
+            }
+            return null;
+        }
+
+        protected ProcessingInstructionTest parsePITest( TokenStream tokens ) {
+            if (tokens.canConsume("processing-instruction", "(")) {
+                if (tokens.canConsume(")")) return new ProcessingInstructionTest(null);
+                String nameOrStringLiteral = tokens.consume();
+                tokens.consume(")");
+                return new ProcessingInstructionTest(nameOrStringLiteral);
+            }
+            return null;
+        }
+
+        protected CommentTest parseCommentTest( TokenStream tokens ) {
+            if (tokens.canConsume("comment", "(", ")")) {
+                return new CommentTest();
+            }
+            return null;
+        }
+
+        protected TextTest parseTextTest( TokenStream tokens ) {
+            if (tokens.canConsume("text", "(", ")")) {
+                return new TextTest();
+            }
+            return null;
+        }
+
+        protected DocumentTest parseDocumentTest( TokenStream tokens ) {
+            if (tokens.canConsume("document-node", "(")) {
+                // Document test ...
+                ElementTest elementTest = parseElementTest(tokens);
+                DocumentTest result = null;
+                if (elementTest != null) {
+                    result = new DocumentTest(elementTest);
+                } else {
+                    SchemaElementTest schemaTest = parseSchemaElementTest(tokens);
+                    result = schemaTest != null ? new DocumentTest(schemaTest) : null;
+                }
+                tokens.consume(")");
+                return result;
+            }
+            return null;
+        }
+
+        protected ElementTest parseElementTest( TokenStream tokens ) {
+            if (tokens.canConsume("element", "(")) {
+                if (tokens.canConsume(")") || tokens.canConsume("*", ")")) {
+                    return new ElementTest(new NameTest(null, null), new NameTest(null, null));
+                }
+                ElementTest result = null;
+                NameTest elementName = parseNameTest(tokens);
+                if (tokens.canConsume(",")) {
+                    NameTest typeName = parseNameTest(tokens);
+                    result = new ElementTest(elementName, typeName);
+                } else {
+                    result = new ElementTest(elementName, new NameTest(null, null));
+                }
+                tokens.consume(")");
+                return result;
+            }
+            return null;
+        }
+
+        protected SchemaElementTest parseSchemaElementTest( TokenStream tokens ) {
+            if (tokens.canConsume("schema-element", "(")) {
+                NameTest elementDeclarationName = parseNameTest(tokens);
+                SchemaElementTest result = new SchemaElementTest(elementDeclarationName);
+                tokens.consume(")");
+                return result;
+            }
+            return null;
+        }
+
+        protected AttributeTest parseAttributeTest( TokenStream tokens ) {
+            if (tokens.canConsume("attribute", "(")) {
+                if (tokens.canConsume(")") || tokens.canConsume("*", ")")) {
+                    return new AttributeTest(new NameTest(null, null), new NameTest(null, null));
+                }
+                AttributeTest result = null;
+                NameTest attributeName = parseNameTest(tokens);
+                if (tokens.canConsume(",")) {
+                    NameTest typeName = parseNameTest(tokens);
+                    result = new AttributeTest(attributeName, typeName);
+                } else {
+                    result = new AttributeTest(attributeName, new NameTest(null, null));
+                }
+                tokens.consume(")");
+                return result;
+            }
+            return null;
+        }
+
+        protected SchemaAttributeTest parseSchemaAttributeTest( TokenStream tokens ) {
+            if (tokens.canConsume("schema-attribute", "(")) {
+                NameTest attributeDeclarationName = parseNameTest(tokens);
+                SchemaAttributeTest result = new SchemaAttributeTest(attributeDeclarationName);
+                tokens.consume(")");
+                return result;
+            }
+            return null;
+        }
+
+        protected void parseSingleType( TokenStream tokens ) {
+        }
+
+        protected void parseSequenceType( TokenStream tokens ) {
+        }
+
+        /**
+         * Remove any leading and trailing single-quotes or double-quotes from the supplied text.
+         * 
+         * @param text the input text; may not be null
+         * @return the text without leading and trailing quotes, or <code>text</code> if there were no square brackets or quotes
+         */
+        protected String removeQuotes( String text ) {
+            return text.replaceFirst("^['\"]+", "").replaceAll("['\"]+$", "");
+        }
+    }
+
+    /**
+     * A {@link TokenStream.Tokenizer} implementation that parses single- and double-quoted strings, symbols, words consisting of
+     * {@link TokenStream.CharacterStream#isNextValidXmlNcNameCharacter() NCName}s (as defined by the <a
+     * href="http://www.w3.org/TR/REC-xml-names/#NT-NCName">Namespaces in XML 1.0</a> specification), XPath comments,and
+     * {@link #OTHER other} single-character tokens. Quoted phrases are delimited by single-quote and double-quote characters
+     * (which may be escaped within the quote). XPath comments begin with a "(:" and end with a ":)".
+     */
+    public static class XPathTokenizer implements TokenStream.Tokenizer {
+        /**
+         * The token type for tokens that represent an unquoted string containing a character sequence made up of non-whitespace
+         * and non-symbol characters.
+         */
+        public static final int NAME = 2 << 0;
+        /**
+         * The token type for tokens that consist of an individual "symbol" character. The set of characters includes:
+         * <code>(){}*.,;+%?$!<>|=:-[]^/\#@</code>
+         */
+        public static final int SYMBOL = 2 << 1;
+        /**
+         * The token type for tokens that consist of all the characters within single-quotes, double-quotes, or square brackets.
+         */
+        public static final int QUOTED_STRING = 2 << 2;
+        /**
+         * The token type for tokens that consist of all the characters between "(:" and ":)".
+         */
+        public static final int COMMENT = 2 << 3;
+        /**
+         * The token type for tokens that consist of single characters that are not a {@link #SYMBOL}, valid {@link #NAME}, or
+         * {@link #QUOTED_STRING}.
+         */
+        public static final int OTHER = 2 << 4;
+
+        private final boolean useComments;
+
+        public XPathTokenizer( boolean useComments ) {
+            this.useComments = useComments;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.common.text.TokenStream.Tokenizer#tokenize(CharacterStream, Tokens)
+         */
+        public void tokenize( CharacterStream input,
+                              Tokens tokens ) throws ParsingException {
+            while (input.hasNext()) {
+                char c = input.next();
+                switch (c) {
+                    case ' ':
+                    case '\t':
+                    case '\n':
+                    case '\r':
+                        // Just skip these whitespace characters ...
+                        break;
+                    case ')':
+                    case '{':
+                    case '}':
+                    case '*':
+                    case '.':
+                    case ',':
+                    case ';':
+                    case '+':
+                    case '%':
+                    case '?':
+                    case '$':
+                    case '!':
+                    case '<':
+                    case '>':
+                    case '|':
+                    case '=':
+                    case ':':
+                    case '-':
+                    case '[':
+                    case ']':
+                    case '^':
+                    case '/':
+                    case '\\':
+                    case '#':
+                    case '@':
+                        tokens.addToken(input.position(), input.index(), input.index() + 1, SYMBOL);
+                        break;
+                    case '\'':
+                    case '\"':
+                        int startIndex = input.index();
+                        char closingChar = c;
+                        Position pos = input.position();
+                        boolean foundClosingQuote = false;
+                        while (input.hasNext()) {
+                            c = input.next();
+                            if (c == closingChar && input.isNext(closingChar)) {
+                                c = input.next(); // consume the next closeChar since it is escaped
+                            } else if (c == closingChar) {
+                                foundClosingQuote = true;
+                                break;
+                            }
+                        }
+                        if (!foundClosingQuote) {
+                            String msg = CommonI18n.noMatchingDoubleQuoteFound.text(pos.getLine(), pos.getColumn());
+                            if (closingChar == '\'') {
+                                msg = CommonI18n.noMatchingSingleQuoteFound.text(pos.getLine(), pos.getColumn());
+                            }
+                            throw new ParsingException(pos, msg);
+                        }
+                        int endIndex = input.index() + 1; // beyond last character read
+                        tokens.addToken(pos, startIndex, endIndex, QUOTED_STRING);
+                        break;
+                    case '(':
+                        startIndex = input.index();
+                        if (input.isNext(':')) {
+                            // This is a comment ...
+                            pos = input.position();
+                            while (input.hasNext() && !input.isNext(':', ')')) {
+                                c = input.next();
+                            }
+                            if (input.hasNext()) input.next(); // consume the ':'
+                            if (input.hasNext()) input.next(); // consume the ')'
+                            if (useComments) {
+                                endIndex = input.index() + 1; // the token will include the closing ':' and ')' characters
+                                tokens.addToken(pos, startIndex, endIndex, COMMENT);
+                            }
+                        } else {
+                            tokens.addToken(input.position(), input.index(), input.index() + 1, SYMBOL);
+                            break;
+                        }
+                        break;
+                    default:
+                        startIndex = input.index();
+                        pos = input.position();
+                        // Read as long as there is a valid XML character ...
+                        int tokenType = (XmlCharacters.isValidNcNameStart(c)) ? NAME : OTHER;
+                        while (input.isNextValidXmlNcNameCharacter()) {
+                            c = input.next();
+                        }
+                        endIndex = input.index() + 1; // beyond last character that was included
+                        tokens.addToken(pos, startIndex, endIndex, tokenType);
+                }
+            }
+        }
+    }
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Added: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java	                        (rev 0)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java	2009-10-03 04:07:41 UTC (rev 1285)
@@ -0,0 +1,867 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you 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.
+ * 
+ * JBoss DNA 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.xpath;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import java.util.Arrays;
+import java.util.List;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.text.TokenStream;
+import org.jboss.dna.common.text.TokenStream.Tokenizer;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.jcr.xpath.XPath.AnyKindTest;
+import org.jboss.dna.jcr.xpath.XPath.AttributeNameTest;
+import org.jboss.dna.jcr.xpath.XPath.AttributeTest;
+import org.jboss.dna.jcr.xpath.XPath.AxisStep;
+import org.jboss.dna.jcr.xpath.XPath.CommentTest;
+import org.jboss.dna.jcr.xpath.XPath.Component;
+import org.jboss.dna.jcr.xpath.XPath.ContextItem;
+import org.jboss.dna.jcr.xpath.XPath.DescendantOrSelf;
+import org.jboss.dna.jcr.xpath.XPath.DocumentTest;
+import org.jboss.dna.jcr.xpath.XPath.ElementTest;
+import org.jboss.dna.jcr.xpath.XPath.FilterStep;
+import org.jboss.dna.jcr.xpath.XPath.FunctionCall;
+import org.jboss.dna.jcr.xpath.XPath.Literal;
+import org.jboss.dna.jcr.xpath.XPath.NameTest;
+import org.jboss.dna.jcr.xpath.XPath.NodeTest;
+import org.jboss.dna.jcr.xpath.XPath.ParenthesizedExpression;
+import org.jboss.dna.jcr.xpath.XPath.PathExpression;
+import org.jboss.dna.jcr.xpath.XPath.SchemaAttributeTest;
+import org.jboss.dna.jcr.xpath.XPath.SchemaElementTest;
+import org.jboss.dna.jcr.xpath.XPath.StepExpression;
+import org.jboss.dna.jcr.xpath.XPath.TextTest;
+import org.jboss.dna.jcr.xpath.XPathParser.XPathTokenizer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * 
+ */
+public class XPathParserTest {
+
+    private ExecutionContext context;
+    private XPathParser.Parser parser;
+
+    @Before
+    public void beforeEach() {
+        context = new ExecutionContext();
+        parser = new XPathParser.Parser(context);
+    }
+
+    @After
+    public void afterEach() {
+        parser = null;
+    }
+
+    @Test
+    public void shouldParseXPathExpressions() {
+        assertParsable("/jcr:root/a/b/c");
+        assertParsable("/jcr:root/a/b/c[*]");
+        assertParsable("/jcr:root/some[1]/element(nodes, my:type)[1]");
+        assertParsable("//element(*,my:type)");
+        assertParsable("//element(*,my:type)[@jcr:title='something' and @globalProperty='something else']");
+        assertParsable("//element(*,my:type)[@jcr:title | @globalProperty]");
+        assertParsable("//element(*, my:type) order by @my:title");
+        assertParsable("//element(*, my:type) [jcr:contains(., 'jcr')] order by jcr:score() descending");
+        assertParsable("//element(*, employee)[@secretary and @assistant]");
+    }
+
+    @Test
+    public void shouldParseXPathExpressionsThatCombineSeparateExpressions() {
+        assertParsable("/jcr:root/a/b/c and /jcr:root/c/d/e");
+        assertParsable("/jcr:root/a/b/c and /jcr:root/c/d/e or /jcr:root/f/g/h");
+    }
+
+    @Test
+    public void shouldFailToParseInvalidXPathExpressions() {
+        assertNotParsable("//"); // http://www.w3.org/XML/2007/qt-errata/xpath20-errata.html#E3
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Path expression
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParsePathExpressionWithAbbreviatedDescendantOrSelfWithNameTestAfter() {
+        assertThat(parser.parsePathExpr(tokenize("//b:c")), is(pathExpr(descendantOrSelf(), axisStep(nameTest("b", "c")))));
+    }
+
+    @Test
+    public void shouldParsePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNamePathPredicate() {
+        assertThat(parser.parsePathExpr(tokenize("//.[c]")), is(pathExpr(descendantOrSelf(),
+                                                                         filterStep(contextItem(),
+                                                                                    relativePathExpr(axisStep(nameTest("c")))))));
+    }
+
+    @Test
+    public void shouldParsePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNumericLiteralPredicate() {
+        assertThat(parser.parsePathExpr(tokenize("//.[3]")), is(pathExpr(descendantOrSelf(),
+                                                                         filterStep(contextItem(),
+                                                                                    relativePathExpr(filterStep(literal("3")))))));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Relative path expression
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseRelativePathExpressionWithAbbreviatedDescendantOrSelfWithNameTestAfter() {
+        assertThat(parser.parseRelativePathExpr(tokenize("a//b:c")), is(relativePathExpr(axisStep(nameTest("a")),
+                                                                                         descendantOrSelf(),
+                                                                                         axisStep(nameTest("b", "c")))));
+    }
+
+    @Test
+    public void shouldParseRelativePathExpressionWithAbbreviatedDescendantOrSelfAtEnd() {
+        assertThat(parser.parseRelativePathExpr(tokenize("a//")),
+                   is(relativePathExpr(axisStep(nameTest("a")), descendantOrSelf())));
+    }
+
+    @Test
+    public void shouldParseRelativePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNamePathPredicate() {
+        assertThat(parser.parseRelativePathExpr(tokenize("a//.[c]")),
+                   is(relativePathExpr(axisStep(nameTest("a")),
+                                       descendantOrSelf(),
+                                       filterStep(contextItem(), relativePathExpr(axisStep(nameTest("c")))))));
+    }
+
+    @Test
+    public void shouldParseRelativePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNumericLiteralPredicate() {
+        assertThat(parser.parseRelativePathExpr(tokenize("a//.[3]")),
+                   is(relativePathExpr(axisStep(nameTest("a")),
+                                       descendantOrSelf(),
+                                       filterStep(contextItem(), relativePathExpr(filterStep(literal("3")))))));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Step Expression
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseStepExpressionFromParenthesizedLiteral() {
+        assertThat(parser.parseStepExpr(tokenize("('foo')")),
+                   is((StepExpression)filterStep(paren(relativePathExpr(filterStep(literal("foo")))))));
+    }
+
+    @Test
+    public void shouldParseStepExpressionFromQuotedStringLiteral() {
+        assertThat(parser.parseStepExpr(tokenize("'foo'")), is((StepExpression)filterStep(literal("foo"))));
+    }
+
+    @Test
+    public void shouldParseStepExpressionFromFunctionCallWithUnqualifiedName() {
+        assertThat(parser.parseStepExpr(tokenize("element(*,*)")),
+                   is((StepExpression)filterStep(functionCall(nameTest("element"),
+                                                              relativePathExpr(axisStep(wildcard())),
+                                                              relativePathExpr(axisStep(wildcard()))))));
+    }
+
+    @Test
+    public void shouldParseStepExpressionFromFunctionCallWithQualifiedName() {
+        assertThat(parser.parseStepExpr(tokenize("foo:bar(*)")),
+                   is((StepExpression)filterStep(functionCall(nameTest("foo", "bar"), relativePathExpr(axisStep(wildcard()))))));
+    }
+
+    @Test
+    public void shouldParseStepExpressionFromQualifiedNameWithPredicate() {
+        assertThat(parser.parseStepExpr(tokenize("foo:bar[3]")),
+                   is((StepExpression)axisStep(nameTest("foo", "bar"), relativePathExpr(filterStep(literal("3"))))));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Axis Step - attribute
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseAxisStepFromAttributeWithNoPrefix() {
+        assertThat(parser.parseAxisStep(tokenize("@foo")), is(axisStep(attributeNameTest(nameTest("foo")))));
+    }
+
+    @Test
+    public void shouldParseAxisStepFromAttributeWithPrefix() {
+        assertThat(parser.parseAxisStep(tokenize("@foo:bar")), is(axisStep(attributeNameTest(nameTest("foo", "bar")))));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseAxisStepFromInvalidAttributeName() {
+        parser.parseAxisStep(tokenize("@3:invalidName"));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Axis Step - name
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseAxisStepFromNameWithNoPrefix() {
+        assertThat(parser.parseAxisStep(tokenize("foo")), is(axisStep(nameTest("foo"))));
+    }
+
+    @Test
+    public void shouldParseAxisStepFromNameWithPrefix() {
+        assertThat(parser.parseAxisStep(tokenize("foo:bar")), is(axisStep(nameTest("foo", "bar"))));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseAxisStepIfInvalidName() {
+        assertThat(parser.parseAxisStep(tokenize("3:invalidName")), is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Axis Step - wildcard
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseAxisStepFromWildcard() {
+        assertThat(parser.parseAxisStep(tokenize("*")), is(axisStep(wildcard())));
+    }
+
+    @Test
+    public void shouldParseAxisStepFromWildcardPrefixAndNonWildcardLocalName() {
+        assertThat(parser.parseAxisStep(tokenize("*:foo")), is(axisStep(nameTest(null, "foo"))));
+    }
+
+    @Test
+    public void shouldParseAxisStepFromWithPrefixAndWildcardLocalName() {
+        assertThat(parser.parseAxisStep(tokenize("foo:*")), is(axisStep(nameTest("foo", null))));
+    }
+
+    @Test
+    public void shouldParseAxisStepFromWildcardPrefixAndNonWildcardLocalNameWithPredicates() {
+        assertThat(parser.parseAxisStep(tokenize("*:name[3]")), is(axisStep(nameTest(null, "name"),
+                                                                            relativePathExpr(filterStep(literal("3"))))));
+    }
+
+    @Test
+    public void shouldParseAxisStepFromWildcardLocalNameWithPredicates() {
+        assertThat(parser.parseAxisStep(tokenize("*[3]")), is(axisStep(wildcard(), relativePathExpr(filterStep(literal("3"))))));
+    }
+
+    @Test
+    public void shouldParseAxisStepFromWildcardPrefixAndLocalNameWithPredicates() {
+        assertThat(parser.parseAxisStep(tokenize("*:*[3]")), is(axisStep(wildcard(), relativePathExpr(filterStep(literal("3"))))));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Axis Step - self::, child::, descendant::, descendant-or-self::, following::, following-sibling::, namespace::
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedSelfAxis() {
+        parser.parseAxisStep(tokenize("self::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedChildAxis() {
+        parser.parseAxisStep(tokenize("child::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedDescendantAxis() {
+        parser.parseAxisStep(tokenize("descendant::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedDescendantOrSelfAxis() {
+        parser.parseAxisStep(tokenize("descendant-or-self::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedFollowingAxis() {
+        parser.parseAxisStep(tokenize("following::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedFollowingSiblingAxis() {
+        parser.parseAxisStep(tokenize("following-sibling::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedNamespaceAxis() {
+        parser.parseAxisStep(tokenize("namespace::x"));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Axis Step - parent::, ancestor::, ancestor-or-self::, preceding::, preceding-sibling::
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedParentAxis() {
+        parser.parseAxisStep(tokenize("parent::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedAncestorAxis() {
+        parser.parseAxisStep(tokenize("ancestor::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedAncestorOrSelfAxis() {
+        parser.parseAxisStep(tokenize("ancestor-or-self::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedPrecedingAxis() {
+        parser.parseAxisStep(tokenize("preceding::x"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNonAbbreviatedPrecedingSiblingAxis() {
+        parser.parseAxisStep(tokenize("preceding-sibling::x"));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Predicates
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParsePredicatesWhenThereIsOnlyOnePredicate() {
+        assertThat(parser.parsePredicates(tokenize("[foo]")), is(predicates(relativePathExpr(axisStep(nameTest("foo"))))));
+    }
+
+    @Test
+    public void shouldParsePredicatesWhenThereAreMultiplePredicates() {
+        assertThat(parser.parsePredicates(tokenize("['foo']['bar']")),
+                   is(predicates(relativePathExpr(filterStep(literal("foo"))), relativePathExpr(filterStep(literal("bar"))))));
+        assertThat(parser.parsePredicates(tokenize("[foo][bar]")), is(predicates(relativePathExpr(axisStep(nameTest("foo"))),
+                                                                                 relativePathExpr(axisStep(nameTest("bar"))))));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Context item
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseContextItemExpr() {
+        assertThat(parser.parseContextItemExpr(tokenize(".")), is(new ContextItem()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Parenthesized expression
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseParenthesizedExpression() {
+        assertThat(parser.parseParenthesizedExpr(tokenize("('foo')")), is(paren(relativePathExpr(filterStep(literal("foo"))))));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Number literal
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseNumberLiteralContainingUnquotedIntegralValuesIntoCanonicalStringRepresentation() {
+        assertThat(parser.parseNumericLiteral(tokenize("1")), is(literal("1")));
+        assertThat(parser.parseNumericLiteral(tokenize("-1")), is(literal("-1")));
+        assertThat(parser.parseNumericLiteral(tokenize("0")), is(literal("0")));
+        assertThat(parser.parseNumericLiteral(tokenize("+1001")), is(literal("1001")));
+    }
+
+    @Test
+    public void shouldParseNumberLiteralContainingUnquotedDecimalValuesIntoCanonicalStringRepresentation() {
+        assertThat(parser.parseNumericLiteral(tokenize("1.2")), is(literal("1.2")));
+        assertThat(parser.parseNumericLiteral(tokenize("-1.2")), is(literal("-1.2")));
+        assertThat(parser.parseNumericLiteral(tokenize("0.2")), is(literal("0.2")));
+        assertThat(parser.parseNumericLiteral(tokenize("+1001.2")), is(literal("1001.2")));
+        assertThat(parser.parseNumericLiteral(tokenize("1.2e10")), is(literal("1.2E10")));
+        assertThat(parser.parseNumericLiteral(tokenize("-1.2e10")), is(literal("-1.2E10")));
+        assertThat(parser.parseNumericLiteral(tokenize("0.2e10")), is(literal("2.0E9")));
+        assertThat(parser.parseNumericLiteral(tokenize("1.2e+10")), is(literal("1.2E10")));
+        assertThat(parser.parseNumericLiteral(tokenize("-1.2e+10")), is(literal("-1.2E10")));
+        assertThat(parser.parseNumericLiteral(tokenize("0.2e+10")), is(literal("2.0E9")));
+        assertThat(parser.parseNumericLiteral(tokenize("1.2e-10")), is(literal("1.2E-10")));
+        assertThat(parser.parseNumericLiteral(tokenize("-1.2e-10")), is(literal("-1.2E-10")));
+        assertThat(parser.parseNumericLiteral(tokenize("0.2e-10")), is(literal("2.0E-11")));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // String literal
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseStringLiteral() {
+        assertThat(parser.parseStringLiteral(tokenize("one")), is(literal("one")));
+        assertThat(parser.parseStringLiteral(tokenize("'one'")), is(literal("one")));
+        assertThat(parser.parseStringLiteral(tokenize("'one word as a quote'")), is(literal("one word as a quote")));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Function call
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseFunctionCallWithZeroParameters() {
+        FunctionCall func = parser.parseFunctionCall(tokenize("a()"));
+        assertThat(func, is(notNullValue()));
+        assertThat(func.getName(), is(nameTest("a")));
+        assertThat(func.getParameters().isEmpty(), is(true));
+    }
+
+    @Test
+    public void shouldParseFunctionCallWithOneQuotedStringLiteralParameter() {
+        FunctionCall func = parser.parseFunctionCall(tokenize("a('foo')"));
+        assertThat(func, is(notNullValue()));
+        assertThat(func.getName(), is(nameTest("a")));
+        assertThat(func.getParameters().size(), is(1));
+        assertThat(func.getParameters().get(0), is((Component)relativePathExpr(filterStep(literal("foo")))));
+    }
+
+    @Test
+    public void shouldParseFunctionCallWithOneUnquotedStringLiteralParameter() {
+        FunctionCall func = parser.parseFunctionCall(tokenize("a(foo)"));
+        assertThat(func, is(notNullValue()));
+        assertThat(func.getName(), is(nameTest("a")));
+        assertThat(func.getParameters().size(), is(1));
+        assertThat(func.getParameters().get(0), is((Component)relativePathExpr(axisStep(nameTest("foo")))));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // General comparison
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseGeneralComp() {
+        assertThat(parser.parseGeneralComp(tokenize("!=")), is(Operator.NOT_EQUAL_TO));
+        assertThat(parser.parseGeneralComp(tokenize("=")), is(Operator.EQUAL_TO));
+        assertThat(parser.parseGeneralComp(tokenize("<")), is(Operator.LESS_THAN));
+        assertThat(parser.parseGeneralComp(tokenize("<=")), is(Operator.LESS_THAN_OR_EQUAL_TO));
+        assertThat(parser.parseGeneralComp(tokenize(">")), is(Operator.GREATER_THAN));
+        assertThat(parser.parseGeneralComp(tokenize(">=")), is(Operator.GREATER_THAN_OR_EQUAL_TO));
+        assertThat(parser.parseGeneralComp(tokenize("<5")), is(Operator.LESS_THAN));
+        assertThat(parser.parseGeneralComp(tokenize(">5")), is(Operator.GREATER_THAN));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseGeneralCompIfOperatorPatternIsNotFound() {
+        assertThat(parser.parseGeneralComp(tokenize("name")), is(nullValue()));
+        assertThat(parser.parseGeneralComp(tokenize("+")), is(nullValue()));
+        assertThat(parser.parseGeneralComp(tokenize("!+")), is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Name
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseName() {
+        assertThat(parser.parseNameTest(tokenize("*")), is(wildcard()));
+        assertThat(parser.parseNameTest(tokenize("*:*")), is(wildcard()));
+        assertThat(parser.parseNameTest(tokenize("*:name")), is(nameTest(null, "name")));
+        assertThat(parser.parseNameTest(tokenize("name:*")), is(nameTest("name", null)));
+        assertThat(parser.parseNameTest(tokenize("abc")), is(nameTest("abc")));
+        assertThat(parser.parseNameTest(tokenize("abc:def")), is(nameTest("abc", "def")));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNameIfNotValidWildcardOrQName() {
+        parser.parseNameTest(tokenize("3ABC:def"));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // QName
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseQName() {
+        assertThat(parser.parseQName(tokenize("abc")), is(nameTest("abc")));
+        assertThat(parser.parseQName(tokenize("abc:def")), is(nameTest("abc", "def")));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseQNameIfStartsWithDigit() {
+        parser.parseNCName(tokenize("3ABC:def"));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // NCName
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseNCName() {
+        assertThat(parser.parseNCName(tokenize("ABCDEFGHIJKLMNOPQRSTUVWXYZ")), is("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+        assertThat(parser.parseNCName(tokenize("abcdefghijklmnopqrstuvwxyz")), is("abcdefghijklmnopqrstuvwxyz"));
+        assertThat(parser.parseNCName(tokenize("a0123456789")), is("a0123456789"));
+        assertThat(parser.parseNCName(tokenize("a_-3b")), is("a_-3b"));
+        assertThat(parser.parseNCName(tokenize("abc:def")), is("abc"));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseNCNameIfStartsWithDigit() {
+        parser.parseNCName(tokenize("3ABC"));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // wildcard
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseWildcard() {
+        assertThat(parser.parseWildcard(tokenize("*")), is(wildcard()));
+        assertThat(parser.parseWildcard(tokenize("*:*")), is(wildcard()));
+        assertThat(parser.parseWildcard(tokenize("*:name")), is(nameTest(null, "name")));
+        assertThat(parser.parseWildcard(tokenize("name:*")), is(nameTest("name", null)));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseWildcardIfNotWildcard() {
+        assertThat(parser.parseWildcard(tokenize("name")), is(nullValue()));
+        assertThat(parser.parseWildcard(tokenize("name:foo")), is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // kind test
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseKindTest() {
+        assertThat(parser.parseKindTest(tokenize("node()")), is(instanceOf(AnyKindTest.class)));
+        assertThat(parser.parseKindTest(tokenize("comment()")), is(instanceOf(CommentTest.class)));
+        assertThat(parser.parseKindTest(tokenize("text()")), is(instanceOf(TextTest.class)));
+        assertThat(parser.parseKindTest(tokenize("document-node(element(foo:bar))")), is(instanceOf(DocumentTest.class)));
+        assertThat(parser.parseKindTest(tokenize("element(foo:bar)")), is(instanceOf(ElementTest.class)));
+        assertThat(parser.parseKindTest(tokenize("schema-element(foo:bar)")), is(instanceOf(SchemaElementTest.class)));
+        assertThat(parser.parseKindTest(tokenize("attribute(foo:bar)")), is(instanceOf(AttributeTest.class)));
+        assertThat(parser.parseKindTest(tokenize("schema-attribute(foo:bar)")), is(instanceOf(SchemaAttributeTest.class)));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // node
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseAnyKindTest() {
+        assertThat(parser.parseAnyKindTest(tokenize("node()")), is(notNullValue()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseAnyKindTestIfNotFollowedByOpenAndCloseParenthesis() {
+        assertThat(parser.parseAnyKindTest(tokenize("node x )")), is(nullValue()));
+        assertThat(parser.parseAnyKindTest(tokenize("node(x )")), is(nullValue()));
+        assertThat(parser.parseAnyKindTest(tokenize("node(x")), is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // comment
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseCommentTest() {
+        assertThat(parser.parseCommentTest(tokenize("comment()")), is(notNullValue()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseCommentTestIfNotFollowedByOpenAndCloseParenthesis() {
+        assertThat(parser.parseCommentTest(tokenize("comment x )")), is(nullValue()));
+        assertThat(parser.parseCommentTest(tokenize("comment(x )")), is(nullValue()));
+        assertThat(parser.parseCommentTest(tokenize("comment(x")), is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // text
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseTextTest() {
+        assertThat(parser.parseTextTest(tokenize("text()")), is(notNullValue()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseTextTestIfNotFollowedByOpenAndCloseParenthesis() {
+        assertThat(parser.parseTextTest(tokenize("text x )")), is(nullValue()));
+        assertThat(parser.parseTextTest(tokenize("text(x )")), is(nullValue()));
+        assertThat(parser.parseTextTest(tokenize("text(x")), is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // document-node
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseDocumentTest() {
+        DocumentTest result = parser.parseDocumentTest(tokenize("document-node(element(foo:bar))"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementTest().getElementName(), is(nameTest("foo", "bar")));
+        assertThat(result.getElementTest().getTypeName(), is(wildcard()));
+        assertThat(result.getSchemaElementTest(), is(nullValue()));
+
+        result = parser.parseDocumentTest(tokenize("document-node(schema-element(foo))"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getSchemaElementTest().getElementDeclarationName(), is(nameTest("foo")));
+        assertThat(result.getElementTest(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseDocumentTestIfOpenParenthesisIsNotIncluded() {
+        assertThat(parser.parseDocumentTest(tokenize("document-node foo")), is(nullValue()));
+        assertThat(parser.parseDocumentTest(tokenize("document-node foo")), is(nullValue()));
+    }
+
+    @Test( expected = ParsingException.class )
+    public void shouldFailToParseDocumentTestIfDocumentNodeDoesNotContainElementOrSchemaElement() {
+        parser.parseDocumentTest(tokenize("document-node(foo)"));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // element
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseElementTest() {
+        ElementTest result = parser.parseElementTest(tokenize("element(foo)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementName(), is(nameTest("foo")));
+        assertThat(result.getTypeName(), is(wildcard()));
+
+        result = parser.parseElementTest(tokenize("element(foo:bar)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(wildcard()));
+
+        result = parser.parseElementTest(tokenize("element(foo:bar,baz)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(nameTest("baz")));
+
+        result = parser.parseElementTest(tokenize("element(foo:bar,baz:bam)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(nameTest("baz", "bam")));
+
+        result = parser.parseElementTest(tokenize("element(foo:bar,*)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(wildcard()));
+
+        result = parser.parseElementTest(tokenize("element(*,foo:bar)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementName(), is(wildcard()));
+        assertThat(result.getTypeName(), is(nameTest("foo", "bar")));
+
+        result = parser.parseElementTest(tokenize("element(*,*)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementName(), is(wildcard()));
+        assertThat(result.getTypeName(), is(wildcard()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseElementTestIfOpenParenthesisIsNotIncluded() {
+        ElementTest result = parser.parseElementTest(tokenize("attribute foo"));
+        assertThat(result, is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // schema-element
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseSchemaElementTest() {
+        SchemaElementTest result = parser.parseSchemaElementTest(tokenize("schema-element(foo)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementDeclarationName(), is(nameTest("foo")));
+
+        result = parser.parseSchemaElementTest(tokenize("schema-element(foo:bar)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementDeclarationName(), is(nameTest("foo", "bar")));
+
+        result = parser.parseSchemaElementTest(tokenize("schema-element(*)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getElementDeclarationName(), is(wildcard()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseSchemaElementTestIfOpenParenthesisIsNotIncluded() {
+        SchemaElementTest result = parser.parseSchemaElementTest(tokenize("schema-element foo"));
+        assertThat(result, is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // attribute
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseAttributeTest() {
+        AttributeTest result = parser.parseAttributeTest(tokenize("attribute(foo)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeName(), is(nameTest("foo")));
+        assertThat(result.getTypeName(), is(wildcard()));
+
+        result = parser.parseAttributeTest(tokenize("attribute(foo:bar)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(wildcard()));
+
+        result = parser.parseAttributeTest(tokenize("attribute(foo:bar,baz)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(nameTest("baz")));
+
+        result = parser.parseAttributeTest(tokenize("attribute(foo:bar,baz:bar)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(nameTest("baz", "bar")));
+
+        result = parser.parseAttributeTest(tokenize("attribute(*,baz:bar)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeName(), is(wildcard()));
+        assertThat(result.getTypeName(), is(nameTest("baz", "bar")));
+
+        result = parser.parseAttributeTest(tokenize("attribute(foo:bar,*)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeName(), is(nameTest("foo", "bar")));
+        assertThat(result.getTypeName(), is(wildcard()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseAttributeTestIfOpenParenthesisIsNotIncluded() {
+        AttributeTest result = parser.parseAttributeTest(tokenize("attribute foo"));
+        assertThat(result, is(nullValue()));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // schema-attribute
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldParseSchemaAttributeTest() {
+        SchemaAttributeTest result = parser.parseSchemaAttributeTest(tokenize("schema-attribute(foo)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeDeclarationName(), is(nameTest("foo")));
+
+        result = parser.parseSchemaAttributeTest(tokenize("schema-attribute(foo:bar)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeDeclarationName(), is(nameTest("foo", "bar")));
+
+        result = parser.parseSchemaAttributeTest(tokenize("schema-attribute(*)"));
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getAttributeDeclarationName(), is(wildcard()));
+    }
+
+    @Test
+    public void shouldReturnNullFromParseSchemaAttributeTestIfOpenParenthesisIsNotIncluded() {
+        SchemaAttributeTest result = parser.parseSchemaAttributeTest(tokenize("schema-attribute foo"));
+        assertThat(result, is(nullValue()));
+    }
+
+    protected TokenStream tokenize( String xpath ) {
+        Tokenizer tokenizer = new XPathTokenizer(false); // skip comments
+        return new TokenStream(xpath, tokenizer, true).start(); // case sensitive!!
+    }
+
+    protected DescendantOrSelf descendantOrSelf() {
+        return new DescendantOrSelf();
+    }
+
+    protected AxisStep axisStep( NodeTest nodeTest,
+                                 Component... predicates ) {
+        return new AxisStep(nodeTest, predicates(predicates));
+    }
+
+    protected FilterStep filterStep( Component primaryExpression,
+                                     Component... predicates ) {
+        return new FilterStep(primaryExpression, predicates(predicates));
+    }
+
+    protected PathExpression pathExpr( StepExpression... steps ) {
+        return pathExpr(Arrays.asList(steps));
+    }
+
+    protected PathExpression pathExpr( List<StepExpression> steps ) {
+        return new PathExpression(false, steps);
+    }
+
+    protected PathExpression relativePathExpr( StepExpression... steps ) {
+        return relativePathExpr(Arrays.asList(steps));
+    }
+
+    protected PathExpression relativePathExpr( List<StepExpression> steps ) {
+        return new PathExpression(true, steps);
+    }
+
+    protected Literal literal( String value ) {
+        return new Literal(value);
+    }
+
+    protected ContextItem contextItem() {
+        return new ContextItem();
+    }
+
+    protected List<Component> predicates( Component... predicates ) {
+        return Arrays.asList(predicates);
+    }
+
+    protected ParenthesizedExpression paren( Component value ) {
+        return new ParenthesizedExpression(value);
+    }
+
+    protected AttributeNameTest attributeNameTest( NameTest nameTest ) {
+        return new AttributeNameTest(nameTest);
+    }
+
+    protected AttributeTest attribute( NameTest name ) {
+        return new AttributeTest(name, null);
+    }
+
+    protected AttributeTest attribute( NameTest name,
+                                       NameTest type ) {
+        return new AttributeTest(name, type);
+    }
+
+    protected FunctionCall functionCall( NameTest name,
+                                         Component... parameters ) {
+        return new FunctionCall(name, Arrays.asList(parameters));
+    }
+
+    protected NameTest wildcard() {
+        return new NameTest(null, null);
+    }
+
+    protected NameTest nameTest( String localPart ) {
+        return new NameTest(null, localPart);
+    }
+
+    protected NameTest nameTest( String prefix,
+                                 String local ) {
+        return new NameTest(prefix, local);
+    }
+
+    protected void assertParsable( String xpath ) {
+        new XPathParser().parseQuery(xpath, context);
+    }
+
+    protected void assertNotParsable( String xpath ) {
+        try {
+            new XPathParser().parseQuery(xpath, context);
+            fail("Expected an invalid XPath:  " + xpath);
+        } catch (ParsingException e) {
+            // expected
+        }
+    }
+
+}


Property changes on: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF



More information about the dna-commits mailing list