1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.jasper.compiler;
19
20 /***
21 * This class implements a parser for EL expressions.
22 * <p/>
23 * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into
24 * a ELNode.Nodes.
25 * <p/>
26 * Currently, it only handles text outside ${..} and functions in ${ ..}.
27 *
28 * @author Kin-man Chung
29 */
30
31 public class ELParser {
32
33 private Token curToken;
34 private ELNode.Nodes expr;
35 private ELNode.Nodes ELexpr;
36 private int index;
37 private String expression;
38 private boolean escapeBS;
39
40 private static final String reservedWords[] = {
41 "and", "div", "empty", "eq", "false",
42 "ge", "gt", "instanceof", "le", "lt", "mod",
43 "ne", "not", "null", "or", "true"};
44
45 public ELParser(String expression) {
46 index = 0;
47 this.expression = expression;
48 expr = new ELNode.Nodes();
49 }
50
51 /***
52 * Parse an EL expression
53 *
54 * @param expression The input expression string of the form
55 * Char* ('${' Char* '}')* Char*
56 * @return Parsed EL expression in ELNode.Nodes
57 */
58 public static ELNode.Nodes parse(String expression) {
59 ELParser parser = new ELParser(expression);
60 while (parser.hasNextChar()) {
61 String text = parser.skipUntilEL();
62 if (text.length() > 0) {
63 parser.expr.add(new ELNode.Text(text));
64 }
65 ELNode.Nodes elexpr = parser.parseEL();
66 if (!elexpr.isEmpty()) {
67 parser.expr.add(new ELNode.Root(elexpr));
68 }
69 }
70 return parser.expr;
71 }
72
73 /***
74 * Parse an EL expression string '${...}'
75 *
76 * @return An ELNode.Nodes representing the EL expression
77 * TODO: Currently only parsed into functions and text strings. This
78 * should be rewritten for a full parser.
79 */
80 private ELNode.Nodes parseEL() {
81
82 StringBuffer buf = new StringBuffer();
83 ELexpr = new ELNode.Nodes();
84 while (hasNext()) {
85 curToken = nextToken();
86 if (curToken instanceof Char) {
87 if (curToken.toChar() == '}') {
88 break;
89 }
90 buf.append(curToken.toChar());
91 } else {
92
93 if (buf.length() > 0) {
94 ELexpr.add(new ELNode.ELText(buf.toString()));
95 }
96 if (!parseFunction()) {
97 ELexpr.add(new ELNode.ELText(curToken.toString()));
98 }
99 }
100 }
101 if (buf.length() > 0) {
102 ELexpr.add(new ELNode.ELText(buf.toString()));
103 }
104
105 return ELexpr;
106 }
107
108 /***
109 * Parse for a function
110 * FunctionInvokation ::= (identifier ':')? identifier '('
111 * (Expression (,Expression)*)? ')'
112 * Note: currently we don't parse arguments
113 */
114 private boolean parseFunction() {
115 if (!(curToken instanceof Id) || isELReserved(curToken.toString())) {
116 return false;
117 }
118 String s1 = null;
119 String s2 = curToken.toString();
120 int mark = getIndex();
121 if (hasNext()) {
122 Token t = nextToken();
123 if (t.toChar() == ':') {
124 if (hasNext()) {
125 Token t2 = nextToken();
126 if (t2 instanceof Id) {
127 s1 = s2;
128 s2 = t2.toString();
129 if (hasNext()) {
130 t = nextToken();
131 }
132 }
133 }
134 }
135 if (t.toChar() == '(') {
136 ELexpr.add(new ELNode.Function(s1, s2));
137 return true;
138 }
139 }
140 setIndex(mark);
141 return false;
142 }
143
144 /***
145 * Test if an id is a reserved word in EL
146 */
147 private boolean isELReserved(String id) {
148 int i = 0;
149 int j = reservedWords.length;
150 while (i < j) {
151 int k = (i + j) / 2;
152 int result = reservedWords[k].compareTo(id);
153 if (result == 0) {
154 return true;
155 }
156 if (result < 0) {
157 i = k + 1;
158 } else {
159 j = k;
160 }
161 }
162 return false;
163 }
164
165 /***
166 * Skip until an EL expression ('${') is reached, allowing escape sequences
167 * '//' and '\$'.
168 *
169 * @return The text string up to the EL expression
170 */
171 private String skipUntilEL() {
172 char prev = 0;
173 StringBuffer buf = new StringBuffer();
174 while (hasNextChar()) {
175 char ch = nextChar();
176 if (prev == '//') {
177 prev = 0;
178 if (ch == '//') {
179 buf.append('//');
180 if (!escapeBS)
181 prev = '//';
182 } else if (ch == '$') {
183 buf.append('$');
184 }
185
186 } else if (prev == '$') {
187 if (ch == '{') {
188 prev = 0;
189 break;
190 }
191 buf.append('$');
192 buf.append(ch);
193 prev = 0;
194 } else if (ch == '//' || ch == '$') {
195 prev = ch;
196 } else {
197 buf.append(ch);
198 }
199 }
200 if (prev != 0) {
201 buf.append(prev);
202 }
203 return buf.toString();
204 }
205
206
207
208
209
210 private boolean hasNext() {
211 skipSpaces();
212 return hasNextChar();
213 }
214
215
216
217
218 private Token nextToken() {
219 skipSpaces();
220 if (hasNextChar()) {
221 char ch = nextChar();
222 if (Character.isJavaIdentifierStart(ch)) {
223 StringBuffer buf = new StringBuffer();
224 buf.append(ch);
225 while ((ch = peekChar()) != -1 &&
226 Character.isJavaIdentifierPart(ch)) {
227 buf.append(ch);
228 nextChar();
229 }
230 return new Id(buf.toString());
231 }
232
233 if (ch == '\'' || ch == '"') {
234 return parseQuotedChars(ch);
235 } else {
236
237 return new Char(ch);
238 }
239 }
240 return null;
241 }
242
243
244
245
246
247 private Token parseQuotedChars(char quote) {
248 StringBuffer buf = new StringBuffer();
249 buf.append(quote);
250 while (hasNextChar()) {
251 char ch = nextChar();
252 if (ch == '//') {
253 ch = nextChar();
254 if (ch == '//' || ch == quote) {
255 buf.append(ch);
256 }
257
258 } else if (ch == quote) {
259 buf.append(ch);
260 break;
261 } else {
262 buf.append(ch);
263 }
264 }
265 return new QuotedString(buf.toString());
266 }
267
268
269
270
271
272
273 private void skipSpaces() {
274 while (hasNextChar()) {
275 if (expression.charAt(index) > ' ')
276 break;
277 index++;
278 }
279 }
280
281 private boolean hasNextChar() {
282 return index < expression.length();
283 }
284
285 private char nextChar() {
286 if (index >= expression.length()) {
287 return (char) -1;
288 }
289 return expression.charAt(index++);
290 }
291
292 private char peekChar() {
293 if (index >= expression.length()) {
294 return (char) -1;
295 }
296 return expression.charAt(index);
297 }
298
299 private int getIndex() {
300 return index;
301 }
302
303 private void setIndex(int i) {
304 index = i;
305 }
306
307
308
309
310 private static class Token {
311
312 char toChar() {
313 return 0;
314 }
315
316 public String toString() {
317 return "";
318 }
319 }
320
321
322
323
324 private static class Id extends Token {
325 String id;
326
327 Id(String id) {
328 this.id = id;
329 }
330
331 public String toString() {
332 return id;
333 }
334 }
335
336
337
338
339 private static class Char extends Token {
340
341 private char ch;
342
343 Char(char ch) {
344 this.ch = ch;
345 }
346
347 char toChar() {
348 return ch;
349 }
350
351 public String toString() {
352 return (new Character(ch)).toString();
353 }
354 }
355
356
357
358
359 private static class QuotedString extends Token {
360
361 private String value;
362
363 QuotedString(String v) {
364 this.value = v;
365 }
366
367 public String toString() {
368 return value;
369 }
370 }
371 }
372