/*
// JaxScript 1.0
// JavaScript Algorithms for XML Web Services
// http://www.jaxcore.com/jaxscript/
//
// Copyright (c) 2010, Dan Steinman <info@jaxcore.com>
// JaxScript is a registered trademark of JaxCore (www.jaxcore.com).
// 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 "JaxCore", nor "Dan Steinman" 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 HOLDER 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.
*/

/*
About:
JaxScript is a JavaScript language extension that adds features commonly required in today's 
web applications.  JaxScript is written using simple, functional, stateless algorithms 
that contain no uses of prototype, classes, this, or new.  Visit JaxCore.com for 
documentation, demos, and tutorials.  JaxCore is developing a new commerical JavaScript 
class architecture and application framework that will elevate JaxScript's capabilities 
even further.  Sign up to our newsletter (http://www.jaxcore.com/newsletter/) to recieve 
more info as our projects progress.
*/

var jaxdoc = (function() {
	var library = {};
	
	function findParent(id) {
		var p = library;
		if (id.indexOf('.')) {
			var s = id.split('.');
			for (var i=0;i<s.length-1;i++) {
				var node = get(p,s[i]);
				if (node) p = node;
				else return null; // invalid id
			}
		}
		return p;
	}
	
	function get() {
		var l = arguments.length;
		if (l==0) return library;
		else if (l==1) {
			var a = arguments[0];
			if (a.indexOf('.')) {
				return get(findParent(a),a.split('.').pop());
			}
			else if (library[a]) {
				return library[a];
			}
		}
		else if (l==2) {
			var parent = arguments[0];
			var n = arguments[1];
			var t = ['class','function','property','method','package','literal','library'];
			for (var i in t) {
				var p = parent[t[i]];
				if (p) {
					for (var j=0;j<p.length;j++) {
						if (p[j].nodeName==n) return p[j];
					}
				}
			}
		}
	}
	
	function make(type) {
		return function(id,def,o) {
			var p = findParent(id);
			if (!p) {
				if (self.console&&self.console.error) self.console.error('jaxdoc error: no parent node for '+id);
				return;
			}
			if (!p[type]) p[type] = [];
			var n = id.split('.');
			if (!o) o = {};
			o.nodeName = n.pop();
			o.nodeType = type;
			o.nodeId = id;
			o.definition = def;
			p[type].push(o);
		}
	}
	
	var addClass = make('class');
	var addFunction = make('function');
	var addProperty = make('property');
	var addMethod = make('method');
	var addLiteral = make('literal');
	var addLibrary = make('library');
	
	function addExample(id, ex) {
		var p = findParent(id);
		var c = id.split('.').pop();
	}
	
	addLibrary('jaxdoc','JavaScript Annotation and Documentation System',{
		title : 'JaxDoc',
		version : 1,
		author : 'Dan Steinman',
		date : new Date(2010,0,1,0,0,0).toGMTString(),
		url : 'http://www.jaxdoc.com',
		license : 'MIT'
	});
	
	return {
		get : get,
		addClass : addClass,
		addExample : addExample,
		addFunction : addFunction,
		addLiteral : addLiteral,
		addLibrary : addLibrary,
		addMethod : addMethod,
		addProperty : addProperty
	}
	
})();
//27

function echo(o) {
	var s = inspect(o);
	var c = window.console;
	if (c && c.log) c.log(s);
}

function get(o,p) {
	if (!!o[p]) return o[p];
}

function set(o,p,v) {
	return o[p] = v;
}

function length(a) {
	return isArray(a)? a.length : 0;
}

function hasProperty(o,p) {
	if (isString(p)) {
		if (p.indexOf('.')>0) {
			var s = p.split('.');
			for (var i=0;i<s.length;i++) {
				o = o[s[i]];
				if (!o) return false;
			}
			return true;
		}
		else return typeof o[p]!='undefined';
	}
}

// exists(document);
// exists(document,'body')
// exists(document,'body.childNodes')
// exists(document,'body','location')
// exists(document,'body.childNodes','location.href')
// exists(document,['body','location'])
// exists(document,['body.childNodes','location.href'])
function exists(o,p) {
	if (!!o && !!p) {
		if (arguments.length>2) return existsArray(o,arguments,1);
		if (isString(p)) return hasProperty(o,p);
		if (isArray(p)) return existsArray(o,p);
	}
	return false;
}
function existsArray(o,a,start) {
	if (!a.length) return false;
	for (var i=start||0;i<a.length;i++)
		if (!exists(o,a[i])) return false;
	return true;
}


var type = {};

function enumerate(s) {
	var o = {};
	if (isString(s)) s = s.split(",");
	if (isArray(s)) {
		for (var i in s)
			o[s[i]] = parseInt(i)+1;
		return o;
	}
}
jaxdoc.addFunction('enumerate','converts a comma separated string of words (or array of words) into an enumerable object',{
	param : [
		{types:type.String},
		{types:type.Array},
	],
	returns : type.Literal
});

function getFields(o) {
	var a = [];
	for (var i in o) {
		a[a.length] = i;
	}
	return a;
}
jaxdoc.addFunction('getFields','returns the fields of an object as an array of strings',{
	param : {obj : type.Object},
	returns : type.Array
});

function enumField(enm,field) {  // adds a field to an enumerable
	var i, c = 0;
	for (i in enm) {
		c = get(Math,'max')(c,enm[i]);
	}
	enm[field] = ++c;
	return c;
}

function isType(t,o) {
	var f = self['is'+t];
	if (isFunction(f)) return f(o);
	return false;
}

function areType(t,a) {
	var l, f, i;
	if (!!t && isArray(a)) {
		l = length(a);
		for (i=0;i<l;i++) {
			f = self['is'+t];
			if (isFunction(f)) {
				if (!f(a[i])) return false;
			}
		}
		return true;
	}
	return false;
}

function defineType(t, fn, desc) {
	//t = ucfirst(t);
	if (typeof self['is'+t]=='undefined') {
		var v = enumField(type,t);
		self['is'+t] = fn;
		//echo('defined type.'+t+' ('+v+') as '+(desc||'[unknown]'));
	}
	else echo('addType() error: '+name+' is an invalid type');
}

// DISCRETE TYPES:

defineType('Null', function(o) {
	return o==undefined || typeof o=='undefined' || o==null || o=='';
},'an empty variable');

defineType('True', function(o) {
	return o===true;
},'true');

defineType('False', function(o) {
	return o===false;
},'false');

defineType('Boolean', function(o) {
	return isTrue(o) || isFalse(o);
},'true or false');

defineType('Integer', function(o) {
	return parseInt(o)==o;
},'a non-floating point number');

defineType('Float', function(o) {
	return parseFloat(o)==Number(o);
},'an integer or floating point number');

defineType('String', function(o) {
	return typeof o==='string';
},'JavaScript String object');

defineType('Object', function(o) {
	return typeof o==='object';
},'a JavaScript object of any type');

defineType('Array', function(o) {
	return Object.prototype.toString.apply(o) === '[object Array]';
},'a JavaScript Array object');

defineType('Function', function(f) {
	return typeof f=='function';
},'a JavaScript function of any type');

defineType('Literal', function(o) {
	if (isObject(o)) {
		var c = 0, i, v;
		for (i in o.prototype) c++
		if (c==0) {
			for (i in o) {
				v = o[i];
				if (isObject(v) || isFunction(v)) return false;
			}
			return true;
		}
	}
	return false;
},'a JavaScript object containing no functions or objects');

defineType('Enum', function(o) {
	if (isLiteral(o)) {
		for (var i in o) {
			if (!isInteger(o[i])) return false;
		}
		return true;
	}
	return false;
},'a JavaScript literal containing only integers');

defineType('XML', function(o) {
	if (isString(o)) {
		o = trim(o);
		if (o.indexOf('<')>-1 && o.indexOf('>')>-1) return true; // is that enough?
	}
},'a string containing xml tags');

defineType('Tag', function(o,type) {
	return isNode(o) && !!o.nodeName && o.nodeName==type;
},'a Node whose nodeName is defined as "type"');

// 19

function arrayContains(a,o) {x
	return arrayIndexOf(a,o)>-1;
};
jaxdoc.addFunction('arrayContains','returns true if the array contains the given object',{
	param : {
		arr : type.Array,
		obj : type.Object
	},
	returns : type.Array
});

function arrayIndexOf(a,o,s) {
	if (isFunction(a.indexOf)) return a.indexOf(o);
	for (var i=(s||0); i<a.length; i++)
		if (a[i] === o) return i;
	echo('arrayRemove() : object '+inspect(o)+' is not a member of Array('+inspect(a)+')');
	return -1;
};
jaxdoc.addFunction('arrayIndexOf','returns the index of the array which contains the given object',{
	param : {
		arr : type.Array,
		obj : type.Object,
		start : type.Integer
	},
	returns : type.Array
});

