FileDocCategorySizeDatePackage
Parser.javaAPI DocGlassfish v2 API70540Fri May 04 22:32:52 BST 2007org.apache.jasper.compiler

Parser

public class Parser extends Object implements TagConstants
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
Kin-man Chung
author
Shawn Bayern
author
Mark Roth

Fields Summary
private ParserController
parserController
private org.apache.jasper.JspCompilationContext
ctxt
private JspReader
reader
private Mark
start
private ErrorDispatcher
err
private int
scriptlessCount
private boolean
isTagFile
private boolean
directivesOnly
private URL
jarFileUrl
private PageInfo
pageInfo
private static final String
JAVAX_BODY_CONTENT_PARAM
private static final String
JAVAX_BODY_CONTENT_PLUGIN
private static final String
JAVAX_BODY_CONTENT_TEMPLATE_TEXT
Constructors Summary
private Parser(ParserController pc, JspReader reader, boolean isTagFile, boolean directivesOnly, URL jarFileUrl, boolean hasBom)
The constructor


           
          
                         
	this.parserController = pc;
	this.ctxt = pc.getJspCompilationContext();
	this.pageInfo = pc.getCompiler().getPageInfo();
	this.err = pc.getCompiler().getErrorDispatcher();
	this.reader = reader;
        this.scriptlessCount = 0;
	this.isTagFile = isTagFile;
	this.directivesOnly = directivesOnly;
	this.jarFileUrl = jarFileUrl;
        start = reader.mark();
    
Methods Summary
private voidaddInclude(Node parent, java.util.List files)
Add a list of files. This is used for implementing include-prelude and include-coda of jsp-config element in web.xml

        if( files != null ) {
            Iterator iter = files.iterator();
            while (iter.hasNext()) {
                String file = (String) iter.next();
                AttributesImpl attrs = new AttributesImpl();
                attrs.addAttribute("", "file", "file", "CDATA", file);

                // Create a dummy Include directive node
                Node includeNode = new Node.IncludeDirective(attrs, 
                    reader.mark(), parent);
                processIncludeDirective(file, includeNode);
            }
        }
    
private voidcheckUnbalancedEndTag()


        if (!reader.matches("</")) {
            return;
        }

        // Check for unbalanced standard actions
        if (reader.matches("jsp:")) {
            err.jspError(start, "jsp.error.unbalanced.endtag", "jsp:");
        }

        // Check for unbalanced custom actions
        String tagName = reader.parseToken(false);
        int i = tagName.indexOf(':");
        if (i == -1 || pageInfo.getURI(tagName.substring(0, i)) == null) {
            reader.reset(start);
            return;
        }

        err.jspError(start, "jsp.error.unbalanced.endtag", tagName);
    
private java.lang.StringgetAttributeBodyType(Node n, java.lang.String name)
Determine the body type of from the enclosing node


	if (n instanceof Node.CustomTag) {
	    TagInfo tagInfo = ((Node.CustomTag)n).getTagInfo();
	    TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
	    for (int i=0; i<tldAttrs.length; i++) {
		if (name.equals(tldAttrs[i].getName())) {
		    if (tldAttrs[i].isFragment()) {
		        return TagInfo.BODY_CONTENT_SCRIPTLESS;
		    }
		    if (tldAttrs[i].canBeRequestTime()) {
		        return TagInfo.BODY_CONTENT_JSP;
		    }
		}
	    }
	    if (tagInfo.hasDynamicAttributes()) {
		return TagInfo.BODY_CONTENT_JSP;
	    }
	} else if (n instanceof Node.IncludeAction) {
	    if ("page".equals(name)) {
		return TagInfo.BODY_CONTENT_JSP;
	    }
	} else if (n instanceof Node.ForwardAction) {
	    if ("page".equals(name)) {
		return TagInfo.BODY_CONTENT_JSP;
	    }
	} else if (n instanceof Node.SetProperty) {
	    if ("value".equals(name)) {
		return TagInfo.BODY_CONTENT_JSP;
	    }
	} else if (n instanceof Node.UseBean) {
	    if ("beanName".equals(name)) {
		return TagInfo.BODY_CONTENT_JSP;
	    }
	} else if (n instanceof Node.PlugIn) {
	    if ("width".equals(name) || "height".equals(name)) {
		return TagInfo.BODY_CONTENT_JSP;
	    }
	} else if (n instanceof Node.ParamAction) {
	    if ("value".equals(name)) {
		return TagInfo.BODY_CONTENT_JSP;
	    }
	} else if (n instanceof Node.JspElement) {
	    return TagInfo.BODY_CONTENT_JSP;
	}

	return JAVAX_BODY_CONTENT_TEMPLATE_TEXT;
    
public static Node.Nodesparse(ParserController pc, java.lang.String path, JspReader reader, Node parent, boolean isTagFile, boolean directivesOnly, java.net.URL jarFileUrl, java.lang.String pageEnc, java.lang.String jspConfigPageEnc, boolean isDefaultPageEncoding, boolean hasBom)
The main entry for Parser

param
pc The ParseController, use for getting other objects in compiler and for parsing included pages
param
reader To read the page
param
parent The parent node to this page, null for top level page
return
list of nodes representing the parsed page


	Parser parser = new Parser(pc, reader, isTagFile, directivesOnly,
                                   jarFileUrl, hasBom);

	Node.Root root = new Node.Root(reader.mark(), parent, false);
	root.setPageEncoding(pageEnc);
	root.setJspConfigPageEncoding(jspConfigPageEnc);
	root.setIsDefaultPageEncoding(isDefaultPageEncoding);
        root.setHasBom(hasBom);

        if (hasBom) {
            // Consume (remove) BOM, so it won't appear in page output
            char bomChar = (char) reader.nextChar();
            if (bomChar != 0xFEFF) {
                parser.err.jspError(
                        reader.mark(),
                        "jsp.error.invalidBom",
                        Integer.toHexString(bomChar).toUpperCase());
            }
        }

	if (directivesOnly) {
	    parser.parseTagFileDirectives(root);
	    return new Node.Nodes(root);
	}

	// For the Top level page, add inlcude-prelude and include-coda
	PageInfo pageInfo = pc.getCompiler().getPageInfo();
	if (parent == null) {
	    parser.addInclude(root, pageInfo.getIncludePrelude());
	}
	while (reader.hasMoreInput()) {
	    parser.parseElements(root);
	}
	if (parent == null) {
	    parser.addInclude(root, pageInfo.getIncludeCoda());
	    parser.pageInfo.setRootPath(path);
	}

	Node.Nodes page = new Node.Nodes(root);
	return page;
    
private booleanparseAttribute(org.xml.sax.helpers.AttributesImpl attrs)
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.


	// 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 = pageInfo.getURI(prefix);
	    if (uri == null) {
		err.jspError(reader.mark(),
			     "jsp.error.attribute.invalidPrefix", prefix);
	    }
	    localName = qName.substring(index+1);
	}

 	reader.skipSpaces();
	if (!reader.matches("="))
	    err.jspError(reader.mark(), "jsp.error.attribute.noequal");

 	reader.skipSpaces();
	char quote = (char) reader.nextChar();
	if (quote != '\'" && quote != '"")
	    err.jspError(reader.mark(), "jsp.error.attribute.noquote");

 	String watchString = "";
	if (reader.matches("<%="))
	    watchString = "%>";
	watchString = watchString + quote;
	
	String attrValue = parseAttributeValue(watchString);
	attrs.addAttribute(uri, localName, qName, "CDATA", attrValue);
	return true;
    
