KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > jasper > compiler > Parser


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.jasper.compiler;
18
19 import java.io.CharArrayWriter JavaDoc;
20 import java.io.FileNotFoundException JavaDoc;
21 import java.net.URL JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24
25 import javax.servlet.jsp.tagext.TagAttributeInfo JavaDoc;
26 import javax.servlet.jsp.tagext.TagFileInfo JavaDoc;
27 import javax.servlet.jsp.tagext.TagInfo JavaDoc;
28 import javax.servlet.jsp.tagext.TagLibraryInfo JavaDoc;
29
30 import org.apache.jasper.Constants;
31 import org.apache.jasper.JasperException;
32 import org.apache.jasper.JspCompilationContext;
33 import org.xml.sax.Attributes JavaDoc;
34 import org.xml.sax.helpers.AttributesImpl JavaDoc;
35
36 /**
37  * This class implements a parser for a JSP page (non-xml view). JSP page
38  * grammar is included here for reference. The token '#' that appears in the
39  * production indicates the current input token location in the production.
40  *
41  * @author Kin-man Chung
42  * @author Shawn Bayern
43  * @author Mark Roth
44  */

45
46 class Parser implements TagConstants {
47
48     private ParserController parserController;
49
50     private JspCompilationContext ctxt;
51
52     private JspReader reader;
53
54     private String JavaDoc currentFile;
55
56     private Mark start;
57
58     private ErrorDispatcher err;
59
60     private int scriptlessCount;
61
62     private boolean isTagFile;
63
64     private boolean directivesOnly;
65
66     private URL JavaDoc jarFileUrl;
67
68     private PageInfo pageInfo;
69
70     // Virtual body content types, to make parsing a little easier.
71
// These are not accessible from outside the parser.
72
private static final String JavaDoc JAVAX_BODY_CONTENT_PARAM = "JAVAX_BODY_CONTENT_PARAM";
73
74     private static final String JavaDoc JAVAX_BODY_CONTENT_PLUGIN = "JAVAX_BODY_CONTENT_PLUGIN";
75
76     private static final String JavaDoc JAVAX_BODY_CONTENT_TEMPLATE_TEXT = "JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
77
78     /**
79      * The constructor
80      */

81     private Parser(ParserController pc, JspReader reader, boolean isTagFile,
82             boolean directivesOnly, URL JavaDoc jarFileUrl) {
83         this.parserController = pc;
84         this.ctxt = pc.getJspCompilationContext();
85         this.pageInfo = pc.getCompiler().getPageInfo();
86         this.err = pc.getCompiler().getErrorDispatcher();
87         this.reader = reader;
88         this.currentFile = reader.mark().getFile();
89         this.scriptlessCount = 0;
90         this.isTagFile = isTagFile;
91         this.directivesOnly = directivesOnly;
92         this.jarFileUrl = jarFileUrl;
93         start = reader.mark();
94     }
95
96     /**
97      * The main entry for Parser
98      *
99      * @param pc
100      * The ParseController, use for getting other objects in compiler
101      * and for parsing included pages
102      * @param reader
103      * To read the page
104      * @param parent
105      * The parent node to this page, null for top level page
106      * @return list of nodes representing the parsed page
107      */

108     public static Node.Nodes parse(ParserController pc, JspReader reader,
109             Node parent, boolean isTagFile, boolean directivesOnly,
110             URL JavaDoc jarFileUrl, String JavaDoc pageEnc, String JavaDoc jspConfigPageEnc,
111             boolean isDefaultPageEncoding, boolean isBomPresent) throws JasperException {
112
113         Parser parser = new Parser(pc, reader, isTagFile, directivesOnly,
114                 jarFileUrl);
115
116         Node.Root root = new Node.Root(reader.mark(), parent, false);
117         root.setPageEncoding(pageEnc);
118         root.setJspConfigPageEncoding(jspConfigPageEnc);
119         root.setIsDefaultPageEncoding(isDefaultPageEncoding);
120         root.setIsBomPresent(isBomPresent);
121
122         if (directivesOnly) {
123             parser.parseTagFileDirectives(root);
124             return new Node.Nodes(root);
125         }
126
127         // For the Top level page, add inlcude-prelude and include-coda
128
PageInfo pageInfo = pc.getCompiler().getPageInfo();
129         if (parent == null) {
130             parser.addInclude(root, pageInfo.getIncludePrelude());
131         }
132         while (reader.hasMoreInput()) {
133             parser.parseElements(root);
134         }
135         if (parent == null) {
136             parser.addInclude(root, pageInfo.getIncludeCoda());
137         }
138
139         Node.Nodes page = new Node.Nodes(root);
140         return page;
141     }
142
143     /**
144      * Attributes ::= (S Attribute)* S?
145      */

146     Attributes JavaDoc parseAttributes() throws JasperException {
147         AttributesImpl JavaDoc attrs = new AttributesImpl JavaDoc();
148
149         reader.skipSpaces();
150         while (parseAttribute(attrs))
151             reader.skipSpaces();
152
153         return attrs;
154     }
155
156     /**
157      * Parse Attributes for a reader, provided for external use
158      */

159     public static Attributes JavaDoc parseAttributes(ParserController pc,
160             JspReader reader) throws JasperException {
161         Parser tmpParser = new Parser(pc, reader, false, false, null);
162         return tmpParser.parseAttributes();
163     }
164
165     /**
166      * Attribute ::= Name S? Eq S? ( '"<%=' RTAttributeValueDouble | '"'
167      * AttributeValueDouble | "'<%=" RTAttributeValueSingle | "'"
168      * AttributeValueSingle } Note: JSP and XML spec does not allow while spaces
169      * around Eq. It is added to be backward compatible with Tomcat, and with
170      * other xml parsers.
171      */

172     private boolean parseAttribute(AttributesImpl JavaDoc attrs) throws JasperException {
173
174         // Get the qualified name
175
String JavaDoc qName = parseName();
176         if (qName == null)
177             return false;
178
179         // Determine prefix and local name components
180
String JavaDoc localName = qName;
181         String JavaDoc uri = "";
182         int index = qName.indexOf(':');
183         if (index != -1) {
184             String JavaDoc prefix = qName.substring(0, index);
185             uri = pageInfo.getURI(prefix);
186             if (uri == null) {
187                 err.jspError(reader.mark(),
188                         "jsp.error.attribute.invalidPrefix", prefix);
189             }
190             localName = qName.substring(index + 1);
191         }
192
193         reader.skipSpaces();
194         if (!reader.matches("="))
195             err.jspError(reader.mark(), "jsp.error.attribute.noequal");
196
197         reader.skipSpaces();
198         char quote = (char) reader.nextChar();
199         if (quote != '\'' && quote != '"')
200             err.jspError(reader.mark(), "jsp.error.attribute.noquote");
201
202         String JavaDoc watchString = "";
203         if (reader.matches("<%="))
204             watchString = "%>";
205         watchString = watchString + quote;
206
207         String JavaDoc attrValue = parseAttributeValue(watchString);
208         attrs.addAttribute(uri, localName, qName, "CDATA", attrValue);
209         return true;
210     }
211
212     /**
213      * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*
214      */

215     private String JavaDoc parseName() throws JasperException {
216         char ch = (char) reader.peekChar();
217         if (Character.isLetter(ch) || ch == '_' || ch == ':') {
218             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
219             buf.append(ch);
220             reader.nextChar();
221             ch = (char) reader.peekChar();
222             while (Character.isLetter(ch) || Character.isDigit(ch) || ch == '.'
223                     || ch == '_' || ch == '-' || ch == ':') {
224                 buf.append(ch);
225                 reader.nextChar();
226                 ch = (char) reader.peekChar();
227             }
228             return buf.toString();
229         }
230         return null;
231     }
232
233     /**
234      * AttributeValueDouble ::= (QuotedChar - '"')* ('"' | <TRANSLATION_ERROR>)
235      * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"')
236      * ('%>"' | TRANSLATION_ERROR)
237      */

238     private String JavaDoc parseAttributeValue(String JavaDoc watch) throws JasperException {
239         Mark start = reader.mark();
240         Mark stop = reader.skipUntilIgnoreEsc(watch);
241         if (stop == null) {
242             err.jspError(start, "jsp.error.attribute.unterminated", watch);
243         }
244
245         String JavaDoc ret = parseQuoted(reader.getText(start, stop));
246         if (watch.length() == 1) // quote
247
return ret;
248
249         // putback delimiter '<%=' and '%>', since they are needed if the
250
// attribute does not allow RTexpression.
251
return "<%=" + ret + "%>";
252     }
253
254     /**
255      * QuotedChar ::= '&apos;' | '&quot;' | '\\' | '\"' | "\'" | '\>' | '\$' |
256      * Char
257      */

258     private String JavaDoc parseQuoted(String JavaDoc tx) {
259         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
260         int size = tx.length();
261         int i = 0;
262         while (i < size) {
263             char ch = tx.charAt(i);
264             if (ch == '&') {
265                 if (i + 5 < size && tx.charAt(i + 1) == 'a'
266                         && tx.charAt(i + 2) == 'p' && tx.charAt(i + 3) == 'o'
267                         && tx.charAt(i + 4) == 's' && tx.charAt(i + 5) == ';') {
268                     buf.append('\'');
269                     i += 6;
270                 } else if (i + 5 < size && tx.charAt(i + 1) == 'q'
271                         && tx.charAt(i + 2) == 'u' && tx.charAt(i + 3) == 'o'
272                         && tx.charAt(i + 4) == 't' && tx.charAt(i + 5) == ';') {
273                     buf.append('"');
274                     i += 6;
275                 } else {
276                     buf.append(ch);
277                     ++i;
278                 }
279             } else if (ch == '\\' && i + 1 < size) {
280                 ch = tx.charAt(i + 1);
281                 if (ch == '\\' || ch == '\"' || ch == '\'' || ch == '>') {
282                     buf.append(ch);
283                     i += 2;
284                 } else if (ch == '$') {
285                     // Replace "\$" with some special char. XXX hack!
286
buf.append(Constants.ESC);
287                     i += 2;
288                 } else {
289                     buf.append('\\');
290                     ++i;
291                 }
292             } else {
293                 buf.append(ch);
294                 ++i;
295             }
296         }
297         return buf.toString();
298     }
299
300     private String JavaDoc parseScriptText(String JavaDoc tx) {
301         CharArrayWriter JavaDoc cw = new CharArrayWriter JavaDoc();
302         int size = tx.length();
303         int i = 0;
304         while (i < size) {
305             char ch = tx.charAt(i);
306             if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\'
307                     && tx.charAt(i + 2) == '>') {
308                 cw.write('%');
309                 cw.write('>');
310                 i += 3;
311             } else {
312                 cw.write(ch);
313                 ++i;
314             }
315         }
316         cw.close();
317         return cw.toString();
318     }
319
320     /*
321      * Invokes parserController to parse the included page
322      */

