//<!--
//*****************************************************************************************************
// 	The AOL Video Search AJAX API version 3.
// 	Copyright (c) 2008, AOL LLC.
// 	All rights reserved.
//*****************************************************************************************************

// The AOLVideoSearch object.  This object is the top-level object for the AOL Video Search AJAX API.
var AOLVideoSearch = function(appid) {

	// PRIVATE CONSTANTS:
	// Production URLS:
	var xmlhttpUrl = 'http://xml.truveo.com/libs/xmlhttp';	         // The URL for the xmlhttp server; this must have the same domain as the serviceUrl.
	var serviceUrl = 'http://xml.truveo.com/apiv3';		   	      // The service URL for the REST API;  this must have the same domain as the xmlhttpUrl.
	var sessionCookie = new Cookie(document, "aolvs_session_token", 720);	// The user session cookie, which stores the user token and will persist for 30 days.
	var stateCookie = new Cookie(document, "aolvs_api_state");				// The api state cookie, which stores the api state; persists only for the current browser session.

	// PRIVATE ATTRIBUTES:
	var urlParams = null;						// An associative array for any parameters passed in the original document URL.
	var onUpdateHandlerArray = new Array();		// The array containing the list of methods to be called when the onupdate event is fired.
	var onLoadHandlerArray = new Array();		// The array containing the list of methods to be called when the onLoad event is fired.
	var onErrorHandlerArray = new Array();		// The array containing the list of methods to be called when the onerror event is fired.
	var errorMessages = { '501': 'Error connecting to XMLHTTP AJAX service',
						  '502': 'Invalid AJAX method call: The wrong number of arguments was provided to this method.',
						  '503': 'Method not available: You must be logged in to use this method.'};

	this.authParams =  new Object();
	// PUBLIC ATTRIBUTES:
	// Request attributes:
	this.appid = appid;			// Application ID for this application.
	this.showAdult = 0;			// Flag to indicate if adult video should be included in results.
	this.showRelatedItems = 0;	// Flag to indicate if related tags, channels, categories and users should be returned with each VideoSet.
	this.start = 0;				// The position of the first video of the current VideoSet in the total result set.
	this.results = 10;			// The number of videos to return in each page of video results.
	this.tagResults = 10;		// The number of tags to return in each TagSet.
	this.channelResults = 10;	// The number of channels to return in each ChannelSet.
	this.categoryResults = 10;	// The number of categories to return in each CategorySet.
	this.userResults = 10;		// The number of users to return in each UserSet.
	this.mode = 'ajax';			// The mode can be set to either 'ajax' or 'rest'.

	// Response attributes:
	this.method = "";			// A string containing the method associated with the most recently received API response.
	this.query = "";			// A string containing the query associated with the current AOLVideoSearch data objects.
	this.querySuggestion = "";			// A string containing the query associated with the current AOLVideoSearch data objects.
	this.previousQuery = "";	// A string containing the query that was executed immediately previous to the current query.
	this.VideoSet = null;		// A data object containing the current VideoSet, as derived from the JSON string in the API response.
	this.TagSet = null;			// A data object containing the current TagSet, as derived from the JSON string in the API response.			
	this.ChannelSet = null;		// A data object containing the current ChannelSet, as derived from the JSON string in the API response.
	this.CategorySet = null;	// A data object containing the current CategorySet, as derived from the JSON string in the API response.		
	this.UserSet = null;		// A data object containing the current UserSet, as derived from the JSON string in the API response.
	this.WatchlistSet = null;	// A data object containing the current WatchlistSet, as derived from the JSON string in the API response.
	this.Result = null;			// A data object containing generic information, typically a status message, associated with an API response.

	// PUBLIC METHODS:
	// Function getVideos() can be used to submit a query to the AOL video search service. This method returns the set of video results 
	// that match the submitted query.  The matching videos will be returned in the VideoSet object.
	this.getVideos = function(query, start) {
		if (arguments.length < 1) { var query = ''; }
		if (arguments.length < 2) { var start = 0; }
		var urlParams = { 'method': 'truveo.videos.getVideos',
					  	  'query': query,
					  	  'results': this.results,
					  	  'start': start,
					  	  'showRelatedItems': this.showRelatedItems,
					  	  'tagResults': this.tagResults,
					  	  'channelResults': this.channelResults,
					  	  'categoryResults': this.categoryResults,
					  	  'userResults': this.userResults,
					  	  'showAdult': this.showAdult };
		this.submitRestQuery(urlParams, this.mode);
		return(true);
	}
	
	// Function getRelatedTags() returns the set of tags that are associated with the videos that match the submitted query.   
	// The resulting tags will be returned in the TagSet object.
	this.getRelatedTags = function(query, start, results) { 
		if (arguments.length < 1) { var query = ''; }
		if (arguments.length < 2) { var start = 0; }
		if (arguments.length < 3) { var results = this.tagResults; }
		var urlParams = { 'method': 'truveo.videos.getRelatedTags',
					  	  'query': query,
					  	  'results': results,
					  	  'start': start };
		this.submitRestQuery(urlParams, this.mode);
		return(true);
	}

	// Function getRelatedChannels() returns the set of channels that are associated with the videos that match the submitted query.   
	// The resulting channels will be returned in the ChannelSet object.
	this.getRelatedChannels = function(query, start, results) { 
		if (arguments.length < 1) { var query = ''; }
		if (arguments.length < 2) { var start = 0; }
		if (arguments.length < 3) { var results = this.channelResults; }
		var urlParams = { 'method': 'truveo.videos.getRelatedChannels',
					  	  'query': query,
					  	  'results': results,
					  	  'start': start };
		this.submitRestQuery(urlParams, this.mode);
		return(true);
	}

	// Function getRelatedCategories() returns the set of categories that are associated with the videos that match the submitted query.   
	// The resulting categories will be returned in the CategorySet object.
	this.getRelatedCategories = function(query, start, results) { 
		if (arguments.length < 1) { var query = ''; }
		if (arguments.length < 2) { var start = 0; }
		if (arguments.length < 3) { var results = this.categoryResults; }
		var urlParams = { 'method': 'truveo.videos.getRelatedCategories',
					  	  'query': query,
					  	  'results': results,
					  	  'start': start };
		this.submitRestQuery(urlParams, this.mode);
		return(true);
	}

	// Function getRelatedUsers() returns the set of users that are associated with the videos that match the submitted query.   
	// The resulting users will be returned in the UserSet object.
	this.getRelatedUsers = function(query, start, results) { 
		if (arguments.length < 1) { var query = ''; }
		if (arguments.length < 2) { var start = 0; }
		if (arguments.length < 3) { var results = this.userResults; }
		var urlParams = { 'method': 'truveo.videos.getRelatedUsers',
					  	  'query': query,
					  	  'results': results,
					  	  'start': start };
		this.submitRestQuery(urlParams, this.mode);
		return(true);
	}

	// Function submitRating() submits the specified rating for the video with the specified id.
	this.submitRating = function(videoId, rating) { 
		if (arguments.length < 2) { 
			this.onerror(502, errorMessages['502']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.videos.submitRating',
					  	  'id': videoId,
					  	  'rating': rating };
		this.submitRestQuery(urlParams, 'ajax');
		return(true);
	}

	// Function login() logs a user into the AOL video search service.  This function takes one argument: the callbackUrl which will
	// be loaded into the browser when the page returns from the login screen.
	this.login = function(callbackUrl) {
		this.saveAPIState();
		var cleanCallbackUrl = this.removeUrlParameters(callbackUrl, ["auth", "token", "loginCancel", "errorCode", "errorMessage", "logout"]);
		var urlParams = { 'method': 'truveo.users.login',
					  	  'callback_url': cleanCallbackUrl };
		window.location.href = this.getRestUrl(urlParams);
	}
	
	// Function logout() logs the users out of the AOL video search service and deletes any sessionCookie.  This function takes one 
	// argument: the callbackUrl which will be loaded into the browser when the page returns from the login screen.  When the browser
	// is redirected to the callbackUrl, the URL will have one additional parameter, 'login', set to 'true'.
	this.logout = function(callbackUrl) {
		sessionCookie.remove();
		//this.saveAPIState();
		var cleanCallbackUrl = this.removeUrlParameters(callbackUrl, ["auth", "token", "loginCancel", "errorCode", "errorMessage", "logout"]);
		if (cleanCallbackUrl.indexOf("?") == -1) { cleanCallbackUrl = cleanCallbackUrl + "?logout=true"; }
		else { cleanCallbackUrl = cleanCallbackUrl + "&logout=true"; }
		var urlParams = { 'method': 'truveo.users.logout',
					  	  'logout_callback_url': cleanCallbackUrl };
		window.location.href = this.getRestUrl(urlParams);
	}


	// Function isLoggedIn() checks the login state of the AOL video search API and returns true is the user is currently
	// logged in, or false otherwise.
	this.isLoggedIn = function() {
		if (sessionCookie.load() && (sessionCookie.publicName)) { return(true); }
		else { return(false); }
	}
	
	// Function getPublicName() retrieves the public name of the user from the session cookie.
	this.getPublicName = function() {
		if (sessionCookie.load() && sessionCookie.publicName) { return(sessionCookie.publicName); }
		else { return(false); }
	}

	// Function getFavoriteVideos() retrieves the VideoSet containing the user's favorite videos.  If the user is not
	// logged in, this function returns false.
	this.getFavoriteVideos = function(start) {
		if (!this.isLoggedIn() && (this.mode == 'ajax')) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		if (arguments.length < 1) { var start = 0; }
		var urlParams = { 'method': 'truveo.users.getFavoriteVideos',
                                                  'start': start,
                                                  'results': this.results,
                                                  'showRelatedItems': this.showRelatedItems };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, this.mode);
		return(true); 
	}
	
	// Function addFavorite() takes a video id and submits it to the user's favorites list.
	this.addFavorite = function(id) { 
		if (!this.isLoggedIn()) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		if (arguments.length < 1) { 
			this.onerror(502, errorMessages['502']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.users.addFavorite',
						  'id': id};
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, 'ajax');
		return(true); 
	}
	
	// Function removeFavorite() removes the video with the specified id from the user's favorites list.  This method returns false
	// if the user is not logged in.
	this.removeFavorite = function(id) { 
		if (!this.isLoggedIn()) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		if (arguments.length < 1) { 
			this.onerror(502, errorMessages['502']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.users.removeFavorite',
						  'id': id };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, 'ajax');
		return(true); 
	}

	// Function getRecentVideos() retrieves the VideoSet containing the user's recent videos.  If the user is not
	// logged in, this function returns false.
	this.getRecentVideos = function(start) { 
		if (!this.isLoggedIn() && (this.mode == 'ajax')) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		if (arguments.length < 1) { var start = 0; }
		var urlParams = { 'method': 'truveo.users.getRecentVideos',
						  'start': start,
						  'results': this.results,
					  	  'showRelatedItems': this.showRelatedItems };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, this.mode);
		return(true); 
	}

	// Function addRecentVideo() takes a video id and submits it to the user's recent videos list.  This method returns false
	// if the user is not logged in.
	this.addRecentVideo = function(id) { 
		if (!this.isLoggedIn()) { 
			return(false); 
		}
		if (arguments.length < 1) { 
			this.onerror(502, errorMessages['502']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.users.addRecentVideo',
						  'id': id };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, 'ajax');
		return(true); 
	}

	// Function clearRecentVideos() removes all videos from the user's recent videos list.  This method returns false
	// if the user is not logged in.
	this.clearRecentVideos = function() { 
		if (!this.isLoggedIn()) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.users.clearRecentVideos' };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, 'ajax');
		return(true); 
	}

	// Function getWatchlists() retrieves the WatchlistSet containing the user's watchlists.  If the user is not
	// logged in, this function returns false.
	this.getWatchlists = function() { 
		if (!this.isLoggedIn() && (this.mode == 'ajax')) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.users.getWatchlists' };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, this.mode);
		return(true); 
	}
	
	// Function addWatchlist() takes a query string and submits it to the user's watchlists.
	this.addWatchlist = function(query) { 
		if (!this.isLoggedIn()) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		if (arguments.length < 1) { 
			this.onerror(502, errorMessages['502']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.users.addWatchlist',
						  'query': query };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, 'ajax');
		return(true); 
	}
	
	// Function removeWatchlist() removes the specified query from the user's watchlists.  This method returns false
	// if the user is not logged in.
	this.removeWatchlist = function(query) { 
		if (!this.isLoggedIn()) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		if (arguments.length < 1) { 
			this.onerror(502, errorMessages['502']);
			return(false); 
		}
		var urlParams = { 'method': 'truveo.users.removeWatchlist',
						  'query': query };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, 'ajax');
		return(true); 
	}

  	// Function getRecommendedVideos() retrieves a VideoSet containing the user's recommended videos.  If the user is not
  	// logged in, this function returns false.
	this.getRecommendedVideos = function(start) {
		if (!this.isLoggedIn() && (this.mode == 'ajax')) { 
			this.onerror(503, errorMessages['503']);
			return(false); 
		}
		if (arguments.length < 1) { var start = 0; }
		var urlParams = { 'method': 'truveo.users.getRecommendedVideos',
						  'start': start,
						  'results': this.results,
					  	  'showRelatedItems': this.showRelatedItems };
		for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
		this.submitRestQuery(urlParams, this.mode);
		return(true); 
	}

	// Function hasNextPage() returns true if another page of video search results is available; otherwise it returns false. 
	this.hasNextPage = function() { return(parseInt(this.VideoSet.firstResultPosition) + parseInt(this.VideoSet.totalResultsReturned) < parseInt(this.VideoSet.totalResultsAvailable)); }

	// Function hasPreviousPage() returns true if a previous page of video search results is available; otherwise it returns false. 
	this.hasPreviousPage = function() { return(parseInt(this.VideoSet.firstResultPosition) > 0); }
	
	// Function nextPage() retrives the VideoSet containing the next page of video search results.
	this.nextPage = function() { 
		var startPosition = parseInt(this.VideoSet.firstResultPosition) + parseInt(this.VideoSet.totalResultsReturned);
		if (this.method.indexOf("getRecentVideos") >= 0) { this.getRecentVideos(startPosition); }
		else if (this.method.indexOf("getFavoriteVideos") >= 0) { this.getFavoriteVideos(startPosition); }
		else if (this.method.indexOf("getRecommendedVideos") >= 0) { this.getRecommendedVideos(startPosition); }
		else { this.getVideos(this.query, startPosition); }
		return(true);
	}

	// Function previousPage() retrives the VideoSet containing the previous page of video search results.
	this.previousPage = function() { 
		var startPosition = parseInt(this.VideoSet.firstResultPosition) - this.results;
		startPosition = (startPosition<0 ? 0 : startPosition);
		if (this.method.indexOf("getRecentVideos") >= 0) { this.getRecentVideos(startPosition); }
		else if (this.method.indexOf("getFavoriteVideos") >= 0) { this.getFavoriteVideos(startPosition); }
		else if (this.method.indexOf("getRecommendedVideos") >= 0) { this.getRecommendedVideos(startPosition); }
		else { this.getVideos(this.query, startPosition); }
		return(true);
	}
	
	// Function goToPage() retrieves the VideoSet containing page of video search results specified by the give pageNum.
	this.goToPage = function(pageNum) {
		var startPosition = (pageNum - 1) * this.results;
		startPosition = (startPosition<0 ? 0 : startPosition);
		if (this.method.indexOf("getRecentVideos") >= 0) { this.getRecentVideos(startPosition); }
		else if (this.method.indexOf("getFavoriteVideos") >= 0) { this.getFavoriteVideos(startPosition); }
		else if (this.method.indexOf("getRecommendedVideos") >= 0) { this.getRecommendedVideos(startPosition); }
		else { this.getVideos(this.query, startPosition); }
		return(true);
	}

	// Function attachEvent() attaches the handler function specified by 'handler' to the specified event. The value provided 
	// in the argument handler will be eval'ed when the event with the specified eventName is fired.  Note that the handler
	// argument can be a string or a pointer to a method.  Also, the handler will always be eval'ed in the global
	// context.
	this.attachEvent = function(eventName, handler) {
		switch(eventName) {
			case 'onupdate':
				var index = onUpdateHandlerArray.length;
				onUpdateHandlerArray[index] = handler;
				break;
			case 'onerror':
				var index = onErrorHandlerArray.length;
				onErrorHandlerArray[index] = handler;
				break;
			case 'onload':
				var index = onLoadHandlerArray.length;
				onLoadHandlerArray[index] = handler;
				break;
		}
		return(true);
	}

	// PRIVATE METHODS:
	// Function onupdate() fires the onupdate event for this AOLVideoSearch object.  It calls all of the handlers that have 
	// been attached to this event using the attachEvent() method.  All the currently attached onupdate handlers are stored 
	// in the array onUpdateHandlerArray[].
	this.onupdate = function(methodName) {
		for (var i=0; i < onUpdateHandlerArray.length; i++) { eval(onUpdateHandlerArray[i]); }
	}

	// Function onerror() fires the onerror event for the AOLVideoSearchobject.  It calls all of the handlers that have 
	// been attached to this event using the attachEvent() method.  All the currently attached onerror handlers are stored 
	// in the array onErrorHandlerArray[].
	this.onerror = function(errorCode, errorMessage) {
		for (var i=0; i < onErrorHandlerArray.length; i++) { eval(onErrorHandlerArray[i]); }
	}

	// Function onload() fires the onload event for the AOLVideoSearchobject.  It calls all of the handlers that have 
	// been attached to this event using the attachEvent() method.  All the currently attached onload handlers are stored 
	// in the array onLoadHandlerArray[].
	this.onload = function(reloadStateFlag) {
		if (arguments.length < 1) reloadStateFlag = false;
		if (reloadStateFlag == true) { this.loadAPIState(); }
		for (var i=0; i < onLoadHandlerArray.length; i++) { eval(onLoadHandlerArray[i]); }
	}

	// Function saveAPIState() saves the current state of the API to a cookie.  Note this function does not save any
	// of the API response data objects.
	this.saveAPIState = function() {
		stateCookie.showAdult = this.showAdult;
		stateCookie.showRelatedItems = this.showRelatedItems;
		stateCookie.start = this.start;
		stateCookie.results = this.results;
		stateCookie.tagResults = this.tagResults;
		stateCookie.channelResults = this.channelResults;
		stateCookie.categoryResults = this.categoryResults;
		stateCookie.userResults = this.userResults;
		stateCookie.mode = this.mode;
		stateCookie.method = this.method;
		stateCookie.query = this.query;
		stateCookie.previousQuery = this.previousQuery;
		stateCookie.store();
	}

	// Function loadAPIState() loads the past state of the API from a cookie.
	this.loadAPIState = function() {
		if (stateCookie.load()) {
			this.showAdult = stateCookie.showAdult;
			this.showRelatedItems = stateCookie.showRelatedItems;
			this.start = stateCookie.start;
			this.results = stateCookie.results;
			this.tagResults = stateCookie.tagResults;
			this.channelResults = stateCookie.channelResults;
			this.categoryResults = stateCookie.categoryResults;
			this.userResults = stateCookie.userResults;
			this.mode = stateCookie.mode;
			this.method = stateCookie.method;
			this.query = stateCookie.query;
			this.previousQuery = stateCookie.previousQuery;
			stateCookie.remove();
		}
	}

	this.saveAuthMethodState = function() {
		//console.log('saving AuthMethod');
		for (var key in this.authParams) {
        	        //console.log(key + ': ' + this.authParams[key]);
                        sessionCookie['authParams' + key] = this.authParams[key];
                }
		sessionCookie.store();
	}

	this.loadAuthMethodState = function() {
		//console.log('loading AuthMethod');
                if (sessionCookie.load()) {
			//console.log(sessionCookie);
			for (var key in sessionCookie) {
				//console.log(key + ': ' + sessionCookie[key]);
				if (key.indexOf('authParams')!=-1) {
					this.authParams[key.substring(10)] = sessionCookie[key];
					//console.log(key.substring(10) + ': ' + sessionCookie[key]);
				}
			}
                }
        }

 	// Function update() updates the state of the AOLVideoSearch object using the XML text string, xmlText.  This function
	// is typically called when a search engine response has been received and new data needs to be handled.  This function
	// takes the xmlText string, transforms it into a JSON string and then a Javascript object, and stores the resulting 
	// object components in the attributes of the AOLVideoSearch object.  When the update is complete, this method will fire 
	// the onupdate event which will trigger any handlers attached to this object.  The XML to JSON conversion is performed
	// using the XML JSON object tree class xotree.
	this.update = function(xmlText) {
		
		// Initialize the ObjTree object.  Specify the XML tags should always be converted to a JSON array.
		var xotree = new XML.ObjTree();
		xotree.force_array = ["Video", "Tag", "Channel", "Category", "User", "Watchlist"];

		// Now parse the xml response message.
		var tree = xotree.parseXML(xmlText); 
		if (tree && ("Response" in tree)) {
			var response = tree.Response;  
			
			// If the response is an error message, then fire an onerror event.
			if ("Error" in response) { this.onerror(response.Error["-Code"], response.Error["#text"]); }
			
			// If the response is from the 'checkToken' method then set the session state depending on the result.  If the token
			// is good, then fire the onload event.  If not, then delete the sessionCookie before firing the onload event.
			else if (("method" in response) && (response.method.indexOf("checkToken") >= 0) && response.Result && response.Result.code) {
				if (response.Result.code == 1) { this.testSetAuthMethodTruveo(); this.onload(); }
				else {
					sessionCookie.remove();
					this.onload();
				}
			}
			
			// If the response is an authentication response, then save the user login info, and refresh the application state.
			else if (("method" in response) && (response.method.indexOf("getToken") >= 0) && response.Result && response.Result.token) {
				sessionCookie.token = response.Result.token;
				if (response.Result.publicName) { sessionCookie.publicName = response.Result.publicName; }
				this.testSetAuthMethodTruveo();
				sessionCookie.store();
				this.onload(true);
			}

			else if (("method" in response) && (response.method.indexOf("checkAuthMethod")>=0) && response.Result && response.Result.authid) {
				//this.saveAuthMethodState(); no need to set the AuthMethod state
				sessionCookie.publicName = response.Result.authid;
				sessionCookie.store();
				this.onload(true);
			}
			
			// For all other responses, save the response data in the appropriate attributes and then fire the onupdate event.
			else {
				var methodName = '';
				if ("method" in response) {
					this.method = response.method;
					methodName = response.method.slice(response.method.lastIndexOf('.') + 1);
				}
				if ("query" in response) { 
					this.previousQuery = this.query;
					this.query = response.query;
				}
				if ("querySuggestion" in response) { 
					this.querySuggestion = response.querySuggestion;
				}
				if ("VideoSet" in response) { 
					this.VideoSet = response.VideoSet;
					if ("firstResultPosition" in response.VideoSet) { this.start = response.VideoSet.firstResultPosition; }
				}
				if ("TagSet" in response) { this.TagSet = response.TagSet; }
				if ("ChannelSet" in response) { this.ChannelSet = response.ChannelSet; }
				if ("CategorySet" in response) { this.CategorySet = response.CategorySet; }
				if ("UserSet" in response) { this.UserSet = response.UserSet; }
				if ("WatchlistSet" in response) {  this.WatchlistSet = response.WatchlistSet; } 
				if ("Result" in response) {  this.Result = response.Result; } 
				this.onupdate(methodName);
			}
		}
	}

	// Function onXMLHTTPLoad() is the handler called by the XMLHTTP object when a query returns.  Before this handler can be
	// called, the AOLVideoSearch object must first be registered with the XMLHTTP object using its registerCallback()
	// method. 
	this.onXMLHTTPLoad = function(xmlhttp) {
		this.update(xmlhttp.responseText);
	}

	// Function onXMLHTTPError() is the handler called by the XMLHTTP object when an error is thrown.  Before this handler can be
	// called, the AOLVideoSearch object must first be registered with the XMLHTTP object using its registerCallback() method. 
	this.onXMLHTTPError = function(message) {
		this.onerror(501, errorMessages['501'] + "; " + message);
	}

	// Function submitRestQuery() takes the submitted urlParameters and sends an HTTP GET request to the AOL video search API.
	// Note that if the AOLVideoSearch object is set to 'rest' mode, this function will load a new webpage into the browser
	// window rather than sending an AJAX request.
	this.submitRestQuery = function(urlParams, mode) {
		if (mode == 'rest') { 
			var restUrl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?';
			for (name in urlParams) { 
				if ((name != 'token') && (name != 'showAdult')) { restUrl += name + '=' + encodeURIComponent(urlParams[name]) + '&'; }
			}
			window.location.href = restUrl.slice(0, -1);
		}
		else {
			//console.log(this.getRestUrl(urlParams));	
			// Initialize the XMLHTTP object, which allows for cross-domain ajax requests.  Specify the server url, register 
			// this object as the callback object, and attach an error handler.  When the response to this request returns, 
			// it will call the onXMLHTTPLoad() method of the callback object.  The onXMLHTTPLoad() method will be called with one argument
			// specifying this xmlhttp object which will contain the response data.
			var xmlhttp = new XMLHTTP();
			xmlhttp.setXMLHTTPURL(xmlhttpUrl);
			xmlhttp.registerCallback(this);
 			xmlhttp.open("GET", this.getRestUrl(urlParams), true);
 			xmlhttp.send(null);
		}
	}
	
	// Function getRestUrl() returns a string containing a properly formated REST URL that can be submitted to the
	// AOL Video Search REST API.  This URL will include all of the URL parameters specified in the provided associative
	// array, urlParameters.
	this.getRestUrl = function(urlParameters) {
		var urlString = serviceUrl + '?appid=' + encodeURIComponent(this.appid);
		for (name in urlParameters) { if (urlParameters[name]) urlString += '&' + name + '=' + encodeURIComponent(urlParameters[name]); }
		return(urlString);
	}
	
	// Function getUrlParameters() takes an URL and returns an associative array containing name-value pairs for all 
	// arguments in the URL.
	this.getUrlParameters = function(url) {
		var args = new Object();
		if (url.lastIndexOf('?') == -1) { return(args); }
		else { var urlQueryString = url.slice(url.lastIndexOf('?') + 1); }
		if (urlQueryString.length == 0) { return(args); }
		var pairs = urlQueryString.split('&');
		for (var i=0; i < pairs.length; i++) {
			var pos = pairs[i].indexOf('=');
			if (pos == -1) {
				var argname = pairs[i];
				var value = '';
			}
			else {
				var argname = pairs[i].substring(0, pos);
				var value = pairs[i].substring(pos+1);
			}
			args[argname] = decodeURIComponent(value);
		}
		return(args);
	}

	// Function removeUrlParameters() takes an URL and removes any both the name and value associated with any of the
	// specified parameters from the URL query string.  All other parameters are left intact.  The first argument supplied
	// to this function should be the URL string.  The second argument should be an array containing the names of each 
	// parameter to be removed from the URL.  This function returns the modified url with the parameters removed.
	this.removeUrlParameters = function(url, paramArray) {
		if (url.lastIndexOf('?') == -1) { var baseUrl = url; }
		else { var baseUrl = url.slice(0, url.lastIndexOf('?')); }
		var urlParameters = this.getUrlParameters(url);
		for (var i=0; i < paramArray.length; i++) {
			if (paramArray[i] in urlParameters) { urlParameters[paramArray[i]] = null; }
		}
		var j = 0;
		var paramString = '';
		for (name in urlParameters) { 
			if (urlParameters[name] != null) {
				if (j==0) { paramString += name + '=' + encodeURIComponent(urlParameters[name]); }
				else { paramString += '&' + name + '=' + encodeURIComponent(urlParameters[name]); }
			}
			j++;
		}
		if (paramString == '') { return(baseUrl); }
		else { return(baseUrl + "?" + paramString); }
	}

	// Function setAuthMethod is called to set the sauthentication method used (eg, 'truveo', 'openauth')
	// truveo requires: token
	// open auth requires: open auth token, devId, referer URL
	// parameters are passed into an object as follows:
	//   var params  = { 'token': '12okdasd3231', 
	//   		     'devId': '12345';
	// 		     'referer': 'http://www.myvideowebsite.com/'};

	this.setAuthMethod = function(method, params){
		this.saveAPIState();
		this.authParams = params;
		this.authParams.authMethod = method;

		if (method == 'truveo') {
		}
		else if (method == 'uas'){
			sessionCookie.publicName = 'user';
		}
		else {	// open auth
			var urlParams = { 'method': 'truveo.users.checkAuthMethod'};
                        for (var authparam in this.authParams) urlParams[authparam] = this.authParams[authparam];
                        this.submitRestQuery(urlParams);
		}
		this.saveAuthMethodState();
	}

        this.resetAuthMethod = function(url) {
		if (this.authParams.authMethod=="truveo")
			this.logout(url);
		else {
			this.clearAuthMethod(url);
		}
        }

	this.clearAuthMethod = function(url) {
		sessionCookie.remove();
		window.location.href = url;
	}

        this.testSetAuthMethodOpenAuth = function(){
                var params  = { 'token': '%2FwQAAAAAAADY9WmKNmCyfmkjX1ztF4MZQcbTVE7UdhNiEfxlN3UUD5r5SPKQO7NhR2QdXdlplflDBoHsNL%2BEWAU8EZ%2B5MRv70zBIDcNAYWbo7xhwBDmbKlsnRBPhOB2AxxvLeLE1K0Eq7zta6T79NoZVKYSGJU%2B4HJ1tIA%3D%3D',
                                'devId': 'co1dDRMvlgZJXvWK',
                                'referer': 'https://api.screenname.aol.com/auth/admin/test.jsp'};

                this.setAuthMethod('openauth', params);
        }

        this.testSetAuthMethodUAS = function(){
                var params  = { 'token': '%2FBcAG0kBQbEAAK9XALmeh0kBQe0I1HD5yJT%2BY6UAAA%3D%3D'};
                this.setAuthMethod('uas', params);
		this.onload(true);	// load it to simulate login since we don't call checkAuthMethod like openauth
        }

        this.testSetAuthMethodTruveo = function(){
                var params  = { 'token': sessionCookie.token};
                this.setAuthMethod('truveo', params);
        }

	this.testSetAuthMethod = function(){
		var method = 'truveo';
		if (method == 'openauth')
			this.testSetAuthMethodOpenAuth();
		else if (method == 'uas')
                        this.testSetAuthMethodUAS();
		else if (method == 'truveo') {
			this.login(window.location.href);
		}
	}



	// FUNCTION INITIALIZE() is called when the AOLVideoSearch object is first instantiated.  This function checks
	// and sets the login state of this object based on the session cookie or URL parameters.
	this.initialize = function() {

		// First, check the user session cookie to see if it is valid.  If the cookie exists and contains
		// the user token, then call the API method checkToken to check the token's validity.
		if (this.isLoggedIn() && (this.mode == 'ajax')) { 
			this.loadAuthMethodState();
			if (this.authParams.authMethod == 'truveo') {
				var newUrlParams = { 'method': 'truveo.users.checkToken',
					  	  	 	 'token': sessionCookie.token };
				this.submitRestQuery(newUrlParams);
			}
			else { this.onload(); }
		}
		
		// If no session cookie is present then, get any arguments in the URL of the document that loaded this class
		// If the token is in the current URL parameters, then the user is already logged in - so store the
		// token in the sessionCookie.  If the auth is in the URL parameters, then attempt to retrive a
		// session token using the auth key.
		else {
			urlParams = this.getUrlParameters(window.location.href);
			if ('token' in urlParams) {
				sessionCookie.token = urlParams.token;
				sessionCookie.store();
				this.onload(true);
				this.testSetAuthMethodTruveo();
			}
			else if ('auth' in urlParams) {
				var newUrlParams = { 'method': 'truveo.users.getToken',
					  	  	 	 	 'auth': urlParams.auth };
				this.submitRestQuery(newUrlParams);
			}
			else if ('loginCancel' in urlParams) { this.onload(true); }
			else if ('logout' in urlParams) { this.onload(); }
			else if ('errorCode' in urlParams) { this.onerror(urlParams.errorCode, urlParams.errorMessage); }
			else { this.onload(); } 
		}
	}
}


