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.io.IOException;
0022: import java.io.InputStream;
0023:
0024: import java.util.Iterator;
0025: import java.util.List;
0026: import java.util.jar.JarFile;
0027:
0028: import javax.servlet.jsp.tagext.TagFileInfo;
0029: import javax.servlet.jsp.tagext.TagInfo;
0030: import javax.servlet.jsp.tagext.TagLibraryInfo;
0031: import javax.xml.parsers.SAXParser;
0032: import javax.xml.parsers.SAXParserFactory;
0033:
0034: import org.apache.jasper.JasperException;
0035: import org.apache.jasper.JspCompilationContext;
0036: import org.xml.sax.Attributes;
0037: import org.xml.sax.InputSource;
0038: import org.xml.sax.Locator;
0039: import org.xml.sax.SAXException;
0040: import org.xml.sax.SAXParseException;
0041: import org.xml.sax.XMLReader;
0042: import org.xml.sax.ext.LexicalHandler;
0043: import org.xml.sax.helpers.AttributesImpl;
0044: import org.xml.sax.helpers.DefaultHandler;
0045:
0046: /**
0047: * Class implementing a parser for a JSP document, that is, a JSP page in XML
0048: * syntax.
0049: *
0050: * @author Jan Luehe
0051: * @author Kin-man Chung
0052: */
0053:
0054: class JspDocumentParser extends DefaultHandler implements
0055: LexicalHandler, TagConstants {
0056:
0057: private static final String JSP_VERSION = "version";
0058: private static final String LEXICAL_HANDLER_PROPERTY = "http://xml.org/sax/properties/lexical-handler";
0059: private static final String JSP_URI = "http://java.sun.com/JSP/Page";
0060:
0061: private static final EnableDTDValidationException ENABLE_DTD_VALIDATION_EXCEPTION = new EnableDTDValidationException(
0062: "jsp.error.enable_dtd_validation", null);
0063:
0064: private ParserController parserController;
0065: private JspCompilationContext ctxt;
0066: private PageInfo pageInfo;
0067: private String path;
0068: private StringBuffer charBuffer;
0069:
0070: // Node representing the XML element currently being parsed
0071: private Node current;
0072:
0073: /*
0074: * Outermost (in the nesting hierarchy) node whose body is declared to be
0075: * scriptless. If a node's body is declared to be scriptless, all its
0076: * nested nodes must be scriptless, too.
0077: */
0078: private Node scriptlessBodyNode;
0079:
0080: private Locator locator;
0081:
0082: //Mark representing the start of the current element. Note
0083: //that locator.getLineNumber() and locator.getColumnNumber()
0084: //return the line and column numbers for the character
0085: //immediately _following_ the current element. The underlying
0086: //XMl parser eats white space that is not part of character
0087: //data, so for Nodes that are not created from character data,
0088: //this is the best we can do. But when we parse character data,
0089: //we get an accurate starting location by starting with startMark
0090: //as set by the previous element, and updating it as we advance
0091: //through the characters.
0092: private Mark startMark;
0093:
0094: // Flag indicating whether we are inside DTD declarations
0095: private boolean inDTD;
0096:
0097: private boolean isValidating;
0098:
0099: private ErrorDispatcher err;
0100: private boolean isTagFile;
0101: private boolean directivesOnly;
0102: private boolean isTop;
0103:
0104: // Nesting level of Tag dependent bodies
0105: private int tagDependentNesting = 0;
0106: // Flag set to delay incrmenting tagDependentNesting until jsp:body
0107: // is first encountered
0108: private boolean tagDependentPending = false;
0109:
0110: /*
0111: * Constructor
0112: */
0113: public JspDocumentParser(ParserController pc, String path,
0114: boolean isTagFile, boolean directivesOnly) {
0115: this .parserController = pc;
0116: this .ctxt = pc.getJspCompilationContext();
0117: this .pageInfo = pc.getCompiler().getPageInfo();
0118: this .err = pc.getCompiler().getErrorDispatcher();
0119: this .path = path;
0120: this .isTagFile = isTagFile;
0121: this .directivesOnly = directivesOnly;
0122: this .isTop = true;
0123: }
0124:
0125: /*
0126: * Parses a JSP document by responding to SAX events.
0127: *
0128: * @throws JasperException
0129: */
0130: public static Node.Nodes parse(ParserController pc, String path,
0131: JarFile jarFile, Node parent, boolean isTagFile,
0132: boolean directivesOnly, String pageEnc,
0133: String jspConfigPageEnc,
0134: boolean isEncodingSpecifiedInProlog, boolean isBomPresent)
0135: throws JasperException {
0136:
0137: JspDocumentParser jspDocParser = new JspDocumentParser(pc,
0138: path, isTagFile, directivesOnly);
0139: Node.Nodes pageNodes = null;
0140:
0141: try {
0142:
0143: // Create dummy root and initialize it with given page encodings
0144: Node.Root dummyRoot = new Node.Root(null, parent, true);
0145: dummyRoot.setPageEncoding(pageEnc);
0146: dummyRoot.setJspConfigPageEncoding(jspConfigPageEnc);
0147: dummyRoot
0148: .setIsEncodingSpecifiedInProlog(isEncodingSpecifiedInProlog);
0149: dummyRoot.setIsBomPresent(isBomPresent);
0150: jspDocParser.current = dummyRoot;
0151: if (parent == null) {
0152: jspDocParser.addInclude(dummyRoot,
0153: jspDocParser.pageInfo.getIncludePrelude());
0154: } else {
0155: jspDocParser.isTop = false;
0156: }
0157:
0158: // Parse the input
0159: SAXParser saxParser = getSAXParser(false, jspDocParser);
0160: InputStream inStream = null;
0161: try {
0162: inStream = JspUtil.getInputStream(path, jarFile,
0163: jspDocParser.ctxt, jspDocParser.err);
0164: saxParser
0165: .parse(new InputSource(inStream), jspDocParser);
0166: } catch (EnableDTDValidationException e) {
0167: saxParser = getSAXParser(true, jspDocParser);
0168: jspDocParser.isValidating = true;
0169: if (inStream != null) {
0170: try {
0171: inStream.close();
0172: } catch (Exception any) {
0173: }
0174: }
0175: inStream = JspUtil.getInputStream(path, jarFile,
0176: jspDocParser.ctxt, jspDocParser.err);
0177: saxParser
0178: .parse(new InputSource(inStream), jspDocParser);
0179: } finally {
0180: if (inStream != null) {
0181: try {
0182: inStream.close();
0183: } catch (Exception any) {
0184: }
0185: }
0186: }
0187:
0188: if (parent == null) {
0189: jspDocParser.addInclude(dummyRoot,
0190: jspDocParser.pageInfo.getIncludeCoda());
0191: }
0192:
0193: // Create Node.Nodes from dummy root
0194: pageNodes = new Node.Nodes(dummyRoot);
0195:
0196: } catch (IOException ioe) {
0197: jspDocParser.err.jspError("jsp.error.data.file.read", path,
0198: ioe);
0199: } catch (SAXParseException e) {
0200: jspDocParser.err.jspError(new Mark(jspDocParser.ctxt, path,
0201: e.getLineNumber(), e.getColumnNumber()), e
0202: .getMessage());
0203: } catch (Exception e) {
0204: jspDocParser.err.jspError(e);
0205: }
0206:
0207: return pageNodes;
0208: }
0209:
0210: /*
0211: * Processes the given list of included files.
0212: *
0213: * This is used to implement the include-prelude and include-coda
0214: * subelements of the jsp-config element in web.xml
0215: */
0216: private void addInclude(Node parent, List files)
0217: throws SAXException {
0218: if (files != null) {
0219: Iterator iter = files.iterator();
0220: while (iter.hasNext()) {
0221: String file = (String) iter.next();
0222: AttributesImpl attrs = new AttributesImpl();
0223: attrs.addAttribute("", "file", "file", "CDATA", file);
0224:
0225: // Create a dummy Include directive node
0226: Node includeDir = new Node.IncludeDirective(attrs,
0227: null, // XXX
0228: parent);
0229: processIncludeDirective(file, includeDir);
0230: }
0231: }
0232: }
0233:
0234: /*
0235: * Receives notification of the start of an element.
0236: *
0237: * This method assigns the given tag attributes to one of 3 buckets:
0238: *
0239: * - "xmlns" attributes that represent (standard or custom) tag libraries.
0240: * - "xmlns" attributes that do not represent tag libraries.
0241: * - all remaining attributes.
0242: *
0243: * For each "xmlns" attribute that represents a custom tag library, the
0244: * corresponding TagLibraryInfo object is added to the set of custom
0245: * tag libraries.
0246: */
0247: public void startElement(String uri, String localName,
0248: String qName, Attributes attrs) throws SAXException {
0249:
0250: AttributesImpl taglibAttrs = null;
0251: AttributesImpl nonTaglibAttrs = null;
0252: AttributesImpl nonTaglibXmlnsAttrs = null;
0253:
0254: processChars();
0255:
0256: checkPrefixes(uri, qName, attrs);
0257:
0258: if (directivesOnly
0259: && !(JSP_URI.equals(uri) && localName
0260: .startsWith(DIRECTIVE_ACTION))) {
0261: return;
0262: }
0263:
0264: // jsp:text must not have any subelements
0265: if (JSP_URI.equals(uri)
0266: && TEXT_ACTION.equals(current.getLocalName())) {
0267: throw new SAXParseException(Localizer
0268: .getMessage("jsp.error.text.has_subelement"),
0269: locator);
0270: }
0271:
0272: startMark = new Mark(ctxt, path, locator.getLineNumber(),
0273: locator.getColumnNumber());
0274:
0275: if (attrs != null) {
0276: /*
0277: * Notice that due to a bug in the underlying SAX parser, the
0278: * attributes must be enumerated in descending order.
0279: */
0280: boolean isTaglib = false;
0281: for (int i = attrs.getLength() - 1; i >= 0; i--) {
0282: isTaglib = false;
0283: String attrQName = attrs.getQName(i);
0284: if (!attrQName.startsWith("xmlns")) {
0285: if (nonTaglibAttrs == null) {
0286: nonTaglibAttrs = new AttributesImpl();
0287: }
0288: nonTaglibAttrs.addAttribute(attrs.getURI(i), attrs
0289: .getLocalName(i), attrs.getQName(i), attrs
0290: .getType(i), attrs.getValue(i));
0291: } else {
0292: if (attrQName.startsWith("xmlns:jsp")) {
0293: isTaglib = true;
0294: } else {
0295: String attrUri = attrs.getValue(i);
0296: // TaglibInfo for this uri already established in
0297: // startPrefixMapping
0298: isTaglib = pageInfo.hasTaglib(attrUri);
0299: }
0300: if (isTaglib) {
0301: if (taglibAttrs == null) {
0302: taglibAttrs = new AttributesImpl();
0303: }
0304: taglibAttrs.addAttribute(attrs.getURI(i), attrs
0305: .getLocalName(i), attrs.getQName(i),
0306: attrs.getType(i), attrs.getValue(i));
0307: } else {
0308: if (nonTaglibXmlnsAttrs == null) {
0309: nonTaglibXmlnsAttrs = new AttributesImpl();
0310: }
0311: nonTaglibXmlnsAttrs.addAttribute(attrs
0312: .getURI(i), attrs.getLocalName(i),
0313: attrs.getQName(i), attrs.getType(i),
0314: attrs.getValue(i));
0315: }
0316: }
0317: }
0318: }
0319:
0320: Node node = null;
0321:
0322: if (tagDependentPending && JSP_URI.equals(uri)
0323: && localName.equals(BODY_ACTION)) {
0324: tagDependentPending = false;
0325: tagDependentNesting++;
0326: current = parseStandardAction(qName, localName,
0327: nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
0328: startMark, current);
0329: return;
0330: }
0331:
0332: if (tagDependentPending && JSP_URI.equals(uri)
0333: && localName.equals(ATTRIBUTE_ACTION)) {
0334: current = parseStandardAction(qName, localName,
0335: nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
0336: startMark, current);
0337: return;
0338: }
0339:
0340: if (tagDependentPending) {
0341: tagDependentPending = false;
0342: tagDependentNesting++;
0343: }
0344:
0345: if (tagDependentNesting > 0) {
0346: node = new Node.UninterpretedTag(qName, localName,
0347: nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
0348: startMark, current);
0349: } else if (JSP_URI.equals(uri)) {
0350: node = parseStandardAction(qName, localName,
0351: nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
0352: startMark, current);
0353: } else {
0354: node = parseCustomAction(qName, localName, uri,
0355: nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
0356: startMark, current);
0357: if (node == null) {
0358: node = new Node.UninterpretedTag(qName, localName,
0359: nonTaglibAttrs, nonTaglibXmlnsAttrs,
0360: taglibAttrs, startMark, current);
0361: } else {
0362: // custom action
0363: String bodyType = getBodyType((Node.CustomTag) node);
0364:
0365: if (scriptlessBodyNode == null
0366: && bodyType
0367: .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
0368: scriptlessBodyNode = node;
0369: } else if (TagInfo.BODY_CONTENT_TAG_DEPENDENT
0370: .equalsIgnoreCase(bodyType)) {
0371: tagDependentPending = true;
0372: }
0373: }
0374: }
0375:
0376: current = node;
0377: }
0378:
0379: /*
0380: * Receives notification of character data inside an element.
0381: *
0382: * The SAX does not call this method with all of the template text, but may
0383: * invoke this method with chunks of it. This is a problem when we try
0384: * to determine if the text contains only whitespaces, or when we are
0385: * looking for an EL expression string. Therefore it is necessary to
0386: * buffer and concatenate the chunks and process the concatenated text
0387: * later (at beginTag and endTag)
0388: *
0389: * @param buf The characters
0390: * @param offset The start position in the character array
0391: * @param len The number of characters to use from the character array
0392: *
0393: * @throws SAXException
0394: */
0395: public void characters(char[] buf, int offset, int len) {
0396:
0397: if (charBuffer == null) {
0398: charBuffer = new StringBuffer();
0399: }
0400: charBuffer.append(buf, offset, len);
0401: }
0402:
0403: private void processChars() throws SAXException {
0404:
0405: if (charBuffer == null || directivesOnly) {
0406: return;
0407: }
0408:
0409: /*
0410: * JSP.6.1.1: All textual nodes that have only white space are to be
0411: * dropped from the document, except for nodes in a jsp:text element,
0412: * and any leading and trailing white-space-only textual nodes in a
0413: * jsp:attribute whose 'trim' attribute is set to FALSE, which are to
0414: * be kept verbatim.
0415: * JSP.6.2.3 defines white space characters.
0416: */
0417: boolean isAllSpace = true;
0418: if (!(current instanceof Node.JspText)
0419: && !(current instanceof Node.NamedAttribute)) {
0420: for (int i = 0; i < charBuffer.length(); i++) {
0421: if (!(charBuffer.charAt(i) == ' '
0422: || charBuffer.charAt(i) == '\n'
0423: || charBuffer.charAt(i) == '\r' || charBuffer
0424: .charAt(i) == '\t')) {
0425: isAllSpace = false;
0426: break;
0427: }
0428: }
0429: }
0430:
0431: if (!isAllSpace && tagDependentPending) {
0432: tagDependentPending = false;
0433: tagDependentNesting++;
0434: }
0435:
0436: if (tagDependentNesting > 0) {
0437: if (charBuffer.length() > 0) {
0438: new Node.TemplateText(charBuffer.toString(), startMark,
0439: current);
0440: }
0441: startMark = new Mark(ctxt, path, locator.getLineNumber(),
0442: locator.getColumnNumber());
0443: charBuffer = null;
0444: return;
0445: }
0446:
0447: if ((current instanceof Node.JspText)
0448: || (current instanceof Node.NamedAttribute)
0449: || !isAllSpace) {
0450:
0451: int line = startMark.getLineNumber();
0452: int column = startMark.getColumnNumber();
0453:
0454: CharArrayWriter ttext = new CharArrayWriter();
0455: int lastCh = 0, elType = 0;
0456: for (int i = 0; i < charBuffer.length(); i++) {
0457:
0458: int ch = charBuffer.charAt(i);
0459: if (ch == '\n') {
0460: column = 1;
0461: line++;
0462: } else {
0463: column++;
0464: }
0465: if ((lastCh == '$' || lastCh == '#') && ch == '{') {
0466: elType = lastCh;
0467: if (ttext.size() > 0) {
0468: new Node.TemplateText(ttext.toString(),
0469: startMark, current);
0470: ttext = new CharArrayWriter();
0471: //We subtract two from the column number to
0472: //account for the '[$,#]{' that we've already parsed
0473: startMark = new Mark(ctxt, path, line,
0474: column - 2);
0475: }
0476: // following "${" || "#{" to first unquoted "}"
0477: i++;
0478: boolean singleQ = false;
0479: boolean doubleQ = false;
0480: lastCh = 0;
0481: for (;; i++) {
0482: if (i >= charBuffer.length()) {
0483: throw new SAXParseException(Localizer
0484: .getMessage(
0485: "jsp.error.unterminated",
0486: (char) elType + "{"),
0487: locator);
0488:
0489: }
0490: ch = charBuffer.charAt(i);
0491: if (ch == '\n') {
0492: column = 1;
0493: line++;
0494: } else {
0495: column++;
0496: }
0497: if (lastCh == '\\' && (singleQ || doubleQ)) {
0498: ttext.write(ch);
0499: lastCh = 0;
0500: continue;
0501: }
0502: if (ch == '}') {
0503: new Node.ELExpression((char) elType, ttext
0504: .toString(), startMark, current);
0505: ttext = new CharArrayWriter();
0506: startMark = new Mark(ctxt, path, line,
0507: column);
0508: break;
0509: }
0510: if (ch == '"')
0511: doubleQ = !doubleQ;
0512: else if (ch == '\'')
0513: singleQ = !singleQ;
0514:
0515: ttext.write(ch);
0516: lastCh = ch;
0517: }
0518: } else if (lastCh == '\\' && (ch == '$' || ch == '#')) {
0519: ttext.write(ch);
0520: ch = 0; // Not start of EL anymore
0521: } else {
0522: if (lastCh == '$' || lastCh == '#'
0523: || lastCh == '\\') {
0524: ttext.write(lastCh);
0525: }
0526: if (ch != '$' && ch != '#' && ch != '\\') {
0527: ttext.write(ch);
0528: }
0529: }
0530: lastCh = ch;
0531: }
0532: if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
0533: ttext.write(lastCh);
0534: }
0535: if (ttext.size() > 0) {
0536: new Node.TemplateText(ttext.toString(), startMark,
0537: current);
0538: }
0539: }
0540: startMark = new Mark(ctxt, path, locator.getLineNumber(),
0541: locator.getColumnNumber());
0542:
0543: charBuffer = null;
0544: }
0545:
0546: /*
0547: * Receives notification of the end of an element.
0548: */
0549: public void endElement(String uri, String localName, String qName)
0550: throws SAXException {
0551:
0552: processChars();
0553:
0554: if (directivesOnly
0555: && !(JSP_URI.equals(uri) && localName
0556: .startsWith(DIRECTIVE_ACTION))) {
0557: return;
0558: }
0559:
0560: if (current instanceof Node.NamedAttribute) {
0561: boolean isTrim = ((Node.NamedAttribute) current).isTrim();
0562: Node.Nodes subElems = ((Node.NamedAttribute) current)
0563: .getBody();
0564: for (int i = 0; subElems != null && i < subElems.size(); i++) {
0565: Node subElem = subElems.getNode(i);
0566: if (!(subElem instanceof Node.TemplateText)) {
0567: continue;
0568: }
0569: // Ignore any whitespace (including spaces, carriage returns,
0570: // line feeds, and tabs, that appear at the beginning and at
0571: // the end of the body of the <jsp:attribute> action, if the
0572: // action's 'trim' attribute is set to TRUE (default).
0573: // In addition, any textual nodes in the <jsp:attribute> that
0574: // have only white space are dropped from the document, with
0575: // the exception of leading and trailing white-space-only
0576: // textual nodes in a <jsp:attribute> whose 'trim' attribute
0577: // is set to FALSE, which must be kept verbatim.
0578: if (i == 0) {
0579: if (isTrim) {
0580: ((Node.TemplateText) subElem).ltrim();
0581: }
0582: } else if (i == subElems.size() - 1) {
0583: if (isTrim) {
0584: ((Node.TemplateText) subElem).rtrim();
0585: }
0586: } else {
0587: if (((Node.TemplateText) subElem).isAllSpace()) {
0588: subElems.remove(subElem);
0589: }
0590: }
0591: }
0592: } else if (current instanceof Node.ScriptingElement) {
0593: checkScriptingBody((Node.ScriptingElement) current);
0594: }
0595:
0596: if (isTagDependent(current)) {
0597: tagDependentNesting--;
0598: }
0599:
0600: if (scriptlessBodyNode != null
0601: && current.equals(scriptlessBodyNode)) {
0602: scriptlessBodyNode = null;
0603: }
0604:
0605: if (current.getParent() != null) {
0606: current = current.getParent();
0607: }
0608: }
0609:
0610: /*
0611: * Receives the document locator.
0612: *
0613: * @param locator the document locator
0614: */
0615: public void setDocumentLocator(Locator locator) {
0616: this .locator = locator;
0617: }
0618:
0619: /*
0620: * See org.xml.sax.ext.LexicalHandler.
0621: */
0622: public void comment(char[] buf, int offset, int len)
0623: throws SAXException {
0624:
0625: processChars(); // Flush char buffer and remove white spaces
0626:
0627: // ignore comments in the DTD
0628: if (!inDTD) {
0629: startMark = new Mark(ctxt, path, locator.getLineNumber(),
0630: locator.getColumnNumber());
0631: new Node.Comment(new String(buf, offset, len), startMark,
0632: current);
0633: }
0634: }
0635:
0636: /*
0637: * See org.xml.sax.ext.LexicalHandler.
0638: */
0639: public void startCDATA() throws SAXException {
0640:
0641: processChars(); // Flush char buffer and remove white spaces
0642: startMark = new Mark(ctxt, path, locator.getLineNumber(),
0643: locator.getColumnNumber());
0644: }
0645:
0646: /*
0647: * See org.xml.sax.ext.LexicalHandler.
0648: */
0649: public void endCDATA() throws SAXException {
0650: processChars(); // Flush char buffer and remove white spaces
0651: }
0652:
0653: /*
0654: * See org.xml.sax.ext.LexicalHandler.
0655: */
0656: public void startEntity(String name) throws SAXException {
0657: // do nothing
0658: }
0659:
0660: /*
0661: * See org.xml.sax.ext.LexicalHandler.
0662: */
0663: public void endEntity(String name) throws SAXException {
0664: // do nothing
0665: }
0666:
0667: /*
0668: * See org.xml.sax.ext.LexicalHandler.
0669: */
0670: public void startDTD(String name, String publicId, String systemId)
0671: throws SAXException {
0672: if (!isValidating) {
0673: fatalError(ENABLE_DTD_VALIDATION_EXCEPTION);
0674: }
0675:
0676: inDTD = true;
0677: }
0678:
0679: /*
0680: * See org.xml.sax.ext.LexicalHandler.
0681: */
0682: public void endDTD() throws SAXException {
0683: inDTD = false;
0684: }
0685:
0686: /*
0687: * Receives notification of a non-recoverable error.
0688: */
0689: public void fatalError(SAXParseException e) throws SAXException {
0690: throw e;
0691: }
0692:
0693: /*
0694: * Receives notification of a recoverable error.
0695: */
0696: public void error(SAXParseException e) throws SAXException {
0697: throw e;
0698: }
0699:
0700: /*
0701: * Receives notification of the start of a Namespace mapping.
0702: */
0703: public void startPrefixMapping(String prefix, String uri)
0704: throws SAXException {
0705: TagLibraryInfo taglibInfo;
0706:
0707: if (directivesOnly && !(JSP_URI.equals(uri))) {
0708: return;
0709: }
0710:
0711: try {
0712: taglibInfo = getTaglibInfo(prefix, uri);
0713: } catch (JasperException je) {
0714: throw new SAXParseException(
0715: Localizer
0716: .getMessage("jsp.error.could.not.add.taglibraries"),
0717: locator, je);
0718: }
0719:
0720: if (taglibInfo != null) {
0721: if (pageInfo.getTaglib(uri) == null) {
0722: pageInfo.addTaglib(uri, taglibInfo);
0723: }
0724: pageInfo.pushPrefixMapping(prefix, uri);
0725: } else {
0726: pageInfo.pushPrefixMapping(prefix, null);
0727: }
0728: }
0729:
0730: /*
0731: * Receives notification of the end of a Namespace mapping.
0732: */
0733: public void endPrefixMapping(String prefix) throws SAXException {
0734:
0735: if (directivesOnly) {
0736: String uri = pageInfo.getURI(prefix);
0737: if (!JSP_URI.equals(uri)) {
0738: return;
0739: }
0740: }
0741:
0742: pageInfo.popPrefixMapping(prefix);
0743: }
0744:
0745: //*********************************************************************
0746: // Private utility methods
0747:
0748: private Node parseStandardAction(String qName, String localName,
0749: Attributes nonTaglibAttrs, Attributes nonTaglibXmlnsAttrs,
0750: Attributes taglibAttrs, Mark start, Node parent)
0751: throws SAXException {
0752:
0753: Node node = null;
0754:
0755: if (localName.equals(ROOT_ACTION)) {
0756: if (!(current instanceof Node.Root)) {
0757: throw new SAXParseException(Localizer
0758: .getMessage("jsp.error.nested_jsproot"),
0759: locator);
0760: }
0761: node = new Node.JspRoot(qName, nonTaglibAttrs,
0762: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0763: if (isTop) {
0764: pageInfo.setHasJspRoot(true);
0765: }
0766: } else if (localName.equals(PAGE_DIRECTIVE_ACTION)) {
0767: if (isTagFile) {
0768: throw new SAXParseException(Localizer.getMessage(
0769: "jsp.error.action.istagfile", localName),
0770: locator);
0771: }
0772: node = new Node.PageDirective(qName, nonTaglibAttrs,
0773: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0774: String imports = nonTaglibAttrs.getValue("import");
0775: // There can only be one 'import' attribute per page directive
0776: if (imports != null) {
0777: ((Node.PageDirective) node).addImport(imports);
0778: }
0779: } else if (localName.equals(INCLUDE_DIRECTIVE_ACTION)) {
0780: node = new Node.IncludeDirective(qName, nonTaglibAttrs,
0781: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0782: processIncludeDirective(nonTaglibAttrs.getValue("file"),
0783: node);
0784: } else if (localName.equals(DECLARATION_ACTION)) {
0785: if (scriptlessBodyNode != null) {
0786: // We're nested inside a node whose body is
0787: // declared to be scriptless
0788: throw new SAXParseException(Localizer.getMessage(
0789: "jsp.error.no.scriptlets", localName), locator);
0790: }
0791: node = new Node.Declaration(qName, nonTaglibXmlnsAttrs,
0792: taglibAttrs, start, current);
0793: } else if (localName.equals(SCRIPTLET_ACTION)) {
0794: if (scriptlessBodyNode != null) {
0795: // We're nested inside a node whose body is
0796: // declared to be scriptless
0797: throw new SAXParseException(Localizer.getMessage(
0798: "jsp.error.no.scriptlets", localName), locator);
0799: }
0800: node = new Node.Scriptlet(qName, nonTaglibXmlnsAttrs,
0801: taglibAttrs, start, current);
0802: } else if (localName.equals(EXPRESSION_ACTION)) {
0803: if (scriptlessBodyNode != null) {
0804: // We're nested inside a node whose body is
0805: // declared to be scriptless
0806: throw new SAXParseException(Localizer.getMessage(
0807: "jsp.error.no.scriptlets", localName), locator);
0808: }
0809: node = new Node.Expression(qName, nonTaglibXmlnsAttrs,
0810: taglibAttrs, start, current);
0811: } else if (localName.equals(USE_BEAN_ACTION)) {
0812: node = new Node.UseBean(qName, nonTaglibAttrs,
0813: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0814: } else if (localName.equals(SET_PROPERTY_ACTION)) {
0815: node = new Node.SetProperty(qName, nonTaglibAttrs,
0816: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0817: } else if (localName.equals(GET_PROPERTY_ACTION)) {
0818: node = new Node.GetProperty(qName, nonTaglibAttrs,
0819: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0820: } else if (localName.equals(INCLUDE_ACTION)) {
0821: node = new Node.IncludeAction(qName, nonTaglibAttrs,
0822: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0823: } else if (localName.equals(FORWARD_ACTION)) {
0824: node = new Node.ForwardAction(qName, nonTaglibAttrs,
0825: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0826: } else if (localName.equals(PARAM_ACTION)) {
0827: node = new Node.ParamAction(qName, nonTaglibAttrs,
0828: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0829: } else if (localName.equals(PARAMS_ACTION)) {
0830: node = new Node.ParamsAction(qName, nonTaglibXmlnsAttrs,
0831: taglibAttrs, start, current);
0832: } else if (localName.equals(PLUGIN_ACTION)) {
0833: node = new Node.PlugIn(qName, nonTaglibAttrs,
0834: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0835: } else if (localName.equals(TEXT_ACTION)) {
0836: node = new Node.JspText(qName, nonTaglibXmlnsAttrs,
0837: taglibAttrs, start, current);
0838: } else if (localName.equals(BODY_ACTION)) {
0839: node = new Node.JspBody(qName, nonTaglibXmlnsAttrs,
0840: taglibAttrs, start, current);
0841: } else if (localName.equals(ATTRIBUTE_ACTION)) {
0842: node = new Node.NamedAttribute(qName, nonTaglibAttrs,
0843: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0844: } else if (localName.equals(OUTPUT_ACTION)) {
0845: node = new Node.JspOutput(qName, nonTaglibAttrs,
0846: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0847: } else if (localName.equals(TAG_DIRECTIVE_ACTION)) {
0848: if (!isTagFile) {
0849: throw new SAXParseException(Localizer.getMessage(
0850: "jsp.error.action.isnottagfile", localName),
0851: locator);
0852: }
0853: node = new Node.TagDirective(qName, nonTaglibAttrs,
0854: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0855: String imports = nonTaglibAttrs.getValue("import");
0856: // There can only be one 'import' attribute per tag directive
0857: if (imports != null) {
0858: ((Node.TagDirective) node).addImport(imports);
0859: }
0860: } else if (localName.equals(ATTRIBUTE_DIRECTIVE_ACTION)) {
0861: if (!isTagFile) {
0862: throw new SAXParseException(Localizer.getMessage(
0863: "jsp.error.action.isnottagfile", localName),
0864: locator);
0865: }
0866: node = new Node.AttributeDirective(qName, nonTaglibAttrs,
0867: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0868: } else if (localName.equals(VARIABLE_DIRECTIVE_ACTION)) {
0869: if (!isTagFile) {
0870: throw new SAXParseException(Localizer.getMessage(
0871: "jsp.error.action.isnottagfile", localName),
0872: locator);
0873: }
0874: node = new Node.VariableDirective(qName, nonTaglibAttrs,
0875: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0876: } else if (localName.equals(INVOKE_ACTION)) {
0877: if (!isTagFile) {
0878: throw new SAXParseException(Localizer.getMessage(
0879: "jsp.error.action.isnottagfile", localName),
0880: locator);
0881: }
0882: node = new Node.InvokeAction(qName, nonTaglibAttrs,
0883: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0884: } else if (localName.equals(DOBODY_ACTION)) {
0885: if (!isTagFile) {
0886: throw new SAXParseException(Localizer.getMessage(
0887: "jsp.error.action.isnottagfile", localName),
0888: locator);
0889: }
0890: node = new Node.DoBodyAction(qName, nonTaglibAttrs,
0891: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0892: } else if (localName.equals(ELEMENT_ACTION)) {
0893: node = new Node.JspElement(qName, nonTaglibAttrs,
0894: nonTaglibXmlnsAttrs, taglibAttrs, start, current);
0895: } else if (localName.equals(FALLBACK_ACTION)) {
0896: node = new Node.FallBackAction(qName, nonTaglibXmlnsAttrs,
0897: taglibAttrs, start, current);
0898: } else {
0899: throw new SAXParseException(Localizer.getMessage(
0900: "jsp.error.xml.badStandardAction", localName),
0901: locator);
0902: }
0903:
0904: return node;
0905: }
0906:
0907: /*
0908: * Checks if the XML element with the given tag name is a custom action,
0909: * and returns the corresponding Node object.
0910: */
0911: private Node parseCustomAction(String qName, String localName,
0912: String uri, Attributes nonTaglibAttrs,
0913: Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
0914: Mark start, Node parent) throws SAXException {
0915:
0916: // Check if this is a user-defined (custom) tag
0917: TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
0918: if (tagLibInfo == null) {
0919: return null;
0920: }
0921:
0922: TagInfo tagInfo = tagLibInfo.getTag(localName);
0923: TagFileInfo tagFileInfo = tagLibInfo.getTagFile(localName);
0924: if (tagInfo == null && tagFileInfo == null) {
0925: throw new SAXException(Localizer.getMessage(
0926: "jsp.error.xml.bad_tag", localName, uri));
0927: }
0928: Class tagHandlerClass = null;
0929: if (tagInfo != null) {
0930: String handlerClassName = tagInfo.getTagClassName();
0931: try {
0932: tagHandlerClass = ctxt.getClassLoader().loadClass(
0933: handlerClassName);
0934: } catch (Exception e) {
0935: throw new SAXException(Localizer.getMessage(
0936: "jsp.error.loadclass.taghandler",
0937: handlerClassName, qName), e);
0938: }
0939: }
0940:
0941: String prefix = "";
0942: int colon = qName.indexOf(':');
0943: if (colon != -1) {
0944: prefix = qName.substring(0, colon);
0945: }
0946:
0947: Node.CustomTag ret = null;
0948: if (tagInfo != null) {
0949: ret = new Node.CustomTag(qName, prefix, localName, uri,
0950: nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
0951: start, parent, tagInfo, tagHandlerClass);
0952: } else {
0953: ret = new Node.CustomTag(qName, prefix, localName, uri,
0954: nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
0955: start, parent, tagFileInfo);
0956: }
0957:
0958: return ret;
0959: }
0960:
0961: /*
0962: * Creates the tag library associated with the given uri namespace, and
0963: * returns it.
0964: *
0965: * @param prefix The prefix of the xmlns attribute
0966: * @param uri The uri namespace (value of the xmlns attribute)
0967: *
0968: * @return The tag library associated with the given uri namespace
0969: */
0970: private TagLibraryInfo getTaglibInfo(String prefix, String uri)
0971: throws JasperException {
0972:
0973: TagLibraryInfo result = null;
0974:
0975: if (uri.startsWith(URN_JSPTAGDIR)) {
0976: // uri (of the form "urn:jsptagdir:path") references tag file dir
0977: String tagdir = uri.substring(URN_JSPTAGDIR.length());
0978: result = new ImplicitTagLibraryInfo(ctxt, parserController,
0979: pageInfo, prefix, tagdir, err);
0980: } else {
0981: // uri references TLD file
0982: boolean isPlainUri = false;
0983: if (uri.startsWith(URN_JSPTLD)) {
0984: // uri is of the form "urn:jsptld:path"
0985: uri = uri.substring(URN_JSPTLD.length());
0986: } else {
0987: isPlainUri = true;
0988: }
0989:
0990: String[] location = ctxt.getTldLocation(uri);
0991: if (location != null || !isPlainUri) {
0992: if (ctxt.getOptions().isCaching()) {
0993: result = (TagLibraryInfoImpl) ctxt.getOptions()
0994: .getCache().get(uri);
0995: }
0996: if (result == null) {
0997: /*
0998: * If the uri value is a plain uri, a translation error must
0999: * not be generated if the uri is not found in the taglib map.
1000: * Instead, any actions in the namespace defined by the uri
1001: * value must be treated as uninterpreted.
1002: */
1003: result = new TagLibraryInfoImpl(ctxt,
1004: parserController, pageInfo, prefix, uri,
1005: location, err);
1006: if (ctxt.getOptions().isCaching()) {
1007: ctxt.getOptions().getCache().put(uri, result);
1008: }
1009: }
1010: }
1011: }
1012:
1013: return result;
1014: }
1015:
1016: /*
1017: * Ensures that the given body only contains nodes that are instances of
1018: * TemplateText.
1019: *
1020: * This check is performed only for the body of a scripting (that is:
1021: * declaration, scriptlet, or expression) element, after the end tag of a
1022: * scripting element has been reached.
1023: */
1024: private void checkScriptingBody(Node.ScriptingElement scriptingElem)
1025: throws SAXException {
1026: Node.Nodes body = scriptingElem.getBody();
1027: if (body != null) {
1028: int size = body.size();
1029: for (int i = 0; i < size; i++) {
1030: Node n = body.getNode(i);
1031: if (!(n instanceof Node.TemplateText)) {
1032: String elemType = SCRIPTLET_ACTION;
1033: if (scriptingElem instanceof Node.Declaration)
1034: elemType = DECLARATION_ACTION;
1035: if (scriptingElem instanceof Node.Expression)
1036: elemType = EXPRESSION_ACTION;
1037: String msg = Localizer
1038: .getMessage(
1039: "jsp.error.parse.xml.scripting.invalid.body",
1040: elemType);
1041: throw new SAXException(msg);
1042: }
1043: }
1044: }
1045: }
1046:
1047: /*
1048: * Parses the given file included via an include directive.
1049: *
1050: * @param fname The path to the included resource, as specified by the
1051: * 'file' attribute of the include directive
1052: * @param parent The Node representing the include directive
1053: */
1054: private void processIncludeDirective(String fname, Node parent)
1055: throws SAXException {
1056:
1057: if (fname == null) {
1058: return;
1059: }
1060:
1061: try {
1062: parserController.parse(fname, parent, null);
1063: } catch (FileNotFoundException fnfe) {
1064: throw new SAXParseException(Localizer.getMessage(
1065: "jsp.error.file.not.found", fname), locator, fnfe);
1066: } catch (Exception e) {
1067: throw new SAXException(e);
1068: }
1069: }
1070:
1071: /*
1072: * Checks an element's given URI, qname, and attributes to see if any
1073: * of them hijack the 'jsp' prefix, that is, bind it to a namespace other
1074: * than http://java.sun.com/JSP/Page.
1075: *
1076: * @param uri The element's URI
1077: * @param qName The element's qname
1078: * @param attrs The element's attributes
1079: */
1080: private void checkPrefixes(String uri, String qName,
1081: Attributes attrs) {
1082:
1083: checkPrefix(uri, qName);
1084:
1085: int len = attrs.getLength();
1086: for (int i = 0; i < len; i++) {
1087: checkPrefix(attrs.getURI(i), attrs.getQName(i));
1088: }
1089: }
1090:
1091: /*
1092: * Checks the given URI and qname to see if they hijack the 'jsp' prefix,
1093: * which would be the case if qName contained the 'jsp' prefix and
1094: * uri was different from http://java.sun.com/JSP/Page.
1095: *
1096: * @param uri The URI to check
1097: * @param qName The qname to check
1098: */
1099: private void checkPrefix(String uri, String qName) {
1100:
1101: int index = qName.indexOf(':');
1102: if (index != -1) {
1103: String prefix = qName.substring(0, index);
1104: pageInfo.addPrefix(prefix);
1105: if ("jsp".equals(prefix) && !JSP_URI.equals(uri)) {
1106: pageInfo.setIsJspPrefixHijacked(true);
1107: }
1108: }
1109: }
1110:
1111: /*
1112: * Gets SAXParser.
1113: *
1114: * @param validating Indicates whether the requested SAXParser should
1115: * be validating
1116: * @param jspDocParser The JSP document parser
1117: *
1118: * @return The SAXParser
1119: */
1120: private static SAXParser getSAXParser(boolean validating,
1121: JspDocumentParser jspDocParser) throws Exception {
1122:
1123: SAXParserFactory factory = SAXParserFactory.newInstance();
1124: factory.setNamespaceAware(true);
1125:
1126: // Preserve xmlns attributes
1127: factory.setFeature(
1128: "http://xml.org/sax/features/namespace-prefixes", true);
1129: factory.setValidating(validating);
1130: //factory.setFeature(
1131: // "http://xml.org/sax/features/validation",
1132: // validating);
1133:
1134: // Configure the parser
1135: SAXParser saxParser = factory.newSAXParser();
1136: XMLReader xmlReader = saxParser.getXMLReader();
1137: xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, jspDocParser);
1138: xmlReader.setErrorHandler(jspDocParser);
1139:
1140: return saxParser;
1141: }
1142:
1143: /*
1144: * Exception indicating that a DOCTYPE declaration is present, but
1145: * validation is turned off.
1146: */
1147: private static class EnableDTDValidationException extends
1148: SAXParseException {
1149:
1150: EnableDTDValidationException(String message, Locator loc) {
1151: super (message, loc);
1152: }
1153: }
1154:
1155: private static String getBodyType(Node.CustomTag custom) {
1156:
1157: if (custom.getTagInfo() != null) {
1158: return custom.getTagInfo().getBodyContent();
1159: }
1160:
1161: return custom.getTagFileInfo().getTagInfo().getBodyContent();
1162: }
1163:
1164: private boolean isTagDependent(Node n) {
1165:
1166: if (n instanceof Node.CustomTag) {
1167: String bodyType = getBodyType((Node.CustomTag) n);
1168: return TagInfo.BODY_CONTENT_TAG_DEPENDENT
1169: .equalsIgnoreCase(bodyType);
1170: }
1171: return false;
1172: }
1173: }
|