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