//*****************************************************************************************************
// 	XMLHTTP JS class is is developed by Alex Serebryakov (#0.9.1)
// 	For more information, consult www.ajaxextended.com
//*****************************************************************************************************

XMLHTTP = function() {

  // The following two options are configurable
  // you don't need to change the rest. Plug & play!
  var _maximumRequestLength = 1500
  
  // This URL must point to the same server where the REST API is located.
  var _apiURL = null;
  this.status = null
  this.statusText = null
  this.responseText = null
  this.responseXML = null
  this.synchronous = false
  this.readyState = 0
  this.callbackContext = null;		// AOL Mods 7.15.06: The object to use as context when calling the callback functions.
  this.onreadystatechange =  function() { }
  this.onXMLHTTPError = function() { }
  this.onXMLHTTPLoad = function() { }
  
  // AOL Mods 7.15.06:
  this.registerCallback = function(obj) {
  	this.callbackContext = obj;
  }

  // AOL Mods 7.15.06:
  this.setXMLHTTPURL = function(url) {
  	_apiURL = url;
  }

  this.abort = function() {
    _stop = true
    _destroyScripts()
  }
  
  this.getAllResponseHeaders = function() {
    // Returns all response headers as a string
    var result = ''
    for (property in _responseHeaders)
      result += property + ': ' + _responseHeaders[property] + '\r\n'
    return result
  }
  
  this.getResponseHeader = function(name) {
    // Returns a response header value
    // Note, that the search is case-insensitive
    for(property in _responseHeaders) {
      if(property.toLowerCase() == name.toLowerCase())
        return _responseHeaders[property]
    }
    return null
  }
  
  this.overrideMimeType = function(type) {
    _overrideMime = type
  }
  
  this.open = function(method, url, sync, userName, password) {
    // Setting the internal values
    if (!_checkParameters(method, url)) return
    _method = (method) ? method : ''
    _url = (url) ? url : ''
    _userName = (userName) ? userName : ''
    _password = (password) ? password : ''
    _setReadyState(1)
  }
  
  this.openRequest = function(method, url, sync, userName, password) {
    // This method is inserted for compatibility purposes only
    return this.open(method, url, sync, userName, password)
  }
  
  this.send = function(data) {
    if (_stop) return
    var src = _createQuery(data)
    _createScript(src)
//    _setReadyState(2)
  }
  
  this.setRequestHeader = function(name, value) {
    // Set the request header. If the defined header
    // already exists (search is case-insensitive), rewrite it
    if (_stop) return
    for(property in _requestHeaders) {
      if(property.toLowerCase() == name.toLowerCase()) {
        _requestHeaders[property] = value; return
      }
    }
    _requestHeaders[name] = value
  }
  
  var _method = ''
  var _url = ''
  var _userName = ''
  var _password = ''
  var _requestHeaders = {
    "HTTP-Referer": document.location,
    "Content-Type": "application/x-www-form-urlencoded"
  }
  var _responseHeaders = { }
  var _overrideMime = ""
  var self = this
  var _id = ''
  var _scripts = []
  var _stop = false
  
  // AOL Mods 7.15.06
  var _throwError = function(description, mythis) {
	  
	// mythis is used to store the context for the current XMLHTTP object. 
  	if (arguments.length < 2) { mythis = self; }

    // Stop script execution and run
    // the user-defined error handler
	if ((mythis.callbackContext != null) && mythis.callbackContext.onXMLHTTPError) { mythis.callbackContext.onXMLHTTPError(description); }
	else { self.onXMLHTTPError(description); }
    self.abort()
    return false
  }
  
  var _createQuery = function(data) {
    if(!data) data = ''
    var headers = ''
    // Truveo mod 9.19.07: encode all request headers
    for (property in _requestHeaders)
      headers += encodeURIComponent(property) + '=' + encodeURIComponent(_requestHeaders[property]) + '&'
    var originalsrc = _method
    + '$' + _id
    + '$' + _userName
    + "$" + _password
    + "$" + headers
    + '$' + _escape(data)
    + '$' + _url
    var src = originalsrc
    var max =  _maximumRequestLength, request = []
    var total = Math.floor(src.length / max), current = 0
    while(src.length > 0) {
      var query = _apiURL + '?'
      + 'multipart' 
      + '$' + _id
      + '$' + current++
      + '$' + total
      + '$' + src.substr(0, max)
      request.push(query)
      src = src.substr(max)
    }
    if(request.length == 1)
      src = _apiURL + '?' + originalsrc
    else
      src = request
    return src
  }
  
  var _checkParameters = function(method, url) {
    // Check the method value (GET, POST, HEAD)
    // and the prefix of the url (http://)
    if(!method)
      return _throwError('Please, specify the query method (GET, POST or HEAD)')
    if(!url)
      return _throwError('Please, specify the URL')
    if(method.toLowerCase() != 'get' &&
      method.toLowerCase() != 'post' &&
      method.toLowerCase() != 'head')
      return _throwError('Please, specify either a GET, POST or a HEAD method')
    if(url.toLowerCase().substr(0,7) != 'http://')
      return _throwError('Only HTTP protocol is supported (http://)')
    return true
  }

  var _createScript = function(src) {
    if ('object' == typeof src) {
      for(var i = 0; i < src.length; i++)
        _createScript(src[i])
      return
    }
    // Create the SCRIPT tag
    var script = document.createElement('script')
    script.src = src
    script.type = 'text/javascript'
    if (navigator.userAgent.indexOf('Safari'))
      script.charset = 'utf-8' // Safari bug
    script = document.getElementsByTagName('head')[0].appendChild(script)
    _scripts.push(script)
    return script
  }
  
  var _escape = function(string) {
    // Native escape() function doesn't quote the plus sign +
    string = escape(string)
    string = string.replace('+', '%2B')
    return string
  }
  
  var _destroyScripts = function() {
    // Removes the SCRIPT nodes used by the class
    for(var i = 0; i < _scripts.length; i++)
      if(_scripts[i].parentNode)
        _scripts[i].parentNode.removeChild(_scripts[i])
  }
  
  var _registerCallback = function() {
    // Register a callback variable (in global scope)
    // that points to current instance of the class
    _id = 'v' + Math.random().toString().substr(2)
    window[_id] = self
  }
  
  // AOL Mods 7.15.06
  var _setReadyState = function(number, mythis) { 	
  
  	// mythis is used to store the context for the current XMLHTTP object. 
  	if (arguments.length < 2) { mythis = self; }
	
    // Set the ready state property of the class
    self.readyState = number
	
	// If the callbackContext has been defined and the handler in the context object has been defined, then call
	// the onreadystatechange or onXMLHTTPLoad handler in the context object.  If not, then call the handlers in this xmlhttp object.
	if ((mythis.callbackContext != null) && mythis.callbackContext.onreadystatechange) { mythis.callbackContext.onreadystatechange(); }
	else { self.onreadystatechange(); }
    if(number == 4) {
		if ((mythis.callbackContext != null) && mythis.callbackContext.onXMLHTTPLoad) { mythis.callbackContext.onXMLHTTPLoad(mythis); }
		else { self.onXMLHTTPLoad(); }
	}
  }
  
  var _parseXML = function() {  
      var type = self.getResponseHeader('Content-type') + _overrideMime
      if(!(type.indexOf('html') > -1 || type.indexOf('xml') > -1)) return
      if(document.implementation &&
	      document.implementation.createDocument &&
	      navigator.userAgent.indexOf('Opera') == -1) {
        var parser = new DOMParser()
        var xml = parser.parseFromString(self.responseText, "text/xml")
        self.responseXML = xml
      } else if (window.ActiveXObject) {
        var xml = new ActiveXObject('MSXML2.DOMDocument.3.0')
        if (xml.loadXML(self.responseText))
        	self.responseXML = xml
      } else {
        var xml = document.body.appendChild(document.createElement('div'))
        xml.style.display = 'none'
        xml.innerHTML = self.responseText
        _cleanWhitespace(xml, true)
        self.responseXML = xml.childNodes[0]
        document.body.removeChild(xml)
     }
  }
  
  var _cleanWhitespace = function(element, deep) {
    var i = element.childNodes.length; if(i == 0) return
    do {
      var node = element.childNodes[--i]
      if (node.nodeType == 3 && !_cleanEmptySymbols(node.nodeValue))
        element.removeChild(node)
      if (node.nodeType == 1 && deep)
        _cleanWhitespace(node, true)
    } while(i > 0)
  }

  var _cleanEmptySymbols = function(string) {
    string = string.replace('\r', '')
    string = string.replace('\n', '')
    string = string.replace(' ', '')
  	return (string.length == 0) ? false : true 
  }
 
  this._parse = function(object) { 
    // Parse the received data and set all
    // the appropriate properties of the class
    if(_stop) return
    if(object.multipart) return
    if(!object.success)
      return _throwError(object.description, this)
    _responseHeaders = object.responseHeaders
    this.status = object.status
    this.statusText = object.statusText
    this.responseText = object.responseText
    _parseXML()
    _destroyScripts()
    _setReadyState(4, this);  // AOL Mods 7.15.06
  }
    
   _registerCallback()
}


