/*
 * 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 org.w3c.dom.*;
import com.silverwrist.util.StringUtils;

/**
 * A class which wraps around the DOM <CODE>Element</CODE> class, providing some
 * additional functionality.  Written to DOM Level 2.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 * @see org.w3c.dom.Element
 */
public final class DOMElementHelper
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private Element m_elt;  // element housed by this helper class

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

  /**
   * Constructs a new <CODE>DOMElementHelper</CODE> to wrap a specific <CODE>Element</CODE>.
   *
   * @param elt The <CODE>Element</CODE> to be wrapped.
   */
  public DOMElementHelper(Element elt)
  {
    m_elt = elt;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal static operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns the content of all text nodes underneath a specified <CODE>Element</CODE>, concatenated
   * together into a single string.
   *
   * @param e The <CODE>Element</CODE> to extract text from.
   * @return The text content under this <CODE>Element</CODE> node.  If the specified <CODE>Element</CODE>
   *         has no text nodes underneath it, returns <CODE>null.</CODE>
   */
  private static final String getTextOfElement(Element e)
  {
    NodeList kids = e.getChildNodes();
    if (kids==null)
      return null;  // no text?

    StringBuffer b = null;
    for (int i=0; i<kids.getLength(); i++)
    { // look for an ELEMENT_NODE node matching the desired name
      Node t = kids.item(i);
      if ((t.getNodeType()==Node.TEXT_NODE) || (t.getNodeType()==Node.CDATA_SECTION_NODE))
      { // append to the string under construction
	if (b==null)
	  b = new StringBuffer();
	else
	  b.append(' ');
	b.append(t.getNodeValue());

      } // end if

    } // end for

    if (b==null)
      return null;  // no TEXT nodes
    else
      return b.toString();  // return the concatenation
    
  } // end getTextOfElement

  /**
   * Returns the value of the text of the specified <CODE>Element</CODE>, expressed as an integer.
   *
   * @param e The <CODE>Element</CODE> to extract text from.
   * @return An <CODE>Integer</CODE> object containing the value of the specified element.  If
   *         the <CODE>Element</CODE> has no text, or if the text cannot be expressed as an integer,
   *         returns <CODE>null</CODE>.
   */
  private static final Integer getIntegerFromElement(Element e)
  {
    try
    { // extract the text and create an Integer around it
      String s = getTextOfElement(e);
      return ((s==null) ? null : new Integer(s.trim()));

    } // end try
    catch (NumberFormatException nfe)
    { // value cannot be parsed as an integer
      return null;

    } // end catch

  } // end getIntegerFromElement

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

  /**
   * Returns the <CODE>Element</CODE> wrapped by this object.
   *
   * @return See above.
   */
  public final Element getElement()
  {
    return m_elt;

  } // end getElement

  /**
   * Searches for the first sub-element of the wrapped <CODE>Element</CODE> with the given name.
   *
   * @param name Name of the sub-element to search for.
   * @return The first sub-element of the wrapped <CODE>Element</CODE> with the specified name.
   *         If the <CODE>Element</CODE> has no child <CODE>Elements</CODE> with the given name,
   *         the method returns <CODE>null</CODE>.
   */
  public final Element getSubElement(String name)
  {
    NodeList kids = m_elt.getChildNodes();
    if (kids==null)
      return null;  // no children?
    for (int i=0; i<kids.getLength(); i++)
    { // look for an ELEMENT_NODE node matching the desired name
      Node t = kids.item(i);
      if ((t.getNodeType()==Node.ELEMENT_NODE) && (t.getNodeName().equals(name)))
	return (Element)t;

    } // end for

    return null;  // not found

  } // end getSubElement

  /**
   * Searches for the first sub-element of the wrapped <CODE>Element</CODE> with the given name.
   *
   * @param namespaceURI Namespace URI for the sub-element to search for.
   * @param name Name of the sub-element to search for.
   * @return The first sub-element of the wrapped <CODE>Element</CODE> with the specified URI/name.
   *         If the <CODE>Element</CODE> has no child <CODE>Elements</CODE> with the given URI/name,
   *         the method returns <CODE>null</CODE>.
   */
  public final Element getSubElementNS(String namespaceURI, String name)
  {
    NodeList kids = m_elt.getChildNodes();
    if (kids==null)
      return null;  // no children?
    for (int i=0; i<kids.getLength(); i++)
    { // look for an ELEMENT_NODE node matching the desired name
      Node t = kids.item(i);
      if (   (t.getNodeType()==Node.ELEMENT_NODE) && t.getNamespaceURI().equals(namespaceURI)
          && t.getNodeName().equals(name))
	return (Element)t;

    } // end for

    return null;  // not found

  } // end getSubElementNS

  /**
   * Returns the content of all text nodes underneath the wrapped <CODE>Element</CODE>, concatenated
   * together into a single string.
   *
   * @return The text content under the wrapped <CODE>Element</CODE> node.  If the wrapped <CODE>Element</CODE>
   *         has not text nodes underneath it, returns <CODE>null.</CODE>
   */
  public final String getElementText()
  {
    return getTextOfElement(m_elt);

  } // end getElementText

  /**
   * Returns the value of the text of the wrapped <CODE>Element</CODE>, expressed as an integer.
   *
   * @return An <CODE>Integer</CODE> object containing the value of the wrapped element.  If
   *         the <CODE>Element</CODE> has no text, or if the text cannot be expressed as an integer,
   *         returns <CODE>null</CODE>.
   */
  public final Integer getElementInt()
  {
    return getIntegerFromElement(m_elt);

  } // end getElementInt

  /**
   * Returns the content of all text nodes underneath the first sub-element of the wrapped
   * <CODE>Element</CODE>, with the given name, concatenated together into a single string.
   *
   * @param name The name of the sub-element to search for.
   * @return The text content under the specified sub-element of the wrapped <CODE>Element</CODE> node.
   *         If the wrapped <CODE>Element</CODE> does not have a sub-element with the given name, or
   *         that sub-element has no text nodes underneath it, returns <CODE>null.</CODE>
   */
  public final String getSubElementText(String name)
  {
    Element se = getSubElement(name);
    return ((se==null) ? null : getTextOfElement(se));

  } // end getSubElementText

  /**
   * Returns the content of all text nodes underneath the first sub-element of the wrapped
   * <CODE>Element</CODE>, with the given name, concatenated together into a single string.
   *
   * @param namespaceURI Namespace URI for the sub-element to search for.
   * @param name The name of the sub-element to search for.
   * @return The text content under the specified sub-element of the wrapped <CODE>Element</CODE> node.
   *         If the wrapped <CODE>Element</CODE> does not have a sub-element with the given URI/name, or
   *         that sub-element has no text nodes underneath it, returns <CODE>null.</CODE>
   */
  public final String getSubElementTextNS(String namespaceURI, String name)
  {
    Element se = getSubElementNS(namespaceURI,name);
    return ((se==null) ? null : getTextOfElement(se));

  } // end getSubElementTextNS

  /**
   * Returns the value of the text underneath the first sub-element of the wrapped
   * <CODE>Element</CODE>, with the given name, expressed as an integer.
   *
   * @param name The name of the sub-element to search for.
   * @return An <CODE>Integer</CODE> object containing the value of the specified element.  If
   *         the wrapped <CODE>Element</CODE> does not have a sub-element with the given name, or that
   *         sub-element has no text, or if the text cannot be expressed as an integer, returns
   *         <CODE>null</CODE>.
   */
  public final Integer getSubElementInt(String name)
  {
    Element se = getSubElement(name);
    return ((se==null) ? null : getIntegerFromElement(se));

  } // end getSubElementInt

  /**
   * Returns the value of the text underneath the first sub-element of the wrapped
   * <CODE>Element</CODE>, with the given name, expressed as an integer.
   *
   * @param namespaceURI Namespace URI for the sub-element to search for.
   * @param name The name of the sub-element to search for.
   * @return An <CODE>Integer</CODE> object containing the value of the specified element.  If
   *         the wrapped <CODE>Element</CODE> does not have a sub-element with the given URI/name, or that
   *         sub-element has no text, or if the text cannot be expressed as an integer, returns
   *         <CODE>null</CODE>.
   */
  public final Integer getSubElementIntNS(String namespaceURI, String name)
  {
    Element se = getSubElementNS(namespaceURI,name);
    return ((se==null) ? null : getIntegerFromElement(se));

  } // end getSubElementIntNS

  /**
   * Determines whether the wrapped <CODE>Element</CODE> has a sub-element with the given name.
   *
   * @param name Name of the sub-element to search for.
   * @return <CODE>true</CODE> if the wrapped <CODE>Element</CODE> has a sub-element with the
   *         specified name, <CODE>false</CODE> if not.
   */
  public final boolean hasChildElement(String name)
  {
    return (getSubElement(name)!=null);

  } // end hasChildElement

  /**
   * Determines whether the wrapped <CODE>Element</CODE> has a sub-element with the given name.
   *
   * @param namespaceURI Namespace URI for the sub-element to search for.
   * @param name Name of the sub-element to search for.
   * @return <CODE>true</CODE> if the wrapped <CODE>Element</CODE> has a sub-element with the
   *         specified name, <CODE>false</CODE> if not.
   */
  public final boolean hasChildElementNS(String namespaceURI, String name)
  {
    return (getSubElementNS(namespaceURI,name)!=null);

  } // end hasChildElementNS

  /**
   * Determines whether the wrapped <CODE>Element</CODE> has an attribute with the given name.
   *
   * @param name Name of the attribute to search for.
   * @return <CODE>true</CODE> if the wrapped <CODE>Element</CODE> has an attribute with the
   *         specified name, <CODE>false</CODE> if not.
   */
  public final boolean hasAttribute(String name)
  {
    return m_elt.hasAttribute(name);

  } // end hasAttribute

  /**
   * Determines whether the wrapped <CODE>Element</CODE> has an attribute with the given name.
   *
   * @param namespaceURI Namespace URI for the attribute to search for.
   * @param name Name of the attribute to search for.
   * @return <CODE>true</CODE> if the wrapped <CODE>Element</CODE> has an attribute with the
   *         specified URI/name, <CODE>false</CODE> if not.
   */
  public final boolean hasAttributeNS(String namespaceURI, String name)
  {
    return m_elt.hasAttributeNS(namespaceURI,name);

  } // end hasAttributeNS

  /**
   * Returns the value of a specified attribute of the wrapped <CODE>Element</CODE>, expressed as
   * an integer.
   *
   * @param name Name of the attribute to search for.
   * @return An <CODE>Integer</CODE> object containing the value of the specified attribute.  If
   *         the wrapped <CODE>Element</CODE> has no such attribute, or if the attribute's value
   *         cannot be expressed as an integer, returns <CODE>null</CODE>.
   */
  public final Integer getAttributeInt(String name)
  {
    String tmp = m_elt.getAttribute(name);
    if (StringUtils.isEmpty(tmp))
      return null;
    try
    { // convert to an Integer
      return new Integer(tmp.trim());

    } // end try
    catch (NumberFormatException nfe)
    { // return a null value on error
      return null;

    } // end catch

  } // end getAttributeInt

  /**
   * Returns the value of a specified attribute of the wrapped <CODE>Element</CODE>, expressed as
   * an integer.
   *
   * @param namespaceURI Namespace URI for the attribute to search for.
   * @param name Name of the attribute to search for.
   * @return An <CODE>Integer</CODE> object containing the value of the specified attribute.  If
   *         the wrapped <CODE>Element</CODE> has no such attribute, or if the attribute's value
   *         cannot be expressed as an integer, returns <CODE>null</CODE>.
   */
  public final Integer getAttributeIntNS(String namespaceURI, String name)
  {
    String tmp = m_elt.getAttributeNS(namespaceURI,name);
    if (StringUtils.isEmpty(tmp))
      return null;
    try
    { // convert to an Integer
      return new Integer(tmp.trim());

    } // end try
    catch (NumberFormatException nfe)
    { // return a null value on error
      return null;

    } // end catch

  } // end getAttributeInt

  /**
   * Returns the value of a specified attribute of the wrapped <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 name Name of the attribute to search for.
   * @return <CODE>Boolean.TRUE</CODE> if the attribute value represents a "true" Boolean value;
   *         <CODE>Boolean.FALSE</CODE> if the attribute value represents a "false" Boolean value; <CODE>null</CODE>
   *         otherwise.
   * @see com.silverwrist.util.StringUtils#isBooleanTrue(java.lang.String)
   * @see com.silverwrist.util.StringUtils#isBooleanFalse(java.lang.String)
   */
  public final Boolean getAttributeBoolean(String name)
  {
    String tmp = m_elt.getAttribute(name);
    if (StringUtils.isBooleanTrue(tmp))
      return Boolean.TRUE;
    else if (StringUtils.isBooleanFalse(tmp))
      return Boolean.FALSE;
    else
      return null;

  } // end getAttributeBoolean

  /**
   * Returns the value of a specified attribute of the wrapped <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 namespaceURI Namespace URI for the attribute to search for.
   * @param name Name of the attribute to search for.
   * @return <CODE>Boolean.TRUE</CODE> if the attribute value represents a "true" Boolean value;
   *         <CODE>Boolean.FALSE</CODE> if the attribute value represents a "false" Boolean value; <CODE>null</CODE>
   *         otherwise.
   * @see com.silverwrist.util.StringUtils#isBooleanTrue(java.lang.String)
   * @see com.silverwrist.util.StringUtils#isBooleanFalse(java.lang.String)
   */
  public final Boolean getAttributeBooleanNS(String namespaceURI, String name)
  {
    String tmp = m_elt.getAttributeNS(namespaceURI,name);
    if (StringUtils.isBooleanTrue(tmp))
      return Boolean.TRUE;
    else if (StringUtils.isBooleanFalse(tmp))
      return Boolean.FALSE;
    else
      return null;

  } // end getAttributeBooleanNS

  /**
   * Returns the value of a specified attribute of the wrapped <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 name Name of the attribute to search for.
   * @param default_val Default value for the attribute.
   * @return <CODE>Boolean.TRUE</CODE> if the attribute value represents a "true" Boolean value;
   *         <CODE>Boolean.FALSE</CODE> if the attribute value represents a "false" Boolean value; the
   *         <CODE>Boolean</CODE> object corresponding to <CODE>default_val</CODE> if the attribute is missing;
   *         <CODE>null</CODE> otherwise.
   * @see com.silverwrist.util.StringUtils#isBooleanTrue(java.lang.String)
   * @see com.silverwrist.util.StringUtils#isBooleanFalse(java.lang.String)
   */
  public final Boolean getAttributeBoolean(String name, boolean default_val)
  {
    if (this.hasAttribute(name))
      return this.getAttributeBoolean(name);
    else
      return (default_val ? Boolean.TRUE : Boolean.FALSE);

  } // end getAttributeBoolean

  /**
   * Returns the value of a specified attribute of the wrapped <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 namespaceURI Namespace URI for the attribute to search for.
   * @param name Name of the attribute to search for.
   * @param default_val Default value for the attribute.
   * @return <CODE>Boolean.TRUE</CODE> if the attribute value represents a "true" Boolean value;
   *         <CODE>Boolean.FALSE</CODE> if the attribute value represents a "false" Boolean value; the
   *         <CODE>Boolean</CODE> object corresponding to <CODE>default_val</CODE> if the attribute is missing;
   *         <CODE>null</CODE> otherwise.
   * @see com.silverwrist.util.StringUtils#isBooleanTrue(java.lang.String)
   * @see com.silverwrist.util.StringUtils#isBooleanFalse(java.lang.String)
   */
  public final Boolean getAttributeBooleanNS(String namespaceURI, String name, boolean default_val)
  {
    if (this.hasAttributeNS(namespaceURI,name))
      return this.getAttributeBooleanNS(namespaceURI,name);
    else
      return (default_val ? Boolean.TRUE : Boolean.FALSE);

  } // end getAttributeBooleanNS

} // end class DOMElementHelper