function arrayRemove(a,o) {
	if (isArray(a)) {
		var i = arrayIndexOf(a,o);
		if (i>-1) a.splice(i,1);
		return a;
	}
	echo('arrayRemove() : argument 0 is not an array: '+inspect(a));
}
jaxdoc.addFunction('arrayRemove','removes a member from an array',{
	param : {
		arr : 'Object[]',
		member : 'Object'
	},
	returns : type.Array
});

// dialog(window,'stuff',1,function(){},{});
// if you click cancel it won't show any more
function dialog() {
	if (self.noDialog) return;
	var o,s='';
	for (var i=0;i<arguments.length;i++) {
		o = arguments[i];
		if (isObject(o)) s += inspect(o);
		else if (s.toString) s += o.toString();
		if (i<arguments.length-1) s += "\n";
	}
	if (!confirm(s)) {
		if (confirm("Quit?")) self.noDialog = true;
	}
}

function isReserved(w) {
	// JavaScript Reserved Words:
	var r = 'abstract,as,boolean,break,byte,case,catch,char,class,continue,const,debugger,'+
	'default,delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,'+
	'goto,if,implements,import,in,instanceof,int,interface,is,long,namespace,native,new,'+
	'null,package,private,protected,public,return,short,static,super,switch,synchronized,'+
	'this,throw,throws,transient,true,try,typeof,use,var,void,volatile,while,with';
	return arrayContains(r.split(','),w);
}
jaxdoc.addFunction('id','a shortcut for document.getElementById, but if arguments[0] is an object just return it, otherwise lookup using document.getElementById()',{
	param : {word : type.String},
	returns : type.Boolean
});

function getDocument(d) {
	if (!!d) return d;
	else return self.document;
}
jaxdoc.addFunction('getDocument','helper function to return self.document in a safe way if none was chosen',{
	param : [
		{'null' : null},
		{str : type.Date}
	],
	returns : type.Integer
});

function ucfirst(s) {
	return s.charAt(0).toUpperCase()+s.substring(1);
}
jaxdoc.addFunction('ucfirst','upper-cases the first character and returns the new string',{
	param : {str : type.String},
	returns : type.String
});

function getSeconds(d) {
	if (!d) d = new Date();
	return d.getTime()/1000;
}
jaxdoc.addFunction('getSeconds','returns the number of seconds from January 1, 1970, or from the given date',{
	param : [
		{'null' : null},
		{str : type.Date}
	],
	returns : type.Integer
});

function round(n,d) {
	n = parseFloat(n);
	if (n!=0) {
		n = parseFloat(n);
		var p;
		p = !d? 1 : Math.pow(10,d||2);
		return Math.round(n*p)/p;
	}
	return 0;
}
jaxdoc.addFunction('round','rounds a floating point number to the given number of decimal points (2 by default)',{
	param : [
		{'null' : type.Null},
		{str : type.Date},
		{str : type.Date, decimals : type.Integer}
	],
	returns : type.Integer
});

function trim(s) {
	return isString(s)? s.replace(/^\s+|\s+$/g,'') : '';
};
jaxdoc.addFunction('trim','strips whitespace from the start and end of a string and returns the new string',{
	param : {str : type.String},
	returns : 'string'
});

function inspect(o,r) {
	if (isString(o)) return o;
	if (isFloat(o)) return o;
	if (isObject(o)) {
		var s = '{\n',t;
		var b = (r==false)?'\t\t':'\t';
		var v;
		for (var i in o) {
			v = o[i];
			if (isString(v)) s += b+i+' : "'+v.replace('"','\"')+'"';
			else if (isBoolean(v)) s += b+i+' : '+(v?'true':'false');
			else if (isFloat(v)) s += b+i+' : '+o[i];
			else if (isFunction(v)) s += b+i+' : "[Function]"';
			else if (isArray(v))  s += b+i+' : "['+t+']"';
			else if (isObject(v)) s += b+i+' : "[Object]"';
			s += ',\n';
		}
		s = s.substring(0,s.length-2)+'\n';
		if (r==false) s+= '\t';
		s += '}';
		return s;
	}
};
jaxdoc.addFunction('inspect','returns an examination of any object as a javascript literal, this is a quick way to take a snapshot of an unknown object for output to the console',{
	param : [
		{elementId : type.String},
		{subject : type.Object},
		{subject : type.Object, recurse:type.Boolean}
	],
	returns : type.String
});

function copy(t,s,p) {
	if (!t) return alert('copy() target does not exist'); //t = {};
	var i,j;
	if (!!p) {
		for (j in p) {
			i = p[j];
			//if (!s[i]) echo('copy(1) : sourceObj property "'+i+'" is null'); // enable for debugging
			t[i] = s[i];
		}
	}
	else for (i in s) {
		//if (!s[i]) echo('copy(2) : sourceObj property "'+i+'" is null'); // enable for debugging
		t[i] = s[i];
	}
	return t;
}
jaxdoc.addFunction('copy','copies properties from sourceObj to targetObj and returns targetObj',{
	param : [
		{targetObj : type.Object},
		{targetObj : type.Object, sourceObj : type.Object},
		{targetObj : type.Object, sourceObj : type.Object, properties:type.Object}
	],
	returns : type.Object
});