323     private void processIncludeDirective(String JavaDoc file, Node parent)
324             throws JasperException {
325         if (file == null) {
326             return;
327         }
328
329         try {
330             parserController.parse(file, parent, jarFileUrl);
331         } catch (FileNotFoundException JavaDoc ex) {
332             err.jspError(start, "jsp.error.file.not.found", file);
333         } catch (Exception JavaDoc ex) {
334             err.jspError(start, ex.getMessage());
335         }
336     }
337
338     /*
339      * Parses a page directive with the following syntax: PageDirective ::= ( S
340      * Attribute)*
341      */

342     private void parsePageDirective(Node parent) throws JasperException {
343         Attributes JavaDoc attrs = parseAttributes();
344         Node.PageDirective n = new Node.PageDirective(attrs, start, parent);
345
346         /*
347          * A page directive may contain multiple 'import' attributes, each of
348          * which consists of a comma-separated list of package names. Store each
349          * list with the node, where it is parsed.
350          */

351         for (int i = 0; i < attrs.getLength(); i++) {
352             if ("import".equals(attrs.getQName(i))) {
353                 n.addImport(attrs.getValue(i));
354             }
355         }
356     }
357
358     /*
359      * Parses an include directive with the following syntax: IncludeDirective
360      * ::= ( S Attribute)*
361      */

362     private void parseIncludeDirective(Node parent) throws JasperException {
363         Attributes JavaDoc attrs = parseAttributes();
364
365         // Included file expanded here
366
Node includeNode = new Node.IncludeDirective(attrs, start, parent);
367         processIncludeDirective(attrs.getValue("file"), includeNode);
368     }
369
370     /**
371      * Add a list of files. This is used for implementing include-prelude and
372      * include-coda of jsp-config element in web.xml
373      */

374     private void addInclude(Node parent, List JavaDoc files) throws JasperException {
375         if (files != null) {
376             Iterator JavaDoc iter = files.iterator();
377             while (iter.hasNext()) {
378                 String JavaDoc file = (String JavaDoc) iter.next();
379                 AttributesImpl JavaDoc attrs = new AttributesImpl JavaDoc();
380                 attrs.addAttribute("", "file", "file", "CDATA", file);
381
382                 // Create a dummy Include directive node
383
Node includeNode = new Node.IncludeDirective(attrs, reader
384                         .mark(), parent);
385                 processIncludeDirective(file, includeNode);
386             }
387         }
388     }
389
390     /*
391      * Parses a taglib directive with the following syntax: Directive ::= ( S
392      * Attribute)*
393      */

394     private void parseTaglibDirective(Node parent) throws JasperException {
395
396         Attributes JavaDoc attrs = parseAttributes();
397         String JavaDoc uri = attrs.getValue("uri");
398         String JavaDoc prefix = attrs.getValue("prefix");
399         if (prefix != null) {
400             Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix);
401             if (prevMark != null) {
402                 err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl",
403                         prefix, prevMark.getFile(), ""
404                                 + prevMark.getLineNumber());
405             }
406             if (uri != null) {
407                 String JavaDoc uriPrev = pageInfo.getURI(prefix);
408                 if (uriPrev != null && !uriPrev.equals(uri)) {
409                     err.jspError(reader.mark(), "jsp.error.prefix.refined",
410                             prefix, uri, uriPrev);
411                 }
412                 if (pageInfo.getTaglib(uri) == null) {
413                     TagLibraryInfoImpl impl = null;
414                     if (ctxt.getOptions().isCaching()) {
415                         impl = (TagLibraryInfoImpl) ctxt.getOptions()
416                                 .getCache().get(uri);
417                     }
418                     if (impl == null) {
419                         String JavaDoc[] location = ctxt.getTldLocation(uri);
420                         impl = new TagLibraryInfoImpl(ctxt, parserController, pageInfo,
421                                 prefix, uri, location, err);
422                         if (ctxt.getOptions().isCaching()) {
423                             ctxt.getOptions().getCache().put(uri, impl);
424                         }
425                     }
426                     pageInfo.addTaglib(uri, impl);
427                 }
428                 pageInfo.addPrefixMapping(prefix, uri);
429             } else {
430                 String JavaDoc tagdir = attrs.getValue("tagdir");
431                 if (tagdir != null) {
432                     String JavaDoc urnTagdir = URN_JSPTAGDIR + tagdir;
433                     if (pageInfo.getTaglib(urnTagdir) == null) {
434                         pageInfo.addTaglib(urnTagdir,
435                                 new ImplicitTagLibraryInfo(ctxt,
436                                         parserController, pageInfo, prefix, tagdir, err));
437                     }
438                     pageInfo.addPrefixMapping(prefix, urnTagdir);
439                 }
440             }
441         }
442
443         new Node.TaglibDirective(attrs, start, parent);
444     }
445
446     /*
447      * Parses a directive with the following syntax: Directive ::= S? ( 'page'
448      * PageDirective | 'include' IncludeDirective | 'taglib' TagLibDirective) S?
449      * '%>'
450      *
451      * TagDirective ::= S? ('tag' PageDirective | 'include' IncludeDirective |
452      * 'taglib' TagLibDirective) | 'attribute AttributeDirective | 'variable
453      * VariableDirective S? '%>'
454      */

