/*
License: GNU Lesser General Public License (http://www.gnu.org/licenses/lgpl.html)
Copyright (C) 2005 tagnetic.org. Please do not remove this copyright/license comment.
*/

//*************************************************************************
//Start cross-browser extras <view:if><view:test>useBrowserExtras</view:test>
//*************************************************************************
/**
 * Defines a DOMParser class that is usable in Mozilla, Safari, IE and Opera.
 * Taken directly from: http://erik.eae.net/archives/2005/07/03/20.19.18/
 * 
 * Example usage:
 *   var parser = new DOMParser();
 *   var xmlDom = parser.parseFromString(xmlString);
 * 
 * Then you can use the normal DOM methods to work with the XML.
 */
if (typeof DOMParser == "undefined")
  {
  DOMParser = function () {};
  DOMParser.prototype.parseFromString = function (str, contentType)
    {
    if (typeof ActiveXObject != "undefined")
      {
      var d = new ActiveXObject("MSXML.DomDocument");
      d.loadXML(str);
      return d;
      }
    else if (typeof XMLHttpRequest != "undefined")
      {
      var req = new XMLHttpRequest;
      req.open("GET", "data:" + (contentType || "application/xml") +
               ";charset=utf-8," + encodeURIComponent(str), false);
      if (req.overrideMimeType)
        req.overrideMimeType(contentType);

      req.send(null);
      return req.responseXML;
      }
    }
  }

/**
 * Adds an Array.filter method if it doesn't already exist.
 * Taken directly from:
 * http://erik.eae.net/playground/arrayextras/
 * Also see Erik's blog post:
 * http://erik.eae.net/archives/2005/06/05/17.53.19/
 */
//http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:filter
if (!Array.prototype.filter)
  {
  Array.prototype.filter = function (f, obj)
    {
    var l = this.length;  // must be fixed during loop... see docs
    var res = [];
    for (var i = 0; i < l; i++)
      {
      if (f.call(obj, this[i], i, this))
        res.push(this[i]);
      }
    return res;
    };
  }

//*************************************************************************
//End cross-browser extras </view:if>
//*************************************************************************

