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