001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1999 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution, if
020: * any, must include the following acknowlegement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowlegement may appear in the software itself,
024: * if and wherever such third-party acknowlegements normally appear.
025: *
026: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache"
032: * nor may "Apache" appear in their names without prior written
033: * permission of the Apache Group.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: *
054: */
055: package com.rimfaxe.webserver.compiler.jsp;
056:
057: import java.io.*;
058: import java.util.Hashtable;
059: import javax.servlet.jsp.tagext.*;
060: import javax.xml.parsers.SAXParserFactory;
061: import javax.xml.parsers.ParserConfigurationException;
062: import javax.xml.parsers.SAXParser;
063: import org.xml.sax.*;
064: import org.xml.sax.ext.LexicalHandler;
065: import org.xml.sax.helpers.DefaultHandler;
066: import org.xml.sax.helpers.AttributesImpl;
067:
068: import com.rimfaxe.webserver.servletapi.jsp.JSPclass;
069:
070: /**
071: * Class implementing a parser for a JSP document, that is, a JSP page in XML
072: * syntax.
073: *
074: * @author Jan Luehe
075: * @author Lars Andersen
076: */
077:
078: public class JspDocumentParser extends DefaultHandler implements
079: LexicalHandler, TagConstants {
080:
081: private static final String XMLNS = "xmlns:";
082: private static final String XMLNS_JSP = "xmlns:jsp";
083: private static final String JSP_VERSION = "version";
084: private static final String URN_JSPTLD = "urn:jsptld:";
085: private static final String LEXICAL_HANDLER_PROPERTY = "http://xml.org/sax/properties/lexical-handler";
086:
087: private ParserController parserController;
088:
089: // XML document source
090: private InputSource inputSource;
091:
092: // XXX
093: private String path;
094:
095: // Node representing the XML element currently being parsed
096: private Node current;
097:
098: // Document locator
099: private Locator locator;
100:
101: // XXX
102: private Hashtable taglibs;
103:
104: // Flag indicating whether we are inside DTD declarations
105: private boolean inDTD;
106:
107: //private ErrorDispatcher err;
108:
109: /*
110: * Constructor
111: */
112: public JspDocumentParser(ParserController pc, String path,
113: InputStreamReader reader) {
114: this .parserController = pc;
115: this .taglibs = pc.getCompiler().getPageInfo().getTagLibraries();
116: //this.taglibs = pc.getWebContext().getTagLibraries();
117: this .path = path;
118: this .inputSource = new InputSource(reader);
119: }
120:
121: /*
122: * Parses a JSP document by responding to SAX events.
123: *
124: * @throws JasperException XXX
125: */
126: public static Node.Nodes parse(ParserController pc, String path,
127: InputStreamReader reader, Node parent)
128: throws JasperException {
129: //System.out.println("JspDocumentParser parse");
130: JspDocumentParser handler = new JspDocumentParser(pc, path,
131: reader);
132: handler.current = parent;
133: Node.Nodes pageNodes = null;
134:
135: try {
136: SAXParser saxParser = new com.rimfaxe.xml.xmlreader.SAXParserImpl(
137: false);
138: XMLReader xmlReader = saxParser.getXMLReader();
139: xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, handler);
140: xmlReader.setErrorHandler(handler);
141:
142: // Parse the input
143: saxParser.parse(handler.inputSource, handler);
144:
145: if (parent == null) {
146: // Add the jsp:root element to the parse result
147: pageNodes = new Node.Nodes(
148: (Node.JspRoot) handler.current);
149: } else {
150: pageNodes = parent.getBody();
151: }
152: } catch (IOException ioe) {
153: pc.getCompiler().getJspClass().setError(JSPclass.JSP_ERROR);
154: pc.getCompiler().getJspClass().writeStacktrace(ioe);
155: //handler.err.jspError("jsp.error.data.file.read", path, ioe);
156: } catch (Exception e) {
157: pc.getCompiler().getJspClass().setError(JSPclass.JSP_ERROR);
158: pc.getCompiler().getJspClass().writeStacktrace(e);
159: //handler.err.jspError(e);
160: }
161:
162: return pageNodes;
163: }
164:
165: /*
166: * Receives notification of the start of an element.
167: */
168: public void startElement(String uri, String localName,
169: String qName, Attributes attrs) throws SAXException {
170:
171: Mark start = new Mark(path, locator.getLineNumber(), locator
172: .getColumnNumber());
173: Attributes attrsCopy = new AttributesImpl(attrs);
174:
175: Node node = null;
176: if (qName.equals(JSP_ROOT_TAG)) {
177: node = new Node.JspRoot(attrsCopy, start, current);
178: try {
179: addCustomTagLibraries(attrsCopy);
180: } catch (JasperException je) {
181: throw new SAXException(je);
182: }
183: } else if (qName.equals(JSP_PAGE_DIRECTIVE_TAG)) {
184: node = new Node.PageDirective(attrsCopy, start, current);
185: String imports = attrs.getValue("import");
186: // There can only be one 'import' attribute per page directive
187: if (imports != null) {
188: ((Node.PageDirective) node).addImport(imports);
189: }
190: } else if (qName.equals(JSP_INCLUDE_DIRECTIVE_TAG)) {
191: node = new Node.IncludeDirective(attrsCopy, start, current);
192: String file = attrsCopy.getValue("file");
193: try {
194: parserController.parse(file, node);
195: } catch (FileNotFoundException fnfe) {
196: //throw new SAXParseException("jsp.error.file.not.found");
197: } catch (Exception e) {
198: throw new SAXException(e);
199: }
200: } else if (qName.equals(JSP_DECLARATION_TAG)) {
201: node = new Node.Declaration(start, current);
202: } else if (qName.equals(JSP_SCRIPTLET_TAG)) {
203: node = new Node.Scriptlet(start, current);
204: } else if (qName.equals(JSP_EXPRESSION_TAG)) {
205: node = new Node.Expression(start, current);
206: } else if (qName.equals(JSP_USE_BEAN_TAG)) {
207: node = new Node.UseBean(attrsCopy, start, current);
208: } else if (qName.equals(JSP_SET_PROPERTY_TAG)) {
209: node = new Node.SetProperty(attrsCopy, start, current);
210: } else if (qName.equals(JSP_GET_PROPERTY_TAG)) {
211: node = new Node.GetProperty(attrsCopy, start, current);
212: } else if (qName.equals(JSP_INCLUDE_TAG)) {
213: node = new Node.IncludeAction(attrsCopy, start, current);
214: } else if (qName.equals(JSP_FORWARD_TAG)) {
215: node = new Node.ForwardAction(attrsCopy, start, current);
216: } else if (qName.equals(JSP_PARAM_TAG)) {
217: node = new Node.ParamAction(attrsCopy, start, current);
218: } else if (qName.equals(JSP_PARAMS_TAG)) {
219: node = new Node.ParamsAction(start, current);
220: } else if (qName.equals(JSP_PLUGIN_TAG)) {
221: node = new Node.PlugIn(attrsCopy, start, current);
222: } else if (qName.equals(JSP_TEXT_TAG)) {
223: node = new Node.JspText(start, current);
224: } else {
225: node = getCustomTag(qName, attrsCopy, start, current);
226: if (node == null) {
227: node = new Node.UninterpretedTag(attrsCopy, start,
228: qName, current);
229: }
230: }
231:
232: current = node;
233: }
234:
235: /*
236: * Receives notification of character data inside an element.
237: *
238: * @param buf The characters
239: * @param offset The start position in the character array
240: * @param len The number of characters to use from the character array
241: *
242: * @throws SAXException
243: */
244: public void characters(char[] buf, int offset, int len)
245: throws SAXException {
246: /*
247: * All textual nodes that have only white space are to be dropped from
248: * the document, except for nodes in a jsp:text element, which are
249: * kept verbatim (JSP 5.2.1).
250: */
251: boolean isAllSpace = true;
252: if (!(current instanceof Node.JspText)) {
253: for (int i = offset; i < offset + len; i++) {
254: if (!Character.isSpace(buf[i])) {
255: isAllSpace = false;
256: break;
257: }
258: }
259: }
260: if ((current instanceof Node.JspText) || !isAllSpace) {
261: Mark start = new Mark(path, locator.getLineNumber(),
262: locator.getColumnNumber());
263: char[] bufCopy = new char[len];
264: System.arraycopy(buf, offset, bufCopy, 0, len);
265: new Node.TemplateText(bufCopy, start, current);
266: }
267: }
268:
269: /*
270: * Receives notification of the end of an element.
271: */
272: public void endElement(String uri, String localName, String qName)
273: throws SAXException {
274: if (current instanceof Node.ScriptingElement) {
275: checkScriptingBody(current.getBody());
276: }
277: if (current.getParent() != null) {
278: current = current.getParent();
279: }
280: }
281:
282: /*
283: * Receives the document locator.
284: *
285: * @param locator the document locator
286: */
287: public void setDocumentLocator(Locator locator) {
288: this .locator = locator;
289: }
290:
291: /*
292: * See org.xml.sax.ext.LexicalHandler.
293: */
294: public void comment(char[] buf, int offset, int len)
295: throws SAXException {
296: // ignore comments in the DTD
297: if (!inDTD) {
298: Mark start = new Mark(path, locator.getLineNumber(),
299: locator.getColumnNumber());
300: char[] bufCopy = new char[len];
301: System.arraycopy(buf, offset, bufCopy, 0, len);
302: new Node.Comment(bufCopy, start, current);
303: }
304: }
305:
306: /*
307: * See org.xml.sax.ext.LexicalHandler.
308: */
309: public void startCDATA() throws SAXException {
310: // do nothing
311: }
312:
313: /*
314: * See org.xml.sax.ext.LexicalHandler.
315: */
316: public void endCDATA() throws SAXException {
317: // do nothing
318: }
319:
320: /*
321: * See org.xml.sax.ext.LexicalHandler.
322: */
323: public void startEntity(String name) throws SAXException {
324: // do nothing
325: }
326:
327: /*
328: * See org.xml.sax.ext.LexicalHandler.
329: */
330: public void endEntity(String name) throws SAXException {
331: // do nothing
332: }
333:
334: /*
335: * See org.xml.sax.ext.LexicalHandler.
336: */
337: public void startDTD(String name, String publicId, String systemId)
338: throws SAXException {
339: inDTD = true;
340: }
341:
342: /*
343: * See org.xml.sax.ext.LexicalHandler.
344: */
345: public void endDTD() throws SAXException {
346: inDTD = false;
347: }
348:
349: /*
350: * Receives notification of a non-recoverable error.
351: */
352: public void fatalError(SAXParseException e) throws SAXException {
353: throw e;
354: }
355:
356: /*
357: * Receives notification of a recoverable error.
358: */
359: public void error(SAXParseException e) throws SAXException {
360: throw e;
361: }
362:
363: //*********************************************************************
364: // Private utility methods
365:
366: /*
367: * Checks if the XML element with the given tag name is a custom action,
368: * and returns the corresponding Node object.
369: */
370: private Node getCustomTag(String qName, Attributes attrs,
371: Mark start, Node parent) throws SAXException {
372: int colon = qName.indexOf(':');
373: if (colon == -1) {
374: return null;
375: }
376:
377: String prefix = qName.substring(0, colon);
378: String shortName = qName.substring(colon + 1);
379: if (shortName.length() == 0) {
380: return null;
381: }
382:
383: // Check if this is a user-defined (custom) tag
384: TagLibraryInfo tagLibInfo = (TagLibraryInfo) taglibs
385: .get(prefix);
386: if (tagLibInfo == null) {
387: return null;
388: }
389: TagInfo tagInfo = tagLibInfo.getTag(shortName);
390: if (tagInfo == null) {
391: System.out.println("### jsp.error.bad_tag " + shortName
392: + " " + prefix);
393: //throw new SAXException(err.getString("jsp.error.bad_tag",
394: // shortName, prefix));
395: }
396:
397: return new Node.CustomTag(attrs, start, qName, prefix,
398: shortName, tagInfo, parent);
399: }
400:
401: /*
402: * Parses the xmlns:prefix attributes from the jsp:root element and adds
403: * the corresponding TagLibraryInfo objects to the set of custom tag
404: * libraries.
405: */
406: private void addCustomTagLibraries(Attributes attrs)
407: throws JasperException {
408: int len = attrs.getLength();
409: for (int i = 0; i < len; i++) {
410: String qName = attrs.getQName(i);
411: if (!qName.startsWith(XMLNS_JSP)
412: && !qName.startsWith(JSP_VERSION)) {
413:
414: // get the prefix
415: String prefix = null;
416: try {
417: prefix = qName.substring(XMLNS.length());
418: } catch (StringIndexOutOfBoundsException e) {
419: continue;
420: }
421:
422: // get the uri
423: String uri = attrs.getValue(i);
424: if (uri.startsWith(URN_JSPTLD)) {
425: // uri value is of the form "urn:jsptld:path"
426: uri = uri.substring(URN_JSPTLD.length());
427: }
428:
429: TagLibraryInfo tl = parserController.getCompiler()
430: .getWebContext().getTagLibrary(uri, prefix);
431:
432: //System.out.println("### Add "+prefix+" to taglibs");
433: taglibs.put(prefix, tl);
434: }
435: }
436: }
437:
438: /*
439: * Ensures that the given body only contains nodes that are instances of
440: * TemplateText.
441: *
442: * This check is performed only for the body of a scripting (that is, a
443: * declaration, scriptlet, or expression) element, after the end tag of a
444: * scripting element has been reached.
445: */
446: private void checkScriptingBody(Node.Nodes body)
447: throws SAXException {
448: if (body != null) {
449: int size = body.size();
450: for (int i = 0; i < size; i++) {
451: Node n = body.getNode(i);
452: if (!(n instanceof Node.TemplateText)) {
453: //String msg = err.getString(
454: // "jsp.error.parse.xml.scripting.invalid.body");
455: throw new SAXException(
456: "jsp.error.parse.xml.scripting.invalid.body");
457: }
458: }
459: }
460: }
461: }
|