/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002-03 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.util.xml;

import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import com.silverwrist.util.StringUtils;

/**
 * A simple class which is commonly used for the loading of XML documents as configuration files.  Various
 * methods on this object load XML documents as DOM trees and extract portions of the information therein.
 * It is implemented as a Singleton.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 */
public class XMLLoader
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Logger logger = Logger.getLogger(XMLLoader.class);

  private static XMLLoader self = null;   // our singleton instance

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  /**
   * Internal constructor for <CODE>XMLLoader</CODE>.  Only one instance is ever constructed.
   */
  private XMLLoader()
  { // do nothing
  } // end constructor

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Parses an XML document into a DOM tree.
   *
   * @param stm The XML document to be parsed, expressed as an <CODE>InputStream</CODE>.
   * @param namespacing <CODE>true</CODE> if we want this XML document parsed in a namespace-aware fashion,
   *                    <CODE>false</CODE> if not.
   * @return The <CODE>Document</CODE> that results from parsing the XML document.
   * @exception java.io.IOException If there was an I/O error reading from the XML document.
   * @exception com.silverwrist.util.xml.XMLLoaderException If there was a parse error in the XML, or other error
   *            accessing the XML parser.
   */
  public final Document load(InputStream stm, boolean namespacing) throws IOException, XMLLoadException
  {
    try
    { // create a simple DOM parser by using the Java XML parsing API
      DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
      fac.setNamespaceAware(namespacing);
      fac.setValidating(false);
      DocumentBuilder parser = fac.newDocumentBuilder();
      if (parser.isNamespaceAware()!=namespacing)
	throw new XMLLoadException("XML parser namespacing parameter is not consistent");

      // access the config file and parse it into our config data tree
      return parser.parse(stm);

    } // end try
    catch (FactoryConfigurationError fce)
    { // if the document builder factory could not be created
      throw new XMLLoadException("XML parser factory could not be created - " + fce.getMessage());

    } // end catch
    catch (ParserConfigurationException pce)
    { // if the XML parser itself could not be created
      throw new XMLLoadException("XML parser could not be created - " + pce.getMessage(),pce);

    } // end catch
    catch (SAXException se)
    { // if the XML parser choked on our document
      if (se instanceof SAXParseException)
      { // we have a detailed message - make a proper exception
	SAXParseException spe = (SAXParseException)se;
	throw new XMLLoadException("Error in XML data: " + spe.getMessage() + " at line "
			           + spe.getLineNumber() + ", column " + spe.getColumnNumber(),spe);

      } // end if
      else
      { // generic exception - just send up a simple error message
	throw new XMLLoadException("Error in XML data - " + se.getMessage(),se);

      } // end else

    } // end catch

  } // end load

  /**
   * Parses an XML document into a DOM tree.
   *
   * @param stm The XML document to be parsed, expressed as a <CODE>File</CODE> object.
   * @param namespacing <CODE>true</CODE> if we want this XML document parsed in a namespace-aware fashion,
   *                    <CODE>false</CODE> if not.
   * @return The <CODE>Document</CODE> that results from parsing the XML document.
   * @exception java.io.IOException If there was an I/O error reading from the XML document.
   * @exception com.silverwrist.util.xml.XMLLoaderException If there was a parse error in the XML, or other error
   *            accessing the XML parser.
   */
  public final Document load(File file, boolean namespacing) throws IOException, XMLLoadException
  {
    return this.load(new FileInputStream(file),namespacing);

  } // end load

  /**
   * Gets the root element of a parsed XML DOM tree, and verifies that it matches a specific name.
   *
   * @param doc The document to get the root element of.
   * @param expected_name The expected name of the root element.
   * @return The root <CODE>Element</CODE> of the document,
   * @exception com.silverwrist.util.XMLLoadException If the root element of the document does not match the
   *            <CODE>expected_name</CODE>.
   */
  public final Element getRootElement(Document doc, String expected_name) throws XMLLoadException
  {
    Element rc = doc.getDocumentElement();
    if (rc.getTagName().equals(expected_name))
      return rc;  // we're OK
    throw new XMLLoadException("expected <" + expected_name + "/> root element",rc);

  } // end getRootElement

  /**
   * Gets the root element of a parsed XML DOM tree, and verifies that it matches a specific name.
   *
   * @param doc The document to get the root element of.
   * @param expected_namespace The expected namespace for the root element of the document.
   * @param expected_name The expected name of the root element.
   * @return The root <CODE>Element</CODE> of the document,
   * @exception com.silverwrist.util.XMLLoadException If the root element of the document does not match the
   *            <CODE>expected_namespace</CODE> and <CODE>expected_name</CODE>.
   */
  public final Element getRootElementNS(Document doc, String expected_namespace,
                                        String expected_name) throws XMLLoadException
  {
    Element rc = doc.getDocumentElement();
    if (rc.getNamespaceURI().equals(expected_namespace) && rc.getTagName().equals(expected_name))
      return rc;  // we're OK
    throw new XMLLoadException("expected <" + expected_namespace + ":" + expected_name + "/> root element",rc);

  } // end getRootElementNS

  /**
   * Gets the text contained in an <CODE>Element</CODE> wrapped in a
   * {@link com.silverwrist.util.xml.DOMElementHelper DOMElementHelper}.
   *
   * @param h The <CODE>DOMElementHelper</CODE> to extract the text from.
   * @return The text contained in the element.
   * @exception com.silverwrist.util.XMLLoadException If there is no text contained in the element.
   */
  public final String getText(DOMElementHelper h) throws XMLLoadException
  {
    String rc = h.getElementText();
    if (rc!=null)
      return rc;  // we're OK
    throw new XMLLoadException("no text found under element",h.getElement());

  } // end getText

  /**
   * Gets the text contained in an <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to extract the text from.
   * @return The text contained in the element.
   * @exception com.silverwrist.util.XMLLoadException If there is no text contained in the element.
   */
  public final String getText(Element elt) throws XMLLoadException
  {
    return getText(new DOMElementHelper(elt));

  } // end getText

  /**
   * Gets the text contained in a named sub-element of an <CODE>Element</CODE> wrapped in a
   * {@link com.silverwrist.util.xml.DOMElementHelper DOMElementHelper}.
   *
   * @param h The <CODE>DOMElementHelper</CODE> to extract the text from.
   * @param elt_name The name of the sub-element to look for text in.
   * @return The text contained in the sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found, or there is no text
   *            contained in the sub-element.
   */
  public final String getSubElementText(DOMElementHelper h, String elt_name) throws XMLLoadException
  {
    String rc = h.getSubElementText(elt_name);
    if (rc!=null)
      return rc;  // we're OK
    throw new XMLLoadException("sublement <" + elt_name + "/> not found",h.getElement());

  } // end getSubElementText

  /**
   * Gets the text contained in a named sub-element of an <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to extract the text from.
   * @param subelt_name The name of the sub-element to look for text in.
   * @return The text contained in the sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found, or there is no text
   *            contained in the sub-element.
   */
  public final String getSubElementText(Element elt, String subelt_name) throws XMLLoadException
  {
    return getSubElementText(new DOMElementHelper(elt),subelt_name);

  } // end getSubElementText

  /**
   * Gets the text contained in a named sub-element of an <CODE>Element</CODE> wrapped in a
   * {@link com.silverwrist.util.xml.DOMElementHelper DOMElementHelper}.
   *
   * @param h The <CODE>DOMElementHelper</CODE> to extract the text from.
   * @param namespace The namespace URI of the sub-element to look for text in.
   * @param elt_name The name of the sub-element to look for text in.
   * @return The text contained in the sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found, or there is no text
   *            contained in the sub-element.
   */
  public final String getSubElementTextNS(DOMElementHelper h, String namespace, String elt_name)
      throws XMLLoadException
  {
    String rc = h.getSubElementTextNS(namespace,elt_name);
    if (rc!=null)
      return rc;  // we're OK
    throw new XMLLoadException("sublement <" + namespace + ":" + elt_name + "/> not found",h.getElement());

  } // end getSubElementTextNS

  /**
   * Gets the text contained in a named sub-element of an <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to extract the text from.
   * @param namespace The namespace URI of the sub-element to look for text in.
   * @param subelt_name The name of the sub-element to look for text in.
   * @return The text contained in the sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found, or there is no text
   *            contained in the sub-element.
   */
  public final String getSubElementTextNS(Element elt, String namespace, String subelt_name)
      throws XMLLoadException
  {
    return getSubElementTextNS(new DOMElementHelper(elt),namespace,subelt_name);

  } // end getSubElementTextNS

  /**
   * Gets a named sub-element of an <CODE>Element</CODE> wrapped in a
   * {@link com.silverwrist.util.xml.DOMElementHelper DOMElementHelper}.
   *
   * @param h The <CODE>DOMElementHelper</CODE> to extract the sub-element from.
   * @param name The name of the sub-element to look for.
   * @return The sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found.
   */
  public final Element getSubElement(DOMElementHelper h, String name) throws XMLLoadException
  {
    Element rc = h.getSubElement(name);
    if (rc!=null)
      return rc;  // we're OK
    throw new XMLLoadException("sublement <" + name + "/> not found",h.getElement());

  } // end getSubElement

  /**
   * Gets a named sub-element of an <CODE>Element</CODE>.
   *
   * @param sect The <CODE>Element</CODE> to extract the sub-element from.
   * @param name The name of the sub-element to look for.
   * @return The sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found.
   */
  public final Element getSubElement(Element sect, String name) throws XMLLoadException
  {
    return getSubElement(new DOMElementHelper(sect),name);

  } // end getSubElement

  /**
   * Gets a named sub-element of an <CODE>Element</CODE> wrapped in a
   * {@link com.silverwrist.util.xml.DOMElementHelper DOMElementHelper}.
   *
   * @param h The <CODE>DOMElementHelper</CODE> to extract the sub-element from.
   * @param namespace The namespace URI of the sub-element to look for.
   * @param name The name of the sub-element to look for.
   * @return The sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found.
   */
  public final Element getSubElementNS(DOMElementHelper h, String namespace, String name)
      throws XMLLoadException
  {
    Element rc = h.getSubElementNS(namespace,name);
    if (rc!=null)
      return rc;  // we're OK
    throw new XMLLoadException("sublement <" + namespace + ":" + name + "/> not found",h.getElement());

  } // end getSubElementNS

  /**
   * Gets a named sub-element of an <CODE>Element</CODE>.
   *
   * @param sect The <CODE>Element</CODE> to extract the sub-element from.
   * @param namespace The namespace URI of the sub-element to look for.
   * @param name The name of the sub-element to look for.
   * @return The sub-element.
   * @exception com.silverwrist.util.XMLLoadException If the sub-element is not found.
   */
  public final Element getSubElementNS(Element sect, String namespace, String name) throws XMLLoadException
  {
    return getSubElementNS(new DOMElementHelper(sect),namespace,name);

  } // end getSubElementNS

  /**
   * Returns all sub-elements of an <CODE>Element</CODE> element that have a specific name.
   *
   * @param sect The <CODE>Element</CODE> to extract the sub-elements from.
   * @param name The name of the sub-elements to look for.
   * @return A <CODE>java.util.List</CODE> containing all the sub-elements of the given <CODE>Element</CODE> with
   *         the specified name.  If there are no matching sub-elements, an empty list is returned.
   */
  public final List getMatchingSubElements(Element sect, String name)
  {
    ArrayList rc = new ArrayList();
    NodeList nl = sect.getChildNodes();
    for (int i=0; i<nl.getLength(); i++)
    { // look for an ELEMENT_NODE node matching the desired name
      Node t = nl.item(i);
      if ((t.getNodeType()==Node.ELEMENT_NODE) && t.getNodeName().equals(name))
	rc.add((Element)t);

    } // end for

    if (rc.isEmpty())
      return Collections.EMPTY_LIST;
    rc.trimToSize();
    return Collections.unmodifiableList(rc);

  } // end getMatchingSubElements

  /**
   * Returns all sub-elements of an <CODE>Element</CODE> element that have a specific name.
   *
   * @param sect The <CODE>Element</CODE> to extract the sub-elements from.
   * @param namespace The namespace URI of the sub-elements to look for.
   * @param name The name of the sub-elements to look for.
   * @return A <CODE>java.util.List</CODE> containing all the sub-elements of the given <CODE>Element</CODE> with
   *         the specified namespace and name.  If there are no matching sub-elements, an empty list is returned.
   */
  public final List getMatchingSubElementsNS(Element sect, String namespace, String name)
  {
    ArrayList rc = new ArrayList();
    NodeList nl = sect.getChildNodes();
    for (int i=0; i<nl.getLength(); i++)
    { // look for an ELEMENT_NODE node matching the desired name
      Node t = nl.item(i);
      if (   (t.getNodeType()==Node.ELEMENT_NODE) && t.getNamespaceURI().equals(namespace)
          && t.getNodeName().equals(name))
	rc.add((Element)t);

    } // end for

    if (rc.isEmpty())
      return Collections.EMPTY_LIST;
    rc.trimToSize();
    return Collections.unmodifiableList(rc);

  } // end getMatchingSubElementsNS

  /**
   * Checks to make sure that the name of a given node is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param n The <CODE>Node</CODE> to test the name of.
   * @param name The name to test the node against.
   * @param enclosing_sect The <CODE>Element</CODE> that contains this node as a child, used in constructing
   *                       the exception if the test fails.
   * @exception com.silverwrist.util.xml.XMLLoadException If the name of the node does not match.
   */
  public final void verifyNodeName(Node n, String name, Element enclosing_sect) throws XMLLoadException
  {
    if (n.getNodeName().equals(name))
      return;
    throw new XMLLoadException("node <" + n.getNodeName() + "/> should be <" + name + "/>",enclosing_sect);
    
  } // end verifyNodeName

  /**
   * Checks to make sure that the name of a given node is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param n The <CODE>Node</CODE> to test the name of.
   * @param name The name to test the node against.
   * @exception com.silverwrist.util.xml.XMLLoadException If the name of the node does not match.
   */
  public final void verifyNodeName(Node n, String name) throws XMLLoadException
  {
    verifyNodeName(n,name,(Element)(n.getParentNode()));

  } // end verifyNodeName

  /**
   * Checks to make sure that the name of a given node is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param n The <CODE>Node</CODE> to test the name of.
   * @param namespace The namespace URI to test the node against.
   * @param name The name to test the node against.
   * @param enclosing_sect The <CODE>Element</CODE> that contains this node as a child, used in constructing
   *                       the exception if the test fails.
   * @exception com.silverwrist.util.xml.XMLLoadException If the name of the node does not match.
   */
  public final void verifyNodeNameNS(Node n, String namespace, String name, Element enclosing_sect)
      throws XMLLoadException
  {
    if (n.getNamespaceURI().equals(namespace) && n.getNodeName().equals(name))
      return;
    throw new XMLLoadException("node <" + n.getNamespaceURI() + ":" + n.getNodeName() + "/> should be <"
                               + namespace + ":" + name + "/>",enclosing_sect);
    
  } // end verifyNodeNameNS

  /**
   * Checks to make sure that the name of a given node is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param n The <CODE>Node</CODE> to test the name of.
   * @param namespace The namespace URI to test the node against.
   * @param name The name to test the node against.
   * @exception com.silverwrist.util.xml.XMLLoadException If the name of the node does not match.
   */
  public final void verifyNodeNameNS(Node n, String namespace, String name) throws XMLLoadException
  {
    verifyNodeNameNS(n,namespace,name,(Element)(n.getParentNode()));

  } // end verifyNodeNameNS

  /**
   * Checks to make sure that the name of a given tag element is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param tag The <CODE>Element</CODE> to test the name of.
   * @param name The name to test the element against.
   * @exception com.silverwrist.util.xml.XMLLoadException If the name of the element does not match.
   */
  public final void verifyTagName(Element tag, String name) throws XMLLoadException
  {
    if (tag.getTagName().equals(name))
      return;
    throw new XMLLoadException("expected <" + name + "/> element",tag);

  } // end verifyTagName

  /**
   * Checks to make sure that the name of a given tag element is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param tag The <CODE>Element</CODE> to test the name of.
   * @param namespace The namespace URI to test the element against.
   * @param name The name to test the element against.
   * @exception com.silverwrist.util.xml.XMLLoadException If the name of the element does not match.
   */
  public final void verifyTagNameNS(Element tag, String namespace, String name) throws XMLLoadException
  {
    if (tag.getNamespaceURI().equals(namespace) && tag.getTagName().equals(name))
      return;
    throw new XMLLoadException("expected <" + namespace + ":" + name + "/> element",tag);

  } // end verifyTagNameNS

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param attr_name The name of the attribute to retrieve.
   * @return The attribute value.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute did not exist on the element.
   */
  public final String getAttribute(Element elt, String attr_name) throws XMLLoadException
  {
    String rc = elt.getAttribute(attr_name);
    if (!(StringUtils.isEmpty(rc)))
      return rc;
    throw new XMLLoadException("no " + attr_name + "= attribute found",elt);

  } // end getAttribute

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param attr_name The name of the attribute to retrieve.
   * @param default_val The default value of the attribute.
   * @return The attribute value, or the value of <CODE>default_val</CODE> if the attribute didn't exist.
   */
  public final String getAttribute(Element elt, String attr_name, String default_val)
  {
    String rc = elt.getAttribute(attr_name);
    return (StringUtils.isEmpty(rc) ? default_val : rc);

  } // end getAttribute

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param namespace The namespace URI of the attribute to retrieve.
   * @param attr_name The name of the attribute to retrieve.
   * @return The attribute value.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute did not exist on the element.
   */
  public final String getAttributeNS(Element elt, String namespace, String attr_name) throws XMLLoadException
  {
    String rc = elt.getAttributeNS(namespace,attr_name);
    if (!(StringUtils.isEmpty(rc)))
      return rc;
    throw new XMLLoadException("no " + namespace + ":" + attr_name + "= attribute found",elt);

  } // end getAttributeNS

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param namespace The namespace URI of the attribute to retrieve.
   * @param attr_name The name of the attribute to retrieve.
   * @param default_val The default value of the attribute.
   * @return The attribute value, or the value of <CODE>default_val</CODE> if the attribute didn't exist.
   */
  public final String getAttributeNS(Element elt, String namespace, String attr_name, String default_val)
  {
    String rc = elt.getAttributeNS(namespace,attr_name);
    return (StringUtils.isEmpty(rc) ? default_val : rc);

  } // end getAttributeNS

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>, expressed as an integer.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param attr_name The name of the attribute to retrieve.
   * @return The attribute value.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute did not exist on the element, or
   *            it could not be parsed as an integer.
   */
  public final int getAttributeInt(Element elt, String attr_name) throws XMLLoadException
  {
    String tmp = elt.getAttribute(attr_name);
    if (StringUtils.isEmpty(tmp))
    { // the attribute is not present
      throw new XMLLoadException("no " + attr_name + "= attribute found",elt);

    } // end if

    try
    { // parse out the integer value
      return Integer.parseInt(tmp.trim());

    } // end try
    catch (NumberFormatException nfe)
    { // but it's not a valid integer - throw something else!
      throw new XMLLoadException(attr_name + "= attribute value is not a valid integer",elt);

    } // end catch

  } // end getAttributeInt

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>, expressed as an integer.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param attr_name The name of the attribute to retrieve.
   * @param default_val The default value of the attribute.
   * @return The attribute value, or the value of <CODE>default_val</CODE> if the attribute didn't exist.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute could not be parsed as an integer.
   */
  public final int getAttributeInt(Element elt, String attr_name, int default_val) throws XMLLoadException
  {
    String tmp = elt.getAttribute(attr_name);
    if (StringUtils.isEmpty(tmp))
      return default_val;  // the attribute is not present

    try
    { // parse out the integer value
      return Integer.parseInt(tmp.trim());

    } // end try
    catch (NumberFormatException nfe)
    { // but it's not a valid integer - throw something else!
      throw new XMLLoadException(attr_name + "= attribute value is not a valid integer",elt);

    } // end catch

  } // end getAttributeInt

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>, expressed as an integer.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param namespace The namespace URI of the attribute to retrieve.
   * @param attr_name The name of the attribute to retrieve.
   * @return The attribute value.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute did not exist on the element, or
   *            it could not be parsed as an integer.
   */
  public final int getAttributeIntNS(Element elt, String namespace, String attr_name) throws XMLLoadException
  {
    String tmp = elt.getAttributeNS(namespace,attr_name);
    if (StringUtils.isEmpty(tmp))
    { // the attribute is not present
      throw new XMLLoadException("no " + namespace + ":" + attr_name + "= attribute found",elt);

    } // end if

    try
    { // parse out the integer value
      return Integer.parseInt(tmp.trim());

    } // end try
    catch (NumberFormatException nfe)
    { // but it's not a valid integer - throw something else!
      throw new XMLLoadException(namespace + ":" + attr_name + "= attribute value is not a valid integer",elt);

    } // end catch

  } // end getAttributeIntNS

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>, expressed as an integer.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param namespace The namespace URI of the attribute to retrieve.
   * @param attr_name The name of the attribute to retrieve.
   * @param default_val The default value of the attribute.
   * @return The attribute value, or the value of <CODE>default_val</CODE> if the attribute didn't exist.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute could not be parsed as an integer.
   */
  public final int getAttributeIntNS(Element elt, String namespace, String attr_name, int default_val)
      throws XMLLoadException
  {
    String tmp = elt.getAttributeNS(namespace,attr_name);
    if (StringUtils.isEmpty(tmp))
      return default_val;  // the attribute is not present

    try
    { // parse out the integer value
      return Integer.parseInt(tmp.trim());

    } // end try
    catch (NumberFormatException nfe)
    { // but it's not a valid integer - throw something else!
      throw new XMLLoadException(namespace + ":" + attr_name + "= attribute value is not a valid integer",elt);

    } // end catch

  } // end getAttributeIntNS

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>, expressed as a Boolean.  Uses the Boolean string
   * methods on {@link com.silverwrist.util.StringUtils StringUtils} to determine the state of the attribute value.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param attr_name The name of the attribute to retrieve.
   * @return The attribute value.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute did not exist on the element, or
   *            it could not be parsed as a Boolean.
   * @see com.silverwrist.util.StringUtils#isBooleanTrue(java.lang.String)
   * @see com.silverwrist.util.StringUtils#isBooleanFalse(java.lang.String)
   */
  public final boolean getAttributeBoolean(Element elt, String attr_name)
      throws XMLLoadException
  {
    String tmp = elt.getAttribute(attr_name);
    if (StringUtils.isEmpty(tmp))
      throw new XMLLoadException("no " + attr_name + "= attribute found",elt);
    if (StringUtils.isBooleanTrue(tmp))
      return true;
    if (StringUtils.isBooleanFalse(tmp))
      return false;
    throw new XMLLoadException(attr_name + "= attribute value is not a valid Boolean",elt);

  } // end getAttributeBoolean

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>, expressed as a Boolean.  Uses the Boolean string
   * methods on {@link com.silverwrist.util.StringUtils StringUtils} to determine the state of the attribute value.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param attr_name The name of the attribute to retrieve.
   * @param default_val The default value of the attribute.
   * @return The attribute value, or the value of <CODE>default_val</CODE> if the attribute didn't exist.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute could not be parsed as a Boolean.
   * @see com.silverwrist.util.StringUtils#isBooleanTrue(java.lang.String)
   * @see com.silverwrist.util.StringUtils#isBooleanFalse(java.lang.String)
   */
  public final boolean getAttributeBoolean(Element elt, String attr_name, boolean default_val)
      throws XMLLoadException
  {
    String tmp = elt.getAttribute(attr_name);
    if (StringUtils.isEmpty(tmp))
      return default_val;
    if (StringUtils.isBooleanTrue(tmp))
      return true;
    if (StringUtils.isBooleanFalse(tmp))
      return false;
    throw new XMLLoadException(attr_name + "= attribute value is not a valid Boolean",elt);

  } // end getAttributeBoolean

  /**
   * Gets the value of an attribute on an <CODE>Element</CODE>, expressed as a Boolean.  Uses the Boolean string
   * methods on {@link com.silverwrist.util.StringUtils StringUtils} to determine the state of the attribute value.
   *
   * @param elt The <CODE>Element</CODE> to retrieve the attribute from.
   * @param namespace The namespace URI of the attribute to retrieve.
   * @param attr_name The name of the attribute to retrieve.
   * @param default_val The default value of the attribute.
   * @return The attribute value, or the value of <CODE>default_val</CODE> if the attribute didn't exist.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute could not be parsed as a Boolean.
   * @see com.silverwrist.util.StringUtils#isBooleanTrue(java.lang.String)
   * @see com.silverwrist.util.StringUtils#isBooleanFalse(java.lang.String)
   */
  public final boolean getAttributeBooleanNS(Element elt, String namespace, String attr_name,
                                             boolean default_val) throws XMLLoadException
  {
    String tmp = elt.getAttributeNS(namespace,attr_name);
    if (StringUtils.isEmpty(tmp))
      return default_val;
    if (StringUtils.isBooleanTrue(tmp))
      return true;
    if (StringUtils.isBooleanFalse(tmp))
      return false;
    throw new XMLLoadException(namespace + ":" + attr_name + "= attribute value is not a valid Boolean",elt);

  } // end getAttributeBooleanNS

  /**
   * Checks to make sure that the value of a given attribute is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param elt The <CODE>Element</CODE> to test the attribute of.
   * @param attr_name The name of the attribute to test.
   * @param attr_value The value to test the attribute against.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute does not exist, or its value does not match.
   */
  public final void verifyAttributeValue(Element elt, String attr_name, String attr_value)
      throws XMLLoadException
  {
    String tmp = this.getAttribute(elt,attr_name);
    if (!(tmp.equals(attr_value)))
      throw new XMLLoadException("attribute " + attr_name + "= should be '" + attr_value + "', but is '"
			         + tmp + "'",elt);

  } // end verifyAttributeValue

  /**
   * Checks to make sure that the value of a given attribute is what we expect, and throws an
   * {@link com.silverwrist.util.xml.XMLLoadException XMLLoadException} if it isn't.
   *
   * @param elt The <CODE>Element</CODE> to test the attribute of.
   * @param namespace The namespace URI of the attribute to test.
   * @param attr_name The name of the attribute to test.
   * @param attr_value The value to test the attribute against.
   * @exception com.silverwrist.util.xml.XMLLoadException If the attribute does not exist, or its value does not match.
   */
  public final void verifyAttributeValueNS(Element elt, String namespace, String attr_name, String attr_value)
      throws XMLLoadException
  {
    String tmp = this.getAttributeNS(elt,namespace,attr_name);
    if (!(tmp.equals(attr_value)))
      throw new XMLLoadException("attribute " + namespace + ":" + attr_name + "= should be '" + attr_value
                                 + "', but is '" + tmp + "'",elt);

  } // end verifyAttributeValueNS

  /*--------------------------------------------------------------------------------
   * External static operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Gets the Singleton instance of the <CODE>XMLLoader</CODE> object.
   *
   * @return The Singleton instance of the <CODE>XMLLoader</CODE> object.
   */
  public static final synchronized XMLLoader get()
  {
    if (self==null)
      self = new XMLLoader();
    return self;

  } // end get

} // end class XMLLoader