//*****************************************************************************************************
// 	XML.ObjTree -- XML source code from/to JavaScript object like E4X
//  See http://www.kawa.net/works/js/xml/objtree-e.html.
//	Copyright (c) 2005-2006 Yusuke Kawasaki. All rights reserved.
//*****************************************************************************************************

if ( typeof(XML) == 'undefined' ) XML = function() {};

//  constructor
XML.ObjTree = function () {
    return this;
};

//  class variables
XML.ObjTree.VERSION = "0.23";

//  object prototype
XML.ObjTree.prototype.xmlDecl = '<?xml version="1.0" encoding="UTF-8" ?>\n';
XML.ObjTree.prototype.attr_prefix = '-';

//  method: parseXML( xmlsource )
XML.ObjTree.prototype.parseXML = function ( xml ) {
    var root;
    if ( window.DOMParser ) {
        var xmldom = new DOMParser();
//      xmldom.async = false;           // DOMParser is always sync-mode
        var dom = xmldom.parseFromString( xml, "application/xml" );
        if ( ! dom ) return;
        root = dom.documentElement;
    } else if ( window.ActiveXObject ) {
        xmldom = new ActiveXObject('Microsoft.XMLDOM');
        xmldom.async = false;
        xmldom.loadXML( xml );
        root = xmldom.documentElement;
    }
    if ( ! root ) return;
    return this.parseDOM( root );
};