455     private void parseDirective(Node parent) throws JasperException {
456         reader.skipSpaces();
457
458         String JavaDoc directive = null;
459         if (reader.matches("page")) {
460             directive = "&lt;%@ page";
461             if (isTagFile) {
462                 err.jspError(reader.mark(), "jsp.error.directive.istagfile",
463                         directive);
464             }
465             parsePageDirective(parent);
466         } else if (reader.matches("include")) {
467             directive = "&lt;%@ include";
468             parseIncludeDirective(parent);
469         } else if (reader.matches("taglib")) {
470             if (directivesOnly) {
471                 // No need to get the tagLibInfo objects. This alos suppresses
472
// parsing of any tag files used in this tag file.
473
return;
474             }
475             directive = "&lt;%@ taglib";
476             parseTaglibDirective(parent);
477         } else if (reader.matches("tag")) {
478             directive = "&lt;%@ tag";
479             if (!isTagFile) {
480                 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
481                         directive);
482             }
483             parseTagDirective(parent);
484         } else if (reader.matches("attribute")) {
485             directive = "&lt;%@ attribute";
486             if (!isTagFile) {
487                 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
488                         directive);
489             }
490             parseAttributeDirective(parent);
491         } else if (reader.matches("variable")) {
492             directive = "&lt;%@ variable";
493             if (!isTagFile) {
494                 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
495                         directive);
496             }
497             parseVariableDirective(parent);
498         } else {
499             err.jspError(reader.mark(), "jsp.error.invalid.directive");
500         }
501
502         reader.skipSpaces();
503         if (!reader.matches("%>")) {
504             err.jspError(start, "jsp.error.unterminated", directive);
505         }
506     }
507
508     /*
509      * Parses a directive with the following syntax:
510      *
511      * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList S? ( '/>' | (
512      * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
513      * S? ETag ) ) | <TRANSLATION_ERROR>
514      *
515      * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList S? ( '/>' | (
516      * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
517      * S? ETag ) ) | ( 'attribute' AttributeDirectiveAttrList S? ( '/>' | ( '>'
518      * S? ETag ) ) | ( 'variable' VariableDirectiveAttrList S? ( '/>' | ( '>' S?
519      * ETag ) ) ) | <TRANSLATION_ERROR>
520      */

521     private void parseXMLDirective(Node parent) throws JasperException {
522         reader.skipSpaces();
523
524         String JavaDoc eTag = null;
525         if (reader.matches("page")) {
526             eTag = "jsp:directive.page";
527             if (isTagFile) {
528                 err.jspError(reader.mark(), "jsp.error.directive.istagfile",
529                         "&lt;" + eTag);
530             }
531             parsePageDirective(parent);
532         } else if (reader.matches("include")) {
533             eTag = "jsp:directive.include";
534             parseIncludeDirective(parent);
535         } else if (reader.matches("tag")) {
536             eTag = "jsp:directive.tag";
537             if (!isTagFile) {
538                 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
539                         "&lt;" + eTag);
540             }
541             parseTagDirective(parent);
542         } else if (reader.matches("attribute")) {
543             eTag = "jsp:directive.attribute";
544             if (!isTagFile) {
545                 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
546                         "&lt;" + eTag);
547             }
548             parseAttributeDirective(parent);
549         } else if (reader.matches("variable")) {
550             eTag = "jsp:directive.variable";
551             if (!isTagFile) {
552                 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
553                         "&lt;" + eTag);
554             }
555             parseVariableDirective(parent);
556         } else {
557             err.jspError(reader.mark(), "jsp.error.invalid.directive");
558         }
559
560         reader.skipSpaces();
561         if (reader.matches(">")) {
562             reader.skipSpaces();
563             if (!reader.matchesETag(eTag)) {
564                 err.jspError(start, "jsp.error.unterminated", "&lt;" + eTag);
565             }
566         } else if (!reader.matches("/>")) {
567             err.jspError(start, "jsp.error.unterminated", "&lt;" + eTag);
568         }
569     }
570
571     /*
572      * Parses a tag directive with the following syntax: PageDirective ::= ( S
573      * Attribute)*
574      */

575     private void parseTagDirective(Node parent) throws JasperException {
576         Attributes JavaDoc attrs = parseAttributes();
577         Node.TagDirective n = new Node.TagDirective(attrs, start, parent);
578
579         /*
580          * A page directive may contain multiple 'import' attributes, each of
581          * which consists of a comma-separated list of package names. Store each
582          * list with the node, where it is parsed.
583          */

584         for (int i = 0; i < attrs.getLength(); i++) {
585             if ("import".equals(attrs.getQName(i))) {
586                 n.addImport(attrs.getValue(i));
587             }
588         }
589     }
590
591     /*
592      * Parses a attribute directive with the following syntax:
593      * AttributeDirective ::= ( S Attribute)*
594      */

595     private void parseAttributeDirective(Node parent) throws JasperException {
596         Attributes JavaDoc attrs = parseAttributes();
597         Node.AttributeDirective n = new Node.AttributeDirective(attrs, start,
598                 parent);
599     }
600
601     /*
602      * Parses a variable directive with the following syntax: PageDirective ::= (
603      * S Attribute)*
604      */

605     private void parseVariableDirective(Node parent) throws JasperException {
606         Attributes JavaDoc attrs = parseAttributes();
607         Node.VariableDirective n = new Node.VariableDirective(attrs, start,
608                 parent);
609     }
610
611     /*
612      * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>'
613      */

614     private void parseComment(Node parent) throws JasperException {
615         start = reader.mark();
616         Mark stop = reader.skipUntil("--%>");
617         if (stop == null) {
618             err.jspError(start, "jsp.error.unterminated", "&lt;%--");
619         }
620
621         new Node.Comment(reader.getText(start, stop), start, parent);
622     }
623
624     /*
625      * DeclarationBody ::= (Char* - (char* '%>')) '%>'
626      */

627     private void parseDeclaration(Node parent) throws JasperException {
628         start = reader.mark();
629         Mark stop = reader.skipUntil("%>");
630         if (stop == null) {
631             err.jspError(start, "jsp.error.unterminated", "&lt;%!");
632         }
633
634         new Node.Declaration(parseScriptText(reader.getText(start, stop)),
635                 start, parent);
636     }
637
638     /*
639      * XMLDeclarationBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
640      * CDSect?)* ETag | <TRANSLATION_ERROR> CDSect ::= CDStart CData CDEnd
641      * CDStart ::= '<![CDATA[' CData ::= (Char* - (Char* ']]>' Char*)) CDEnd
642      * ::= ']]>'
643      */

644     private void parseXMLDeclaration(Node parent) throws JasperException {
645         reader.skipSpaces();
646         if (!reader.matches("/>")) {
647             if (!reader.matches(">")) {
648                 err.jspError(start, "jsp.error.unterminated",
649                         "&lt;jsp:declaration&gt;");
650             }
651             Mark stop;
652             String JavaDoc text;
653             while (true) {
654                 start = reader.mark();
655                 stop = reader.skipUntil("<");
656                 if (stop == null) {
657                     err.jspError(start, "jsp.error.unterminated",
658                             "&lt;jsp:declaration&gt;");
659                 }
660                 text = parseScriptText(reader.getText(start, stop));
661                 new Node.Declaration(text, start, parent);
662                 if (reader.matches("![CDATA[")) {
663                     start = reader.mark();
664                     stop = reader.skipUntil("]]>");
665                     if (stop == null) {
666                         err.jspError(start, "jsp.error.unterminated", "CDATA");
667                     }
668                     text = parseScriptText(reader.getText(start, stop));
669                     new Node.Declaration(text, start, parent);
670                 } else {
671                     break;
672                 }
673             }
674
675             if (!reader.matchesETagWithoutLessThan("jsp:declaration")) {
676                 err.jspError(start, "jsp.error.unterminated",
677                         "&lt;jsp:declaration&gt;");
678             }
679         }
680     }
681
682     /*
683      * ExpressionBody ::= (Char* - (char* '%>')) '%>'
684      */

685     private void parseExpression(Node parent) throws JasperException {
686         start = reader.mark();
687         Mark stop = reader.skipUntil("%>");
688         if (stop == null) {
689             err.jspError(start, "jsp.error.unterminated", "&lt;%=");
690         }
691
692         new Node.Expression(parseScriptText(reader.getText(start, stop)),
693                 start, parent);
694     }
695
696     /*
697      * XMLExpressionBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
698      * CDSect?)* ETag ) | <TRANSLATION_ERROR>
699      */

