/*
 JUG Object Interface library

 Copyright (c) 2010, Simon Wiseman
 
 This software is provided 'as-is', without any express or implied
 warranty. In no event will the authors be held liable for any damages
 arising from the use of this software.
 
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:
 
    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.
 
    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.
 
    3. This notice may not be removed or altered from any source distribution.
*/

//************************************************************************************************************
// The JUG Object interface for Javascript supports the serialisation of objects using CSV and XML 
//************************************************************************************************************

// Parse a CSV row into an array of strings
function JugCsvToArray( csvText )
{
	if( csvText == '' )
	{
		return [];
	}

	var a = [];
	var len = csvText.length;
	var i = 0;
	while( i < len )
	{
		var c = csvText.charAt(i);
		var delimiter;
		var doubleDelimiter;
		if( c == "'" )
		{
			delimiter = "'";
			doubleDelimiter = "''";
		}
		else if( c == '"' )
		{
			delimiter = '"';
			doubleDelimiter = '"""';
		}
		else
		{
			delimiter = "";
		}
		
		if( delimiter != '' )
		{
			// field enclosed by apostrophe or quote
			i += 1;	// skip the apostrophe/quote
        	var start = i;
        	while( i < len )
        	{
        		if( csvText.charAt(i) == delimiter )
        		{
        			if( i+1 < len && csvText.charAt(i+1) == delimiter )
        			{
        				// double apostrophe/quote, so carry on
        				i += 2;
        			}
        			else
        			{
        				// single apostrophe/quote, so this the end of the field
        				break;
        			}
        		}
        		else
        		{
	        		i += 1;
        		}
			}
			var field = csvText.substr( start, i - start );
			// skip until the comma - should only be spaces
			while( i < len && csvText.charAt(i) != ',')
			{
				i += 1;
			}
			a.push( field.replace( doubleDelimiter, delimiter ) );		// replace double apostrophes/quotes
			i += 1;	// skip over comma if it is present
		}
		else
		{
			// field not enclosed
			var start = i;
			while( i < csvText.length && csvText.chatAt(i) != ',' )
			{
				i += 1;
			}
			a.push( csvText.substr( start, i - start ) );
			i += 1;	// skip over comma if it is present
		}
	}
	return a;
}

// Convert an array of strings to a CSV row
function JugArrayToCsv( strings )
{
	var a = [];
	for( s in strings )
	{
		var string = strings[s].replace( '"','""' );
		a.push( '"' + string + '"' );
	}
	return a.join( ',' );
}

// Create an XML document by parsing some text
function JugMakeXMLDoc( xmlText )
{
	if( typeof ActiveXObject != "undefined" )
	{
		//Internet Explorer
		var xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );
		xmlDoc.async = "false";
		xmlDoc.loadXML( xmlText );
		return xmlDoc;
	}
	else
	{
		// assume W3C compliant
		var parser = new DOMParser();
		return parser.parseFromString( xmlText, "text/xml" );
	}
}

// Create a new XML document with just an outer tag having the given name
function JugNewXMLDoc( tagName )
{
	if( typeof ActiveXObject != "undefined" )
	{
		//Internet Explorer
		var xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );
		xmlDoc.async = "false";
		var text = "<" + tagName + "/>";
		xmlDoc.loadXML( text );
		return xmlDoc;
	}
	else
	{
		// assume W3C compliant
		var xmlDoc = document.implementation.createDocument( "", tagName, null );
		return xmlDoc;
	}
}

// Produce text representation of an XML document
function JugXMLToText( doc )
{
	if( typeof ActiveXObject != "undefined" )
	{
		//Internet Explorer
		return doc.xml;
	}
	else
	{
		// Mozilla interface
		var serializer = new XMLSerializer();
		return serializer.serializeToString( doc );
	}
}

