/*
 * 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@ricochet.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.util;

import org.w3c.dom.*;

/**
 * A class which wraps around the DOM {@link org.w3c.dom.Element Element} class, providing some
 * additional functionality.
 *
 * @author Eric J. Bowersox &lt;erbo@ricochet.com&gt;
 * @version $Id$
 * @see org.w3c.dom.Element
 */
public final class DOMElementHelper
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  /** The {@link org.w3c.dom.Element Element} housed by this helper class. */
  private final Element elt;

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

  /**
   * Constructs a new <code>DOMElementHelper</code> to wrap a specific {@link org.w3c.dom.Element Element}.
   *
   * @param elt The <code>Element</code> to be wrapped.
   */
  public DOMElementHelper(Element elt)
  {
    this.elt = elt;

  } // end constructor

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

  /**
   * Returns the content of all text nodes underneath a specified {@link org.w3c.dom.Element Element}, 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().trim();  // return the concatenation
    
  } // end getTextOfElement

  /**
   * Returns the value of the text of the specified {@link org.w3c.dom.Element Element}, expressed as an integer.
   *
   * @param e The <code>Element</code> to extract text from.
   * @return An {@link java.lang.Integer Integer} 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 {@link org.w3c.dom.Element Element} wrapped by this object.
   *
   * @return See above.
   */
  public final Element getElement()
  {
    return elt;

  } // end getElement

  /**
   * Searches for the first sub-element of the wrapped {@link org.w3c.dom.Element Element} 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 = 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

  /**
   * Returns the content of all text nodes underneath the wrapped {@link org.w3c.dom.Element Element}, 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(elt);

  } // end getElementText

  /**
   * Returns the value of the text of the wrapped {@link org.w3c.dom.Element Element}, expressed as an integer.
   *
   * @return An {@link java.lang.Integer Integer} 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(elt);

  } // end getElementInt

  /**
   * Returns the content of all text nodes underneath the first sub-element of the wrapped
   * {@link org.w3c.dom.Element Element}, 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 value of the text underneath the first sub-element of the wrapped
   * {@link org.w3c.dom.Element Element}, with the given name, expressed as an integer.
   *
   * @param name The name of the sub-element to search for.
   * @return An {@link java.lang.Integer Integer} 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

  /**
   * Determines whether the wrapped {@link org.w3c.dom.Element Element} 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 {@link org.w3c.dom.Element Element} 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 !(StringUtil.isStringEmpty(elt.getAttribute(name)));

  } // end hasAttribute

  /**
   * Returns the value of a specified attribute of the wrapped {@link org.w3c.dom.Element Element}, expressed as
   * an integer.
   *
   * @param name Name of the attribute to search for.
   * @return An {@link java.lang.Integer Integer} 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 = elt.getAttribute(name);
    if (StringUtil.isStringEmpty(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 {@link org.w3c.dom.Element Element}, expressed as
   * a {@link java.lang.Boolean Boolean}.
   *
   * @param name Name of the attribute to search for.
   * @return A <code>Boolean</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 a Boolean, returns <code>null</code>.
   */
  public final Boolean getAttributeBoolean(String name)
  {
    String tmp = elt.getAttribute(name);
    if (StringUtil.isBooleanTrue(tmp))
      return Boolean.TRUE;
    else if (StringUtil.isBooleanFalse(tmp))
      return Boolean.FALSE;
    else
      return null;

  } // end getAttributeBoolean

  /**
   * Returns the value of a specified attribute of the wrapped {@link org.w3c.dom.Element Element}, expressed as
   * a {@link java.lang.Boolean Boolean}.
   *
   * @param name Name of the attribute to search for.
   * @param default_val Default value to return if the attribute does not exist.
   * @return A <code>Boolean</code> object containing the value of the specified attribute.  If
   *         the wrapped <code>Element</code> has no such attribute, returns the Boolean equivalent of
   *         <code><i>default_val</i></code>.  If the attribute's value cannot be expressed as a Boolean,
   *         returns <code>null</code>.
   */
  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

} // end DOMElementHelper