700     private void parseXMLExpression(Node parent) throws JasperException {
701         reader.skipSpaces();
702         if (!reader.matches("/>")) {
703             if (!reader.matches(">")) {
704                 err.jspError(start, "jsp.error.unterminated",
705                         "&lt;jsp:expression&gt;");
706             }
707             Mark stop;
708             String JavaDoc text;
709             while (true) {
710                 start = reader.mark();
711                 stop = reader.skipUntil("<");
712                 if (stop == null) {
713                     err.jspError(start, "jsp.error.unterminated",
714                             "&lt;jsp:expression&gt;");
715                 }
716                 text = parseScriptText(reader.getText(start, stop));
717                 new Node.Expression(text, start, parent);
718                 if (reader.matches("![CDATA[")) {
719                     start = reader.mark();
720                     stop = reader.skipUntil("]]>");
721                     if (stop == null) {
722                         err.jspError(start, "jsp.error.unterminated", "CDATA");
723                     }
724                     text = parseScriptText(reader.getText(start, stop));
725                     new Node.Expression(text, start, parent);
726                 } else {
727                     break;
728                 }
729             }
730             if (!reader.matchesETagWithoutLessThan("jsp:expression")) {
731                 err.jspError(start, "jsp.error.unterminated",
732                         "&lt;jsp:expression&gt;");
733             }
734         }
735     }
736
737     /*
738      * ELExpressionBody (following "${" to first unquoted "}") // XXX add formal
739      * production and confirm implementation against it, // once it's decided
740      */

741     private void parseELExpression(Node parent, char type) throws JasperException {
742         start = reader.mark();
743         Mark last = null;
744         boolean singleQuoted = false, doubleQuoted = false;
745         int currentChar;
746         do {
747             // XXX could move this logic to JspReader
748
last = reader.mark(); // XXX somewhat wasteful
749
currentChar = reader.nextChar();
750             if (currentChar == '\\' && (singleQuoted || doubleQuoted)) {
751                 // skip character following '\' within quotes
752
reader.nextChar();
753                 currentChar = reader.nextChar();
754             }
755             if (currentChar == -1)
756                 err.jspError(start, "jsp.error.unterminated", type + "{");
757             if (currentChar == '"')
758                 doubleQuoted = !doubleQuoted;
759             if (currentChar == '\'')
760                 singleQuoted = !singleQuoted;
761         } while (currentChar != '}' || (singleQuoted || doubleQuoted));
762
763         new Node.ELExpression(type, reader.getText(start, last), start, parent);
764     }
765
766     /*
767      * ScriptletBody ::= (Char* - (char* '%>')) '%>'
768      */

769     private void parseScriptlet(Node parent) throws JasperException {
770         start = reader.mark();
771         Mark stop = reader.skipUntil("%>");
772         if (stop == null) {
773             err.jspError(start, "jsp.error.unterminated", "&lt;%");
774         }
775
776         new Node.Scriptlet(parseScriptText(reader.getText(start, stop)), start,
777                 parent);
778     }
779
780     /*
781      * XMLScriptletBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
782      * CDSect?)* ETag ) | <TRANSLATION_ERROR>
783      */

784     private void parseXMLScriptlet(Node parent) throws JasperException {
785         reader.skipSpaces();
786         if (!reader.matches("/>")) {
787             if (!reader.matches(">")) {
788                 err.jspError(start, "jsp.error.unterminated",
789                         "&lt;jsp:scriptlet&gt;");
790             }
791             Mark stop;
792             String JavaDoc text;
793             while (true) {
794                 start = reader.mark();
795                 stop = reader.skipUntil("<");
796                 if (stop == null) {
797                     err.jspError(start, "jsp.error.unterminated",
798                             "&lt;jsp:scriptlet&gt;");
799                 }
800                 text = parseScriptText(reader.getText(start, stop));
801                 new Node.Scriptlet(text, start, parent);
802                 if (reader.matches("![CDATA[")) {
803                     start = reader.mark();
804                     stop = reader.skipUntil("]]>");
805                     if (stop == null) {
806                         err.jspError(start, "jsp.error.unterminated", "CDATA");
807                     }
808                     text = parseScriptText(reader.getText(start, stop));
809                     new Node.Scriptlet(text, start, parent);
810                 } else {
811                     break;
812                 }
813             }
814
815             if (!reader.matchesETagWithoutLessThan("jsp:scriptlet")) {
816                 err.jspError(start, "jsp.error.unterminated",
817                         "&lt;jsp:scriptlet&gt;");
818             }
819         }
820     }
821
822     /**
823      * Param ::= '<jsp:param' S Attributes S? EmptyBody S?
824      */

825     private void parseParam(Node parent) throws JasperException {
826         if (!reader.matches("<jsp:param")) {
827             err.jspError(reader.mark(), "jsp.error.paramexpected");
828         }
829         Attributes JavaDoc attrs = parseAttributes();
830         reader.skipSpaces();
831
832         Node paramActionNode = new Node.ParamAction(attrs, start, parent);
833
834         parseEmptyBody(paramActionNode, "jsp:param");
835
836         reader.skipSpaces();
837     }
838
839     /*
840      * For Include: StdActionContent ::= Attributes ParamBody
841      *
842      * ParamBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body'
843      * (JspBodyParam | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? Param* ETag )
844      *
845      * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
846      * NamedAttributes ETag )
847      *
848      * JspBodyParam ::= S? '>' Param* '</jsp:body>'
849      */

850     private void parseInclude(Node parent) throws JasperException {
851         Attributes JavaDoc attrs = parseAttributes();
852         reader.skipSpaces();
853
854         Node includeNode = new Node.IncludeAction(attrs, start, parent);
855
856         parseOptionalBody(includeNode, "jsp:include", JAVAX_BODY_CONTENT_PARAM);
857     }
858
859     /*
860      * For Forward: StdActionContent ::= Attributes ParamBody
861      */

862     private void parseForward(Node parent) throws JasperException {
863         Attributes JavaDoc attrs = parseAttributes();
864         reader.skipSpaces();
865
866         Node forwardNode = new Node.ForwardAction(attrs, start, parent);
867
868         parseOptionalBody(forwardNode, "jsp:forward", JAVAX_BODY_CONTENT_PARAM);
869     }
870
871     private void parseInvoke(Node parent) throws JasperException {
872         Attributes JavaDoc attrs = parseAttributes();
873         reader.skipSpaces();
874
875         Node invokeNode = new Node.InvokeAction(attrs, start, parent);
876
877         parseEmptyBody(invokeNode, "jsp:invoke");
878     }
879
880     private void parseDoBody(Node parent) throws JasperException {
881         Attributes JavaDoc attrs = parseAttributes();
882         reader.skipSpaces();
883
884         Node doBodyNode = new Node.DoBodyAction(attrs, start, parent);
885
886         parseEmptyBody(doBodyNode, "jsp:doBody");
887     }
888
889     private void parseElement(Node parent) throws JasperException {
890         Attributes JavaDoc attrs = parseAttributes();
891         reader.skipSpaces();
892
893         Node elementNode = new Node.JspElement(attrs, start, parent);
894
895         parseOptionalBody(elementNode, "jsp:element", TagInfo.BODY_CONTENT_JSP);
896     }
897
898     /*
899      * For GetProperty: StdActionContent ::= Attributes EmptyBody
900      */

901     private void parseGetProperty(Node parent) throws JasperException {
902         Attributes JavaDoc attrs = parseAttributes();
903         reader.skipSpaces();
904
905         Node getPropertyNode = new Node.GetProperty(attrs, start, parent);
906
907         parseOptionalBody(getPropertyNode, "jsp:getProperty",
908                 TagInfo.BODY_CONTENT_EMPTY);
909     }
910
911     /*
912      * For SetProperty: StdActionContent ::= Attributes EmptyBody
913      */

914     private void parseSetProperty(Node parent) throws JasperException {
915         Attributes JavaDoc attrs = parseAttributes();
916         reader.skipSpaces();
917
918         Node setPropertyNode = new Node.SetProperty(attrs, start, parent);
919
920         parseOptionalBody(setPropertyNode, "jsp:setProperty",
921                 TagInfo.BODY_CONTENT_EMPTY);
922     }
923
924     /*
925      * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
926      * NamedAttributes ETag )
927      */

928     private void parseEmptyBody(Node parent, String JavaDoc tag) throws JasperException {
929         if (reader.matches("/>")) {
930             // Done
931
} else if (reader.matches(">")) {
932             if (reader.matchesETag(tag)) {
933                 // Done
934
} else if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
935                 // Parse the one or more named attribute nodes
936
parseNamedAttributes(parent);
937                 if (!reader.matchesETag(tag)) {
938                     // Body not allowed
939
err.jspError(reader.mark(),
940                             "jsp.error.jspbody.emptybody.only", "&lt;" + tag);
941                 }
942             } else {
943                 err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only",
944                         "&lt;" + tag);
945             }
946         } else {
947             err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;" + tag);
948         }
949     }
950
951     /*
952      * For UseBean: StdActionContent ::= Attributes OptionalBody
953      */

