001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.jxpath.ri;
019
020import java.io.StringReader;
021
022import org.apache.commons.jxpath.JXPathInvalidSyntaxException;
023import org.apache.commons.jxpath.ri.parser.ParseException;
024import org.apache.commons.jxpath.ri.parser.TokenMgrError;
025import org.apache.commons.jxpath.ri.parser.XPathParser;
026
027/**
028 * XPath parser.
029 */
030public class Parser {
031
032    private static final XPathParser PARSER = new XPathParser(new StringReader(""));
033
034    /**
035     * Add escapes to the specified String.
036     *
037     * @param string incoming String
038     * @return String
039     */
040    private static String addEscapes(final String string) {
041        // Piggy-back on the code generated by JavaCC
042        return TokenMgrError.addEscapes(string);
043    }
044
045    /**
046     * Describe a parse position.
047     *
048     * @param expression to parse
049     * @param position   parse position
050     * @return String
051     */
052    private static String describePosition(final String expression, final int position) {
053        if (position <= 0) {
054            return "at the beginning of the expression";
055        }
056        if (position >= expression.length()) {
057            return "- expression incomplete";
058        }
059        return "after: '" + addEscapes(expression.substring(0, position)) + "'";
060    }
061
062    /**
063     * Parses the XPath expression. Throws a JXPathException in case of a syntax error.
064     *
065     * @param expression to parse
066     * @param compiler   the compiler
067     * @return parsed Object
068     */
069    public static Object parseExpression(final String expression, final Compiler compiler) {
070        synchronized (PARSER) {
071            PARSER.setCompiler(compiler);
072            Object expr;
073            try {
074                PARSER.ReInit(new StringReader(expression));
075                expr = PARSER.parseExpression();
076            } catch (final TokenMgrError e) {
077                throw new JXPathInvalidSyntaxException("Invalid XPath: '" + addEscapes(expression) + "'. Invalid symbol '"
078                        + addEscapes(String.valueOf(e.getCharacter())) + "' " + describePosition(expression, e.getPosition()));
079            } catch (final ParseException e) {
080                throw new JXPathInvalidSyntaxException(
081                        "Invalid XPath: '" + addEscapes(expression) + "'. Syntax error " + describePosition(expression, e.currentToken.beginColumn));
082            }
083            return expr;
084        }
085    }
086
087    /**
088     * Constructs a new instance.
089     *
090     *@deprecated Will be private in the next major version.
091     */
092    @Deprecated
093    public Parser() {
094        // empty
095    }
096}