//  method: parseDOM( documentroot )
XML.ObjTree.prototype.parseDOM = function ( root ) { 
    if ( ! root ) return;

    this.__force_array = {};
    if ( this.force_array ) {
        for( var i=0; i<this.force_array.length; i++ ) {
            this.__force_array[this.force_array[i]] = 1;
        }
    }

    var json = this.parseElement( root );  	// parse root node
    if ( this.__force_array[root.nodeName] ) {
        json = [ json ];
    }
    if ( root.nodeType != 11 ) {            // DOCUMENT_FRAGMENT_NODE
        var tmp = {};
        tmp[root.nodeName] = json;          // root nodeName
        json = tmp;
    } 
    return json;
};

//  method: parseElement( element )
XML.ObjTree.prototype.parseElement = function ( elem ) { 
    //  COMMENT_NODE
    if ( elem.nodeType == 7 ) {
        return;
    }

    //  TEXT_NODE CDATA_SECTION_NODE
    if ( elem.nodeType == 3 || elem.nodeType == 4 ) {
        var bool = elem.nodeValue.match( /[^\x00-\x20]/ );
        if ( bool == null ) return;     // ignore white spaces
        return elem.nodeValue;
    }

    var retval;
    var cnt = {};  

    //  parse attributes
    if ( elem.attributes && elem.attributes.length ) {
        retval = {}; 
        for ( var i=0; i<elem.attributes.length; i++ ) {
            var key = elem.attributes[i].nodeName;
            if ( typeof(key) != "string" ) continue;
            var val = elem.attributes[i].nodeValue;
            if ( ! val ) continue;
            key = this.attr_prefix + key;
            if ( typeof(cnt[key]) == "undefined" ) cnt[key] = 0;
            cnt[key] ++;
            this.addNode( retval, key, cnt[key], val );
        }
    }

    //  parse child nodes (recursive)  -- AOL Mods 7.20.06 - changed condition below from elem.childNodes.length to (elem.childNode.length>=0)
    if ( elem.childNodes && (elem.childNodes.length>=0) ) {
        var textonly = true;
        if ( retval ) textonly = false;        // some attributes exists
        for ( var i=0; i<elem.childNodes.length && textonly; i++ ) {
            var ntype = elem.childNodes[i].nodeType;
            if ( ntype == 3 || ntype == 4 ) continue;
            textonly = false;
        } 
        if ( textonly ) {
            if ( ! retval ) retval = new String();		// AOL Mods: 7/20/06 - Changed from "" to 'new String()'
            for ( var i=0; i<elem.childNodes.length; i++ ) {
                retval += elem.childNodes[i].nodeValue;
            } 
        } else { 
            if ( ! retval ) retval = {};
            for ( var i=0; i<elem.childNodes.length; i++ ) {
                var key = elem.childNodes[i].nodeName;
                if ( typeof(key) != "string" ) continue;
                var val = this.parseElement( elem.childNodes[i] );
                if ( ! val ) continue;
                if ( typeof(cnt[key]) == "undefined" ) cnt[key] = 0;
                cnt[key] ++;
                this.addNode( retval, key, cnt[key], val );
            }
        }
    } 
    return retval;
};