954     private void parseUseBean(Node parent) throws JasperException {
955         Attributes JavaDoc attrs = parseAttributes();
956         reader.skipSpaces();
957
958         Node useBeanNode = new Node.UseBean(attrs, start, parent);
959
960         parseOptionalBody(useBeanNode, "jsp:useBean", TagInfo.BODY_CONTENT_JSP);
961     }
962
963     /*
964      * Parses OptionalBody, but also reused to parse bodies for plugin and param
965      * since the syntax is identical (the only thing that differs substantially
966      * is how to process the body, and thus we accept the body type as a
967      * parameter).
968      *
969      * OptionalBody ::= EmptyBody | ActionBody
970      *
971      * ScriptlessOptionalBody ::= EmptyBody | ScriptlessActionBody
972      *
973      * TagDependentOptionalBody ::= EmptyBody | TagDependentActionBody
974      *
975      * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
976      * NamedAttributes ETag )
977      *
978      * ActionBody ::= JspAttributeAndBody | ( '>' Body ETag )
979      *
980      * ScriptlessActionBody ::= JspAttributeAndBody | ( '>' ScriptlessBody ETag )
981      *
982      * TagDependentActionBody ::= JspAttributeAndBody | ( '>' TagDependentBody
983      * ETag )
984      *
985      */

986     private void parseOptionalBody(Node parent, String JavaDoc tag, String JavaDoc bodyType)
987             throws JasperException {
988         if (reader.matches("/>")) {
989             // EmptyBody
990
return;
991         }
992
993         if (!reader.matches(">")) {
994             err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;" + tag);
995         }
996
997         if (reader.matchesETag(tag)) {
998             // EmptyBody
999
return;
1000        }
1001
1002        if (!parseJspAttributeAndBody(parent, tag, bodyType)) {
1003            // Must be ( '>' # Body ETag )
1004
parseBody(parent, tag, bodyType);
1005        }
1006    }
1007
1008    /**
1009     * Attempts to parse 'JspAttributeAndBody' production. Returns true if it
1010     * matched, or false if not. Assumes EmptyBody is okay as well.
1011     *
1012     * JspAttributeAndBody ::= ( '>' # S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
1013     * JspBodyBody | <TRANSLATION_ERROR> ) S? ETag )
1014     */

1015    private boolean parseJspAttributeAndBody(Node parent, String JavaDoc tag,
1016            String JavaDoc bodyType) throws JasperException {
1017        boolean result = false;
1018
1019        if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
1020            // May be an EmptyBody, depending on whether
1021
// There's a "<jsp:body" before the ETag
1022

1023            // First, parse <jsp:attribute> elements:
1024
parseNamedAttributes(parent);
1025
1026            result = true;
1027        }
1028
1029        if (reader.matchesOptionalSpacesFollowedBy("<jsp:body")) {
1030            // ActionBody
1031
parseJspBody(parent, bodyType);
1032            reader.skipSpaces();
1033            if (!reader.matchesETag(tag)) {
1034                err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;"
1035                        + tag);
1036            }
1037
1038            result = true;
1039        } else if (result && !reader.matchesETag(tag)) {
1040            // If we have <jsp:attribute> but something other than
1041
// <jsp:body> or the end tag, translation error.
1042
err.jspError(reader.mark(), "jsp.error.jspbody.required", "&lt;"
1043                    + tag);
1044        }
1045
1046        return result;
1047    }
1048
1049    /*
1050     * Params ::= `>' S? ( ( `<jsp:body>' ( ( S? Param+ S? `</jsp:body>' ) |
1051     * <TRANSLATION_ERROR> ) ) | Param+ ) '</jsp:params>'
1052     */

1053    private void parseJspParams(Node parent) throws JasperException {
1054        Node jspParamsNode = new Node.ParamsAction(start, parent);
1055        parseOptionalBody(jspParamsNode, "jsp:params", JAVAX_BODY_CONTENT_PARAM);
1056    }
1057
1058    /*
1059     * Fallback ::= '/>' | ( `>' S? `<jsp:body>' ( ( S? ( Char* - ( Char* `</jsp:body>' ) ) `</jsp:body>'
1060     * S? ) | <TRANSLATION_ERROR> ) `</jsp:fallback>' ) | ( '>' ( Char* - (
1061     * Char* '</jsp:fallback>' ) ) '</jsp:fallback>' )
1062     */

1063    private void parseFallBack(Node parent) throws JasperException {
1064        Node fallBackNode = new Node.FallBackAction(start, parent);
1065        parseOptionalBody(fallBackNode, "jsp:fallback",
1066                JAVAX_BODY_CONTENT_TEMPLATE_TEXT);
1067    }
1068
1069    /*
1070     * For Plugin: StdActionContent ::= Attributes PluginBody
1071     *
1072     * PluginBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
1073     * JspBodyPluginTags | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? PluginTags
1074     * ETag )
1075     *
1076     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
1077     * NamedAttributes ETag )
1078     *
1079     */

1080    private void parsePlugin(Node parent) throws JasperException {
1081        Attributes JavaDoc attrs = parseAttributes();
1082        reader.skipSpaces();
1083
1084        Node pluginNode = new Node.PlugIn(attrs, start, parent);
1085
1086        parseOptionalBody(pluginNode, "jsp:plugin", JAVAX_BODY_CONTENT_PLUGIN);
1087    }
1088
1089    /*
1090     * PluginTags ::= ( '<jsp:params' Params S? )? ( '<jsp:fallback' Fallback?
1091     * S? )?
1092     */

1093    private void parsePluginTags(Node parent) throws JasperException {
1094        reader.skipSpaces();
1095
1096        if (reader.matches("<jsp:params")) {
1097            parseJspParams(parent);
1098            reader.skipSpaces();
1099        }
1100
1101        if (reader.matches("<jsp:fallback")) {
1102            parseFallBack(parent);
1103            reader.skipSpaces();
1104        }
1105    }
1106
1107    /*
1108     * StandardAction ::= 'include' StdActionContent | 'forward'
1109     * StdActionContent | 'invoke' StdActionContent | 'doBody' StdActionContent |
1110     * 'getProperty' StdActionContent | 'setProperty' StdActionContent |
1111     * 'useBean' StdActionContent | 'plugin' StdActionContent | 'element'
1112     * StdActionContent
1113     */

1114    private void parseStandardAction(Node parent) throws JasperException {
1115        Mark start = reader.mark();
1116
1117        if (reader.matches(INCLUDE_ACTION)) {
1118            parseInclude(parent);
1119        } else if (reader.matches(FORWARD_ACTION)) {
1120            parseForward(parent);
1121        } else if (reader.matches(INVOKE_ACTION)) {
1122            if (!isTagFile) {
1123                err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
1124                        "&lt;jsp:invoke");
1125            }
1126            parseInvoke(parent);
1127        } else if (reader.matches(DOBODY_ACTION)) {
1128            if (!isTagFile) {
1129                err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
1130                        "&lt;jsp:doBody");
1131            }
1132            parseDoBody(parent);
1133        } else if (reader.matches(GET_PROPERTY_ACTION)) {
1134            parseGetProperty(parent);
1135        } else if (reader.matches(SET_PROPERTY_ACTION)) {
1136            parseSetProperty(parent);
1137        } else if (reader.matches(USE_BEAN_ACTION)) {
1138            parseUseBean(parent);
1139        } else if (reader.matches(PLUGIN_ACTION)) {
1140            parsePlugin(parent);
1141        } else if (reader.matches(ELEMENT_ACTION)) {
1142            parseElement(parent);
1143        } else if (reader.matches(ATTRIBUTE_ACTION)) {
1144            err.jspError(start, "jsp.error.namedAttribute.invalidUse");
1145        } else if (reader.matches(BODY_ACTION)) {
1146            err.jspError(start, "jsp.error.jspbody.invalidUse");
1147        } else if (reader.matches(FALLBACK_ACTION)) {
1148            err.jspError(start, "jsp.error.fallback.invalidUse");
1149        } else if (reader.matches(PARAMS_ACTION)) {
1150            err.jspError(start, "jsp.error.params.invalidUse");
1151        } else if (reader.matches(PARAM_ACTION)) {
1152            err.jspError(start, "jsp.error.param.invalidUse");
1153        } else if (reader.matches(OUTPUT_ACTION)) {
1154            err.jspError(start, "jsp.error.jspoutput.invalidUse");
1155        } else {
1156            err.jspError(start, "jsp.error.badStandardAction");
1157        }
1158    }
1159
1160    /*
1161     * # '<' CustomAction CustomActionBody
1162     *
1163     * CustomAction ::= TagPrefix ':' CustomActionName
1164     *
1165     * TagPrefix ::= Name
1166     *
1167     * CustomActionName ::= Name
1168     *
1169     * CustomActionBody ::= ( Attributes CustomActionEnd ) | <TRANSLATION_ERROR>
1170     *
1171     * Attributes ::= ( S Attribute )* S?
1172     *
1173     * CustomActionEnd ::= CustomActionTagDependent | CustomActionJSPContent |
1174     * CustomActionScriptlessContent
1175     *
1176     * CustomActionTagDependent ::= TagDependentOptionalBody
1177     *
1178     * CustomActionJSPContent ::= OptionalBody
1179     *
1180     * CustomActionScriptlessContent ::= ScriptlessOptionalBody
1181     */

