| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * Licensed 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 java.io.CharArrayWriter; |
| import java.io.FileNotFoundException; |
| import java.net.URL; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import javax.servlet.jsp.tagext.TagAttributeInfo; |
| import javax.servlet.jsp.tagext.TagFileInfo; |
| import javax.servlet.jsp.tagext.TagInfo; |
| import javax.servlet.jsp.tagext.TagLibraryInfo; |
| |
| import org.apache.jasper.Constants; |
| import org.apache.jasper.JasperException; |
| import org.apache.jasper.JspCompilationContext; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.helpers.AttributesImpl; |
| |
| /** |
| * 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 |
| */ |
| |
| 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"; |
| |
| /** |
| * The constructor |
| */ |
| private Parser(ParserController pc, JspReader reader, boolean isTagFile, |
| boolean directivesOnly, URL jarFileUrl) { |
| this.parserController = pc; |
| this.ctxt = pc.getJspCompilationContext(); |
| this.pageInfo = pc.getCompiler().getPageInfo(); |
| this.err = pc.getCompiler().getErrorDispatcher(); |
| this.reader = reader; |
| this.currentFile = reader.mark().getFile(); |
| this.scriptlessCount = 0; |
| this.isTagFile = isTagFile; |
| this.directivesOnly = directivesOnly; |
| this.jarFileUrl = jarFileUrl; |
| start = reader.mark(); |
| } |
| |
| /** |
| * 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 |
| */ |
| public static Node.Nodes parse(ParserController pc, |
| JspReader reader, |
| Node parent, |
| boolean isTagFile, |
| boolean directivesOnly, |
| URL jarFileUrl, |
| String pageEnc, |
| String jspConfigPageEnc, |
| boolean isDefaultPageEncoding) |
| throws JasperException { |
| |
| Parser parser = new Parser(pc, reader, isTagFile, directivesOnly, |
| jarFileUrl); |
| |
| Node.Root root = new Node.Root(reader.mark(), parent, false); |
| root.setPageEncoding(pageEnc); |
| root.setJspConfigPageEncoding(jspConfigPageEnc); |
| root.setIsDefaultPageEncoding(isDefaultPageEncoding); |
| |
| 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()); |
| } |
| |
| Node.Nodes page = new Node.Nodes(root); |
| return page; |
| } |
| |
| /** |
| * Attributes ::= (S Attribute)* S? |
| */ |
| Attributes parseAttributes() throws JasperException { |
| AttributesImpl attrs = new AttributesImpl(); |
| |
| reader.skipSpaces(); |
| while (parseAttribute(attrs)) |
| reader.skipSpaces(); |
| |
| return attrs; |
| } |
| |
| /** |
| * Parse Attributes for a reader, provided for external use |
| */ |
| public static Attributes parseAttributes(ParserController pc, |
| JspReader reader) |
| throws JasperException { |
| Parser tmpParser = new Parser(pc, reader, false, false, null); |
| 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 attrs) |
| throws 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 = 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; |
| } |
| |
| /** |
| * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')* |
| */ |
| private String parseName() throws JasperException { |
| 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; |
| } |
| |
| /** |
| * AttributeValueDouble ::= (QuotedChar - '"')* |
| * ('"' | <TRANSLATION_ERROR>) |
| * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"') |
| * ('%>"' | TRANSLATION_ERROR) |
| */ |
| private String parseAttributeValue(String watch) throws JasperException { |
| 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 + "%>"; |
| } |
| |
| /** |
| * QuotedChar ::= ''' |
| * | '"' |
| * | '\\' |
| * | '\"' |
| * | "\'" |
| * | '\>' |
| * | '\$' |
| * | Char |
| */ |
| private String parseQuoted(String tx) { |
| 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 if (ch == '$') { |
| // Replace "\$" with some special char. XXX hack! |
| buf.append(Constants.ESC); |
| i += 2; |
| } else { |
| buf.append('\\'); |
| ++i; |
| } |
| } else { |
| buf.append(ch); |
| ++i; |
| } |
| } |
| return buf.toString(); |
| } |
| |
| 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 file, Node parent) |
| throws JasperException { |
| 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.getMessage()); |
| } |
| } |
| |
| /* |
| * Parses a page directive with the following syntax: |
| * PageDirective ::= ( S Attribute)* |
| */ |
| private void parsePageDirective(Node parent) throws JasperException { |
| 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)); |
| } |
| } |
| } |
| |
| /* |
| * Parses an include directive with the following syntax: |
| * IncludeDirective ::= ( S Attribute)* |
| */ |
| private void parseIncludeDirective(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| |
| // Included file expanded here |
| Node includeNode = new Node.IncludeDirective(attrs, start, parent); |
| 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 parent, List files) throws JasperException { |
| 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); |
| } |
| } |
| } |
| |
| /* |
| * Parses a taglib directive with the following syntax: |
| * Directive ::= ( S Attribute)* |
| */ |
| private void parseTaglibDirective(Node parent) throws JasperException { |
| |
| 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); |
| } |
| if (pageInfo.getTaglib(uri) == null) { |
| String[] location = ctxt.getTldLocation(uri); |
| pageInfo.addTaglib(uri, |
| new TagLibraryInfoImpl(ctxt, |
| parserController, |
| prefix, |
| uri, |
| location, |
| err)); |
| } |
| 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); |
| } |
| |
| /* |
| * 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 parent) throws JasperException { |
| 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.skipSpaces(); |
| if (!reader.matches("%>")) { |
| err.jspError(start, "jsp.error.unterminated", 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 parent) throws JasperException { |
| 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.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 ); |
| } |
| } |
| |
| /* |
| * Parses a tag directive with the following syntax: |
| * PageDirective ::= ( S Attribute)* |
| */ |
| private void parseTagDirective(Node parent) throws JasperException { |
| 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)); |
| } |
| } |
| } |
| |
| /* |
| * Parses a attribute directive with the following syntax: |
| * AttributeDirective ::= ( S Attribute)* |
| */ |
| private void parseAttributeDirective(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| Node.AttributeDirective n = |
| new Node.AttributeDirective(attrs, start, parent); |
| } |
| |
| /* |
| * Parses a variable directive with the following syntax: |
| * PageDirective ::= ( S Attribute)* |
| */ |
| private void parseVariableDirective(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| Node.VariableDirective n = |
| new Node.VariableDirective(attrs, start, parent); |
| } |
| |
| /* |
| * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>' |
| */ |
| private void parseComment(Node parent) throws JasperException { |
| 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); |
| } |
| |
| /* |
| * DeclarationBody ::= (Char* - (char* '%>')) '%>' |
| */ |
| private void parseDeclaration(Node parent) throws JasperException { |
| 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); |
| } |
| |
| /* |
| * XMLDeclarationBody ::= ( S? '/>' ) |
| * | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag |
| * | <TRANSLATION_ERROR> |
| * CDSect ::= CDStart CData CDEnd |
| * CDStart ::= '<![CDATA[' |
| * CData ::= (Char* - (Char* ']]>' Char*)) |
| * CDEnd ::= ']]>' |
| */ |
| private void parseXMLDeclaration(Node parent) throws JasperException { |
| 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>"); |
| } |
| } |
| } |
| |
| /* |
| * ExpressionBody ::= (Char* - (char* '%>')) '%>' |
| */ |
| private void parseExpression(Node parent) throws JasperException { |
| 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); |
| } |
| |
| /* |
| * XMLExpressionBody ::= ( S? '/>' ) |
| * | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag ) |
| * | <TRANSLATION_ERROR> |
| */ |
| private void parseXMLExpression(Node parent) throws JasperException { |
| 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>"); |
| } |
| } |
| } |
| |
| /* |
| * ELExpressionBody |
| * (following "${" to first unquoted "}") |
| * // XXX add formal production and confirm implementation against it, |
| * // once it's decided |
| */ |
| private void parseELExpression(Node parent) throws JasperException { |
| start = reader.mark(); |
| Mark last = null; |
| boolean singleQuoted = false, doubleQuoted = false; |
| int currentChar; |
| do { |
| // XXX could move this logic to JspReader |
| last = reader.mark(); // XXX somewhat wasteful |
| 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", "${"); |
| if (currentChar == '"') |
| doubleQuoted = !doubleQuoted; |
| if (currentChar == '\'') |
| singleQuoted = !singleQuoted; |
| } while (currentChar != '}' || (singleQuoted || doubleQuoted)); |
| |
| new Node.ELExpression(reader.getText(start, last), start, parent); |
| } |
| |
| /* |
| * ScriptletBody ::= (Char* - (char* '%>')) '%>' |
| */ |
| private void parseScriptlet(Node parent) throws JasperException { |
| 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); |
| } |
| |
| /* |
| * XMLScriptletBody ::= ( S? '/>' ) |
| * | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag ) |
| * | <TRANSLATION_ERROR> |
| */ |
| private void parseXMLScriptlet(Node parent) throws JasperException { |
| 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>"); |
| } |
| } |
| } |
| |
| /** |
| * Param ::= '<jsp:param' S Attributes S? EmptyBody S? |
| */ |
| private void parseParam(Node parent) throws JasperException { |
| 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(); |
| } |
| |
| /* |
| * 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 parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node includeNode = new Node.IncludeAction( attrs, start, parent ); |
| |
| parseOptionalBody(includeNode, "jsp:include", |
| JAVAX_BODY_CONTENT_PARAM); |
| } |
| |
| /* |
| * For Forward: |
| * StdActionContent ::= Attributes ParamBody |
| */ |
| private void parseForward(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node forwardNode = new Node.ForwardAction( attrs, start, parent ); |
| |
| parseOptionalBody(forwardNode, "jsp:forward", |
| JAVAX_BODY_CONTENT_PARAM); |
| } |
| |
| private void parseInvoke(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node invokeNode = new Node.InvokeAction(attrs, start, parent); |
| |
| parseEmptyBody(invokeNode, "jsp:invoke"); |
| } |
| |
| private void parseDoBody(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node doBodyNode = new Node.DoBodyAction(attrs, start, parent); |
| |
| parseEmptyBody(doBodyNode, "jsp:doBody"); |
| } |
| |
| private void parseElement(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node elementNode = new Node.JspElement(attrs, start, parent); |
| |
| parseOptionalBody( elementNode, "jsp:element", |
| TagInfo.BODY_CONTENT_JSP ); |
| } |
| |
| /* |
| * For GetProperty: |
| * StdActionContent ::= Attributes EmptyBody |
| */ |
| private void parseGetProperty(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node getPropertyNode = new Node.GetProperty( attrs, start, parent ); |
| |
| parseOptionalBody(getPropertyNode, "jsp:getProperty", |
| TagInfo.BODY_CONTENT_EMPTY); |
| } |
| |
| /* |
| * For SetProperty: |
| * StdActionContent ::= Attributes EmptyBody |
| */ |
| private void parseSetProperty(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node setPropertyNode = new Node.SetProperty( attrs, start, parent ); |
| |
| parseOptionalBody(setPropertyNode, "jsp:setProperty", |
| TagInfo.BODY_CONTENT_EMPTY); |
| } |
| |
| /* |
| * EmptyBody ::= '/>' |
| * | ( '>' ETag ) |
| * | ( '>' S? '<jsp:attribute' NamedAttributes ETag ) |
| */ |
| private void parseEmptyBody( Node parent, String tag ) |
| throws JasperException |
| { |
| 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 ); |
| } |
| } |
| |
| /* |
| * For UseBean: |
| * StdActionContent ::= Attributes OptionalBody |
| */ |
| private void parseUseBean(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node useBeanNode = new Node.UseBean( attrs, start, parent ); |
| |
| parseOptionalBody( useBeanNode, "jsp:useBean", |
| TagInfo.BODY_CONTENT_JSP ); |
| } |
| |
| /* |
| * 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 parent, String tag, String bodyType ) |
| throws JasperException |
| { |
| 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 ); |
| } |
| } |
| |
| /** |
| * Attempts to parse 'JspAttributeAndBody' production. Returns true if |
| * it matched, or false if not. Assumes EmptyBody is okay as well. |
| * |
| * JspAttributeAndBody ::= |
| * ( '>' # S? ( '<jsp:attribute' NamedAttributes )? |
| * '<jsp:body' |
| * ( JspBodyBody | <TRANSLATION_ERROR> ) |
| * S? ETag |
| * ) |
| */ |
| private boolean parseJspAttributeAndBody( Node parent, String tag, |
| String bodyType ) |
| throws JasperException |
| { |
| 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; |
| } |
| |
| /* |
| * Params ::= `>' S? |
| * ( ( `<jsp:body>' |
| * ( ( S? Param+ S? `</jsp:body>' ) |
| * | <TRANSLATION_ERROR> |
| * ) |
| * ) |
| * | Param+ |
| * ) |
| * '</jsp:params>' |
| */ |
| private void parseJspParams(Node parent) throws JasperException { |
| Node jspParamsNode = new Node.ParamsAction(start, parent); |
| parseOptionalBody(jspParamsNode, "jsp:params", |
| JAVAX_BODY_CONTENT_PARAM ); |
| } |
| |
| /* |
| * Fallback ::= '/>' |
| * | ( `>' S? `<jsp:body>' |
| * ( ( S? |
| * ( Char* - ( Char* `</jsp:body>' ) ) |
| * `</jsp:body>' S? |
| * ) |
| * | <TRANSLATION_ERROR> |
| * ) |
| * `</jsp:fallback>' |
| * ) |
| * | ( '>' |
| * ( Char* - ( Char* '</jsp:fallback>' ) ) |
| * '</jsp:fallback>' |
| * ) |
| */ |
| private void parseFallBack(Node parent) throws JasperException { |
| Node fallBackNode = new Node.FallBackAction(start, parent); |
| parseOptionalBody(fallBackNode, "jsp:fallback", |
| JAVAX_BODY_CONTENT_TEMPLATE_TEXT); |
| } |
| |
| /* |
| * For Plugin: |
| * StdActionContent ::= Attributes PluginBody |
| * |
| * PluginBody ::= EmptyBody |
| * | ( '>' S? ( '<jsp:attribute' NamedAttributes )? |
| * '<jsp:body' |
| * ( JspBodyPluginTags | <TRANSLATION_ERROR> ) |
| * S? ETag |
| * ) |
| * | ( '>' S? PluginTags ETag ) |
| * |
| * EmptyBody ::= '/>' |
| * | ( '>' ETag ) |
| * | ( '>' S? '<jsp:attribute' NamedAttributes ETag ) |
| * |
| */ |
| private void parsePlugin(Node parent) throws JasperException { |
| Attributes attrs = parseAttributes(); |
| reader.skipSpaces(); |
| |
| Node pluginNode = new Node.PlugIn(attrs, start, parent); |
| |
| parseOptionalBody( pluginNode, "jsp:plugin", |
| JAVAX_BODY_CONTENT_PLUGIN ); |
| } |
| |
| /* |
| * PluginTags ::= ( '<jsp:params' Params S? )? |
| * ( '<jsp:fallback' Fallback? S? )? |
| */ |
| private void parsePluginTags( Node parent ) throws JasperException { |
| reader.skipSpaces(); |
| |
| if( reader.matches( "<jsp:params" ) ) { |
| parseJspParams( parent ); |
| reader.skipSpaces(); |
| } |
| |
| if( reader.matches( "<jsp:fallback" ) ) { |
| parseFallBack( parent ); |
| reader.skipSpaces(); |
| } |
| } |
| |
| /* |
| * StandardAction ::= 'include' StdActionContent |
| * | 'forward' StdActionContent |
| * | 'invoke' StdActionContent |
| * | 'doBody' StdActionContent |
| * | 'getProperty' StdActionContent |
| * | 'setProperty' StdActionContent |
| * | 'useBean' StdActionContent |
| * | 'plugin' StdActionContent |
| * | 'element' StdActionContent |
| */ |
| private void parseStandardAction(Node parent) throws JasperException { |
| 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"); |
| } |
| } |
| |
| /* |
| * # '<' CustomAction CustomActionBody |
| * |
| * CustomAction ::= TagPrefix ':' CustomActionName |
| * |
| * TagPrefix ::= Name |
| * |
| * CustomActionName ::= Name |
| * |
| * CustomActionBody ::= ( Attributes CustomActionEnd ) |
| * | <TRANSLATION_ERROR> |
| * |
| * Attributes ::= ( S Attribute )* S? |
| * |
| * CustomActionEnd ::= CustomActionTagDependent |
| * | CustomActionJSPContent |
| * | CustomActionScriptlessContent |
| * |
| * CustomActionTagDependent ::= TagDependentOptionalBody |
| * |
| * CustomActionJSPContent ::= OptionalBody |
| * |
| * CustomActionScriptlessContent ::= ScriptlessOptionalBody |
| */ |
| private boolean parseCustomTag(Node parent) throws JasperException { |
| |
| 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(tagName, prefix, shortTagName, uri, attrs, |
| start, parent, tagInfo, tagHandlerClass); |
| } else { |
| new Node.CustomTag(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(tagName, prefix, shortTagName, uri, |
| attrs, start, parent, tagInfo, |
| tagHandlerClass); |
| } else { |
| tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri, |
| attrs, start, parent, tagFileInfo); |
| } |
| |
| parseOptionalBody( tagNode, tagName, bc ); |
| |
| return true; |
| } |
| |
| /* |
| * Parse for a template text string until '<' or "${" is encountered, |
| * recognizing escape sequences "\%" and "\$". |
| */ |
| private void parseTemplateText(Node parent) throws JasperException { |
| |
| if (!reader.hasMoreInput()) |
| return; |
| |
| CharArrayWriter ttext = new CharArrayWriter(); |
| // Output the first character |
| int ch = reader.nextChar(); |
| ttext.write(ch); |
| |
| while (reader.hasMoreInput()) { |
| ch = reader.nextChar(); |
| if (ch == '<') { |
| reader.pushChar(); |
| break; |
| } |
| else if( ch == '$' ) { |
| if (!reader.hasMoreInput()) { |
| ttext.write('$'); |
| break; |
| } |
| ch = reader.nextChar(); |
| if (ch == '{') { |
| reader.pushChar(); |
| reader.pushChar(); |
| break; |
| } |
| ttext.write('$'); |
| reader.pushChar(); |
| continue; |
| } |
| else if (ch == '\\') { |
| if (!reader.hasMoreInput()) { |
| ttext.write('\\'); |
| break; |
| } |
| char next = (char)reader.peekChar(); |
| // Looking for \% or \$ |
| // TODO: only recognize \$ if isELIgnored is false, but since |
| // it can be set in a page directive, it cannot be determined |
| // here. Argh! |
| if (next == '%' || next == '$') { |
| ch = reader.nextChar(); |
| } |
| } |
| ttext.write(ch); |
| } |
| new Node.TemplateText(ttext.toString(), start, parent); |
| } |
| |
| /* |
| * XMLTemplateText ::= ( S? '/>' ) |
| * | ( S? '>' |
| * ( ( Char* - ( Char* ( '<' | '${' ) ) ) |
| * ( '${' ELExpressionBody )? |
| * CDSect? |
| * )* ETag |
| * ) |
| * | <TRANSLATION_ERROR> |
| */ |
| private void parseXMLTemplateText(Node parent) throws JasperException { |
| 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 != '$' ) { |
| ttext.write('\\'); |
| } |
| ttext.write(ch); |
| } |
| else if( ch == '$' ) { |
| if (!reader.hasMoreInput()) { |
| ttext.write('$'); |
| break; |
| } |
| ch = reader.nextChar(); |
| if (ch != '{') { |
| ttext.write('$'); |
| 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); |
| |
| 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"); |
| } |
| } |
| } |
| |
| /* |
| * AllBody ::= ( '<%--' JSPCommentBody ) |
| * | ( '<%@' DirectiveBody ) |
| * | ( '<jsp:directive.' XMLDirectiveBody ) |
| * | ( '<%!' DeclarationBody ) |
| * | ( '<jsp:declaration' XMLDeclarationBody ) |
| * | ( '<%=' ExpressionBody ) |
| * | ( '<jsp:expression' XMLExpressionBody ) |
| * | ( '${' ELExpressionBody ) |
| * | ( '<%' ScriptletBody ) |
| * | ( '<jsp:scriptlet' XMLScriptletBody ) |
| * | ( '<jsp:text' XMLTemplateText ) |
| * | ( '<jsp:' StandardAction ) |
| * | ( '<' CustomAction |
| * CustomActionBody ) |
| * | TemplateText |
| */ |
| private void parseElements(Node parent) |
| throws JasperException |
| { |
| 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("<jsp:")) { |
| parseStandardAction(parent); |
| } else if (!parseCustomTag(parent)) { |
| checkUnbalancedEndTag(); |
| parseTemplateText(parent); |
| } |
| } |
| |
| /* |
| * ScriptlessBody ::= ( '<%--' JSPCommentBody ) |
| * | ( '<%@' DirectiveBody ) |
| * | ( '<jsp:directive.' XMLDirectiveBody ) |
| * | ( '<%!' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:declaration' <TRANSLATION_ERROR> ) |
| * | ( '<%=' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:expression' <TRANSLATION_ERROR> ) |
| * | ( '<%' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:scriptlet' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:text' XMLTemplateText ) |
| * | ( '${' ELExpressionBody ) |
| * | ( '<jsp:' StandardAction ) |
| * | ( '<' CustomAction |
| * CustomActionBody ) |
| * | TemplateText |
| */ |
| private void parseElementsScriptless(Node parent) |
| throws JasperException |
| { |
| // 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("<jsp:")) { |
| parseStandardAction(parent); |
| } else if (!parseCustomTag(parent)) { |
| checkUnbalancedEndTag(); |
| parseTemplateText(parent); |
| } |
| |
| scriptlessCount--; |
| } |
| |
| /* |
| * TemplateTextBody ::= ( '<%--' JSPCommentBody ) |
| * | ( '<%@' DirectiveBody ) |
| * | ( '<jsp:directive.' XMLDirectiveBody ) |
| * | ( '<%!' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:declaration' <TRANSLATION_ERROR> ) |
| * | ( '<%=' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:expression' <TRANSLATION_ERROR> ) |
| * | ( '<%' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:scriptlet' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:text' <TRANSLATION_ERROR> ) |
| * | ( '${' <TRANSLATION_ERROR> ) |
| * | ( '<jsp:' <TRANSLATION_ERROR> ) |
| * | TemplateText |
| */ |
| private void parseElementsTemplateText(Node parent) |
| throws JasperException |
| { |
| 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("${")) { |
| 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); |
| } |
| } |
| |
| /* |
| * Flag as error if an unbalanced end tag appears by itself. |
| */ |
| private void checkUnbalancedEndTag() throws JasperException { |
| |
| 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); |
| } |
| |
| /** |
| * TagDependentBody := |
| */ |
| private void parseTagDependentBody(Node parent, String tag) |
| throws JasperException{ |
| 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); |
| } |
| |
| /* |
| * Parses jsp:body action. |
| */ |
| private void parseJspBody(Node parent, String bodyType) |
| throws JasperException |
| { |
| 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 ); |
| } |
| } |
| |
| /* |
| * Parse the body as JSP content. |
| * @param tag The name of the tag whose end tag would terminate the body |
| * @param bodyType One of the TagInfo body types |
| */ |
| private void parseBody(Node parent, String tag, String bodyType) |
| throws JasperException |
| { |
| 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, "jasper.error.bad.bodycontent.type"); |
| } |
| } |
| |
| /* |
| * Parses named attributes. |
| */ |
| private void parseNamedAttributes(Node parent) throws JasperException { |
| do { |
| Mark start = reader.mark(); |
| Attributes attrs = parseAttributes(); |
| 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" ) ); |
| } |
| |
| /** |
| * Determine the body type of <jsp:attribute> from the enclosing node |
| */ |
| private String getAttributeBodyType(Node n, String name) { |
| |
| 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; |
| } |
| |
| private void parseTagFileDirectives(Node parent) |
| throws JasperException |
| { |
| 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("<"); |
| } |
| } |
| } |
| |