//  method: addNode( hash, key, count, value )
XML.ObjTree.prototype.addNode = function ( hash, key, cnts, val ) {
    if ( this.__force_array[key] ) {
        if ( cnts == 1 ) hash[key] = [];
        hash[key][hash[key].length] = val;      // push
    } else if ( cnts == 1 ) {                   // 1st sibling
        hash[key] = val;
    } else if ( cnts == 2 ) {                   // 2nd sibling
        hash[key] = [ hash[key], val ];
    } else {                                    // 3rd sibling and more
        hash[key][hash[key].length] = val;
    }
};

//  method: writeXML( tree )
XML.ObjTree.prototype.writeXML = function ( tree ) {
    var xml = this.hash_to_xml( null, tree );
    return this.xmlDecl + xml;
};

//  method: hash_to_xml( tagName, tree )
XML.ObjTree.prototype.hash_to_xml = function ( name, tree ) {
    var elem = [];
    var attr = [];
    for( var key in tree ) {
        if ( ! tree.hasOwnProperty(key) ) continue;
        var val = tree[key];
        if ( key.charAt(0) != this.attr_prefix ) {
            if ( typeof(val) == "undefined" || val == null ) {
                elem[elem.length] = "<"+key+" />";
            } else if ( typeof(val) == "object" && val.constructor == Array ) {
                elem[elem.length] = this.array_to_xml( key, val );
            } else if ( typeof(val) == "object" ) {
                elem[elem.length] = this.hash_to_xml( key, val );
            } else {
                elem[elem.length] = this.scalar_to_xml( key, val );
            }
        } else {
            attr[attr.length] = " "+(key.substring(1))+'="'+(this.xml_escape( val ))+'"';
        }
    }
    var jattr = attr.join("");
    var jelem = elem.join("");
    if ( typeof(name) == "undefined" || name == null ) {
        // no tag
    } else if ( elem.length > 0 ) {
        if ( jelem.match( /\n/ )) {
            jelem = "<"+name+jattr+">\n"+jelem+"</"+name+">\n";
        } else {
            jelem = "<"+name+jattr+">"  +jelem+"</"+name+">\n";
        }
    } else {
        jelem = "<"+name+jattr+" />\n";
    }
    return jelem;
};