1182    private boolean parseCustomTag(Node parent) throws JasperException {
1183
1184        if (reader.peekChar() != '<') {
1185            return false;
1186        }
1187
1188        // Parse 'CustomAction' production (tag prefix and custom action name)
1189
reader.nextChar(); // skip '<'
1190
String JavaDoc tagName = reader.parseToken(false);
1191        int i = tagName.indexOf(':');
1192        if (i == -1) {
1193            reader.reset(start);
1194            return false;
1195        }
1196
1197        String JavaDoc prefix = tagName.substring(0, i);
1198        String JavaDoc shortTagName = tagName.substring(i + 1);
1199
1200        // Check if this is a user-defined tag.
1201
String JavaDoc uri = pageInfo.getURI(prefix);
1202        if (uri == null) {
1203            reader.reset(start);
1204            // Remember the prefix for later error checking
1205
pageInfo.putNonCustomTagPrefix(prefix, reader.mark());
1206            return false;
1207        }
1208
1209        TagLibraryInfo JavaDoc tagLibInfo = pageInfo.getTaglib(uri);
1210        TagInfo JavaDoc tagInfo = tagLibInfo.getTag(shortTagName);
1211        TagFileInfo JavaDoc tagFileInfo = tagLibInfo.getTagFile(shortTagName);
1212        if (tagInfo == null && tagFileInfo == null) {
1213            err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix);
1214        }
1215        Class JavaDoc tagHandlerClass = null;
1216        if (tagInfo != null) {
1217            // Must be a classic tag, load it here.
1218
// tag files will be loaded later, in TagFileProcessor
1219
String JavaDoc handlerClassName = tagInfo.getTagClassName();
1220            try {
1221                tagHandlerClass = ctxt.getClassLoader().loadClass(
1222                        handlerClassName);
1223            } catch (Exception JavaDoc e) {
1224                err.jspError(start, "jsp.error.loadclass.taghandler",
1225                        handlerClassName, tagName);
1226            }
1227        }
1228
1229        // Parse 'CustomActionBody' production:
1230
// At this point we are committed - if anything fails, we produce
1231
// a translation error.
1232

1233        // Parse 'Attributes' production:
1234
Attributes JavaDoc attrs = parseAttributes();
1235        reader.skipSpaces();
1236
1237        // Parse 'CustomActionEnd' production:
1238
if (reader.matches("/>")) {
1239            if (tagInfo != null) {
1240                new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
1241                        start, parent, tagInfo, tagHandlerClass);
1242            } else {
1243                new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
1244                        start, parent, tagFileInfo);
1245            }
1246            return true;
1247        }
1248
1249        // Now we parse one of 'CustomActionTagDependent',
1250
// 'CustomActionJSPContent', or 'CustomActionScriptlessContent'.
1251
// depending on body-content in TLD.
1252

1253        // Looking for a body, it still can be empty; but if there is a
1254
// a tag body, its syntax would be dependent on the type of
1255
// body content declared in the TLD.
1256
String JavaDoc bc;
1257        if (tagInfo != null) {
1258            bc = tagInfo.getBodyContent();
1259        } else {
1260            bc = tagFileInfo.getTagInfo().getBodyContent();
1261        }
1262
1263        Node tagNode = null;
1264        if (tagInfo != null) {
1265            tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
1266                    attrs, start, parent, tagInfo, tagHandlerClass);
1267        } else {
1268            tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
1269                    attrs, start, parent, tagFileInfo);
1270        }
1271
1272        parseOptionalBody(tagNode, tagName, bc);
1273
1274        return true;
1275    }
1276
1277    /*
1278     * Parse for a template text string until '<' or "${" or "#{" is encountered,
1279     * recognizing escape sequences "\%", "\$", and "\#".
1280     */

1281    private void parseTemplateText(Node parent) throws JasperException {
1282
1283        if (!reader.hasMoreInput())
1284            return;
1285
1286        CharArrayWriter JavaDoc ttext = new CharArrayWriter JavaDoc();
1287        // Output the first character
1288
int ch = reader.nextChar();
1289        if (ch == '\\') {
1290            reader.pushChar();
1291        } else {
1292            ttext.write(ch);
1293        }
1294
1295        while (reader.hasMoreInput()) {
1296            ch = reader.nextChar();
1297            if (ch == '<') {
1298                reader.pushChar();
1299                break;
1300            } else if (ch == '$' || ch == '#') {
1301                if (!reader.hasMoreInput()) {
1302                    ttext.write(ch);
1303                    break;
1304                }
1305                if (reader.nextChar() == '{') {
1306                    reader.pushChar();
1307                    reader.pushChar();
1308                    break;
1309                }
1310                ttext.write(ch);
1311                reader.pushChar();
1312                continue;
1313            } else if (ch == '\\') {
1314                if (!reader.hasMoreInput()) {
1315                    ttext.write('\\');
1316                    break;
1317                }
1318                char next = (char) reader.peekChar();
1319                // Looking for \% or \$ or \#
1320
// TODO: only recognize \$ or \# if isELIgnored is false, but since
1321
// it can be set in a page directive, it cannot be determined
1322
// here. Argh! (which is the way it should be since we shouldn't
1323
// convolude multiple steps at once and create confusing parsers...)
1324
if (next == '%' || next == '$' || next == '#') {
1325                    ch = reader.nextChar();
1326                }
1327            }
1328            ttext.write(ch);
1329        }
1330        new Node.TemplateText(ttext.toString(), start, parent);
1331    }
1332
1333    /*
1334     * XMLTemplateText ::= ( S? '/>' ) | ( S? '>' ( ( Char* - ( Char* ( '<' |
1335     * '${' ) ) ) ( '${' ELExpressionBody )? CDSect? )* ETag ) |
1336     * <TRANSLATION_ERROR>
1337     */

