Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
    * contributor license agreements.  See the NOTICE file distributed with
    * this work for additional information regarding copyright ownership.
    * The ASF licenses this file to You under the Apache License, Version 2.0
    * (the "License"); you may not use this file except in compliance with
    * the License.  You may obtain a copy of the License at
    * 
    *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.jasper.compiler;
  
  import static org.jboss.web.JasperMessages.MESSAGES;
  
  import java.net.URL;
  import java.util.List;
  
  
This class implements a parser for a JSP page (non-xml view). JSP page grammar is included here for reference. The token '#' that appears in the production indicates the current input token location in the production.

Author(s):
Kin-man Chung
Shawn Bayern
Mark Roth
  
  
  class Parser implements TagConstants {
  
      private ParserController parserController;
  
      private JspCompilationContext ctxt;
  
      private JspReader reader;
  
      private String currentFile;
  
      private Mark start;
  
      private ErrorDispatcher err;
  
      private int scriptlessCount;
  
      private boolean isTagFile;
  
      private boolean directivesOnly;
  
      private URL jarFileUrl;
  
      private PageInfo pageInfo;
  
      // Virtual body content types, to make parsing a little easier.
      // These are not accessible from outside the parser.
      private static final String JAVAX_BODY_CONTENT_PARAM = "JAVAX_BODY_CONTENT_PARAM";
  
      private static final String JAVAX_BODY_CONTENT_PLUGIN = "JAVAX_BODY_CONTENT_PLUGIN";
  
      private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT = "JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
  
      private static final boolean OPTIMIZE_SCRIPTLETS = Boolean.valueOf(
              System.getProperty("org.apache.jasper.compiler.Parser.OPTIMIZE_SCRIPTLETS",
                      "false")).booleanValue();

    
The constructor
  
      private Parser(ParserController pcJspReader readerboolean isTagFile,
              boolean directivesOnlyURL jarFileUrl) {
          this. = pc;
          this. = pc.getJspCompilationContext();
          this. = pc.getCompiler().getPageInfo();
          this. = pc.getCompiler().getErrorDispatcher();
          this. = reader;
          this. = reader.mark().getFile();
          this. = 0;
          this. = isTagFile;
          this. = directivesOnly;
          this. = jarFileUrl;
           = reader.mark();
     }

    
The main entry for Parser

Parameters:
pc The ParseController, use for getting other objects in compiler and for parsing included pages
reader To read the page
parent The parent node to this page, null for top level page
Returns:
list of nodes representing the parsed page
 
     public static Node.Nodes parse(ParserController pcJspReader reader,
             Node parentboolean isTagFileboolean directivesOnly,
             URL jarFileUrlString pageEncString jspConfigPageEnc,
             boolean isDefaultPageEncodingboolean isBomPresentthrows JasperException {
 
         Parser parser = new Parser(pcreaderisTagFiledirectivesOnly,
                 jarFileUrl);
 
         Node.Root root = new Node.Root(reader.mark(), parentfalse);
         root.setPageEncoding(pageEnc);
         root.setJspConfigPageEncoding(jspConfigPageEnc);
         root.setIsDefaultPageEncoding(isDefaultPageEncoding);
         root.setIsBomPresent(isBomPresent);
 
         // For the Top level page, add include-prelude and include-coda
         PageInfo pageInfo = pc.getCompiler().getPageInfo();
         if (parent == null && !isTagFile) {
             parser.addInclude(rootpageInfo.getIncludePrelude());
         }
         if (directivesOnly) {
             parser.parseFileDirectives(root);
         } else {
             while (reader.hasMoreInput()) {
                 parser.parseElements(root);
             }
         }
         if (parent == null && !isTagFile) {
             parser.addInclude(rootpageInfo.getIncludeCoda());
         }
 
         Node.Nodes page = new Node.Nodes(root);
         return page;
     }

    
Attributes ::= (S Attribute)* S?
 
         AttributesImpl attrs = new AttributesImpl();
 
         .skipSpaces();
         while (parseAttribute(attrs))
             .skipSpaces();
 
         return attrs;
     }

    
Parse Attributes for a reader, provided for external use
 
     public static Attributes parseAttributes(ParserController pc,
             JspReader readerthrows JasperException {
         Parser tmpParser = new Parser(pcreaderfalsefalsenull);
         return tmpParser.parseAttributes();
     }

    
Attribute ::= Name S? Eq S? ( '"<%=' RTAttributeValueDouble | '"' AttributeValueDouble | "'<%=" RTAttributeValueSingle | "'" AttributeValueSingle } Note: JSP and XML spec does not allow while spaces around Eq. It is added to be backward compatible with Tomcat, and with other xml parsers.
 
     private boolean parseAttribute(AttributesImpl attrsthrows JasperException {
 
         // Get the qualified name
         String qName = parseName();
         if (qName == null)
             return false;
 
         // Determine prefix and local name components
         String localName = qName;
         String uri = "";
         int index = qName.indexOf(':');
         if (index != -1) {
             String prefix = qName.substring(0, index);
             uri = .getURI(prefix);
             if (uri == null) {
                 .jspError(.mark(), .invalidAttributePrefix(prefix));
             }
             localName = qName.substring(index + 1);
         }
 
         .skipSpaces();
         if (!.matches("="))
             .jspError(.mark(), .missingEqual());
 
         .skipSpaces();
         char quote = (char.nextChar();
         if (quote != '\'' && quote != '"')
             .jspError(.mark(), .missingQuote());
 
         String watchString = "";
         if (.matches("<%="))
             watchString = "%>";
         watchString = watchString + quote;
 
         String attrValue = parseAttributeValue(watchString);
         attrs.addAttribute(urilocalNameqName"CDATA"attrValue);
         return true;
     }

    
Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*
 
     private String parseName() throws JasperException {
         char ch = (char.peekChar();
         if (Character.isLetter(ch) || ch == '_' || ch == ':') {
             StringBuilder buf = new StringBuilder();
             buf.append(ch);
             .nextChar();
             ch = (char.peekChar();
             while (Character.isLetter(ch) || Character.isDigit(ch) || ch == '.'
                     || ch == '_' || ch == '-' || ch == ':') {
                 buf.append(ch);
                 .nextChar();
                 ch = (char.peekChar();
             }
             return buf.toString();
         }
         return null;
     }

    
AttributeValueDouble ::= (QuotedChar - '"')* ('"' | <TRANSLATION_ERROR>) RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"') ('%>"' | TRANSLATION_ERROR)
 
     private String parseAttributeValue(String watchthrows JasperException {
         Mark start = .mark();
         Mark stop = .skipUntilIgnoreEsc(watch);
         if (stop == null) {
             .jspError(start.unterminatedAttribute(watch));
         }
 
         String ret = null;
         try {
             char quote = watch.charAt(watch.length() - 1);
 
             // If watch is longer than 1 character this is a scripting
             // expression and EL is always ignored
             boolean isElIgnored =
                 .isELIgnored() || watch.length() > 1;
 
             ret = AttributeParser.getUnquoted(.getText(startstop),
                     quoteisElIgnored,
                     .isDeferredSyntaxAllowedAsLiteral());
         } catch (IllegalArgumentException iae) {
             .jspError(start.errorUnquotingAttributeValue(), iae);
         }
         if (watch.length() == 1) // quote
             return ret;
 
         // Put back delimiter '<%=' and '%>', since they are needed if the
         // attribute does not allow RTexpression.
         return "<%=" + ret + "%>";
     }
 
     private String parseScriptText(String tx) {
         CharArrayWriter cw = new CharArrayWriter();
         int size = tx.length();
         int i = 0;
         while (i < size) {
             char ch = tx.charAt(i);
             if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\'
                     && tx.charAt(i + 2) == '>') {
                 cw.write('%');
                 cw.write('>');
                 i += 3;
             } else {
                 cw.write(ch);
                 ++i;
             }
         }
         cw.close();
         return cw.toString();
     }
 
     /*
      * Invokes parserController to parse the included page
      */
     private void processIncludeDirective(String fileNode parent)
             throws JasperException {
         if (file == null) {
             return;
         }
 
         try {
             .parse(fileparent);
         } catch (FileNotFoundException ex) {
             .jspError(.fileNotFound(file));
         } catch (Exception ex) {
             .jspError(parent.getStart(), .errorIncluding(file), ex);
         }
     }
 
     /*
      * Parses a page directive with the following syntax: PageDirective ::= ( S
      * Attribute)*
      */
     private void parsePageDirective(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         Node.PageDirective n = new Node.PageDirective(attrsparent);
 
         /*
          * A page directive may contain multiple 'import' attributes, each of
          * which consists of a comma-separated list of package names. Store each
          * list with the node, where it is parsed.
          */
         for (int i = 0; i < attrs.getLength(); i++) {
             if ("import".equals(attrs.getQName(i))) {
                 n.addImport(attrs.getValue(i));
             }
         }
     }
 
     /*
      * Parses an include directive with the following syntax: IncludeDirective
      * ::= ( S Attribute)*
      */
     private void parseIncludeDirective(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
 
         // Included file expanded here
         Node includeNode = new Node.IncludeDirective(attrsparent);
         processIncludeDirective(attrs.getValue("file"), includeNode);
     }

    
Add a list of files. This is used for implementing include-prelude and include-coda of jsp-config element in web.xml
 
     private void addInclude(Node parentList filesthrows JasperException {
         if (files != null) {
             Iterator iter = files.iterator();
             while (iter.hasNext()) {
                 String file = (Stringiter.next();
                 AttributesImpl attrs = new AttributesImpl();
                 attrs.addAttribute("""file""file""CDATA"file);
 
                 // Create a dummy Include directive node
                 Node includeNode = new Node.IncludeDirective(attrs
                         .mark(), parent);
                 processIncludeDirective(fileincludeNode);
             }
         }
     }
 
     /*
      * Parses a taglib directive with the following syntax: Directive ::= ( S
      * Attribute)*
      */
     private void parseTaglibDirective(Node parentthrows JasperException {
 
         Attributes attrs = parseAttributes();
         String uri = attrs.getValue("uri");
         String prefix = attrs.getValue("prefix");
         if (prefix != null) {
             Mark prevMark = .getNonCustomTagPrefix(prefix);
             if (prevMark != null) {
                 .jspError(.mark(), .prefixAlreadyInUse
                         (prefixprevMark.getFile(), prevMark.getLineNumber()));
             }
             if (uri != null) {
                 String uriPrev = .getURI(prefix);
                 if (uriPrev != null && !uriPrev.equals(uri)) {
                     .jspError(.mark(), .prefixRedefinition(prefixuriuriPrev));
                 }
                 if (.getTaglib(uri) == null) {
                     TagLibraryInfoImpl impl = null;
                     if (.getOptions().isCaching()) {
                         impl = (TagLibraryInfoImpl.getOptions()
                                 .getCache().get(uri);
                     }
                     if (impl == null) {
                         String[] location = .getTldLocation(uri);
                         impl = new TagLibraryInfoImpl(,
                                 prefixurilocation);
                         if (.getOptions().isCaching()) {
                             .getOptions().getCache().put(uriimpl);
                         }
                     } else {
                         // Current compilation context needs location of cached
                         // tag files
                         for (TagFileInfo info : impl.getTagFiles()) {
                             .setTagFileJarUrl(info.getPath(), .getTagFileJarUrl());
                         }
                     }
                     .addTaglib(uriimpl);
                 }
                 .addPrefixMapping(prefixuri);
             } else {
                 String tagdir = attrs.getValue("tagdir");
                 if (tagdir != null) {
                     String urnTagdir =  + tagdir;
                     if (.getTaglib(urnTagdir) == null) {
                         .addTaglib(urnTagdir,
                                 new ImplicitTagLibraryInfo(,
                                         prefixtagdir));
                     }
                     .addPrefixMapping(prefixurnTagdir);
                 }
             }
         }
 
         new Node.TaglibDirective(attrsparent);
     }
 
     /*
      * Parses a directive with the following syntax: Directive ::= S? ( 'page'
      * PageDirective | 'include' IncludeDirective | 'taglib' TagLibDirective) S?
      * '%>'
      * 
      * TagDirective ::= S? ('tag' PageDirective | 'include' IncludeDirective |
      * 'taglib' TagLibDirective) | 'attribute AttributeDirective | 'variable
      * VariableDirective S? '%>'
      */
     private void parseDirective(Node parentthrows JasperException {
         .skipSpaces();
 
         String directive = null;
         if (.matches("page")) {
             directive = "&lt;%@ page";
             if () {
                 .jspError(.mark(), .invalidDirectiveInTagFile(directive));
             }
             parsePageDirective(parent);
         } else if (.matches("include")) {
             directive = "&lt;%@ include";
             parseIncludeDirective(parent);
         } else if (.matches("taglib")) {
             if () {
                 // No need to get the tagLibInfo objects. This alos suppresses
                 // parsing of any tag files used in this tag file.
                 return;
             }
             directive = "&lt;%@ taglib";
             parseTaglibDirective(parent);
         } else if (.matches("tag")) {
             directive = "&lt;%@ tag";
             if (!) {
                 .jspError(.mark(), .invalidDirectiveInPage(directive));
             }
             parseTagDirective(parent);
         } else if (.matches("attribute")) {
             directive = "&lt;%@ attribute";
             if (!) {
                 .jspError(.mark(), .invalidDirectiveInPage(directive));
             }
             parseAttributeDirective(parent);
         } else if (.matches("variable")) {
             directive = "&lt;%@ variable";
             if (!) {
                 .jspError(.mark(), .invalidDirectiveInPage(directive));
             }
             parseVariableDirective(parent);
         } else {
             .jspError(.mark(), .invalidDirective());
         }
 
         .skipSpaces();
         if (!.matches("%>")) {
             .jspError(.unterminatedTag(directive));
         }
     }
 
     /*
      * Parses a directive with the following syntax:
      * 
      * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList S? ( '/>' | (
      * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
      * S? ETag ) ) | <TRANSLATION_ERROR>
      * 
      * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList S? ( '/>' | (
      * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
      * S? ETag ) ) | ( 'attribute' AttributeDirectiveAttrList S? ( '/>' | ( '>'
      * S? ETag ) ) | ( 'variable' VariableDirectiveAttrList S? ( '/>' | ( '>' S?
      * ETag ) ) ) | <TRANSLATION_ERROR>
      */
     private void parseXMLDirective(Node parentthrows JasperException {
         .skipSpaces();
 
         String eTag = null;
         if (.matches("page")) {
             eTag = "jsp:directive.page";
             if () {
                 .jspError(.mark(), .invalidDirectiveInTagFile("&lt;" + eTag));
             }
             parsePageDirective(parent);
         } else if (.matches("include")) {
             eTag = "jsp:directive.include";
             parseIncludeDirective(parent);
         } else if (.matches("tag")) {
             eTag = "jsp:directive.tag";
             if (!) {
                 .jspError(.mark(), .invalidDirectiveInPage("&lt;" + eTag));
             }
             parseTagDirective(parent);
         } else if (.matches("attribute")) {
             eTag = "jsp:directive.attribute";
             if (!) {
                 .jspError(.mark(), .invalidDirectiveInPage("&lt;" + eTag));
             }
             parseAttributeDirective(parent);
         } else if (.matches("variable")) {
             eTag = "jsp:directive.variable";
             if (!) {
                 .jspError(.mark(), .invalidDirectiveInPage("&lt;" + eTag));
             }
             parseVariableDirective(parent);
         } else {
             .jspError(.mark(), .invalidDirective());
         }
 
         .skipSpaces();
         if (.matches(">")) {
             .skipSpaces();
             if (!.matchesETag(eTag)) {
                 .jspError(.unterminatedTag("&lt;" + eTag));
             }
         } else if (!.matches("/>")) {
             .jspError(.unterminatedTag("&lt;" + eTag));
         }
     }
 
     /*
      * Parses a tag directive with the following syntax: PageDirective ::= ( S
      * Attribute)*
      */
     private void parseTagDirective(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         Node.TagDirective n = new Node.TagDirective(attrsparent);
 
         /*
          * A page directive may contain multiple 'import' attributes, each of
          * which consists of a comma-separated list of package names. Store each
          * list with the node, where it is parsed.
          */
         for (int i = 0; i < attrs.getLength(); i++) {
             if ("import".equals(attrs.getQName(i))) {
                 n.addImport(attrs.getValue(i));
             }
         }
     }
 
     /*
      * Parses a attribute directive with the following syntax:
      * AttributeDirective ::= ( S Attribute)*
      */
     private void parseAttributeDirective(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         Node.AttributeDirective n = new Node.AttributeDirective(attrs,
                 parent);
     }
 
     /*
      * Parses a variable directive with the following syntax: PageDirective ::= (
      * S Attribute)*
      */
     private void parseVariableDirective(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         Node.VariableDirective n = new Node.VariableDirective(attrs,
                 parent);
     }
 
     /*
      * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>'
      */
     private void parseComment(Node parentthrows JasperException {
          = .mark();
         Mark stop = .skipUntil("--%>");
         if (stop == null) {
             .jspError(.unterminatedTag("&lt;%--"));
         }
 
         new Node.Comment(.getText(stop), parent);
     }
 
     /*
      * DeclarationBody ::= (Char* - (char* '%>')) '%>'
      */
     private void parseDeclaration(Node parentthrows JasperException {
          = .mark();
         Mark stop = .skipUntil("%>");
         if (stop == null) {
             .jspError(.unterminatedTag("&lt;%!"));
         }
 
         new Node.Declaration(parseScriptText(.getText(stop)),
                 parent);
     }
 
     /*
      * XMLDeclarationBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
      * CDSect?)* ETag | <TRANSLATION_ERROR> CDSect ::= CDStart CData CDEnd
      * CDStart ::= '<![CDATA[' CData ::= (Char* - (Char* ']]>' Char*)) CDEnd
      * ::= ']]>'
      */
     private void parseXMLDeclaration(Node parentthrows JasperException {
         .skipSpaces();
         if (!.matches("/>")) {
             if (!.matches(">")) {
                 .jspError(.unterminatedTag("&lt;jsp:declaration&gt;"));
             }
             Mark stop;
             String text;
             while (true) {
                  = .mark();
                 stop = .skipUntil("<");
                 if (stop == null) {
                     .jspError(.unterminatedTag("&lt;jsp:declaration&gt;"));
                 }
                 text = parseScriptText(.getText(stop));
                 new Node.Declaration(textparent);
                 if (.matches("![CDATA[")) {
                      = .mark();
                     stop = .skipUntil("]]>");
                     if (stop == null) {
                         .jspError(.unterminatedTag("CDATA"));
                     }
                     text = parseScriptText(.getText(stop));
                     new Node.Declaration(textparent);
                 } else {
                     break;
                 }
             }
 
             if (!.matchesETagWithoutLessThan("jsp:declaration")) {
                 .jspError(.unterminatedTag("&lt;jsp:declaration&gt;"));
             }
         }
     }
 
     /*
      * ExpressionBody ::= (Char* - (char* '%>')) '%>'
      */
     private void parseExpression(Node parentthrows JasperException {
          = .mark();
         Mark stop = .skipUntil("%>");
         if (stop == null) {
             .jspError(.unterminatedTag("&lt;%="));
         }
 
         String expression = .getText(stop);
         // check for string concatenation inside expressions, separating from expression allows for optimizations later on
         if(!){
             new Node.Expression(parseScriptText(expression),
                     parent);
         }
         else {
             if (!matchesConcat(expression)) {
                 new Node.Expression(parseScriptText(expression),
                         parent);
             } else {
                 //need to separate expressions being concatenated
                 expression = expression.replaceAll("\\+\\s*\"""\\+ \"").replaceAll("\"\\s*\\+""\" \\+");
                 String[] tokens = expression.split("((?=\\+\\s\")|(?<=\"\\s\\+))");
                 if (tokens.length > 1) {
                     for (String token : tokens) {
                         if (matchesStringLiteral(token) && !matchesStringParam(token)) {
                             //maybe evaluate the expression here before storing as text node?
                             new Node.TemplateText(cleanTextToken(token),
                                     parent);
                         } else {
                             new Node.Expression(parseScriptText(cleanExprToken(token)),
                                     parent);
                         }
                     }
                 } else {
                     //only have one token, therefore there is no string concatenation occurring and string literal is being used as part of expression
                     new Node.Expression(parseScriptText(tokens[0]),
                             parent);
 
                 }
             }
         }
     }
 
     private boolean matchesStringLiteral(String token) {
         return Pattern.compile("\"").matcher(token).find() || "".equals(token.trim());
     }
 
     private boolean matchesStringParam(String token) {
         return Pattern.compile("\"\\s*\\)|\\(\\s*\"").matcher(token).find();
     }
 
     private boolean matchesConcat(String token) {
         return Pattern.compile("\\+\\s*\"|\"\\s*\\+").matcher(token).find();
     }
 
     private String cleanTextToken(String token) {
         return cleanExprToken(token.trim().replaceAll("(?<!\\\\)\"|\t|\n|\r""").replaceAll("\\\\\"","\""));
     }
 
     private String cleanExprToken(String token) {
         return token.trim().replaceAll("^\\+|\\+$","").trim();
     }
 
     /*
      * XMLExpressionBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
      * CDSect?)* ETag ) | <TRANSLATION_ERROR>
      */
     private void parseXMLExpression(Node parentthrows JasperException {
         .skipSpaces();
         if (!.matches("/>")) {
             if (!.matches(">")) {
                 .jspError(.unterminatedTag("&lt;jsp:expression&gt;"));
             }
             Mark stop;
             String text;
             while (true) {
                  = .mark();
                 stop = .skipUntil("<");
                 if (stop == null) {
                     .jspError(.unterminatedTag("&lt;jsp:expression&gt;"));
                 }
                 text = parseScriptText(.getText(stop));
                 new Node.Expression(textparent);
                 if (.matches("![CDATA[")) {
                      = .mark();
                     stop = .skipUntil("]]>");
                     if (stop == null) {
                         .jspError(.unterminatedTag("CDATA"));
                     }
                     text = parseScriptText(.getText(stop));
                     new Node.Expression(textparent);
                 } else {
                     break;
                 }
             }
             if (!.matchesETagWithoutLessThan("jsp:expression")) {
                 .jspError(.unterminatedTag("&lt;jsp:expression&gt;"));
             }
         }
     }
 
     /*
      * ELExpressionBody (following "${" to first unquoted "}") // XXX add formal
      * production and confirm implementation against it, // once it's decided
      */
     private void parseELExpression(Node parentchar typethrows JasperException {
          = .mark();
         Mark last = null;
         boolean singleQuoted = falsedoubleQuoted = false;
         int currentChar;
         do {
             // XXX could move this logic to JspReader
             last = .mark(); // XXX somewhat wasteful
             currentChar = .nextChar();
             if (currentChar == '\\' && (singleQuoted || doubleQuoted)) {
                 // skip character following '\' within quotes
                 .nextChar();
                 currentChar = .nextChar();
             }
             if (currentChar == -1)
                 .jspError(.unterminatedTag(type + "{"));
             if (currentChar == '"' && !singleQuoted)
                 doubleQuoted = !doubleQuoted;
             if (currentChar == '\'' && !doubleQuoted)
                 singleQuoted = !singleQuoted;
         } while (currentChar != '}' || (singleQuoted || doubleQuoted));
 
         new Node.ELExpression(type.getText(last), parent);
     }
 
     /*
      * ScriptletBody ::= (Char* - (char* '%>')) '%>'
      */
     private void parseScriptlet(Node parentthrows JasperException {
          = .mark();
         Mark stop = .skipUntil("%>");
         if (stop == null) {
             .jspError(.unterminatedTag("&lt;%"));
         }
 
         new Node.Scriptlet(parseScriptText(.getText(stop)), ,
                 parent);
     }
 
     /*
      * XMLScriptletBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
      * CDSect?)* ETag ) | <TRANSLATION_ERROR>
      */
     private void parseXMLScriptlet(Node parentthrows JasperException {
         .skipSpaces();
         if (!.matches("/>")) {
             if (!.matches(">")) {
                 .jspError(.unterminatedTag("&lt;jsp:scriptlet&gt;"));
             }
             Mark stop;
             String text;
             while (true) {
                  = .mark();
                 stop = .skipUntil("<");
                 if (stop == null) {
                     .jspError(.unterminatedTag("&lt;jsp:scriptlet&gt;"));
                 }
                 text = parseScriptText(.getText(stop));
                 new Node.Scriptlet(textparent);
                 if (.matches("![CDATA[")) {
                      = .mark();
                     stop = .skipUntil("]]>");
                     if (stop == null) {
                         .jspError(.unterminatedTag("CDATA"));
                     }
                     text = parseScriptText(.getText(stop));
                     new Node.Scriptlet(textparent);
                 } else {
                     break;
                 }
             }
 
             if (!.matchesETagWithoutLessThan("jsp:scriptlet")) {
                 .jspError(.unterminatedTag("&lt;jsp:scriptlet&gt;"));
             }
         }
     }

    
Param ::= '<jsp:param' S Attributes S? EmptyBody S?
 
     private void parseParam(Node parentthrows JasperException {
         if (!.matches("<jsp:param")) {
             .jspError(.mark(), .missingParamAction());
         }
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node paramActionNode = new Node.ParamAction(attrsparent);
 
         parseEmptyBody(paramActionNode"jsp:param");
 
         .skipSpaces();
     }
 
     /*
      * For Include: StdActionContent ::= Attributes ParamBody
      * 
      * ParamBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body'
      * (JspBodyParam | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? Param* ETag )
      * 
      * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
      * NamedAttributes ETag )
      * 
      * JspBodyParam ::= S? '>' Param* '</jsp:body>'
      */
     private void parseInclude(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node includeNode = new Node.IncludeAction(attrsparent);
 
         parseOptionalBody(includeNode"jsp:include");
     }
 
     /*
      * For Forward: StdActionContent ::= Attributes ParamBody
      */
     private void parseForward(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node forwardNode = new Node.ForwardAction(attrsparent);
 
         parseOptionalBody(forwardNode"jsp:forward");
     }
 
     private void parseInvoke(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node invokeNode = new Node.InvokeAction(attrsparent);
 
         parseEmptyBody(invokeNode"jsp:invoke");
     }
 
     private void parseDoBody(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node doBodyNode = new Node.DoBodyAction(attrsparent);
 
         parseEmptyBody(doBodyNode"jsp:doBody");
     }
 
     private void parseElement(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node elementNode = new Node.JspElement(attrsparent);
 
         parseOptionalBody(elementNode"jsp:element".);
     }
 
     /*
      * For GetProperty: StdActionContent ::= Attributes EmptyBody
      */
     private void parseGetProperty(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node getPropertyNode = new Node.GetProperty(attrsparent);
 
         parseOptionalBody(getPropertyNode"jsp:getProperty",
                 .);
     }
 
     /*
      * For SetProperty: StdActionContent ::= Attributes EmptyBody
      */
     private void parseSetProperty(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node setPropertyNode = new Node.SetProperty(attrsparent);
 
         parseOptionalBody(setPropertyNode"jsp:setProperty",
                 .);
     }
 
     /*
      * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
      * NamedAttributes ETag )
      */
     private void parseEmptyBody(Node parentString tagthrows JasperException {
         if (.matches("/>")) {
             // Done
         } else if (.matches(">")) {
             if (.matchesETag(tag)) {
                 // Done
             } else if (.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
                 // Parse the one or more named attribute nodes
                 parseNamedAttributes(parent);
                 if (!.matchesETag(tag)) {
                     // Body not allowed
                     .jspError(.mark(),
                             .invalidEmptyBodyTag("&lt;" + tag));
                 }
             } else {
                 .jspError(.mark(), .invalidEmptyBodyTag("&lt;" + tag));
             }
         } else {
             .jspError(.mark(), .unterminatedTag("&lt;" + tag));
         }
     }
 
     /*
      * For UseBean: StdActionContent ::= Attributes OptionalBody
      */
     private void parseUseBean(Node parentthrows JasperException {
         Attributes attrs = parseAttributes();
         .skipSpaces();
 
         Node useBeanNode = new Node.UseBean(attrsparent);
 
         parseOptionalBody(useBeanNode"jsp:useBean".);
     }
 
     /*
      * Parses OptionalBody, but also reused to parse bodies for plugin and param
      * since the syntax is identical (the only thing that differs substantially
      * is how to process the body, and thus we accept the body type as a
      * parameter).
      * 
      * OptionalBody ::= EmptyBody | ActionBody
      * 
      * ScriptlessOptionalBody ::= EmptyBody | ScriptlessActionBody
      * 
      * TagDependentOptionalBody ::= EmptyBody | TagDependentActionBody
      * 
      * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
      * NamedAttributes ETag )
      * 
      * ActionBody ::= JspAttributeAndBody | ( '>' Body ETag )
      * 
      * ScriptlessActionBody ::= JspAttributeAndBody | ( '>' ScriptlessBody ETag )
      * 
      * TagDependentActionBody ::= JspAttributeAndBody | ( '>' TagDependentBody
      * ETag )
      * 
      */
     private void parseOptionalBody(Node parentString tagString bodyType)
             throws JasperException {