//  method: array_to_xml( tagName, array )
XML.ObjTree.prototype.array_to_xml = function ( name, array ) {
    var out = [];
    for( var i=0; i<array.length; i++ ) {
        var val = array[i];
        if ( typeof(val) == "undefined" || val == null ) {
            out[out.length] = "<"+name+" />";
        } else if ( typeof(val) == "object" && val.constructor == Array ) {
            out[out.length] = this.array_to_xml( name, val );
        } else if ( typeof(val) == "object" ) {
            out[out.length] = this.hash_to_xml( name, val );
        } else {
            out[out.length] = this.scalar_to_xml( name, val );
        }
    }
    return out.join("");
};

//  method: scalar_to_xml( tagName, text )
XML.ObjTree.prototype.scalar_to_xml = function ( name, text ) {
    if ( name == "#text" ) {
        return this.xml_escape(text);
    } else {
        return "<"+name+">"+this.xml_escape(text)+"</"+name+">\n";
    }
};

//  method: xml_escape( text )
XML.ObjTree.prototype.xml_escape = function ( text ) {
    return text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
};


// The constructor function: creates a cookie object for the specified
// document, with a specified name and optional attributes.
// Arguments:
//   document: The Document object that the cookie is stored for. Required.
//   name:     A string that specifies a name for the cookie. Required.
//   hours:    An optional number that specifies the number of hours from now
//             that the cookie should expire.
//   path:     An optional string that specifies the cookie path attribute.
//   domain:   An optional string that specifies the cookie domain attribute.
//   secure:   An optional Boolean value that, if true, requests a secure cookie.
//
function Cookie(document, name, hours, path, domain, secure)
{
    // All the predefined properties of this object begin with '$'
    // to distinguish them from other properties which are the values to
    // be stored in the cookie.
    this.$document = document;
    this.$name = name;
    if (hours)
        this.$expiration = new Date((new Date()).getTime() + hours*3600000);
    else this.$expiration = null;
    if (path) this.$path = path; else this.$path = null;
    if (domain) this.$domain = domain; else this.$domain = null;
    if (secure) this.$secure = true; else this.$secure = false;
}