// Create an XML document representing a Javascript object
function JugObjectAsXML( obj )
{
	// obj in a Javascript object
	// the result is an XML document representing the object
	//	the XML document can be converted back to an object by calling JugXMLAsObject
	var doc = JugNewXMLDoc( "object" );
	
	// add a value's type and value to an xml node
	function addValue( value, node, name )
	{
		switch( typeof value )
		{
			case "boolean":
				node.setAttribute( "type", "boolean" );
				if( value )
				{
					node.setAttribute( "value", "true" );
				}
				else
				{
					node.setAttribute( "value", "false" );
				}
				break;
			case "string":
				node.setAttribute( "type", "string" );
				node.setAttribute( "value", value );
				break;
			case "number":
				node.setAttribute( "type", typeof value );
				node.setAttribute( "value", value );
				break;
			case "object":
				if( value instanceof Array )
				{
					// the object is actually an array
					node.setAttribute( "type", "array" );
					var i;
					for( i=0; i < value.length; i++ )
					{
						var arrayItem = doc.createElement( 'item' );
						node.appendChild( arrayItem );
						arrayItem.setAttribute( "index", i );
						addValue( value[ i ], arrayItem, "index " + i );
					}
				}
				else
				{
					// the object is a general one
					node.setAttribute( "type", "object" );
					addObject( value, node );
				}
				break;
			default:
				throw new Error( "JugXMLAsObject: unhandled object " + name + " type " + typeof value );
		}
	}
	
	function addObject( obj, node )
	{
		for( attr in obj )
		{
			var element = doc.createElement( 'element' );
			node.appendChild( element );
			element.setAttribute( "name", attr );

			addValue( obj[ attr ], element, attr );
		}
	}
	
	addObject( obj, doc.documentElement );
	
	return doc;
}

// Create a Javascript object from an XML description
function JugXMLAsObject( doc )
{
	// doc is an XML document object, whose outer tag must be "object"
	//	typically the XML would be produced by calling JugObjectAsXML
	// the result is a Javascript object
	
	if( doc.documentElement.tagName != "object" )
	{
		throw new Error( "JugXMLAsObject: doc is " + doc.documentElement.tagName + " not object" );
	}
	
	function buildValue( node, name )
	{
		var type = node.getAttribute( "type" );
		if( type == "boolean" )
		{
			var value = node.getAttribute( "value" );
			if( value == "true" )
			{
				return true;
			}
			else if( value == "false" )
			{
				return false;
			}
			else
			{
				throw new Error( "JugXMLAsObject: " + name + " = invalid boolean " + value );
			}
		}
		else if( type == "number" )
		{
			var value = node.getAttribute( "value" );
			return parseFloat( value );
		}
		else if( type == "string" )
		{
			var value = node.getAttribute( "value" );
			if( value )
			{
				return value;
			}
			else
			{
				return "";	// lack of value means empty string because PHP bug doesn't let us give attribute an empty value
			}
		}
		else if( type == "object" )
		{
			return buildObject( node );
		}
		else if( type == "array" )
		{
			return buildArray( node );
		}
		else
		{
			throw new Error( "JugXMLAsObject: " + name + " unknown type " + type );
		}
	}
	
	function buildArray( node )
	{
		var a = [];
		
		var nodes = node.childNodes;
		var i;
		for( i=0; i < nodes.length; i++ )
		{
			var node = nodes[ i ];
			var index = node.getAttribute( "index" );
			a[ index ] = buildValue( node, "array" );
		}
		
		return a;
	}
	
	function buildObject( objectNode )
	{
		var obj = {};
		
		var nodes = objectNode.childNodes;
		var i;
		for( i=0; i < nodes.length; i++ )
		{
			var node = nodes[ i ];
			if( node.nodeType == 1 )	// 1 => element node
			{
				if( node.nodeName == "element" )
				{
					var name = node.getAttribute( "name" );
					obj[name] = buildValue( node, name );
				}
				else
				{
					throw new Error( "JugXMLAsObject: unknown tag " + node.nodeName );
				}
			}
		}
		
		return obj;
	}

	return buildObject( doc.documentElement );
}

