001: /*
002: * IF YOU ARE HAVING TROUBLE COMPILING THIS CLASS, IT IS PROBABLY BECAUSE Lexer.java IS MISSING.
003: *
004: * Use 'ant jflex' to generate the file, which will reside in build/java
005: */
006:
007: package com.opensymphony.module.sitemesh.html.tokenizer;
008:
009: import com.opensymphony.module.sitemesh.html.Tag;
010: import com.opensymphony.module.sitemesh.html.Text;
011: import com.opensymphony.module.sitemesh.html.util.CharArray;
012: import com.opensymphony.module.sitemesh.util.CharArrayReader;
013:
014: import java.io.IOException;
015:
016: /**
017: * Looks for patterns of tokens in the Lexer and translates these to calls to pass to the TokenHandler.
018: *
019: * @author Joe Walnes
020: * @see TagTokenizer
021: */
022: public class Parser extends Lexer {
023:
024: private final CharArray attributeBuffer = new CharArray(64);
025: private final ReusableToken reusableToken = new ReusableToken();
026:
027: private int pushbackToken = -1;
028: private String pushbackText;
029:
030: public final static short SLASH = 257;
031: public final static short WHITESPACE = 258;
032: public final static short EQUALS = 259;
033: public final static short QUOTE = 260;
034: public final static short WORD = 261;
035: public final static short TEXT = 262;
036: public final static short QUOTED = 263;
037: public final static short LT = 264;
038: public final static short GT = 265;
039: public final static short LT_OPEN_MAGIC_COMMENT = 266;
040: public final static short LT_CLOSE_MAGIC_COMMENT = 267;
041:
042: private final char[] input;
043:
044: private TokenHandler handler;
045:
046: private int position;
047: private int length;
048:
049: private String name;
050: private int type;
051:
052: public Parser(char[] input, TokenHandler handler) {
053: super (new CharArrayReader(input));
054: this .input = input;
055: this .handler = handler;
056: }
057:
058: private String text() {
059: if (pushbackToken == -1) {
060: return yytext();
061: } else {
062: return pushbackText;
063: }
064: }
065:
066: private void skipWhiteSpace() throws IOException {
067: while (true) {
068: int next;
069: if (pushbackToken == -1) {
070: next = yylex();
071: } else {
072: next = pushbackToken;
073: pushbackToken = -1;
074: }
075: if (next != Parser.WHITESPACE) {
076: pushBack(next);
077: break;
078: }
079: }
080: }
081:
082: private void pushBack(int next) {
083: if (pushbackToken != -1) {
084: reportError("Cannot pushback more than once", line(),
085: column());
086: }
087: pushbackToken = next;
088: if (next == Parser.WORD || next == Parser.QUOTED
089: || next == Parser.SLASH || next == Parser.EQUALS) {
090: pushbackText = yytext();
091: } else {
092: pushbackText = null;
093: }
094: }
095:
096: public void start() {
097: try {
098: while (true) {
099: int token;
100: if (pushbackToken == -1) {
101: token = yylex();
102: } else {
103: token = pushbackToken;
104: pushbackToken = -1;
105: }
106: if (token == 0) {
107: // EOF
108: return;
109: } else if (token == Parser.TEXT) {
110: // Got some text
111: parsedText(position(), length());
112: } else if (token == Parser.LT) {
113: // Token "<" - start of tag
114: parseTag(Tag.OPEN);
115: } else if (token == Parser.LT_OPEN_MAGIC_COMMENT) {
116: // Token "<!--[" - start of open magic comment
117: parseTag(Tag.OPEN_MAGIC_COMMENT);
118: } else if (token == Parser.LT_CLOSE_MAGIC_COMMENT) {
119: // Token "<![" - start of close magic comment
120: parseTag(Tag.CLOSE_MAGIC_COMMENT);
121: } else {
122: reportError(
123: "Unexpected token from lexer, was expecting TEXT or LT",
124: line(), column());
125: }
126: }
127: } catch (IOException e) {
128: throw new RuntimeException(e);
129: }
130: }
131:
132: private void parseTag(int type) throws IOException {
133: // Start parsing a TAG
134:
135: int start = position();
136: skipWhiteSpace();
137: int token;
138: if (pushbackToken == -1) {
139: token = yylex();
140: } else {
141: token = pushbackToken;
142: pushbackToken = -1;
143: }
144:
145: if (token == Parser.SLASH) {
146: // Token "/" - it's a closing tag
147: type = Tag.CLOSE;
148: if (pushbackToken == -1) {
149: token = yylex();
150: } else {
151: token = pushbackToken;
152: pushbackToken = -1;
153: }
154: }
155:
156: if (token == Parser.WORD) {
157: // Token WORD - name of tag
158: String name = text();
159: if (handler.shouldProcessTag(name)) {
160: parseFullTag(type, name, start);
161: } else {
162: resetLexerState();
163: pushBack(yylex()); // take and replace the next token, so the position is correct
164: parsedText(start, position() - start);
165: }
166: } else if (token == Parser.GT) {
167: // Token ">" - an illegal <> or < > tag. Ignore
168: } else if (token == 0) {
169: parsedText(start, position() - start); // eof
170: } else {
171: reportError("Could not recognise tag", line(), column());
172: }
173: }
174:
175: private void parseFullTag(int type, String name, int start)
176: throws IOException {
177: int token;
178: while (true) {
179: skipWhiteSpace();
180: if (pushbackToken == -1) {
181: token = yylex();
182: } else {
183: token = pushbackToken;
184: pushbackToken = -1;
185: }
186: pushBack(token);
187:
188: if (token == Parser.SLASH || token == Parser.GT) {
189: break; // no more attributes here
190: } else if (token == Parser.WORD) {
191: parseAttribute(); // start of an attribute
192: } else if (token == 0) {
193: parsedText(start, position() - start); // eof
194: return;
195: } else {
196: reportError("Illegal tag", line(), column());
197: break;
198: }
199: }
200:
201: if (pushbackToken == -1) {
202: token = yylex();
203: } else {
204: token = pushbackToken;
205: pushbackToken = -1;
206: }
207: if (token == Parser.SLASH) {
208: // Token "/" - it's an empty tag
209: type = Tag.EMPTY;
210: if (pushbackToken == -1) {
211: token = yylex();
212: } else {
213: token = pushbackToken;
214: pushbackToken = -1;
215: }
216: }
217:
218: if (token == Parser.GT) {
219: // Token ">" - YAY! end of tag.. process it!
220: parsedTag(type, name, start, position() - start + 1);
221: } else if (token == 0) {
222: parsedText(start, position() - start); // eof
223: } else {
224: reportError("Expected end of tag", line(), column());
225: parsedTag(type, name, start, position() - start + 1);
226: }
227: }
228:
229: private void parseAttribute() throws IOException {
230: int token;
231: if (pushbackToken == -1) {
232: token = yylex();
233: } else {
234: token = pushbackToken;
235: pushbackToken = -1;
236: }
237: // Token WORD - start of an attribute
238: String attributeName = text();
239: skipWhiteSpace();
240: if (pushbackToken == -1) {
241: token = yylex();
242: } else {
243: token = pushbackToken;
244: pushbackToken = -1;
245: }
246: if (token == Parser.EQUALS) {
247: // Token "=" - the attribute has a value
248: skipWhiteSpace();
249: if (pushbackToken == -1) {
250: token = yylex();
251: } else {
252: token = pushbackToken;
253: pushbackToken = -1;
254: }
255: if (token == Parser.QUOTED) {
256: // token QUOTED - a quoted literal as the attribute value
257: parsedAttribute(attributeName, text(), true);
258: } else if (token == Parser.WORD || token == Parser.SLASH) {
259: // unquoted word
260: attributeBuffer.clear();
261: attributeBuffer.append(text());
262: while (true) {
263: int next;
264: if (pushbackToken == -1) {
265: next = yylex();
266: } else {
267: next = pushbackToken;
268: pushbackToken = -1;
269: }
270: if (next == Parser.WORD || next == Parser.EQUALS
271: || next == Parser.SLASH) {
272: attributeBuffer.append(text());
273: } else {
274: pushBack(next);
275: break;
276: }
277: }
278: parsedAttribute(attributeName, attributeBuffer
279: .toString(), false);
280: } else if (token == Parser.SLASH || token == Parser.GT) {
281: // no more attributes
282: pushBack(token);
283: } else if (token == 0) {
284: return;
285: } else {
286: reportError("Illegal attribute value", line(), column());
287: }
288: } else if (token == Parser.SLASH || token == Parser.GT
289: || token == Parser.WORD) {
290: // it was a value-less HTML style attribute
291: parsedAttribute(attributeName, null, false);
292: pushBack(token);
293: } else if (token == 0) {
294: return;
295: } else {
296: reportError("Illegal attribute name", line(), column());
297: }
298: }
299:
300: protected void parsedText(int position, int length) {
301: this .position = position;
302: this .length = length;
303: handler.text(reusableToken);
304: }
305:
306: protected void parsedTag(int type, String name, int start,
307: int length) {
308: this .type = type;
309: this .name = name;
310: this .position = start;
311: this .length = length;
312: handler.tag(reusableToken);
313: reusableToken.attributeCount = 0;
314: }
315:
316: protected void parsedAttribute(String name, String value,
317: boolean quoted) {
318: if (reusableToken.attributeCount + 2 >= reusableToken.attributes.length) {
319: String[] newAttributes = new String[reusableToken.attributeCount * 2];
320: System.arraycopy(reusableToken.attributes, 0,
321: newAttributes, 0, reusableToken.attributeCount);
322: reusableToken.attributes = newAttributes;
323: }
324: reusableToken.attributes[reusableToken.attributeCount++] = name;
325: if (quoted) {
326: reusableToken.attributes[reusableToken.attributeCount++] = value
327: .substring(1, value.length() - 1);
328: } else {
329: reusableToken.attributes[reusableToken.attributeCount++] = value;
330: }
331: }
332:
333: protected void reportError(String message, int line, int column) {
334: handler.warning(message, line, column);
335: }
336:
337: public class ReusableToken implements Tag, Text {
338:
339: public int attributeCount = 0;
340: public String[] attributes = new String[10]; // name1, value1, name2, value2...
341:
342: public String getName() {
343: return name;
344: }
345:
346: public int getType() {
347: return type;
348: }
349:
350: public String getContents() {
351: return new String(input, position, length);
352: }
353:
354: public void writeTo(CharArray out) {
355: out.append(input, position, length);
356: }
357:
358: public int getAttributeCount() {
359: return attributeCount / 2;
360: }
361:
362: public int getAttributeIndex(String name, boolean caseSensitive) {
363: if (attributeCount == 0)
364: return -1;
365: final int len = attributeCount;
366: for (int i = 0; i < len; i += 2) {
367: final String current = attributes[i];
368: if (caseSensitive ? name.equals(current) : name
369: .equalsIgnoreCase(current)) {
370: return i / 2;
371: }
372: }
373: return -1;
374: }
375:
376: public String getAttributeName(int index) {
377: return attributes[index * 2];
378: }
379:
380: public String getAttributeValue(int index) {
381: return attributes[index * 2 + 1];
382: }
383:
384: public String getAttributeValue(String name,
385: boolean caseSensitive) {
386: if (attributeCount == 0)
387: return null;
388: final int len = attributeCount;
389: for (int i = 0; i < len; i += 2) {
390: final String current = attributes[i];
391: if (caseSensitive ? name.equals(current) : name
392: .equalsIgnoreCase(current)) {
393: return attributes[i + 1];
394: }
395: }
396: return null;
397: }
398:
399: public boolean hasAttribute(String name, boolean caseSensitive) {
400: return getAttributeIndex(name, caseSensitive) > -1;
401: }
402:
403: }
404: }
|