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