/* * $Header: /home/projects/jaxen/scm/jaxen/src/java/main/org/jaxen/saxpath/base/XPathReader.java,v 1.32 2006/04/07 23:47:37 elharo Exp $ * $Revision: 1.32 $ * $Date: 2006/04/07 23:47:37 $ * * ==================================================================== * * Copyright 2000-2002 bob mcwhirter & James Strachan. * All rights reserved. * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the Jaxen Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ==================================================================== * This software consists of voluntary contributions made by many * individuals on behalf of the Jaxen Project and was originally * created by bob mcwhirter <bob@werken.com> and * James Strachan <jstrachan@apache.org>. For more information on the * Jaxen Project, please see <http://www.jaxen.org/>. * * $Id: XPathReader.java,v 1.32 2006/04/07 23:47:37 elharo Exp $ */ package org.orbeon.jaxen.saxpath.base; import org.orbeon.jaxen.saxpath.*; import org.orbeon.jaxen.saxpath.helpers.DefaultXPathHandler; import java.util.ArrayList; /** Implementation of SAXPath's <code>XPathReader</code> which * generates callbacks to an <code>XPathHandler</code>. * * @author bob mcwhirter (bob@werken.com) */ public class XPathReader implements org.orbeon.jaxen.saxpath.XPathReader { private ArrayList tokens; private XPathLexer lexer; private XPathHandler handler; private static XPathHandler defaultHandler = new DefaultXPathHandler(); /** * Create a new <code>XPathReader</code> with a do-nothing * <code>XPathHandler</code>. */ public XPathReader() { setXPathHandler( defaultHandler ); } public void setXPathHandler(XPathHandler handler) { this.handler = handler; } public XPathHandler getXPathHandler() { return this.handler; } public void parse(String xpath) throws SAXPathException { setUpParse( xpath ); getXPathHandler().startXPath(); expr(); getXPathHandler().endXPath(); if ( LA(1) != TokenTypes.EOF ) { XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" ); throw ex; } lexer = null; tokens = null; } void setUpParse(String xpath) { this.tokens = new ArrayList(); this.lexer = new XPathLexer( xpath ); } private void pathExpr() throws SAXPathException { getXPathHandler().startPathExpr(); switch ( LA(1) ) { case TokenTypes.DOUBLE: case TokenTypes.LITERAL: { filterExpr(); if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH ) { XPathSyntaxException ex = createSyntaxException("Node-set expected"); throw ex; } break; } case TokenTypes.LEFT_PAREN: case TokenTypes.DOLLAR: { filterExpr(); if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH) { locationPath( false ); } break; } case TokenTypes.IDENTIFIER: { if ( ( LA(2) == TokenTypes.LEFT_PAREN && ! isNodeTypeName( LT(1) ) ) || ( LA(2) == TokenTypes.COLON && LA(4) == TokenTypes.LEFT_PAREN) ) { filterExpr(); if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH) { locationPath( false ); } } else { locationPath( false ); } break; } case TokenTypes.DOT: case TokenTypes.DOT_DOT: case TokenTypes.STAR: case TokenTypes.AT: { locationPath( false ); break; } case TokenTypes.SLASH: case TokenTypes.DOUBLE_SLASH: { locationPath( true ); break; } default: { XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" ); throw ex; } } getXPathHandler().endPathExpr(); } private void literal() throws SAXPathException { Token token = match( TokenTypes.LITERAL ); getXPathHandler().literal( token.getTokenText() ); } private void functionCall() throws SAXPathException { String prefix = null; String functionName = null; if ( LA(2) == TokenTypes.COLON ) { prefix = match( TokenTypes.IDENTIFIER ).getTokenText(); match( TokenTypes.COLON ); } else { prefix = ""; } functionName = match( TokenTypes.IDENTIFIER ).getTokenText(); getXPathHandler().startFunction( prefix, functionName ); match ( TokenTypes.LEFT_PAREN ); arguments(); match ( TokenTypes.RIGHT_PAREN ); getXPathHandler().endFunction(); } private void arguments() throws SAXPathException { while ( LA(1) != TokenTypes.RIGHT_PAREN ) { expr(); if ( LA(1) == TokenTypes.COMMA ) { match( TokenTypes.COMMA ); } else { break; } } } private void filterExpr() throws SAXPathException { getXPathHandler().startFilterExpr(); switch ( LA(1) ) { case TokenTypes.DOUBLE: { Token token = match( TokenTypes.DOUBLE ); getXPathHandler().number( Double.parseDouble( token.getTokenText() ) ); break; } case TokenTypes.LITERAL: { literal(); break; } case TokenTypes.LEFT_PAREN: { match( TokenTypes.LEFT_PAREN ); expr(); match( TokenTypes.RIGHT_PAREN ); break; } case TokenTypes.IDENTIFIER: { functionCall(); break; } case TokenTypes.DOLLAR: { variableReference(); break; } } predicates(); getXPathHandler().endFilterExpr(); } private void variableReference() throws SAXPathException { match( TokenTypes.DOLLAR ); String prefix = null; String variableName = null; if ( LA(2) == TokenTypes.COLON ) { prefix = match( TokenTypes.IDENTIFIER ).getTokenText(); match( TokenTypes.COLON ); } else { prefix = ""; } variableName = match( TokenTypes.IDENTIFIER ).getTokenText(); getXPathHandler().variableReference( prefix, variableName ); } void locationPath(boolean isAbsolute) throws SAXPathException { switch ( LA(1) ) { case TokenTypes.SLASH: case TokenTypes.DOUBLE_SLASH: { if ( isAbsolute ) { absoluteLocationPath(); } else { relativeLocationPath(); } break; } case TokenTypes.AT: case TokenTypes.IDENTIFIER: case TokenTypes.DOT: case TokenTypes.DOT_DOT: case TokenTypes.STAR: { relativeLocationPath(); break; } default: { XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" ); throw ex; } } } private void absoluteLocationPath() throws SAXPathException { getXPathHandler().startAbsoluteLocationPath(); switch ( LA(1) ) { case TokenTypes.SLASH: { match( TokenTypes.SLASH ); switch ( LA(1) ) { case TokenTypes.DOT: case TokenTypes.DOT_DOT: case TokenTypes.AT: case TokenTypes.IDENTIFIER: case TokenTypes.STAR: { steps(); break; } } break; } case TokenTypes.DOUBLE_SLASH: { getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF ); getXPathHandler().endAllNodeStep(); match( TokenTypes.DOUBLE_SLASH ); switch ( LA(1) ) { case TokenTypes.DOT: case TokenTypes.DOT_DOT: case TokenTypes.AT: case TokenTypes.IDENTIFIER: case TokenTypes.STAR: { steps(); break; } default: XPathSyntaxException ex = this.createSyntaxException("Location path cannot end with //"); throw ex; } break; } } getXPathHandler().endAbsoluteLocationPath(); } private void relativeLocationPath() throws SAXPathException { getXPathHandler().startRelativeLocationPath(); switch ( LA(1) ) { case TokenTypes.SLASH: { match( TokenTypes.SLASH ); break; } case TokenTypes.DOUBLE_SLASH: { getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF ); getXPathHandler().endAllNodeStep(); match( TokenTypes.DOUBLE_SLASH ); break; } } steps(); getXPathHandler().endRelativeLocationPath(); } private void steps() throws SAXPathException { switch ( LA(1) ) { case TokenTypes.DOT: case TokenTypes.DOT_DOT: case TokenTypes.AT: case TokenTypes.IDENTIFIER: case TokenTypes.STAR: { step(); break; } case TokenTypes.EOF: { return; } default: { XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" ); throw ex; } } do { if ( ( LA(1) == TokenTypes.SLASH) || ( LA(1) == TokenTypes.DOUBLE_SLASH ) ) { switch ( LA(1) ) { case TokenTypes.SLASH: { match( TokenTypes.SLASH ); break; } case TokenTypes.DOUBLE_SLASH: { getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF ); getXPathHandler().endAllNodeStep(); match( TokenTypes.DOUBLE_SLASH ); break; } } } else { return; } switch ( LA(1) ) { case TokenTypes.DOT: case TokenTypes.DOT_DOT: case TokenTypes.AT: case TokenTypes.IDENTIFIER: case TokenTypes.STAR: { step(); break; } default: { XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" ); throw ex; } } } while ( true ); } void step() throws SAXPathException { int axis = 0; switch ( LA(1) ) { case TokenTypes.DOT: case TokenTypes.DOT_DOT: { abbrStep(); return; } case TokenTypes.AT: { axis = axisSpecifier(); break; } case TokenTypes.IDENTIFIER: { if ( LA(2) == TokenTypes.DOUBLE_COLON ) { axis = axisSpecifier(); } else { axis = Axis.CHILD; } break; } case TokenTypes.STAR: { axis = Axis.CHILD; break; } } nodeTest( axis ); } private int axisSpecifier() throws SAXPathException { int axis = 0; switch ( LA(1) ) { case TokenTypes.AT: { match( TokenTypes.AT ); axis = Axis.ATTRIBUTE; break; } case TokenTypes.IDENTIFIER: { Token token = LT( 1 ); axis = Axis.lookup( token.getTokenText() ); if ( axis == Axis.INVALID_AXIS ) { throwInvalidAxis( token.getTokenText() ); } match( TokenTypes.IDENTIFIER ); match( TokenTypes.DOUBLE_COLON ); break; } } return axis; } private void nodeTest(int axis) throws SAXPathException { switch ( LA(1) ) { case TokenTypes.IDENTIFIER: { switch ( LA(2) ) { case TokenTypes.LEFT_PAREN: { nodeTypeTest( axis ); break; } default: { nameTest( axis ); break; } } break; } case TokenTypes.STAR: { nameTest( axis ); break; } default: XPathSyntaxException ex = createSyntaxException("Expected <QName> or *"); throw ex; } } private void nodeTypeTest(int axis) throws SAXPathException { Token nodeTypeToken = match( TokenTypes.IDENTIFIER ); String nodeType = nodeTypeToken.getTokenText(); match( TokenTypes.LEFT_PAREN ); if ( "processing-instruction".equals( nodeType ) ) { String piName = ""; if ( LA(1) == TokenTypes.LITERAL ) { piName = match( TokenTypes.LITERAL ).getTokenText(); } match( TokenTypes.RIGHT_PAREN ); getXPathHandler().startProcessingInstructionNodeStep( axis, piName ); predicates(); getXPathHandler().endProcessingInstructionNodeStep(); } else if ( "node".equals( nodeType ) ) { match( TokenTypes.RIGHT_PAREN ); getXPathHandler().startAllNodeStep( axis ); predicates(); getXPathHandler().endAllNodeStep(); } else if ( "text".equals( nodeType ) ) { match( TokenTypes.RIGHT_PAREN ); getXPathHandler().startTextNodeStep( axis ); predicates(); getXPathHandler().endTextNodeStep(); } else if ( "comment".equals( nodeType ) ) { match( TokenTypes.RIGHT_PAREN ); getXPathHandler().startCommentNodeStep( axis ); predicates(); getXPathHandler().endCommentNodeStep(); } else { XPathSyntaxException ex = createSyntaxException( "Expected node-type" ); throw ex; } } private void nameTest(int axis) throws SAXPathException { String prefix = null; String localName = null; switch ( LA(2) ) { case TokenTypes.COLON: { switch ( LA(1) ) { case TokenTypes.IDENTIFIER: { prefix = match( TokenTypes.IDENTIFIER ).getTokenText(); match( TokenTypes.COLON ); break; } } break; } } switch ( LA(1) ) { case TokenTypes.IDENTIFIER: { localName = match( TokenTypes.IDENTIFIER ).getTokenText(); break; } case TokenTypes.STAR: { match( TokenTypes.STAR ); localName = "*"; break; } } if ( prefix == null ) { prefix = ""; } getXPathHandler().startNameStep( axis, prefix, localName ); predicates(); getXPathHandler().endNameStep(); } private void abbrStep() throws SAXPathException { switch ( LA(1) ) { case TokenTypes.DOT: { match( TokenTypes.DOT ); getXPathHandler().startAllNodeStep( Axis.SELF ); predicates(); getXPathHandler().endAllNodeStep(); break; } case TokenTypes.DOT_DOT: { match( TokenTypes.DOT_DOT ); getXPathHandler().startAllNodeStep( Axis.PARENT ); predicates(); getXPathHandler().endAllNodeStep(); break; } } } private void predicates() throws SAXPathException { while (true ) { if ( LA(1) == TokenTypes.LEFT_BRACKET ) { predicate(); } else { break; } } } void predicate() throws SAXPathException { getXPathHandler().startPredicate(); match( TokenTypes.LEFT_BRACKET ); predicateExpr(); match( TokenTypes.RIGHT_BRACKET ); getXPathHandler().endPredicate(); } private void predicateExpr() throws SAXPathException { expr(); } private void expr() throws SAXPathException { orExpr(); } private void orExpr() throws SAXPathException { getXPathHandler().startOrExpr(); andExpr(); boolean create = false; switch ( LA(1) ) { case TokenTypes.OR: { create = true; match( TokenTypes.OR ); orExpr(); break; } } getXPathHandler().endOrExpr( create ); } private void andExpr() throws SAXPathException { getXPathHandler().startAndExpr(); equalityExpr(); boolean create = false; switch ( LA(1) ) { case TokenTypes.AND: { create = true; match( TokenTypes.AND ); andExpr(); break; } } getXPathHandler().endAndExpr( create ); } private void equalityExpr() throws SAXPathException { relationalExpr(); int la = LA(1); while (la == TokenTypes.EQUALS || la == TokenTypes.NOT_EQUALS) { switch ( la ) { case TokenTypes.EQUALS: { match( TokenTypes.EQUALS ); getXPathHandler().startEqualityExpr(); relationalExpr(); getXPathHandler().endEqualityExpr( Operator.EQUALS ); break; } case TokenTypes.NOT_EQUALS: { match( TokenTypes.NOT_EQUALS ); getXPathHandler().startEqualityExpr(); relationalExpr(); getXPathHandler().endEqualityExpr( Operator.NOT_EQUALS ); break; } } la = LA(1); } } private void relationalExpr() throws SAXPathException { additiveExpr(); int la = LA(1); // Very important: TokenTypes.LESS_THAN != Operator.LESS_THAN // TokenTypes.GREATER_THAN != Operator.GREATER_THAN // TokenTypes.GREATER_THAN_EQUALS != Operator.GREATER_THAN_EQUALS // TokenTypes.LESS_THAN_EQUALS != Operator.LESS_THAN_EQUALS while (la == TokenTypes.LESS_THAN_SIGN || la == TokenTypes.GREATER_THAN_SIGN || la == TokenTypes.LESS_THAN_OR_EQUALS_SIGN || la == TokenTypes.GREATER_THAN_OR_EQUALS_SIGN ) { switch ( la ) { case TokenTypes.LESS_THAN_SIGN: { match( TokenTypes.LESS_THAN_SIGN ); getXPathHandler().startRelationalExpr(); additiveExpr(); getXPathHandler().endRelationalExpr( Operator.LESS_THAN ); break; } case TokenTypes.GREATER_THAN_SIGN: { match( TokenTypes.GREATER_THAN_SIGN ); getXPathHandler().startRelationalExpr(); additiveExpr(); getXPathHandler().endRelationalExpr( Operator.GREATER_THAN ); break; } case TokenTypes.GREATER_THAN_OR_EQUALS_SIGN: { match( TokenTypes.GREATER_THAN_OR_EQUALS_SIGN ); getXPathHandler().startRelationalExpr(); additiveExpr(); getXPathHandler().endRelationalExpr( Operator.GREATER_THAN_EQUALS ); break; } case TokenTypes.LESS_THAN_OR_EQUALS_SIGN: { match( TokenTypes.LESS_THAN_OR_EQUALS_SIGN ); getXPathHandler().startRelationalExpr(); additiveExpr(); getXPathHandler().endRelationalExpr( Operator.LESS_THAN_EQUALS ); break; } } la = LA(1); } } private void additiveExpr() throws SAXPathException { multiplicativeExpr(); int la = LA(1); while (la == TokenTypes.PLUS || la == TokenTypes.MINUS) { switch ( la ) { case TokenTypes.PLUS: { match( TokenTypes.PLUS ); getXPathHandler().startAdditiveExpr(); multiplicativeExpr(); getXPathHandler().endAdditiveExpr( Operator.ADD ); break; } case TokenTypes.MINUS: { match( TokenTypes.MINUS ); getXPathHandler().startAdditiveExpr(); multiplicativeExpr(); getXPathHandler().endAdditiveExpr( Operator.SUBTRACT ); break; } } la = LA(1); } } private void multiplicativeExpr() throws SAXPathException { unaryExpr(); int la = LA(1); while (la == TokenTypes.STAR || la == TokenTypes.DIV || la == TokenTypes.MOD) { switch ( la ) { case TokenTypes.STAR: { match( TokenTypes.STAR ); getXPathHandler().startMultiplicativeExpr(); unaryExpr(); getXPathHandler().endMultiplicativeExpr( Operator.MULTIPLY ); break; } case TokenTypes.DIV: { match( TokenTypes.DIV ); getXPathHandler().startMultiplicativeExpr(); unaryExpr(); getXPathHandler().endMultiplicativeExpr( Operator.DIV ); break; } case TokenTypes.MOD: { match( TokenTypes.MOD ); getXPathHandler().startMultiplicativeExpr(); unaryExpr(); getXPathHandler().endMultiplicativeExpr( Operator.MOD ); break; } } la = LA(1); } } private void unaryExpr() throws SAXPathException { switch ( LA(1) ) { case TokenTypes.MINUS: { getXPathHandler().startUnaryExpr(); match( TokenTypes.MINUS ); unaryExpr(); getXPathHandler().endUnaryExpr( Operator.NEGATIVE ); break; } default: { unionExpr(); break; } } } private void unionExpr() throws SAXPathException { getXPathHandler().startUnionExpr(); pathExpr(); boolean create = false; switch ( LA(1) ) { case TokenTypes.PIPE: { match( TokenTypes.PIPE ); create = true; expr(); break; } } getXPathHandler().endUnionExpr( create ); } private Token match(int tokenType) throws XPathSyntaxException { LT(1); Token token = (Token) tokens.get( 0 ); if ( token.getTokenType() == tokenType ) { tokens.remove(0); return token; } XPathSyntaxException ex = createSyntaxException( "Expected: " + TokenTypes.getTokenText( tokenType ) ); throw ex; } private int LA(int position) { return LT(position).getTokenType(); } // XXX This method's a HotSpot; could we improve it? private Token LT(int position) { if ( tokens.size() <= ( position - 1 ) ) { for ( int i = 0 ; i < position ; ++i ) { tokens.add( lexer.nextToken() ); } } return (Token) tokens.get( position - 1 ); } private boolean isNodeTypeName(Token name) { String text = name.getTokenText(); if ( "node".equals( text ) || "comment".equals( text ) || "text".equals( text ) || "processing-instruction".equals( text ) ) { return true; } return false; } private XPathSyntaxException createSyntaxException(String message) { String xpath = this.lexer.getXPath(); int position = LT(1).getTokenBegin(); return new XPathSyntaxException( xpath, position, message ); } private void throwInvalidAxis(String invalidAxis) throws SAXPathException { String xpath = this.lexer.getXPath(); int position = LT(1).getTokenBegin(); String message = "Expected valid axis name instead of [" + invalidAxis + "]"; throw new XPathSyntaxException( xpath, position, message ); } }