View Javadoc

    1   /*
    2    * Copyright 2002,2004 The Apache Software Foundation.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.jelly.parser;
    17
    18  import java.io.File;
    19  import java.io.InputStream;
    20  import java.io.IOException;
    21  import java.io.Reader;
    22  import java.net.URL;
    23  import java.util.ArrayList;
    24  import java.util.EmptyStackException;
    25  import java.util.HashMap;
    26  import java.util.Iterator;
    27  import java.util.Map;
    28  import java.util.Properties;
    29
    30  import javax.xml.parsers.SAXParser;
    31  import javax.xml.parsers.SAXParserFactory;
    32
    33  import org.apache.commons.collections.ArrayStack;
    34
    35  import org.apache.commons.jelly.JellyContext;
    36  import org.apache.commons.jelly.JellyException;
    37  import org.apache.commons.jelly.Script;
    38  import org.apache.commons.jelly.Tag;
    39  import org.apache.commons.jelly.TagLibrary;
    40  import org.apache.commons.jelly.impl.CompositeTextScriptBlock;
    41  import org.apache.commons.jelly.impl.ExpressionScript;
    42  import org.apache.commons.jelly.impl.StaticTag;
    43  import org.apache.commons.jelly.impl.ScriptBlock;
    44  import org.apache.commons.jelly.impl.StaticTagScript;
    45  import org.apache.commons.jelly.impl.TagFactory;
    46  import org.apache.commons.jelly.impl.TagScript;
    47  import org.apache.commons.jelly.impl.TextScript;
    48  import org.apache.commons.jelly.util.ClassLoaderUtils;
    49  import org.apache.commons.jelly.expression.CompositeExpression;
    50  import org.apache.commons.jelly.expression.ConstantExpression;
    51  import org.apache.commons.jelly.expression.Expression;
    52  import org.apache.commons.jelly.expression.ExpressionFactory;
    53  import org.apache.commons.jelly.expression.jexl.JexlExpressionFactory;
    54
    55  import org.apache.commons.logging.Log;
    56  import org.apache.commons.logging.LogFactory;
    57
    58  import org.xml.sax.Attributes;
    59  import org.xml.sax.ErrorHandler;
    60  import org.xml.sax.helpers.DefaultHandler;
    61  import org.xml.sax.InputSource;
    62  import org.xml.sax.Locator;
    63  import org.xml.sax.SAXException;
    64  import org.xml.sax.SAXParseException;
    65  import org.xml.sax.XMLReader;
    66  import org.xml.sax.helpers.AttributesImpl;
    67
    68  /*** <p><code>XMLParser</code> parses the XML Jelly format.
    69   * The SAXParser and XMLReader portions of this code come from Digester.</p>
    70   *
    71   * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
    72   * @version $Revision: 155420 $
    73   */
    74  public class XMLParser extends DefaultHandler {
    75
    76      /***
    77       * Share the Jelly properties across parsers
    78       */
    79      private static Properties jellyProperties;
    80
    81
    82      /*** JellyContext which is used to locate tag libraries*/
    83      private JellyContext context = new JellyContext();
    84
    85      /*** the expression factory used to evaluate tag attributes */
    86      private ExpressionFactory expressionFactory;
    87
    88      /*** The current script block */
    89      private ScriptBlock script;
    90
    91      /*** The current, parent tagScript */
    92      private TagScript tagScript;
    93
    94      /*** The stack of body scripts. */
    95      private ArrayStack scriptStack = new ArrayStack();
    96
    97      /*** The stack of tagScripts - use ArrayList as it allows null. */
    98      private ArrayList tagScriptStack = new ArrayList();
    99
    100     /*** The current text buffer where non-custom tags get written */
    101     private StringBuffer textBuffer;
    102
    103     /***
    104      * The class loader to use for instantiating application objects.
    105      * If not specified, the context class loader, or the class loader
    106      * used to load XMLParser itself, is used, based on the value of the
    107      * <code>useContextClassLoader</code> variable.
    108      */
    109     protected ClassLoader classLoader = null;
    110
    111     /***
    112      * Do we want to use the Context ClassLoader when loading classes
    113      * for instantiating new objects?  Default is <code>false</code>.
    114      */
    115     protected boolean useContextClassLoader = false;
    116
    117     /***
    118      * The application-supplied error handler that is notified when parsing
    119      * warnings, errors, or fatal errors occur.
    120      */
    121     protected ErrorHandler errorHandler = null;
    122
    123     /***
    124      * The SAXParserFactory that is created the first time we need it.
    125      */
    126     protected static SAXParserFactory factory = null;
    127
    128     /***
    129      * The SAXParser we will use to parse the input stream.
    130      */
    131     protected SAXParser parser = null;
    132
    133     /***
    134      * The XMLReader used to parse digester rules.
    135      */
    136     protected XMLReader reader = null;
    137
    138     /***
    139      * The Locator associated with our parser.
    140      */
    141     protected Locator locator = null;
    142
    143     /***
    144      * Registered namespaces we are currently processing.  The key is the
    145      * namespace prefix that was declared in the document.  The value is an
    146      * ArrayStack of the namespace URIs this prefix has been mapped to --
    147      * the top Stack element is the most current one.  (This architecture
    148      * is required because documents can declare nested uses of the same
    149      * prefix for different Namespace URIs).
    150      */
    151     protected Map namespaces = new HashMap();
    152
    153     /*** The Map of the namespace prefix -&gt; URIs defined for the current element */
    154     private Map elementNamespaces;
    155
    156     /***
    157      * The name of the file being parsed that is passed to the TagScript objects
    158      * for error reporting
    159      */
    160     private String fileName;
    161
    162     /***
    163      * Do we want to use a validating parser?
    164      */
    165     protected boolean validating = false;
    166
    167     /*** Flag to indicate if this object has been configured */
    168     private boolean configured;
    169
    170     /***
    171      * when not null, set the default namespace for
    172      * unprefixed elements via the DefaultNamespaceFilter
    173      * class
    174      */
    175     private String defaultNamespaceURI = null;
    176
    177     /***
    178      * The Log to which logging calls will be made.
    179      */
    180     private Log log = LogFactory.getLog(XMLParser.class);
    181
    182     /***
    183      * Construct a new XMLParser with default properties.
    184      */
    185     public XMLParser() {
    186     }
    187
    188     /***
    189      * Construct a new XMLParser, allowing a SAXParser to be passed in.  This
    190      * allows XMLParser to be used in environments which are unfriendly to
    191      * JAXP1.1 (such as WebLogic 6.0).  Thanks for the request to change go to
    192      * James House (james@interobjective.com).  This may help in places where
    193      * you are able to load JAXP 1.1 classes yourself.
    194      */
    195     public XMLParser(SAXParser parser) {
    196         this.parser = parser;
    197     }
    198
    199     /***
    200      * Construct a new XMLParser, allowing an XMLReader to be passed in.  This
    201      * allows XMLParser to be used in environments which are unfriendly to
    202      * JAXP1.1 (such as WebLogic 6.0).  Note that if you use this option you
    203      * have to configure namespace and validation support yourself, as these
    204      * properties only affect the SAXParser and emtpy constructor.
    205      */
    206     public XMLParser(XMLReader reader) {
    207         this.reader = reader;
    208     }
    209
    210     /***
    211      * Parse the content of the specified file using this XMLParser.  Returns
    212      * the root element from the object stack (if any).
    213      *
    214      * @param file File containing the XML data to be parsed
    215      *
    216      * @exception IOException if an input/output error occurs
    217      * @exception SAXException if a parsing exception occurs
    218      */
    219     public Script parse(File file) throws IOException, SAXException {
    220         return parse(file.toURL());
    221     }
    222
    223     /***
    224      * Parse the content of the specified file using this XMLParser.  Returns
    225      * the root element from the object stack (if any).
    226      *
    227      * @param url URL containing the XML data to be parsed
    228      *
    229      * @exception IOException if an input/output error occurs
    230      * @exception SAXException if a parsing exception occurs
    231      */
    232     public Script parse(URL url) throws IOException, SAXException {
    233         ensureConfigured();
    234         this.fileName = url.toString();
    235
    236         InputSource source = new InputSource(url.toString());
    237
    238         getXMLReader().parse(source);
    239         return script;
    240     }
    241
    242     /***
    243      * Parse the content of the specified input source using this XMLParser.
    244      * Returns the root element from the object stack (if any).
    245      *
    246      * @param input Input source containing the XML data to be parsed
    247      *
    248      * @exception IOException if an input/output error occurs
    249      * @exception SAXException if a parsing exception occurs
    250      */
    251     public Script parse(InputSource input) throws IOException, SAXException {
    252         ensureConfigured();
    253         this.fileName = input.getSystemId();
    254         getXMLReader().parse(input);
    255         return script;
    256     }
    257
    258     /***
    259      * Parse the content of the specified input stream using this XMLParser.
    260      * Returns the root element from the object stack (if any).
    261      * (Note: if reading a File or URL, use one of the URL-based
    262      * parse methods instead.  This method will not be able
    263      * to resolve any relative paths inside a DTD.)
    264      *
    265      * @param input  Input stream containing the XML data to be parsed
    266      * @return
    267      * @exception IOException
    268      *                   if an input/output error occurs
    269      * @exception SAXException
    270      *                   if a parsing exception occurs
    271      */
    272     public Script parse(InputStream input) throws IOException, SAXException {
    273         ensureConfigured();
    274         this.fileName = getCurrentURI();
    275         getXMLReader().parse(new InputSource(input));
    276         return script;
    277     }
    278
    279     /***
    280      * Parse the content of the specified reader using this XMLParser.
    281      * Returns the root element from the object stack (if any).
    282      * (Note: if reading a File or URL, use one of the URL-based
    283      * parse methods instead.  This method will not be able
    284      * to resolve any relative paths inside a DTD.)
    285      *
    286      * @param reader Reader containing the XML data to be parsed
    287      * @return
    288      * @exception IOException
    289      *                   if an input/output error occurs
    290      * @exception SAXException
    291      *                   if a parsing exception occurs
    292      */
    293     public Script parse(Reader reader) throws IOException, SAXException {
    294         ensureConfigured();
    295         this.fileName = getCurrentURI();
    296         getXMLReader().parse(new InputSource(reader));
    297         return script;
    298     }
    299
    300     /***
    301      * Parse the content of the specified URI using this XMLParser.
    302      * Returns the root element from the object stack (if any).
    303      *
    304      * @param uri URI containing the XML data to be parsed
    305      *
    306      * @exception IOException if an input/output error occurs
    307      * @exception SAXException if a parsing exception occurs
    308      */
    309     public Script parse(String uri) throws IOException, SAXException {
    310         ensureConfigured();
    311         this.fileName = uri;
    312         getXMLReader().parse(uri);
    313         return script;
    314     }
    315
    316     /***
    317      * Return the currently mapped namespace URI for the specified prefix,
    318      * if any; otherwise return <code>null</code>.  These mappings come and
    319      * go dynamically as the document is parsed.
    320      *
    321      * @param prefix Prefix to look up
    322      */
    323     public String findNamespaceURI(String prefix) {
    324         ArrayStack stack = (ArrayStack) namespaces.get(prefix);
    325         if (stack == null) {
    326             return (null);
    327         }
    328         try {
    329             return ((String) stack.peek());
    330         }
    331         catch (EmptyStackException e) {
    332             return (null);
    333         }
    334     }
    335
    336     // Properties
    337     //-------------------------------------------------------------------------
    338     public JellyContext getContext() {
    339         return context;
    340     }
    341
    342     public void setContext(JellyContext context) {
    343         this.context = context;
    344     }
    345
    346     /***
    347      * Set the jelly namespace to use for unprefixed elements.
    348      * Will be overridden by an explicit namespace in the
    349      * XML document.
    350      *
    351      * @param namespace jelly namespace to use (e.g. 'jelly:core')
    352      */
    353     public void setDefaultNamespaceURI(String namespace) {
    354         this.defaultNamespaceURI = namespace;
    355     }
    356
    357     /***
    358      * Return the class loader to be used for instantiating application objects
    359      * when required.  This is determined based upon the following rules:
    360      * <ul>
    361      * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
    362      * <li>The thread context class loader, if it exists and the
    363      *     <code>useContextClassLoader</code> property is set to true</li>
    364      * <li>The class loader used to load the XMLParser class itself.
    365      * </ul>
    366      */
    367     public ClassLoader getClassLoader() {
    368         return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
    369     }
    370
    371     /***
    372      * Set the class loader to be used for instantiating application objects
    373      * when required.
    374      *
    375      * @param classLoader The new class loader to use, or <code>null</code>
    376      *  to revert to the standard rules
    377      */
    378     public void setClassLoader(ClassLoader classLoader) {
    379         this.classLoader = classLoader;
    380     }
    381
    382     /***
    383      * Return the boolean as to whether the context classloader should be used.
    384      */
    385     public boolean getUseContextClassLoader() {
    386         return useContextClassLoader;
    387     }
    388
    389     /***
    390      * Determine whether to use the Context ClassLoader (the one found by
    391      * calling <code>Thread.currentThread().getContextClassLoader()</code>)
    392      * to resolve/load classes.  If not
    393      * using Context ClassLoader, then the class-loading defaults to
    394      * using the calling-class' ClassLoader.
    395      *
    396      * @param use determines whether to use JellyContext ClassLoader.
    397      */
    398     public void setUseContextClassLoader(boolean use) {
    399         useContextClassLoader = use;
    400     }
    401
    402     /***
    403      * Return the error handler for this XMLParser.
    404      */
    405     public ErrorHandler getErrorHandler() {
    406         return (this.errorHandler);
    407     }
    408
    409     /***
    410      * Set the error handler for this XMLParser.
    411      *
    412      * @param errorHandler The new error handler
    413      */
    414     public void setErrorHandler(ErrorHandler errorHandler) {
    415         this.errorHandler = errorHandler;
    416     }
    417
    418     /***
    419      * Return the current Logger associated with this instance of the XMLParser
    420      */
    421     public Log getLogger() {
    422         return log;
    423     }
    424
    425     /***
    426      * Set the current logger for this XMLParser.
    427      */
    428     public void setLogger(Log log) {
    429         this.log = log;
    430     }
    431
    432     /*** @return the expression factory used to evaluate tag attributes */
    433     public ExpressionFactory getExpressionFactory() {
    434         if (expressionFactory == null) {
    435             expressionFactory = createExpressionFactory();
    436         }
    437         return expressionFactory;
    438     }
    439
    440     /*** Sets the expression factory used to evaluate tag attributes */
    441     public void setExpressionFactory(ExpressionFactory expressionFactory) {
    442         this.expressionFactory = expressionFactory;
    443     }
    444
    445     /***
    446      * Return the SAXParser we will use to parse the input stream.  If there
    447      * is a problem creating the parser, return <code>null</code>.
    448      */
    449     public SAXParser getParser() {
    450         // Return the parser we already created (if any)
    451         if (parser != null) {
    452             return (parser);
    453         }
    454         // Create and return a new parser
    455         synchronized (this) {
    456             try {
    457                 if (factory == null) {
    458                     factory = SAXParserFactory.newInstance();
    459                 }
    460                 factory.setNamespaceAware(true);
    461                 factory.setValidating(validating);
    462                 parser = factory.newSAXParser();
    463                 return (parser);
    464             }
    465             catch (Exception e) {
    466                 log.error("XMLParser.getParser: ", e);
    467                 return (null);
    468             }
    469         }
    470     }
    471
    472     /***
    473      * By setting the reader in the constructor, you can bypass JAXP and
    474      * be able to use digester in Weblogic 6.0.
    475      *
    476      * @deprecated Use getXMLReader() instead, which can throw a
    477      *  SAXException if the reader cannot be instantiated
    478      */
    479     public XMLReader getReader() {
    480         try {
    481             return (getXMLReader());
    482         }
    483         catch (SAXException e) {
    484             log.error("Cannot get XMLReader", e);
    485             return (null);
    486         }
    487     }
    488
    489     /***
    490      * Return the XMLReader to be used for parsing the input document.
    491      *
    492      * @exception SAXException if no XMLReader can be instantiated
    493      */
    494     public synchronized XMLReader getXMLReader() throws SAXException {
    495         if (reader == null) {
    496             reader = getParser().getXMLReader();
    497             if (this.defaultNamespaceURI != null) {
    498                 reader = new DefaultNamespaceFilter(this.defaultNamespaceURI,reader);
    499             }
    500         }
    501         //set up the parse
    502         reader.setContentHandler(this);
    503         reader.setDTDHandler(this);
    504         //reader.setEntityResolver(this);
    505         reader.setErrorHandler(this);
    506
    507         return reader;
    508     }
    509
    510     /***
    511      * Return the validating parser flag.
    512      */
    513     public boolean getValidating() {
    514         return (this.validating);
    515     }
    516
    517     /***
    518      * Set the validating parser flag.  This must be called before
    519      * <code>parse()</code> is called the first time.
    520      *
    521      * @param validating The new validating parser flag.
    522      */
    523     public void setValidating(boolean validating) {
    524         this.validating = validating;
    525     }
    526
    527
    528     /***
    529      * Returns the script that has just been created if this class is used
    530      * as a SAX ContentHandler and passed into some XML processor or parser.
    531      *
    532      * @return the ScriptBlock created if SAX events are piped into this class,
    533      * which must include a startDocument() and endDocument()
    534      */
    535     public ScriptBlock getScript() {
    536         return script;
    537     }
    538
    539
    540     // ContentHandler interface
    541     //-------------------------------------------------------------------------
    542     /***
    543      * Process notification of the beginning of the document being reached.
    544      *
    545      * @exception SAXException if a parsing error is to be reported
    546      */
    547     public void startDocument() throws SAXException {
    548         script = new ScriptBlock();
    549         textBuffer = new StringBuffer();
    550         tagScript = null;
    551         scriptStack.clear();
    552         tagScriptStack.clear();
    553     }
    554
    555     /***
    556      * Process notification of the end of the document being reached.
    557      *
    558      * @exception SAXException if a parsing error is to be reported
    559      */
    560     public void endDocument() throws SAXException {
    561         textBuffer = null;
    562     }
    563
    564     /***
    565      * Process notification of the start of an XML element being reached.
    566      *
    567      * @param namespaceURI The Namespace URI, or the empty string if the
    568      *   element has no Namespace URI or if Namespace processing is not
    569      *   being performed.
    570      * @param localName The local name (without prefix), or the empty
    571      *   string if Namespace processing is not being performed.
    572      * @param qName The qualified name (with prefix), or the empty
    573      *   string if qualified names are not available.\
    574      * @param list The attributes attached to the element. If there are
    575      *   no attributes, it shall be an empty Attributes object.
    576      * @exception SAXException if a parsing error is to be reported
    577      */
    578     public void startElement(
    579         String namespaceURI,
    580         String localName,
    581         String qName,
    582         Attributes list)
    583         throws SAXException {
    584
    585         try {
    586             // add check to ensure namespace URI is "" for no namespace
    587             if ( namespaceURI == null ) {
    588                 namespaceURI = "";
    589             }
    590
    591             // if this is a tag then create a script to run it
    592             // otherwise pass the text to the current body
    593             TagScript newTagScript = createTag(namespaceURI, localName, list);
    594             if (newTagScript == null) {
    595                 newTagScript = createStaticTag(namespaceURI, localName, qName, list);
    596             }
    597             tagScript = newTagScript;
    598             tagScriptStack.add(tagScript);
    599             if (tagScript != null) {
    600                 // set the line number details
    601                 if ( locator != null ) {
    602                     tagScript.setLocator(locator);
    603                 }
    604                 // sets the file name element names
    605                 tagScript.setFileName(fileName);
    606                 tagScript.setElementName(qName);
    607                 tagScript.setLocalName(localName);
    608
    609                 if (textBuffer.length() > 0) {
    610                     addTextScript(textBuffer.toString());
    611                     textBuffer.setLength(0);
    612                 }
    613                 script.addScript(tagScript);
    614                 // start a new body
    615                 scriptStack.push(script);
    616                 script = new ScriptBlock();
    617                 tagScript.setTagBody(script);
    618             }
    619             else {
    620                 // XXXX: might wanna handle empty elements later...
    621                 textBuffer.append("<");
    622                 textBuffer.append(qName);
    623                 int size = list.getLength();
    624                 for (int i = 0; i < size; i++) {
    625                     textBuffer.append(" ");
    626                     textBuffer.append(list.getQName(i));
    627                     textBuffer.append("=");
    628                     textBuffer.append("\"");
    629                     textBuffer.append(list.getValue(i));
    630                     textBuffer.append("\"");
    631                 }
    632                 textBuffer.append(">");
    633             }
    634         }
    635         catch (SAXException e) {
    636             throw e;
    637         }
    638         catch (Exception e) {
    639             log.error( "Caught exception: " + e, e );
    640             throw new SAXException( "Runtime Exception: " + e, e );
    641         }
    642     }
    643
    644     /***
    645      * Process notification of character data received from the body of
    646      * an XML element.
    647      *
    648      * @param buffer The characters from the XML document
    649      * @param start Starting offset into the buffer
    650      * @param length Number of characters from the buffer
    651      *
    652      * @exception SAXException if a parsing error is to be reported
    653      */
    654     public void characters(char buffer[], int start, int length)
    655         throws SAXException {
    656         textBuffer.append(buffer, start, length);
    657     }
    658
    659     /***
    660      * Process notification of the end of an XML element being reached.
    661      *
    662      * @param namespaceURI The Namespace URI, or the empty string if the
    663      *   element has no Namespace URI or if Namespace processing is not
    664      *   being performed.
    665      * @param localName The local name (without prefix), or the empty
    666      *   string if Namespace processing is not being performed.
    667      * @param qName The qualified XML 1.0 name (with prefix), or the
    668      *   empty string if qualified names are not available.
    669      * @exception SAXException if a parsing error is to be reported
    670      */
    671     public void endElement(String namespaceURI, String localName, String qName)
    672         throws SAXException {
    673         try {
    674             tagScript = (TagScript) tagScriptStack.remove(tagScriptStack.size() - 1);
    675             if (tagScript != null) {
    676                 if (textBuffer.length() > 0) {
    677                     addTextScript(textBuffer.toString());
    678                     textBuffer.setLength(0);
    679                 }
    680                 script = (ScriptBlock) scriptStack.pop();
    681             }
    682             else {
    683                 textBuffer.append("</");
    684                 textBuffer.append(qName);
    685                 textBuffer.append(">");
    686             }
    687
    688             // now lets set the parent tag variable
    689             if ( tagScriptStack.isEmpty() ) {
    690                 tagScript = null;
    691             }
    692             else {
    693                 tagScript = (TagScript) tagScriptStack.get(tagScriptStack.size() - 1);
    694             }
    695         } catch (Exception e) {
    696             log.error( "Caught exception: " + e, e );
    697             throw new SAXException( "Runtime Exception: " + e, e );
    698         }
    699     }
    700
    701     /***
    702      * Process notification that a namespace prefix is coming in to scope.
    703      *
    704      * @param prefix Prefix that is being declared
    705      * @param namespaceURI Corresponding namespace URI being mapped to
    706      *
    707      * @exception SAXException if a parsing error is to be reported
    708      */
    709     public void startPrefixMapping(String prefix, String namespaceURI)
    710         throws SAXException {
    711         // Register this prefix mapping
    712         ArrayStack stack = (ArrayStack) namespaces.get(prefix);
    713         if (stack == null) {
    714             stack = new ArrayStack();
    715             namespaces.put(prefix, stack);
    716         }
    717         stack.push(namespaceURI);
    718
    719         if ( elementNamespaces == null ) {
    720             elementNamespaces = new HashMap();
    721         }
    722         elementNamespaces.put(prefix, namespaceURI);
    723     }
    724
    725     /***
    726      * Process notification that a namespace prefix is going out of scope.
    727      *
    728      * @param prefix Prefix that is going out of scope
    729      *
    730      * @exception SAXException if a parsing error is to be reported
    731      */
    732     public void endPrefixMapping(String prefix) throws SAXException {
    733         // Deregister this prefix mapping
    734         ArrayStack stack = (ArrayStack) namespaces.get(prefix);
    735         if (stack == null) {
    736             return;
    737         }
    738         try {
    739             stack.pop();
    740             if (stack.empty()) {
    741                 namespaces.remove(prefix);
    742             }
    743         }
    744         catch (EmptyStackException e) {
    745             throw createSAXException("endPrefixMapping popped too many times");
    746         }
    747     }
    748
    749     /***
    750      * Process notification of ignorable whitespace received from the body of
    751      * an XML element.
    752      *
    753      * @param buffer The characters from the XML document
    754      * @param start Starting offset into the buffer
    755      * @param len Number of characters from the buffer
    756      *
    757      * @exception SAXException if a parsing error is to be reported
    758      */
    759     public void ignorableWhitespace(char buffer[], int start, int len)
    760         throws SAXException {
    761         ; // No processing required
    762     }
    763
    764     /***
    765      * Process notification of a processing instruction that was encountered.
    766      *
    767      * @param target The processing instruction target
    768      * @param data The processing instruction data (if any)
    769      *
    770      * @exception SAXException if a parsing error is to be reported
    771      */
    772     public void processingInstruction(String target, String data)
    773         throws SAXException {
    774         ; // No processing is required
    775     }
    776
    777     /***
    778      * Set the document locator associated with our parser.
    779      *
    780      * @param locator The new locator
    781      */
    782     public void setDocumentLocator(Locator locator) {
    783         this.locator = locator;
    784     }
    785
    786     /***
    787      * Process notification of a skipped entity.
    788      *
    789      * @param name Name of the skipped entity
    790      *
    791      * @exception SAXException if a parsing error is to be reported
    792      */
    793     public void skippedEntity(String name) throws SAXException {
    794         ; // No processing required
    795     }
    796
    797
    798     // DTDHandler interface
    799     //-------------------------------------------------------------------------
    800
    801     /***
    802      * Receive notification of a notation declaration event.
    803      *
    804      * @param name The notation name
    805      * @param publicId The public identifier (if any)
    806      * @param systemId The system identifier (if any)
    807      */
    808     public void notationDecl(String name, String publicId, String systemId) {
    809     }
    810
    811     /***
    812      * Receive notification of an unparsed entity declaration event.
    813      *
    814      * @param name The unparsed entity name
    815      * @param publicId The public identifier (if any)
    816      * @param systemId The system identifier (if any)
    817      * @param notation The name of the associated notation
    818      */
    819     public void unparsedEntityDecl(
    820         String name,
    821         String publicId,
    822         String systemId,
    823         String notation) {
    824     }
    825
    826
    827     // ErrorHandler interface
    828     //-------------------------------------------------------------------------
    829
    830     /***
    831      * Forward notification of a parsing error to the application supplied
    832      * error handler, if any, otherwise throw a SAXException with the error.
    833      *
    834      * @param exception The error information
    835      *
    836      * @exception SAXException if a parsing exception occurs
    837      */
    838     public void error(SAXParseException exception) throws SAXException {
    839         log.error(
    840             "Parse Error at line "
    841                 + exception.getLineNumber()
    842                 + " column "
    843                 + exception.getColumnNumber()
    844                 + ": "
    845                 + exception.getMessage(),
    846             exception);
    847         if (errorHandler != null) {
    848             errorHandler.error(exception);
    849         } else {
    850             throw exception;
    851         }
    852     }
    853
    854     /***
    855      * Forward notification of a fatal parsing error to the application
    856      * supplied error handler, if any, otherwise throw a SAXException with the error.
    857      *
    858      * @param exception The fatal error information
    859      *
    860      * @exception SAXException if a parsing exception occurs
    861      */
    862     public void fatalError(SAXParseException exception) throws SAXException {
    863         log.error(
    864             "Parse Fatal Error at line "
    865                 + exception.getLineNumber()
    866                 + " column "
    867                 + exception.getColumnNumber()
    868                 + ": "
    869                 + exception.getMessage(),
    870             exception);
    871         if (errorHandler != null) {
    872             errorHandler.fatalError(exception);
    873         } else {
    874             throw exception;
    875         }
    876     }
    877
    878     /***
    879      * Forward notification of a parse warning to the application supplied
    880      * error handler (if any).  Unlike XMLParser.error(SAXParseException) and
    881      * XMLParser.fatalError(SAXParseException), this implementation will
    882      * NOT throw a SAXException by default if no error handler is supplied.
    883      *
    884      * @param exception The warning information
    885      *
    886      * @exception SAXException if a parsing exception occurs
    887      */
    888     public void warning(SAXParseException exception) throws SAXException {
    889         log.error(
    890             "Parse Warning at line "
    891                 + exception.getLineNumber()
    892                 + " column "
    893                 + exception.getColumnNumber()
    894                 + ": "
    895                 + exception.getMessage(),
    896             exception);
    897         if (errorHandler != null) {
    898             errorHandler.warning(exception);
    899         }
    900     }
    901
    902     // Implementation methods
    903     //-------------------------------------------------------------------------
    904     /***
    905      * If this object has not been configured then register the default
    906      * namespaces
    907      */
    908     private void ensureConfigured() {
    909         if (!configured) {
    910             configure();
    911             configured = true;
    912         }
    913     }
    914
    915     /***
    916      * This method is called only once before parsing occurs
    917      * which allows tag libraries to be registered and so forth
    918      */
    919     protected void configure() {
    920         // load the properties file of libraries available
    921         Properties properties = getJellyProperties();
    922         for (Iterator iter = properties.entrySet().iterator(); iter.hasNext();) {
    923             Map.Entry entry = (Map.Entry) iter.next();
    924             String uri = (String) entry.getKey();
    925             String className = (String) entry.getValue();
    926             String libraryURI = "jelly:" + uri;
    927
    928             // don't overload any Mock Tags already
    929             if ( ! context.isTagLibraryRegistered(libraryURI) ) {
    930                 context.registerTagLibrary(libraryURI, className);
    931             }
    932         }
    933     }
    934
    935
    936     /***
    937      * A helper method which loads the static Jelly properties once on startup
    938      */
    939     protected synchronized Properties getJellyProperties() {
    940         if (jellyProperties == null) {
    941             jellyProperties = new Properties();
    942
    943             InputStream in = null;
    944             URL url =
    945                 getClassLoader().getResource("org/apache/commons/jelly/jelly.properties");
    946             if (url != null) {
    947                 log.debug("Loading Jelly default tag libraries from: " + url);
    948                 try {
    949                     in = url.openStream();
    950                     jellyProperties .load(in);
    951                 }
    952                 catch (IOException e) {
    953                     log.error("Could not load jelly properties from: " + url + ". Reason: " + e, e);
    954                 }
    955                 finally {
    956                     try {
    957                         in.close();
    958                     }
    959                     catch (Exception e) {
    960                         if (log.isDebugEnabled()) log.debug("error closing jelly.properties", e);
    961                     }
    962                 }
    963             }
    964         }
    965         return jellyProperties;
    966     }
    967
    968     /***
    969      * Factory method to create new Tag script for the given namespaceURI and name or
    970      * return null if this is not a custom Tag.
    971      */
    972     protected TagScript createTag(
    973         String namespaceURI,
    974         String localName,
    975         Attributes list)
    976         throws SAXException {
    977         try {
    978             // use the URI to load a taglib
    979             TagLibrary taglib = context.getTagLibrary(namespaceURI);
    980             if (taglib == null) {
    981                 if (namespaceURI != null && namespaceURI.startsWith("jelly:")) {
    982                     String uri = namespaceURI.substring(6);
    983                     // try to find the class on the claspath
    984                     try {
    985                         Class taglibClass = getClassLoader().loadClass(uri);
    986                         taglib = (TagLibrary) taglibClass.newInstance();
    987                         context.registerTagLibrary(namespaceURI, taglib);
    988                     }
    989                     catch (ClassNotFoundException e) {
    990                         throw createSAXException("Could not load class: " + uri + " so taglib instantiation failed", e);
    991                     }
    992                     catch (IllegalAccessException e) {
    993                         throw createSAXException("Constructor for class is not accessible: " + uri + " so taglib instantiation failed",e);
    994                     }
    995                     catch (InstantiationException e) {
    996                         throw createSAXException("Class could not be instantiated: " + uri + " so taglib instantiation failed",e);
    997                     }
    998                     catch (ClassCastException e) {
    999                         throw createSAXException("Class is not a TagLibrary: " + uri + " so taglib instantiation failed",e);
    1000                     }
    1001                 }
    1002             }
    1003             if (taglib != null) {
    1004                 TagScript script = taglib.createTagScript(localName, list);
    1005                 if ( script != null ) {
    1006                     configureTagScript(script);
    1007
    1008                     // clone the attributes to keep them around after this parse
    1009                     script.setSaxAttributes(new AttributesImpl(list));
    1010
    1011                     // now iterate through through the expressions
    1012                     int size = list.getLength();
    1013                     for (int i = 0; i < size; i++) {
    1014                         String attributeName = list.getLocalName(i);
    1015                         String attributeValue = list.getValue(i);
    1016                         Expression expression =
    1017                             taglib.createExpression(
    1018                                 getExpressionFactory(),
    1019                                 script,
    1020                                 attributeName,
    1021                                 attributeValue);
    1022                         if (expression == null) {
    1023                             expression = createConstantExpression(localName, attributeName, attributeValue);
    1024                         }
    1025                         script.addAttribute(attributeName, expression);
    1026                     }
    1027                 }
    1028                 return script;
    1029             }
    1030             return null;
    1031         }
    1032         catch (Exception e) {
    1033             log.warn(
    1034                 "Could not create taglib or URI: " + namespaceURI + " tag name: " + localName,
    1035                 e);
    1036             throw createSAXException(e);
    1037         }
    1038     }
    1039
    1040
    1041     /***
    1042      * Factory method to create a static Tag that represents some static content.
    1043      */
    1044     protected TagScript createStaticTag(
    1045         final String namespaceURI,
    1046         final String localName,
    1047         final String qName,
    1048         Attributes list)
    1049         throws SAXException {
    1050         try {
    1051             StaticTag tag = new StaticTag( namespaceURI, localName, qName );
    1052             StaticTagScript script = new StaticTagScript(
    1053                 new TagFactory() {
    1054                     public Tag createTag(String name, Attributes attributes) {
    1055                         return new StaticTag( namespaceURI, localName, qName );
    1056                     }
    1057                 }
    1058             );
    1059             configureTagScript(script);
    1060
    1061             // now iterate through through the expressions
    1062             int size = list.getLength();
    1063             for (int i = 0; i < size; i++) {
    1064                 String attributeValue = list.getValue(i);
    1065                 Expression expression = CompositeExpression.parse(
    1066                         attributeValue, getExpressionFactory()
    1067                     );
    1068                 String attrQName = list.getQName(i);
    1069                 script.addAttribute(attrQName, expression);
    1070             }
    1071             return script;
    1072         }
    1073         catch (Exception e) {
    1074             log.warn(
    1075                 "Could not create static tag for URI: "
    1076                     + namespaceURI
    1077                     + " tag name: "
    1078                     + localName,
    1079                 e);
    1080             throw createSAXException(e);
    1081         }
    1082     }
    1083
    1084
    1085     /***
    1086      * Configure a newly created TagScript instance before any Expressions are created
    1087      *
    1088      * @param aTagScript
    1089      */
    1090     protected void configureTagScript(TagScript aTagScript) {
    1091         // set parent relationship...
    1092         aTagScript.setParent(this.tagScript);
    1093
    1094         // set the namespace Map
    1095         if ( elementNamespaces != null ) {
    1096             aTagScript.setTagNamespacesMap( elementNamespaces );
    1097             elementNamespaces = null;
    1098         }
    1099     }
    1100
    1101     /***
    1102      * Adds the text to the current script block parsing any embedded
    1103      * expressions inot ExpressionScript objects.
    1104      */
    1105     protected void addTextScript(String text) throws JellyException {
    1106         Expression expression =
    1107             CompositeExpression.parse(text, getExpressionFactory());
    1108
    1109         addExpressionScript(script, expression);
    1110     }
    1111
    1112
    1113     /***
    1114      * Adds the given Expression object to the current Script.
    1115      */
    1116     protected void addExpressionScript(ScriptBlock script, Expression expression) {
    1117         if ( expression instanceof ConstantExpression ) {
    1118             ConstantExpression constantExpression
    1119                 = (ConstantExpression) expression;
    1120             Object value = constantExpression.getValue();
    1121             if ( value != null ) {
    1122                 script.addScript(new TextScript( value.toString() ));
    1123             }
    1124         }
    1125         else
    1126         if ( expression instanceof CompositeExpression ) {
    1127             CompositeTextScriptBlock newBlock = new CompositeTextScriptBlock();
    1128             script.addScript(newBlock);
    1129
    1130             CompositeExpression compositeExpression
    1131                 = (CompositeExpression) expression;
    1132             Iterator iter = compositeExpression.getExpressions().iterator();
    1133             while (iter.hasNext()) {
    1134                 addExpressionScript( newBlock, (Expression) iter.next() );
    1135             }
    1136         }
    1137         else {
    1138             script.addScript(new ExpressionScript(expression));
    1139         }
    1140     }
    1141
    1142     protected Expression createConstantExpression(
    1143         String tagName,
    1144         String attributeName,
    1145         String attributeValue)  {
    1146         return new ConstantExpression(attributeValue);
    1147     }
    1148
    1149     protected ExpressionFactory createExpressionFactory() {
    1150         return new JexlExpressionFactory();
    1151     }
    1152
    1153     /***
    1154      * @return the current context URI as a String or null if there is no
    1155      * current context defined on the JellyContext
    1156      */
    1157     protected String getCurrentURI() {
    1158         URL url = this.getContext().getCurrentURL();
    1159         return (url != null) ? url.toString() : null;
    1160     }
    1161
    1162     /***
    1163      * Create a SAX exception which also understands about the location in
    1164      * the file where the exception occurs
    1165      *
    1166      * @return the new exception
    1167      */
    1168     protected SAXException createSAXException(String message, Exception e) {
    1169         log.warn("Underlying exception: " + e);
    1170         e.printStackTrace();
    1171         if (locator != null) {
    1172             String error =
    1173                 "Error at ("
    1174                     + locator.getLineNumber()
    1175                     + ", "
    1176                     + locator.getColumnNumber()
    1177                     + "): "
    1178                     + message;
    1179             if (e != null) {
    1180                 return new SAXParseException(error, locator, e);
    1181             }
    1182             else {
    1183                 return new SAXParseException(error, locator);
    1184             }
    1185         }
    1186         log.error("No Locator!");
    1187         if (e != null) {
    1188             return new SAXException(message, e);
    1189         }
    1190         else {
    1191             return new SAXException(message);
    1192         }
    1193     }
    1194
    1195     /***
    1196      * Create a SAX exception which also understands about the location in
    1197      * the digester file where the exception occurs
    1198      *
    1199      * @return the new exception
    1200      */
    1201     protected SAXException createSAXException(Exception e) {
    1202         return createSAXException(e.getMessage(), e);
    1203     }
    1204     /***
    1205      * Create a SAX exception which also understands about the location in
    1206      * the digester file where the exception occurs
    1207      *
    1208      * @return the new exception
    1209      */
    1210     protected SAXException createSAXException(String message) {
    1211         return createSAXException(message, null);
    1212     }
    1213 }