var jaxscript = (function() {

	var start = getSeconds();
	var runs = [];
	var busy = true;
	var loaded = false;
	var supported = false;
	var version = 0.1;
	
	jaxdoc.addLibrary('jaxscript','JavaScript Algorithms for XML Web Services',{
		title : 'JaxScript',
		version : version,
		date : new Date(2010,0,1,0,0,0).toGMTString(),
		author : 'Dan Steinman',
		url : 'http://www.jaxcore.com/jaxscript',
		license : 'MIT'
	});
	
	function main() {
		busy = false;
		loaded = true;
		for (var i=0;i<runs.length;i++) {
			runs[i]();
		}
		echo('jaxscript types are '+getFields(type).join(', '));
		echo('jaxscript client is '+getUserAgent());
		echo('jaxscript server is '+location.host);
		echo('jaxscript '+version+' loaded in '+round(getSeconds()-start,3)+' seconds');
		
	};
	
	var features = {};
	
	function getUserAgent() {
		return (typeof navigator=='object')? navigator.userAgent : '';
	}
	
	function getPlugin(s) {
		var a = navigator.plugins;
		if (a.length>0)
			for (i=0;i<a.length;i++)
				if (a[i].name.indexOf(s) > -1)
					return a[i];
	}
	
	jaxdoc.addFunction('jaxscript.getPlugin','returns a web browser plugin by the given name',{
		param : {name : type.String},
		returns : type.Object
	});
	
	function addFeature(id,name,test) {
		features[id] = {
			name : name,
			test : test
		};
	}
	jaxdoc.addFunction('jaxscript.addFeature','adds a feature checking function to the client library',{
		param : {
			id : type.String, 
			name : type.String,
			test : type.Function
		},
		returns : type.Object
	});
	
	function supports(id,v) {
		if (features[id]) return features[id].test(v);
		return false;
	}
	
	addFeature('activex','ActiveX',function() {
		return typeof ActiveXObject=='object' || typeof ActiveXObject=='function';
	});
	
	addFeature('fixed','CSS-P Fixed Position extension',function(v) {
		// TO DO: Internet Explorer supports css fixed only if a !DOCTYPE is specified
		return !supports('activex');
	});
	addFeature('cookies','Browser cookies',function(v) {
		document.cookie = "1";
		return document.cookie.indexOf("1")>-1;
	});
	addFeature('dom','Document Object Model',function(v) {
		if (!v) v = 1;
		var cv = 0;
		// )
		if (
			( typeof document.addEventListener=='function' && 
			(typeof DOMParser=='function'||typeof DOMParser=='object') && 
			(typeof XMLSerializer=='function'||typeof XMLSerializer=='object') && 
			(typeof XSLTProcessor=='function'||typeof XSLTProcessor=='object') ) || 
			(supports('activex') && typeof self.attachEvent=='object' && typeof document.getElementById=='object')
			) cv = 2;
		return cv >= v;
	});
	addFeature('ecma','ECMAScript',function(v) {
		if (!v) v = 1;
		var cv = 0;
		if (typeof [].pop=='function' && typeof parseFloat=='function' && typeof decodeURIComponent=='function')
			cv = 3;
		return cv >= v;
	});
	addFeature('flash','Adobe Shockwave Flash',function(v) {
		return !!getPlugin("Shockwave Flash");
		// to do : versions
	});
	addFeature('gecko','Gecko HTML Rendering Engine',function() {
		return getInfo().toLowerCase().indexOf("gecko")>-1 && !supports("webkit");  // webkit pretends to be gecko
	});
	addFeature('webkit','WebKit HTML Rendering Engine',function() {
		return getInfo().toLowerCase().indexOf("webkit")>-1;
	});
	addFeature('safari','Safari version of WebKit',function() {
		return supports("webkit") && getInfo().toLowerCase().indexOf("safari")>-1 && !supports("chrome");  // chrome pretends to be safari
	});
	addFeature('chrome','Chrome version of WebKit',function() {
		return supports("webkit") && getInfo().indexOf("Chrome")>-1;
	});
	addFeature('iphone','Apple iPhone/iPod',function() {
		return supports("webkit") && getInfo().indexOf("iPhone")>-1 || getInfo().indexOf("iPod")>-1;
	});
	addFeature('java','Sun Microsystems Java',function() {
		return typeof java=='object' && !!getPlugin("Java");
	});
	addFeature('js','JavaScript',function(v) {
		if (!v) v = 1;
		var cv = 1.5; // assume client supports 1.5
		if (typeof [].indexOf=='function' && typeof [].forEach=='function')
			cv = 1.6;
		return cv > v;
	});
	addFeature('xhr','XMLHTTPRequest communication',function() {
		return supports('activex') || typeof XMLHttpRequest=='function' || typeof XMLHttpRequest=='object';
	});
	//mousewheel : false,
	//DOMMouseScroll : false
	
	if (supports('dom',2) && supports('xhr') && supports('ecma',3)) {
		if (typeof document.onreadystatechange=='object') { // for IE 5/6 support
			supported = true;
			document.onreadystatechange = function() {
				if (document.readyState == "complete") {
					main();
				}
			};
		}
		else if (typeof document.addEventListener=='function') {
			supported = true;
			document.addEventListener("DOMContentLoaded", main, true);  // ff, opera, safari
		}
	}
	
	if (!isSupported()) unsupported();
	
	function isBusy() {
		return busy;
	}
	jaxdoc.addFunction('jaxscript.isBusy','returns true before the DOM has been initialized and during XHR requests',{
		returns : type.Boolean
	});
	
	function isLoaded() {
		return !busy && loaded;
	}
	jaxdoc.addFunction('jaxscript.isLoaded','returns whether the DOM has been initialized and all required JaxScript libraries are loaded',{
		returns : type.Boolean
	});
	
	function isSupported() {
		return supported;
	}
	jaxdoc.addFunction('jaxscript.isSupported','returns whether the web browser is supported by JaxScript',{
		returns : type.Boolean
	});
	
	function unsupported() {
		if (confirm('Sorry. Your web browser is unsupported.\n\nWould you like to upgrade?'))
			top.location = 'http://www.jaxcore.com/upgrade/';
	};
	jaxdoc.addFunction('unsupported','this function is called if the web browser is not supported by JaxScript, it is intended that you overwrite this function with your desired handling');
	
	function run(f) {
		runs.push(f);
	};
	jaxdoc.addFunction('jaxscript.run','defines a handler function to be executed after the DOM is initialized and all required JaxScript libraries are loaded',{
		param : {handler : type.Function}
	});
	
	// I haven't fully decided if request() should go in dom library
	function request(o) {
		if (!supports('xhr')) return unsupported();
		o = o||{};
		if (!o.method) o.method = "get";
		if (o.async==null) o.async = true;
		if (o.cache===false) url += url.indexOf('?')>0?'&':'?'+'nocache='+Math.random().toString().substring(2);
		var r = (typeof XMLHttpRequest=='function' || typeof XMLHttpRequest=='object')? new XMLHttpRequest():new ActiveXObject("Microsoft.XMLHTTP");
		r.open(o.method,o.url,o.async);
		r.onreadystatechange = function(){
			if (r.readyState==4) {
				if (r.status == 200 && typeof o.handler=='function') return o.handler(r);
				if (typeof o.errorHandler=='function') o.errorHandler(r);
			}
		};
		r.send('');
		return r;		
	};
	jaxdoc.addFunction('jaxscript.request',{
		definition : 'this is JaxScript\'s XMLHttpRequest function, options are {url,method,async,data,handler,errorHandler}, returns the XMLHttpRequest result object',
		param : {options:type.Literal},
		returns : type.Object
	});
	
	return {
		getPlugin : getPlugin,
		getUserAgent : getUserAgent,
		supports : supports,
		addFeature : addFeature,
		isBusy : isBusy,
		isLoaded : isLoaded,
		isSupported : isSupported,
		unsupported : unsupported,
		run : run,
		request : request
	};

})();

// 36
// DOM Types:

defineType('Node', function(n,t) {
	var b = (isObject(n) && exists(n,['nodeType','nodeName']));
	if (b && isString(t))
		return n.nodeName.toUpperCase()==t.toUpperCase();
	return b;
},'a DOM Node');

defineType('Window', function(o) {
	return o==window || o.nodeName=='frame' || o.nodeName=='iframe';  // test it
},'a browser window or frame element');

defineType('Document', function(o) {
	return isNode(o) && o.nodeType==1||o.nodeType==9;
},'a DOM Document');

defineType('Element', function(o) {
	return isNode(o) && isChild(o,document.body);
},'a DOM Node that is a child of document.body');

defineType('Event', function(e) {
	if (!!self.event) return e===self.event;
	else return (isObject(e) && !!e.type);  // o.src should maybe make a better test
},'a DOM Event object');

// DOM FUNCTIONS:

function dimensions(o,frame) {
	var x=y=w=h=0;
	var f = !!frame? frame : window;
	var d = f.document;
	var b = d.body;
	var de = d.documentElement;
	
	if (o=='#document') {
		// document size
		if (exists(f,'innerWidth','innerHeight','scrollMaxY','scrollMaxY')) { // ff
			w = f.innerWidth + f.scrollMaxX;
			h = f.innerHeight + f.scrollMaxY;
		}
		else if (exists(b,'scrollWidth','offsetWidth','scrollHeight','offsetHeight')) { // dom
			w = (b.scrollWidth > b.offsetWidth)? b.scrollWidth : b.offsetWidth;
			h = (b.scrollHeight > b.offsetHeight)? b.scrollHeight : b.offsetHeight;
		}
		else if (exists(f,'innerHeight','innerHeight')) { // ie
			w = f.innerWidth,
			h = f.innerHeight
		}
		else if (exists(b,'offsetWidth','offsetHeight')) {
			echo('dimensions(document) : falling back to offsetWidth/Height');
			w = b.offsetWidth;
			h = b.offsetHeight;
		}
		
		// document scroll
		if (exists(b,'scrollLeft','scrollTop')) {
			x = b.scrollLeft;
			y = b.scrollTop;
		}
		if (x==0 && y==0 && exists(de,'scrollLeft','scrollTop')) {
			x = de.scrollLeft;
			y = de.scrollTop;
		}
		
		//echo('dimensions(document) = w:'+w+', h:'+h+', scroll x:'+x+', y:'+y);
	}
	else if (o=='#window') {
		// inner width/height of the window
		if (exists(f,'innerWidth','innerHeight')) {
			w = f.innerWidth;
			h = f.innerHeight;
		}
		else if (exists(b,'clientWidth','clientHeight')) {
			w = b.clientWidth;
			h = b.clientHeight;
		}
		else if (exists(de,'clientWidth','clientHeight')) {
			w = de.clientWidth;
			h = de.clientHeight;
		}
		
		// window position
		if (jaxscript.supports('activex')) {
			x = f.screenLeft;
			y = f.screenTop;
		}
		else {
			x = f.screenX;
			y = f.screenY;
		}
		
		//echo('dimensions(window) = x:'+x+', y:'+y+', w:'+w+', h:'+h,'i');
	}
	else if (o=='#screen') {
		w = screen.width;
		h = screen.height;
	}
	else {
		o = id(o);
		if (!!o) {
			var sx = 0;
			if (b && b.scrollLeft) sx = b.scrollLeft;
			if (de && de.scrollLeft) sx = de.scrollLeft;
			var sy = 0;
			if (b && b.scrollTop) sy = b.scrollTop;
			if (de && de.scrollTop) sy = de.scrollTop;
			
			var r;
			if (o.getBoundingClientRect) { // CSS3
				r = o.getBoundingClientRect();
				x = r.left + sx;
				y = r.top + sy;
				w = r.right - r.left;
				h = r.bottom - r.top;
			}
			else if (d.getBoxObjectFor) { // FF
				r = d.getBoxObjectFor(o);
				x = r.x;
				y = r.y;
				w = r.width;
				h = r.height;
			}
			else {  // the old fashioned way
				return offsetDimensions(o);
			}
		}
		//echo('dimensions('+(exists(o,'nodeName','id')?o.nodeName+'#'+o.id:'element')+') = x:'+x+', y:'+y+', w:'+w+', h:'+h);
	}
	return {x:round(x),y:round(y),w:round(w),h:round(h)};
};
jaxdoc.addFunction('dimensions','returns {x,y,w,h} containing the absolute position (x=left,y=top) and size (w=width,h=height) of the element, also includes window and document (document scroll position is available in the {x,y} properties)',{
	param : [
		{node : type.Node},
		{win : type.Window},
		{doc : type.Document}
	],
	returns : type.Literal
});