private voidparseAttributeDirective(Node parent)

	Attributes attrs = parseAttributes();
        new Node.AttributeDirective(attrs, start, parent);
    
private java.lang.StringparseAttributeValue(java.lang.String watch)
AttributeValueDouble ::= (QuotedChar - '"')* ('"' | ) RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"') ('%>"' | TRANSLATION_ERROR)

	Mark start = reader.mark();
	Mark stop = reader.skipUntilIgnoreEsc(watch);
	if (stop == null) {
	    err.jspError(start, "jsp.error.attribute.unterminated", watch);
	}

	String ret = parseQuoted(reader.getText(start, stop));
	if (watch.length() == 1)	// quote
	    return ret;

	// putback delimiter '<%=' and '%>', since they are needed if the
	// attribute does not allow RTexpression.
	return "<%=" + ret + "%>";
    
org.xml.sax.AttributesparseAttributes()
Attributes ::= (S Attribute)* S?

	AttributesImpl attrs = new AttributesImpl();

	reader.skipSpaces();
	while (parseAttribute(attrs))
	    reader.skipSpaces();

	return attrs;
    
public static org.xml.sax.AttributesparseAttributes(ParserController pc, JspReader reader)
Parse Attributes for a reader, provided for external use

	Parser tmpParser = new Parser(pc, reader, false, false, null, false);
	return tmpParser.parseAttributes();
    
private voidparseBody(Node parent, java.lang.String tag, java.lang.String bodyType)

        if( bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_TAG_DEPENDENT ) ) {
            parseTagDependentBody( parent, tag );
        }
        else if( bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_EMPTY ) ) {
            if( !reader.matchesETag( tag ) ) {
		err.jspError(start, "jasper.error.emptybodycontent.nonempty",
			     tag);
            }
        }
        else if( bodyType == JAVAX_BODY_CONTENT_PLUGIN ) {
            // (note the == since we won't recognize JAVAX_* 
            // from outside this module).
            parsePluginTags(parent);
            if( !reader.matchesETag( tag ) ) {
                err.jspError( reader.mark(), "jsp.error.unterminated",
                    "<" + tag  );
            }
        }
        else if( bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_JSP ) ||
            bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_SCRIPTLESS ) ||
            (bodyType == JAVAX_BODY_CONTENT_PARAM) ||
            (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) )
        {
            while (reader.hasMoreInput()) {
                if (reader.matchesETag(tag)) {
                    return;
                }
                
                // Check for nested jsp:body or jsp:attribute
                if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) {
                    if (reader.matches("<jsp:attribute")) {
                        err.jspError(reader.mark(), "jsp.error.nested.jspattribute");
                    }
                    else if (reader.matches("<jsp:body")) {
                        err.jspError(reader.mark(), "jsp.error.nested.jspbody");
                    }
                }

                if( bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_JSP ) ) {
                    parseElements( parent );
                }
                else if( bodyType.equalsIgnoreCase( 
                    TagInfo.BODY_CONTENT_SCRIPTLESS ) ) 
                {
                    parseElementsScriptless( parent );
                }
                else if( bodyType == JAVAX_BODY_CONTENT_PARAM ) {
                    // (note the == since we won't recognize JAVAX_* 
                    // from outside this module).
                    reader.skipSpaces();
                    parseParam( parent );
                }
		else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) {
		    parseElementsTemplateText(parent);
		}
            }
            err.jspError(start, "jsp.error.unterminated", "<"+tag );
        }
        else {
            err.jspError(start, "jsp.error.tld.badbodycontent", bodyType,
                         tag);
        }
    
private voidparseComment(Node parent)

	
	start = reader.mark();
	Mark stop = reader.skipUntil("--%>");
	if (stop == null) {
	    err.jspError(start, "jsp.error.unterminated", "<%--");
	}

	new Node.Comment(reader.getText(start, stop), start, parent);
    
