// SpryXML.js - version 0.4 - Spry Pre-Release 1.5
//
// Copyright (c) 2006. Adobe Systems Incorporated.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of Adobe Systems Incorporated nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

var Spry; if (!Spry) Spry = {}; if (!Spry.XML) Spry.XML = {}; if (!Spry.XML.Schema) Spry.XML.Schema = {};

Spry.XML.Schema.Node = function(nodeName)
{
	this.nodeName = nodeName;
	this.isAttribute = false;
	this.appearsMoreThanOnce = false;
	this.children = new Array;
};

Spry.XML.Schema.Node.prototype.toString = function (indentStr)
{
	if (!indentStr)
		indentStr = "";

	var str = indentStr + this.nodeName;

	if (this.appearsMoreThanOnce)
		str += " (+)";

	str += "\n";

	var newIndentStr = indentStr + "    ";

	for (var $childName in this.children)
	{
		var child = this.children[$childName];
		if (child.isAttribute)
			str += newIndentStr + child.nodeName + "\n";
		else
			str += child.toString(newIndentStr);
	}

	return str;
};

Spry.XML.Schema.mapElementIntoSchemaNode = function(ele, schemaNode)
{
	if (!ele || !schemaNode)
		return;

	// Add all attributes as children to schemaNode!

	var i = 0;
	for (i = 0; i < ele.attributes.length; i++)
	{
		var attr = ele.attributes.item(i);
		if (attr && attr.nodeType == 2 /* Node.ATTRIBUTE_NODE */)
		{
			var attrName = "@" + attr.name;

			// We don't track the number of times an attribute appears
			// in a given element so we only handle the case where the
			// attribute doesn't already exist in the schemaNode.children array.

			if (!schemaNode.children[attrName])
			{
				var attrObj = new Spry.XML.Schema.Node(attrName);
				attrObj.isAttribute = true;
				schemaNode.children[attrName] = attrObj;
			}
		}
	}

	// Now add all of element's element children as children of schemaNode!

	var child = ele.firstChild;
	var namesSeenSoFar = new Array;
  
	while (child)
	{
		if (child.nodeType == 1 /* Node.ELEMENT_NODE */)
		{
			var childSchemaNode = schemaNode.children[child.nodeName];

			if (!childSchemaNode)
			{
				childSchemaNode = new Spry.XML.Schema.Node(child.nodeName);
				if (childSchemaNode)
					schemaNode.children[child.nodeName] = childSchemaNode;
			}

			if (childSchemaNode)
			{
				if (namesSeenSoFar[childSchemaNode.nodeName])
					childSchemaNode.appearsMoreThanOnce = true;
				else
					namesSeenSoFar[childSchemaNode.nodeName] = true;
			}

			Spry.XML.Schema.mapElementIntoSchemaNode(child, childSchemaNode);
		}

		child = child.nextSibling;
	} 
};

Spry.XML.getSchemaForElement = function(ele)
{
	if (!ele)
		return null;

	schemaNode = new Spry.XML.Schema.Node(ele.nodeName);
	Spry.XML.Schema.mapElementIntoSchemaNode(ele, schemaNode);

	return schemaNode;
};

Spry.XML.getSchema = function(xmlDoc)
{
	if (!xmlDoc)
		return null;

	// Find the first element in the document that doesn't start with "xml".
	// According to the XML spec tags with names that start with "xml" are reserved
	// for future use.

	var node = xmlDoc.firstChild;

	while (node)
	{
		if (node.nodeType == 1 /* Node.ELEMENT_NODE */)
			break;

		node = node.nextSibling;
	}

	return Spry.XML.getSchemaForElement(node);
};

Spry.XML.nodeHasValue = function(node)
{
	if (node)
	{
		var child = node.firstChild;
		if (child && child.nextSibling == null && (child.nodeType == 3 /* Node.TEXT_NODE */ || child.nodeType == 4 /* CDATA_SECTION_NODE */))
			return true;
	}
	return false;
};

Spry.XML.XObject = function()
{
};