function offsetDimensions(o) {
	var x=y=0;
	var w = o.offsetWidth;
	var h = o.offsetHeight;
	while (o.offsetParent) {
		x += o.offsetLeft;
		y += o.offsetTop;
		o = o.offsetParent;
	}
	var d = document;
	x += (d.body.scrollLeft || d.documentElement.scrollLeft || 0);
	y += (d.body.scrollTop || d.documentElement.scrollTop || 0);
	return {x:x,y:y,w:w,h:h};
};
jaxdoc.addFunction('dimensions','returns {x,y,w,h} containing the offset position and size of an element, this is used as a backup to the dimensions() function',{
	param : {elm : type.Element},
	returns : type.Literal
});

function print(s,d) {
	if (jaxscript.isLoaded()) dom.append(s);
	else document.write(s);
}
jaxdoc.addFunction('print','prints text to the document, this can be called before or after the DOM initialized',{
	param : [
		{str : type.String},
		{str : type.String, doc : type.DOMDocument}
	]
});

function println(s,d) {
	if (!jaxscript.isBusy() && jaxscript.isLoaded()) {
		var e = document.createElement('div');
		e.className = "println";
		e.innerHTML = s;
		dom.append(e); 
	}
	else document.write('<div class="println">'+s+'<\/div>');
}
jaxdoc.addFunction('println','outputs a new line of text to the document by wrapping the text in a DIV element, this can be called before or after the DOM initialized',{
	param : [
		{str : type.String},
		{str : type.String, doc : type.DOMDocument}
	]

});

// DOM LIBRARY:

var dom = jaxscript.dom = (function() {

	jaxdoc.addLibrary('dom','DOM-related functions and server communication');
	
	function id(s,d) {
		if (isObject(s)) return s;
		if (isString(s)) {
			d = getDocument(d);
			if (exists(d,'getElementById')) {
				var o = d.getElementById(s);
				if (!!o) return o;
				//echo('id() : element not found using document.getElementById("'+s+'")');
			}
		}
	};
	
	jaxdoc.addFunction('id','a shortcut for document.getElementById, but if arguments[0] is an object just return it, otherwise lookup using document.getElementById()',{
		param : [
			{id : type.String},
			{node : type.Node}
		],
		returns : type.Node
	});
	
	function addClass(n,c) { // needs testing
		n = id(n);
		if (n.nodeType==1) {
			if (!hasClass(n,c)) {
				if (n.className.indexOf(' ')) n.className = " "+c;
				else n.className = c;
			}
		}
		return n;
	};
	jaxdoc.addFunction('dom.addClass','adds a class name to an element',{
		param : [
			{node:type.Node, className:type.String},
			{nodeId:type.String, className:type.String}
		]
	});
	
	function children(n) {
		if (n && n.childNodes && n.childNodes.length>0) return n.childNodes;
		else return [];
	}
	
	function findParent(n,tag) {
		n = id(n);
		while (n.parentNode) {
			if (n.parentNode.tagName.toUpperCase()==tag.toUpperCase())
				return n.parentNode;
			n = n.parentNode;
		}
	};
	jaxdoc.addFunction('dom.findParent','travels up the DOM tree and returns the node that matches the given tag name',{
		param : [
			{node:type.Node, tagName:type.String},
			{nodeId:type.String, tagName:type.String}
		],
		returns : type.Node
	});
	
	function findClass(tagAndOrClass,parentNode) {
		var node = !!parentNode? id(parentNode) : document.body;
		var r = [];
		if (!node) return r;
		var n;
		var dot = tagAndOrClass.indexOf('.');
		if (dot>=0) {  // handle tag + className which is more efficient than searching just by className
			var tagname = tagAndOrClass.substring(0,dot);
			var className = tagAndOrClass.substring(dot+1);
			var nodes = node.getElementsByTagName(tagname);
			for (var i=0;i<nodes.length;i++) {
				nd = nodes[i];
				if (hasClass(nd,className)) r.push(nd);
			}
		}
		else {  // walk through the entire dom tree (try to avoid this when possible)
			walkDOM(node, function(nd) {
				if (hasClass(nd,cname)) r.push(nd);
			});
		}
		return r;
	};
	jaxdoc.addFunction('dom.findClass','examines an element and returns an array of DOM nodes that match the given class name or tag + class name ("className" or "tagName.className")',{
		param : [
			{node:type.Node, tagAndOrClass:type.String},
			{elementId:type.String, tagAndOrClass:type.String}
		],
		returns : type.Array
	});
	
	function hasClass(node,cname) {
		return !!node.className && (node.className==cname || arrayIndexOf(node.className.split(' '),cname)>-1);
	};
	jaxdoc.addFunction('dom.hasClass',{
		definition : 'returns whether the node has a given class name',
		param : [
			{node:type.Node, className:type.String},
			{nodeId:type.String, className:type.String}
		]
	});
	
	function insertTag(o) {
		var s = document.createElement(o.tagName);
		for (var i in o.attributes) {
			s.setAttribute(i,o.attributes[i]);
		}
		// if (o.insertAfter) insertAfter(s, o.insertAfter)
		// else if (o.insertBefore) insertBefore(s, o.insertBefore)
		// else if (o.insertFirst) insertFirst(s, o.insertFirst)
		// else if (o.insertLast) insertLast(s, o.insertLast)
		// will replace the following
		if (o.elementAppend) o.elementAppend.appendChild(s);
		else if (o.elementBefore) o.elementBefore.insertBefore(s,o.elementBrother);
		else document.body.appendChild(s);  // appends to body by default
		
		
		var h = o.handler;
		if (typeof h=='function') {
			if (typeof s.onreadystatechange=='Object') {
				s.onreadystatechange = function() {
					if (s.readyState=='loaded') h();
				};
				return;
			}
			if (/webkit/i.test(ua)) { 
				// script.onload is not available in Safari but document readyState is useful
				var t = setInterval(function() {
					if (document.readyState=='complete') {
						clearInterval(t);
						h();
					}
				},10);
			}
			else s.onload = h;
		}
		return s;
	};
	jaxdoc.addFunction('dom.insertTag',{
		definition : 'inserts an element into the document before or after an existing element (appends to body by default) and returns the node, \
		options are: {tag,handler,attributes,elementAppend,addBefore,elementBrother}',
		param : {options : type.Literal},
		returns : type.Node
	});
	
	function after(n, s) {
		//if (exists(s,'parentNode.insertBefore')) 
		s.parentNode.insertBefore(id(n), s.nextSibling);
	};
	jaxdoc.addFunction('dom.after',{
		definition : 'inserts a new node as a sibling after another node',
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String},
		]
	});

	function before(n, s) {
		//if (exists(s,'parentNode.insertBefore')) 
		s.parentNode.insertBefore(id(n), s);
	}
	jaxdoc.addFunction('dom.before',{
		definition : 'inserts a new node as a sibling after another node',
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String},
		]
	});
	
	function hasChildren(o) {
		return isNode(o) && o.childNodes.length>0;
	}
	
	function prepend(n, p) {
		if (!p && exists(document,'body')) p = document.body;
		if (hasChildren(n)) before(n, p.childNodes[0]);
		else append(n, p);
		return n;
	}
	jaxdoc.addFunction('dom.insertFirst','inserts a new node as the first child of another node',{
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String}
		],
		returns : type.Node
	});
	
	function append(n, p) {
		if (jaxscript.isLoaded()) {
			if (!p && exists(document,'body')) {
				p = document.body;
			}
			if (!!p && !!p.appendChild) p.appendChild(id(n));
		}
		else echo("Error: append() cannot be called before the DOM is initialized");
		return ;
	}
	jaxdoc.addFunction('dom.append',{
		definition : 'inserts a new node as the last child of another node',
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String},
		],
		returns : type.Node
	});

	function isChild(n,p) {
		return isParent(p,n);
	};
	jaxdoc.addFunction('dom.isChild',{
		definition : 'returns whether elementA is a child of elementB',
		param : [
			{elementA:type.Node, elementB:type.Node},
			{elementIdA:type.String, elementIdB:type.Node},
			{elementA:type.Node, elementBid:type.String},
			{elementIdA:type.String, elementIdB:type.String},
		],
		returns : type.Boolean
	});
	
	function isParent(p,n) {
		p = id(p);
		n = id(n);
		if (jaxscript.supports('activex') && typeof p.contains == 'function' && n.nodeType == 1) {  // this is not tested
			return p == n || p.contains(n);
		}
		while (n) {
			if (n===p) return true;
			if (!!n.parentNode) n = n.parentNode;
			else return false;
		}
		return false;
	};
	jaxdoc.addFunction('dom.isParent',{
		definition : 'returns whether elementA is the parent of elementB',
		param : [
			{elementA:type.Node, elementB:type.Node},
			{elementIdA:type.String, elementIdB:type.Node},
			{elementA:type.Node, elementBid:type.String},
			{elementIdA:type.String, elementIdB:type.String},
		],
		returns : type.Boolean
	});
	
	function isSibling(n,s) { // needs testing
		s = id(s);
		var c = id(n).parentNode.childNodes;
		for (var i=0;i<c.length;i++)
			if (c[i]==s) return true;
		return false;
	};
	jaxdoc.addFunction('dom.isSibling',{
		definition : 'returns whether elementA is a sibling of elementB',
		param : [
			{elementA:type.Node, elementB:type.Node},
			{elementIdA:type.String, elementIdB:type.Node},
			{elementA:type.Node, elementBid:type.String},
			{elementIdA:type.String, elementIdB:type.String},
		],
		returns : type.Boolean
	});
	
	function loadCSS(s,fn) {
		/*
		if (!jaxscript.isLoaded()) document.write('<link rel="stylesheet" type="text/css" src="'+js+'"><\/script>');
		*/
		return insertTag({
			tagName : 'link',
			handler : fn,
			attributes : {
				href : s,
				type : 'text/css',
				rel : 'stylesheet'
			},
			elementAppend : document.getElementsByTagName('head')[0]
		});
	};
	jaxdoc.addFunction('dom.loadCSS',{
		definition : 'inserts a script into the head of the document and returns the link node, calls handler function when the file is loaded',
		param : {
			url : type.String,
			handler : type.Function
		},
		returns : type.Node
	});
	
	function loadJS(s,fn) {
		/*
		if (!jaxscript.isLoaded()) {
			document.write('<script type="text/javascript" src="'+js+'"><\/script>');
			if (isFunction(fn)) fn();
		}
		*/
		return insertTag({
			tagName : 'script',
			handler : fn,
			attributes : {
				src : s,
				type : 'text/javascript'
			},
			elementAppend : document.getElementsByTagName('head')[0]
		});
	};
	jaxdoc.addFunction('dom.includeJS',{
		definition : 'appends a javascript file to the head of the document and returns the script node, calls handler function when the file is loaded, and returns the script tag',
		param : {url:type.String,handler:type.Function},
		returns : type.Node
	});

	function loadDOM(url) {
		// loadDOM does not have a handler because it is a synchronous request
		var r = jaxscript.request({
			url : url,
			method : "get",
			async : false
		});
		return r.responseXML;
	};
	jaxdoc.addFunction('dom.loadDOM',{
		definition : 'loads an XML file synchronously and returns the DOM',
		param : {xmlURL:type.String},
		returns : type.DOMDocument
	});
	
	function replaceClass(n,className,newClassName) {
		n = id(n);
		if (hasClass(n,className)) n.className = n.className.replace(className,newClassName);
		return n;
	}; 
	jaxdoc.addFunction('dom.replaceClass',{
		definition : 'replaces an element\'s class name with a new one, and returns the node',
		param : [
			{node:type.Node, className:type.String, newClassName:type.String},
			{nodeId:type.String, className:type.String, newClassName:type.String}
		],
		returns : type.Node
	});
	
	function removeClass(n,c) {
		if (hasClass(n,c)) n.className = trim(n.className.replace(c,'').replace('  ',''));
	};
	jaxdoc.addFunction('dom.removeClass',{
		definition : 'removes a given class name from an element',
		param : [
			{node:type.Node, className:type.String},
			{nodeId:type.String, className:type.String}
		]
	});

	function walk(n, fn) {
		if (n.nodeName && n.childNodes) {
			fn(n);
			n = n.firstChild;
			while (n) {
				walk(n,f);
				n = n.nextSibling;
			}
		}
		else if (isArray(n)) { // also handle an array of nodes
			for (var i in n) {
				fn(n[i]);
			}
		}
	};
	jaxdoc.addFunction('dom.walk',{
		definition : 'examines a DOM node (or array of nodes) and recursively passes each child as a parameter for a handler function',
		param : {
			node : type.Node,
			handler : type.Function
		}
	});
	
	function addEvent(n,e,h,p) {
		n = id(n);
		if (n) {
			if (n.addEventListener) n.addEventListener(e,h,(p==null)?false:p);
			else if (n.attachEvent) n.attachEvent("on"+e,h);
		}
		else echo('addEvent node does not exists');
	}
	jaxdoc.addFunction('dom.addEvent',{
		definition : 'adds an event handler &><\'" to an element',
		param : [
			{element:type.Element,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean},
			{elementId:type.String,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean}
		]
	});
	
	function removeEvent(n,e,h,p) {
		n = id(n);
		if (n) {
			if (n.removeEventListener) n.removeEventListener(e,h,(p==null)?false:p);
			else if (n.detachEvent) n.detachEvent("on"+e,h);
		}
	}
	jaxdoc.addFunction('dom.removeEvent',{
		definition : 'removes an event handler from a given node',
		param : [
			{element:type.Node,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean},
			{elementId:type.String,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean}
		]
	});
	
	function cancelEvent(e) {
		e.cancelBubble = true;
		e.returnValue = false;
		if (e.stopPropagation) e.stopPropagation();
		if (e.preventDefault) e.preventDefault();
		return false;
	};
	jaxdoc.addFunction('dom.cancelEvent',{
		definition : 'cancels event propagation and bubbling',
		param : {e : type.Event},
		returns : type.Boolean
	});
	
	function eventPosition(e) {
		if (window.event) {
			e = window.event;
			var b = document.body;
			return {
				x : e.clientX + b.scrollLeft,
				y : e.clientY + b.scrollTop
			};
		}
		else if (exists(e,'pageX','pageY')) return {x:e.pageX,y:e.pageY};
		else return null;
	};
	jaxdoc.addFunction('dom.eventPosition',{
		definition : 'returns the absolute mouse position (including scroll position) as {x,y}',
		param : {e : type.Event},
		returns : type.Literal
	});
	
	function relatedTarget(e) {
		var r = e.relatedTarget;
		if (r) {
			try {
				r.nodeName;
			}
			catch (e) {
				if (r.nodeType==3) return r.parentNode; // skip text nodes
				echo('error: relatedTarget() had an invalid node');
				return null;
			}
			return r;
		}
		if (window.event) {
			if (e.type=="mouseover" && window.event.fromElement) return window.event.fromElement;
			if (e.type=="mouseout" && window.event.toElement) return window.event.toElement;
		}
	}
	jaxdoc.addFunction('dom.relatedTarget',{
		definition : 'returns the element the mouse came "from" during a mouseover event, or the element the mouse went "to" during a mouseout event',
		param : {e : type.Event},
		returns : type.Node
	});
	
	function eventTarget(e) {
		var t = (e && e.target)? e.target : window.event.srcElement;
		return (t.nodeType == 3)? t.parentNode:t; // skip text nodes
	};
	jaxdoc.addFunction('dom.eventTarget',{
		definition : 'returns the element the mouse moved "to" during a mouseout event',
		param : {e : type.Event},
		returns : type.Node
	});
	
	return {
		id : id,
		addClass : addClass,
		children : children,
		findParent : findParent,
		findClass : findClass,
		hasClass : hasClass,
		insertTag : insertTag,
		
		append : append,
		prepend : prepend,
		after : after,
		before : before,

		isChild : isChild,
		isParent : isParent,
		isSibling : isSibling,
		loadCSS : loadCSS,
		loadJS : loadJS,
		loadDOM : loadDOM,
		removeClass : removeClass,
		replaceClass : replaceClass,
		walk : walk,
		
		addEvent : addEvent,
		removeEvent : removeEvent,
		cancelEvent : cancelEvent,
		eventPosition : eventPosition,
		relatedTarget : relatedTarget,
		eventTarget : eventTarget
	};
	
})();