// This function is the store() method of the Cookie object.
Cookie.prototype.store = function () {
    // First, loop through the properties of the Cookie object and
    // put together the value of the cookie. Since cookies use the
    // equals sign and semicolons as separators, we'll use colons
    // and ampersands for the individual state variables we store 
    // within a single cookie value. Note that we escape the value
    // of each state variable, in case it contains punctuation or other
    // illegal characters.
    var cookieval = "";
    for(var prop in this) {
        // Ignore properties with names that begin with '$' and also methods.
        if ((prop.charAt(0) == '$') || ((typeof this[prop]) == 'function')) 
            continue;
        if (cookieval != "") cookieval += '&';
        cookieval += prop + ':' + escape(this[prop]);
    }

    // Now that we have the value of the cookie, put together the 
    // complete cookie string, which includes the name and the various
    // attributes specified when the Cookie object was created.
    var cookie = this.$name + '=' + cookieval;
    if (this.$expiration)
        cookie += '; expires=' + this.$expiration.toGMTString();
    if (this.$path) cookie += '; path=' + this.$path;
    if (this.$domain) cookie += '; domain=' + this.$domain;
    if (this.$secure) cookie += '; secure';

    // Now store the cookie by setting the magic Document.cookie property.
    this.$document.cookie = cookie;
}

