0001: /*
0002: * Copyright 2002,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.commons.jelly.parser;
0017:
0018: import java.io.File;
0019: import java.io.InputStream;
0020: import java.io.IOException;
0021: import java.io.Reader;
0022: import java.net.URL;
0023: import java.util.ArrayList;
0024: import java.util.EmptyStackException;
0025: import java.util.HashMap;
0026: import java.util.Iterator;
0027: import java.util.Map;
0028: import java.util.Properties;
0029:
0030: import javax.xml.parsers.SAXParser;
0031: import javax.xml.parsers.SAXParserFactory;
0032:
0033: import org.apache.commons.collections.ArrayStack;
0034:
0035: import org.apache.commons.jelly.JellyContext;
0036: import org.apache.commons.jelly.JellyException;
0037: import org.apache.commons.jelly.Script;
0038: import org.apache.commons.jelly.Tag;
0039: import org.apache.commons.jelly.TagLibrary;
0040: import org.apache.commons.jelly.impl.CompositeTextScriptBlock;
0041: import org.apache.commons.jelly.impl.ExpressionScript;
0042: import org.apache.commons.jelly.impl.StaticTag;
0043: import org.apache.commons.jelly.impl.ScriptBlock;
0044: import org.apache.commons.jelly.impl.StaticTagScript;
0045: import org.apache.commons.jelly.impl.TagFactory;
0046: import org.apache.commons.jelly.impl.TagScript;
0047: import org.apache.commons.jelly.impl.TextScript;
0048: import org.apache.commons.jelly.util.ClassLoaderUtils;
0049: import org.apache.commons.jelly.expression.CompositeExpression;
0050: import org.apache.commons.jelly.expression.ConstantExpression;
0051: import org.apache.commons.jelly.expression.Expression;
0052: import org.apache.commons.jelly.expression.ExpressionFactory;
0053: import org.apache.commons.jelly.expression.jexl.JexlExpressionFactory;
0054:
0055: import org.apache.commons.logging.Log;
0056: import org.apache.commons.logging.LogFactory;
0057:
0058: import org.xml.sax.Attributes;
0059: import org.xml.sax.ErrorHandler;
0060: import org.xml.sax.helpers.DefaultHandler;
0061: import org.xml.sax.InputSource;
0062: import org.xml.sax.Locator;
0063: import org.xml.sax.SAXException;
0064: import org.xml.sax.SAXParseException;
0065: import org.xml.sax.XMLReader;
0066: import org.xml.sax.helpers.AttributesImpl;
0067:
0068: /** <p><code>XMLParser</code> parses the XML Jelly format.
0069: * The SAXParser and XMLReader portions of this code come from Digester.</p>
0070: *
0071: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
0072: * @version $Revision: 155420 $
0073: */
0074: public class XMLParser extends DefaultHandler {
0075:
0076: /**
0077: * Share the Jelly properties across parsers
0078: */
0079: private static Properties jellyProperties;
0080:
0081: /** JellyContext which is used to locate tag libraries*/
0082: private JellyContext context = new JellyContext();
0083:
0084: /** the expression factory used to evaluate tag attributes */
0085: private ExpressionFactory expressionFactory;
0086:
0087: /** The current script block */
0088: private ScriptBlock script;
0089:
0090: /** The current, parent tagScript */
0091: private TagScript tagScript;
0092:
0093: /** The stack of body scripts. */
0094: private ArrayStack scriptStack = new ArrayStack();
0095:
0096: /** The stack of tagScripts - use ArrayList as it allows null. */
0097: private ArrayList tagScriptStack = new ArrayList();
0098:
0099: /** The current text buffer where non-custom tags get written */
0100: private StringBuffer textBuffer;
0101:
0102: /**
0103: * The class loader to use for instantiating application objects.
0104: * If not specified, the context class loader, or the class loader
0105: * used to load XMLParser itself, is used, based on the value of the
0106: * <code>useContextClassLoader</code> variable.
0107: */
0108: protected ClassLoader classLoader = null;
0109:
0110: /**
0111: * Do we want to use the Context ClassLoader when loading classes
0112: * for instantiating new objects? Default is <code>false</code>.
0113: */
0114: protected boolean useContextClassLoader = false;
0115:
0116: /**
0117: * The application-supplied error handler that is notified when parsing
0118: * warnings, errors, or fatal errors occur.
0119: */
0120: protected ErrorHandler errorHandler = null;
0121:
0122: /**
0123: * The SAXParserFactory that is created the first time we need it.
0124: */
0125: protected static SAXParserFactory factory = null;
0126:
0127: /**
0128: * The SAXParser we will use to parse the input stream.
0129: */
0130: protected SAXParser parser = null;
0131:
0132: /**
0133: * The XMLReader used to parse digester rules.
0134: */
0135: protected XMLReader reader = null;
0136:
0137: /**
0138: * The Locator associated with our parser.
0139: */
0140: protected Locator locator = null;
0141:
0142: /**
0143: * Registered namespaces we are currently processing. The key is the
0144: * namespace prefix that was declared in the document. The value is an
0145: * ArrayStack of the namespace URIs this prefix has been mapped to --
0146: * the top Stack element is the most current one. (This architecture
0147: * is required because documents can declare nested uses of the same
0148: * prefix for different Namespace URIs).
0149: */
0150: protected Map namespaces = new HashMap();
0151:
0152: /** The Map of the namespace prefix -> URIs defined for the current element */
0153: private Map elementNamespaces;
0154:
0155: /**
0156: * The name of the file being parsed that is passed to the TagScript objects
0157: * for error reporting
0158: */
0159: private String fileName;
0160:
0161: /**
0162: * Do we want to use a validating parser?
0163: */
0164: protected boolean validating = false;
0165:
0166: /** Flag to indicate if this object has been configured */
0167: private boolean configured;
0168:
0169: /**
0170: * when not null, set the default namespace for
0171: * unprefixed elements via the DefaultNamespaceFilter
0172: * class
0173: */
0174: private String defaultNamespaceURI = null;
0175:
0176: /**
0177: * The Log to which logging calls will be made.
0178: */
0179: private Log log = LogFactory.getLog(XMLParser.class);
0180:
0181: /**
0182: * Construct a new XMLParser with default properties.
0183: */
0184: public XMLParser() {
0185: }
0186:
0187: /**
0188: * Construct a new XMLParser, allowing a SAXParser to be passed in. This
0189: * allows XMLParser to be used in environments which are unfriendly to
0190: * JAXP1.1 (such as WebLogic 6.0). Thanks for the request to change go to
0191: * James House (james@interobjective.com). This may help in places where
0192: * you are able to load JAXP 1.1 classes yourself.
0193: */
0194: public XMLParser(SAXParser parser) {
0195: this .parser = parser;
0196: }
0197:
0198: /**
0199: * Construct a new XMLParser, allowing an XMLReader to be passed in. This
0200: * allows XMLParser to be used in environments which are unfriendly to
0201: * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you
0202: * have to configure namespace and validation support yourself, as these
0203: * properties only affect the SAXParser and emtpy constructor.
0204: */
0205: public XMLParser(XMLReader reader) {
0206: this .reader = reader;
0207: }
0208:
0209: /**
0210: * Parse the content of the specified file using this XMLParser. Returns
0211: * the root element from the object stack (if any).
0212: *
0213: * @param file File containing the XML data to be parsed
0214: *
0215: * @exception IOException if an input/output error occurs
0216: * @exception SAXException if a parsing exception occurs
0217: */
0218: public Script parse(File file) throws IOException, SAXException {
0219: return parse(file.toURL());
0220: }
0221:
0222: /**
0223: * Parse the content of the specified file using this XMLParser. Returns
0224: * the root element from the object stack (if any).
0225: *
0226: * @param url URL containing the XML data to be parsed
0227: *
0228: * @exception IOException if an input/output error occurs
0229: * @exception SAXException if a parsing exception occurs
0230: */
0231: public Script parse(URL url) throws IOException, SAXException {
0232: ensureConfigured();
0233: this .fileName = url.toString();
0234:
0235: InputSource source = new InputSource(url.toString());
0236:
0237: getXMLReader().parse(source);
0238: return script;
0239: }
0240:
0241: /**
0242: * Parse the content of the specified input source using this XMLParser.
0243: * Returns the root element from the object stack (if any).
0244: *
0245: * @param input Input source containing the XML data to be parsed
0246: *
0247: * @exception IOException if an input/output error occurs
0248: * @exception SAXException if a parsing exception occurs
0249: */
0250: public Script parse(InputSource input) throws IOException,
0251: SAXException {
0252: ensureConfigured();
0253: this .fileName = input.getSystemId();
0254: getXMLReader().parse(input);
0255: return script;
0256: }
0257:
0258: /**
0259: * Parse the content of the specified input stream using this XMLParser.
0260: * Returns the root element from the object stack (if any).
0261: * (Note: if reading a File or URL, use one of the URL-based
0262: * parse methods instead. This method will not be able
0263: * to resolve any relative paths inside a DTD.)
0264: *
0265: * @param input Input stream containing the XML data to be parsed
0266: * @return
0267: * @exception IOException
0268: * if an input/output error occurs
0269: * @exception SAXException
0270: * if a parsing exception occurs
0271: */
0272: public Script parse(InputStream input) throws IOException,
0273: SAXException {
0274: ensureConfigured();
0275: this .fileName = getCurrentURI();
0276: getXMLReader().parse(new InputSource(input));
0277: return script;
0278: }
0279:
0280: /**
0281: * Parse the content of the specified reader using this XMLParser.
0282: * Returns the root element from the object stack (if any).
0283: * (Note: if reading a File or URL, use one of the URL-based
0284: * parse methods instead. This method will not be able
0285: * to resolve any relative paths inside a DTD.)
0286: *
0287: * @param reader Reader containing the XML data to be parsed
0288: * @return
0289: * @exception IOException
0290: * if an input/output error occurs
0291: * @exception SAXException
0292: * if a parsing exception occurs
0293: */
0294: public Script parse(Reader reader) throws IOException, SAXException {
0295: ensureConfigured();
0296: this .fileName = getCurrentURI();
0297: getXMLReader().parse(new InputSource(reader));
0298: return script;
0299: }
0300:
0301: /**
0302: * Parse the content of the specified URI using this XMLParser.
0303: * Returns the root element from the object stack (if any).
0304: *
0305: * @param uri URI containing the XML data to be parsed
0306: *
0307: * @exception IOException if an input/output error occurs
0308: * @exception SAXException if a parsing exception occurs
0309: */
0310: public Script parse(String uri) throws IOException, SAXException {
0311: ensureConfigured();
0312: this .fileName = uri;
0313: getXMLReader().parse(uri);
0314: return script;
0315: }
0316:
0317: /**
0318: * Return the currently mapped namespace URI for the specified prefix,
0319: * if any; otherwise return <code>null</code>. These mappings come and
0320: * go dynamically as the document is parsed.
0321: *
0322: * @param prefix Prefix to look up
0323: */
0324: public String findNamespaceURI(String prefix) {
0325: ArrayStack stack = (ArrayStack) namespaces.get(prefix);
0326: if (stack == null) {
0327: return (null);
0328: }
0329: try {
0330: return ((String) stack.peek());
0331: } catch (EmptyStackException e) {
0332: return (null);
0333: }
0334: }
0335:
0336: // Properties
0337: //-------------------------------------------------------------------------
0338: public JellyContext getContext() {
0339: return context;
0340: }
0341:
0342: public void setContext(JellyContext context) {
0343: this .context = context;
0344: }
0345:
0346: /**
0347: * Set the jelly namespace to use for unprefixed elements.
0348: * Will be overridden by an explicit namespace in the
0349: * XML document.
0350: *
0351: * @param namespace jelly namespace to use (e.g. 'jelly:core')
0352: */
0353: public void setDefaultNamespaceURI(String namespace) {
0354: this .defaultNamespaceURI = namespace;
0355: }
0356:
0357: /**
0358: * Return the class loader to be used for instantiating application objects
0359: * when required. This is determined based upon the following rules:
0360: * <ul>
0361: * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
0362: * <li>The thread context class loader, if it exists and the
0363: * <code>useContextClassLoader</code> property is set to true</li>
0364: * <li>The class loader used to load the XMLParser class itself.
0365: * </ul>
0366: */
0367: public ClassLoader getClassLoader() {
0368: return ClassLoaderUtils.getClassLoader(classLoader,
0369: useContextClassLoader, getClass());
0370: }
0371:
0372: /**
0373: * Set the class loader to be used for instantiating application objects
0374: * when required.
0375: *
0376: * @param classLoader The new class loader to use, or <code>null</code>
0377: * to revert to the standard rules
0378: */
0379: public void setClassLoader(ClassLoader classLoader) {
0380: this .classLoader = classLoader;
0381: }
0382:
0383: /**
0384: * Return the boolean as to whether the context classloader should be used.
0385: */
0386: public boolean getUseContextClassLoader() {
0387: return useContextClassLoader;
0388: }
0389:
0390: /**
0391: * Determine whether to use the Context ClassLoader (the one found by
0392: * calling <code>Thread.currentThread().getContextClassLoader()</code>)
0393: * to resolve/load classes. If not
0394: * using Context ClassLoader, then the class-loading defaults to
0395: * using the calling-class' ClassLoader.
0396: *
0397: * @param use determines whether to use JellyContext ClassLoader.
0398: */
0399: public void setUseContextClassLoader(boolean use) {
0400: useContextClassLoader = use;
0401: }
0402:
0403: /**
0404: * Return the error handler for this XMLParser.
0405: */
0406: public ErrorHandler getErrorHandler() {
0407: return (this .errorHandler);
0408: }
0409:
0410: /**
0411: * Set the error handler for this XMLParser.
0412: *
0413: * @param errorHandler The new error handler
0414: */
0415: public void setErrorHandler(ErrorHandler errorHandler) {
0416: this .errorHandler = errorHandler;
0417: }
0418:
0419: /**
0420: * Return the current Logger associated with this instance of the XMLParser
0421: */
0422: public Log getLogger() {
0423: return log;
0424: }
0425:
0426: /**
0427: * Set the current logger for this XMLParser.
0428: */
0429: public void setLogger(Log log) {
0430: this .log = log;
0431: }
0432:
0433: /** @return the expression factory used to evaluate tag attributes */
0434: public ExpressionFactory getExpressionFactory() {
0435: if (expressionFactory == null) {
0436: expressionFactory = createExpressionFactory();
0437: }
0438: return expressionFactory;
0439: }
0440:
0441: /** Sets the expression factory used to evaluate tag attributes */
0442: public void setExpressionFactory(ExpressionFactory expressionFactory) {
0443: this .expressionFactory = expressionFactory;
0444: }
0445:
0446: /**
0447: * Return the SAXParser we will use to parse the input stream. If there
0448: * is a problem creating the parser, return <code>null</code>.
0449: */
0450: public SAXParser getParser() {
0451: // Return the parser we already created (if any)
0452: if (parser != null) {
0453: return (parser);
0454: }
0455: // Create and return a new parser
0456: synchronized (this ) {
0457: try {
0458: if (factory == null) {
0459: factory = SAXParserFactory.newInstance();
0460: }
0461: factory.setNamespaceAware(true);
0462: factory.setValidating(validating);
0463: parser = factory.newSAXParser();
0464: return (parser);
0465: } catch (Exception e) {
0466: log.error("XMLParser.getParser: ", e);
0467: return (null);
0468: }
0469: }
0470: }
0471:
0472: /**
0473: * By setting the reader in the constructor, you can bypass JAXP and
0474: * be able to use digester in Weblogic 6.0.
0475: *
0476: * @deprecated Use getXMLReader() instead, which can throw a
0477: * SAXException if the reader cannot be instantiated
0478: */
0479: public XMLReader getReader() {
0480: try {
0481: return (getXMLReader());
0482: } catch (SAXException e) {
0483: log.error("Cannot get XMLReader", e);
0484: return (null);
0485: }
0486: }
0487:
0488: /**
0489: * Return the XMLReader to be used for parsing the input document.
0490: *
0491: * @exception SAXException if no XMLReader can be instantiated
0492: */
0493: public synchronized XMLReader getXMLReader() throws SAXException {
0494: if (reader == null) {
0495: reader = getParser().getXMLReader();
0496: if (this .defaultNamespaceURI != null) {
0497: reader = new DefaultNamespaceFilter(
0498: this .defaultNamespaceURI, reader);
0499: }
0500: }
0501: //set up the parse
0502: reader.setContentHandler(this );
0503: reader.setDTDHandler(this );
0504: //reader.setEntityResolver(this);
0505: reader.setErrorHandler(this );
0506:
0507: return reader;
0508: }
0509:
0510: /**
0511: * Return the validating parser flag.
0512: */
0513: public boolean getValidating() {
0514: return (this .validating);
0515: }
0516:
0517: /**
0518: * Set the validating parser flag. This must be called before
0519: * <code>parse()</code> is called the first time.
0520: *
0521: * @param validating The new validating parser flag.
0522: */
0523: public void setValidating(boolean validating) {
0524: this .validating = validating;
0525: }
0526:
0527: /**
0528: * Returns the script that has just been created if this class is used
0529: * as a SAX ContentHandler and passed into some XML processor or parser.
0530: *
0531: * @return the ScriptBlock created if SAX events are piped into this class,
0532: * which must include a startDocument() and endDocument()
0533: */
0534: public ScriptBlock getScript() {
0535: return script;
0536: }
0537:
0538: // ContentHandler interface
0539: //-------------------------------------------------------------------------
0540: /**
0541: * Process notification of the beginning of the document being reached.
0542: *
0543: * @exception SAXException if a parsing error is to be reported
0544: */
0545: public void startDocument() throws SAXException {
0546: script = new ScriptBlock();
0547: textBuffer = new StringBuffer();
0548: tagScript = null;
0549: scriptStack.clear();
0550: tagScriptStack.clear();
0551: }
0552:
0553: /**
0554: * Process notification of the end of the document being reached.
0555: *
0556: * @exception SAXException if a parsing error is to be reported
0557: */
0558: public void endDocument() throws SAXException {
0559: textBuffer = null;
0560: }
0561:
0562: /**
0563: * Process notification of the start of an XML element being reached.
0564: *
0565: * @param namespaceURI The Namespace URI, or the empty string if the
0566: * element has no Namespace URI or if Namespace processing is not
0567: * being performed.
0568: * @param localName The local name (without prefix), or the empty
0569: * string if Namespace processing is not being performed.
0570: * @param qName The qualified name (with prefix), or the empty
0571: * string if qualified names are not available.\
0572: * @param list The attributes attached to the element. If there are
0573: * no attributes, it shall be an empty Attributes object.
0574: * @exception SAXException if a parsing error is to be reported
0575: */
0576: public void startElement(String namespaceURI, String localName,
0577: String qName, Attributes list) throws SAXException {
0578:
0579: try {
0580: // add check to ensure namespace URI is "" for no namespace
0581: if (namespaceURI == null) {
0582: namespaceURI = "";
0583: }
0584:
0585: // if this is a tag then create a script to run it
0586: // otherwise pass the text to the current body
0587: TagScript newTagScript = createTag(namespaceURI, localName,
0588: list);
0589: if (newTagScript == null) {
0590: newTagScript = createStaticTag(namespaceURI, localName,
0591: qName, list);
0592: }
0593: tagScript = newTagScript;
0594: tagScriptStack.add(tagScript);
0595: if (tagScript != null) {
0596: // set the line number details
0597: if (locator != null) {
0598: tagScript.setLocator(locator);
0599: }
0600: // sets the file name element names
0601: tagScript.setFileName(fileName);
0602: tagScript.setElementName(qName);
0603: tagScript.setLocalName(localName);
0604:
0605: if (textBuffer.length() > 0) {
0606: addTextScript(textBuffer.toString());
0607: textBuffer.setLength(0);
0608: }
0609: script.addScript(tagScript);
0610: // start a new body
0611: scriptStack.push(script);
0612: script = new ScriptBlock();
0613: tagScript.setTagBody(script);
0614: } else {
0615: // XXXX: might wanna handle empty elements later...
0616: textBuffer.append("<");
0617: textBuffer.append(qName);
0618: int size = list.getLength();
0619: for (int i = 0; i < size; i++) {
0620: textBuffer.append(" ");
0621: textBuffer.append(list.getQName(i));
0622: textBuffer.append("=");
0623: textBuffer.append("\"");
0624: textBuffer.append(list.getValue(i));
0625: textBuffer.append("\"");
0626: }
0627: textBuffer.append(">");
0628: }
0629: } catch (SAXException e) {
0630: throw e;
0631: } catch (Exception e) {
0632: log.error("Caught exception: " + e, e);
0633: throw new SAXException("Runtime Exception: " + e, e);
0634: }
0635: }
0636:
0637: /**
0638: * Process notification of character data received from the body of
0639: * an XML element.
0640: *
0641: * @param buffer The characters from the XML document
0642: * @param start Starting offset into the buffer
0643: * @param length Number of characters from the buffer
0644: *
0645: * @exception SAXException if a parsing error is to be reported
0646: */
0647: public void characters(char buffer[], int start, int length)
0648: throws SAXException {
0649: textBuffer.append(buffer, start, length);
0650: }
0651:
0652: /**
0653: * Process notification of the end of an XML element being reached.
0654: *
0655: * @param namespaceURI The Namespace URI, or the empty string if the
0656: * element has no Namespace URI or if Namespace processing is not
0657: * being performed.
0658: * @param localName The local name (without prefix), or the empty
0659: * string if Namespace processing is not being performed.
0660: * @param qName The qualified XML 1.0 name (with prefix), or the
0661: * empty string if qualified names are not available.
0662: * @exception SAXException if a parsing error is to be reported
0663: */
0664: public void endElement(String namespaceURI, String localName,
0665: String qName) throws SAXException {
0666: try {
0667: tagScript = (TagScript) tagScriptStack
0668: .remove(tagScriptStack.size() - 1);
0669: if (tagScript != null) {
0670: if (textBuffer.length() > 0) {
0671: addTextScript(textBuffer.toString());
0672: textBuffer.setLength(0);
0673: }
0674: script = (ScriptBlock) scriptStack.pop();
0675: } else {
0676: textBuffer.append("</");
0677: textBuffer.append(qName);
0678: textBuffer.append(">");
0679: }
0680:
0681: // now lets set the parent tag variable
0682: if (tagScriptStack.isEmpty()) {
0683: tagScript = null;
0684: } else {
0685: tagScript = (TagScript) tagScriptStack
0686: .get(tagScriptStack.size() - 1);
0687: }
0688: } catch (Exception e) {
0689: log.error("Caught exception: " + e, e);
0690: throw new SAXException("Runtime Exception: " + e, e);
0691: }
0692: }
0693:
0694: /**
0695: * Process notification that a namespace prefix is coming in to scope.
0696: *
0697: * @param prefix Prefix that is being declared
0698: * @param namespaceURI Corresponding namespace URI being mapped to
0699: *
0700: * @exception SAXException if a parsing error is to be reported
0701: */
0702: public void startPrefixMapping(String prefix, String namespaceURI)
0703: throws SAXException {
0704: // Register this prefix mapping
0705: ArrayStack stack = (ArrayStack) namespaces.get(prefix);
0706: if (stack == null) {
0707: stack = new ArrayStack();
0708: namespaces.put(prefix, stack);
0709: }
0710: stack.push(namespaceURI);
0711:
0712: if (elementNamespaces == null) {
0713: elementNamespaces = new HashMap();
0714: }
0715: elementNamespaces.put(prefix, namespaceURI);
0716: }
0717:
0718: /**
0719: * Process notification that a namespace prefix is going out of scope.
0720: *
0721: * @param prefix Prefix that is going out of scope
0722: *
0723: * @exception SAXException if a parsing error is to be reported
0724: */
0725: public void endPrefixMapping(String prefix) throws SAXException {
0726: // Deregister this prefix mapping
0727: ArrayStack stack = (ArrayStack) namespaces.get(prefix);
0728: if (stack == null) {
0729: return;
0730: }
0731: try {
0732: stack.pop();
0733: if (stack.empty()) {
0734: namespaces.remove(prefix);
0735: }
0736: } catch (EmptyStackException e) {
0737: throw createSAXException("endPrefixMapping popped too many times");
0738: }
0739: }
0740:
0741: /**
0742: * Process notification of ignorable whitespace received from the body of
0743: * an XML element.
0744: *
0745: * @param buffer The characters from the XML document
0746: * @param start Starting offset into the buffer
0747: * @param len Number of characters from the buffer
0748: *
0749: * @exception SAXException if a parsing error is to be reported
0750: */
0751: public void ignorableWhitespace(char buffer[], int start, int len)
0752: throws SAXException {
0753: ; // No processing required
0754: }
0755:
0756: /**
0757: * Process notification of a processing instruction that was encountered.
0758: *
0759: * @param target The processing instruction target
0760: * @param data The processing instruction data (if any)
0761: *
0762: * @exception SAXException if a parsing error is to be reported
0763: */
0764: public void processingInstruction(String target, String data)
0765: throws SAXException {
0766: ; // No processing is required
0767: }
0768:
0769: /**
0770: * Set the document locator associated with our parser.
0771: *
0772: * @param locator The new locator
0773: */
0774: public void setDocumentLocator(Locator locator) {
0775: this .locator = locator;
0776: }
0777:
0778: /**
0779: * Process notification of a skipped entity.
0780: *
0781: * @param name Name of the skipped entity
0782: *
0783: * @exception SAXException if a parsing error is to be reported
0784: */
0785: public void skippedEntity(String name) throws SAXException {
0786: ; // No processing required
0787: }
0788:
0789: // DTDHandler interface
0790: //-------------------------------------------------------------------------
0791:
0792: /**
0793: * Receive notification of a notation declaration event.
0794: *
0795: * @param name The notation name
0796: * @param publicId The public identifier (if any)
0797: * @param systemId The system identifier (if any)
0798: */
0799: public void notationDecl(String name, String publicId,
0800: String systemId) {
0801: }
0802:
0803: /**
0804: * Receive notification of an unparsed entity declaration event.
0805: *
0806: * @param name The unparsed entity name
0807: * @param publicId The public identifier (if any)
0808: * @param systemId The system identifier (if any)
0809: * @param notation The name of the associated notation
0810: */
0811: public void unparsedEntityDecl(String name, String publicId,
0812: String systemId, String notation) {
0813: }
0814:
0815: // ErrorHandler interface
0816: //-------------------------------------------------------------------------
0817:
0818: /**
0819: * Forward notification of a parsing error to the application supplied
0820: * error handler, if any, otherwise throw a SAXException with the error.
0821: *
0822: * @param exception The error information
0823: *
0824: * @exception SAXException if a parsing exception occurs
0825: */
0826: public void error(SAXParseException exception) throws SAXException {
0827: log.error("Parse Error at line " + exception.getLineNumber()
0828: + " column " + exception.getColumnNumber() + ": "
0829: + exception.getMessage(), exception);
0830: if (errorHandler != null) {
0831: errorHandler.error(exception);
0832: } else {
0833: throw exception;
0834: }
0835: }
0836:
0837: /**
0838: * Forward notification of a fatal parsing error to the application
0839: * supplied error handler, if any, otherwise throw a SAXException with the error.
0840: *
0841: * @param exception The fatal error information
0842: *
0843: * @exception SAXException if a parsing exception occurs
0844: */
0845: public void fatalError(SAXParseException exception)
0846: throws SAXException {
0847: log.error("Parse Fatal Error at line "
0848: + exception.getLineNumber() + " column "
0849: + exception.getColumnNumber() + ": "
0850: + exception.getMessage(), exception);
0851: if (errorHandler != null) {
0852: errorHandler.fatalError(exception);
0853: } else {
0854: throw exception;
0855: }
0856: }
0857:
0858: /**
0859: * Forward notification of a parse warning to the application supplied
0860: * error handler (if any). Unlike XMLParser.error(SAXParseException) and
0861: * XMLParser.fatalError(SAXParseException), this implementation will
0862: * NOT throw a SAXException by default if no error handler is supplied.
0863: *
0864: * @param exception The warning information
0865: *
0866: * @exception SAXException if a parsing exception occurs
0867: */
0868: public void warning(SAXParseException exception)
0869: throws SAXException {
0870: log.error("Parse Warning at line " + exception.getLineNumber()
0871: + " column " + exception.getColumnNumber() + ": "
0872: + exception.getMessage(), exception);
0873: if (errorHandler != null) {
0874: errorHandler.warning(exception);
0875: }
0876: }
0877:
0878: // Implementation methods
0879: //-------------------------------------------------------------------------
0880: /**
0881: * If this object has not been configured then register the default
0882: * namespaces
0883: */
0884: private void ensureConfigured() {
0885: if (!configured) {
0886: configure();
0887: configured = true;
0888: }
0889: }
0890:
0891: /**
0892: * This method is called only once before parsing occurs
0893: * which allows tag libraries to be registered and so forth
0894: */
0895: protected void configure() {
0896: // load the properties file of libraries available
0897: Properties properties = getJellyProperties();
0898: for (Iterator iter = properties.entrySet().iterator(); iter
0899: .hasNext();) {
0900: Map.Entry entry = (Map.Entry) iter.next();
0901: String uri = (String) entry.getKey();
0902: String className = (String) entry.getValue();
0903: String libraryURI = "jelly:" + uri;
0904:
0905: // don't overload any Mock Tags already
0906: if (!context.isTagLibraryRegistered(libraryURI)) {
0907: context.registerTagLibrary(libraryURI, className);
0908: }
0909: }
0910: }
0911:
0912: /**
0913: * A helper method which loads the static Jelly properties once on startup
0914: */
0915: protected synchronized Properties getJellyProperties() {
0916: if (jellyProperties == null) {
0917: jellyProperties = new Properties();
0918:
0919: InputStream in = null;
0920: URL url = getClassLoader().getResource(
0921: "org/apache/commons/jelly/jelly.properties");
0922: if (url != null) {
0923: log.debug("Loading Jelly default tag libraries from: "
0924: + url);
0925: try {
0926: in = url.openStream();
0927: jellyProperties.load(in);
0928: } catch (IOException e) {
0929: log.error("Could not load jelly properties from: "
0930: + url + ". Reason: " + e, e);
0931: } finally {
0932: try {
0933: in.close();
0934: } catch (Exception e) {
0935: if (log.isDebugEnabled())
0936: log.debug("error closing jelly.properties",
0937: e);
0938: }
0939: }
0940: }
0941: }
0942: return jellyProperties;
0943: }
0944:
0945: /**
0946: * Factory method to create new Tag script for the given namespaceURI and name or
0947: * return null if this is not a custom Tag.
0948: */
0949: protected TagScript createTag(String namespaceURI,
0950: String localName, Attributes list) throws SAXException {
0951: try {
0952: // use the URI to load a taglib
0953: TagLibrary taglib = context.getTagLibrary(namespaceURI);
0954: if (taglib == null) {
0955: if (namespaceURI != null
0956: && namespaceURI.startsWith("jelly:")) {
0957: String uri = namespaceURI.substring(6);
0958: // try to find the class on the claspath
0959: try {
0960: Class taglibClass = getClassLoader().loadClass(
0961: uri);
0962: taglib = (TagLibrary) taglibClass.newInstance();
0963: context
0964: .registerTagLibrary(namespaceURI,
0965: taglib);
0966: } catch (ClassNotFoundException e) {
0967: throw createSAXException(
0968: "Could not load class: "
0969: + uri
0970: + " so taglib instantiation failed",
0971: e);
0972: } catch (IllegalAccessException e) {
0973: throw createSAXException(
0974: "Constructor for class is not accessible: "
0975: + uri
0976: + " so taglib instantiation failed",
0977: e);
0978: } catch (InstantiationException e) {
0979: throw createSAXException(
0980: "Class could not be instantiated: "
0981: + uri
0982: + " so taglib instantiation failed",
0983: e);
0984: } catch (ClassCastException e) {
0985: throw createSAXException(
0986: "Class is not a TagLibrary: "
0987: + uri
0988: + " so taglib instantiation failed",
0989: e);
0990: }
0991: }
0992: }
0993: if (taglib != null) {
0994: TagScript script = taglib.createTagScript(localName,
0995: list);
0996: if (script != null) {
0997: configureTagScript(script);
0998:
0999: // clone the attributes to keep them around after this parse
1000: script.setSaxAttributes(new AttributesImpl(list));
1001:
1002: // now iterate through through the expressions
1003: int size = list.getLength();
1004: for (int i = 0; i < size; i++) {
1005: String attributeName = list.getLocalName(i);
1006: String attributeValue = list.getValue(i);
1007: Expression expression = taglib
1008: .createExpression(
1009: getExpressionFactory(), script,
1010: attributeName, attributeValue);
1011: if (expression == null) {
1012: expression = createConstantExpression(
1013: localName, attributeName,
1014: attributeValue);
1015: }
1016: script.addAttribute(attributeName, expression);
1017: }
1018: }
1019: return script;
1020: }
1021: return null;
1022: } catch (Exception e) {
1023: log.warn("Could not create taglib or URI: " + namespaceURI
1024: + " tag name: " + localName, e);
1025: throw createSAXException(e);
1026: }
1027: }
1028:
1029: /**
1030: * Factory method to create a static Tag that represents some static content.
1031: */
1032: protected TagScript createStaticTag(final String namespaceURI,
1033: final String localName, final String qName, Attributes list)
1034: throws SAXException {
1035: try {
1036: StaticTag tag = new StaticTag(namespaceURI, localName,
1037: qName);
1038: StaticTagScript script = new StaticTagScript(
1039: new TagFactory() {
1040: public Tag createTag(String name,
1041: Attributes attributes) {
1042: return new StaticTag(namespaceURI,
1043: localName, qName);
1044: }
1045: });
1046: configureTagScript(script);
1047:
1048: // now iterate through through the expressions
1049: int size = list.getLength();
1050: for (int i = 0; i < size; i++) {
1051: String attributeValue = list.getValue(i);
1052: Expression expression = CompositeExpression.parse(
1053: attributeValue, getExpressionFactory());
1054: String attrQName = list.getQName(i);
1055: script.addAttribute(attrQName, expression);
1056: }
1057: return script;
1058: } catch (Exception e) {
1059: log.warn("Could not create static tag for URI: "
1060: + namespaceURI + " tag name: " + localName, e);
1061: throw createSAXException(e);
1062: }
1063: }
1064:
1065: /**
1066: * Configure a newly created TagScript instance before any Expressions are created
1067: *
1068: * @param aTagScript
1069: */
1070: protected void configureTagScript(TagScript aTagScript) {
1071: // set parent relationship...
1072: aTagScript.setParent(this .tagScript);
1073:
1074: // set the namespace Map
1075: if (elementNamespaces != null) {
1076: aTagScript.setTagNamespacesMap(elementNamespaces);
1077: elementNamespaces = null;
1078: }
1079: }
1080:
1081: /**
1082: * Adds the text to the current script block parsing any embedded
1083: * expressions inot ExpressionScript objects.
1084: */
1085: protected void addTextScript(String text) throws JellyException {
1086: Expression expression = CompositeExpression.parse(text,
1087: getExpressionFactory());
1088:
1089: addExpressionScript(script, expression);
1090: }
1091:
1092: /**
1093: * Adds the given Expression object to the current Script.
1094: */
1095: protected void addExpressionScript(ScriptBlock script,
1096: Expression expression) {
1097: if (expression instanceof ConstantExpression) {
1098: ConstantExpression constantExpression = (ConstantExpression) expression;
1099: Object value = constantExpression.getValue();
1100: if (value != null) {
1101: script.addScript(new TextScript(value.toString()));
1102: }
1103: } else if (expression instanceof CompositeExpression) {
1104: CompositeTextScriptBlock newBlock = new CompositeTextScriptBlock();
1105: script.addScript(newBlock);
1106:
1107: CompositeExpression compositeExpression = (CompositeExpression) expression;
1108: Iterator iter = compositeExpression.getExpressions()
1109: .iterator();
1110: while (iter.hasNext()) {
1111: addExpressionScript(newBlock, (Expression) iter.next());
1112: }
1113: } else {
1114: script.addScript(new ExpressionScript(expression));
1115: }
1116: }
1117:
1118: protected Expression createConstantExpression(String tagName,
1119: String attributeName, String attributeValue) {
1120: return new ConstantExpression(attributeValue);
1121: }
1122:
1123: protected ExpressionFactory createExpressionFactory() {
1124: return new JexlExpressionFactory();
1125: }
1126:
1127: /**
1128: * @return the current context URI as a String or null if there is no
1129: * current context defined on the JellyContext
1130: */
1131: protected String getCurrentURI() {
1132: URL url = this .getContext().getCurrentURL();
1133: return (url != null) ? url.toString() : null;
1134: }
1135:
1136: /**
1137: * Create a SAX exception which also understands about the location in
1138: * the file where the exception occurs
1139: *
1140: * @return the new exception
1141: */
1142: protected SAXException createSAXException(String message,
1143: Exception e) {
1144: log.warn("Underlying exception: " + e);
1145: e.printStackTrace();
1146: if (locator != null) {
1147: String error = "Error at (" + locator.getLineNumber()
1148: + ", " + locator.getColumnNumber() + "): "
1149: + message;
1150: if (e != null) {
1151: return new SAXParseException(error, locator, e);
1152: } else {
1153: return new SAXParseException(error, locator);
1154: }
1155: }
1156: log.error("No Locator!");
1157: if (e != null) {
1158: return new SAXException(message, e);
1159: } else {
1160: return new SAXException(message);
1161: }
1162: }
1163:
1164: /**
1165: * Create a SAX exception which also understands about the location in
1166: * the digester file where the exception occurs
1167: *
1168: * @return the new exception
1169: */
1170: protected SAXException createSAXException(Exception e) {
1171: return createSAXException(e.getMessage(), e);
1172: }
1173:
1174: /**
1175: * Create a SAX exception which also understands about the location in
1176: * the digester file where the exception occurs
1177: *
1178: * @return the new exception
1179: */
1180: protected SAXException createSAXException(String message) {
1181: return createSAXException(message, null);
1182: }
1183: }
|