0001: /*
0002: * Copyright 1999,2004 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.apache.jasper.compiler;
0017:
0018: import java.io.CharArrayWriter;
0019: import java.io.FileNotFoundException;
0020: import java.net.URL;
0021: import java.util.Iterator;
0022: import java.util.List;
0023:
0024: import javax.servlet.jsp.tagext.TagAttributeInfo;
0025: import javax.servlet.jsp.tagext.TagFileInfo;
0026: import javax.servlet.jsp.tagext.TagInfo;
0027: import javax.servlet.jsp.tagext.TagLibraryInfo;
0028:
0029: import org.apache.jasper.Constants;
0030: import org.apache.jasper.JasperException;
0031: import org.apache.jasper.JspCompilationContext;
0032: import org.xml.sax.Attributes;
0033: import org.xml.sax.helpers.AttributesImpl;
0034:
0035: /**
0036: * This class implements a parser for a JSP page (non-xml view).
0037: * JSP page grammar is included here for reference. The token '#'
0038: * that appears in the production indicates the current input token
0039: * location in the production.
0040: *
0041: * @author Kin-man Chung
0042: * @author Shawn Bayern
0043: * @author Mark Roth
0044: */
0045:
0046: class Parser implements TagConstants {
0047:
0048: private ParserController parserController;
0049: private JspCompilationContext ctxt;
0050: private JspReader reader;
0051: private String currentFile;
0052: private Mark start;
0053: private ErrorDispatcher err;
0054: private int scriptlessCount;
0055: private boolean isTagFile;
0056: private boolean directivesOnly;
0057: private URL jarFileUrl;
0058: private PageInfo pageInfo;
0059:
0060: // Virtual body content types, to make parsing a little easier.
0061: // These are not accessible from outside the parser.
0062: private static final String JAVAX_BODY_CONTENT_PARAM = "JAVAX_BODY_CONTENT_PARAM";
0063: private static final String JAVAX_BODY_CONTENT_PLUGIN = "JAVAX_BODY_CONTENT_PLUGIN";
0064: private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT = "JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
0065:
0066: /**
0067: * The constructor
0068: */
0069: private Parser(ParserController pc, JspReader reader,
0070: boolean isTagFile, boolean directivesOnly, URL jarFileUrl) {
0071: this .parserController = pc;
0072: this .ctxt = pc.getJspCompilationContext();
0073: this .pageInfo = pc.getCompiler().getPageInfo();
0074: this .err = pc.getCompiler().getErrorDispatcher();
0075: this .reader = reader;
0076: this .currentFile = reader.mark().getFile();
0077: this .scriptlessCount = 0;
0078: this .isTagFile = isTagFile;
0079: this .directivesOnly = directivesOnly;
0080: this .jarFileUrl = jarFileUrl;
0081: start = reader.mark();
0082: }
0083:
0084: /**
0085: * The main entry for Parser
0086: *
0087: * @param pc The ParseController, use for getting other objects in compiler
0088: * and for parsing included pages
0089: * @param reader To read the page
0090: * @param parent The parent node to this page, null for top level page
0091: * @return list of nodes representing the parsed page
0092: */
0093: public static Node.Nodes parse(ParserController pc,
0094: JspReader reader, Node parent, boolean isTagFile,
0095: boolean directivesOnly, URL jarFileUrl, String pageEnc,
0096: String jspConfigPageEnc, boolean isDefaultPageEncoding)
0097: throws JasperException {
0098:
0099: Parser parser = new Parser(pc, reader, isTagFile,
0100: directivesOnly, jarFileUrl);
0101:
0102: Node.Root root = new Node.Root(reader.mark(), parent, false);
0103: root.setPageEncoding(pageEnc);
0104: root.setJspConfigPageEncoding(jspConfigPageEnc);
0105: root.setIsDefaultPageEncoding(isDefaultPageEncoding);
0106:
0107: if (directivesOnly) {
0108: parser.parseTagFileDirectives(root);
0109: return new Node.Nodes(root);
0110: }
0111:
0112: // For the Top level page, add inlcude-prelude and include-coda
0113: PageInfo pageInfo = pc.getCompiler().getPageInfo();
0114: if (parent == null) {
0115: parser.addInclude(root, pageInfo.getIncludePrelude());
0116: }
0117: while (reader.hasMoreInput()) {
0118: parser.parseElements(root);
0119: }
0120: if (parent == null) {
0121: parser.addInclude(root, pageInfo.getIncludeCoda());
0122: }
0123:
0124: Node.Nodes page = new Node.Nodes(root);
0125: return page;
0126: }
0127:
0128: /**
0129: * Attributes ::= (S Attribute)* S?
0130: */
0131: Attributes parseAttributes() throws JasperException {
0132: AttributesImpl attrs = new AttributesImpl();
0133:
0134: reader.skipSpaces();
0135: while (parseAttribute(attrs))
0136: reader.skipSpaces();
0137:
0138: return attrs;
0139: }
0140:
0141: /**
0142: * Parse Attributes for a reader, provided for external use
0143: */
0144: public static Attributes parseAttributes(ParserController pc,
0145: JspReader reader) throws JasperException {
0146: Parser tmpParser = new Parser(pc, reader, false, false, null);
0147: return tmpParser.parseAttributes();
0148: }
0149:
0150: /**
0151: * Attribute ::= Name S? Eq S?
0152: * ( '"<%=' RTAttributeValueDouble
0153: * | '"' AttributeValueDouble
0154: * | "'<%=" RTAttributeValueSingle
0155: * | "'" AttributeValueSingle
0156: * }
0157: * Note: JSP and XML spec does not allow while spaces around Eq. It is
0158: * added to be backward compatible with Tomcat, and with other xml parsers.
0159: */
0160: private boolean parseAttribute(AttributesImpl attrs)
0161: throws JasperException {
0162:
0163: // Get the qualified name
0164: String qName = parseName();
0165: if (qName == null)
0166: return false;
0167:
0168: // Determine prefix and local name components
0169: String localName = qName;
0170: String uri = "";
0171: int index = qName.indexOf(':');
0172: if (index != -1) {
0173: String prefix = qName.substring(0, index);
0174: uri = pageInfo.getURI(prefix);
0175: if (uri == null) {
0176: err.jspError(reader.mark(),
0177: "jsp.error.attribute.invalidPrefix", prefix);
0178: }
0179: localName = qName.substring(index + 1);
0180: }
0181:
0182: reader.skipSpaces();
0183: if (!reader.matches("="))
0184: err.jspError(reader.mark(), "jsp.error.attribute.noequal");
0185:
0186: reader.skipSpaces();
0187: char quote = (char) reader.nextChar();
0188: if (quote != '\'' && quote != '"')
0189: err.jspError(reader.mark(), "jsp.error.attribute.noquote");
0190:
0191: String watchString = "";
0192: if (reader.matches("<%="))
0193: watchString = "%>";
0194: watchString = watchString + quote;
0195:
0196: String attrValue = parseAttributeValue(watchString);
0197: attrs.addAttribute(uri, localName, qName, "CDATA", attrValue);
0198: return true;
0199: }
0200:
0201: /**
0202: * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*
0203: */
0204: private String parseName() throws JasperException {
0205: char ch = (char) reader.peekChar();
0206: if (Character.isLetter(ch) || ch == '_' || ch == ':') {
0207: StringBuffer buf = new StringBuffer();
0208: buf.append(ch);
0209: reader.nextChar();
0210: ch = (char) reader.peekChar();
0211: while (Character.isLetter(ch) || Character.isDigit(ch)
0212: || ch == '.' || ch == '_' || ch == '-' || ch == ':') {
0213: buf.append(ch);
0214: reader.nextChar();
0215: ch = (char) reader.peekChar();
0216: }
0217: return buf.toString();
0218: }
0219: return null;
0220: }
0221:
0222: /**
0223: * AttributeValueDouble ::= (QuotedChar - '"')*
0224: * ('"' | <TRANSLATION_ERROR>)
0225: * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"')
0226: * ('%>"' | TRANSLATION_ERROR)
0227: */
0228: private String parseAttributeValue(String watch)
0229: throws JasperException {
0230: Mark start = reader.mark();
0231: Mark stop = reader.skipUntilIgnoreEsc(watch);
0232: if (stop == null) {
0233: err.jspError(start, "jsp.error.attribute.unterminated",
0234: watch);
0235: }
0236:
0237: String ret = parseQuoted(reader.getText(start, stop));
0238: if (watch.length() == 1) // quote
0239: return ret;
0240:
0241: // putback delimiter '<%=' and '%>', since they are needed if the
0242: // attribute does not allow RTexpression.
0243: return "<%=" + ret + "%>";
0244: }
0245:
0246: /**
0247: * QuotedChar ::= '''
0248: * | '"'
0249: * | '\\'
0250: * | '\"'
0251: * | "\'"
0252: * | '\>'
0253: * | '\$'
0254: * | Char
0255: */
0256: private String parseQuoted(String tx) {
0257: StringBuffer buf = new StringBuffer();
0258: int size = tx.length();
0259: int i = 0;
0260: while (i < size) {
0261: char ch = tx.charAt(i);
0262: if (ch == '&') {
0263: if (i + 5 < size && tx.charAt(i + 1) == 'a'
0264: && tx.charAt(i + 2) == 'p'
0265: && tx.charAt(i + 3) == 'o'
0266: && tx.charAt(i + 4) == 's'
0267: && tx.charAt(i + 5) == ';') {
0268: buf.append('\'');
0269: i += 6;
0270: } else if (i + 5 < size && tx.charAt(i + 1) == 'q'
0271: && tx.charAt(i + 2) == 'u'
0272: && tx.charAt(i + 3) == 'o'
0273: && tx.charAt(i + 4) == 't'
0274: && tx.charAt(i + 5) == ';') {
0275: buf.append('"');
0276: i += 6;
0277: } else {
0278: buf.append(ch);
0279: ++i;
0280: }
0281: } else if (ch == '\\' && i + 1 < size) {
0282: ch = tx.charAt(i + 1);
0283: if (ch == '\\' || ch == '\"' || ch == '\'' || ch == '>') {
0284: buf.append(ch);
0285: i += 2;
0286: } else if (ch == '$') {
0287: // Replace "\$" with some special char. XXX hack!
0288: buf.append(Constants.ESC);
0289: i += 2;
0290: } else {
0291: buf.append('\\');
0292: ++i;
0293: }
0294: } else {
0295: buf.append(ch);
0296: ++i;
0297: }
0298: }
0299: return buf.toString();
0300: }
0301:
0302: private String parseScriptText(String tx) {
0303: CharArrayWriter cw = new CharArrayWriter();
0304: int size = tx.length();
0305: int i = 0;
0306: while (i < size) {
0307: char ch = tx.charAt(i);
0308: if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\'
0309: && tx.charAt(i + 2) == '>') {
0310: cw.write('%');
0311: cw.write('>');
0312: i += 3;
0313: } else {
0314: cw.write(ch);
0315: ++i;
0316: }
0317: }
0318: cw.close();
0319: return cw.toString();
0320: }
0321:
0322: /*
0323: * Invokes parserController to parse the included page
0324: */
0325: private void processIncludeDirective(String file, Node parent)
0326: throws JasperException {
0327: if (file == null) {
0328: return;
0329: }
0330:
0331: try {
0332: parserController.parse(file, parent, jarFileUrl);
0333: } catch (FileNotFoundException ex) {
0334: err.jspError(start, "jsp.error.file.not.found", file);
0335: } catch (Exception ex) {
0336: err.jspError(start, ex.getMessage());
0337: }
0338: }
0339:
0340: /*
0341: * Parses a page directive with the following syntax:
0342: * PageDirective ::= ( S Attribute)*
0343: */
0344: private void parsePageDirective(Node parent) throws JasperException {
0345: Attributes attrs = parseAttributes();
0346: Node.PageDirective n = new Node.PageDirective(attrs, start,
0347: parent);
0348:
0349: /*
0350: * A page directive may contain multiple 'import' attributes, each of
0351: * which consists of a comma-separated list of package names.
0352: * Store each list with the node, where it is parsed.
0353: */
0354: for (int i = 0; i < attrs.getLength(); i++) {
0355: if ("import".equals(attrs.getQName(i))) {
0356: n.addImport(attrs.getValue(i));
0357: }
0358: }
0359: }
0360:
0361: /*
0362: * Parses an include directive with the following syntax:
0363: * IncludeDirective ::= ( S Attribute)*
0364: */
0365: private void parseIncludeDirective(Node parent)
0366: throws JasperException {
0367: Attributes attrs = parseAttributes();
0368:
0369: // Included file expanded here
0370: Node includeNode = new Node.IncludeDirective(attrs, start,
0371: parent);
0372: processIncludeDirective(attrs.getValue("file"), includeNode);
0373: }
0374:
0375: /**
0376: * Add a list of files. This is used for implementing include-prelude
0377: * and include-coda of jsp-config element in web.xml
0378: */
0379: private void addInclude(Node parent, List files)
0380: throws JasperException {
0381: if (files != null) {
0382: Iterator iter = files.iterator();
0383: while (iter.hasNext()) {
0384: String file = (String) iter.next();
0385: AttributesImpl attrs = new AttributesImpl();
0386: attrs.addAttribute("", "file", "file", "CDATA", file);
0387:
0388: // Create a dummy Include directive node
0389: Node includeNode = new Node.IncludeDirective(attrs,
0390: reader.mark(), parent);
0391: processIncludeDirective(file, includeNode);
0392: }
0393: }
0394: }
0395:
0396: /*
0397: * Parses a taglib directive with the following syntax:
0398: * Directive ::= ( S Attribute)*
0399: */
0400: private void parseTaglibDirective(Node parent)
0401: throws JasperException {
0402:
0403: Attributes attrs = parseAttributes();
0404: String uri = attrs.getValue("uri");
0405: String prefix = attrs.getValue("prefix");
0406: if (prefix != null) {
0407: Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix);
0408: if (prevMark != null) {
0409: err.jspError(reader.mark(),
0410: "jsp.error.prefix.use_before_dcl", prefix,
0411: prevMark.getFile(), ""
0412: + prevMark.getLineNumber());
0413: }
0414: if (uri != null) {
0415: String uriPrev = pageInfo.getURI(prefix);
0416: if (uriPrev != null && !uriPrev.equals(uri)) {
0417: err.jspError(reader.mark(),
0418: "jsp.error.prefix.refined", prefix, uri,
0419: uriPrev);
0420: }
0421: if (pageInfo.getTaglib(uri) == null) {
0422: String[] location = ctxt.getTldLocation(uri);
0423: pageInfo.addTaglib(uri, new TagLibraryInfoImpl(
0424: ctxt, parserController, prefix, uri,
0425: location, err));
0426: }
0427: pageInfo.addPrefixMapping(prefix, uri);
0428: } else {
0429: String tagdir = attrs.getValue("tagdir");
0430: if (tagdir != null) {
0431: String urnTagdir = URN_JSPTAGDIR + tagdir;
0432: if (pageInfo.getTaglib(urnTagdir) == null) {
0433: pageInfo.addTaglib(urnTagdir,
0434: new ImplicitTagLibraryInfo(ctxt,
0435: parserController, prefix,
0436: tagdir, err));
0437: }
0438: pageInfo.addPrefixMapping(prefix, urnTagdir);
0439: }
0440: }
0441: }
0442:
0443: new Node.TaglibDirective(attrs, start, parent);
0444: }
0445:
0446: /*
0447: * Parses a directive with the following syntax:
0448: * Directive ::= S? ( 'page' PageDirective
0449: * | 'include' IncludeDirective
0450: * | 'taglib' TagLibDirective)
0451: * S? '%>'
0452: *
0453: * TagDirective ::= S? ('tag' PageDirective
0454: * | 'include' IncludeDirective
0455: * | 'taglib' TagLibDirective)
0456: * | 'attribute AttributeDirective
0457: * | 'variable VariableDirective
0458: * S? '%>'
0459: */
0460: private void parseDirective(Node parent) throws JasperException {
0461: reader.skipSpaces();
0462:
0463: String directive = null;
0464: if (reader.matches("page")) {
0465: directive = "<%@ page";
0466: if (isTagFile) {
0467: err.jspError(reader.mark(),
0468: "jsp.error.directive.istagfile", directive);
0469: }
0470: parsePageDirective(parent);
0471: } else if (reader.matches("include")) {
0472: directive = "<%@ include";
0473: parseIncludeDirective(parent);
0474: } else if (reader.matches("taglib")) {
0475: if (directivesOnly) {
0476: // No need to get the tagLibInfo objects. This alos suppresses
0477: // parsing of any tag files used in this tag file.
0478: return;
0479: }
0480: directive = "<%@ taglib";
0481: parseTaglibDirective(parent);
0482: } else if (reader.matches("tag")) {
0483: directive = "<%@ tag";
0484: if (!isTagFile) {
0485: err.jspError(reader.mark(),
0486: "jsp.error.directive.isnottagfile", directive);
0487: }
0488: parseTagDirective(parent);
0489: } else if (reader.matches("attribute")) {
0490: directive = "<%@ attribute";
0491: if (!isTagFile) {
0492: err.jspError(reader.mark(),
0493: "jsp.error.directive.isnottagfile", directive);
0494: }
0495: parseAttributeDirective(parent);
0496: } else if (reader.matches("variable")) {
0497: directive = "<%@ variable";
0498: if (!isTagFile) {
0499: err.jspError(reader.mark(),
0500: "jsp.error.directive.isnottagfile", directive);
0501: }
0502: parseVariableDirective(parent);
0503: } else {
0504: err.jspError(reader.mark(), "jsp.error.invalid.directive");
0505: }
0506:
0507: reader.skipSpaces();
0508: if (!reader.matches("%>")) {
0509: err.jspError(start, "jsp.error.unterminated", directive);
0510: }
0511: }
0512:
0513: /*
0514: * Parses a directive with the following syntax:
0515: *
0516: * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList
0517: * S? ( '/>' | ( '>' S? ETag ) )
0518: * | ( 'include' IncludeDirectiveAttrList
0519: * S? ( '/>' | ( '>' S? ETag ) )
0520: * | <TRANSLATION_ERROR>
0521: *
0522: * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList
0523: * S? ( '/>' | ( '>' S? ETag ) )
0524: * | ( 'include' IncludeDirectiveAttrList
0525: * S? ( '/>' | ( '>' S? ETag ) )
0526: * | ( 'attribute' AttributeDirectiveAttrList
0527: * S? ( '/>' | ( '>' S? ETag ) )
0528: * | ( 'variable' VariableDirectiveAttrList
0529: * S? ( '/>' | ( '>' S? ETag ) )
0530: * )
0531: * | <TRANSLATION_ERROR>
0532: */
0533: private void parseXMLDirective(Node parent) throws JasperException {
0534: reader.skipSpaces();
0535:
0536: String eTag = null;
0537: if (reader.matches("page")) {
0538: eTag = "jsp:directive.page";
0539: if (isTagFile) {
0540: err.jspError(reader.mark(),
0541: "jsp.error.directive.istagfile", "<" + eTag);
0542: }
0543: parsePageDirective(parent);
0544: } else if (reader.matches("include")) {
0545: eTag = "jsp:directive.include";
0546: parseIncludeDirective(parent);
0547: } else if (reader.matches("tag")) {
0548: eTag = "jsp:directive.tag";
0549: if (!isTagFile) {
0550: err.jspError(reader.mark(),
0551: "jsp.error.directive.isnottagfile", "<"
0552: + eTag);
0553: }
0554: parseTagDirective(parent);
0555: } else if (reader.matches("attribute")) {
0556: eTag = "jsp:directive.attribute";
0557: if (!isTagFile) {
0558: err.jspError(reader.mark(),
0559: "jsp.error.directive.isnottagfile", "<"
0560: + eTag);
0561: }
0562: parseAttributeDirective(parent);
0563: } else if (reader.matches("variable")) {
0564: eTag = "jsp:directive.variable";
0565: if (!isTagFile) {
0566: err.jspError(reader.mark(),
0567: "jsp.error.directive.isnottagfile", "<"
0568: + eTag);
0569: }
0570: parseVariableDirective(parent);
0571: } else {
0572: err.jspError(reader.mark(), "jsp.error.invalid.directive");
0573: }
0574:
0575: reader.skipSpaces();
0576: if (reader.matches(">")) {
0577: reader.skipSpaces();
0578: if (!reader.matchesETag(eTag)) {
0579: err.jspError(start, "jsp.error.unterminated", "<"
0580: + eTag);
0581: }
0582: } else if (!reader.matches("/>")) {
0583: err
0584: .jspError(start, "jsp.error.unterminated", "<"
0585: + eTag);
0586: }
0587: }
0588:
0589: /*
0590: * Parses a tag directive with the following syntax:
0591: * PageDirective ::= ( S Attribute)*
0592: */
0593: private void parseTagDirective(Node parent) throws JasperException {
0594: Attributes attrs = parseAttributes();
0595: Node.TagDirective n = new Node.TagDirective(attrs, start,
0596: parent);
0597:
0598: /*
0599: * A page directive may contain multiple 'import' attributes, each of
0600: * which consists of a comma-separated list of package names.
0601: * Store each list with the node, where it is parsed.
0602: */
0603: for (int i = 0; i < attrs.getLength(); i++) {
0604: if ("import".equals(attrs.getQName(i))) {
0605: n.addImport(attrs.getValue(i));
0606: }
0607: }
0608: }
0609:
0610: /*
0611: * Parses a attribute directive with the following syntax:
0612: * AttributeDirective ::= ( S Attribute)*
0613: */
0614: private void parseAttributeDirective(Node parent)
0615: throws JasperException {
0616: Attributes attrs = parseAttributes();
0617: Node.AttributeDirective n = new Node.AttributeDirective(attrs,
0618: start, parent);
0619: }
0620:
0621: /*
0622: * Parses a variable directive with the following syntax:
0623: * PageDirective ::= ( S Attribute)*
0624: */
0625: private void parseVariableDirective(Node parent)
0626: throws JasperException {
0627: Attributes attrs = parseAttributes();
0628: Node.VariableDirective n = new Node.VariableDirective(attrs,
0629: start, parent);
0630: }
0631:
0632: /*
0633: * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>'
0634: */
0635: private void parseComment(Node parent) throws JasperException {
0636: start = reader.mark();
0637: Mark stop = reader.skipUntil("--%>");
0638: if (stop == null) {
0639: err.jspError(start, "jsp.error.unterminated", "<%--");
0640: }
0641:
0642: new Node.Comment(reader.getText(start, stop), start, parent);
0643: }
0644:
0645: /*
0646: * DeclarationBody ::= (Char* - (char* '%>')) '%>'
0647: */
0648: private void parseDeclaration(Node parent) throws JasperException {
0649: start = reader.mark();
0650: Mark stop = reader.skipUntil("%>");
0651: if (stop == null) {
0652: err.jspError(start, "jsp.error.unterminated", "<%!");
0653: }
0654:
0655: new Node.Declaration(parseScriptText(reader
0656: .getText(start, stop)), start, parent);
0657: }
0658:
0659: /*
0660: * XMLDeclarationBody ::= ( S? '/>' )
0661: * | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag
0662: * | <TRANSLATION_ERROR>
0663: * CDSect ::= CDStart CData CDEnd
0664: * CDStart ::= '<![CDATA['
0665: * CData ::= (Char* - (Char* ']]>' Char*))
0666: * CDEnd ::= ']]>'
0667: */
0668: private void parseXMLDeclaration(Node parent)
0669: throws JasperException {
0670: reader.skipSpaces();
0671: if (!reader.matches("/>")) {
0672: if (!reader.matches(">")) {
0673: err.jspError(start, "jsp.error.unterminated",
0674: "<jsp:declaration>");
0675: }
0676: Mark stop;
0677: String text;
0678: while (true) {
0679: start = reader.mark();
0680: stop = reader.skipUntil("<");
0681: if (stop == null) {
0682: err.jspError(start, "jsp.error.unterminated",
0683: "<jsp:declaration>");
0684: }
0685: text = parseScriptText(reader.getText(start, stop));
0686: new Node.Declaration(text, start, parent);
0687: if (reader.matches("![CDATA[")) {
0688: start = reader.mark();
0689: stop = reader.skipUntil("]]>");
0690: if (stop == null) {
0691: err.jspError(start, "jsp.error.unterminated",
0692: "CDATA");
0693: }
0694: text = parseScriptText(reader.getText(start, stop));
0695: new Node.Declaration(text, start, parent);
0696: } else {
0697: break;
0698: }
0699: }
0700:
0701: if (!reader.matchesETagWithoutLessThan("jsp:declaration")) {
0702: err.jspError(start, "jsp.error.unterminated",
0703: "<jsp:declaration>");
0704: }
0705: }
0706: }
0707:
0708: /*
0709: * ExpressionBody ::= (Char* - (char* '%>')) '%>'
0710: */
0711: private void parseExpression(Node parent) throws JasperException {
0712: start = reader.mark();
0713: Mark stop = reader.skipUntil("%>");
0714: if (stop == null) {
0715: err.jspError(start, "jsp.error.unterminated", "<%=");
0716: }
0717:
0718: new Node.Expression(
0719: parseScriptText(reader.getText(start, stop)), start,
0720: parent);
0721: }
0722:
0723: /*
0724: * XMLExpressionBody ::= ( S? '/>' )
0725: * | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag )
0726: * | <TRANSLATION_ERROR>
0727: */
0728: private void parseXMLExpression(Node parent) throws JasperException {
0729: reader.skipSpaces();
0730: if (!reader.matches("/>")) {
0731: if (!reader.matches(">")) {
0732: err.jspError(start, "jsp.error.unterminated",
0733: "<jsp:expression>");
0734: }
0735: Mark stop;
0736: String text;
0737: while (true) {
0738: start = reader.mark();
0739: stop = reader.skipUntil("<");
0740: if (stop == null) {
0741: err.jspError(start, "jsp.error.unterminated",
0742: "<jsp:expression>");
0743: }
0744: text = parseScriptText(reader.getText(start, stop));
0745: new Node.Expression(text, start, parent);
0746: if (reader.matches("![CDATA[")) {
0747: start = reader.mark();
0748: stop = reader.skipUntil("]]>");
0749: if (stop == null) {
0750: err.jspError(start, "jsp.error.unterminated",
0751: "CDATA");
0752: }
0753: text = parseScriptText(reader.getText(start, stop));
0754: new Node.Expression(text, start, parent);
0755: } else {
0756: break;
0757: }
0758: }
0759: if (!reader.matchesETagWithoutLessThan("jsp:expression")) {
0760: err.jspError(start, "jsp.error.unterminated",
0761: "<jsp:expression>");
0762: }
0763: }
0764: }
0765:
0766: /*
0767: * ELExpressionBody
0768: * (following "${" to first unquoted "}")
0769: * // XXX add formal production and confirm implementation against it,
0770: * // once it's decided
0771: */
0772: private void parseELExpression(Node parent) throws JasperException {
0773: start = reader.mark();
0774: Mark last = null;
0775: boolean singleQuoted = false, doubleQuoted = false;
0776: int currentChar;
0777: do {
0778: // XXX could move this logic to JspReader
0779: last = reader.mark(); // XXX somewhat wasteful
0780: currentChar = reader.nextChar();
0781: if (currentChar == '\\' && (singleQuoted || doubleQuoted)) {
0782: // skip character following '\' within quotes
0783: reader.nextChar();
0784: currentChar = reader.nextChar();
0785: }
0786: if (currentChar == -1)
0787: err.jspError(start, "jsp.error.unterminated", "${");
0788: if (currentChar == '"')
0789: doubleQuoted = !doubleQuoted;
0790: if (currentChar == '\'')
0791: singleQuoted = !singleQuoted;
0792: } while (currentChar != '}' || (singleQuoted || doubleQuoted));
0793:
0794: new Node.ELExpression(reader.getText(start, last), start,
0795: parent);
0796: }
0797:
0798: /*
0799: * ScriptletBody ::= (Char* - (char* '%>')) '%>'
0800: */
0801: private void parseScriptlet(Node parent) throws JasperException {
0802: start = reader.mark();
0803: Mark stop = reader.skipUntil("%>");
0804: if (stop == null) {
0805: err.jspError(start, "jsp.error.unterminated", "<%");
0806: }
0807:
0808: new Node.Scriptlet(
0809: parseScriptText(reader.getText(start, stop)), start,
0810: parent);
0811: }
0812:
0813: /*
0814: * XMLScriptletBody ::= ( S? '/>' )
0815: * | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag )
0816: * | <TRANSLATION_ERROR>
0817: */
0818: private void parseXMLScriptlet(Node parent) throws JasperException {
0819: reader.skipSpaces();
0820: if (!reader.matches("/>")) {
0821: if (!reader.matches(">")) {
0822: err.jspError(start, "jsp.error.unterminated",
0823: "<jsp:scriptlet>");
0824: }
0825: Mark stop;
0826: String text;
0827: while (true) {
0828: start = reader.mark();
0829: stop = reader.skipUntil("<");
0830: if (stop == null) {
0831: err.jspError(start, "jsp.error.unterminated",
0832: "<jsp:scriptlet>");
0833: }
0834: text = parseScriptText(reader.getText(start, stop));
0835: new Node.Scriptlet(text, start, parent);
0836: if (reader.matches("![CDATA[")) {
0837: start = reader.mark();
0838: stop = reader.skipUntil("]]>");
0839: if (stop == null) {
0840: err.jspError(start, "jsp.error.unterminated",
0841: "CDATA");
0842: }
0843: text = parseScriptText(reader.getText(start, stop));
0844: new Node.Scriptlet(text, start, parent);
0845: } else {
0846: break;
0847: }
0848: }
0849:
0850: if (!reader.matchesETagWithoutLessThan("jsp:scriptlet")) {
0851: err.jspError(start, "jsp.error.unterminated",
0852: "<jsp:scriptlet>");
0853: }
0854: }
0855: }
0856:
0857: /**
0858: * Param ::= '<jsp:param' S Attributes S? EmptyBody S?
0859: */
0860: private void parseParam(Node parent) throws JasperException {
0861: if (!reader.matches("<jsp:param")) {
0862: err.jspError(reader.mark(), "jsp.error.paramexpected");
0863: }
0864: Attributes attrs = parseAttributes();
0865: reader.skipSpaces();
0866:
0867: Node paramActionNode = new Node.ParamAction(attrs, start,
0868: parent);
0869:
0870: parseEmptyBody(paramActionNode, "jsp:param");
0871:
0872: reader.skipSpaces();
0873: }
0874:
0875: /*
0876: * For Include:
0877: * StdActionContent ::= Attributes ParamBody
0878: *
0879: * ParamBody ::= EmptyBody
0880: * | ( '>' S? ( '<jsp:attribute' NamedAttributes )?
0881: * '<jsp:body'
0882: * (JspBodyParam | <TRANSLATION_ERROR> )
0883: * S? ETag
0884: * )
0885: * | ( '>' S? Param* ETag )
0886: *
0887: * EmptyBody ::= '/>'
0888: * | ( '>' ETag )
0889: * | ( '>' S? '<jsp:attribute' NamedAttributes ETag )
0890: *
0891: * JspBodyParam ::= S? '>' Param* '</jsp:body>'
0892: */
0893: private void parseInclude(Node parent) throws JasperException {
0894: Attributes attrs = parseAttributes();
0895: reader.skipSpaces();
0896:
0897: Node includeNode = new Node.IncludeAction(attrs, start, parent);
0898:
0899: parseOptionalBody(includeNode, "jsp:include",
0900: JAVAX_BODY_CONTENT_PARAM);
0901: }
0902:
0903: /*
0904: * For Forward:
0905: * StdActionContent ::= Attributes ParamBody
0906: */
0907: private void parseForward(Node parent) throws JasperException {
0908: Attributes attrs = parseAttributes();
0909: reader.skipSpaces();
0910:
0911: Node forwardNode = new Node.ForwardAction(attrs, start, parent);
0912:
0913: parseOptionalBody(forwardNode, "jsp:forward",
0914: JAVAX_BODY_CONTENT_PARAM);
0915: }
0916:
0917: private void parseInvoke(Node parent) throws JasperException {
0918: Attributes attrs = parseAttributes();
0919: reader.skipSpaces();
0920:
0921: Node invokeNode = new Node.InvokeAction(attrs, start, parent);
0922:
0923: parseEmptyBody(invokeNode, "jsp:invoke");
0924: }
0925:
0926: private void parseDoBody(Node parent) throws JasperException {
0927: Attributes attrs = parseAttributes();
0928: reader.skipSpaces();
0929:
0930: Node doBodyNode = new Node.DoBodyAction(attrs, start, parent);
0931:
0932: parseEmptyBody(doBodyNode, "jsp:doBody");
0933: }
0934:
0935: private void parseElement(Node parent) throws JasperException {
0936: Attributes attrs = parseAttributes();
0937: reader.skipSpaces();
0938:
0939: Node elementNode = new Node.JspElement(attrs, start, parent);
0940:
0941: parseOptionalBody(elementNode, "jsp:element",
0942: TagInfo.BODY_CONTENT_JSP);
0943: }
0944:
0945: /*
0946: * For GetProperty:
0947: * StdActionContent ::= Attributes EmptyBody
0948: */
0949: private void parseGetProperty(Node parent) throws JasperException {
0950: Attributes attrs = parseAttributes();
0951: reader.skipSpaces();
0952:
0953: Node getPropertyNode = new Node.GetProperty(attrs, start,
0954: parent);
0955:
0956: parseOptionalBody(getPropertyNode, "jsp:getProperty",
0957: TagInfo.BODY_CONTENT_EMPTY);
0958: }
0959:
0960: /*
0961: * For SetProperty:
0962: * StdActionContent ::= Attributes EmptyBody
0963: */
0964: private void parseSetProperty(Node parent) throws JasperException {
0965: Attributes attrs = parseAttributes();
0966: reader.skipSpaces();
0967:
0968: Node setPropertyNode = new Node.SetProperty(attrs, start,
0969: parent);
0970:
0971: parseOptionalBody(setPropertyNode, "jsp:setProperty",
0972: TagInfo.BODY_CONTENT_EMPTY);
0973: }
0974:
0975: /*
0976: * EmptyBody ::= '/>'
0977: * | ( '>' ETag )
0978: * | ( '>' S? '<jsp:attribute' NamedAttributes ETag )
0979: */
0980: private void parseEmptyBody(Node parent, String tag)
0981: throws JasperException {
0982: if (reader.matches("/>")) {
0983: // Done
0984: } else if (reader.matches(">")) {
0985: if (reader.matchesETag(tag)) {
0986: // Done
0987: } else if (reader
0988: .matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
0989: // Parse the one or more named attribute nodes
0990: parseNamedAttributes(parent);
0991: if (!reader.matchesETag(tag)) {
0992: // Body not allowed
0993: err.jspError(reader.mark(),
0994: "jsp.error.jspbody.emptybody.only", "<"
0995: + tag);
0996: }
0997: } else {
0998: err.jspError(reader.mark(),
0999: "jsp.error.jspbody.emptybody.only", "<"
1000: + tag);
1001: }
1002: } else {
1003: err.jspError(reader.mark(), "jsp.error.unterminated",
1004: "<" + tag);
1005: }
1006: }
1007:
1008: /*
1009: * For UseBean:
1010: * StdActionContent ::= Attributes OptionalBody
1011: */
1012: private void parseUseBean(Node parent) throws JasperException {
1013: Attributes attrs = parseAttributes();
1014: reader.skipSpaces();
1015:
1016: Node useBeanNode = new Node.UseBean(attrs, start, parent);
1017:
1018: parseOptionalBody(useBeanNode, "jsp:useBean",
1019: TagInfo.BODY_CONTENT_JSP);
1020: }
1021:
1022: /*
1023: * Parses OptionalBody, but also reused to parse bodies for plugin
1024: * and param since the syntax is identical (the only thing that
1025: * differs substantially is how to process the body, and thus
1026: * we accept the body type as a parameter).
1027: *
1028: * OptionalBody ::= EmptyBody | ActionBody
1029: *
1030: * ScriptlessOptionalBody ::= EmptyBody | ScriptlessActionBody
1031: *
1032: * TagDependentOptionalBody ::= EmptyBody | TagDependentActionBody
1033: *
1034: * EmptyBody ::= '/>'
1035: * | ( '>' ETag )
1036: * | ( '>' S? '<jsp:attribute' NamedAttributes ETag )
1037: *
1038: * ActionBody ::= JspAttributeAndBody
1039: * | ( '>' Body ETag )
1040: *
1041: * ScriptlessActionBody ::= JspAttributeAndBody
1042: * | ( '>' ScriptlessBody ETag )
1043: *
1044: * TagDependentActionBody ::= JspAttributeAndBody
1045: * | ( '>' TagDependentBody ETag )
1046: *
1047: */
1048: private void parseOptionalBody(Node parent, String tag,
1049: String bodyType) throws JasperException {
1050: if (reader.matches("/>")) {
1051: // EmptyBody
1052: return;
1053: }
1054:
1055: if (!reader.matches(">")) {
1056: err.jspError(reader.mark(), "jsp.error.unterminated",
1057: "<" + tag);
1058: }
1059:
1060: if (reader.matchesETag(tag)) {
1061: // EmptyBody
1062: return;
1063: }
1064:
1065: if (!parseJspAttributeAndBody(parent, tag, bodyType)) {
1066: // Must be ( '>' # Body ETag )
1067: parseBody(parent, tag, bodyType);
1068: }
1069: }
1070:
1071: /**
1072: * Attempts to parse 'JspAttributeAndBody' production. Returns true if
1073: * it matched, or false if not. Assumes EmptyBody is okay as well.
1074: *
1075: * JspAttributeAndBody ::=
1076: * ( '>' # S? ( '<jsp:attribute' NamedAttributes )?
1077: * '<jsp:body'
1078: * ( JspBodyBody | <TRANSLATION_ERROR> )
1079: * S? ETag
1080: * )
1081: */
1082: private boolean parseJspAttributeAndBody(Node parent, String tag,
1083: String bodyType) throws JasperException {
1084: boolean result = false;
1085:
1086: if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
1087: // May be an EmptyBody, depending on whether
1088: // There's a "<jsp:body" before the ETag
1089:
1090: // First, parse <jsp:attribute> elements:
1091: parseNamedAttributes(parent);
1092:
1093: result = true;
1094: }
1095:
1096: if (reader.matchesOptionalSpacesFollowedBy("<jsp:body")) {
1097: // ActionBody
1098: parseJspBody(parent, bodyType);
1099: reader.skipSpaces();
1100: if (!reader.matchesETag(tag)) {
1101: err.jspError(reader.mark(), "jsp.error.unterminated",
1102: "<" + tag);
1103: }
1104:
1105: result = true;
1106: } else if (result && !reader.matchesETag(tag)) {
1107: // If we have <jsp:attribute> but something other than
1108: // <jsp:body> or the end tag, translation error.
1109: err.jspError(reader.mark(), "jsp.error.jspbody.required",
1110: "<" + tag);
1111: }
1112:
1113: return result;
1114: }
1115:
1116: /*
1117: * Params ::= `>' S?
1118: * ( ( `<jsp:body>'
1119: * ( ( S? Param+ S? `</jsp:body>' )
1120: * | <TRANSLATION_ERROR>
1121: * )
1122: * )
1123: * | Param+
1124: * )
1125: * '</jsp:params>'
1126: */
1127: private void parseJspParams(Node parent) throws JasperException {
1128: Node jspParamsNode = new Node.ParamsAction(start, parent);
1129: parseOptionalBody(jspParamsNode, "jsp:params",
1130: JAVAX_BODY_CONTENT_PARAM);
1131: }
1132:
1133: /*
1134: * Fallback ::= '/>'
1135: * | ( `>' S? `<jsp:body>'
1136: * ( ( S?
1137: * ( Char* - ( Char* `</jsp:body>' ) )
1138: * `</jsp:body>' S?
1139: * )
1140: * | <TRANSLATION_ERROR>
1141: * )
1142: * `</jsp:fallback>'
1143: * )
1144: * | ( '>'
1145: * ( Char* - ( Char* '</jsp:fallback>' ) )
1146: * '</jsp:fallback>'
1147: * )
1148: */
1149: private void parseFallBack(Node parent) throws JasperException {
1150: Node fallBackNode = new Node.FallBackAction(start, parent);
1151: parseOptionalBody(fallBackNode, "jsp:fallback",
1152: JAVAX_BODY_CONTENT_TEMPLATE_TEXT);
1153: }
1154:
1155: /*
1156: * For Plugin:
1157: * StdActionContent ::= Attributes PluginBody
1158: *
1159: * PluginBody ::= EmptyBody
1160: * | ( '>' S? ( '<jsp:attribute' NamedAttributes )?
1161: * '<jsp:body'
1162: * ( JspBodyPluginTags | <TRANSLATION_ERROR> )
1163: * S? ETag
1164: * )
1165: * | ( '>' S? PluginTags ETag )
1166: *
1167: * EmptyBody ::= '/>'
1168: * | ( '>' ETag )
1169: * | ( '>' S? '<jsp:attribute' NamedAttributes ETag )
1170: *
1171: */
1172: private void parsePlugin(Node parent) throws JasperException {
1173: Attributes attrs = parseAttributes();
1174: reader.skipSpaces();
1175:
1176: Node pluginNode = new Node.PlugIn(attrs, start, parent);
1177:
1178: parseOptionalBody(pluginNode, "jsp:plugin",
1179: JAVAX_BODY_CONTENT_PLUGIN);
1180: }
1181:
1182: /*
1183: * PluginTags ::= ( '<jsp:params' Params S? )?
1184: * ( '<jsp:fallback' Fallback? S? )?
1185: */
1186: private void parsePluginTags(Node parent) throws JasperException {
1187: reader.skipSpaces();
1188:
1189: if (reader.matches("<jsp:params")) {
1190: parseJspParams(parent);
1191: reader.skipSpaces();
1192: }
1193:
1194: if (reader.matches("<jsp:fallback")) {
1195: parseFallBack(parent);
1196: reader.skipSpaces();
1197: }
1198: }
1199:
1200: /*
1201: * StandardAction ::= 'include' StdActionContent
1202: * | 'forward' StdActionContent
1203: * | 'invoke' StdActionContent
1204: * | 'doBody' StdActionContent
1205: * | 'getProperty' StdActionContent
1206: * | 'setProperty' StdActionContent
1207: * | 'useBean' StdActionContent
1208: * | 'plugin' StdActionContent
1209: * | 'element' StdActionContent
1210: */
1211: private void parseStandardAction(Node parent)
1212: throws JasperException {
1213: Mark start = reader.mark();
1214:
1215: if (reader.matches(INCLUDE_ACTION)) {
1216: parseInclude(parent);
1217: } else if (reader.matches(FORWARD_ACTION)) {
1218: parseForward(parent);
1219: } else if (reader.matches(INVOKE_ACTION)) {
1220: if (!isTagFile) {
1221: err.jspError(reader.mark(),
1222: "jsp.error.action.isnottagfile",
1223: "<jsp:invoke");
1224: }
1225: parseInvoke(parent);
1226: } else if (reader.matches(DOBODY_ACTION)) {
1227: if (!isTagFile) {
1228: err.jspError(reader.mark(),
1229: "jsp.error.action.isnottagfile",
1230: "<jsp:doBody");
1231: }
1232: parseDoBody(parent);
1233: } else if (reader.matches(GET_PROPERTY_ACTION)) {
1234: parseGetProperty(parent);
1235: } else if (reader.matches(SET_PROPERTY_ACTION)) {
1236: parseSetProperty(parent);
1237: } else if (reader.matches(USE_BEAN_ACTION)) {
1238: parseUseBean(parent);
1239: } else if (reader.matches(PLUGIN_ACTION)) {
1240: parsePlugin(parent);
1241: } else if (reader.matches(ELEMENT_ACTION)) {
1242: parseElement(parent);
1243: } else if (reader.matches(ATTRIBUTE_ACTION)) {
1244: err.jspError(start, "jsp.error.namedAttribute.invalidUse");
1245: } else if (reader.matches(BODY_ACTION)) {
1246: err.jspError(start, "jsp.error.jspbody.invalidUse");
1247: } else if (reader.matches(FALLBACK_ACTION)) {
1248: err.jspError(start, "jsp.error.fallback.invalidUse");
1249: } else if (reader.matches(PARAMS_ACTION)) {
1250: err.jspError(start, "jsp.error.params.invalidUse");
1251: } else if (reader.matches(PARAM_ACTION)) {
1252: err.jspError(start, "jsp.error.param.invalidUse");
1253: } else if (reader.matches(OUTPUT_ACTION)) {
1254: err.jspError(start, "jsp.error.jspoutput.invalidUse");
1255: } else {
1256: err.jspError(start, "jsp.error.badStandardAction");
1257: }
1258: }
1259:
1260: /*
1261: * # '<' CustomAction CustomActionBody
1262: *
1263: * CustomAction ::= TagPrefix ':' CustomActionName
1264: *
1265: * TagPrefix ::= Name
1266: *
1267: * CustomActionName ::= Name
1268: *
1269: * CustomActionBody ::= ( Attributes CustomActionEnd )
1270: * | <TRANSLATION_ERROR>
1271: *
1272: * Attributes ::= ( S Attribute )* S?
1273: *
1274: * CustomActionEnd ::= CustomActionTagDependent
1275: * | CustomActionJSPContent
1276: * | CustomActionScriptlessContent
1277: *
1278: * CustomActionTagDependent ::= TagDependentOptionalBody
1279: *
1280: * CustomActionJSPContent ::= OptionalBody
1281: *
1282: * CustomActionScriptlessContent ::= ScriptlessOptionalBody
1283: */
1284: private boolean parseCustomTag(Node parent) throws JasperException {
1285:
1286: if (reader.peekChar() != '<') {
1287: return false;
1288: }
1289:
1290: // Parse 'CustomAction' production (tag prefix and custom action name)
1291: reader.nextChar(); // skip '<'
1292: String tagName = reader.parseToken(false);
1293: int i = tagName.indexOf(':');
1294: if (i == -1) {
1295: reader.reset(start);
1296: return false;
1297: }
1298:
1299: String prefix = tagName.substring(0, i);
1300: String shortTagName = tagName.substring(i + 1);
1301:
1302: // Check if this is a user-defined tag.
1303: String uri = pageInfo.getURI(prefix);
1304: if (uri == null) {
1305: reader.reset(start);
1306: // Remember the prefix for later error checking
1307: pageInfo.putNonCustomTagPrefix(prefix, reader.mark());
1308: return false;
1309: }
1310:
1311: TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
1312: TagInfo tagInfo = tagLibInfo.getTag(shortTagName);
1313: TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName);
1314: if (tagInfo == null && tagFileInfo == null) {
1315: err.jspError(start, "jsp.error.bad_tag", shortTagName,
1316: prefix);
1317: }
1318: Class tagHandlerClass = null;
1319: if (tagInfo != null) {
1320: // Must be a classic tag, load it here.
1321: // tag files will be loaded later, in TagFileProcessor
1322: String handlerClassName = tagInfo.getTagClassName();
1323: try {
1324: tagHandlerClass = ctxt.getClassLoader().loadClass(
1325: handlerClassName);
1326: } catch (Exception e) {
1327: err.jspError(start, "jsp.error.loadclass.taghandler",
1328: handlerClassName, tagName);
1329: }
1330: }
1331:
1332: // Parse 'CustomActionBody' production:
1333: // At this point we are committed - if anything fails, we produce
1334: // a translation error.
1335:
1336: // Parse 'Attributes' production:
1337: Attributes attrs = parseAttributes();
1338: reader.skipSpaces();
1339:
1340: // Parse 'CustomActionEnd' production:
1341: if (reader.matches("/>")) {
1342: if (tagInfo != null) {
1343: new Node.CustomTag(tagName, prefix, shortTagName, uri,
1344: attrs, start, parent, tagInfo, tagHandlerClass);
1345: } else {
1346: new Node.CustomTag(tagName, prefix, shortTagName, uri,
1347: attrs, start, parent, tagFileInfo);
1348: }
1349: return true;
1350: }
1351:
1352: // Now we parse one of 'CustomActionTagDependent',
1353: // 'CustomActionJSPContent', or 'CustomActionScriptlessContent'.
1354: // depending on body-content in TLD.
1355:
1356: // Looking for a body, it still can be empty; but if there is a
1357: // a tag body, its syntax would be dependent on the type of
1358: // body content declared in the TLD.
1359: String bc;
1360: if (tagInfo != null) {
1361: bc = tagInfo.getBodyContent();
1362: } else {
1363: bc = tagFileInfo.getTagInfo().getBodyContent();
1364: }
1365:
1366: Node tagNode = null;
1367: if (tagInfo != null) {
1368: tagNode = new Node.CustomTag(tagName, prefix, shortTagName,
1369: uri, attrs, start, parent, tagInfo, tagHandlerClass);
1370: } else {
1371: tagNode = new Node.CustomTag(tagName, prefix, shortTagName,
1372: uri, attrs, start, parent, tagFileInfo);
1373: }
1374:
1375: parseOptionalBody(tagNode, tagName, bc);
1376:
1377: return true;
1378: }
1379:
1380: /*
1381: * Parse for a template text string until '<' or "${" is encountered,
1382: * recognizing escape sequences "\%" and "\$".
1383: */
1384: private void parseTemplateText(Node parent) throws JasperException {
1385:
1386: if (!reader.hasMoreInput())
1387: return;
1388:
1389: CharArrayWriter ttext = new CharArrayWriter();
1390: // Output the first character
1391: int ch = reader.nextChar();
1392: ttext.write(ch);
1393:
1394: while (reader.hasMoreInput()) {
1395: ch = reader.nextChar();
1396: if (ch == '<') {
1397: reader.pushChar();
1398: break;
1399: } else if (ch == '$') {
1400: if (!reader.hasMoreInput()) {
1401: ttext.write('$');
1402: break;
1403: }
1404: ch = reader.nextChar();
1405: if (ch == '{') {
1406: reader.pushChar();
1407: reader.pushChar();
1408: break;
1409: }
1410: ttext.write('$');
1411: reader.pushChar();
1412: continue;
1413: } else if (ch == '\\') {
1414: if (!reader.hasMoreInput()) {
1415: ttext.write('\\');
1416: break;
1417: }
1418: char next = (char) reader.peekChar();
1419: // Looking for \% or \$
1420: // TODO: only recognize \$ if isELIgnored is false, but since
1421: // it can be set in a page directive, it cannot be determined
1422: // here. Argh!
1423: if (next == '%' || next == '$') {
1424: ch = reader.nextChar();
1425: }
1426: }
1427: ttext.write(ch);
1428: }
1429: new Node.TemplateText(ttext.toString(), start, parent);
1430: }
1431:
1432: /*
1433: * XMLTemplateText ::= ( S? '/>' )
1434: * | ( S? '>'
1435: * ( ( Char* - ( Char* ( '<' | '${' ) ) )
1436: * ( '${' ELExpressionBody )?
1437: * CDSect?
1438: * )* ETag
1439: * )
1440: * | <TRANSLATION_ERROR>
1441: */
1442: private void parseXMLTemplateText(Node parent)
1443: throws JasperException {
1444: reader.skipSpaces();
1445: if (!reader.matches("/>")) {
1446: if (!reader.matches(">")) {
1447: err.jspError(start, "jsp.error.unterminated",
1448: "<jsp:text>");
1449: }
1450: CharArrayWriter ttext = new CharArrayWriter();
1451: while (reader.hasMoreInput()) {
1452: int ch = reader.nextChar();
1453: if (ch == '<') {
1454: // Check for <![CDATA[
1455: if (!reader.matches("![CDATA[")) {
1456: break;
1457: }
1458: start = reader.mark();
1459: Mark stop = reader.skipUntil("]]>");
1460: if (stop == null) {
1461: err.jspError(start, "jsp.error.unterminated",
1462: "CDATA");
1463: }
1464: String text = reader.getText(start, stop);
1465: ttext.write(text, 0, text.length());
1466: } else if (ch == '\\') {
1467: if (!reader.hasMoreInput()) {
1468: ttext.write('\\');
1469: break;
1470: }
1471: ch = reader.nextChar();
1472: if (ch != '$') {
1473: ttext.write('\\');
1474: }
1475: ttext.write(ch);
1476: } else if (ch == '$') {
1477: if (!reader.hasMoreInput()) {
1478: ttext.write('$');
1479: break;
1480: }
1481: ch = reader.nextChar();
1482: if (ch != '{') {
1483: ttext.write('$');
1484: reader.pushChar();
1485: continue;
1486: }
1487: // Create a template text node
1488: new Node.TemplateText(ttext.toString(), start,
1489: parent);
1490:
1491: // Mark and parse the EL expression and create its node:
1492: start = reader.mark();
1493: parseELExpression(parent);
1494:
1495: start = reader.mark();
1496: ttext = new CharArrayWriter();
1497: } else {
1498: ttext.write(ch);
1499: }
1500: }
1501:
1502: new Node.TemplateText(ttext.toString(), start, parent);
1503:
1504: if (!reader.hasMoreInput()) {
1505: err.jspError(start, "jsp.error.unterminated",
1506: "<jsp:text>");
1507: } else if (!reader.matchesETagWithoutLessThan("jsp:text")) {
1508: err.jspError(start, "jsp.error.jsptext.badcontent");
1509: }
1510: }
1511: }
1512:
1513: /*
1514: * AllBody ::= ( '<%--' JSPCommentBody )
1515: * | ( '<%@' DirectiveBody )
1516: * | ( '<jsp:directive.' XMLDirectiveBody )
1517: * | ( '<%!' DeclarationBody )
1518: * | ( '<jsp:declaration' XMLDeclarationBody )
1519: * | ( '<%=' ExpressionBody )
1520: * | ( '<jsp:expression' XMLExpressionBody )
1521: * | ( '${' ELExpressionBody )
1522: * | ( '<%' ScriptletBody )
1523: * | ( '<jsp:scriptlet' XMLScriptletBody )
1524: * | ( '<jsp:text' XMLTemplateText )
1525: * | ( '<jsp:' StandardAction )
1526: * | ( '<' CustomAction
1527: * CustomActionBody )
1528: * | TemplateText
1529: */
1530: private void parseElements(Node parent) throws JasperException {
1531: if (scriptlessCount > 0) {
1532: // vc: ScriptlessBody
1533: // We must follow the ScriptlessBody production if one of
1534: // our parents is ScriptlessBody.
1535: parseElementsScriptless(parent);
1536: return;
1537: }
1538:
1539: start = reader.mark();
1540: if (reader.matches("<%--")) {
1541: parseComment(parent);
1542: } else if (reader.matches("<%@")) {
1543: parseDirective(parent);
1544: } else if (reader.matches("<jsp:directive.")) {
1545: parseXMLDirective(parent);
1546: } else if (reader.matches("<%!")) {
1547: parseDeclaration(parent);
1548: } else if (reader.matches("<jsp:declaration")) {
1549: parseXMLDeclaration(parent);
1550: } else if (reader.matches("<%=")) {
1551: parseExpression(parent);
1552: } else if (reader.matches("<jsp:expression")) {
1553: parseXMLExpression(parent);
1554: } else if (reader.matches("<%")) {
1555: parseScriptlet(parent);
1556: } else if (reader.matches("<jsp:scriptlet")) {
1557: parseXMLScriptlet(parent);
1558: } else if (reader.matches("<jsp:text")) {
1559: parseXMLTemplateText(parent);
1560: } else if (reader.matches("${")) {
1561: parseELExpression(parent);
1562: } else if (reader.matches("<jsp:")) {
1563: parseStandardAction(parent);
1564: } else if (!parseCustomTag(parent)) {
1565: checkUnbalancedEndTag();
1566: parseTemplateText(parent);
1567: }
1568: }
1569:
1570: /*
1571: * ScriptlessBody ::= ( '<%--' JSPCommentBody )
1572: * | ( '<%@' DirectiveBody )
1573: * | ( '<jsp:directive.' XMLDirectiveBody )
1574: * | ( '<%!' <TRANSLATION_ERROR> )
1575: * | ( '<jsp:declaration' <TRANSLATION_ERROR> )
1576: * | ( '<%=' <TRANSLATION_ERROR> )
1577: * | ( '<jsp:expression' <TRANSLATION_ERROR> )
1578: * | ( '<%' <TRANSLATION_ERROR> )
1579: * | ( '<jsp:scriptlet' <TRANSLATION_ERROR> )
1580: * | ( '<jsp:text' XMLTemplateText )
1581: * | ( '${' ELExpressionBody )
1582: * | ( '<jsp:' StandardAction )
1583: * | ( '<' CustomAction
1584: * CustomActionBody )
1585: * | TemplateText
1586: */
1587: private void parseElementsScriptless(Node parent)
1588: throws JasperException {
1589: // Keep track of how many scriptless nodes we've encountered
1590: // so we know whether our child nodes are forced scriptless
1591: scriptlessCount++;
1592:
1593: start = reader.mark();
1594: if (reader.matches("<%--")) {
1595: parseComment(parent);
1596: } else if (reader.matches("<%@")) {
1597: parseDirective(parent);
1598: } else if (reader.matches("<jsp:directive.")) {
1599: parseXMLDirective(parent);
1600: } else if (reader.matches("<%!")) {
1601: err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1602: } else if (reader.matches("<jsp:declaration")) {
1603: err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1604: } else if (reader.matches("<%=")) {
1605: err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1606: } else if (reader.matches("<jsp:expression")) {
1607: err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1608: } else if (reader.matches("<%")) {
1609: err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1610: } else if (reader.matches("<jsp:scriptlet")) {
1611: err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1612: } else if (reader.matches("<jsp:text")) {
1613: parseXMLTemplateText(parent);
1614: } else if (reader.matches("${")) {
1615: parseELExpression(parent);
1616: } else if (reader.matches("<jsp:")) {
1617: parseStandardAction(parent);
1618: } else if (!parseCustomTag(parent)) {
1619: checkUnbalancedEndTag();
1620: parseTemplateText(parent);
1621: }
1622:
1623: scriptlessCount--;
1624: }
1625:
1626: /*
1627: * TemplateTextBody ::= ( '<%--' JSPCommentBody )
1628: * | ( '<%@' DirectiveBody )
1629: * | ( '<jsp:directive.' XMLDirectiveBody )
1630: * | ( '<%!' <TRANSLATION_ERROR> )
1631: * | ( '<jsp:declaration' <TRANSLATION_ERROR> )
1632: * | ( '<%=' <TRANSLATION_ERROR> )
1633: * | ( '<jsp:expression' <TRANSLATION_ERROR> )
1634: * | ( '<%' <TRANSLATION_ERROR> )
1635: * | ( '<jsp:scriptlet' <TRANSLATION_ERROR> )
1636: * | ( '<jsp:text' <TRANSLATION_ERROR> )
1637: * | ( '${' <TRANSLATION_ERROR> )
1638: * | ( '<jsp:' <TRANSLATION_ERROR> )
1639: * | TemplateText
1640: */
1641: private void parseElementsTemplateText(Node parent)
1642: throws JasperException {
1643: start = reader.mark();
1644: if (reader.matches("<%--")) {
1645: parseComment(parent);
1646: } else if (reader.matches("<%@")) {
1647: parseDirective(parent);
1648: } else if (reader.matches("<jsp:directive.")) {
1649: parseXMLDirective(parent);
1650: } else if (reader.matches("<%!")) {
1651: err.jspError(reader.mark(), "jsp.error.not.in.template",
1652: "Declarations");
1653: } else if (reader.matches("<jsp:declaration")) {
1654: err.jspError(reader.mark(), "jsp.error.not.in.template",
1655: "Declarations");
1656: } else if (reader.matches("<%=")) {
1657: err.jspError(reader.mark(), "jsp.error.not.in.template",
1658: "Expressions");
1659: } else if (reader.matches("<jsp:expression")) {
1660: err.jspError(reader.mark(), "jsp.error.not.in.template",
1661: "Expressions");
1662: } else if (reader.matches("<%")) {
1663: err.jspError(reader.mark(), "jsp.error.not.in.template",
1664: "Scriptlets");
1665: } else if (reader.matches("<jsp:scriptlet")) {
1666: err.jspError(reader.mark(), "jsp.error.not.in.template",
1667: "Scriptlets");
1668: } else if (reader.matches("<jsp:text")) {
1669: err.jspError(reader.mark(), "jsp.error.not.in.template",
1670: "<jsp:text");
1671: } else if (reader.matches("${")) {
1672: err.jspError(reader.mark(), "jsp.error.not.in.template",
1673: "Expression language");
1674: } else if (reader.matches("<jsp:")) {
1675: err.jspError(reader.mark(), "jsp.error.not.in.template",
1676: "Standard actions");
1677: } else if (parseCustomTag(parent)) {
1678: err.jspError(reader.mark(), "jsp.error.not.in.template",
1679: "Custom actions");
1680: } else {
1681: checkUnbalancedEndTag();
1682: parseTemplateText(parent);
1683: }
1684: }
1685:
1686: /*
1687: * Flag as error if an unbalanced end tag appears by itself.
1688: */
1689: private void checkUnbalancedEndTag() throws JasperException {
1690:
1691: if (!reader.matches("</")) {
1692: return;
1693: }
1694:
1695: // Check for unbalanced standard actions
1696: if (reader.matches("jsp:")) {
1697: err.jspError(start, "jsp.error.unbalanced.endtag", "jsp:");
1698: }
1699:
1700: // Check for unbalanced custom actions
1701: String tagName = reader.parseToken(false);
1702: int i = tagName.indexOf(':');
1703: if (i == -1 || pageInfo.getURI(tagName.substring(0, i)) == null) {
1704: reader.reset(start);
1705: return;
1706: }
1707:
1708: err.jspError(start, "jsp.error.unbalanced.endtag", tagName);
1709: }
1710:
1711: /**
1712: * TagDependentBody :=
1713: */
1714: private void parseTagDependentBody(Node parent, String tag)
1715: throws JasperException {
1716: Mark bodyStart = reader.mark();
1717: Mark bodyEnd = reader.skipUntilETag(tag);
1718: if (bodyEnd == null) {
1719: err.jspError(start, "jsp.error.unterminated", "<" + tag);
1720: }
1721: new Node.TemplateText(reader.getText(bodyStart, bodyEnd),
1722: bodyStart, parent);
1723: }
1724:
1725: /*
1726: * Parses jsp:body action.
1727: */
1728: private void parseJspBody(Node parent, String bodyType)
1729: throws JasperException {
1730: Mark start = reader.mark();
1731: Node bodyNode = new Node.JspBody(start, parent);
1732:
1733: reader.skipSpaces();
1734: if (!reader.matches("/>")) {
1735: if (!reader.matches(">")) {
1736: err.jspError(start, "jsp.error.unterminated",
1737: "<jsp:body");
1738: }
1739: parseBody(bodyNode, "jsp:body", bodyType);
1740: }
1741: }
1742:
1743: /*
1744: * Parse the body as JSP content.
1745: * @param tag The name of the tag whose end tag would terminate the body
1746: * @param bodyType One of the TagInfo body types
1747: */
1748: private void parseBody(Node parent, String tag, String bodyType)
1749: throws JasperException {
1750: if (bodyType
1751: .equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)) {
1752: parseTagDependentBody(parent, tag);
1753: } else if (bodyType
1754: .equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)) {
1755: if (!reader.matchesETag(tag)) {
1756: err.jspError(start,
1757: "jasper.error.emptybodycontent.nonempty", tag);
1758: }
1759: } else if (bodyType == JAVAX_BODY_CONTENT_PLUGIN) {
1760: // (note the == since we won't recognize JAVAX_*
1761: // from outside this module).
1762: parsePluginTags(parent);
1763: if (!reader.matchesETag(tag)) {
1764: err.jspError(reader.mark(), "jsp.error.unterminated",
1765: "<" + tag);
1766: }
1767: } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)
1768: || bodyType
1769: .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)
1770: || (bodyType == JAVAX_BODY_CONTENT_PARAM)
1771: || (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT)) {
1772: while (reader.hasMoreInput()) {
1773: if (reader.matchesETag(tag)) {
1774: return;
1775: }
1776:
1777: // Check for nested jsp:body or jsp:attribute
1778: if (tag.equals("jsp:body")
1779: || tag.equals("jsp:attribute")) {
1780: if (reader.matches("<jsp:attribute")) {
1781: err.jspError(reader.mark(),
1782: "jsp.error.nested.jspattribute");
1783: } else if (reader.matches("<jsp:body")) {
1784: err.jspError(reader.mark(),
1785: "jsp.error.nested.jspbody");
1786: }
1787: }
1788:
1789: if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) {
1790: parseElements(parent);
1791: } else if (bodyType
1792: .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
1793: parseElementsScriptless(parent);
1794: } else if (bodyType == JAVAX_BODY_CONTENT_PARAM) {
1795: // (note the == since we won't recognize JAVAX_*
1796: // from outside this module).
1797: reader.skipSpaces();
1798: parseParam(parent);
1799: } else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) {
1800: parseElementsTemplateText(parent);
1801: }
1802: }
1803: err.jspError(start, "jsp.error.unterminated", "<" + tag);
1804: } else {
1805: err.jspError(start, "jasper.error.bad.bodycontent.type");
1806: }
1807: }
1808:
1809: /*
1810: * Parses named attributes.
1811: */
1812: private void parseNamedAttributes(Node parent)
1813: throws JasperException {
1814: do {
1815: Mark start = reader.mark();
1816: Attributes attrs = parseAttributes();
1817: Node.NamedAttribute namedAttributeNode = new Node.NamedAttribute(
1818: attrs, start, parent);
1819:
1820: reader.skipSpaces();
1821: if (!reader.matches("/>")) {
1822: if (!reader.matches(">")) {
1823: err.jspError(start, "jsp.error.unterminated",
1824: "<jsp:attribute");
1825: }
1826: if (namedAttributeNode.isTrim()) {
1827: reader.skipSpaces();
1828: }
1829: parseBody(namedAttributeNode, "jsp:attribute",
1830: getAttributeBodyType(parent, attrs
1831: .getValue("name")));
1832: if (namedAttributeNode.isTrim()) {
1833: Node.Nodes subElems = namedAttributeNode.getBody();
1834: if (subElems != null) {
1835: Node lastNode = subElems.getNode(subElems
1836: .size() - 1);
1837: if (lastNode instanceof Node.TemplateText) {
1838: ((Node.TemplateText) lastNode).rtrim();
1839: }
1840: }
1841: }
1842: }
1843: reader.skipSpaces();
1844: } while (reader.matches("<jsp:attribute"));
1845: }
1846:
1847: /**
1848: * Determine the body type of <jsp:attribute> from the enclosing node
1849: */
1850: private String getAttributeBodyType(Node n, String name) {
1851:
1852: if (n instanceof Node.CustomTag) {
1853: TagInfo tagInfo = ((Node.CustomTag) n).getTagInfo();
1854: TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
1855: for (int i = 0; i < tldAttrs.length; i++) {
1856: if (name.equals(tldAttrs[i].getName())) {
1857: if (tldAttrs[i].isFragment()) {
1858: return TagInfo.BODY_CONTENT_SCRIPTLESS;
1859: }
1860: if (tldAttrs[i].canBeRequestTime()) {
1861: return TagInfo.BODY_CONTENT_JSP;
1862: }
1863: }
1864: }
1865: if (tagInfo.hasDynamicAttributes()) {
1866: return TagInfo.BODY_CONTENT_JSP;
1867: }
1868: } else if (n instanceof Node.IncludeAction) {
1869: if ("page".equals(name)) {
1870: return TagInfo.BODY_CONTENT_JSP;
1871: }
1872: } else if (n instanceof Node.ForwardAction) {
1873: if ("page".equals(name)) {
1874: return TagInfo.BODY_CONTENT_JSP;
1875: }
1876: } else if (n instanceof Node.SetProperty) {
1877: if ("value".equals(name)) {
1878: return TagInfo.BODY_CONTENT_JSP;
1879: }
1880: } else if (n instanceof Node.UseBean) {
1881: if ("beanName".equals(name)) {
1882: return TagInfo.BODY_CONTENT_JSP;
1883: }
1884: } else if (n instanceof Node.PlugIn) {
1885: if ("width".equals(name) || "height".equals(name)) {
1886: return TagInfo.BODY_CONTENT_JSP;
1887: }
1888: } else if (n instanceof Node.ParamAction) {
1889: if ("value".equals(name)) {
1890: return TagInfo.BODY_CONTENT_JSP;
1891: }
1892: } else if (n instanceof Node.JspElement) {
1893: return TagInfo.BODY_CONTENT_JSP;
1894: }
1895:
1896: return JAVAX_BODY_CONTENT_TEMPLATE_TEXT;
1897: }
1898:
1899: private void parseTagFileDirectives(Node parent)
1900: throws JasperException {
1901: reader.setSingleFile(true);
1902: reader.skipUntil("<");
1903: while (reader.hasMoreInput()) {
1904: start = reader.mark();
1905: if (reader.matches("%--")) {
1906: parseComment(parent);
1907: } else if (reader.matches("%@")) {
1908: parseDirective(parent);
1909: } else if (reader.matches("jsp:directive.")) {
1910: parseXMLDirective(parent);
1911: }
1912: reader.skipUntil("<");
1913: }
1914: }
1915: }
|