// This function is the load() method of the Cookie object.
Cookie.prototype.load = function() { 
    // First, get a list of all cookies that pertain to this document.
    // We do this by reading the magic Document.cookie property.
    var allcookies = this.$document.cookie;
    if (allcookies == "") return false;

    // Now extract just the named cookie from that list.
    var start = allcookies.indexOf(this.$name + '=');
    if (start == -1) return false;   // Cookie not defined for this page.
    start += this.$name.length + 1;  // Skip name and equals sign.
    var end = allcookies.indexOf(';', start);
    if (end == -1) end = allcookies.length;
    var cookieval = allcookies.substring(start, end);

    // Now that we've extracted the value of the named cookie, we've
    // got to break that value down into individual state variable 
    // names and values. The name/value pairs are separated from each
    // other by ampersands, and the individual names and values are
    // separated from each other by colons. We use the split method
    // to parse everything.
    var a = cookieval.split('&');    // Break it into array of name/value pairs.
    for(var i=0; i < a.length; i++)  // Break each pair into an array.
        a[i] = a[i].split(':');

    // Now that we've parsed the cookie value, set all the names and values
    // of the state variables in this Cookie object. Note that we unescape()
    // the property value, because we called escape() when we stored it.
    for(var i = 0; i < a.length; i++) {
        this[a[i][0]] = unescape(a[i][1]);
    }

    // We're done, so return the success code.
    return true;
}

// This function is the remove() method of the Cookie object.
Cookie.prototype.remove = function() {
    var cookie;
    cookie = this.$name + '=';
    if (this.$path) cookie += '; path=' + this.$path;
    if (this.$domain) cookie += '; domain=' + this.$domain;
    cookie += '; expires=Fri, 02-Jan-1970 00:00:00 GMT';

    this.$document.cookie = cookie;
}

//-->
