0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: *
0023: * Free Software Foundation, Inc.
0024: * 59 Temple Place, Suite 330
0025: * Boston, MA 02111-1307 USA
0026: *
0027: * @author Scott Ferguson
0028: */
0029:
0030: package com.caucho.jsp;
0031:
0032: import com.caucho.java.LineMap;
0033: import com.caucho.jsp.java.JspNode;
0034: import com.caucho.log.Log;
0035: import com.caucho.util.CharBuffer;
0036: import com.caucho.util.L10N;
0037: import com.caucho.util.LineCompileException;
0038: import com.caucho.vfs.Path;
0039: import com.caucho.vfs.ReadStream;
0040: import com.caucho.xml.QName;
0041: import com.caucho.xml.XmlChar;
0042:
0043: import java.io.IOException;
0044: import java.util.ArrayList;
0045: import java.util.HashSet;
0046: import java.util.logging.Level;
0047: import java.util.logging.Logger;
0048:
0049: /**
0050: * Parses the JSP page. Both the XML and JSP tags are understood. However,
0051: * escaping is always done using JSP rules.
0052: */
0053: public class JspParser {
0054: static L10N L = new L10N(JspParser.class);
0055: static final Logger log = Log.open(JspParser.class);
0056:
0057: public static final String JSP_NS = "http://java.sun.com/JSP/Page";
0058: public static final String JSTL_CORE_URI = "http://java.sun.com/jsp/jstl/core";
0059: public static final String JSTL_FMT_URI = "http://java.sun.com/jsp/jstl/fmt";
0060:
0061: public static final QName PREFIX = new QName("prefix");
0062: public static final QName TAGLIB = new QName("taglib");
0063: public static final QName TAGDIR = new QName("tagdir");
0064: public static final QName URI = new QName("uri");
0065:
0066: public static final QName JSP_DECLARATION = new QName("jsp",
0067: "declaration", JSP_NS);
0068:
0069: public static final QName JSP_SCRIPTLET = new QName("jsp",
0070: "scriptlet", JSP_NS);
0071:
0072: public static final QName JSP_EXPRESSION = new QName("jsp",
0073: "expression", JSP_NS);
0074:
0075: public static final QName JSP_DIRECTIVE_PAGE = new QName("jsp",
0076: "directive.page", JSP_NS);
0077:
0078: public static final QName JSP_DIRECTIVE_INCLUDE = new QName("jsp",
0079: "directive.include", JSP_NS);
0080:
0081: public static final QName JSP_DIRECTIVE_CACHE = new QName("jsp",
0082: "directive.cache", JSP_NS);
0083:
0084: public static final QName JSP_DIRECTIVE_TAGLIB = new QName("jsp",
0085: "directive.taglib", JSP_NS);
0086:
0087: public static final QName JSP_DIRECTIVE_ATTRIBUTE = new QName(
0088: "jsp", "directive.attribute", JSP_NS);
0089:
0090: public static final QName JSP_DIRECTIVE_VARIABLE = new QName("jsp",
0091: "directive.variable", JSP_NS);
0092:
0093: public static final QName JSP_DIRECTIVE_TAG = new QName("jsp",
0094: "directive.tag", JSP_NS);
0095:
0096: public static final QName JSTL_CORE_OUT = new QName("resin-c",
0097: "out", "urn:jsptld:" + JSTL_CORE_URI);
0098:
0099: public static final QName JSTL_CORE_CHOOSE = new QName("resin-c",
0100: "choose", "urn:jsptld:" + JSTL_CORE_URI);
0101:
0102: public static final QName JSTL_CORE_WHEN = new QName("resin-c",
0103: "when", "urn:jsptld:" + JSTL_CORE_URI);
0104:
0105: public static final QName JSTL_CORE_OTHERWISE = new QName(
0106: "resin-c", "otherwise", "urn:jsptld:" + JSTL_CORE_URI);
0107:
0108: public static final QName JSTL_CORE_FOREACH = new QName("resin-c",
0109: "forEach", "urn:jsptld:" + JSTL_CORE_URI);
0110:
0111: private static final int TAG_UNKNOWN = 0;
0112: private static final int TAG_JSP = 1;
0113: private static final int TAG_RAW = 2;
0114:
0115: private ParseState _parseState;
0116: private JspBuilder _jspBuilder;
0117: private ParseTagManager _tagManager;
0118:
0119: private LineMap _lineMap;
0120:
0121: private ArrayList<String> _preludeList = new ArrayList<String>();
0122: private ArrayList<String> _codaList = new ArrayList<String>();
0123:
0124: private ArrayList<Include> _includes = new ArrayList<Include>();
0125:
0126: private HashSet<String> _prefixes = new HashSet<String>();
0127:
0128: private Path _jspPath;
0129: private ReadStream _stream;
0130: private String _uriPwd;
0131:
0132: private String _contextPath = "";
0133: private String _filename = "";
0134: private int _line;
0135: private int _lineStart;
0136:
0137: private int _charCount;
0138: private int _startText;
0139:
0140: private int _peek = -1;
0141: private boolean _seenCr = false;
0142:
0143: private Namespace _namespaces = new Namespace(null, "jsp", JSP_NS);
0144:
0145: private boolean _isXml;
0146: private boolean _isTop = true;
0147:
0148: private CharBuffer _tag = new CharBuffer();
0149: private CharBuffer _value = new CharBuffer();
0150:
0151: private CharBuffer _text = new CharBuffer();
0152:
0153: /**
0154: * Sets the JSP builder, which receives the SAX-like events from
0155: * JSP parser.
0156: */
0157: void setJspBuilder(JspBuilder builder) {
0158: _jspBuilder = builder;
0159: }
0160:
0161: /**
0162: * Sets the context path for error messages.
0163: */
0164: void setContextPath(String contextPath) {
0165: _contextPath = contextPath;
0166: }
0167:
0168: /**
0169: * Sets the parse state, which stores state information for the parsing.
0170: */
0171: void setParseState(ParseState parseState) {
0172: _parseState = parseState;
0173: }
0174:
0175: /**
0176: * Sets the parse state, which stores state information for the parsing.
0177: */
0178: ParseState getParseState() {
0179: return _parseState;
0180: }
0181:
0182: /**
0183: * Sets the tag manager
0184: */
0185: void setTagManager(ParseTagManager manager) {
0186: _tagManager = manager;
0187: }
0188:
0189: /**
0190: * Returns true if JSP EL expressions are enabled.
0191: */
0192: private boolean isELIgnored() {
0193: return _parseState.isELIgnored();
0194: }
0195:
0196: /**
0197: * Returns true if Velocity-style statements are enabled.
0198: */
0199: private boolean isVelocity() {
0200: return _parseState.isVelocityEnabled();
0201: }
0202:
0203: /**
0204: * Returns true if JSP EL expressions are enabled.
0205: */
0206: private boolean isDeferredSyntaxAllowedAsLiteral() {
0207: return _parseState.isDeferredSyntaxAllowedAsLiteral();
0208: }
0209:
0210: /**
0211: * Adds a prelude.
0212: */
0213: public void addPrelude(String prelude) {
0214: _preludeList.add(prelude);
0215: }
0216:
0217: /**
0218: * Adds a coda.
0219: */
0220: public void addCoda(String coda) {
0221: _codaList.add(coda);
0222: }
0223:
0224: /**
0225: * Starts parsing the JSP page.
0226: *
0227: * @param path the JSP source file
0228: * @param uri the URI for the JSP source file.
0229: */
0230: void parse(Path path, String uri) throws Exception {
0231: _parseState.pushNamespace("jsp", JSP_NS);
0232:
0233: _isXml = _parseState.isXml();
0234:
0235: _filename = _contextPath + uri;
0236:
0237: if (uri != null) {
0238: int p = uri.lastIndexOf('/');
0239: _uriPwd = p <= 0 ? "/" : uri.substring(0, p + 1);
0240: } else {
0241: _uriPwd = "/";
0242: }
0243: _parseState.setUriPwd(_uriPwd);
0244:
0245: ReadStream is = path.openRead();
0246: path.setUserPath(uri);
0247:
0248: try {
0249: parseJsp(is);
0250: } finally {
0251: is.close();
0252: for (int i = 0; i < _includes.size(); i++) {
0253: Include inc = _includes.get(i);
0254: inc._stream.close();
0255: }
0256: }
0257: }
0258:
0259: /**
0260: * Starts parsing the JSP page as a tag.
0261: *
0262: * @param path the JSP source file
0263: * @param uri the URI for the JSP source file.
0264: */
0265: void parseTag(Path path, String uri) throws Exception {
0266: _parseState.setTag(true);
0267:
0268: parse(path, uri);
0269: }
0270:
0271: /**
0272: * Top-level JSP parser.
0273: *
0274: * @param stream the read stream containing the JSP file
0275: *
0276: * @return an XML DOM containing the JSP.
0277: */
0278: private void parseJsp(ReadStream stream) throws Exception {
0279: _text.clear();
0280: _includes.clear();
0281:
0282: String uriPwd = _uriPwd;
0283:
0284: for (int i = _codaList.size() - 1; i >= 0; i--)
0285: pushInclude(_codaList.get(i), true);
0286:
0287: addInclude(stream, uriPwd);
0288:
0289: for (int i = _preludeList.size() - 1; i >= 0; i--)
0290: pushInclude(_preludeList.get(i), true);
0291:
0292: setLocation();
0293: _jspBuilder.startDocument();
0294:
0295: String pageEncoding = _parseState.getPageEncoding();
0296:
0297: int ch;
0298:
0299: if (pageEncoding != null) {
0300: _parseState.setPageEncoding(pageEncoding);
0301:
0302: stream.setEncoding(pageEncoding);
0303: }
0304:
0305: switch ((ch = stream.read())) {
0306: case 0xfe:
0307: if ((ch = stream.read()) != 0xff) {
0308: throw error(L
0309: .l("Expected 0xff in UTF-16 header. UTF-16 pages with the initial byte 0xfe expect 0xff immediately following. The 0xfe 0xff sequence is used by some application to suggest UTF-16 encoding without a directive."));
0310: } else {
0311: //_parseState.setContentType("text/html; charset=UTF-16BE");
0312: _parseState.setPageEncoding("UTF-16BE");
0313: stream.setEncoding("UTF-16BE");
0314: }
0315: break;
0316:
0317: case 0xff:
0318: if ((ch = stream.read()) != 0xfe) {
0319: throw error(L
0320: .l("Expected 0xfe in UTF-16 header. UTF-16 pages with the initial byte 0xff expect 0xfe immediately following. The 0xff 0xfe sequence is used by some application to suggest UTF-16 encoding without a directive."));
0321: } else {
0322: //_parseState.setContentType("text/html; charset=UTF-16LE");
0323: _parseState.setPageEncoding("UTF-16LE");
0324: stream.setEncoding("UTF-16LE");
0325: }
0326: break;
0327:
0328: case 0xef:
0329: if ((ch = stream.read()) != 0xbb) {
0330: stream.unread();
0331: stream.unread();
0332: } else if ((ch = stream.read()) != 0xbf) {
0333: throw error(L
0334: .l("Expected 0xbf in UTF-8 header. UTF-8 pages with the initial byte 0xbb expect 0xbf immediately following. The 0xbb 0xbf sequence is used by some application to suggest UTF-8 encoding without a directive."));
0335: } else {
0336: _parseState.setContentType("text/html; charset=UTF-8");
0337: _parseState.setPageEncoding("UTF-8");
0338: stream.setEncoding("UTF-8");
0339: }
0340: break;
0341:
0342: case -1:
0343: break;
0344:
0345: default:
0346: stream.unread();
0347: break;
0348: }
0349:
0350: ch = read();
0351:
0352: ch = parseXmlDeclaration(ch);
0353:
0354: try {
0355: parseNode(ch);
0356: } finally {
0357: for (int i = 0; i < _includes.size(); i++) {
0358: Include inc = _includes.get(i);
0359: inc._stream.close();
0360: }
0361: }
0362:
0363: setLocation();
0364: _jspBuilder.endDocument();
0365: }
0366:
0367: private int parseXmlDeclaration(int ch) throws IOException,
0368: JspParseException {
0369: if (ch != '<')
0370: return ch;
0371: else if ((ch = read()) != '?') {
0372: unread(ch);
0373: return '<';
0374: } else if ((ch = read()) != 'x') {
0375: addText("<?");
0376: return ch;
0377: } else if ((ch = read()) != 'm') {
0378: addText("<?x");
0379: return ch;
0380: } else if ((ch = read()) != 'l') {
0381: addText("<?xm");
0382: return ch;
0383: } else if (!XmlChar.isWhitespace((ch = read()))) {
0384: addText("<?xml");
0385: return ch;
0386: }
0387:
0388: String encoding = null;
0389:
0390: addText("<?xml ");
0391: ch = skipWhitespace(ch);
0392: while (XmlChar.isNameStart(ch)) {
0393: ch = readName(ch);
0394: String name = _tag.toString();
0395:
0396: addText(name);
0397: if (XmlChar.isWhitespace(ch))
0398: addText(' ');
0399:
0400: ch = skipWhitespace(ch);
0401: if (ch != '=')
0402: return ch;
0403:
0404: readValue(name, ch, true);
0405: String value = _value.toString();
0406:
0407: addText("=\"");
0408: addText(value);
0409: addText("\"");
0410:
0411: if (name.equals("encoding"))
0412: encoding = value;
0413:
0414: ch = read();
0415: if (XmlChar.isWhitespace(ch))
0416: addText(' ');
0417: ch = skipWhitespace(ch);
0418: }
0419:
0420: if (ch != '?')
0421: return ch;
0422: else if ((ch = read()) != '>') {
0423: addText('?');
0424: return ch;
0425: } else {
0426: addText("?>");
0427:
0428: if (encoding != null) {
0429: _stream.setEncoding(encoding);
0430: _parseState.setPageEncoding(encoding);
0431: }
0432:
0433: return read();
0434: }
0435: }
0436:
0437: private void parseNode(int ch) throws IOException,
0438: JspParseException {
0439: while (ch != -1) {
0440: switch (ch) {
0441: case '<': {
0442: switch ((ch = read())) {
0443: case '%':
0444: if (_isXml)
0445: throw error(L
0446: .l("'<%' syntax is not allowed in JSP/XML syntax."));
0447:
0448: parseScriptlet();
0449: _startText = _charCount;
0450:
0451: // escape '\\' after scriptlet at end of line
0452: if ((ch = read()) == '\\') {
0453: if ((ch = read()) == '\n') {
0454: ch = read();
0455: } else if (ch == '\r') {
0456: if ((ch = read()) == '\n')
0457: ch = read();
0458: } else
0459: addText('\\');
0460: }
0461: break;
0462:
0463: case '/':
0464: ch = parseCloseTag();
0465: break;
0466:
0467: case '\\':
0468: if ((ch = read()) == '%') {
0469: addText("<%");
0470: ch = read();
0471: } else
0472: addText("<\\");
0473: break;
0474:
0475: case '!':
0476: if (!_isXml)
0477: addText("<!");
0478: else if ((ch = read()) == '[')
0479: parseCdata();
0480: else if (ch == '-' && (ch = read()) == '-')
0481: parseXmlComment();
0482: else
0483: throw error(L
0484: .l(
0485: "'{0}' was not expected after '<!'. In the XML syntax, only <!-- ... --> and <![CDATA[ ... ]> are legal. You can use '&!' to escape '<!'.",
0486: badChar(ch)));
0487:
0488: ch = read();
0489: break;
0490:
0491: default:
0492: if (!XmlChar.isNameStart(ch)) {
0493: addText('<');
0494: break;
0495: }
0496:
0497: ch = readName(ch);
0498: String name = _tag.toString();
0499: int tagCode = getTag(name);
0500: if (!_isXml && tagCode == TAG_UNKNOWN) {
0501: addText("<");
0502: addText(name);
0503: break;
0504: }
0505:
0506: if (_isTop && name.equals("jsp:root")) {
0507: if (_parseState.isForbidXml())
0508: throw error(L
0509: .l("jsp:root must be in a JSP (XML) document, not a plain JSP."));
0510:
0511: _text.clear();
0512: _isXml = true;
0513: _parseState.setELIgnoredDefault(false);
0514: _parseState.setXml(true);
0515:
0516: }
0517: _isTop = false;
0518: parseOpenTag(name, ch, tagCode == TAG_UNKNOWN);
0519:
0520: ch = read();
0521:
0522: // escape '\\' after scriptlet at end of line
0523: if (!_isXml && ch == '\\') {
0524: if ((ch = read()) == '\n') {
0525: ch = read();
0526: } else if (ch == '\r') {
0527: if ((ch = read()) == '\n')
0528: ch = read();
0529: }
0530: }
0531: }
0532: break;
0533: }
0534:
0535: case '&':
0536: if (!_isXml)
0537: addText((char) ch);
0538: else {
0539: addText((char) parseEntity());
0540: }
0541: ch = read();
0542: break;
0543:
0544: case '$':
0545: ch = read();
0546:
0547: if (ch == '{' && !isELIgnored())
0548: ch = parseJspExpression();
0549: else
0550: addText('$');
0551: break;
0552:
0553: case '#':
0554: ch = read();
0555:
0556: if (isVelocity()) {
0557: ch = parseVelocity(ch);
0558: } else if (ch != '{' || isELIgnored()) {
0559: addText('#');
0560: } else if (isDeferredSyntaxAllowedAsLiteral()) {
0561: addText('#');
0562: } else
0563: throw error(L
0564: .l("Deferred syntax ('#{...}') not allowed as literal."));
0565: break;
0566:
0567: case '\\':
0568: switch (ch = read()) {
0569: case '$':
0570: if (!isELIgnored()) {
0571: addText('$');
0572: ch = read();
0573: } else
0574: addText('\\');
0575: break;
0576:
0577: case '#':
0578: if (!isELIgnored()) {
0579: addText('#');
0580: ch = read();
0581: } else
0582: addText('\\');
0583: break;
0584:
0585: case '\\':
0586: addText('\\');
0587: break;
0588:
0589: default:
0590: addText('\\');
0591: break;
0592: }
0593: break;
0594:
0595: default:
0596: addText((char) ch);
0597: ch = read();
0598: break;
0599: }
0600: }
0601:
0602: addText();
0603:
0604: /* XXX: end document
0605: if (! _activeNode.getNodeName().equals("jsp:root"))
0606: throw error(L.l("'</{0}>' expected at end of file. For XML, the top-level tag must have a matching closing tag.",
0607: activeNode.getNodeName()));
0608: */
0609: }
0610:
0611: /**
0612: * JSTL-style expressions. Currently understood:
0613: *
0614: * <code><pre>
0615: * ${a * b} - any arbitrary expression
0616: * </pre></code>
0617: */
0618: private int parseJspExpression() throws IOException,
0619: JspParseException {
0620: addText();
0621:
0622: Path jspPath = _jspPath;
0623: String filename = _filename;
0624: int line = _line;
0625:
0626: CharBuffer cb = CharBuffer.allocate();
0627: int ch;
0628: cb.append("${");
0629: for (ch = read(); ch >= 0 && ch != '}'; ch = read())
0630: cb.append((char) ch);
0631: cb.append("}");
0632:
0633: ch = read();
0634:
0635: String prefix = _parseState.findPrefix(JSTL_CORE_URI);
0636:
0637: if (prefix == null) {
0638: prefix = "resin-c";
0639:
0640: /*
0641: _jspBuilder.startElement(JSP_DIRECTIVE_TAGLIB);
0642: _jspBuilder.attribute(new QName("prefix"), prefix);
0643: _jspBuilder.attribute(new QName("uri"), JSTL_CORE_URI);
0644: _jspBuilder.endAttributes();
0645: _jspBuilder.endElement(JSP_DIRECTIVE_TAGLIB.getName());
0646: */
0647: _jspBuilder.addNamespace(prefix, JSTL_CORE_URI);
0648:
0649: processTaglib(prefix, JSTL_CORE_URI);
0650: }
0651:
0652: setLocation(jspPath, filename, line);
0653: _jspBuilder.startElement(JSTL_CORE_OUT);
0654: _jspBuilder.attribute(new QName("value"), cb.close());
0655: _jspBuilder.attribute(new QName("escapeXml"), "false");
0656: _jspBuilder.endAttributes();
0657: _jspBuilder.endElement(JSTL_CORE_OUT.getName());
0658:
0659: return ch;
0660: }
0661:
0662: private int parseVelocity(int ch) throws IOException,
0663: JspParseException {
0664: if (ch == '{') {
0665: return parseVelocityScriptlet();
0666: } else if ('a' <= ch && ch <= 'z') {
0667: ch = readName(ch);
0668: String name = _tag.toString();
0669:
0670: if (name.equals("if")) {
0671: ch = parseVelocityIf("if");
0672: } else if (name.equals("elseif")) {
0673: addText();
0674: setLocation();
0675:
0676: JspNode node = _jspBuilder.getCurrentNode();
0677: if (!"resin-c:when".equals(node.getTagName()))
0678: throw error(L
0679: .l("#elseif is missing a corresponding #if. Velocity-style #if syntax needs matching #if ... #elseif ... #else ... #end. The #if statements must also nest properly with any tags."));
0680:
0681: _jspBuilder.endElement("resin-c:when");
0682: ch = parseVelocityIf("elseif");
0683: } else if (name.equals("else")) {
0684: addText();
0685: setLocation();
0686: _jspBuilder.endElement("resin-c:when");
0687:
0688: setLocation(_jspPath, _filename, _lineStart);
0689: _lineStart = _line;
0690: _jspBuilder.startElement(JSTL_CORE_OTHERWISE);
0691: _jspBuilder.endAttributes();
0692:
0693: ch = skipWhitespaceToEndOfLine(ch);
0694: } else if (name.equals("foreach")) {
0695: ch = parseVelocityForeach("resin-c:forEach");
0696: } else if (name.equals("end")) {
0697: addText();
0698:
0699: JspNode node = _jspBuilder.getCurrentNode();
0700: String nodeName = null;
0701: if (node != null)
0702: nodeName = node.getTagName();
0703:
0704: if (nodeName.equals("resin-c:when")
0705: || nodeName.equals("resin-c:otherwise")) {
0706: _jspBuilder.endElement(nodeName);
0707: _jspBuilder.endElement(JSTL_CORE_CHOOSE.getName());
0708: } else if (nodeName.equals("resin-c:forEach"))
0709: _jspBuilder.endElement(nodeName);
0710: else {
0711: throw error(L
0712: .l("#end is missing a corresponding #if or #foreach. Velocity-style #if syntax needs matching #if ... #elseif ... #else ... #end. The #if statements must also nest properly with any tags."));
0713: }
0714:
0715: ch = skipWhitespaceToEndOfLine(ch);
0716: } else {
0717: addText('#');
0718: addText(name);
0719: }
0720: } else
0721: addText('#');
0722:
0723: return ch;
0724: }
0725:
0726: /**
0727: * This syntax isn't part of velocity.
0728: *
0729: * <code><pre>
0730: * #{ int foo = 3; }#
0731: * </pre></code>
0732: */
0733: private int parseVelocityScriptlet() throws IOException,
0734: JspParseException {
0735: addText();
0736:
0737: setLocation(_jspPath, _filename, _line);
0738: _lineStart = _line;
0739: _jspBuilder.startElement(JSP_SCRIPTLET);
0740: _jspBuilder.endAttributes();
0741:
0742: int ch = read();
0743: while (ch >= 0) {
0744: if (ch == '}') {
0745: ch = read();
0746: if (ch == '#')
0747: break;
0748: else
0749: addText('}');
0750: } else {
0751: addText((char) ch);
0752: ch = read();
0753: }
0754: }
0755:
0756: createText();
0757:
0758: _jspBuilder.endElement(JSP_SCRIPTLET.getName());
0759:
0760: ch = read();
0761:
0762: if (ch == '\r') {
0763: ch = read();
0764: if (ch == '\n')
0765: return read();
0766: else
0767: return ch;
0768: } else if (ch == '\n')
0769: return read();
0770: else
0771: return ch;
0772: }
0773:
0774: /**
0775: * parses a #foreach statement
0776: *
0777: * <pre>
0778: * #foreach ([Type] var in expr)
0779: * ...
0780: * #end
0781: * </pre>
0782: *
0783: * <pre>
0784: * #foreach ([Type] var in [min .. max])
0785: * ...
0786: * #end
0787: * </pre>
0788: */
0789: private int parseVelocityForeach(String eltName)
0790: throws IOException, JspParseException {
0791: int ch;
0792:
0793: for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
0794: }
0795:
0796: if (ch != '(')
0797: throw error(L
0798: .l(
0799: "Expected `(' after #foreach at `{0}'. The velocity-style #foreach syntax needs parentheses: #foreach ($a in expr)",
0800: badChar(ch)));
0801:
0802: addText();
0803:
0804: processTaglib("resin-c", JSTL_CORE_URI);
0805:
0806: setLocation(_jspPath, _filename, _lineStart);
0807: _lineStart = _line;
0808: _jspBuilder.startElement(JSTL_CORE_FOREACH);
0809:
0810: CharBuffer cb = CharBuffer.allocate();
0811: parseVelocityName(cb);
0812:
0813: if (cb.length() == 0) {
0814: throw error(L
0815: .l(
0816: "Expected iteration variable for #foreach at `{0}'. The velocity-style #foreach syntax is: #foreach ($a in expr)",
0817: badChar(ch)));
0818: }
0819:
0820: String name = cb.toString();
0821:
0822: cb.clear();
0823: parseVelocityName(cb);
0824:
0825: if (cb.length() == 0) {
0826: throw error(L
0827: .l(
0828: "Expected 'in' for #foreach at `{0}'. The velocity-style #foreach syntax is: #foreach ($a in expr)",
0829: badChar(ch)));
0830: }
0831:
0832: String value = cb.toString();
0833: if (!value.equals("in")) {
0834: throw error(L
0835: .l(
0836: "Expected 'in' for #foreach at `{0}'. The velocity-style #foreach syntax is: #foreach ($a in expr)",
0837: badChar(ch)));
0838: }
0839:
0840: if (name.startsWith("$"))
0841: name = name.substring(1);
0842:
0843: _jspBuilder.attribute(new QName("var"), name);
0844:
0845: cb.clear();
0846: parseVelocityExpr(cb, ')');
0847: String expr = cb.close();
0848:
0849: if (expr.indexOf("..") > 0) {
0850: int h = 0;
0851: for (; Character.isWhitespace(expr.charAt(h)); h++) {
0852: }
0853:
0854: if (expr.charAt(h) != '[')
0855: throw error(L
0856: .l(
0857: "Expected '[' for #foreach `{0}'. The velocity-style #foreach syntax is: #foreach ([Type] $a in [min .. max])",
0858: badChar(expr.charAt(h))));
0859:
0860: int t = expr.length() - 1;
0861: for (; Character.isWhitespace(expr.charAt(t)); t--) {
0862: }
0863:
0864: if (expr.charAt(t) != ']')
0865: throw error(L
0866: .l(
0867: "Expected ']' for #foreach `{0}'. The velocity-style #foreach syntax is: #foreach ($a in [min .. max])",
0868: badChar(expr.charAt(t))));
0869:
0870: int p = expr.indexOf("..");
0871:
0872: String min = expr.substring(h + 1, p);
0873: String max = expr.substring(p + 2, t);
0874:
0875: _jspBuilder.attribute(new QName("begin"), "${" + min + "}");
0876: _jspBuilder.attribute(new QName("end"), "${" + max + "}");
0877: } else {
0878: _jspBuilder
0879: .attribute(new QName("items"), "${" + expr + "}");
0880: }
0881: _jspBuilder.endAttributes();
0882:
0883: return skipWhitespaceToEndOfLine(read());
0884: }
0885:
0886: /**
0887: * parses an #if statement
0888: */
0889: private int parseVelocityIf(String eltName) throws IOException,
0890: JspParseException {
0891: int ch;
0892:
0893: for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
0894: }
0895:
0896: if (ch != '(')
0897: throw error(L
0898: .l(
0899: "Expected `(' after #if at `{0}'. The velocity-style #if syntax needs parentheses: #if (...)",
0900: badChar(ch)));
0901:
0902: addText();
0903:
0904: processTaglib("resin-c", JSTL_CORE_URI);
0905:
0906: setLocation(_jspPath, _filename, _line);
0907: if (eltName.equals("if")) {
0908: _jspBuilder.startElement(JSTL_CORE_CHOOSE);
0909: _jspBuilder.endAttributes();
0910: }
0911: _jspBuilder.startElement(JSTL_CORE_WHEN);
0912: _lineStart = _line;
0913:
0914: CharBuffer cb = CharBuffer.allocate();
0915: parseVelocityExpr(cb, ')');
0916: _jspBuilder.attribute(new QName("test"), "${" + cb.close()
0917: + "}");
0918: _jspBuilder.endAttributes();
0919:
0920: return skipWhitespaceToEndOfLine(read());
0921: }
0922:
0923: private int parseVelocityName(CharBuffer cb) throws IOException,
0924: JspParseException {
0925: int ch;
0926:
0927: for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
0928: }
0929:
0930: for (; Character.isJavaIdentifierPart((char) ch); ch = read())
0931: cb.append((char) ch);
0932:
0933: return ch;
0934: }
0935:
0936: private int parseVelocityMin(CharBuffer cb) throws IOException,
0937: JspParseException {
0938: int ch;
0939:
0940: for (ch = read(); ch >= 0; ch = read()) {
0941: if (ch != '$')
0942: cb.append((char) ch);
0943:
0944: if (ch == '(') {
0945: ch = parseVelocityExpr(cb, ')');
0946: cb.append((char) ch);
0947: } else if (ch == '[') {
0948: ch = parseVelocityExpr(cb, ']');
0949: cb.append((char) ch);
0950: } else if (ch == '.') {
0951: ch = read();
0952: if (ch == '.')
0953: return ch;
0954: else {
0955: cb.append('.');
0956: _peek = ch;
0957: }
0958: }
0959: }
0960:
0961: return ch;
0962: }
0963:
0964: private int parseVelocityExpr(CharBuffer cb, int end)
0965: throws IOException, JspParseException {
0966: int ch;
0967:
0968: for (ch = read(); ch >= 0 && ch != end; ch = read()) {
0969: if (ch != '$')
0970: cb.append((char) ch);
0971:
0972: if (ch == '(') {
0973: ch = parseVelocityExpr(cb, ')');
0974: cb.append((char) ch);
0975: } else if (ch == '[') {
0976: ch = parseVelocityExpr(cb, ']');
0977: cb.append((char) ch);
0978: }
0979: }
0980:
0981: return ch;
0982: }
0983:
0984: /**
0985: * Parses a <![CDATA[ block. All text in the CDATA is uninterpreted.
0986: */
0987: private void parseCdata() throws IOException, JspParseException {
0988: int ch;
0989:
0990: ch = readName(read());
0991:
0992: String name = _tag.toString();
0993:
0994: if (!name.equals("CDATA"))
0995: throw error(L.l("Expected <![CDATA[ at <!['{0}'.", name,
0996: "XML only recognizes the <![CDATA directive."));
0997:
0998: if (ch != '[')
0999: throw error(L
1000: .l(
1001: "Expected '[' at '{0}'. The XML CDATA syntax is <![CDATA[...]]>.",
1002: String.valueOf(ch)));
1003:
1004: String filename = _filename;
1005: int line = _line;
1006:
1007: while ((ch = read()) >= 0) {
1008: while (ch == ']') {
1009: if ((ch = read()) != ']')
1010: addText(']');
1011: else if ((ch = read()) != '>')
1012: addText("]]");
1013: else
1014: return;
1015: }
1016:
1017: addText((char) ch);
1018: }
1019:
1020: throw error(L
1021: .l(
1022: "Expected closing ]]> at end of file to match <![[CDATA at {0}.",
1023: filename + ":" + line));
1024: }
1025:
1026: /**
1027: * Parses an XML name for elements and attribute names. The parsed name
1028: * is stored in the 'tag' class variable.
1029: *
1030: * @param ch the next character
1031: *
1032: * @return the character following the name
1033: */
1034: private int readName(int ch) throws IOException, JspParseException {
1035: _tag.clear();
1036:
1037: for (; XmlChar.isNameChar((char) ch); ch = read())
1038: _tag.append((char) ch);
1039:
1040: return ch;
1041: }
1042:
1043: private void parsePageDirective(String name, String value)
1044: throws IOException, JspParseException {
1045: if ("isELIgnored".equals(name)) {
1046: if ("true".equals(value))
1047: _parseState.setELIgnored(true);
1048: }
1049: }
1050:
1051: /**
1052: * Parses a special JSP syntax.
1053: */
1054: private void parseScriptlet() throws IOException, JspParseException {
1055: addText();
1056:
1057: _lineStart = _line;
1058:
1059: int ch = read();
1060:
1061: // probably should be a qname
1062: QName eltName = null;
1063:
1064: switch (ch) {
1065: case '=':
1066: eltName = JSP_EXPRESSION;
1067: ch = read();
1068: break;
1069:
1070: case '!':
1071: eltName = JSP_DECLARATION;
1072: ch = read();
1073: break;
1074:
1075: case '@':
1076: parseDirective();
1077: return;
1078:
1079: case '-':
1080: if ((ch = read()) == '-') {
1081: parseComment();
1082: return;
1083: } else {
1084: eltName = JSP_SCRIPTLET;
1085: addText('-');
1086: }
1087: break;
1088:
1089: default:
1090: eltName = JSP_SCRIPTLET;
1091: break;
1092: }
1093:
1094: setLocation(_jspPath, _filename, _lineStart);
1095: _jspBuilder.startElement(eltName);
1096: _jspBuilder.endAttributes();
1097:
1098: while (ch >= 0) {
1099: switch (ch) {
1100: case '\\':
1101: addText('\\');
1102: ch = read();
1103: if (ch >= 0)
1104: addText((char) ch);
1105: ch = read();
1106: break;
1107:
1108: case '%':
1109: ch = read();
1110: if (ch == '>') {
1111: createText();
1112: setLocation();
1113: _jspBuilder.endElement(eltName.getName());
1114: return;
1115: } else if (ch == '\\') {
1116: ch = read();
1117: if (ch == '>') {
1118: addText("%");
1119: } else
1120: addText("%\\");
1121: } else
1122: addText('%');
1123: break;
1124:
1125: default:
1126: addText((char) ch);
1127: ch = read();
1128: break;
1129: }
1130: }
1131:
1132: createText();
1133: setLocation();
1134: _jspBuilder.endElement(eltName.getName());
1135: }
1136:
1137: /**
1138: * Parses the JSP directive syntax.
1139: */
1140: private void parseDirective() throws IOException, JspParseException {
1141: String language = null;
1142:
1143: int ch = skipWhitespace(read());
1144: String directive = "";
1145: if (XmlChar.isNameStart(ch)) {
1146: ch = readName(ch);
1147: directive = _tag.toString();
1148: } else
1149: throw error(L
1150: .l(
1151: "Expected jsp directive name at '{0}'. JSP directive syntax is <%@ name attr1='value1' ... %>",
1152: badChar(ch)));
1153:
1154: QName qname;
1155:
1156: if (directive.equals("page"))
1157: qname = JSP_DIRECTIVE_PAGE;
1158: else if (directive.equals("include"))
1159: qname = JSP_DIRECTIVE_INCLUDE;
1160: else if (directive.equals("taglib"))
1161: qname = JSP_DIRECTIVE_TAGLIB;
1162: else if (directive.equals("cache"))
1163: qname = JSP_DIRECTIVE_CACHE;
1164: else if (directive.equals("attribute"))
1165: qname = JSP_DIRECTIVE_ATTRIBUTE;
1166: else if (directive.equals("variable"))
1167: qname = JSP_DIRECTIVE_VARIABLE;
1168: else if (directive.equals("tag"))
1169: qname = JSP_DIRECTIVE_TAG;
1170: else
1171: throw error(L
1172: .l(
1173: "'{0}' is an unknown jsp directive. Only <%@ page ... %>, <%@ include ... %>, <%@ taglib ... %>, and <%@ cache ... %> are known.",
1174: directive));
1175:
1176: unread(ch);
1177:
1178: ArrayList<QName> keys = new ArrayList<QName>();
1179: ArrayList<String> values = new ArrayList<String>();
1180:
1181: parseAttributes(keys, values);
1182:
1183: ch = skipWhitespace(read());
1184:
1185: if (ch != '%' || (ch = read()) != '>') {
1186: throw error(L
1187: .l(
1188: "expected '%>' at {0}. JSP directive syntax is '<%@ name attr1='value1' ... %>'. (Started at line {1})",
1189: badChar(ch), _lineStart));
1190: }
1191:
1192: setLocation(_jspPath, _filename, _lineStart);
1193: _lineStart = _line;
1194: _jspBuilder.startElement(qname);
1195:
1196: for (int i = 0; i < keys.size(); i++) {
1197: _jspBuilder.attribute(keys.get(i), values.get(i));
1198: }
1199: _jspBuilder.endAttributes();
1200:
1201: if (qname.equals(JSP_DIRECTIVE_TAGLIB))
1202: processTaglibDirective(keys, values);
1203:
1204: setLocation();
1205: _jspBuilder.endElement(qname.getName());
1206:
1207: if (qname.equals(JSP_DIRECTIVE_PAGE)
1208: || qname.equals(JSP_DIRECTIVE_TAG)) {
1209: String contentEncoding = _parseState.getPageEncoding();
1210: if (contentEncoding == null)
1211: contentEncoding = _parseState.getCharEncoding();
1212:
1213: if (contentEncoding != null) {
1214: try {
1215: _stream.setEncoding(contentEncoding);
1216: } catch (Exception e) {
1217: log.log(Level.FINER, e.toString(), e);
1218:
1219: throw error(L.l("unknown content encoding '{0}'",
1220: contentEncoding), e);
1221: }
1222: }
1223: }
1224: /*
1225: if (directive.equals("include"))
1226: parseIncludeDirective(elt);
1227: else if (directive.equals("taglib"))
1228: parseTaglibDirective(elt);
1229: */
1230: }
1231:
1232: /**
1233: * Parses an XML comment.
1234: */
1235: private void parseComment() throws IOException, JspParseException {
1236: int ch = read();
1237:
1238: while (ch >= 0) {
1239: if (ch == '-') {
1240: ch = read();
1241: while (ch == '-') {
1242: if ((ch = read()) == '-')
1243: continue;
1244: else if (ch == '%' && (ch = read()) == '>')
1245: return;
1246: else if (ch == '-')
1247: ch = read();
1248: }
1249: } else
1250: ch = read();
1251: }
1252: }
1253:
1254: private void parseXmlComment() throws IOException,
1255: JspParseException {
1256: int ch;
1257:
1258: while ((ch = read()) >= 0) {
1259: while (ch == '-') {
1260: if ((ch = read()) == '-' && (ch = read()) == '>')
1261: return;
1262: }
1263: }
1264: }
1265:
1266: /**
1267: * Parses the open tag.
1268: */
1269: private void parseOpenTag(String name, int ch, boolean isXml)
1270: throws IOException, JspParseException {
1271: addText();
1272:
1273: ch = skipWhitespace(ch);
1274:
1275: ArrayList<QName> keys = new ArrayList<QName>();
1276: ArrayList<String> values = new ArrayList<String>();
1277:
1278: unread(ch);
1279:
1280: parseAttributes(keys, values);
1281:
1282: QName qname = getElementQName(name);
1283:
1284: setLocation(_jspPath, _filename, _lineStart);
1285: _lineStart = _line;
1286:
1287: _jspBuilder.startElement(qname);
1288:
1289: for (int i = 0; i < keys.size(); i++) {
1290: QName key = keys.get(i);
1291: String value = values.get(i);
1292:
1293: _jspBuilder.attribute(key, value);
1294: }
1295:
1296: _jspBuilder.endAttributes();
1297:
1298: if (qname.equals(JSP_DIRECTIVE_TAGLIB))
1299: processTaglibDirective(keys, values);
1300:
1301: ch = skipWhitespace(read());
1302:
1303: JspNode node = _jspBuilder.getCurrentNode();
1304:
1305: if (ch == '/') {
1306: if ((ch = read()) != '>')
1307: throw error(L
1308: .l(
1309: "expected '/>' at '{0}' (for tag '<{1}>' at line {2}). The XML empty tag syntax is: <tag attr1='value1'/>",
1310: badChar(ch), name, String
1311: .valueOf(_lineStart)));
1312:
1313: setLocation();
1314: _jspBuilder.endElement(qname.getName());
1315: } else if (ch != '>')
1316: throw error(L
1317: .l(
1318: "expected '>' at '{0}' (for tag '<{1}>' at line {2}). The XML tag syntax is: <tag attr1='value1'>",
1319: badChar(ch), name, String
1320: .valueOf(_lineStart)));
1321: // If tagdependent and not XML mode, then read the raw text.
1322: else if ("tagdependent".equals(node.getBodyContent())
1323: && !_isXml) {
1324: String tail = "</" + name + ">";
1325: for (ch = read(); ch >= 0; ch = read()) {
1326: _text.append((char) ch);
1327: if (_text.endsWith(tail)) {
1328: _text.setLength(_text.length() - tail.length());
1329: addText();
1330: _jspBuilder.endElement(qname.getName());
1331: return;
1332: }
1333: }
1334: throw error(L
1335: .l(
1336: "expected '{0}' at end of file (for tag <{1}> at line {2}). Tags with 'tagdependent' content need close tags.",
1337: tail, String.valueOf(_lineStart)));
1338: }
1339: }
1340:
1341: /**
1342: * Returns the full QName for the JSP page's name.
1343: */
1344: private QName getElementQName(String name) {
1345: int p = name.lastIndexOf(':');
1346:
1347: if (p > 0) {
1348: String prefix = name.substring(0, p);
1349: String url = Namespace.find(_namespaces, prefix);
1350:
1351: _prefixes.add(prefix);
1352:
1353: if (url != null)
1354: return new QName(prefix, name.substring(p + 1), url);
1355: else
1356: return new QName("", name, "");
1357: } else {
1358: String url = Namespace.find(_namespaces, "");
1359:
1360: if (url != null)
1361: return new QName("", name, url);
1362: else
1363: return new QName("", name, "");
1364: }
1365: }
1366:
1367: /**
1368: * Returns the full QName for the JSP page's name.
1369: */
1370: private QName getAttributeQName(String name) {
1371: int p = name.lastIndexOf(':');
1372:
1373: if (p > 0) {
1374: String prefix = name.substring(0, p);
1375: String url = Namespace.find(_namespaces, prefix);
1376:
1377: if (url != null)
1378: return new QName(prefix, name.substring(p + 1), url);
1379: else
1380: return new QName("", name, "");
1381: } else
1382: return new QName("", name, "");
1383: }
1384:
1385: /**
1386: * Parses the attributes of an element.
1387: */
1388: private void parseAttributes(ArrayList<QName> names,
1389: ArrayList<String> values) throws IOException,
1390: JspParseException {
1391: names.clear();
1392: values.clear();
1393:
1394: int ch = skipWhitespace(read());
1395:
1396: while (XmlChar.isNameStart(ch)) {
1397: ch = readName(ch);
1398: String key = _tag.toString();
1399:
1400: readValue(key, ch, _isXml);
1401: String value = _value.toString();
1402:
1403: if (key.startsWith("xmlns:")) {
1404: String prefix = key.substring(6);
1405:
1406: _jspBuilder.startPrefixMapping(prefix, value);
1407: //_parseState.pushNamespace(prefix, value);
1408: _namespaces = new Namespace(_namespaces, prefix, value);
1409: } else if (key.equals("xmlns")) {
1410: _jspBuilder.startPrefixMapping("", value);
1411: //_parseState.pushNamespace(prefix, value);
1412: //_parseState.pushNamespace("", value);
1413: _namespaces = new Namespace(_namespaces, "", value);
1414: } else {
1415: names.add(getAttributeQName(key));
1416: values.add(value);
1417: }
1418:
1419: ch = skipWhitespace(read());
1420: }
1421:
1422: unread(ch);
1423: }
1424:
1425: /**
1426: * Reads an attribute value.
1427: */
1428: private void readValue(String attribute, int ch, boolean isXml)
1429: throws IOException, JspParseException {
1430: boolean isRuntimeAttribute = false;
1431:
1432: _value.clear();
1433:
1434: ch = skipWhitespace(ch);
1435:
1436: if (ch != '=') {
1437: unread(ch);
1438: return;
1439: }
1440:
1441: ch = skipWhitespace(read());
1442:
1443: if (ch != '\'' && ch != '"') {
1444: if (XmlChar.isNameChar(ch)) {
1445: ch = readName(ch);
1446:
1447: throw error(L
1448: .l(
1449: "'{0}' attribute value must be quoted at '{1}'. JSP attribute syntax is either attr=\"value\" or attr='value'.",
1450: attribute, _tag));
1451: } else
1452: throw error(L
1453: .l(
1454: "'{0}' attribute value must be quoted at '{1}'. JSP attribute syntax is either attr=\"value\" or attr='value'.",
1455: attribute, badChar(ch)));
1456: }
1457:
1458: int end = ch;
1459: int lastCh = 0;
1460:
1461: ch = read();
1462: if (ch != '<') {
1463: } else if ((ch = read()) != '%')
1464: _value.append('<');
1465: else if ((ch = read()) != '=')
1466: _value.append("<%");
1467: else {
1468: _value.append("<%");
1469: isRuntimeAttribute = true;
1470: }
1471:
1472: while (ch != -1 && (ch != end || isRuntimeAttribute)) {
1473: if (ch == '\\') {
1474: switch ((ch = read())) {
1475: case '\\':
1476: case '\'':
1477: case '\"':
1478: // jsp/1505 vs jsp/184s
1479: // _value.append('\\');
1480: _value.append((char) ch);
1481: ch = read();
1482: break;
1483:
1484: case '>':
1485: if (lastCh == '%') {
1486: _value.append('>');
1487: ch = read();
1488: } else
1489: _value.append('\\');
1490: break;
1491:
1492: case '%':
1493: if (lastCh == '<') {
1494: _value.append('%');
1495: ch = read();
1496: } else
1497: _value.append('\\');
1498: break;
1499:
1500: default:
1501: _value.append('\\');
1502: break;
1503: }
1504: } else if (ch == '%' && isRuntimeAttribute) {
1505: _value.append('%');
1506: ch = read();
1507: if (ch == '>')
1508: isRuntimeAttribute = false;
1509: } else if (ch == '&' && isXml) {
1510: lastCh = -1;
1511: _value.append((char) parseEntity());
1512: ch = read();
1513: } else if (ch == '&') {
1514: if ((ch = read()) == 'a') {
1515: if ((ch = read()) != 'p')
1516: _value.append("&a");
1517: else if ((ch = read()) != 'o')
1518: _value.append("&ap");
1519: else if ((ch = read()) != 's')
1520: _value.append("&apo");
1521: else if ((ch = read()) != ';')
1522: _value.append("&apos");
1523: else {
1524: _value.append('\'');
1525: ch = read();
1526: }
1527: } else if (ch == 'q') {
1528: if ((ch = read()) != 'u')
1529: _value.append("&q");
1530: else if ((ch = read()) != 'o')
1531: _value.append("&qu");
1532: else if ((ch = read()) != 't')
1533: _value.append("&quo");
1534: else if ((ch = read()) != ';')
1535: _value.append(""");
1536: else {
1537: _value.append('"');
1538: ch = read();
1539: }
1540: } else
1541: _value.append('&');
1542: } else {
1543: _value.append((char) ch);
1544: lastCh = ch;
1545: ch = read();
1546: }
1547: }
1548: }
1549:
1550: /**
1551: * Parses an XML entity.
1552: */
1553: int parseEntity() throws IOException, JspParseException {
1554: int ch = read();
1555:
1556: if (_isXml && ch == '#') {
1557: int value = 0;
1558:
1559: for (ch = read(); ch >= '0' && ch <= '9'; ch = read())
1560: value = 10 * value + ch - '0';
1561:
1562: if (ch != ';')
1563: throw error(L
1564: .l(
1565: "expected ';' at '{0}' in character entity. The XML character entities syntax is &#nn;",
1566: badChar(ch)));
1567:
1568: return (char) value;
1569: }
1570:
1571: CharBuffer cb = CharBuffer.allocate();
1572: for (; ch >= 'a' && ch <= 'z'; ch = read())
1573: cb.append((char) ch);
1574:
1575: if (ch != ';') {
1576:
1577: log
1578: .warning(L
1579: .l(
1580: "expected ';' at '{0}' in entity '&{1}'. The XML entity syntax is &name;",
1581: badChar(ch), cb));
1582: }
1583:
1584: String entity = cb.close();
1585: if (entity.equals("lt"))
1586: return '<';
1587: else if (entity.equals("gt"))
1588: return '>';
1589: else if (entity.equals("amp"))
1590: return '&';
1591: else if (entity.equals("apos"))
1592: return '\'';
1593: else if (entity.equals("quot"))
1594: return '"';
1595: else
1596: throw error(L
1597: .l(
1598: "unknown entity '&{0};'. XML only recognizes the special entities <, >, &, ' and "",
1599: entity));
1600: }
1601:
1602: private int parseCloseTag() throws IOException, JspParseException {
1603: int ch;
1604:
1605: if (!XmlChar.isNameStart(ch = read())) {
1606: addText("</");
1607: return ch;
1608: }
1609:
1610: ch = readName(ch);
1611: String name = _tag.toString();
1612: if (!_isXml && getTag(name) == TAG_UNKNOWN) {
1613: addText("</");
1614: addText(name);
1615: return ch;
1616: }
1617:
1618: ch = skipWhitespace(ch);
1619: if (ch != '>')
1620: throw error(L
1621: .l(
1622: "expected '>' at {0}. The XML close tag syntax is </name>.",
1623: badChar(ch)));
1624:
1625: JspNode node = _jspBuilder.getCurrentNode();
1626: String nodeName = node.getTagName();
1627: if (nodeName.equals(name)) {
1628: } else if (nodeName.equals("resin-c:when")) {
1629: throw error(L
1630: .l(
1631: "#if expects closing #end before </{0}> (#if at {1}). #if statements require #end before the enclosing tag closes.",
1632: name, String.valueOf(node.getStartLine())));
1633: } else if (nodeName.equals("resin-c:otherwise")) {
1634: throw error(L
1635: .l(
1636: "#else expects closing #end before </{0}> (#else at {1}). #if statements require #end before the enclosing tag closes.",
1637: name, String.valueOf(node.getStartLine())));
1638: } else {
1639: throw error(L
1640: .l(
1641: "expected </{0}> at </{1}>. Closing tags must match opened tags.",
1642: nodeName, name));
1643: }
1644:
1645: addText();
1646:
1647: setLocation();
1648: _jspBuilder.endElement(name);
1649:
1650: return read();
1651: }
1652:
1653: private void processTaglibDirective(ArrayList<QName> keys,
1654: ArrayList<String> values) throws IOException,
1655: JspParseException {
1656: int p = keys.indexOf(PREFIX);
1657: if (p < 0)
1658: throw error(L
1659: .l("The taglib directive requires a 'prefix' attribute. 'prefix' is the XML prefix for all tags in the taglib."));
1660: String prefix = values.get(p);
1661:
1662: if (_prefixes.contains(prefix)
1663: && _parseState.getQName(prefix) == null) {
1664: throw error(L
1665: .l(
1666: "The taglib prefix '{0}' must be defined before it is used.",
1667: prefix));
1668: }
1669:
1670: String uri = null;
1671: p = keys.indexOf(URI);
1672: if (p >= 0)
1673: uri = values.get(p);
1674:
1675: String tagdir = null;
1676: p = keys.indexOf(TAGDIR);
1677: if (p >= 0)
1678: tagdir = values.get(p);
1679:
1680: if (uri != null)
1681: processTaglib(prefix, uri);
1682: else if (tagdir != null)
1683: processTaglibDir(prefix, tagdir);
1684: }
1685:
1686: /**
1687: * Adds a new known taglib prefix to the current namespace.
1688: */
1689: private void processTaglib(String prefix, String uri)
1690: throws JspParseException {
1691: Taglib taglib = null;
1692:
1693: int colon = uri.indexOf(':');
1694: int slash = uri.indexOf('/');
1695:
1696: String location = null;
1697:
1698: if (colon > 0 && colon < slash)
1699: location = uri;
1700: else if (slash == 0)
1701: location = uri;
1702: else
1703: location = _uriPwd + uri;
1704:
1705: try {
1706: taglib = _tagManager.addTaglib(prefix, uri, location);
1707: String tldURI = "urn:jsptld:" + uri;
1708:
1709: _parseState.pushNamespace(prefix, tldURI);
1710: _namespaces = new Namespace(_namespaces, prefix, tldURI);
1711: return;
1712: } catch (JspParseException e) {
1713: throw error(e);
1714: } catch (Exception e) {
1715: log.log(Level.WARNING, e.toString(), e);
1716: }
1717:
1718: if (colon > 0 && colon < slash)
1719: throw error(L
1720: .l(
1721: "Unknown taglib '{0}'. Taglibs specified with an absolute URI must either be:\n1) specified in the web.xml\n2) defined in a jar's .tld in META-INF\n3) defined in a .tld in WEB-INF\n4) predefined by Resin",
1722: uri));
1723: }
1724:
1725: /**
1726: * Adds a new known tag dir to the current namespace.
1727: */
1728: private void processTaglibDir(String prefix, String tagDir)
1729: throws JspParseException {
1730: Taglib taglib = null;
1731:
1732: try {
1733: taglib = _tagManager.addTaglibDir(prefix, tagDir);
1734: String tagURI = "urn:jsptagdir:" + tagDir;
1735: _parseState.pushNamespace(prefix, tagURI);
1736: _namespaces = new Namespace(_namespaces, prefix, tagURI);
1737: return;
1738: } catch (JspParseException e) {
1739: throw error(e);
1740: } catch (Exception e) {
1741: log.log(Level.WARNING, e.toString(), e);
1742: }
1743: }
1744:
1745: private void processIncludeDirective(ArrayList keys,
1746: ArrayList values) throws IOException, JspParseException {
1747: int p = keys.indexOf("file");
1748: if (p < 0)
1749: throw error(L
1750: .l("The include directive requires a 'file' attribute."));
1751: String file = (String) values.get(p);
1752:
1753: pushInclude(file);
1754: }
1755:
1756: public void pushInclude(String value) throws IOException,
1757: JspParseException {
1758: pushInclude(value, false);
1759: }
1760:
1761: public void pushInclude(String value, boolean allowDuplicate)
1762: throws IOException, JspParseException {
1763: if (value.equals(""))
1764: throw error("include directive needs 'file' attribute. Use either <%@ include file='myfile.jsp' %> or <jsp:directive.include file='myfile.jsp'/>");
1765:
1766: Path include;
1767: if (value.length() > 0 && value.charAt(0) == '/')
1768: include = _parseState.resolvePath(value);
1769: else
1770: include = _parseState.resolvePath(_uriPwd + value);
1771:
1772: String newUrl = _uriPwd;
1773:
1774: if (value.startsWith("/"))
1775: newUrl = value;
1776: else
1777: newUrl = _uriPwd + value;
1778:
1779: include.setUserPath(newUrl);
1780:
1781: String newUrlPwd;
1782: int p = newUrl.lastIndexOf('/');
1783: newUrlPwd = newUrl.substring(0, p + 1);
1784:
1785: if (_jspPath != null && _jspPath.equals(include)
1786: && !allowDuplicate)
1787: throw error(L
1788: .l(
1789: "circular include of '{0}' forbidden. A JSP file may not include itself.",
1790: include));
1791: for (int i = 0; i < _includes.size(); i++) {
1792: Include inc = _includes.get(i);
1793: if (inc._stream.getPath().equals(include)
1794: && !allowDuplicate)
1795: throw error(L
1796: .l(
1797: "circular include of '{0}'. A JSP file may not include itself.",
1798: include));
1799: }
1800:
1801: try {
1802: addInclude(include.openRead(), newUrlPwd);
1803: } catch (IOException e) {
1804: log.log(Level.WARNING, e.toString(), e);
1805:
1806: if (include.exists())
1807: throw error(L
1808: .l(
1809: "can't open include of '{0}'. '{1}' exists but it's not readable.",
1810: value, include.getNativePath()));
1811: else
1812: throw error(L
1813: .l(
1814: "can't open include of '{0}'. '{1}' does not exist.",
1815: value, include.getNativePath()));
1816: }
1817: }
1818:
1819: private void addInclude(ReadStream stream, String newUrlPwd)
1820: throws IOException, JspParseException {
1821: addText();
1822:
1823: readLf();
1824:
1825: Include inc = null;
1826:
1827: if (_stream != null) {
1828: inc = new Include(_stream, _line, _uriPwd);
1829:
1830: _includes.add(inc);
1831: }
1832:
1833: _parseState.addDepend(stream.getPath());
1834:
1835: try {
1836: String encoding = _stream.getEncoding();
1837: if (encoding != null)
1838: stream.setEncoding(encoding);
1839: } catch (Exception e) {
1840: }
1841: _stream = stream;
1842:
1843: _filename = stream.getUserPath();
1844: _jspPath = stream.getPath();
1845: _line = 1;
1846: _lineStart = _line;
1847: _uriPwd = newUrlPwd;
1848: _parseState.setUriPwd(_uriPwd);
1849: }
1850:
1851: /**
1852: * Skips whitespace characters.
1853: *
1854: * @param ch the current character
1855: *
1856: * @return the first non-whitespace character
1857: */
1858: private int skipWhitespace(int ch) throws IOException,
1859: JspParseException {
1860: for (; XmlChar.isWhitespace(ch); ch = read()) {
1861: }
1862:
1863: return ch;
1864: }
1865:
1866: /**
1867: * Skips whitespace to end of line
1868: *
1869: * @param ch the current character
1870: *
1871: * @return the first non-whitespace character
1872: */
1873: private int skipWhitespaceToEndOfLine(int ch) throws IOException,
1874: JspParseException {
1875: for (; XmlChar.isWhitespace(ch); ch = read()) {
1876: if (ch == '\n')
1877: return read();
1878: else if (ch == '\r') {
1879: ch = read();
1880: if (ch == '\n')
1881: return read();
1882: else
1883: return ch;
1884: }
1885: }
1886:
1887: return ch;
1888: }
1889:
1890: private void addText(char ch) {
1891: _text.append(ch);
1892: }
1893:
1894: private void addText(String s) {
1895: _text.append(s);
1896: }
1897:
1898: private void addText() throws JspParseException {
1899: if (_text.length() > 0)
1900: createText();
1901:
1902: _startText = _charCount;
1903: _lineStart = _line;
1904: }
1905:
1906: private void createText() throws JspParseException {
1907: String string = _text.toString();
1908:
1909: setLocation(_jspPath, _filename, _lineStart);
1910:
1911: if (_parseState.isTrimWhitespace() && isWhitespace(string)) {
1912: } else
1913: _jspBuilder.text(string, _filename, _lineStart, _line);
1914:
1915: _lineStart = _line;
1916: _text.clear();
1917: _startText = _charCount;
1918: }
1919:
1920: private boolean isWhitespace(String s) {
1921: int length = s.length();
1922:
1923: for (int i = 0; i < length; i++) {
1924: if (!Character.isWhitespace(s.charAt(i)))
1925: return false;
1926: }
1927:
1928: return true;
1929: }
1930:
1931: /**
1932: * Checks to see if the element name represents a tag.
1933: */
1934: private int getTag(String name) throws JspParseException {
1935: int p = name.indexOf(':');
1936: if (p < 0)
1937: return TAG_UNKNOWN;
1938:
1939: String prefix = name.substring(0, p);
1940: String local = name.substring(p + 1);
1941:
1942: _prefixes.add(prefix);
1943:
1944: String url = Namespace.find(_namespaces, prefix);
1945:
1946: if (url != null)
1947: return TAG_JSP;
1948: else
1949: return TAG_UNKNOWN;
1950:
1951: /*
1952: QName qname;
1953:
1954: if (url != null)
1955: qname = new QName(prefix, local, url);
1956: else
1957: qname = new QName(prefix, local, null);
1958:
1959: TagInfo tag = _tagManager.getTag(qname);
1960:
1961: if (tag != null)
1962: return TAG_JSP;
1963: else
1964: return TAG_UNKNOWN;
1965: */
1966: }
1967:
1968: private void unread(int ch) {
1969: _peek = ch;
1970: }
1971:
1972: /**
1973: * Reads the next character we're in the middle of cr/lf.
1974: */
1975: private void readLf() throws IOException, JspParseException {
1976: if (_seenCr) {
1977: int ch = read();
1978:
1979: if (ch != '\n')
1980: _peek = ch;
1981: }
1982: }
1983:
1984: /**
1985: * Reads the next character.
1986: */
1987: private int read() throws IOException, JspParseException {
1988: int ch;
1989:
1990: if (_peek >= 0) {
1991: ch = _peek;
1992: _peek = -1;
1993: return ch;
1994: }
1995:
1996: try {
1997: if ((ch = _stream.readChar()) >= 0) {
1998: _charCount++;
1999:
2000: if (ch == '\r') {
2001: _line++;
2002: _charCount = 0;
2003: _seenCr = true;
2004: } else if (ch == '\n' && _seenCr) {
2005: _seenCr = false;
2006: _charCount = 0;
2007: } else if (ch == '\n') {
2008: _line++;
2009: _charCount = 0;
2010: } else {
2011: _seenCr = false;
2012: }
2013:
2014: return ch;
2015: }
2016: } catch (IOException e) {
2017: throw error(e.toString());
2018: }
2019:
2020: _stream.close();
2021: _seenCr = false;
2022:
2023: if (_includes.size() > 0) {
2024: setLocation(_jspPath, _filename, _line);
2025:
2026: Include include = _includes.get(_includes.size() - 1);
2027: _includes.remove(_includes.size() - 1);
2028:
2029: _stream = include._stream;
2030: _filename = _stream.getUserPath();
2031: _jspPath = _stream.getPath();
2032: _line = include._line;
2033: _lineStart = _line;
2034: _uriPwd = include._uriPwd;
2035: _parseState.setUriPwd(_uriPwd);
2036:
2037: setLocation(_jspPath, _filename, _line);
2038:
2039: return read();
2040: }
2041:
2042: return -1;
2043: }
2044:
2045: void clear(Path appDir, String errorPage) {
2046: }
2047:
2048: /**
2049: * Creates an error message adding the filename and line.
2050: *
2051: * @param message the error message
2052: */
2053: public JspParseException error(Exception e) {
2054: String message = e.getMessage();
2055:
2056: if (e instanceof JspParseException) {
2057: log.log(Level.FINE, e.toString(), e);
2058: }
2059:
2060: if (e instanceof JspLineParseException)
2061: return (JspLineParseException) e;
2062: else if (e instanceof LineCompileException)
2063: return new JspLineParseException(e);
2064:
2065: if (_lineMap == null)
2066: return new JspLineParseException(_filename + ":" + _line
2067: + ": " + message, e);
2068: else {
2069: LineMap.Line line = _lineMap.getLine(_line);
2070:
2071: return new JspLineParseException(line.getSourceFilename()
2072: + ":" + line.getSourceLine(_line) + ": " + message,
2073: e);
2074: }
2075: }
2076:
2077: /**
2078: * Creates an error message adding the filename and line.
2079: *
2080: * @param message the error message
2081: */
2082: public JspParseException error(String message) {
2083: JspGenerator gen = _jspBuilder.getGenerator();
2084:
2085: if (_lineMap == null) {
2086: if (gen != null)
2087: return new JspLineParseException(_filename + ":"
2088: + _line + ": " + message
2089: + gen.getSourceLines(_jspPath, _line));
2090: else
2091: return new JspLineParseException(_filename + ":"
2092: + _line + ": " + message);
2093: } else {
2094: LineMap.Line line = _lineMap.getLine(_line);
2095:
2096: return new JspLineParseException(line.getSourceFilename()
2097: + ":" + line.getSourceLine(_line) + ": " + message);
2098: }
2099: }
2100:
2101: /**
2102: * Creates an error message adding the filename and line.
2103: *
2104: * @param message the error message
2105: */
2106: public JspParseException error(String message, Throwable e) {
2107: if (_lineMap == null)
2108: return new JspLineParseException(_filename + ":" + _line
2109: + ": " + message, e);
2110: else {
2111: LineMap.Line line = _lineMap.getLine(_line);
2112:
2113: return new JspLineParseException(line.getSourceFilename()
2114: + ":" + line.getSourceLine(_line) + ": " + message,
2115: e);
2116: }
2117: }
2118:
2119: /**
2120: * Sets the current location in the original file
2121: */
2122: private void setLocation() {
2123: setLocation(_jspPath, _filename, _line);
2124: }
2125:
2126: /**
2127: * Sets the current location in the original file
2128: *
2129: * @param filename the filename
2130: * @param line the line in the source file
2131: */
2132: private void setLocation(Path jspPath, String filename, int line) {
2133: if (_lineMap == null) {
2134: _jspBuilder.setLocation(jspPath, filename, line);
2135: } else {
2136: LineMap.Line srcLine = _lineMap.getLine(line);
2137:
2138: if (srcLine != null) {
2139: _jspBuilder.setLocation(jspPath, srcLine
2140: .getSourceFilename(), srcLine
2141: .getSourceLine(line));
2142: }
2143: }
2144: }
2145:
2146: private String badChar(int ch) {
2147: if (ch < 0)
2148: return "end of file";
2149: else if (ch == '\n' || ch == '\r')
2150: return "end of line";
2151: else if (ch >= 0x20 && ch <= 0x7f)
2152: return "'" + (char) ch + "'";
2153: else
2154: return "'" + (char) ch + "' (\\u" + hex(ch) + ")";
2155: }
2156:
2157: private String hex(int value) {
2158: CharBuffer cb = new CharBuffer();
2159:
2160: for (int b = 3; b >= 0; b--) {
2161: int v = (value >> (4 * b)) & 0xf;
2162: if (v < 10)
2163: cb.append((char) (v + '0'));
2164: else
2165: cb.append((char) (v - 10 + 'a'));
2166: }
2167:
2168: return cb.toString();
2169: }
2170:
2171: class Include {
2172: ReadStream _stream;
2173: int _line;
2174: String _uriPwd;
2175:
2176: Include(ReadStream stream, int line, String uriPwd) {
2177: _stream = stream;
2178: _line = line;
2179: _uriPwd = uriPwd;
2180: }
2181: }
2182: }
|