Spry.XML.XObject.prototype._value = function()
{
	var val = this["#text"];
	if (val != undefined)
		return val;
	return this["#cdata-section"];
};

Spry.XML.XObject.prototype._hasValue = function()
{
	return this._value() != undefined;
};

Spry.XML.XObject.prototype._valueIsText = function()
{
	return this["#text"] != undefined;
};

Spry.XML.XObject.prototype._valueIsCData = function()
{
	return this["#cdata-section"] != undefined;
};

Spry.XML.XObject.prototype._propertyIsArray = function(prop)
{
	var val = this[prop];
	if (val == undefined)
		return false;
	return (typeof val == "object" && val.constructor == Array);
};

Spry.XML.XObject.prototype._getPropertyAsArray = function(prop)
{
	var arr = [];
	var val = this[prop];
	if (val != undefined)
	{
		if (typeof val == "object" && val.constructor == Array)
			return val;
		arr.push(val);
	}
	return arr;
};

Spry.XML.XObject.prototype._getProperties = function()
{
	var props = [];
	for (var p in this)
	{
		if (!/^_/.test(p))
			props.push(p);
	}
	return props;
};

Spry.XML.nodeToObject = function(node)
{
	if (!node)
		return null;

	var obj = new Spry.XML.XObject();

	// Add all attributes as properties of the object.

	for (var i = 0; i < node.attributes.length; i++)
	{
		var attr = node.attributes[i];
		var attrName = "@" + attr.name;
		obj[attrName] = attr.value;
	}

	var child;

	if (Spry.XML.nodeHasValue(node))
	{	
		try
		{
			child = node.firstChild;

			if (child.nodeType == 3 /* TEXT_NODE */)
				obj[child.nodeName] = Spry.Utils.encodeEntities(child.data);
			else if (child.nodeType == 4 /* CDATA_SECTION_NODE */)
				obj[child.nodeName] = child.data;
		} catch (e) { Spry.Debug.reportError("Spry.XML.nodeToObject() exception caught: " + e + "\n"); }
	}
	else
	{
		// The node has no value, so run through any element children
		// it may have and add them as properties.

		child = node.firstChild;
		while (child)
		{
			if (child.nodeType == 1 /* Node.ELEMENT_NODE */)
			{
				var isArray = false;
				var tagName = child.nodeName;
				if (obj[tagName])
				{
					if (obj[tagName].constructor != Array)
					{
						var curValue = obj[tagName];
						obj[tagName] = new Array;
						obj[tagName].push(curValue);
					}
					isArray = true;
				}

				var childObj = Spry.XML.nodeToObject(child);
				
				if (isArray)
					obj[tagName].push(childObj);
				else
					obj[tagName] = childObj;
			}
			child = child.nextSibling;
		}
	}
	return obj;
};

// Spry.XML.documentToObject - Utility method for creating a
// JavaScript object with properties and nested objects that
// mirror an XML document tree structure.
//
// Sample XML:
//
//		<employees>
//			<employee id="1000">
//				<name>John Doe</name>
//			</employee>
//			<employee id="2000">
//				<name><![CDATA[Jane Smith]]></name>
//			</employee>
//		</employees>
//
// Object returned by documentToObject():
//
//		{
//			employees:
//				{
//					employee:
//						[
//							{
//								@id: "1000",
//								name: { "#text": "John Doe" }
//							},
//							{
//								@id: "2000",
//								name: { "#cdata-section": "Jane Smith" }
//							}
//						]
//				}
//		}

Spry.XML.documentToObject = function(xmlDoc)
{
	var obj = null;
	if (xmlDoc && xmlDoc.firstChild)
	{
		var child = xmlDoc.firstChild;
		while (child)
		{
			if (child.nodeType == 1 /* Node.ELEMENT_NODE */)
			{
				obj = new Spry.XML.XObject();
				obj[child.nodeName] = Spry.XML.nodeToObject(child);
				break;
			}
			child = child.nextSibling;
		}
	}
	return obj;
};