//*************************************************************************
//Start SimpleDom <view:if><view:test>useSimpleDom</view:test>
//*************************************************************************
SDom =
  {
  //*************************************************************************
  /**
   * Factory for creating a simple dom object.
   * 
   * @param {Object} domNode: A DOM Node. Can be a DOM Document.
   * 
   * @param {String} rootElement: If domNode is a DOM Document,
   * specify a root element to use as the first SimpleDom object.
   * 
   * For the following XML document:
   * 
   *  
   * A SimpleDom object will have
   * the following structure:
   * node._sdNode = true. Use to find out if this is a SimpleDom node.
   * node._n      = String. The node name (typically element name).
   * node._v      = String .If there was only one child for this node, and that node
   *                was a text or cdata node. If there are more than one text/cdata nodes,
   *                or text/cdata nodes mixed with element nodes, this property will not
   *                be defined.
   * node._a      = a hashmap object of attributes. For instance, use node._a['foo']
   *                or nod._a.foo to access the foo attribute of a node.
   * node._c      = an array of the child elements, in the order that they appear in the
   *                XML. If there were no children, or if the only child was a text/cdata
   *                node, then this property will not be defined.
   * 
   * In addition to child elements being stored in the _c array, child elements are
   * also stored off the node by their nodeName. So, if there was a <phonenumber>
   * element under the node, it could be accessed by:
   * 
   * node.phonenumber or node['phonenumber']
   * 
   * If there are more than one child element with the same name (for instance,
   * more than one <phonebook> element), then that name will be an array:
   * 
   * node.phonenumber.length would give the length of the array.
   * 
   * To find out if a named member of a node is just a SimpleDom object or an array
   * of SimpleDomObjects, the ._n and ._v properties will be undefined for an array.
   * Also, there will be no ._sdNode = true property.
   * So for the phonenumber case where it is an array,
   * 
   * node.phonenumber._n or node.phonenumber._v or node._sdNode
   * 
   * will be undefined. You could use a:
   * 
   * typeof(node.phonenumber._sdNode) == 'undefined')
   * or
   * !node.phonenumber._sdNode
   * 
   * check to see if it is an array. Testing for node.phonenumber.length is not
   * advised, since length could be used as the name of an element in the DOM.
   * 
   * Notes:
   * - It may not work so well with XML elements that start with an underscore (_).
   * - It does not support names spaces officially. Whatever is returned
   *   by the DOM property node.nodeName is used as the name of the node.
   * - It is mostly useful for creating a read-only JavaScript structure.
   *   There is a SDom.serialize that will convert a SimpleDom structure to
   *   an XML string, but that XML is not guaranteed to be equivalent to
   *   the XML that was used to create the SimpleDom.
   * - Only Element, CDATA and Text nodes are processed.
   */
  create: function(domNode, rootElementName)
    {
    var result = null;
    //If this is a document node, find root element and start with that.
    if (domNode.nodeType == 9)
      {
      if (rootElementName)
        {
        var roots = domNode.getElementsByTagName(rootElementName);
        if (roots && roots[0])
          result = new SDom._construct(roots[0]);
        }
      }
    return result;
    },

  //*************************************************************************
  /**
   * Class method to convert a SimpleDom to a string. Does not guarantee that
   * the string output will match the XML string that was used to create a
   * SimpleDom.
   */
  serialize: function(simpleDom)
    {
    var result = '';

    if (!simpleDom)
      return result;
    
    //If the simpleDom is an array, then call the serialize method for each
    //item in the array.
    if (simpleDom instanceof Array)
      {
      for (var index = 0; index < simpleDom.length; index++)
        result += SDom.serialize(simpleDom[index]);
      return result;
      }

    //If not an array, Make sure it is a simple dom node.
    //If not, it must be a string (or something that can convert
    //properly to a string).
    if (!simpleDom._sdNode)
      return simpleDom;
    
    //Get the attributes
    var attributes = '';
    if (simpleDom._a)
      {
      for (var param in simpleDom._a)
        attributes += ' ' + param + '="' + simpleDom._a[param] + '"';
      }

    //If no value or children, empty node.
    if (typeof(simpleDom._v) == 'undefined' && typeof(simpleDom._c) == 'undefined')
      return '<' + simpleDom._n + attributes + ' />';
    
    //Make the start tag
    result = '<' + simpleDom._n + attributes + '>';
    
    if (simpleDom._v)
      result += simpleDom._v;
    else if (simpleDom._c)
      {
      result += SDom.serialize(simpleDom._c);
      }
    
    result += '</' + simpleDom._n + '>';
    
    return result;
    },
    
  //*************************************************************************
  //*************************************************************************
  //*************************************************************************
  /**
   * @private Constructor for creating a simple dom object.
   * Do not call this method directly.
   * Use the factory method SDom.create() instead.
   */
  _construct: function(domNode, rootElementName)
    {
    //Set a property indicating this object is a simple dom node.
    //Some of things attached in a simple dom structure will just
    //be strings (for text and cdata nodes).
    this._sdNode = true;
    
    this._n = domNode.nodeName;

    //Get the attributes
    var attributes = domNode.attributes;
    var i, node;
    if (attributes && attributes.length > 0)
      {
      this._a = new Object();
      for (i = 0; i < attributes.length; i++)
        {
        node = attributes.item(i);
        this._a[node.nodeName] = node.nodeValue;
        }
      }
      
    //Get the children
    var childIndex = 0;
    var namedChildList;
    var children = domNode.childNodes;
    
    if (children && children.length > 0)
      {
      //Find out if there is only one child and if it is a text node (3) or cdata node (4).
      if (children.length == 1 && (children[0].nodeType == 3 || children[0].nodeType == 4))
        {
        this._v = children[0].nodeValue;
        }
      else
        {
        this._c = new Array();

        //Iterate through children and add to the dom.
        for (i = 0; i < children.length; i++)
          {
          node = children[i];
          //Only handling text, cdata and other elements right now.
          switch(node.nodeType)
            {
            case 1:  //Element
              this._c[childIndex] = new SDom._construct(node);
              
              //Also add the child as a named property of the node.
              if (!this[node.nodeName])
                this[node.nodeName] = this._c[childIndex];
              else
                {
                if (this[node.nodeName]._sdNode)
                  {
                  //If the named property is a sdom node already,
                  //convert the named property into an array.
                  var temp = this[node.nodeName];
                  this[node.nodeName] = new Array();
                  this[node.nodeName].push(temp);
                  }

                this[node.nodeName].push(this._c[childIndex]);
                }
              childIndex++;
              break;
            case 3:  //Text Node
            case 4:  //CData Node
              this._c[childIndex] = node.nodeValue;
              childIndex++;
              break;
            }
          }
        }
      }
    }
  }
//*************************************************************************
//End SimpleDom </view:if>
//*************************************************************************


