| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.jasper.compiler; |
| |
| /** |
| * This class implements a parser for EL expressions. |
| * |
| * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into |
| * a ELNode.Nodes. |
| * |
| * Currently, it only handles text outside ${..} and functions in ${ ..}. |
| * |
| * @author Kin-man Chung |
| */ |
| |
| public class ELParser { |
| |
| private Token curToken; // current token |
| private ELNode.Nodes expr; |
| private ELNode.Nodes ELexpr; |
| private int index; // Current index of the expression |
| private String expression; // The EL expression |
| private boolean escapeBS; // is '\' an escape char in text outside EL? |
| |
| private static final String reservedWords[] = { |
| "and", "div", "empty", "eq", "false", |
| "ge", "gt", "instanceof", "le", "lt", "mod", |
| "ne", "not", "null", "or", "true"}; |
| |
| public ELParser(String expression) { |
| index = 0; |
| this.expression = expression; |
| expr = new ELNode.Nodes(); |
| } |
| |
| /** |
| * Parse an EL expression |
| * @param expression The input expression string of the form |
| * Char* ('${' Char* '}')* Char* |
| * @return Parsed EL expression in ELNode.Nodes |
| */ |
| public static ELNode.Nodes parse(String expression) { |
| ELParser parser = new ELParser(expression); |
| while (parser.hasNextChar()) { |
| String text = parser.skipUntilEL(); |
| if (text.length() > 0) { |
| parser.expr.add(new ELNode.Text(text)); |
| } |
| ELNode.Nodes elexpr = parser.parseEL(); |
| if (! elexpr.isEmpty()) { |
| parser.expr.add(new ELNode.Root(elexpr)); |
| } |
| } |
| return parser.expr; |
| } |
| |
| /** |
| * Parse an EL expression string '${...}' |
| *@return An ELNode.Nodes representing the EL expression |
| * TODO: Currently only parsed into functions and text strings. This |
| * should be rewritten for a full parser. |
| */ |
| private ELNode.Nodes parseEL() { |
| |
| StringBuffer buf = new StringBuffer(); |
| ELexpr = new ELNode.Nodes(); |
| while (hasNext()) { |
| curToken = nextToken(); |
| if (curToken instanceof Char) { |
| if (curToken.toChar() == '}') { |
| break; |
| } |
| buf.append(curToken.toChar()); |
| } else { |
| // Output whatever is in buffer |
| if (buf.length() > 0) { |
| ELexpr.add(new ELNode.ELText(buf.toString())); |
| } |
| if (!parseFunction()) { |
| ELexpr.add(new ELNode.ELText(curToken.toString())); |
| } |
| } |
| } |
| if (buf.length() > 0) { |
| ELexpr.add(new ELNode.ELText(buf.toString())); |
| } |
| |
| return ELexpr; |
| } |
| |
| /** |
| * Parse for a function |
| * FunctionInvokation ::= (identifier ':')? identifier '(' |
| * (Expression (,Expression)*)? ')' |
| * Note: currently we don't parse arguments |
| */ |
| private boolean parseFunction() { |
| if (! (curToken instanceof Id) || isELReserved(curToken.toString())) { |
| return false; |
| } |
| String s1 = null; // Function prefix |
| String s2 = curToken.toString(); // Function name |
| int mark = getIndex(); |
| if (hasNext()) { |
| Token t = nextToken(); |
| if (t.toChar() == ':') { |
| if (hasNext()) { |
| Token t2 = nextToken(); |
| if (t2 instanceof Id) { |
| s1 = s2; |
| s2 = t2.toString(); |
| if (hasNext()) { |
| t = nextToken(); |
| } |
| } |
| } |
| } |
| if (t.toChar() == '(') { |
| ELexpr.add(new ELNode.Function(s1, s2)); |
| return true; |
| } |
| } |
| setIndex(mark); |
| return false; |
| } |
| |
| /** |
| * Test if an id is a reserved word in EL |
| */ |
| private boolean isELReserved(String id) { |
| int i = 0; |
| int j = reservedWords.length; |
| while (i < j) { |
| int k = (i+j)/2; |
| int result = reservedWords[k].compareTo(id); |
| if (result == 0) { |
| return true; |
| } |
| if (result < 0) { |
| i = k+1; |
| } else { |
| j = k; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Skip until an EL expression ('${') is reached, allowing escape sequences |
| * '\\' and '\$'. |
| * @return The text string up to the EL expression |
| */ |
| private String skipUntilEL() { |
| char prev = 0; |
| StringBuffer buf = new StringBuffer(); |
| while (hasNextChar()) { |
| char ch = nextChar(); |
| if (prev == '\\') { |
| prev = 0; |
| if (ch == '\\') { |
| buf.append('\\'); |
| if (!escapeBS) |
| prev = '\\'; |
| } else if (ch == '$') { |
| buf.append('$'); |
| } |
| // else error! |
| } else if (prev == '$') { |
| if (ch == '{') { |
| prev = 0; |
| break; |
| } |
| buf.append('$'); |
| buf.append(ch); |
| } else if (ch == '\\' || ch == '$') { |
| prev = ch; |
| } else { |
| buf.append(ch); |
| } |
| } |
| if (prev != 0) { |
| buf.append(prev); |
| } |
| return buf.toString(); |
| } |
| |
| /* |
| * @return true if there is something left in EL expression buffer other |
| * than white spaces. |
| */ |
| private boolean hasNext() { |
| skipSpaces(); |
| return hasNextChar(); |
| } |
| |
| /* |
| * @return The next token in the EL expression buffer. |
| */ |
| private Token nextToken() { |
| skipSpaces(); |
| if (hasNextChar()) { |
| char ch = nextChar(); |
| if (Character.isJavaIdentifierStart(ch)) { |
| StringBuffer buf = new StringBuffer(); |
| buf.append(ch); |
| while ((ch = peekChar()) != -1 && |
| Character.isJavaIdentifierPart(ch)) { |
| buf.append(ch); |
| nextChar(); |
| } |
| return new Id(buf.toString()); |
| } |
| |
| if (ch == '\'' || ch == '"') { |
| return parseQuotedChars(ch); |
| } else { |
| // For now... |
| return new Char(ch); |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * Parse a string in single or double quotes, allowing for escape sequences |
| * '\\', and ('\"', or "\'") |
| */ |
| private Token parseQuotedChars(char quote) { |
| StringBuffer buf = new StringBuffer(); |
| buf.append(quote); |
| while (hasNextChar()) { |
| char ch = nextChar(); |
| if (ch == '\\') { |
| ch = nextChar(); |
| if (ch == '\\' || ch == quote) { |
| buf.append(ch); |
| } |
| // else error! |
| } else if (ch == quote) { |
| buf.append(ch); |
| break; |
| } else { |
| buf.append(ch); |
| } |
| } |
| return new QuotedString(buf.toString()); |
| } |
| |
| /* |
| * A collection of low level parse methods dealing with character in |
| * the EL expression buffer. |
| */ |
| |
| private void skipSpaces() { |
| while (hasNextChar()) { |
| if (expression.charAt(index) > ' ') |
| break; |
| index++; |
| } |
| } |
| |
| private boolean hasNextChar() { |
| return index < expression.length(); |
| } |
| |
| private char nextChar() { |
| if (index >= expression.length()) { |
| return (char)-1; |
| } |
| return expression.charAt(index++); |
| } |
| |
| private char peekChar() { |
| if (index >= expression.length()) { |
| return (char)-1; |
| } |
| return expression.charAt(index); |
| } |
| |
| private int getIndex() { |
| return index; |
| } |
| |
| private void setIndex(int i) { |
| index = i; |
| } |
| |
| /* |
| * Represents a token in EL expression string |
| */ |
| private static class Token { |
| |
| char toChar() { |
| return 0; |
| } |
| |
| public String toString() { |
| return ""; |
| } |
| } |
| |
| /* |
| * Represents an ID token in EL |
| */ |
| private static class Id extends Token { |
| String id; |
| |
| Id(String id) { |
| this.id = id; |
| } |
| |
| public String toString() { |
| return id; |
| } |
| } |
| |
| /* |
| * Represents a character token in EL |
| */ |
| private static class Char extends Token { |
| |
| private char ch; |
| |
| Char(char ch) { |
| this.ch = ch; |
| } |
| |
| char toChar() { |
| return ch; |
| } |
| |
| public String toString() { |
| return (new Character(ch)).toString(); |
| } |
| } |
| |
| /* |
| * Represents a quoted (single or double) string token in EL |
| */ |
| private static class QuotedString extends Token { |
| |
| private String value; |
| |
| QuotedString(String v) { |
| this.value = v; |
| } |
| |
| public String toString() { |
| return value; |
| } |
| } |
| } |
| |