private booleanparseCustomTag(Node parent)


	if (reader.peekChar() != '<") {
	    return false;
	}

        // Parse 'CustomAction' production (tag prefix and custom action name)
	reader.nextChar();	// skip '<'
	String tagName = reader.parseToken(false);
	int i = tagName.indexOf(':");
	if (i == -1) {
	    reader.reset(start);
	    return false;
	}

	String prefix = tagName.substring(0, i);
	String shortTagName = tagName.substring(i+1);

	// Check if this is a user-defined tag.
	String uri = pageInfo.getURI(prefix);
        if (uri == null) {
	    reader.reset(start);
            // Remember the prefix for later error checking
            pageInfo.putNonCustomTagPrefix(prefix, reader.mark());
	    return false;
	}

        TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
	TagInfo tagInfo = tagLibInfo.getTag(shortTagName);
	TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName);
	if (tagInfo == null && tagFileInfo == null) {
	    err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix);
	}
	Class tagHandlerClass = null;
	if (tagInfo != null) {
	    // Must be a classic tag, load it here.
	    // tag files will be loaded later, in TagFileProcessor
	    String handlerClassName = tagInfo.getTagClassName();
	    try {
	        tagHandlerClass = ctxt.getClassLoader().loadClass(handlerClassName);
	    } catch (Exception e) {
	        err.jspError(start, "jsp.error.loadclass.taghandler",
			     handlerClassName, tagName);
	    }
	}

        // Parse 'CustomActionBody' production:
        // At this point we are committed - if anything fails, we produce
        // a translation error.

        // Parse 'Attributes' production:
	Attributes attrs = parseAttributes();
	reader.skipSpaces();
	
        // Parse 'CustomActionEnd' production:
	if (reader.matches("/>")) {
	    if (tagInfo != null) {
		new Node.CustomTag(tagLibInfo.getRequiredVersion(),
                                   tagName, prefix, shortTagName, uri, attrs,
				   start, parent, tagInfo, tagHandlerClass);
	    } else {
		new Node.CustomTag(tagLibInfo.getRequiredVersion(),
                                   tagName, prefix, shortTagName, uri, attrs,
				   start, parent, tagFileInfo);
	    }
	    return true;
	}
	
        // Now we parse one of 'CustomActionTagDependent', 
        // 'CustomActionJSPContent', or 'CustomActionScriptlessContent'.
        // depending on body-content in TLD.

	// Looking for a body, it still can be empty; but if there is a
	// a tag body, its syntax would be dependent on the type of
	// body content declared in the TLD.
	String bc;
	if (tagInfo != null) {
	    bc = tagInfo.getBodyContent();
	} else {
	    bc = tagFileInfo.getTagInfo().getBodyContent();
	}

	Node tagNode = null;
	if (tagInfo != null) {
	    tagNode = new Node.CustomTag(tagLibInfo.getRequiredVersion(),
                                         tagName, prefix, shortTagName, uri,
					 attrs, start, parent, tagInfo,
					 tagHandlerClass);
	} else {
	    tagNode = new Node.CustomTag(tagLibInfo.getRequiredVersion(),
                                         tagName, prefix, shortTagName, uri,
					 attrs, start, parent, tagFileInfo);
	}

	parseOptionalBody( tagNode, tagName, bc );

	return true;
    
private voidparseDeclaration(Node parent)

	start = reader.mark();
	Mark stop = reader.skipUntil("%>");
	if (stop == null) {
	    err.jspError(start, "jsp.error.unterminated", "<%!");
	}

	new Node.Declaration(parseScriptText(reader.getText(start, stop)),
			     start, parent);
    
private voidparseDirective(Node parent)

	reader.skipSpaces();

	String directive = null;
	if (reader.matches("page")) {
	    directive = "<%@ page";
	    if (isTagFile) {
		err.jspError(reader.mark(), "jsp.error.directive.istagfile",
					    directive);
	    }
	    parsePageDirective(parent);
	} else if (reader.matches("include")) {
	    directive = "<%@ include";
	    parseIncludeDirective(parent);
	} else if (reader.matches("taglib")) {
	    if (directivesOnly) {
	        // No need to get the tagLibInfo objects.  This alos suppresses
	        // parsing of any tag files used in this tag file.
	        return;
	    }
	    directive = "<%@ taglib";
	    parseTaglibDirective(parent);
	} else if (reader.matches("tag")) {
	    directive = "<%@ tag";
	    if (!isTagFile) {
		err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
					    directive);
	    }
	    parseTagDirective(parent);
	} else if (reader.matches("attribute")) {
	    directive = "<%@ attribute";
	    if (!isTagFile) {
		err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
					    directive);
	    }
	    parseAttributeDirective(parent);
	} else if (reader.matches("variable")) {
	    directive = "<%@ variable";
	    if (!isTagFile) {
		err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
					    directive);
	    }
	    parseVariableDirective(parent);
	} else {
	    err.jspError(reader.mark(), "jsp.error.invalid.directive",
                reader.parseToken(false));
	}

	reader.skipSpaces();
	if (!reader.matches("%>")) {
	    err.jspError(start, "jsp.error.unterminated", directive);
	}
    