1338    private void parseXMLTemplateText(Node parent) throws JasperException {
1339        reader.skipSpaces();
1340        if (!reader.matches("/>")) {
1341            if (!reader.matches(">")) {
1342                err.jspError(start, "jsp.error.unterminated",
1343                        "&lt;jsp:text&gt;");
1344            }
1345            CharArrayWriter JavaDoc ttext = new CharArrayWriter JavaDoc();
1346            while (reader.hasMoreInput()) {
1347                int ch = reader.nextChar();
1348                if (ch == '<') {
1349                    // Check for <![CDATA[
1350
if (!reader.matches("![CDATA[")) {
1351                        break;
1352                    }
1353                    start = reader.mark();
1354                    Mark stop = reader.skipUntil("]]>");
1355                    if (stop == null) {
1356                        err.jspError(start, "jsp.error.unterminated", "CDATA");
1357                    }
1358                    String JavaDoc text = reader.getText(start, stop);
1359                    ttext.write(text, 0, text.length());
1360                } else if (ch == '\\') {
1361                    if (!reader.hasMoreInput()) {
1362                        ttext.write('\\');
1363                        break;
1364                    }
1365                    ch = reader.nextChar();
1366                    if (ch != '$' && ch != '#') {
1367                        ttext.write('\\');
1368                    }
1369                    ttext.write(ch);
1370                } else if (ch == '$' || ch == '#') {
1371                    if (!reader.hasMoreInput()) {
1372                        ttext.write(ch);
1373                        break;
1374                    }
1375                    if (reader.nextChar() != '{') {
1376                        ttext.write(ch);
1377                        reader.pushChar();
1378                        continue;
1379                    }
1380                    // Create a template text node
1381
new Node.TemplateText(ttext.toString(), start, parent);
1382
1383                    // Mark and parse the EL expression and create its node:
1384
start = reader.mark();
1385                    parseELExpression(parent, (char) ch);
1386
1387                    start = reader.mark();
1388                    ttext = new CharArrayWriter JavaDoc();
1389                } else {
1390                    ttext.write(ch);
1391                }
1392            }
1393
1394            new Node.TemplateText(ttext.toString(), start, parent);
1395
1396            if (!reader.hasMoreInput()) {
1397                err.jspError(start, "jsp.error.unterminated",
1398                        "&lt;jsp:text&gt;");
1399            } else if (!reader.matchesETagWithoutLessThan("jsp:text")) {
1400                err.jspError(start, "jsp.error.jsptext.badcontent");
1401            }
1402        }
1403    }
1404
1405    /*
1406     * AllBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
1407     * XMLDirectiveBody ) | ( '<%!' DeclarationBody ) | ( '<jsp:declaration'
1408     * XMLDeclarationBody ) | ( '<%=' ExpressionBody ) | ( '<jsp:expression'
1409     * XMLExpressionBody ) | ( '${' ELExpressionBody ) | ( '<%' ScriptletBody ) | ( '<jsp:scriptlet'
1410     * XMLScriptletBody ) | ( '<jsp:text' XMLTemplateText ) | ( '<jsp:'
1411     * StandardAction ) | ( '<' CustomAction CustomActionBody ) | TemplateText
1412     */

1413    private void parseElements(Node parent) throws JasperException {
1414        if (scriptlessCount > 0) {
1415            // vc: ScriptlessBody
1416
// We must follow the ScriptlessBody production if one of
1417
// our parents is ScriptlessBody.
1418
parseElementsScriptless(parent);
1419            return;
1420        }
1421
1422        start = reader.mark();
1423        if (reader.matches("<%--")) {
1424            parseComment(parent);
1425        } else if (reader.matches("<%@")) {
1426            parseDirective(parent);
1427        } else if (reader.matches("<jsp:directive.")) {
1428            parseXMLDirective(parent);
1429        } else if (reader.matches("<%!")) {
1430            parseDeclaration(parent);
1431        } else if (reader.matches("<jsp:declaration")) {
1432            parseXMLDeclaration(parent);
1433        } else if (reader.matches("<%=")) {
1434            parseExpression(parent);
1435        } else if (reader.matches("<jsp:expression")) {
1436            parseXMLExpression(parent);
1437        } else if (reader.matches("<%")) {
1438            parseScriptlet(parent);
1439        } else if (reader.matches("<jsp:scriptlet")) {
1440            parseXMLScriptlet(parent);
1441        } else if (reader.matches("<jsp:text")) {
1442            parseXMLTemplateText(parent);
1443        } else if (reader.matches("${")) {
1444            parseELExpression(parent, '$');
1445        } else if (reader.matches("#{")) {
1446            parseELExpression(parent, '#');
1447        } else if (reader.matches("<jsp:")) {
1448            parseStandardAction(parent);
1449        } else if (!parseCustomTag(parent)) {
1450            checkUnbalancedEndTag();
1451            parseTemplateText(parent);
1452        }
1453    }
1454
1455    /*
1456     * ScriptlessBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
1457     * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
1458     * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
1459     * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
1460     * <TRANSLATION_ERROR> ) | ( '<jsp:text' XMLTemplateText ) | ( '${'
1461     * ELExpressionBody ) | ( '<jsp:' StandardAction ) | ( '<' CustomAction
1462     * CustomActionBody ) | TemplateText
1463     */

1464    private void parseElementsScriptless(Node parent) throws JasperException {
1465        // Keep track of how many scriptless nodes we've encountered
1466
// so we know whether our child nodes are forced scriptless
1467
scriptlessCount++;
1468
1469        start = reader.mark();
1470        if (reader.matches("<%--")) {
1471            parseComment(parent);
1472        } else if (reader.matches("<%@")) {
1473            parseDirective(parent);
1474        } else if (reader.matches("<jsp:directive.")) {
1475            parseXMLDirective(parent);
1476        } else if (reader.matches("<%!")) {
1477            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1478        } else if (reader.matches("<jsp:declaration")) {
1479            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1480        } else if (reader.matches("<%=")) {
1481            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1482        } else if (reader.matches("<jsp:expression")) {
1483            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1484        } else if (reader.matches("<%")) {
1485            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1486        } else if (reader.matches("<jsp:scriptlet")) {
1487            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1488        } else if (reader.matches("<jsp:text")) {
1489            parseXMLTemplateText(parent);
1490        } else if (reader.matches("${")) {
1491            parseELExpression(parent, '$');
1492        } else if (reader.matches("#{")) {
1493            parseELExpression(parent, '#');
1494        } else if (reader.matches("<jsp:")) {
1495            parseStandardAction(parent);
1496        } else if (!parseCustomTag(parent)) {
1497            checkUnbalancedEndTag();
1498            parseTemplateText(parent);
1499        }
1500
1501        scriptlessCount--;
1502    }
1503
1504    /*
1505     * TemplateTextBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
1506     * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
1507     * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
1508     * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
1509     * <TRANSLATION_ERROR> ) | ( '<jsp:text' <TRANSLATION_ERROR> ) | ( '${'
1510     * <TRANSLATION_ERROR> ) | ( '<jsp:' <TRANSLATION_ERROR> ) | TemplateText
1511     */

1512    private void parseElementsTemplateText(Node parent) throws JasperException {
1513        start = reader.mark();
1514        if (reader.matches("<%--")) {
1515            parseComment(parent);
1516        } else if (reader.matches("<%@")) {
1517            parseDirective(parent);
1518        } else if (reader.matches("<jsp:directive.")) {
1519            parseXMLDirective(parent);
1520        } else if (reader.matches("<%!")) {
1521            err.jspError(reader.mark(), "jsp.error.not.in.template",
1522                    "Declarations");
1523        } else if (reader.matches("<jsp:declaration")) {
1524            err.jspError(reader.mark(), "jsp.error.not.in.template",
1525                    "Declarations");
1526        } else if (reader.matches("<%=")) {
1527            err.jspError(reader.mark(), "jsp.error.not.in.template",
1528                    "Expressions");
1529        } else if (reader.matches("<jsp:expression")) {
1530            err.jspError(reader.mark(), "jsp.error.not.in.template",
1531                    "Expressions");
1532        } else if (reader.matches("<%")) {
1533            err.jspError(reader.mark(), "jsp.error.not.in.template",
1534                    "Scriptlets");
1535        } else if (reader.matches("<jsp:scriptlet")) {
1536            err.jspError(reader.mark(), "jsp.error.not.in.template",
1537                    "Scriptlets");
1538        } else if (reader.matches("<jsp:text")) {
1539            err.jspError(reader.mark(), "jsp.error.not.in.template",
1540                    "&lt;jsp:text");
1541        } else if (reader.matches("${")) {
1542            err.jspError(reader.mark(), "jsp.error.not.in.template",
1543                    "Expression language");
1544        } else if (reader.matches("#{")) {
1545            err.jspError(reader.mark(), "jsp.error.not.in.template",
1546                    "Expression language");
1547        } else if (reader.matches("<jsp:")) {
1548            err.jspError(reader.mark(), "jsp.error.not.in.template",
1549                    "Standard actions");
1550        } else if (parseCustomTag(parent)) {
1551            err.jspError(reader.mark(), "jsp.error.not.in.template",
1552                    "Custom actions");
1553        } else {
1554            checkUnbalancedEndTag();
1555            parseTemplateText(parent);
1556        }
1557    }
1558
1559    /*
1560     * Flag as error if an unbalanced end tag appears by itself.
1561     */

