You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/07/31 20:12:51 UTC
svn commit: r799681 [9/24] - in /struts/sandbox/trunk/struts2-jsp-plugin: ./
src/main/java/org/apache/struts/ src/main/java/org/apache/struts2/
src/main/java/org/apache/struts2/compiler/
src/main/java/org/apache/struts2/jasper/ src/main/java/org/apache...
Added: struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Parser.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Parser.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Parser.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Parser.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,1948 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.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.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.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";
+
+ private static final boolean STRICT_QUOTE_ESCAPING = Boolean.valueOf(
+ System.getProperty(
+ "org.apache.struts2.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING",
+ "true")).booleanValue();
+
+ /**
+ * 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(start, reader.getText(start, stop),
+ watch.charAt(watch.length() - 1));
+ 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(Mark start, String tx, char quote)
+ throws JasperException {
+ 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.HACK_CHAR);
+ i += 2;
+ } else {
+ buf.append('\\');
+ ++i;
+ }
+ } else if (ch == quote && STRICT_QUOTE_ESCAPING) {
+ // Unescaped quote character
+ err.jspError(start, "jsp.error.attribute.noescape", tx,
+ "" + quote);
+ } 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) {
+ TagLibraryInfoImpl impl = null;
+ if (ctxt.getOptions().isCaching()) {
+ impl = (TagLibraryInfoImpl) ctxt.getOptions().getCache().get(uri);
+ }
+ if (impl == null) {
+ String[] location = ctxt.getTldLocation(uri);
+ impl = new TagLibraryInfoImpl(ctxt,
+ parserController,
+ prefix,
+ uri,
+ location,
+ err);
+ if (ctxt.getOptions().isCaching()) {
+ ctxt.getOptions().getCache().put(uri, impl);
+ }
+ }
+ pageInfo.addTaglib(uri, impl);
+ }
+ 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();
+ if (ch == '\\') {
+ reader.pushChar();
+ } else {
+ 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;
+ }
+ // Look for \% or \$
+ // Only recognize \$ if isELIgnored is false, but since it can
+ // be set in a page directive, it cannot be determined yet.
+ char next = (char)reader.peekChar();
+ if (next == '%') {
+ ch = reader.nextChar();
+ } else if(next == '$') {
+ // Skip the $ and use a hack to flag this sequence
+ reader.nextChar();
+ ch = Constants.HACK_CHAR;
+ }
+ }
+ 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("<");
+ }
+ }
+}
+