private voidparseDoBody(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();

        Node doBodyNode = new Node.DoBodyAction(attrs, start, parent);
        
        parseEmptyBody(doBodyNode, "jsp:doBody");
    
private voidparseELExpression(Node parent, java.lang.String typeEL)

        start = reader.mark();
        boolean singleQuoted = false, doubleQuoted = false;
        int currentChar;
        do {
            // XXX could move this logic to JspReader
            currentChar = reader.nextChar();
            if (currentChar == '\\" && (singleQuoted || doubleQuoted)) {
                // skip character following '\' within quotes
                reader.nextChar();
                currentChar = reader.nextChar();
            }
            if (currentChar == -1)
                err.jspError(start, "jsp.error.unterminated", typeEL);
            if (currentChar == '"")
                doubleQuoted = !doubleQuoted;
            if (currentChar == '\'")
                singleQuoted = !singleQuoted;
        } while (currentChar != '}" || (singleQuoted || doubleQuoted));

        String text = typeEL + reader.getText(start, reader.mark());
        new Node.ELExpression(text, start, parent);
    
private voidparseElement(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();

        Node elementNode = new Node.JspElement(attrs, start, parent);
        
        parseOptionalBody( elementNode, "jsp:element", 
            TagInfo.BODY_CONTENT_JSP );
    
private voidparseElements(Node parent)

        if( scriptlessCount > 0 ) {
            // vc: ScriptlessBody
            // We must follow the ScriptlessBody production if one of
            // our parents is ScriptlessBody.
            parseElementsScriptless( parent );
            return;
        }
        
	start = reader.mark();
	if (reader.matches("<%--")) {
	    parseComment(parent);
	} else if (reader.matches("<%@")) {
	    parseDirective(parent);
        } else if (reader.matches("<jsp:directive.")) {
            parseXMLDirective(parent);
	} else if (reader.matches("<%!")) {
	    parseDeclaration(parent);
        } else if (reader.matches("<jsp:declaration")) {
            parseXMLDeclaration(parent);
        } else if (reader.matches("<%=")) {
            parseExpression(parent);
        } else if (reader.matches("<jsp:expression")) {
            parseXMLExpression(parent);
	} else if (reader.matches("<%")) {
	    parseScriptlet(parent);
        } else if (reader.matches("<jsp:scriptlet")) {
            parseXMLScriptlet(parent);
        } else if (reader.matches("<jsp:text")) {
            parseXMLTemplateText(parent);
        } else if (reader.matches("${")) {
            parseELExpression(parent, "${");
        } else if (reader.matches("#{")) {
            parseELExpression(parent, "#{");
	} else if (reader.matches("<jsp:")) {
	    parseStandardAction(parent);
	} else if (!parseCustomTag(parent)) {
            checkUnbalancedEndTag();
            parseTemplateText(parent);
	}
    
private voidparseElementsScriptless(Node parent)

        // Keep track of how many scriptless nodes we've encountered
        // so we know whether our child nodes are forced scriptless
        scriptlessCount++;
        
	start = reader.mark();
	if (reader.matches("<%--")) {
	    parseComment(parent);
	} else if (reader.matches("<%@")) {
	    parseDirective(parent);
        } else if (reader.matches("<jsp:directive.")) {
            parseXMLDirective(parent);
	} else if (reader.matches("<%!")) {
	    err.jspError( reader.mark(), "jsp.error.no.scriptlets" );
        } else if (reader.matches("<jsp:declaration")) {
            err.jspError( reader.mark(), "jsp.error.no.scriptlets" );
	} else if (reader.matches("<%=")) {
	    err.jspError( reader.mark(), "jsp.error.no.scriptlets" );
        } else if (reader.matches("<jsp:expression")) {
            err.jspError( reader.mark(), "jsp.error.no.scriptlets" );
	} else if (reader.matches("<%")) {
	    err.jspError( reader.mark(), "jsp.error.no.scriptlets" );
        } else if (reader.matches("<jsp:scriptlet")) {
            err.jspError( reader.mark(), "jsp.error.no.scriptlets" );
        } else if (reader.matches("<jsp:text")) {
            parseXMLTemplateText(parent);
	} else if (reader.matches("${")) {
	    parseELExpression(parent, "${");
	} else if (reader.matches("#{")) {
	    parseELExpression(parent, "#{");
	} else if (reader.matches("<jsp:")) {
	    parseStandardAction(parent);
	} else if (!parseCustomTag(parent)) {
            checkUnbalancedEndTag();
            parseTemplateText(parent);
	}
        
        scriptlessCount--;
    
private voidparseElementsTemplateText(Node parent)

        start = reader.mark();
        if (reader.matches("<%--")) {
            parseComment(parent);
        } else if (reader.matches("<%@")) {
            parseDirective(parent);
        } else if (reader.matches("<jsp:directive.")) {
            parseXMLDirective(parent);
        } else if (reader.matches("<%!")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Declarations" );
        } else if (reader.matches("<jsp:declaration")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Declarations" );
        } else if (reader.matches("<%=")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Expressions" );
        } else if (reader.matches("<jsp:expression")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Expressions" );
        } else if (reader.matches("<%")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Scriptlets" );
        } else if (reader.matches("<jsp:scriptlet")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Scriptlets" );
        } else if (reader.matches("<jsp:text")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"<jsp:text" );
        } else if (reader.matches("${") || reader.matches("#{")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Expression language" );
        } else if (reader.matches("<jsp:")) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Standard actions" );
	} else if (parseCustomTag(parent)) {
            err.jspError( reader.mark(), "jsp.error.not.in.template",
		"Custom actions" );
	} else {
            checkUnbalancedEndTag();
            parseTemplateText(parent);
	}
    
private voidparseEmptyBody(Node parent, java.lang.String tag)

	if( reader.matches("/>") ) {
            // Done
        }
        else if( reader.matches( ">" ) ) {
            if( reader.matchesETag( tag ) ) {
                // Done
            }
            else if( reader.matchesOptionalSpacesFollowedBy(
                "<jsp:attribute" ) )
            {
                // Parse the one or more named attribute nodes
                parseNamedAttributes( parent );
                if( !reader.matchesETag( tag ) ) {
                    // Body not allowed
                    err.jspError(reader.mark(),
                        "jsp.error.jspbody.emptybody.only",
                        "<" + tag );
                }
            }
            else {
                err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only",
                    "<" + tag );
            }
        }
        else {
	    err.jspError(reader.mark(), "jsp.error.unterminated",
                "<" + tag );
        }
    
private voidparseExpression(Node parent)

	start = reader.mark();
	Mark stop = reader.skipUntil("%>");
	if (stop == null) {
	    err.jspError(start, "jsp.error.unterminated", "<%=");
	}

	new Node.Expression(parseScriptText(reader.getText(start, stop)),
			    start, parent);
    
private voidparseFallBack(Node parent)

	Node fallBackNode = new Node.FallBackAction(start, parent);
	parseOptionalBody(fallBackNode, "jsp:fallback", 
			  JAVAX_BODY_CONTENT_TEMPLATE_TEXT);
    
private voidparseForward(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();

        Node forwardNode = new Node.ForwardAction( attrs, start, parent );
        
        parseOptionalBody(forwardNode, "jsp:forward",
			  JAVAX_BODY_CONTENT_PARAM);
    
private voidparseGetProperty(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();

        Node getPropertyNode = new Node.GetProperty( attrs, start, parent );
        
        parseOptionalBody(getPropertyNode, "jsp:getProperty",
			  TagInfo.BODY_CONTENT_EMPTY);
    
private voidparseInclude(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();

        Node includeNode = new Node.IncludeAction( attrs, start, parent );
        
        parseOptionalBody(includeNode, "jsp:include", 
			  JAVAX_BODY_CONTENT_PARAM);
    
private voidparseIncludeDirective(Node parent)

	Attributes attrs = parseAttributes();

	// Included file expanded here
	Node includeNode = new Node.IncludeDirective(attrs, start, parent);
	processIncludeDirective(attrs.getValue("file"), includeNode);
    
private voidparseInvoke(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();

        Node invokeNode = new Node.InvokeAction(attrs, start, parent);
        
        parseEmptyBody(invokeNode, "jsp:invoke");
    
private booleanparseJspAttributeAndBody(Node parent, java.lang.String tag, java.lang.String bodyType)
Attempts to parse 'JspAttributeAndBody' production. Returns true if it matched, or false if not. Assumes EmptyBody is okay as well. JspAttributeAndBody ::= ( '>' # S? ( ' ) S? ETag )

        boolean result = false;
        
        if( reader.matchesOptionalSpacesFollowedBy( "<jsp:attribute" ) ) {
            // May be an EmptyBody, depending on whether
            // There's a "<jsp:body" before the ETag
            
            // First, parse <jsp:attribute> elements:
            parseNamedAttributes( parent );
            
            result = true;
        }
        
        if( reader.matchesOptionalSpacesFollowedBy( "<jsp:body" ) ) {
            // ActionBody
            parseJspBody( parent, bodyType );
            reader.skipSpaces();
            if( !reader.matchesETag( tag ) ) {
                err.jspError(reader.mark(), "jsp.error.unterminated", 
                    "<" + tag );
            }
            
            result = true;
        }
        else if( result && !reader.matchesETag( tag ) ) {
            // If we have <jsp:attribute> but something other than
            // <jsp:body> or the end tag, translation error.
            err.jspError(reader.mark(), "jsp.error.jspbody.required", 
                "<" + tag );
        }
        
        return result;
    
private voidparseJspBody(Node parent, java.lang.String bodyType)

        Mark start = reader.mark();
	Node bodyNode = new Node.JspBody(start, parent);

	reader.skipSpaces();
	if (!reader.matches("/>")) {
	    if (!reader.matches(">")) {
		err.jspError(start, "jsp.error.unterminated",
			     "<jsp:body");
	    }
	    parseBody( bodyNode, "jsp:body", bodyType );
	}
    
private voidparseJspParams(Node parent)

	Node jspParamsNode = new Node.ParamsAction(start, parent);
	parseOptionalBody(jspParamsNode, "jsp:params",
			  JAVAX_BODY_CONTENT_PARAM );
    
private java.lang.StringparseName()
Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*

	char ch = (char)reader.peekChar();
	if (Character.isLetter(ch) || ch == '_" || ch == ':") {
	    StringBuffer buf = new StringBuffer();
	    buf.append(ch);
	    reader.nextChar();
	    ch = (char)reader.peekChar();
	    while (Character.isLetter(ch) || Character.isDigit(ch) ||
			ch == '." || ch == '_" || ch == '-" || ch == ':") {
		buf.append(ch);
		reader.nextChar();
		ch = (char) reader.peekChar();
	    }
	    return buf.toString();
	}
	return null;
    
private voidparseNamedAttributes(Node parent)

        do {
            Mark start = reader.mark();
            Attributes attrs = parseAttributes();
            if (attrs == null || attrs.getValue("name") == null) {
                err.jspError(start, "jsp.error.jspAttribute.missing.name");
            }
            Node.NamedAttribute namedAttributeNode =
                new Node.NamedAttribute( attrs, start, parent );

            reader.skipSpaces();
	    if (!reader.matches("/>")) {
		if (!reader.matches(">")) {
		    err.jspError(start, "jsp.error.unterminated",
				 "<jsp:attribute");
		}
                if (namedAttributeNode.isTrim()) {
                    reader.skipSpaces();
                }
                parseBody(namedAttributeNode, "jsp:attribute", 
			  getAttributeBodyType(parent,
					       attrs.getValue("name")));
                if (namedAttributeNode.isTrim()) {
                    Node.Nodes subElems = namedAttributeNode.getBody();
		    if (subElems != null) {
			Node lastNode = subElems.getNode(subElems.size() - 1);
			if (lastNode instanceof Node.TemplateText) {
			    ((Node.TemplateText)lastNode).rtrim();
			}
		    }
                }
            }
            reader.skipSpaces();
        } while( reader.matches( "<jsp:attribute" ) );
    
private voidparseOptionalBody(Node parent, java.lang.String tag, java.lang.String bodyType)

	if (reader.matches("/>")) {
	    // EmptyBody
	    return;
	}

	if (!reader.matches(">")) {
	    err.jspError(reader.mark(), "jsp.error.unterminated",
			 "<" + tag );
	}
        
        if( reader.matchesETag( tag ) ) {
            // EmptyBody
            return;
        }
        
        if( !parseJspAttributeAndBody( parent, tag, bodyType ) ) {
            // Must be ( '>' # Body ETag )
            parseBody(parent, tag, bodyType );
        }
    
private voidparsePageDirective(Node parent)

	Attributes attrs = parseAttributes();
	Node.PageDirective n = new Node.PageDirective(attrs, start, parent);

	/*
	 * 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));
	    }
	}
    
private voidparseParam(Node parent)
Param ::= '

	if (!reader.matches("<jsp:param")) {
	    err.jspError(reader.mark(), "jsp.error.paramexpected");
	}
	Attributes attrs = parseAttributes();
	reader.skipSpaces();
        
        Node paramActionNode = new Node.ParamAction( attrs, start, parent );
        
        parseEmptyBody( paramActionNode, "jsp:param" );
        
        reader.skipSpaces();
    
private voidparsePlugin(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();
        
	Node pluginNode = new Node.PlugIn(attrs, start, parent);
        
        parseOptionalBody( pluginNode, "jsp:plugin", 
            JAVAX_BODY_CONTENT_PLUGIN );
    
private voidparsePluginTags(Node parent)

        reader.skipSpaces();
        
        if( reader.matches( "<jsp:params" ) ) {
            parseJspParams( parent );
            reader.skipSpaces();
        }
        
        if( reader.matches( "<jsp:fallback" ) ) {
            parseFallBack( parent );
            reader.skipSpaces();
        }
    
private java.lang.StringparseQuoted(java.lang.String tx)
QuotedChar ::= ''' | '"' | '\\' | '\"' | "\'" | '\>' | '\$' | '\#' | Char

	StringBuffer buf = new StringBuffer();
	int size = tx.length();
	int i = 0;
	while (i < size) {
	    char ch = tx.charAt(i);
	    if (ch == '&") {
		if (i+5 < size && tx.charAt(i+1) == 'a"
		        && tx.charAt(i+2) == 'p" && tx.charAt(i+3) == 'o"
		        && tx.charAt(i+4) == 's" && tx.charAt(i+5) == ';") {
		    buf.append('\'");
		    i += 6;
		} else if (i+5 < size && tx.charAt(i+1) == 'q"
			   && tx.charAt(i+2) == 'u" && tx.charAt(i+3) == 'o"
			   && tx.charAt(i+4) == 't" && tx.charAt(i+5) == ';") {
		    buf.append('"");
		    i += 6;
		} else {
		    buf.append(ch);
		    ++i;
		}
	    } else if (ch == '\\" && i+1 < size) {
		ch = tx.charAt(i+1);
		if (ch == '\\" || ch == '\"" || ch == '\'" || ch == '>") {
		    buf.append(ch);
		    i += 2;
		} else {
		    buf.append('\\");
		    ++i;
		}
	    } else {
		buf.append(ch);
		++i;
	    }
	}
	return buf.toString();
    
private java.lang.StringparseScriptText(java.lang.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();
    
private voidparseScriptlet(Node parent)

	start = reader.mark();
	Mark stop = reader.skipUntil("%>");
	if (stop == null) {
	    err.jspError(start, "jsp.error.unterminated", "<%");
	}

	new Node.Scriptlet(parseScriptText(reader.getText(start, stop)),
			   start, parent);
    
private voidparseSetProperty(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();

        Node setPropertyNode = new Node.SetProperty( attrs, start, parent );
        
        parseOptionalBody(setPropertyNode, "jsp:setProperty",
			  TagInfo.BODY_CONTENT_EMPTY);
    
private voidparseStandardAction(Node parent)

	Mark start = reader.mark();

	if (reader.matches(INCLUDE_ACTION)) {
	    parseInclude(parent);
	} else if (reader.matches(FORWARD_ACTION)) {
	    parseForward(parent);
	} else if (reader.matches(INVOKE_ACTION)) {
	    if (!isTagFile) {
		err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
			     "<jsp:invoke");
	    }
	    parseInvoke(parent);
	} else if (reader.matches(DOBODY_ACTION)) {
	    if (!isTagFile) {
		err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
			     "<jsp:doBody");
	    }
	    parseDoBody(parent);
	} else if (reader.matches(GET_PROPERTY_ACTION)) {
	    parseGetProperty(parent);
	} else if (reader.matches(SET_PROPERTY_ACTION)) {
	    parseSetProperty(parent);
	} else if (reader.matches(USE_BEAN_ACTION)) {
	    parseUseBean(parent);
	} else if (reader.matches(PLUGIN_ACTION)) {
	    parsePlugin(parent);
	} else if (reader.matches(ELEMENT_ACTION)) {
	    parseElement(parent);
	} else if (reader.matches(ATTRIBUTE_ACTION)) {
	    err.jspError(start, "jsp.error.namedAttribute.invalidUse");
	} else if (reader.matches(BODY_ACTION)) {
	    err.jspError(start, "jsp.error.jspbody.invalidUse");
	} else if (reader.matches(FALLBACK_ACTION)) {
	    err.jspError(start, "jsp.error.fallback.invalidUse");
	} else if (reader.matches(PARAMS_ACTION)) {
	    err.jspError(start, "jsp.error.params.invalidUse");
	} else if (reader.matches(PARAM_ACTION)) {
	    err.jspError(start, "jsp.error.param.invalidUse");
	} else if (reader.matches(OUTPUT_ACTION)) {
	    err.jspError(start, "jsp.error.jspoutput.invalidUse");
	} else {
	    err.jspError(start, "jsp.error.badStandardAction");
	}
    
private voidparseTagDependentBody(Node parent, java.lang.String tag)
TagDependentBody :=

	Mark bodyStart = reader.mark();
	Mark bodyEnd = reader.skipUntilETag(tag);
	if (bodyEnd == null) {
	    err.jspError(start, "jsp.error.unterminated", "<"+tag );
	}
	new Node.TemplateText(reader.getText(bodyStart, bodyEnd), bodyStart,
			      parent);
    
private voidparseTagDirective(Node parent)

	Attributes attrs = parseAttributes();
	Node.TagDirective n = new Node.TagDirective(attrs, start, parent);

        /*
         * 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));
            }
        }
    
private voidparseTagFileDirectives(Node parent)

	reader.setSingleFile(true);
	reader.skipUntil("<");
        while (reader.hasMoreInput()) {
            start = reader.mark();
            if (reader.matches("%--")) {
                parseComment(parent);
            } else if (reader.matches("%@")) {
                parseDirective(parent);
            } else if (reader.matches("jsp:directive.")) {
                parseXMLDirective(parent);
            }
	    reader.skipUntil("<");
	}
    
private voidparseTaglibDirective(Node parent)


	Attributes attrs = parseAttributes();
	String uri = attrs.getValue("uri");
	String prefix = attrs.getValue("prefix");
	if (prefix != null) {
            Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix);
            if (prevMark != null) {
                err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl",
                    prefix, prevMark.getFile(), "" + prevMark.getLineNumber());
            }
	    if (uri != null) {
		String uriPrev = pageInfo.getURI(prefix);
		if (uriPrev != null && !uriPrev.equals(uri)) {
		    err.jspError(reader.mark(), "jsp.error.prefix.refined",
			prefix, uri, uriPrev);
		}
                /* GlassFish 750
		if (pageInfo.getTaglib(uri) == null) {
		    String[] location = ctxt.getTldLocation(uri);
                    TagLibraryInfoImpl taglib = null;
                    try {
		        taglib = new TagLibraryInfoImpl(ctxt,
                                                        parserController,
                                                        prefix,
                                                        uri,
                                                        location,
                                                        err);
                    } catch (JasperException je) {
                        err.throwException(reader.mark(), je);
                    }
		    pageInfo.addTaglib(uri, taglib);
		}
                */
                // START GlassFish 750
                ConcurrentHashMap<String, TagLibraryInfoImpl> taglibs =
                    ctxt.getTaglibs();
                TagLibraryInfoImpl taglib = taglibs.get(uri);
                if (taglib == null) {
                    synchronized (taglibs) {
                        taglib = taglibs.get(uri);
                        if (taglib == null) {
                            String[] location = ctxt.getTldLocation(uri);
                            try {
                                taglib = new TagLibraryInfoImpl(
                                                        ctxt,
                                                        parserController,
                                                        prefix,
                                                        uri,
                                                        location,
                                                        err);
                            } catch (JasperException je) {
                                err.throwException(reader.mark(), je);
                            }
                            ctxt.addTaglib(uri, taglib);
                            pageInfo.addTaglib(uri, taglib);
                        }
                    }
                }
                if (pageInfo.getTaglib(uri) == null) {
                    pageInfo.addTaglib(uri,
                                       new TagLibraryInfoImpl(prefix,
                                                              uri,
                                                              taglib,
                                                              pageInfo));
                }
                // END GlassFish 750  
		pageInfo.addPrefixMapping(prefix, uri);
	    } else {
		String tagdir = attrs.getValue("tagdir");
		if (tagdir != null) {
		    String urnTagdir = URN_JSPTAGDIR + tagdir;
		    if (pageInfo.getTaglib(urnTagdir) == null) {
			pageInfo.addTaglib(urnTagdir,
					   new ImplicitTagLibraryInfo(
                                                   ctxt,
						   parserController,
						   prefix, 
						   tagdir,
						   err));
		    }
		    pageInfo.addPrefixMapping(prefix, urnTagdir);
		}
	    }
	}

	new Node.TaglibDirective(attrs, start, parent);
    
private voidparseTemplateText(Node parent)


	if (!reader.hasMoreInput())
	    return;

	CharArrayWriter ttext = new CharArrayWriter();
	// Output the first character
	int ch = reader.nextChar();
        if (ch == '\\") {
            reader.pushChar();
        } else {
            ttext.write(ch);
        }

	while (reader.hasMoreInput()) {
	    ch = reader.nextChar();
	    if (ch == '<") {
                reader.pushChar();
                break;
            }
	    else if( ch == '$" || ch == '#") {
		if (!reader.hasMoreInput()) {
		    ttext.write(ch);
		    break;
                }
		if (reader.nextChar() == '{") {
		    reader.pushChar();
		    reader.pushChar();
		    break;
		}
		ttext.write(ch);
		reader.pushChar();
		continue;
	    }
	    else if (ch == '\\") {
		if (!reader.hasMoreInput()) {
		    ttext.write('\\");
		    break;
		}
                char next = (char)reader.peekChar();
                // Looking for \% or \$
                // Note that this behavior can be altered by the attributes
                // el-ignored and deferred-syntax-allowed-as-literal and
                // similar attributes in a page directive.  However, since
                // the page direcitve may appear later in the same page, the
                // '\' will be regenerated in Generator.java.
                if (next == '%" || next == '$" || next == '#") {
                    ch = reader.nextChar();
                }
	    }
	    ttext.write(ch);
	}
	new Node.TemplateText(ttext.toString(), start, parent);
    
private voidparseUseBean(Node parent)

	Attributes attrs = parseAttributes();
	reader.skipSpaces();
        
        Node useBeanNode = new Node.UseBean( attrs, start, parent );
        
        parseOptionalBody( useBeanNode, "jsp:useBean", 
            TagInfo.BODY_CONTENT_JSP );
    
private voidparseVariableDirective(Node parent)

	Attributes attrs = parseAttributes();
        new Node.VariableDirective(attrs, start, parent);
    
private voidparseXMLDeclaration(Node parent)

        reader.skipSpaces();
        if( !reader.matches( "/>" ) ) {
            if( !reader.matches( ">" ) ) {
                err.jspError(start, "jsp.error.unterminated",
                        "<jsp:declaration>");
            }
	    Mark stop;
            String text;
            while (true) {
                start = reader.mark();
                stop = reader.skipUntil("<");
                if (stop == null) {
                    err.jspError(start, "jsp.error.unterminated",
                        "<jsp:declaration>");
                }
		text = parseScriptText(reader.getText(start, stop));
                new Node.Declaration(text, start, parent);
                if (reader.matches("![CDATA[")) {
                    start = reader.mark();
                    stop = reader.skipUntil("]]>");
                    if (stop == null) {
                        err.jspError(start, "jsp.error.unterminated", "CDATA");
                    }
		    text = parseScriptText(reader.getText(start, stop));
                    new Node.Declaration(text, start, parent);
                }
                else {
                    break;
                }
	    }
		
            if (!reader.matchesETagWithoutLessThan( "jsp:declaration" ) ) {
                err.jspError(start, "jsp.error.unterminated",
                        "<jsp:declaration>");
            }
        }
    
private voidparseXMLDirective(Node parent)

       reader.skipSpaces();

        String eTag = null;
       if (reader.matches("page")) {
            eTag = "jsp:directive.page";
           if (isTagFile) {
               err.jspError(reader.mark(), "jsp.error.directive.istagfile",
                                           "<" + eTag);
           }
           parsePageDirective(parent);
       } else if (reader.matches("include")) {
            eTag = "jsp:directive.include";
           parseIncludeDirective(parent);
       } else if (reader.matches("tag")) {
            eTag = "jsp:directive.tag";
           if (!isTagFile) {
               err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
                                           "<" + eTag);
           }
           parseTagDirective(parent);
       } else if (reader.matches("attribute")) {
            eTag = "jsp:directive.attribute";
           if (!isTagFile) {
               err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
                                           "<" + eTag);
           }
           parseAttributeDirective(parent);
       } else if (reader.matches("variable")) {
            eTag = "jsp:directive.variable";
           if (!isTagFile) {
               err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
                                           "<" + eTag);
           }
           parseVariableDirective(parent);
       } else {
	   err.jspError(reader.mark(), "jsp.error.invalid.directive",
               reader.parseToken(false));
       }

       reader.skipSpaces();
        if( reader.matches( ">" ) ) {
            reader.skipSpaces();
            if( !reader.matchesETag( eTag ) ) {
                err.jspError(start, "jsp.error.unterminated", "<" + eTag );
            }
        }
        else if( !reader.matches( "/>" ) ) {
            err.jspError(start, "jsp.error.unterminated", "<" + eTag );
        }
    
private voidparseXMLExpression(Node parent)

        reader.skipSpaces();
        if( !reader.matches( "/>" ) ) {
            if( !reader.matches( ">" ) ) {
                err.jspError(start, "jsp.error.unterminated",
                    "<jsp:expression>");
            }
            Mark stop;
            String text;
            while (true) {
                start = reader.mark();
                stop = reader.skipUntil("<");
                if (stop == null) {
                    err.jspError(start, "jsp.error.unterminated",
                        "<jsp:expression>");
                }
                text = parseScriptText(reader.getText(start, stop));
                new Node.Expression(text, start, parent);
                if (reader.matches("![CDATA[")) {
                    start = reader.mark();
                    stop = reader.skipUntil("]]>");
                    if (stop == null) {
                        err.jspError(start, "jsp.error.unterminated", "CDATA");
                    }
                    text = parseScriptText(reader.getText(start, stop));
                    new Node.Expression(text, start, parent);
                }
                else {
                    break;
                }
            }
            if (!reader.matchesETagWithoutLessThan( "jsp:expression" )) {
                err.jspError(start, "jsp.error.unterminated",
                    "<jsp:expression>");
            }
        }
    
private voidparseXMLScriptlet(Node parent)

        reader.skipSpaces();
        if( !reader.matches( "/>" ) ) {
            if( !reader.matches( ">" ) ) {
                err.jspError(start, "jsp.error.unterminated",
                    "<jsp:scriptlet>");
            }
            Mark stop;
            String text;
            while (true) {
                start = reader.mark();
                stop = reader.skipUntil("<");
                if (stop == null) {
                    err.jspError(start, "jsp.error.unterminated",
                        "<jsp:scriptlet>");
                }
                text = parseScriptText(reader.getText(start, stop));
                new Node.Scriptlet(text, start, parent);
                if (reader.matches("![CDATA[")) {
                    start = reader.mark();
                    stop = reader.skipUntil("]]>");
                    if (stop == null) {
                        err.jspError(start, "jsp.error.unterminated", "CDATA");
                    }
                    text = parseScriptText(reader.getText(start, stop));
                    new Node.Scriptlet(text, start, parent);
                }
                else {
                    break;
                }
            }

            if (!reader.matchesETagWithoutLessThan( "jsp:scriptlet" )) {
                err.jspError(start, "jsp.error.unterminated",
                    "<jsp:scriptlet>");
            }
        }
    
private voidparseXMLTemplateText(Node parent)

        reader.skipSpaces();
        if( !reader.matches( "/>" ) ) {
            if( !reader.matches( ">" ) ) {
                err.jspError(start, "jsp.error.unterminated",
                    "<jsp:text>" );
            }
            CharArrayWriter ttext = new CharArrayWriter();
            while (reader.hasMoreInput()) {
        	int ch = reader.nextChar();
                if( ch == '<" ) {
                    // Check for <![CDATA[
                    if (!reader.matches("![CDATA[")) {
                        break;
                    }
                    start = reader.mark();
                    Mark stop = reader.skipUntil("]]>");
                    if (stop == null) {
                        err.jspError(start, "jsp.error.unterminated", "CDATA");
                    }
                    String text = reader.getText(start, stop);
                    ttext.write(text, 0, text.length());
                }
                else if( ch == '\\") {
                    if (!reader.hasMoreInput()) {
                        ttext.write('\\");
                        break;
		    }
                    ch = reader.nextChar();
                    if (ch != '$" && ch != '#") {
                        ttext.write('\\");
                    }
                    ttext.write(ch);
                }
                else if( ch == '$" || ch == '#") {
                    if (!reader.hasMoreInput()) {
                        ttext.write(ch);
                        break;
                    }
                    if (reader.nextChar() != '{") {
                        ttext.write(ch);
                        reader.pushChar();
                        continue;
                    }
                    // Create a template text node
                    new Node.TemplateText( ttext.toString(), start, parent);

                    // Mark and parse the EL expression and create its node:
                    start = reader.mark();
                    parseELExpression(parent, (ch == '$")? "${": "#{");

                    start = reader.mark();
                    ttext = new CharArrayWriter();
                }
                else {
                    ttext.write( ch );
                }
            }

            new Node.TemplateText( ttext.toString(), start, parent );

	    if (! reader.hasMoreInput()) {
                err.jspError( start, "jsp.error.unterminated",
                    "<jsp:text>" );
	    } else if( !reader.matchesETagWithoutLessThan( "jsp:text" ) ) {
                err.jspError( start, "jsp.error.jsptext.badcontent");
            }
        }
    
private voidprocessIncludeDirective(java.lang.String file, Node parent)

	if (file == null) {
	    return;
	}

	try {
	    parserController.parse(file, parent, jarFileUrl);
	} catch (FileNotFoundException ex) {
	    err.jspError(start, "jsp.error.file.not.found", file);
	} catch (Exception ex) {
	    err.jspError(start, ex);
	}