/* because id() and dimensions() are so commonly used I prefer them to be available outside the dom library */

var id = dom.id;

// 24
// STYLE TYPES:

defineType('CSSDelcaration', function(o) {
	if (isString(o)) return o.indexOf(':')>1;
},'a CSS declaration (the stuff inside CSS brackets)');

defineType('CSSRule', function(o) {
	if (isString(o)) return o.indexOf(':')>1;
},'a CSS Rule');

defineType('Style', isLiteral, 'a JavaScript literal representing CSS declarations');

// STYLE FUNCTIONS:

var style = jaxscript.style = (function() {
	jaxdoc.addLibrary('jaxscript.style','CSS style library');
	
	function clip(n,i) {
		return set(n,{
			clip : (i&&i.length==4)? 'rect('+i[0]+'px '+i[1]+'px '+i[2]+'px '+i[3]+'px)' : 'auto'
		});
	};
	jaxdoc.addFunction('jaxscript.style.clip','sets "clip" style using a 4-element array [top,right,bottom,left]',{
		param : [
			{node:type.Node,clipArray:type.Array},
			{nodeId:type.String,clipArray:type.Array}
		],
		returns : type.Node
	});
	
	function display(n,b) {
		return set(n,{
			display : b? 'block' : 'none'
		});
	};
	jaxdoc.addFunction('jaxscript.style.display','sets the "display" style of an element to either "block" (true) or "none" (false)',{
		param : [
			{node:type.Node,displayed:type.Boolean},
			{nodeId:type.String,displayed:type.Boolean}
		],
		returns : type.Node
	});
	
	function getClip(n) {
		n = id(n);
		var c = n.style.clip;
		if (c && c.indexOf('rect(') == 0) {
			c = c.replace("rect(","");
			c = c.replace(")","");
			var v = c.split(" ");
			for (var i in v) v[i] = parseInt(v[i]);
			return v;
		}
		else return [0, n.offsetWidth, n.offsetHeight, 0];
	};
	jaxdoc.addFunction('jaxscript.style.getClip','returns "clip" style as 4-element array [top,right,bottom,left]',{
		param : {node:type.Node},
		returns : type.Array
	});

	function getOpacity(n) {
		n = id(n);
		return isFloat(n.style.opacity)? parseFloat(n.style.opacity) : 1;
	};
	jaxdoc.addFunction('jaxscript.style.getOpacity','reads the _opacity property set in style.opacity() if not available, otherwise returns 1',{ 
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Integer
	});
	
	function get(n,s) {
		n = id(n);
		if (isString(s)) {
			if (s=="opacity") return isFloat(n.style.opacity)? n.style.opacity : 1;
			if (!!document.defaultView) {
				var v = document.defaultView.getComputedStyle(n,"").getPropertyValue(s);
				return v;
			}
			else if (n.currentStyle) return n.currentStyle[s];
			else if (n.style[s]) return n.style[s];
			else if (isNode(n)) echo('error: style.get() could not obtain style for node '+node.nodeName+'#'+n.id);
		}
	};
	jaxdoc.addFunction('jaxscript.style.get','a shortcut for "element.style.property"',{
		param : [
			{node:type.Node,style:type.String},
			{nodeId:type.String,style:type.String}
		],
		returns : 'Object'
	});
	
	function getXY(n) {
		n = id(n);
		return {
			x : parseInt(get(n,'left')),
			y : parseInt(get(n,'top'))
		}
	};
	jaxdoc.addFunction('jaxscript.style.getXY','returns the left and top position of an element as a JSON set {x,y}',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Literal
	});
	
	var _z = 5000; // assume this is high enough
	function maxZ(n) {
		return set(n,{zIndex:++_z});
	};
	jaxdoc.addFunction('jaxscript.style.maxZ','sets the z-index of an element to be the top most layer',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Node
	});
	
	function getSize(n) {
		return {
			w : parseInt(get(n,'width')),
			h : parseInt(get(n,'height'))
		}
	};
	jaxdoc.addFunction('jaxscript.style.getSize','returns an element\'s width and height styles as an integer set {w,h}',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Literal
	});
	
	function left(n,x) {
		return set(n,{left:px(x)});
	};
	jaxdoc.addFunction('jaxscript.style.left','sets the "left" style of an element as an integer',{
		param : [
			{node : type.Node, left : type.Integer},
			{nodeId : type.String, left : type.Integer}
		],
		returns : type.Node
	});
	
	function top(n,y) {
		return set(n,{top:px(y)});
	};
	jaxdoc.addFunction('jaxscript.style.left','sets the "top" style of an element as an integer',{
		param : [
			{node : type.Node, top : type.Integer},
			{nodeId : type.String, top : type.Integer}
		],
		returns : type.Node
	});
	
	function right(n,x) {
		return set(n,{right:px(x)});
	};
	jaxdoc.addFunction('jaxscript.style.right','sets the "right" style of an element as an integer',{
		param : [
			{node : type.Node, right : type.Integer},
			{nodeId : type.String, right : type.Integer}
		],
		returns : type.Node
	});
	
	function bottom(n,x) {
		return set(n,{bottom:px(x)});
	};
	jaxdoc.addFunction('jaxscript.style.bottom','sets the "bottom" style of an element as an integer',{
		param : [
			{node : type.Node, bottom : type.Integer},
			{nodeId : type.String, bottom : type.Integer}
		],
		returns : type.Node
	});
	
	function width(n,w) {
		return set(n,{width:px(w)});
	};
	jaxdoc.addFunction('jaxscript.style.width','sets the "width" style of an element as an integer',{
		param : [
			{node : type.Node, width : type.Integer},
			{nodeId : type.String, width : type.Integer}
		],
		returns : type.Node
	});
	
	function height(n,h) {
		return set(n,{height:px(h)});
	};
	jaxdoc.addFunction('jaxscript.style.height','sets the "height" style of an element as an integer',{
		param : [
			{node : type.Node, height : type.Integer},
			{nodeId : type.String, height : type.Integer}
		],
		returns : type.Node
	});
	
	function move(n,x,y) {
		return set(n,{
			left:px(x),
			top:px(y)
		});
	};
	jaxdoc.addFunction('jaxscript.style.move','sets the (left,top) position of an element',{
		param : [
			{node : type.Node, left : type.Integer , top:type.Integer},
			{nodeId : type.String, left : type.Integer , top:type.Integer}
		],
		returns : type.Node
	});

	function opacity(n,f) {
		n = id(n);
		f = parseFloat(f);
		if (f<0) f = 0;
		if (f>1) f = 1;
		n.style.opacity = f;
		if (jaxscript.supports('activex')) n.style.filter = 'alpha(opacity='+f*100+')';
		//echo('style.opacity(): '+n.nodeName+((n.id)?'#'+n.id:'')+' opacity = '+f);
		return n;
	};
	jaxdoc.addFunction('jaxscript.style.opacity','sets the opacity of an element from 0 (transparent) to 1 (opaque), and adds a _opacity property to the element for reading back',{
		param : [
			{node : type.Node , opacityPercent:type.Float},
			{nodeId : type.String , opacityPercent:type.Float}
		],
		returns : type.Literal
	});
	
	function px(i) {
		//return Math.round(parseFloat(i))+'px';
		return round(i)+'px';  // test
	};
	jaxdoc.addFunction('jaxscript.style.px','parses a value to an integer then adds "px" for use as a css style',{
		param : {intval : type.Integer},
		returns : type.Node
	});
	
	function size(n,w,h) {
		return set(n,{
			width:px(w),
			height:px(h)
		});
	};
	jaxdoc.addFunction('jaxscript.style.size','sets an element\'s size as (width,height) in pixels and returns the element object',{
		param : [
			{node : type.Node , width:type.Integer , height:type.Integer},
			{nodeId : type.String , width:type.Integer , height:type.Integer}
		],
		returns : type.Node
	});
	
	function set(o,s) {
		var n = id(o);
		if (!!n) {
			if (isArray(n)) {  // handle an array of elements
				for (var i=0;i<n.length;i++)
					copy(n[i].style,s);
			}
			else if (!!n.style) {
				copy(n.style,s);
				return n;
			}
		}
		echo('style.set() error: element '+(isString(o)?o:'')+' does not exist, properties were '+inspect(s));
	};
	jaxdoc.addFunction('jaxscript.style.style','a shortcut for "element.style.property = value" allowing multiple styles to be set in a single command',{
		param : [
			{node : type.Node, style : type.Literal},
			{nodeId : type.String, style : type.Literal},
			{node : type.Array, style : type.Literal}
		],
		returns : type.Node
	});
	
	function swap(a,b) {
		display(a,0);
		display(b,1);
	};
	jaxdoc.addFunction('jaxscript.style.swap','sets nodeA.style.display to "none" and sets nodeB.style.display to "block"',{
		param : [
			{nodeA : type.Node, nodeB : type.Node},
			{nodeIdA : type.String, nodeB : type.Node},
			{nodeA : type.Node, nodeIdB : type.String},
			{nodeIdA : type.String, nodeIdB : type.String}
		]
	});
	
	function show(o) {
		return visible(o,true);
	}
	jaxdoc.addFunction('jaxscript.style.hide','sets the "visibility" style of an element to "visible"',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Node
	});
	
	function hide(o) {
		return visible(o,false);
	}
	jaxdoc.addFunction('jaxscript.style.hide','sets the "visibility" style of an element to "hidden"',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Node
	});
	
	function visible(n,b) {
		return set(n,{visibility:b?'visible':'hidden'});
	};
	jaxdoc.addFunction('jaxscript.style.visible','sets the "visibility" style of an element true (="visible"), false (="hidden")',{
		param : [
			{node : type.Node, visible : type.Boolean},
			{nodeId : type.String, visible : type.Boolean}
		],
		returns : type.Node
	});
	
	return {
		clip : clip,
		display : display,
		getClip : getClip,
		getOpacity : getOpacity,
		getSize : getSize,
		get : get,
		getXY : getXY,
		maxZ : maxZ,
		move : move,
		opacity : opacity,
		size : size,
		set : set,
		visible : visible,
		left : left,
		top : top,
		right : right,
		bottom : bottom,
		width : width,
		height : height,
		show : show,
		hide : hide
	}

})();var fx = jaxscript.fx = (function() {
	jaxdoc.addLibrary('jaxscript.fx','Special Effects library');

	function fadeIn(n,fn) {
		fade(n,1,fn);
	}
	function fadeOut(n,fn) {
		fade(n,0,fn);
	}
	function fadeCancel(n) {
		if (!!n._fadeTimer) clearTimeout(n._fadeTimer);
	}
	
	function fade(n,limit,fn,inc,s) {
		n = id(n);
		style.opacity(n, style.getOpacity(n));  // this resets the opacity to 1 if none was set
		if (inc==null) inc = 0.1;
		else inc = Math.abs(inc);
		if (s==null) s = 50;
		if (limit==null || limit<0) limit = 0;
		if (limit>1) limit = 1;
		if (style.getOpacity(n)>limit) inc = -inc;
		//echo('fade(): '+n.nodeName+((n.id)?'#'+n.id:'')+' fading from '+style.getOpacity(n)+' to '+limit+' in '+inc+' increments');
		fadeCancel(n);
		fadeStep(n,limit,fn,inc,s);
	};
	jaxdoc.addFunction('jaxscript.fx.fade','fades an element into or out of view by gradually changing its opacity, then executes the handler function when finished',{
		param : [
			{element:type.Node,visibility:type.Boolean},
			{elementId:type.String,visibility:type.Boolean},
			{element:type.Node,visibility:type.Boolean,handler:type.Function,increment:type.Float,limit:type.Float,speed:type.Integer},
			{elementId:type.String,visibility:type.Boolean,handler:type.Function,increment:type.Float,limit:type.Float,speed:type.Integer}
		]
	});
	
	function fadeStep(n,limit,fn,inc,s) {
		var x = style.getOpacity(n) + inc;
		if (inc>0 && x>limit || inc<0 && x<limit) x = limit;
		style.opacity(n,x);
		if (x!=limit)
			n._fadeTimer = setTimeout(function() {
				fadeStep(n,limit,fn,inc,s);
			},s);
		else {
			if (fn) fn();
			//echo('fx.fadeStep(): '+n.nodeName+((n.id)?'#'+n.id:'')+' fade complete');
		}
	};
	
	return {
		fade : fade,
		fadeIn : fadeIn,
		fadeOut : fadeOut,
		fadeCancel : fadeCancel
	}
	
})();// 17
// TRANSFORM LIBRARY:

