001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.jasper.compiler;
018:
019: /**
020: * This class implements a parser for EL expressions.
021: *
022: * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into
023: * a ELNode.Nodes.
024: *
025: * Currently, it only handles text outside ${..} and functions in ${ ..}.
026: *
027: * @author Kin-man Chung
028: */
029:
030: public class ELParser {
031:
032: private Token curToken; // current token
033: private ELNode.Nodes expr;
034: private ELNode.Nodes ELexpr;
035: private int index; // Current index of the expression
036: private String expression; // The EL expression
037: private boolean escapeBS; // is '\' an escape char in text outside EL?
038:
039: private static final String reservedWords[] = { "and", "div",
040: "empty", "eq", "false", "ge", "gt", "instanceof", "le",
041: "lt", "mod", "ne", "not", "null", "or", "true" };
042:
043: public ELParser(String expression) {
044: index = 0;
045: this .expression = expression;
046: expr = new ELNode.Nodes();
047: }
048:
049: /**
050: * Parse an EL expression
051: * @param expression The input expression string of the form
052: * Char* ('${' Char* '}')* Char*
053: * @return Parsed EL expression in ELNode.Nodes
054: */
055: public static ELNode.Nodes parse(String expression) {
056: ELParser parser = new ELParser(expression);
057: while (parser.hasNextChar()) {
058: String text = parser.skipUntilEL();
059: if (text.length() > 0) {
060: parser.expr.add(new ELNode.Text(text));
061: }
062: ELNode.Nodes elexpr = parser.parseEL();
063: if (!elexpr.isEmpty()) {
064: parser.expr.add(new ELNode.Root(elexpr));
065: }
066: }
067: return parser.expr;
068: }
069:
070: /**
071: * Parse an EL expression string '${...}'
072: *@return An ELNode.Nodes representing the EL expression
073: * TODO: Currently only parsed into functions and text strings. This
074: * should be rewritten for a full parser.
075: */
076: private ELNode.Nodes parseEL() {
077:
078: StringBuffer buf = new StringBuffer();
079: ELexpr = new ELNode.Nodes();
080: while (hasNext()) {
081: curToken = nextToken();
082: if (curToken instanceof Char) {
083: if (curToken.toChar() == '}') {
084: break;
085: }
086: buf.append(curToken.toChar());
087: } else {
088: // Output whatever is in buffer
089: if (buf.length() > 0) {
090: ELexpr.add(new ELNode.ELText(buf.toString()));
091: }
092: if (!parseFunction()) {
093: ELexpr.add(new ELNode.ELText(curToken.toString()));
094: }
095: }
096: }
097: if (buf.length() > 0) {
098: ELexpr.add(new ELNode.ELText(buf.toString()));
099: }
100:
101: return ELexpr;
102: }
103:
104: /**
105: * Parse for a function
106: * FunctionInvokation ::= (identifier ':')? identifier '('
107: * (Expression (,Expression)*)? ')'
108: * Note: currently we don't parse arguments
109: */
110: private boolean parseFunction() {
111: if (!(curToken instanceof Id)
112: || isELReserved(curToken.toString())) {
113: return false;
114: }
115: String s1 = null; // Function prefix
116: String s2 = curToken.toString(); // Function name
117: int mark = getIndex();
118: if (hasNext()) {
119: Token t = nextToken();
120: if (t.toChar() == ':') {
121: if (hasNext()) {
122: Token t2 = nextToken();
123: if (t2 instanceof Id) {
124: s1 = s2;
125: s2 = t2.toString();
126: if (hasNext()) {
127: t = nextToken();
128: }
129: }
130: }
131: }
132: if (t.toChar() == '(') {
133: ELexpr.add(new ELNode.Function(s1, s2));
134: return true;
135: }
136: }
137: setIndex(mark);
138: return false;
139: }
140:
141: /**
142: * Test if an id is a reserved word in EL
143: */
144: private boolean isELReserved(String id) {
145: int i = 0;
146: int j = reservedWords.length;
147: while (i < j) {
148: int k = (i + j) / 2;
149: int result = reservedWords[k].compareTo(id);
150: if (result == 0) {
151: return true;
152: }
153: if (result < 0) {
154: i = k + 1;
155: } else {
156: j = k;
157: }
158: }
159: return false;
160: }
161:
162: /**
163: * Skip until an EL expression ('${') is reached, allowing escape sequences
164: * '\\' and '\$'.
165: * @return The text string up to the EL expression
166: */
167: private String skipUntilEL() {
168: char prev = 0;
169: StringBuffer buf = new StringBuffer();
170: while (hasNextChar()) {
171: char ch = nextChar();
172: if (prev == '\\') {
173: prev = 0;
174: if (ch == '\\') {
175: buf.append('\\');
176: if (!escapeBS)
177: prev = '\\';
178: } else if (ch == '$') {
179: buf.append('$');
180: }
181: // else error!
182: } else if (prev == '$') {
183: if (ch == '{') {
184: prev = 0;
185: break;
186: }
187: buf.append('$');
188: buf.append(ch);
189: } else if (ch == '\\' || ch == '$') {
190: prev = ch;
191: } else {
192: buf.append(ch);
193: }
194: }
195: if (prev != 0) {
196: buf.append(prev);
197: }
198: return buf.toString();
199: }
200:
201: /*
202: * @return true if there is something left in EL expression buffer other
203: * than white spaces.
204: */
205: private boolean hasNext() {
206: skipSpaces();
207: return hasNextChar();
208: }
209:
210: /*
211: * @return The next token in the EL expression buffer.
212: */
213: private Token nextToken() {
214: skipSpaces();
215: if (hasNextChar()) {
216: char ch = nextChar();
217: if (Character.isJavaIdentifierStart(ch)) {
218: StringBuffer buf = new StringBuffer();
219: buf.append(ch);
220: while ((ch = peekChar()) != -1
221: && Character.isJavaIdentifierPart(ch)) {
222: buf.append(ch);
223: nextChar();
224: }
225: return new Id(buf.toString());
226: }
227:
228: if (ch == '\'' || ch == '"') {
229: return parseQuotedChars(ch);
230: } else {
231: // For now...
232: return new Char(ch);
233: }
234: }
235: return null;
236: }
237:
238: /*
239: * Parse a string in single or double quotes, allowing for escape sequences
240: * '\\', and ('\"', or "\'")
241: */
242: private Token parseQuotedChars(char quote) {
243: StringBuffer buf = new StringBuffer();
244: buf.append(quote);
245: while (hasNextChar()) {
246: char ch = nextChar();
247: if (ch == '\\') {
248: ch = nextChar();
249: if (ch == '\\' || ch == quote) {
250: buf.append(ch);
251: }
252: // else error!
253: } else if (ch == quote) {
254: buf.append(ch);
255: break;
256: } else {
257: buf.append(ch);
258: }
259: }
260: return new QuotedString(buf.toString());
261: }
262:
263: /*
264: * A collection of low level parse methods dealing with character in
265: * the EL expression buffer.
266: */
267:
268: private void skipSpaces() {
269: while (hasNextChar()) {
270: if (expression.charAt(index) > ' ')
271: break;
272: index++;
273: }
274: }
275:
276: private boolean hasNextChar() {
277: return index < expression.length();
278: }
279:
280: private char nextChar() {
281: if (index >= expression.length()) {
282: return (char) -1;
283: }
284: return expression.charAt(index++);
285: }
286:
287: private char peekChar() {
288: if (index >= expression.length()) {
289: return (char) -1;
290: }
291: return expression.charAt(index);
292: }
293:
294: private int getIndex() {
295: return index;
296: }
297:
298: private void setIndex(int i) {
299: index = i;
300: }
301:
302: /*
303: * Represents a token in EL expression string
304: */
305: private static class Token {
306:
307: char toChar() {
308: return 0;
309: }
310:
311: public String toString() {
312: return "";
313: }
314: }
315:
316: /*
317: * Represents an ID token in EL
318: */
319: private static class Id extends Token {
320: String id;
321:
322: Id(String id) {
323: this .id = id;
324: }
325:
326: public String toString() {
327: return id;
328: }
329: }
330:
331: /*
332: * Represents a character token in EL
333: */
334: private static class Char extends Token {
335:
336: private char ch;
337:
338: Char(char ch) {
339: this .ch = ch;
340: }
341:
342: char toChar() {
343: return ch;
344: }
345:
346: public String toString() {
347: return (new Character(ch)).toString();
348: }
349: }
350:
351: /*
352: * Represents a quoted (single or double) string token in EL
353: */
354: private static class QuotedString extends Token {
355:
356: private String value;
357:
358: QuotedString(String v) {
359: this .value = v;
360: }
361:
362: public String toString() {
363: return value;
364: }
365: }
366: }
|