1562    private void checkUnbalancedEndTag() throws JasperException {
1563
1564        if (!reader.matches("</")) {
1565            return;
1566        }
1567
1568        // Check for unbalanced standard actions
1569
if (reader.matches("jsp:")) {
1570            err.jspError(start, "jsp.error.unbalanced.endtag", "jsp:");
1571        }
1572
1573        // Check for unbalanced custom actions
1574
String JavaDoc tagName = reader.parseToken(false);
1575        int i = tagName.indexOf(':');
1576        if (i == -1 || pageInfo.getURI(tagName.substring(0, i)) == null) {
1577            reader.reset(start);
1578            return;
1579        }
1580
1581        err.jspError(start, "jsp.error.unbalanced.endtag", tagName);
1582    }
1583
1584    /**
1585     * TagDependentBody :=
1586     */

1587    private void parseTagDependentBody(Node parent, String JavaDoc tag)
1588            throws JasperException {
1589        Mark bodyStart = reader.mark();
1590        Mark bodyEnd = reader.skipUntilETag(tag);
1591        if (bodyEnd == null) {
1592            err.jspError(start, "jsp.error.unterminated", "&lt;" + tag);
1593        }
1594        new Node.TemplateText(reader.getText(bodyStart, bodyEnd), bodyStart,
1595                parent);
1596    }
1597
1598    /*
1599     * Parses jsp:body action.
1600     */

1601    private void parseJspBody(Node parent, String JavaDoc bodyType)
1602            throws JasperException {
1603        Mark start = reader.mark();
1604        Node bodyNode = new Node.JspBody(start, parent);
1605
1606        reader.skipSpaces();
1607        if (!reader.matches("/>")) {
1608            if (!reader.matches(">")) {
1609                err.jspError(start, "jsp.error.unterminated", "&lt;jsp:body");
1610            }
1611            parseBody(bodyNode, "jsp:body", bodyType);
1612        }
1613    }
1614
1615    /*
1616     * Parse the body as JSP content. @param tag The name of the tag whose end
1617     * tag would terminate the body @param bodyType One of the TagInfo body
1618     * types
1619     */

1620    private void parseBody(Node parent, String JavaDoc tag, String JavaDoc bodyType)
1621            throws JasperException {
1622        if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)) {
1623            parseTagDependentBody(parent, tag);
1624        } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)) {
1625            if (!reader.matchesETag(tag)) {
1626                err.jspError(start, "jasper.error.emptybodycontent.nonempty",
1627                        tag);
1628            }
1629        } else if (bodyType == JAVAX_BODY_CONTENT_PLUGIN) {
1630            // (note the == since we won't recognize JAVAX_*
1631
// from outside this module).
1632
parsePluginTags(parent);
1633            if (!reader.matchesETag(tag)) {
1634                err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;"
1635                        + tag);
1636            }
1637        } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)
1638                || bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)
1639                || (bodyType == JAVAX_BODY_CONTENT_PARAM)
1640                || (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT)) {
1641            while (reader.hasMoreInput()) {
1642                if (reader.matchesETag(tag)) {
1643                    return;
1644                }
1645
1646                // Check for nested jsp:body or jsp:attribute
1647
if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) {
1648                    if (reader.matches("<jsp:attribute")) {
1649                        err.jspError(reader.mark(),
1650                                "jsp.error.nested.jspattribute");
1651                    } else if (reader.matches("<jsp:body")) {
1652                        err.jspError(reader.mark(), "jsp.error.nested.jspbody");
1653                    }
1654                }
1655
1656                if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) {
1657                    parseElements(parent);
1658                } else if (bodyType
1659                        .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
1660                    parseElementsScriptless(parent);
1661                } else if (bodyType == JAVAX_BODY_CONTENT_PARAM) {
1662                    // (note the == since we won't recognize JAVAX_*
1663
// from outside this module).
1664
reader.skipSpaces();
1665                    parseParam(parent);
1666                } else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) {
1667                    parseElementsTemplateText(parent);
1668                }
1669            }
1670            err.jspError(start, "jsp.error.unterminated", "&lt;" + tag);
1671        } else {
1672            err.jspError(start, "jasper.error.bad.bodycontent.type");
1673        }
1674    }
1675
1676    /*
1677     * Parses named attributes.
1678     */

1679    private void parseNamedAttributes(Node parent) throws JasperException {
1680        do {
1681            Mark start = reader.mark();
1682            Attributes JavaDoc attrs = parseAttributes();
1683            Node.NamedAttribute namedAttributeNode = new Node.NamedAttribute(
1684                    attrs, start, parent);
1685
1686            reader.skipSpaces();
1687            if (!reader.matches("/>")) {
1688                if (!reader.matches(">")) {
1689                    err.jspError(start, "jsp.error.unterminated",
1690                            "&lt;jsp:attribute");
1691                }
1692                if (namedAttributeNode.isTrim()) {
1693                    reader.skipSpaces();
1694                }
1695                parseBody(namedAttributeNode, "jsp:attribute",
1696                        getAttributeBodyType(parent, attrs.getValue("name")));
1697                if (namedAttributeNode.isTrim()) {
1698                    Node.Nodes subElems = namedAttributeNode.getBody();
1699                    if (subElems != null) {
1700                        Node lastNode = subElems.getNode(subElems.size() - 1);
1701                        if (lastNode instanceof Node.TemplateText) {
1702                            ((Node.TemplateText) lastNode).rtrim();
1703                        }
1704                    }
1705                }
1706            }
1707            reader.skipSpaces();
1708        } while (reader.matches("<jsp:attribute"));
1709    }
1710
1711    /**
1712     * Determine the body type of <jsp:attribute> from the enclosing node
1713     */

1714    private String JavaDoc getAttributeBodyType(Node n, String JavaDoc name) {
1715
1716        if (n instanceof Node.CustomTag) {
1717            TagInfo JavaDoc tagInfo = ((Node.CustomTag) n).getTagInfo();
1718            TagAttributeInfo JavaDoc[] tldAttrs = tagInfo.getAttributes();
1719            for (int i = 0; i < tldAttrs.length; i++) {
1720                if (name.equals(tldAttrs[i].getName())) {
1721                    if (tldAttrs[i].isFragment()) {
1722                        return TagInfo.BODY_CONTENT_SCRIPTLESS;
1723                    }
1724                    if (tldAttrs[i].canBeRequestTime()) {
1725                        return TagInfo.BODY_CONTENT_JSP;
1726                    }
1727                }
1728            }
1729            if (tagInfo.hasDynamicAttributes()) {
1730                return TagInfo.BODY_CONTENT_JSP;
1731            }
1732        } else if (n instanceof Node.IncludeAction) {
1733            if ("page".equals(name)) {
1734                return TagInfo.BODY_CONTENT_JSP;
1735            }
1736        } else if (n instanceof Node.ForwardAction) {
1737            if ("page".equals(name)) {
1738                return TagInfo.BODY_CONTENT_JSP;
1739            }
1740        } else if (n instanceof Node.SetProperty) {
1741            if ("value".equals(name)) {
1742                return TagInfo.BODY_CONTENT_JSP;
1743            }
1744        } else if (n instanceof Node.UseBean) {
1745            if ("beanName".equals(name)) {
1746                return TagInfo.BODY_CONTENT_JSP;
1747            }
1748        } else if (n instanceof Node.PlugIn) {
1749            if ("width".equals(name) || "height".equals(name)) {
1750                return TagInfo.BODY_CONTENT_JSP;
1751            }
1752        } else if (n instanceof Node.ParamAction) {
1753            if ("value".equals(name)) {
1754                return TagInfo.BODY_CONTENT_JSP;
1755            }
1756        } else if (n instanceof Node.JspElement) {
1757            return TagInfo.BODY_CONTENT_JSP;
1758        }
1759
1760        return JAVAX_BODY_CONTENT_TEMPLATE_TEXT;
1761    }
1762
1763    private void parseTagFileDirectives(Node parent) throws JasperException {
1764        reader.setSingleFile(true);
1765        reader.skipUntil("<");
1766        while (reader.hasMoreInput()) {
1767            start = reader.mark();
1768            if (reader.matches("%--")) {
1769                parseComment(parent);
1770            } else if (reader.matches("%@")) {
1771                parseDirective(parent);
1772            } else if (reader.matches("jsp:directive.")) {
1773                parseXMLDirective(parent);
1774            }
1775            reader.skipUntil("<");
1776        }
1777    }
1778}
1779
Popular Tags