var transform = jaxscript.transform = (function() {
	
	jaxdoc.addLibrary('jaxscript.transform','JaxScript Data Transform Library');
	
	function param2obj(url) {
		if (!url) url = window.location.search;
		if (url.indexOf('?')>-1) url = url.substring(url.indexOf('?')+1);
		var o = {};
		var q = url.length>1?url.split("&"):[];
		for (var i=0;i<q.length;i++)
			o[q[i].match(/^[^=]+/)] = unescape(q[i].replace(/^[^=]+=?/, ""));
		return o;
	};
	
	jaxdoc.addFunction('jaxscript.transform.param2obj',{
		definition : 'parses a URL string and returns the parameters as a object literal',
		param : [
			{'null' : type.Null},
			{url : type.String},
		],
		returns : type.Literal
	});
	
	function obj2param(o) { // needs testing
		var i, a = [];
		for (i in o) {
			a[a.length] = i + '=' + encodeURIComponent(o[i]);
		}
		return a.join('&');
	};
	jaxdoc.addFunction('jaxscript.transform.obj2param',{
		definition : 'serializes an object literal to a URL parameter string',
		param : {params:type.Object},
		returns : type.String
	});

	function attr2json(n,json) {
		var a;
		for (var i=0;i<n.attributes.length;i++) {
			a = n.attributes[i];
			json[a.nodeName] = trim(a.nodeValue);
		}
		return json;
	};
	jaxdoc.addFunction('jaxscript.transform.attr2json',{
		definition : 'reads attributes from a DOM Node and appends them to a JSON Object',
		param : {node:type.DOMNode},
		returns : type.JSON
	});
	
	function node2json(n) {
		n = id(n);
		var json;
		var l = n.childNodes.length;
		var i;
		
		if (l==0) { // empty element
			if (n.attributes && n.attributes.length>0) {
				json = attr2json(n,{});
			}
		}
		else {
			var childnodes = [];
			var child;
			var isarray = true;  // ?? why is this true
			for (i=0;i<n.childNodes.length;i++) {
				child = n.childNodes[i];
				
				if (child.nodeName=="#text") {
					continue;
				}
				
				l = childnodes.length;
				childnodes[l] = child;
				if (isarray && l>0) {
					if (childnodes[l-1].nodeName != child.nodeName) isarray = false;
				}
			}
			if (childnodes.length==0) {
				var v = trim(n.childNodes[0].nodeValue);
				json = (v=="")?null:v;
			}
			else if (childnodes.length==1) {
				json = attr2json(n,{});
				json[childnodes[0].nodeName] = node2json(childnodes[0]);
			}
			else {
				if (isarray) {
					var arrayname = childnodes[0].nodeName;
					json = {};
					json[arrayname] = [];
					for (i=0;i<childnodes.length;i++) {
						json[arrayname][i] = node2json(childnodes[i]);
					}
				}
				else {
					json = {};
					for (i=0;i<childnodes.length;i++) {
						child = childnodes[i];
						json[child.nodeName] = node2json(child);
					}
				}
			}
		}
		return json;
	}
	jaxdoc.addFunction('jaxscript.transform.node2json',{
		definition : 'converts a DOM Node into JSON node',
		param : [
			{node:type.DOMNode},
			{nodeId:type.String}
		],
		returns : type.JSON
	});
	
	function dom2json(d) {
		if (d.childNodes.length>=1) {
			var n = d.childNodes[0];
			var j = {};
			j[n.nodeName] = node2json(n);
			return j;
		}
		return {};
	};
	jaxdoc.addFunction('dom2json',{
		definition : 'converts a top-level DOM Document Node to a JSON Object',
		param : {xml:type.String},
		returns : type.JSON
	});

	
	function dom2xml(n) {
		if (typeof XMLSerializer=='function') return (new XMLSerializer()).serializeToString(n);
		else if (n.xml) return n.xml;
		echoError('dom2xml() : could not serialize DOM to XML, not supported');
	};
	jaxdoc.addFunction('jaxscript.transform.dom2xml',{
		definition : 'converts DOM Node to an XML string',
		param : {element:type.DOMNode},
		returns : type.String
	});


	function json2dom(json) {
		return xml2dom(json2xml(json));
	};
	jaxdoc.addFunction('jaxscript.transform.json2dom',{
		definition : 'converts a JSON Object to a DOM Node',
		param : {json:type.JSON},
		returns : type.DOMNode
	});

	
	function xmlstr(s) {
		if (s.indexOf('\n')>-1 && s.indexOf('<![CDATA[')==-1) return '<![CDATA[' + s + ']]>';
		else return trim(s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;').replace('\'','&apos;'));
	}
	
	function json2xml(o,tag,addDefinition) {
		var xml = (addDefinition)? '<?xml version="1.0"?>\n':'';
		if (isArray(o))
			for (var i=0;i<o.length;i++)
				xml += json2xml(o[i],tag);
		else {
			var start = (tag)?'<'+tag+'>':'';
			var end = (tag)?'</'+tag+'>':'';
			if (isInteger(o) || isFloat(o)) xml += start + o + end;
			if (isBoolean(o)) xml += start + o + end;
			else if (isString(o)) xml += start + xmlstr(o) + end;
			else if (isObject(o)) {
				xml += start;
				for (var i in o)
					xml += json2xml(o[i],i);
				xml += end;
			}
		}
		return xml;
	};
	jaxdoc.addFunction('json2xml','converts an object literal (JSON) to an XML string, if addDefinition is true it will return the xml string with <?xml version="1.0"?> as the first line',{
		param : [
			{json:type.JSON},
			{json:type.JSON, tagName:type.String},
			{json:type.JSON, tagName:type.String, addDefinition:type.Boolean},
		],
		returns : type.XML
	});

	function stripXML(xml) {
		xml = xml.replace(/\r/g,'');
		xml = xml.replace(/\n/g,'\uffff');
		xml = xml.replace(/<\!(.*?)>/g,"");
		xml = xml.replace(/<\?(.*?)\?>/g,"");
		xml = xml.replace(/\uffff/g,'\n');
		//xml = xml.replace(/>(\s+?)</g,">\n<");
		return trim(xml);
	};
	jaxdoc.addFunction('jaxscript.transform.stripXML',{
		definition : 'removes XML tags from a string',
		param : {xml:type.String},
		returns : type.String
	});
	
	function xml2dom(xml) {
		var d,p;
		xml = stripXML(xml);
		if (typeof DOMParser=='function' || typeof DOMParser=='object') {
			try {
				p = new DOMParser();
				d = p.parseFromString(xml,"text/xml");
				//     d = xmldom.parseFromString( xml, "application/xml" );
			}
			catch (e) {
				echo('transform.xml2dom() error: '+e.message);
			}
		}
		else if (jaxscript.supports('activex')) {
			try {
				d = new ActiveXObject("Microsoft.XMLDOM");
				d.async = false;
				//xml = '<?xml version="1.0"?>\n'+xml; // need this?
				d.loadXML(xml);
			}
			catch (e) {
				echo('transform.xml2dom() error: '+e.message);
			}
		}
		return d;
	};
	jaxdoc.addFunction('jaxscript.transform.xml2dom','converts an XML string to a DOM Node',{
		param : {element:type.DOMNode},
		returns : type.String
	});
	
	function xml2json(xml) {
		return dom2json(xml2dom(xml));
	};
	jaxdoc.addFunction('jaxscript.transform.xml2json','converts an XML string to a JSON Object',{
		param : {xml:type.String},
		returns : type.JSON
	});

	function obj2json(o, nullStrings, depth) {
		if (!!o) {
			var json = {};
			for (var i in o) {
				var v = o[i];
				if (!!v) {
					if (isArray(v)) {
						json[i] = v;
					}
					else if (isObject(v)) {
						var o_json = obj2json(v,nullStrings,(depth>1)?--depth:0);
						if (o_json) json[i] = o_json;				
					}
					else if (!isFunction(v)) json[i] = v;
				}
				else if (nullStrings) json[i] = '';
			}
			return json;
		}
		else if (nullStrings) return {};
	}
	jaxdoc.addFunction('jaxscript.transform.obj2json','serializes an object to it\'s json equivalent (if possible) by ignoring functions and non-primitive properties',{
		param : [
			{object:type.Object},
			{object:type.Object,nullStrings:type.Boolean},
			{object:type.Object,nullStrings:type.Boolean,depth:type.Integer},
		],
		returns : type.JSON
	});
	
	// private
	function quote(s) {
		// this function was copied from JSON.quote() written by Douglas Crockford at http://json.org
		var fn = function (a) {
			var meta = {
				'\b': '\\b',
				'\t': '\\t',
				'\n': '\\n',
				'\f': '\\f',
				'\r': '\\r',
				'"' : '\\"',
				'\\': '\\\\'
			}
			var c = meta[a];
			return typeof c === type.String ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
		};
		var c = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
		return c.test(s)? '"' + s.replace(c, fn) + '"' : '"' + s + '"';
	}
	
	function json2string(o,k) {
		var s = '';
		if (isArray(o)) {
			var a = [];
			for (var i=0; i<o.length; i++) {
				a.push(json2string(o[i]));
			}
			s = '[' +a.join(',') + ']';
		}
		else if (isObject(o)) {
			var a = [];
			for (var i in o) {
				//if (!!o[i]) {
				if (typeof o[i]!='function') {
					a.push(quote(i) + ':' + json2string(o[i]));
				}
			}
			s = '{' + a.join(',') + '}';
		}
		else if (isString(o)) s = quote(o);
		else if (isInteger(o) || isFloat(o)) {
			if (isFinite(o)) s = String(o);
			else echo('transform.json2string','value of '+o+' is not finite');
		}
		else if (isBoolean(o)) s = String(o);
		else if (isNull(o)) s = '""';
		else echo('transform.json2string','value '+inspect(o)+' could not be converted to a JSON string');
		
		if (isString(k)) return '{' + quote(k) + ':' + s + '}';
		else return s;
	}
	jaxdoc.addFunction('jaxscript.transform.json2string',{
		definition : 'serializes an object literal to a JSON string',
		param : [
			{json:type.JSON},
			{json:type.JSON,key:type.String}
		],
		returns : type.String
	});
	
	function string2json(s) {
		// this function was copied from JSON.parse() written by David Crawford at http://json.org
		// escape control characters
		var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
		if (cx.test(s)) {
			s = s.replace(cx, function(a) {
				echo('string2json() : string contains control characters : \n'+s);
				return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
			});
		}
		return eval('(' + s + ')');
		
		// this was causing problems
		//if (/^[\],:{}\s]*$/.test(s.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
		//	return eval('(' + s + ')');
		//}
		
	}
	jaxdoc.addFunction('jaxscript.transform.string2json','evaluates a JSON string to an object literal',{
		param : [
			{json:type.JSON},
			{json:type.JSON,key:type.String}
		],
		returns : type.String
	});
	
	function xslt(xsldom, xmldom, returnType) {
		//if (supports('xslt')) {
		
		// if xsldom or xmldom are strings load them as URLs
		if (isString(xsldom)) xsldom = dom.loadDOM(xsldom);
		if (isString(xmldom)) xmldom = dom.loadDOM(xmldom);
		
		// returnType can be 'dom', 'document', or 'fragment', default is xml string
		
		if (!!xsldom && !!xsldom.childNodes && !!xmldom && !!xmldom.childNodes) {
			// ff
			if ((typeof XSLTProcessor=='function' && typeof XMLSerializer=='function') || (typeof XSLTProcessor=='object' && typeof XMLSerializer=='object')) {
				var p=new XSLTProcessor(),s=new XMLSerializer();
				p.importStylesheet(xsldom);
				if (returnType=="fragment") return p.transformToFragment(xmldom,document);
				if (returnType=="document") return p.transformToDocument(xmldom);
				var t = p.transformToFragment(xmldom,document);  // assume html or xml return type
				if (!!t.childNodes) {
					return s.serializeToString(t,"text/xml");
				}
				else echo('transform.xslt() : transformation result had no nodes');
				return '';
			}
			// ie
			else if (typeof ActiveXObject=='function') {
				var xml = xmldom.transformNode(xsldom);
				if (returnType=="document" || returnType=="fragment") return xml2dom(xml);
				else return xml;
			}
			else unsupported();
		}
		echo('transform.xslt() error : invalid DOM document(s)');
		return (returnType=="dom")?null:"";
	}; 
	jaxdoc.addFunction('jaxscript.transform.xslt',{
		definition : 'performs an XSL transformation and returns the XML string (default), the DOM (returnType="dom"), or DOM fragment (returnType="fragment")',
		param : [
			{xsl:type.DOMNode,xml:type.DOMNode},
			{xsl:type.DOMNode,xml:type.DOMNode,returnType:type.String}
		],
		returns : type.DOMNode
	});
	
	return {
		param2obj : param2obj,
		obj2param : obj2param,
		attr2json : attr2json,
		node2json : node2json,
		dom2json : dom2json,
		dom2xml : dom2xml,
		json2dom : json2dom,
		xmlstr : xmlstr,
		json2xml : json2xml,
		stripXML : stripXML,
		xml2dom : xml2dom,
		xml2json : xml2json,
		obj2json : obj2json,
		json2string : json2string,
		string2json : string2json,
		xslt : xslt
	}
	
})();