// $Id: swissarmyknife.js 1764 2010-11-01 13:11:52Z joris $
(function () {
window['__'] = undefined; // Can be used to skip an argument when calling a function. For example: test(__,__, 213);
var X = window.X = {};

var clockdrift = 0;
var timezonediff = 0;
var focusedNode = null;

X.SWA_initialize = function()
{
	// Bereken hoeveel de klok van de browser afwijkt van de klok op de server
	// Dit is natuurlijk niet 100% nauwkeurig vanwege pagina laad tijden
	clockdrift = new Date().getTime() - rmdata.swa.servertime * 1000;
	
	timezonediff = rmdata.swa.servertimezone + new Date().getTimezoneOffset() * 60;
	timezonediff *= 1000;
	
	if (window.addEventListener)
	{
		window.addEventListener('focus', function(e) 
		{
			if (e.target == document || e.target == window)
				return;
			
			focusedNode = e.target;
		}, true);
		
		window.addEventListener('blur', function(e) 
		{
			if (e.target == document || e.target == window)
				return;
			
			if (focusedNode == e.target)
				focusedNode = null;
		}, true);
	}
};

X.getFocusedNode = function()
{
	return focusedNode;
};

// Noot: in de interface moet de tijd altijd weergeven worden zoals de TIMEZONE voorschrijft

X.browserToServerTime = function(date)
{
	return new Date(date.getTime() - clockdrift);
};

X.serverToBrowserTime = function(date)
{
	return new Date(date.getTime() + clockdrift);
};



// gebruik dit als met .getHour(), etc de tijd van de server moet worden laten zien. 
// Let op: de unix timestamp is hierna ongeldig, gebruik myTimeZoneToBrowser om dit te corrigeren
X.browserToMyTimeZone = function(date) 
{
	return new Date(date.getTime() + timezonediff);
};

X.myTimeZoneToBrowser = function(date)
{
	return new Date(date.getTime() - timezonediff);
};


/*
	Left side must be 1 character, right side may be any length
	X.replaceCharTable("bla",
		{'b': 'a', 
		'l': 'b', 
		97: 'c'});
*/
X.replaceCharTable = function(str, table)
{
	var a, ret;
	var character, charCode;
	ret = '';
	
	for (a = 0; a < str.length; a++)
	{
		charCode = str.charCodeAt(a);
		character = str.charAt(a);
		
		// UTF-16
		if (0xD800 <= charCode && charCode <= 0xDBFF) // High surrogate (could change last hex to 0xDB7F to treat high private surrogates as single characters); https://developer.mozilla.org/index.php?title=en/Core_JavaScript_1.5_Reference/Global_Objects/String/charCodeAt&revision=39
		{
			charCode = ((charCode - 0xD800) * 0x400) + (str.charCodeAt(a+1) - 0xDC00) + 0x10000;
			character += str.charAt(a+1);
			a++; // skip the next one
		}
		
		// We never come across a low surrogate because we skip them
		
		if (table[character] != undefined)
		{
			ret += table[character];
			continue;
		}
		
		if (table[charCode] != undefined)
		{
			ret += table[charCode];
			continue;
		}
		
		ret += character;
	}
	
	return ret;
};

X.stringWithSimpleVariables = function(text, variables)
{
	return text.replace(/\{(\w+?)\}/g, function(str, p1, offset, s) 
	{
		if (variables[p1] !== undefined)
			return variables[p1];
		else
			return str;
	});
};

/*
	var A = function() { 
		this.myProperty = 123;
	}; // constructor
	A.myStaticProperty = 5;
	A.myStaticProperty2 = 10;
	A.prototype.myProperty = null; // geef initiele waarde in constructor
	
	var B = function() { A.call(this, ...); ... }; // constructor
	B.myStaticProperty = 8; // static properties die VOOR extendClass() komen overschrijven degene van de superclass
	extendClass(B, A); // Roep aan NA het instellen van "static" properties, en VOOR het instellen van niet static properties
	B.prototype.myProperty2 = 5; // deze dus na extendClass

	B.myStaticProperty2 = 11; // deze komt na de extendClass, dus degene in A.myStaticProperty2 is ook 11
	B.myStaticProperty = 9; // we hebben het boven overschreden voor extendClass; A is dus hier ook ongewijzigd
	
	var b = new B();
	b.constructor.myStaticProperty = 5;
	b.myProperty = 546;
*/
X.extendClass = function(fnTo, fnFrom)
{
	var a;
	for (a in fnFrom)
	{
		if (fnFrom.hasOwnProperty(a) && fnTo[a] === undefined)
		{
			(function(fnTo, fnFrom, a)
			{
				fnTo.__defineGetter__(a, function() { return fnFrom[a]; });
				fnTo.__defineSetter__(a, function(val) { fnFrom[a] = val });
			})(fnTo, fnFrom, a);
		}
	}
	
	for (a in fnFrom.prototype)
		fnTo.prototype[a] = fnFrom.prototype[a];
};

var DOMContentLoaded_eventFired = false;
var DOMContentLoaded_events = [];
function DOMContentLoaded_evt()
{
	var a, len;
	if (DOMContentLoaded_eventFired || !DOMContentLoaded_events) return;
	DOMContentLoaded_eventFired = true;
	
	len = DOMContentLoaded_events.length;
	 
	for (a = 0; a < len; a++)
	{
		DOMContentLoaded_events[a]();
	}
	DOMContentLoaded_events = [];	
}


X.addDOMLoadEvent = function(listener)
{
	if (DOMContentLoaded_eventFired)
		setTimeout(listener, 0); //defer it (like an event)
	
	if (!DOMContentLoaded_events)
		DOMContentLoaded_events = [];
		
	DOMContentLoaded_events.push(listener);
};

if (window.addEventListener)
{
	window.addEventListener('DOMContentLoaded', DOMContentLoaded_evt, false);
	window.addEventListener('load', DOMContentLoaded_evt, false);
}
else if (window.attachEvent)
{
	window.attachEvent('onload', DOMContentLoaded_evt);
}

if (window.jQuery) // jQuery is mischien eerder
{
	jQuery(DOMContentLoaded_evt);
}

/*@cc_on
(function(){ // Stolen from jquery
	if (DOMContentLoaded_eventFired) return;
	try {
		// If IE is used, use the trick by Diego Perini
		// http://javascript.nwbox.com/IEContentLoaded/
		document.documentElement.doScroll("left");
	} catch (error) {
		setTimeout( arguments.callee, 4);
		return;
	}
	
	DOMContentLoaded_evt();
})();
@*/

var rm_callbacks = {};
X.callback = function(identifier, fn)
{	
	if (!rm_callbacks[identifier])
		rm_callbacks[identifier] = [];
	
	rm_callbacks[identifier].push(fn);
};

X.removecallback = function(identifier, fn)
{
	var i;
	if (!rm_callbacks[identifier])
		return;
	
	i = rm_callbacks[identifier].indexOf(fn);
	if (i >= 0)
		rm_callbacks[identifier].splice(i, 1);
};

X.docallback = function(identifier, args)
{
	var a, len;
	
	if (!rm_callbacks[identifier])
		return;
	
	if (!args)
		args = [];
	
	args.unshift(identifier);
	
	len = rm_callbacks[identifier].length;
	for (a = 0; a < len; a++)
	{
		try
		{
			rm_callbacks[identifier][a].apply(window, args);
		}
		catch (err)
		{
			dS("Error while firing callback %s: %s (%o)", identifier, err.description, err);
		}
		
	}
};

var logFirst = true;
window.d = function()
{
	if (logFirst)
	{
		logFirst = false;
		if (window.loadFirebugConsole)
			loadFirebugConsole();
	}

	if (window.console && console.log)
	{
		console.log.apply(console, arguments);
	}
};

window.dS = function()
{
	if (logFirst)
	{
		logFirst = false;
		if (window.loadFirebugConsole)
			loadFirebugConsole();
	}
	
	if (window.console)
	{
		if (console.groupCollapsed)
		{
			console.groupCollapsed.apply(console, arguments);
			console.trace();
			console.groupEnd();
		}
		else if (console.log)
		{
			console.log.apply(console, arguments);
		}
	}
};

window.loadFirebugLite = function()
{
	var firebug;
	if (window.firebug && window.firebug.init)
		return;
	
	firebug = document.createElement('script');
	X.attr(firebug, 'src','http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js');
	document.body.appendChild(firebug);
	
	function initFirebug() 
	{
		if (window.firebug && window.firebug.init)
		{
			window.firebug.init();
		}
		else
		{
			setTimeout(initFirebug, 1);
		}
	}
	
	initFirebug();
	return undefined;
};

X.assert = function (bool, message)
{
	var err;
	if (!bool)
	{
		err = new Error();
		err.name = "Assertion Error";
		if (message)
			err.message = message;
		else
			err.message = "Assertion Error";
		
		if (window.console)
		{
			console.group("Assertion Error: %s", err.message);
			console.trace();
			console.groupEnd();
		}
		throw err;
	}
};

window.id = function(html_id)
{
	return document.getElementById(html_id);
};

X.clip = function(value, min, max)
{
	if (min != undefined && value < min)
		return min;
	
	if (max != undefined && value > max)
		return max;
	
	return value;
};
X.phpRegExp = function(pattern)
{
	var offset;
	offset = pattern.lastIndexOf('/');
	if (offset < 0)
	{
		return new RegExp(pattern);
	}
	else
	{
		return new RegExp(pattern.substr(1, offset-1), pattern.substr(offset+1));
	}	
};

X.str2bool = function(value, def)
{
	var v, i;
	if (!value)
		return !!def;
	
	v =  (value+'').trim().toLowerCase();
	if (v == 'yes' || v == 'true' || v == 'ok' || v == 'y' || parseInt(v) > 0)
		return true;
	else if (v == 'no' || v == 'false' || v == 'notok' || v == 'n' || v == '0')
		return false;
	else
		return !!def;
};

X.stopEvent_listener = function(e)
{
	e.preventDefault();
	e.stopPropagation();
};

X.addGET = function(url, key, value)
{
	var parts, replaced, keyEncoded;
	url += ''; // make sure it is a string
	
	if (key == undefined)
		return url;
		
	key = key.toLowerCase();
	keyEncoded = X.urlencode(key);
	
	parts = url.split('#');
	url = parts.shift();
	
	replaced = false;
	
	// RFC 3986: [\w\-\.~]
	url = url.replace(/([?&])([\w\-\.~]+)=[\w\-\.~]+/, function(str, p1, p2, offset, s)
	{
		if (p2.toLowerCase() != keyEncoded.toLowerCase())
			return str;
		
		replaced = true;
		
		if (value == null)
		{
			if (p1 == '?' && s.indexOf('&') >= 0)
				return '?';
			else
				return '';
		}
		else
		{
			return p1 + keyEncoded + '=' + X.urlencode(value);
		}
	});
	url = url.replace('?&', '?');
	
	if (!replaced)
	{
		if (url.indexOf('?') < 0)
			url += '?';
		else
			url += '&';
		
		url += keyEncoded;
		if (value != undefined)
			url += '=' + X.urlencode(value);
	}
	if (parts.length)
		url += '#' + parts.join('#');
	
	return url;
};

X.explode = function (delimiter, string, limit) 
{
	// Splits a string on string separator and return array of components. If limit is positive only limit number of components is returned. If limit is negative all components except the last abs(limit) are returned.  
	// 
	// version: 1003.2411
	// discuss at: http://phpjs.org/functions/explode    // +     original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +     improved by: kenneth
	// +     improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +     improved by: d3x
	// +     bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)    // *     example 1: explode(' ', 'Kevin van Zonneveld');
	// *     returns 1: {0: 'Kevin', 1: 'van', 2: 'Zonneveld'}
	// *     example 2: explode('=', 'a=bc=d', 2);
	// *     returns 2: ['a', 'bc=d']
	var emptyArray = { 0: '' };
	
	// third argument is not required
	if ( arguments.length < 2 ||
	     typeof arguments[0] == 'undefined' ||
	     typeof arguments[1] == 'undefined' ) 
	{
		return null;
	}
	
	if ( delimiter === '' ||        delimiter === false ||
	delimiter === null ) 
	{
		return false;
	}
	
	if ( typeof delimiter == 'function' ||
	     typeof delimiter == 'object' ||
	     typeof string == 'function' ||
	     typeof string == 'object' ) 
	{
		return emptyArray;    
	}
	
	if ( delimiter === true ) 
	{
		delimiter = '1';
	}
	
	if (!limit) 
	{
		return string.toString().split(delimiter.toString());
	}
	else
	{
		// support for limit argument        
		var splitted = string.toString().split(delimiter.toString());
		var partA = splitted.splice(0, limit - 1);
		var partB = splitted.join(delimiter.toString());
		partA.push(partB);
		return partA;    
	}
};

X.addslashes = function (str) 
{
	// Escapes single quote, double quotes and backslash characters in a string with backslashes  
	// 
	// version: 908.406
	// discuss at: http://phpjs.org/functions/addslashes
	// +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +   improved by: Ates Goral (http://magnetiq.com)
	// +   improved by: marrtins
	// +   improved by: Nate
	// +   improved by: Onno Marsman
	// +   input by: Denny Wardhana
	// +   improved by: Brett Zamir (http://brett-zamir.me)
	// *     example 1: addslashes("kevin's birthday");
	// *     returns 1: 'kevin\'s birthday'
	
	return (str+'').replace(/([\\"'])/g, "\\$1").replace(/\u0000/g, "\\0");
};


X.stripslashes = function(str) 
{
	// +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +   improved by: Ates Goral (http://magnetiq.com)
	// +      fixed by: Mick@el
	// +   improved by: marrtins
	// +   bugfixed by: Onno Marsman
	// +   improved by: rezna
	// +   input by: Rick Waldron
	// +   reimplemented by: Brett Zamir (http://brett-zamir.me)
	// *     example 1: stripslashes('Kevin\'s code');
	// *     returns 1: "Kevin's code"
	// *     example 2: stripslashes('Kevin\\\'s code');
	// *     returns 2: "Kevin\'s code"
	return (str+'').replace(/\\(.?)/g, function (s, n1) 
	{
	switch (n1) 
	{
		case '\\':
			return '\\';
		case '0':
			return '\0';
		case '':
			return '';
		default:
			return n1;
	}
	});
};

X.get_html_translation_table = function (table, quote_style) 
{
	// Returns the internal translation table used by htmlspecialchars and htmlentities  
	// 
	// version: 908.406
	// discuss at: http://phpjs.org/functions/get_html_translation_table
	// +   original by: Philip Peterson
	// +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +   bugfixed by: noname
	// +   bugfixed by: Alex
	// +   bugfixed by: Marco
	// +   bugfixed by: madipta
	// +   improved by: KELAN
	// +   improved by: Brett Zamir (http://brett-zamir.me)
	// +   bugfixed by: Brett Zamir (http://brett-zamir.me)
	// +      input by: Frank Forte
	// +   bugfixed by: T.Wild
	// +      input by: Ratheous
	// %          note: It has been decided that we're not going to add global
	// %          note: dependencies to php.js, meaning the constants are not
	// %          note: real constants, but strings instead. Integers are also supported if someone
	// %          note: chooses to create the constants themselves.
	// *     example 1: get_html_translation_table('HTML_SPECIALCHARS');
	// *     returns 1: {'"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;'}
	
	var entities = {}, hash_map = {}, decimal = 0, symbol = '';
	var constMappingTable = {}, constMappingQuoteStyle = {};
	var useTable = {}, useQuoteStyle = {};
	
	// Translate arguments
	constMappingTable[0]      = 'HTML_SPECIALCHARS';
	constMappingTable[1]      = 'HTML_ENTITIES';
	constMappingQuoteStyle[0] = 'ENT_NOQUOTES';
	constMappingQuoteStyle[2] = 'ENT_COMPAT';
	constMappingQuoteStyle[3] = 'ENT_QUOTES';
	
	useTable       = !isNaN(table) ? constMappingTable[table] : table ? table.toUpperCase() : 'HTML_SPECIALCHARS';
	useQuoteStyle = !isNaN(quote_style) ? constMappingQuoteStyle[quote_style] : quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT';
	
	if (useTable !== 'HTML_SPECIALCHARS' && useTable !== 'HTML_ENTITIES') 
	{
		throw new Error("Table: "+useTable+' not supported');
		return false;
	}
	
	entities['38'] = '&amp;';
	if (useTable === 'HTML_ENTITIES') 
	{
		entities['160'] = '&nbsp;';
		entities['161'] = '&iexcl;';
		entities['162'] = '&cent;';
		entities['163'] = '&pound;';
		entities['164'] = '&curren;';
		entities['165'] = '&yen;';
		entities['166'] = '&brvbar;';
		entities['167'] = '&sect;';
		entities['168'] = '&uml;';
		entities['169'] = '&copy;';
		entities['170'] = '&ordf;';
		entities['171'] = '&laquo;';
		entities['172'] = '&not;';
		entities['173'] = '&shy;';
		entities['174'] = '&reg;';
		entities['175'] = '&macr;';
		entities['176'] = '&deg;';
		entities['177'] = '&plusmn;';
		entities['178'] = '&sup2;';
		entities['179'] = '&sup3;';
		entities['180'] = '&acute;';
		entities['181'] = '&micro;';
		entities['182'] = '&para;';
		entities['183'] = '&middot;';
		entities['184'] = '&cedil;';
		entities['185'] = '&sup1;';
		entities['186'] = '&ordm;';
		entities['187'] = '&raquo;';
		entities['188'] = '&frac14;';
		entities['189'] = '&frac12;';
		entities['190'] = '&frac34;';
		entities['191'] = '&iquest;';
		entities['192'] = '&Agrave;';
		entities['193'] = '&Aacute;';
		entities['194'] = '&Acirc;';
		entities['195'] = '&Atilde;';
		entities['196'] = '&Auml;';
		entities['197'] = '&Aring;';
		entities['198'] = '&AElig;';
		entities['199'] = '&Ccedil;';
		entities['200'] = '&Egrave;';
		entities['201'] = '&Eacute;';
		entities['202'] = '&Ecirc;';
		entities['203'] = '&Euml;';
		entities['204'] = '&Igrave;';
		entities['205'] = '&Iacute;';
		entities['206'] = '&Icirc;';
		entities['207'] = '&Iuml;';
		entities['208'] = '&ETH;';
		entities['209'] = '&Ntilde;';
		entities['210'] = '&Ograve;';
		entities['211'] = '&Oacute;';
		entities['212'] = '&Ocirc;';
		entities['213'] = '&Otilde;';
		entities['214'] = '&Ouml;';
		entities['215'] = '&times;';
		entities['216'] = '&Oslash;';
		entities['217'] = '&Ugrave;';
		entities['218'] = '&Uacute;';
		entities['219'] = '&Ucirc;';
		entities['220'] = '&Uuml;';
		entities['221'] = '&Yacute;';
		entities['222'] = '&THORN;';
		entities['223'] = '&szlig;';
		entities['224'] = '&agrave;';
		entities['225'] = '&aacute;';
		entities['226'] = '&acirc;';
		entities['227'] = '&atilde;';
		entities['228'] = '&auml;';
		entities['229'] = '&aring;';
		entities['230'] = '&aelig;';
		entities['231'] = '&ccedil;';
		entities['232'] = '&egrave;';
		entities['233'] = '&eacute;';
		entities['234'] = '&ecirc;';
		entities['235'] = '&euml;';
		entities['236'] = '&igrave;';
		entities['237'] = '&iacute;';
		entities['238'] = '&icirc;';
		entities['239'] = '&iuml;';
		entities['240'] = '&eth;';
		entities['241'] = '&ntilde;';
		entities['242'] = '&ograve;';
		entities['243'] = '&oacute;';
		entities['244'] = '&ocirc;';
		entities['245'] = '&otilde;';
		entities['246'] = '&ouml;';
		entities['247'] = '&divide;';
		entities['248'] = '&oslash;';
		entities['249'] = '&ugrave;';
		entities['250'] = '&uacute;';
		entities['251'] = '&ucirc;';
		entities['252'] = '&uuml;';
		entities['253'] = '&yacute;';
		entities['254'] = '&thorn;';
		entities['255'] = '&yuml;';
	}
	
	if (useQuoteStyle !== 'ENT_NOQUOTES') 
	{
		entities['34'] = '&quot;';
	}
	
	if (useQuoteStyle === 'ENT_QUOTES') 
	{
		entities['39'] = '&#39;';
	}
	
	entities['60'] = '&lt;';
	entities['62'] = '&gt;';
	
	
	// ascii decimals to real symbols
	for (decimal in entities) {
	symbol = String.fromCharCode(decimal);
	hash_map[symbol] = entities[decimal];
	}
	
	return hash_map;
}

X.parse_str = function(str, array)
{
	// Parses GET/POST/COOKIE data and sets global variables  
	// 
	// version: 909.322
	// discuss at: http://phpjs.org/functions/parse_str    // +   original by: Cagri Ekin
	// +   improved by: Michael White (http://getsprink.com)
	// +    tweaked by: Jack
	// +   bugfixed by: Onno Marsman
	// +   reimplemented by: stag019    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
	// +   bugfixed by: stag019
	// -    depends on: urldecode
	// %        note 1: When no argument is specified, will put variables in global scope.
	// *     example 1: var arr = {};    // *     example 1: parse_str('first=foo&second=bar', arr);
	// *     results 1: arr == { first: 'foo', second: 'bar' }
	// *     example 2: var arr = {};
	// *     example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', arr);
	// *     results 2: arr == { str_a: "Jack and Jill didn't see the well." }    var glue1 = '=', glue2 = '&', array2 = String(str).split(glue2),
	var glue1 = '=', glue2 = '&', array2 = String(str).split(glue2),
	i, j, chr, tmp, key, value, bracket, keys, evalStr, that = this,
	fixStr = function (str) 
	{
		return that.urldecode(str).replace(/([\\"'])/g, '\\$1').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
	}; 
	if (!array) 
	{
		array = this.window;
	}
	
	for (i = 0; i < array2.length; i++) 
	{
		tmp = array2[i].split(glue1);
		if (tmp.length < 2) 
		{
			tmp = [tmp, ''];
		}
		key   = fixStr(tmp[0]);
		value = fixStr(tmp[1]);
		
		while (key.charAt(0) === ' ') 
		{
			key = key.substr(1);
		}
		
		if (key.indexOf('\0') !== -1) 
		{
			key = key.substr(0, key.indexOf('\0'));
		}
		
		if (key && key.charAt(0) !== '[') 
		{
			keys = [];
			bracket = 0;
			
			for (j = 0; j < key.length; j++) 
			{
				if (key.charAt(j) === '[' && !bracket) 
				{
					bracket = j + 1;
				} 
				else if (key.charAt(j) === ']') 
				{
					if (bracket) 
					{
						if (!keys.length) 
						{
							keys.push(key.substr(0, bracket - 1));
						}
						
						keys.push(key.substr(bracket, j - bracket));
						bracket = 0;
						
						if (key.charAt(j + 1) !== '[') 
						{
							break;
						}
					}
				}
			}
			
			if (!keys.length) 
			{
				keys = [key];
			}
			
			for (j = 0; j < keys[0].length; j++) 
			{
				chr = keys[0].charAt(j);
				if (chr === ' ' || chr === '.' || chr === '[') 
				{
					keys[0] = keys[0].substr(0, j) + '_' + keys[0].substr(j + 1);
				}
				
				if (chr === '[') 
				{
					break;
				}
			}
			evalStr = 'array';
			for (j = 0; j < keys.length; j++) 
			{
				key = keys[j];
				if ((key !== '' && key !== ' ') || j === 0) 
				{
					key = "'" + key + "'";
				}
				else 
				{
					key = eval(evalStr + '.push([]);') - 1;
				}
				evalStr += '[' + key + ']';
				
				if (j !== keys.length - 1 && eval('typeof ' + evalStr) === 'undefined') 
				{
					eval(evalStr + ' = [];');
				}
			}
			evalStr += " = '" + value + "';\n";
			eval(evalStr);
		}
	}
}

X.htmlentities = function(string, quote_style) {
	// Convert all applicable characters to HTML entities  
	// 
	// version: 907.503
	// discuss at: http://phpjs.org/functions/htmlentities
	// +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +   improved by: nobbler
	// +    tweaked by: Jack
	// +   bugfixed by: Onno Marsman
	// +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +    bugfixed by: Brett Zamir (http://brett-zamir.me)
	// +      input by: Ratheous
	// -    depends on: get_html_translation_table
	// *     example 1: htmlentities('Kevin & van Zonneveld');
	// *     returns 1: 'Kevin &amp; van Zonneveld'
	// *     example 2: htmlentities("foo'bar","ENT_QUOTES");
	// *     returns 2: 'foo&#039;bar'
	var hash_map = {}, symbol = '', tmp_str = '', entity = '';
	tmp_str = string.toString();
	
	if (false === (hash_map = this.get_html_translation_table('HTML_ENTITIES', quote_style))) 
	{
		return false;
	}
	hash_map["'"] = '&#039;';
	for (symbol in hash_map) 
	{
		entity = hash_map[symbol];
		tmp_str = tmp_str.split(symbol).join(entity);
	}
	
	return tmp_str;
};

X.html_entity_decode = function(string, quote_style) 
{
	// Convert all HTML entities to their applicable characters  
	// 
	// version: 908.406
	// discuss at: http://phpjs.org/functions/html_entity_decode
	// +   original by: john (http://www.jd-tech.net)
	// +      input by: ger
	// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +   bugfixed by: Onno Marsman
	// +   improved by: marc andreu
	// +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +    bugfixed by: Brett Zamir (http://brett-zamir.me)
	// +      input by: Ratheous
	// -    depends on: get_html_translation_table
	// *     example 1: html_entity_decode('Kevin &amp; van Zonneveld');
	// *     returns 1: 'Kevin & van Zonneveld'
	// *     example 2: html_entity_decode('&amp;lt;');
	// *     returns 2: '&lt;'
	var hash_map = {}, symbol = '', tmp_str = '', entity = '';
	tmp_str = string.toString();
	
	if (false === (hash_map = this.get_html_translation_table('HTML_ENTITIES', quote_style))) 
	{
		return false;
	}
	
	for (symbol in hash_map) 
	{
		entity = hash_map[symbol];
		tmp_str = tmp_str.split(entity).join(symbol);
	}
	tmp_str = tmp_str.split('&#039;').join("'");
	
	return tmp_str;
};

X.ucwords = function (str) 
{
    // http://kevin.vanzonneveld.net
    // +   original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
    // +   improved by: Waldo Malqui Silva
    // +   bugfixed by: Onno Marsman
    // +   improved by: Robin
    // *     example 1: ucwords('kevin van zonneveld');
    // *     returns 1: 'Kevin Van Zonneveld'
    // *     example 2: ucwords('HELLO WORLD');
    // *     returns 2: 'HELLO WORLD'

    return (str + '').replace(/^(.)|\s(.)/g, function ($1)
    {
        return $1.toUpperCase();
    });
}

// Make an image dimension fit within the max dimenions without breaking the aspect ratio
// maxWidth = 0 and maxHeight = 0 is a special case meaning unrestricted
X.fitDimension = function(maxWidth, maxHeight, width, height)
{
	var ratio;
	var newWidthBasedOnHeight;
	var newHeightBasedOnWidth;
	
	if (!maxWidth || maxWidth < 0) maxWidth = false;
	if (!maxHeight || maxHeight < 0) maxHeight = false;
	
	if (width < maxWidth && height < maxHeight)
	{	//Image is smaller then the desired dimensions
		return {w:width, h:height, resizeFactor: 1.0};
	}
	else
	{
		ratio = width / height;
	
		newWidthBasedOnHeight = parseInt(maxHeight * ratio);
		newHeightBasedOnWidth = parseInt(maxWidth / ratio);
		
		if (!maxWidth)
		{
			if (!maxHeight)
				return {w:width, h:height,resizeFactor: 1};
			
			if (height < maxHeight)
				return {w:width, h:height,resizeFactor: 1};
			
			return {w:newWidthBasedOnHeight, h:maxHeight, resizeFactor:maxHeight / height};
		}
		
		if (!maxHeight)
		{
			if (width < maxWidth)
				return {w:width, h:height,resizeFactor: 1};
			
			return {w:maxWidth, h:newHeightBasedOnWidth, resizeFactor: maxWidth / width};
		}
		
		if (newWidthBasedOnHeight <= maxWidth) // Resizing by decreasing the height fits?
			return {w:newWidthBasedOnHeight, h:maxHeight,resizeFactor:maxHeight / height};
		
		
		//if (newHeightBasedOnWidth <= maxHeight) // Resizing by decreasing the width fits?
			return {w:maxWidth, h:newHeightBasedOnWidth, resizeFactor: maxWidth / width};
		 
	} 
};

X.binarySize_UTF8 = function(str)
{
	//iedere character is 1, 2, 3 of 4 bytes
	
	var bytes, charCode, a;
	
	bytes = 0;
	for (a = 0; a < str.length; a++)
	{
		charCode = str.charCodeAt(a);
		if (charCode < 128) //2^7
			bytes += 1;
		else if (charCode < 2048) // 2^11
			bytes += 2;
		else if (charCode < 65536) // 2^16
			bytes += 3;
		else // This should never happen; browsers use UTF-16
			bytes += 4;
	}
	
	return bytes;
};

X.isBlock = function(node)
{
	var nodeName;
	if (node instanceof Node)
	{
		if (node._swa_isBlock !== undefined)
			return node._swa_isBlock;
		
		nodeName = node.nodeName;
	}
	else
	{
		nodeName = node;
	}
	switch(nodeName.toLowerCase())
	{
		case 'div':
		case 'map':
		case 'section':
		case 'nav':
		case 'article':
		case 'aside':
		case 'h1':
		case 'h2':
		case 'h3':
		case 'h4':
		case 'h5':
		case 'h6':
		case 'hgroup':
		case 'header':
		case 'footer':
		case 'address':
		case 'p':
		case 'hr':
		case 'pre':
		case 'dialog':
		case 'blockquote':
		case 'ol':
		case 'ul':
		case 'li':
		case 'dl':
		case 'dt':
		case 'dd':
		case 'figure':
		case 'iframe':
		case 'embed':
		case 'object':
		case 'video':
		case 'audio':
		case 'canvas':
		case 'table':
		case 'form':
		case 'details':
		case 'menu':
			if (node instanceof Node)
				node._swa_isBlock = true;
			
			return true;
	}
	
	if (node instanceof Node)
		node._swa_isBlock = false;
	return false;
};

X.isVoidElement = function(node)
{
	var nodeName;
	if (node instanceof Node)
	{
		if (node._swa_isVoidElement !== undefined)
			return node._swa_isVoidElement;
		
		nodeName = node.nodeName;
	}
	else
	{
		nodeName = node;
	}

	switch (nodeName.toLowerCase())
	{
		case 'area':
		case 'base': 
		case 'br': 
		case 'col': 
		case 'command': 
		case 'embed': 
		case 'hr': 
		case 'img': 
		case 'input': 
		case 'keygen': 
		case 'link': 
		case 'meta': 
		case 'param': 
		case 'source':
			if (node instanceof Node)
				node._swa_isVoidElement = true;
			return true;	
	} 
	
	if (node instanceof Node)
		node._swa_isVoidElement = false;
	
	return false;
};


X.isPartOfList = function(node)
{
	var nodeName;
	if (node instanceof Node)
	{
		if (node._swa_isPartOfList !== undefined)
			return node._swa_isPartOfList;
		
		nodeName = node.nodeName;
	}
	else
	{
		nodeName = node;
	}
	
	switch(nodeName.toLowerCase())
	{
		case 'ol':
		case 'ul':
		case 'li':
		case 'dl':
		case 'dt':
		case 'dd':
			if (node instanceof Node)
				node._swa_isPartOfList = true;
			
			return true;
	}
	
	if (node instanceof Node)
		node._swa_isPartOfList = false;
	return false;
};

X.urlencode = function(url)
{
	// This function mimmicks PHP's rawurlencode under UTF-8
	// Any non legal URL characters (RFC 3986) are converted to percentages
	// Any exotic characters are denoted using UTF-8 (1 percentage notation 
	// per byte, for example the euro sign is %E2%82%A)
	// Only tested on Mozilla browsers (aka SpiderMonkey)
	// Does NOT use any of encodeURIComponent, encodeURI, escape, etc
	// Supports 4 byte characters (so unicode characters 0x0000 through 0x10FFFF)
	//
	// urlencode("test\u0024\u00A2\u20AC") == "test%24%C2%A2%E2%82%AC"
	// urlencode("test$Â¢â‚¬"               ) == "test%24%C2%A2%E2%82%AC"
	// 
	// Original by Joris van der Wel
	var chr, a, ret, c;
	
	if (url === undefined) return undefined;
	if (url === null) return null;
	url = url.toString();
	
	function percentize(charCode)
	{
		// Always 2 characters and uppercase
		return '%' + (charCode < 16 ? '0' : '') + charCode.toString(16).toUpperCase();
	}
	
	ret = '';
	for (a = 0; a < url.length; a++)
	{
		chr = url.charAt(a);
		if (
		   (chr >=  'A' && chr <=  'Z') || // The only (non special) legal characters in an url. See RFC 3986 
		   (chr >=  'a' && chr <=  'z') ||
		   (chr >=  '0' && chr <=  '9') ||
		    chr === '-' || chr === '_'  ||
		    chr === '.' || chr === '~'
		   )
		{
			ret += chr; // These are legal, so just include them in the string
			continue;
		}
	
		
		// Strings in javascript (ECMA-262 to be exact) are UTF-16; But we need UTF-8, so we convert it first
		c = url.charCodeAt(a);
		
		if (0xD800 <= c && c <= 0xDBFF) // High surrogate (could change last hex to 0xDB7F to treat high private surrogates as single characters); https://developer.mozilla.org/index.php?title=en/Core_JavaScript_1.5_Reference/Global_Objects/String/charCodeAt&revision=39
		{
			c = ((c - 0xD800) * 0x400) + (url.charCodeAt(a+1) - 0xDC00) + 0x10000;
			a++; // skip the next one
		}
		// We never come across a low surrogate because we skip them
		
		
		if (c >= 0x0001 && c <= 0x007F)
		{
			ret += percentize(c);
		}
		else if (c >= 0x0080 && c <= 0x07FF)
		{
			ret += percentize(0xC0 | ((c >>  6) & 0x1F));
			ret += percentize(0x80 | ((c >>  0) & 0x3F));
		}
		else if (c >= 0x0800 && c <= 0xFFFF)
		{
			ret += percentize(0xE0 | ((c >> 12) & 0x0F));
			ret += percentize(0x80 | ((c >>  6) & 0x3F));
			ret += percentize(0x80 | ((c >>  0) & 0x3F));
		}
		else
		{
			ret += percentize(0xF0 | ((c >> 18) & 0xF8));
			ret += percentize(0x80 | ((c >> 12) & 0x3F));
			ret += percentize(0x80 | ((c >>  6) & 0x3F));
			ret += percentize(0x80 | ((c >>  0) & 0x3F));
		}
		
	}
	return ret;
};

X.urldecode = function(url)
{
	// This function mimmicks PHP's rawurldecode under UTF-8
	// Any percentage notation is converted to its UTF-16 character.
	// Only tested on Mozilla browsers (Firefox 3.5)
	// Does NOT use any of decodeURIComponent, decodeURI, unescape, etc
	// Supports 4 byte characters (so unicode characters 0x0000 through 0x10FFFF)
	//
	// Original by Joris van der Wel
	var chr, a, ret, c, c2, c3, c4, hi, low;
	
	if (url === undefined) return undefined;
	if (url === null) return null;
	url = url.toString();
	ret = '';
	for (a = 0; a < url.length; a++)
	{
		chr = url.charAt(a);
		if (chr != '%')
		{
			ret += chr;
			continue;
		}
		
		c = parseInt(url.charAt(a+1) + url.charAt(a+2), 16);
		if (isNaN(c))
		{
			ret += '%'; // If php comes across something invalid, it just shows it without parsing 
			continue;
		}
		
		a += 2; // skip 2
		
		ret += String.fromCharCode(c);
	}
	
	// second pass, convert UTF-8 to UTF-16 (Strings in javascript (ECMA-262 to be exact) are UTF-16)
	url = ret;
	ret = '';
	for (a = 0; a < url.length; a++)
	{
		c = url.charCodeAt(a);
		
		//        c & 1000 0000  === 0000 0000
		if(      (c &      0x80) === 0        ) // 0xxxxxxx
		{
			ret += url.charAt(a);
		}
		//        c & 1110 0000  === 1100 0000
		else if ((c &      0xE0) ===      0xC0) // 110y yyxx	10xx xxxx
		{
			a++;
			c2 = url.charCodeAt(a);
			ret += String.fromCharCode(
					((c  & 0x1F) << 6) | 
					((c2 & 0x3F) << 0)
				);
		}
		//        c & 1111 0000  === 1110 0000
		else if ((c &      0xF0) ===      0xE0) // 1110 yyyy	10yy yyxx	10xx xxxx
		{
			a++;
			c2 = url.charCodeAt(a);
			a++;
			c3 = url.charCodeAt(a);
			ret += String.fromCharCode(
				       ((c  & 0x0F) << 12) |
				       ((c2 & 0x3F) << 6 ) |
				       ((c3 & 0x3F) << 0 )
			       );
		}
		//        c & 1111 1000  === 1111 0000
		else if ((c &      0xF8) ===      0xF0) // 1111 0zzz	10zz yyyy	10yy yyxx	10xx xxxx 
		{
			a++;
			c2 = url.charCodeAt(a);
			a++;
			c3 = url.charCodeAt(a);
			a++;
			c4 = url.charCodeAt(a);
			
			c =	((c  & 0x07) << 18) |
				((c2 & 0x3F) << 12) |
				((c3 & 0x3F) << 6 ) |
				((c4 & 0x3F) << 0 ) ;
			
			if (c >= 0x10000) // split it up using surrogates
			{
				c -= 0x10000;
				
				hi  = (c & 0xFFC00) >> 10; // first 10 bits
				low = c & 0x003FF; // last  10 bits
				
				hi  += 0xD800; // high surrogate range
				low += 0xDC00; // low surrogate range
				ret += String.fromCharCode(hi, low);
			} 
			else
			{
				ret += String.fromCharCode(c);
			}
		}
	}
	
	return ret;
};

X.SITEURL_RELATIVE = 1; //                              category/page
X.SITEURL_NORMAL = 0; //                     /subfolder/category/page
X.SITEURL_FULL = 2; // http://www.example.com/subfolder/category/page
X.siteURL = function(page, method, revision)
{
	var parts, anchor;
	parts = page.split('#', 2);
	page = parts[0];
	anchor = parts[1] ? '#' + parts[1] : '';
	
	if (revision != null)
	{
		page += page.indexOf('?') < 0 ? '?' : '&';
		page += 'r=' + revision;
	}
	
	if (!method)
		method = X.SITEURL_NORMAL;

	if (FURL_METHOD == 'get')
		page = page.replace('?', '&'); //voorkom dubbele ?
	
	page += anchor;
	
	if (method == X.SITEURL_RELATIVE)
	{
		return PAGE_URL_RELATIVE + page;
	}
	else if (method == X.SITEURL_FULL)
	{
		return PAGE_URL_FULL + page;
	}
	else //if (method == X.SITEURL_NORMAL)
	{
		return PAGE_URL + page;
	}
};

X.debuildSiteURL = function(url)
{
	var i, found, revision, result;
	url = url.trim();
	url = url.replace(/\\/g, '/');
	
	found = false;
	
	revision = null;
	
	if (!found && PAGE_URL_FULL)
	{
		i = url.indexOf(PAGE_URL_FULL);
		if (i == 0)
		{
			url = url.substr(PAGE_URL_FULL.length);
			found = true;
		}
	}
	
	if (!found && PAGE_URL)
	{
		i = url.indexOf(PAGE_URL);
		if (i == 0)
		{
			url = url.substr(PAGE_URL.length);
			found = true;
		}
	
	}
	
	if (!found && PAGE_URL_RELATIVE)
	{
		i = url.indexOf(PAGE_URL_RELATIVE);
		if (i == 0)
		{
			url = url.substr(PAGE_URL_RELATIVE.length);
			found = true;
		}
	}
	
	url = url.trim();
	
	if (url.charAt(0) == '/')
		return null;
	
	if (url.indexOf('__miracle') == 0)
		return null;
	
	result = /[\?&]r=(\d+)/.exec(url); // find the revision
	if (result && result[1] != undefined)
		revision = parseInt(result[1], 10);
	
	url = url.split('#', 2)[0];
	url = url.split('?', 2)[0]; //remove GET
	
	return {fullname: url, revision: revision};
};

window.classAppend = function (elm, className)
{
	var a;
	if (!className)
		return;
	
	className = className.split(' ');
	for (a = 0; a < className.length; a++)
	{
		if (!className[a])
			continue;
	
		if (elm.classList) // HTML 5 (Gecko 1.9.2 / Firefox 3.6)
		{
			elm.classList.add(className[a]);
			continue;
		}
	
		if (!classCheck(elm, className[a]))
		{
			elm.className += (elm.className.length ? ' ' : '') + className[a];
		}
	}
};

window.classRemove = function (elm, className)
{
	if (!className)
		return;
	if (elm.classList) // HTML 5 (Gecko 1.9.2 / Firefox 3.6)
	{
		elm.classList.remove(className);
		return;
	}


	var tmpRegex = new RegExp('\\b(' + className + ')\\b', 'gi');
	var tmpclassName = elm.className.replace (tmpRegex,' ');
	elm.className = tmpclassName.replace (/\s+/g,' ').replace (/(^\s+)|(\s+$)/g,'');
};

window.classCheck = function (elm, className)
{
	if (!className)
		return false;
	if (elm.classList) // HTML 5 (Gecko 1.9.2 / Firefox 3.6)
	{
		return elm.classList.contains(className);
	}

	var tmpclassName = ' ' + elm.className+ ' ';
	return (tmpclassName.indexOf(' ' + className + ' ') > -1);
};

X.applyCurrentPageClass = function(page, node)
{
	var currentPage, group;
	if (page)
	{
		currentPage = Page.getCurrentPage();
		if (currentPage.id == page.id)
		{
			classAppend(node, 'currentpage');
			classRemove(node, 'currentgroup');
			return;
		}
		
		group = currentPage.parentPage;
		while (group)
		{
			if (group.id == page.id)
			{
				classRemove(node, 'currentpage');
				classAppend(node, 'currentgroup');
				return;
			}
			group = group.parentPage;
		}
	}
	
	classRemove(node, 'currentpage');
	classRemove(node, 'currentgroup');
};

X.getDir = function(url) 
{
	var a, d, i;
	a = url.replace(/\\/g, '/').split('/');
	d = '';
	for (i = 0; i < a.length - 1; i++) 
	{
		d+=a[i]+'/';
	}
	return d;
}

X.getFile = function(url)
{
	var i, i2;
	url = url.replace(/\\/g, '/');
	
	i = url.lastIndexOf('/');
	if (i < 0) return url;
	return url.substr(i+1);
}

X.isLeap = function(year) 
{
	if (year % 400 == 0) return true;
	if (year % 100 == 0) return false;
	if (year % 4 == 0) return true;
	return false;
};

//http://www.quirksmode.org/js/cookies.html
X.createCookie = function(name,value,days) 
{
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
};

X.readCookie = function(name, def) 
{
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	if (!def)
		def = null;
	
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return def;
};

X.eraseCookie = function(name) 
{
	this.createCookie(name,"",-1);
};

X.nanToNull = function(number)
{
	if (isNaN(number))
		return null;
	else
		return number;
};

X.lngdate = function(format, timestamp) 
{
    // Aangepast voor localisatie 
    
    //http://phpjs.org/functions/date

    // http://kevin.vanzonneveld.net
    // +   original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
    // +      parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html)
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: MeEtc (http://yass.meetcweb.com)
    // +   improved by: Brad Touesnard
    // +   improved by: Tim Wiel
    // +   improved by: Bryan Elliott
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: David Randall
    // +      input by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: Theriault
    // +  derived from: gettimeofday
    // +      input by: majak
    // +   bugfixed by: majak
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: Alex
    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: Theriault
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: Theriault
    // +   improved by: Thomas Beaucourt  (http://www.webapp.fr)
    // +   improved by: JT
    // +   improved by: Theriault
    // %        note 1: Uses global: php_js to store the default timezone
    // *     example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400);
    // *     returns 1: '09:09:40 m is month'
    // *     example 2: date('F j, Y, g:i a', 1062462400);
    // *     returns 2: 'September 2, 2003, 2:26 am'
    // *     example 3: date('Y W o', 1062462400);
    // *     returns 3: '2003 36 2003'
    // *     example 4: x = date('Y m d', (new Date()).getTime()/1000); 
    // *     example 4: (x+'').length == 10 // 2009 01 09
    // *     returns 4: true
    // *     example 5: date('W', 1104534000);
    // *     returns 5: '53'
    // *     example 6: date('B t', 1104534000);
    // *     returns 6: '999 31'
    // *     example 7: date('W', 1293750000); // 2010-12-31
    // *     returns 7: '52'
    // *     example 8: date('W', 1293836400); // 2011-01-01
    // *     returns 8: '52'
    // *     example 9: date('W Y-m-d', 1293974054); // 2011-01-02
    // *     returns 9: '52 2011-01-02'
    var that = this,
        jsdate, f, formatChr = /\\?([a-z])/gi, formatChrCb,
        // Keep this here (works, but for code commented-out
        // below for file size reasons)
        //, tal= [],
        _pad = function (n, c) {
            if ((n = n + "").length < c) {
                return new Array((++c) - n.length).join("0") + n;
            } else {
                return n;
            }
        },
        txt_words = ["sun", "mon", "tues", "wednes", "thurs", "fri", "satur",
        "january", "february", "march", "april", "may", "june", "july",
        "august", "september", "october", "november", "december"],
        txt_ordin = {
            1: "st",
            2: "nd",
            3: "rd",
            21: "st", 
            22: "nd",
            23: "rd",
            31: "st"
        };
    formatChrCb = function (t, s) {
        return f[t] ? f[t]() : s;
    };
    f = {
    // Day
        d: function () { // Day of month w/leading 0; 01..31
            return _pad(f.j(), 2);
        },
        D: function () { // Shorthand day name; Mon...Sun
            return L('date_'+txt_words[f.w()].slice(0, 3));
        },
        j: function () { // Day of month; 1..31
            return jsdate.getDate();
        },
        l: function () { // Full day name; Monday...Sunday
            return L('date_'+txt_words[f.w()] + 'day');
        },
        N: function () { // ISO-8601 day of week; 1[Mon]..7[Sun]
            return f.w() || 7;
        },
        S: function () { // Ordinal suffix for day of month; st, nd, rd, th
            return txt_ordin[f.j()] || 'th';
        },
        w: function () { // Day of week; 0[Sun]..6[Sat]
            return jsdate.getDay();
        },
        z: function () { // Day of year; 0..365
            var a = new Date(f.Y(), f.n() - 1, f.j()),
                b = new Date(f.Y(), 0, 1);
            return Math.round((a - b) / 864e5) + 1;
        },

    // Week
        W: function () { // ISO-8601 week number
            var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3),
                b = new Date(a.getFullYear(), 0, 4);
            return 1 + Math.round((a - b) / 864e5 / 7);
        },

    // Month
        F: function () { // Full month name; January...December
            return L('date_'+txt_words[6 + f.n()]);
        },
        m: function () { // Month w/leading 0; 01...12
            return _pad(f.n(), 2);
        },
        M: function () { // Shorthand month name; Jan...Dec
            return L('date_'+txt_words[6 + f.n()].slice(0, 3));
        },
        n: function () { // Month; 1...12
            return jsdate.getMonth() + 1;
        },
        t: function () { // Days in month; 28...31
            return (new Date(f.Y(), f.n(), 0)).getDate();
        },

    // Year
        L: function () { // Is leap year?; 0 or 1
            var y = f.Y(), a = y & 3, b = y % 4e2, c = y % 1e2;
            return 0 + (!a && (c || !b));
        },
        o: function () { // ISO-8601 year
            var n = f.n(), W = f.W(), Y = f.Y();
            return Y + (n === 12 && W < 9 ? -1 : n === 1 && W > 9);
        },
        Y: function () { // Full year; e.g. 1980...2010
            return jsdate.getFullYear();
        },
        y: function () { // Last two digits of year; 00...99
            return (f.Y() + "").slice(-2);
        },

    // Time
        a: function () { // am or pm
            return jsdate.getHours() > 11 ? "pm" : "am";
        },
        A: function () { // AM or PM
            return f.a().toUpperCase();
        },
        B: function () { // Swatch Internet time; 000..999
            var H = jsdate.getUTCHours() * 36e2, // Hours
                i = jsdate.getUTCMinutes() * 60, // Minutes
                s = jsdate.getUTCSeconds(); // Seconds
            return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
        },
        g: function () { // 12-Hours; 1..12
            return f.G() % 12 || 12;
        },
        G: function () { // 24-Hours; 0..23
            return jsdate.getHours();
        },
        h: function () { // 12-Hours w/leading 0; 01..12
            return _pad(f.g(), 2);
        },
        H: function () { // 24-Hours w/leading 0; 00..23
            return _pad(f.G(), 2);
        },
        i: function () { // Minutes w/leading 0; 00..59
            return _pad(jsdate.getMinutes(), 2);
        },
        s: function () { // Seconds w/leading 0; 00..59
            return _pad(jsdate.getSeconds(), 2);
        },
        u: function () { // Microseconds; 000000-999000
            return _pad(jsdate.getMilliseconds() * 1000, 6);
        },

    // Timezone
        e: function () { // Timezone identifier; e.g. Atlantic/Azores, ...
// The following works, but requires inclusion of the very large
// timezone_abbreviations_list() function.
/*              var abbr = '', i = 0, os = 0;
            if (that.php_js && that.php_js.default_timezone) {
                return that.php_js.default_timezone;
            }
            if (!tal.length) {
                tal = that.timezone_abbreviations_list();
            }
            for (abbr in tal) {
                for (i = 0; i < tal[abbr].length; i++) {
                    os = -jsdate.getTimezoneOffset() * 60;
                    if (tal[abbr][i].offset === os) {
                        return tal[abbr][i].timezone_id;
                    }
                }
            }
*/
            return 'NA';
        },
        I: function () { // DST observed?; 0 or 1
            // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
            // If they are not equal, then DST is observed.
            var a = new Date(f.Y(), 0), // Jan 1
                c = Date.UTC(f.Y(), 0), // Jan 1 UTC
                b = new Date(f.Y(), 6), // Jul 1
                d = Date.UTC(f.Y(), 6); // Jul 1 UTC
            return 0 + ((a - c) !== (b - d));
        },
        O: function () { // Difference to GMT in hour format; e.g. +0200
            var a = jsdate.getTimezoneOffset();
            return (a > 0 ? "-" : "+") + _pad(Math.abs(a / 60 * 100), 4);
        },
        P: function () { // Difference to GMT w/colon; e.g. +02:00
            var O = f.O();
            return (O.substr(0, 3) + ":" + O.substr(3, 2));
        },
        T: function () { // Timezone abbreviation; e.g. EST, MDT, ...
// The following works, but requires inclusion of the very
// large timezone_abbreviations_list() function.
/*              var abbr = '', i = 0, os = 0, default = 0;
            if (!tal.length) {
                tal = that.timezone_abbreviations_list();
            }
            if (that.php_js && that.php_js.default_timezone) {
                default = that.php_js.default_timezone;
                for (abbr in tal) {
                    for (i=0; i < tal[abbr].length; i++) {
                        if (tal[abbr][i].timezone_id === default) {
                            return abbr.toUpperCase();
                        }
                    }
                }
            }
            for (abbr in tal) {
                for (i = 0; i < tal[abbr].length; i++) {
                    os = -jsdate.getTimezoneOffset() * 60;
                    if (tal[abbr][i].offset === os) {
                        return abbr.toUpperCase();
                    }
                }
            }
*/
            return 'NA';
        },
        Z: function () { // Timezone offset in seconds (-43200...50400)
            return -jsdate.getTimezoneOffset() * 60;
        },

    // Full Date/Time
        c: function () { // ISO-8601 date.
            return 'Y-m-d\\Th:i:sP'.replace(formatChr, formatChrCb);
        },
        r: function () { // RFC 2822
            return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
        },
        U: function () { // Seconds since UNIX epoch
            return Math.round(jsdate.getTime() / 1000);
        }
    };
    this.date = function (format, timestamp) {
        that = this;
        jsdate = (
            (typeof timestamp === 'undefined') ? new Date() : // Not provided
            (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
            new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
        );
        return format.replace(formatChr, formatChrCb);
    };
    return this.date(format, timestamp);
};

X.createFlashElement = function(url, width, height, options) // hier is ook een php versie van
{
	var node, node2, param, a, link;
	if (!options)
		options = {};
	_.extend(options, {
		'wmode': 'opaque',
		'allowFullScreen': 'true',
		'allowScriptAccess': 'never',
		'swliveconnect': 'false'
	});
	
	function param(name, value)
	{
		var param;
		param = document.createElement('param');
		X.attr(param, 'name', name);
		X.attr(param, 'value', value);
		return param;
	}
	
	node = document.createElement('object');
	X.attr(node, 'classid', 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000');
	X.attr(node, 'codebase', 'http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab');
	X.attr(node, 'class', 'riFlashMovie');
	X.attr(node, 'width', width);
	X.attr(node, 'height', height);
	
	node.appendChild(param('movie', url));
	for (a in options)
	{
		node.appendChild(param(a, options[a]));
	}
	
	node2 = document.createElement('object');
	node.appendChild(node2);
	X.attr(node2, 'data', url);
	X.attr(node2, 'type', 'application/x-shockwave-flash');
	X.attr(node2, 'class', 'riFlashMovie');
	X.attr(node2, 'width', width);
	X.attr(node2, 'height', height);
	
	node2.appendChild(param('pluginurl', 'http://get.adobe.com/flashplayer/'));
	for (a in options)
	{
		node2.appendChild(param(a, options[a]));
	}
	
	node2.appendChild(document.createTextNode('Unable to load the flash video '));
	
	node2.appendChild(link = document.createElement('a'));
	X.attr(link, 'href', url);
	link.appendChild(document.createTextNode(url));
	
	node2.appendChild(document.createTextNode(', you may need to install flash: '));
	node2.appendChild(link = document.createElement('a'));
	X.attr(link, 'href', 'http://get.adobe.com/flashplayer/');
	link.appendChild(document.createTextNode('http://get.adobe.com/flashplayer/'));
		
	return node;
};

window.HTMLClassParser = function()
{
	this.classes = {};
	this.rmData = {};
};
var HTMLClassParser_ = window.HTMLClassParser.prototype;
HTMLClassParser_.setClassName = function(className)
{
	this.classes = {};
	this.rmData = {};
	this.addClassName(className);
}

HTMLClassParser_.addClassName = function(className)
{
	var classes, a;
	if (className == null) return;
	className = className.replace(/\s{2,}/g, ' ').trim();
	classes = X.explode(' ', className);
	
	for (a = 0; a < classes.length; a++)
	{
		this.addClass(classes[a]);
	}
}

HTMLClassParser_.removeClassName = function(className)
{
	var classes, a;
	className = className.replace(/\s{2,}/g, ' ').trim();
	classes = X.explode(' ', className);
	
	for (a = 0; a < classes.length; a++)
	{
		this.removeClass(classes[a]);
	}
}

HTMLClassParser_.getClassName = function(hideRMData)
{
	var ret, cls, key, value;
	ret = '';
	for (cls in this.classes)
	{
		if (this.classes[cls])
			ret += (ret ? ' ': '') + cls;
	}
	
	if (hideRMData)
		return ret;
	
	for (key in this.rmData)
	{
		value = this.rmData[key];
		ret += (ret ? ' ': '')+ 'rmd-'+key + (value == '' ? '' : '-'+value);
	}
	
	return ret;
}

HTMLClassParser_.addClass = function(cls)
{
	var rmd;
	if ( (rmd = breakApartRMData(cls)) )
	{
		this.setRMData(rmd.key, rmd.value);
	}
	else
	{
		this.classes[cls] = true;
	}
}

HTMLClassParser_.hasClass = function(cls)
{
	var rmd, val;
	if ( (rmd = breakApartRMData(cls)) )
	{
		val = this.getRMData(rmd.key);		
		return val !== false && val == rmd.value;
	}
	else
	{
		return !!this.classes[cls];
	}
}

HTMLClassParser_.removeClass = function(cls)
{
	var rmd;
	if ( (rmd = breakApartRMData(cls)) )
	{			
		if (this.getRMData(rmd.key) == rmd.value)
			this.removeRMData(rmd.key);
	}
	else
	{
		delete this.classes[cls];
	}
}

// Voor key zijn - en spaties niet toegestaan, voor value zijn alleen spaties niet toegestaan: http://www.w3.org/TR/html4/struct/global.html#adef-class
HTMLClassParser_.setRMData = function(key, value)
{
	this.rmData[key] = value;
}

HTMLClassParser_.removeRMData = function(key)
{
	delete this.rmData[key];
}

HTMLClassParser_.getRMData = function(key, def)
{
	if (this.rmData[key] !== undefined)
		return this.rmData[key];
	else
		return def === undefined ? false : def;
}

HTMLClassParser_.getRMIntData = function(key)
{
	var val;
	val = parseInt(this.getRMData(key, 0), 10);
	if (isNaN(val))
		return 0;
	else
		return val; 
	
}

HTMLClassParser_.hasRMData = function(key)
{
	return this.rmData[key] !== undefined;
}

HTMLClassParser_.setFromElement = function(elm)
{
	this.setClassName(X.attr(elm, 'class'));
}

HTMLClassParser_.applyOnElement = function(elm, hideRMData)
{
	var cls;
	cls = this.getClassName(hideRMData);
	if (!cls)
		X.attr(elm, 'class', null);
	else
		X.attr(elm, 'class', cls);
}

function breakApartRMData(cls)
{
	var arr;
	if (cls.substr(0, 4) == 'rmd-')
	{
		arr = X.explode('-', cls, 3);
		if (!arr[1])
			return false;
		
		return {'key': arr[1], 'value': (arr[2] ? arr[2] : '')};
	}
	return false;
}






if (window.jQuery)
{
	jQuery.expr[':'].icontains = function(obj, index, meta, stack)
	{
		if (!meta[3])
			return false;
		return (obj.textContent || obj.innerText || jQuery(obj).text() || '').toLowerCase().indexOf(meta[3].toLowerCase()) >= 0;
	};
	
	jQuery.easing['BounceEaseOut'] = function(p, t, b, c, d) 
	{
		if ((t/=d) < (1/2.75)) 
		{
			return c*(7.5625*t*t) + b;
		}
		else if (t < (2/2.75))
		{
			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
		}
		else if (t < (2.5/2.75))
		{
			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
		}
		else
		{
			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
		}
	};

}


var imagesToPreload = []; //FIFO
var preloadObject = new Image();
preloadObject.onload = preloadObject.onLoad = imageLoad;
preloadObject.onerror = preloadObject.onError = imageError;
var startPreloading = false;
var isPreloading = false;

function preload_load(e)
{
	startPreloading = true;
	preloadNext();
}

if (window.addEventListener)
	window.addEventListener('load', preload_load, false);
else if (window.attachEvent)
	window.attachEvent('onload', preload_load);

X.preloadImg = function(img)
{
	imagesToPreload.push(img);

	if (!isPreloading) preloadNext();
};

X.preloadImgs = function(images)
{
	var a, len;
	
	for (a = 0, len = images.length; a < len; a++)
		preloadImg(images[a]);
};

function imageLoad()
{
	if (!isPreloading) return;
	preloadNext();
}

function imageError()
{
	if (!isPreloading) return;

	// There might be a browser which fires both onload and onerror. However because in preloadNext the src is reset, the other event should never appear
	preloadNext();
}

function preloadNext()
{
	var url;
	
	isPreloading = false;
	if (!startPreloading) return;
	
	url = imagesToPreload.shift();
	if (!url)
	{
		preloadObject.src = 'about:blank';
		return; // There is nothing to preload. At this point isPreloading is false, so the next time a preloadImage is added, the preloading restarts
	}
	d('Starting preload of %s', url);
	preloadObject.src = url;
	isPreloading = true;
}



X.Range = {};

X.Range.insertNode = function(rng, node)
{
	var startContainer, endContainer, startOffset, endOffset, before, after, parent, createFn, data;
	startContainer = rng.startContainer;
	startOffset = rng.startOffset; 
	endContainer = rng.endContainer;
	endOffset = rng.endOffset;
	parent = startContainer.parentNode;
	
	if (startContainer instanceof CharacterData && parent)
	{ //gecko bug occurs under these conditions
		if (!rng.startOffset)
		{
			parent.insertBefore(node, startContainer);
			rng.setStart(node, 0);
		}
		else // we need to break up a text node (or something similar)
		{
			if (startContainer.nodeType == Node.TEXT_NODE)
				createFn = document.createTextNode;
			else if (startContainer.nodeType == Node.COMMENT_NODE)
				createFn = document.createComment;
			else if (startContainer.nodeType == Node.CDATA_SECTION_NODE)
				createFn = document.createCDATASection;
			
			data = startContainer.data;
			
			before = createFn.call(document, data.substring(0,startOffset));
			after  = createFn.call(document, data.substr(startOffset));
			
			parent.insertBefore(before, startContainer);
			parent.insertBefore(node, startContainer);
			parent.insertBefore(after, startContainer);
			parent.removeChild(startContainer);
			rng.setStart(node, 0);
			
			if (startContainer == endContainer)
				rng.setEnd(after, X.clip(endOffset-startOffset, 0, after.length));
		}
	}
	else
	{
		rng.insertNode(node); //the original implementation
	}
};

X.Range.setNode = function(rng, node) // w3 selectNode does not always behave properly
{
	var parent, offset, len;
	
	len = X.Node.getNodeLength(node);
	
	if (len)
	{
		rng.setStart(node, 0);
		rng.setEnd(node, len);
	}
	else if (parent = node.parentNode)
	{
		offset = X.Element.getChildOffset(parent, node);
		rng.setStart(parent, offset);
		rng.setEnd(parent, offset+1);
	}
};

X.Range.equals = function(rng, otherRange)
{
	if (!otherRange)
		return false;

	return rng.startContainer === otherRange.startContainer &&
	       rng.endContainer   === otherRange.endContainer   &&
	       rng.startOffset    === otherRange.startOffset    &&
	       rng.endOffset      === otherRange.endOffset       ;
};

X.Range.fullyContainsRange = function(rng, range)
{
	return rng.compareBoundaryPoints(Range.START_TO_START, range) !== 1 &&
	       rng.compareBoundaryPoints(Range.END_TO_END, range) !== -1
};

X.Range.intersectsRange = function(rng, range)
{
	return rng.compareBoundaryPoints(Range.END_TO_START, range) !== 1 && 
	       rng.compareBoundaryPoints(Range.START_TO_END, range) !== -1;    
};



X.Selection = {};
X.Selection.getAllRanges = function(sel) // array is not live
{
	var a, ranges;
	ranges = [];
	for (a = 0; a < sel.rangeCount; a++)
		ranges.push(sel.getRangeAt(a));
	
	return ranges;
};

X.Selection.fullyContainsRange = function(sel, range)
{
	var a;
	for (a = 0; a < sel.rangeCount; a++)
	{
		if (X.Range.fullyContainsRange(sel.getRangeAt(a), range))
			return true;	
	}
	
	return false;
};

X.Node = {};
X.Element = {};
X.CharacterData = {};
// splits the text node
// the existing text node is the data before the offset
// a second text node is created and added for the data after the offset
// returns the second text node
X.CharacterData.splitNode = function(node, offset)
{
	var createFn, data, second;

	if (node.nodeType == Node.TEXT_NODE)
		createFn = 'createTextNode';
	else if (node.nodeType == Node.COMMENT_NODE)
		createFn = 'createComment';
	else if (node.nodeType == Node.CDATA_SECTION_NODE)
		createFn = 'createCDATASection';
	
	data = node.data;
	
	node.data = data.substring(0,offset);
	second  = document[createFn](data.substr(offset));
	
	if (node.parentNode)
		node.parentNode.insertBefore(second, node.nextSibling);
	
	return second;
};

X.Element.splitNode = function(node, offset)
{
	var a, childNodes, newNode;
	
	if (!node.parentNode)
		return null;
	
	newNode = document.createElement(node.tagName);
	for (a = 0; a < node.attributes.length; a++)
	{
		X.attr(newNode, node.attributes[a].name, node.attributes[a].value);
	}
	
	node.parentNode.insertBefore(newNode, node.nextSibling);
	
	childNodes = _.toArray(node.childNodes);
	for (a = offset; a < childNodes.length; a++)
	{
		newNode.appendChild(childNodes[a]);
	}
	
	return newNode;
};

X.Node.splitNode = function(node, offset)
{
	if (node instanceof Element)
		return X.Element.splitNode(node, offset);
	else
		return X.CharacterData.splitNode(node, offset);
};

X.Element.copyAttributesFromElement = function(node, source)
{
	var a;
	
	for (a = 0; a < source.attributes.length; a++)	
		X.attr(node, source.attributes[a].name, source.attributes[a].value);
};

X.Node.getNodeLength = function(node)
{
	if (node instanceof Element)
		return node.childNodes.length;
	else
		return node.length;
};

X.Element.getChildOffset = function(node, child)
{
	var a;
	
	for (a = 0; a < node.childNodes.length; a++)
	{
		if (node.childNodes[a] === child)
			return a;
	}
	
	return -1;
};

X.Node.isOffsetAtFront = function(node, offset)
{
	if (node instanceof Element)
		return X.Element.isOffsetAtFront(node, offset);
	else
		return X.CharacterData.isOffsetAtFront(node, offset);
};

X.Node.isOffsetAtEnd = function(node, offset)
{
	if (node instanceof Element)
		return X.Element.isOffsetAtEnd(node, offset);
	else
		return X.CharacterData.isOffsetAtEnd(node, offset);
};

X.Element.isOffsetAtFront = function(node, offset)
{
	var firstNode;
	if (!offset) 
		return true;
	
	firstNode = X.Element.firstUsefulChild(node);
	if (!firstNode)
		return true;
	
	if (X.Element.getChildOffset(node, firstNode) < offset)
		return false;
	
	return true;
	
};

X.Element.isOffsetAtEnd = function(node, offset)
{
	var lastNode, len;
	len = X.Node.getNodeLength(node);
	if (offset === len)
		return true;
	
	if (node instanceof CharacterData)
		return false;
	
	lastNode = X.Element.lastUsefulChild(node);
	if (!lastNode)
		return true;
	
	if (X.Element.getChildOffset(node, lastNode) < offset)
		return true;
	
	return false;
};

X.attr = function(node, attribute, value)
{
	if (attribute === undefined)
		return node.attributes;

	if (value === undefined)
	{
		return node.getAttribute(attribute);
	}
	else if (value === null)
	{
		node.removeAttribute(attribute);
		return null;
	}
	else
	{
		value = value.toString();
		node.setAttribute(attribute, value);
		return value;
	}
};

X.CharacterData.isOffsetAtFront = function(node, offset)
{
	return offset === 0;
};

X.CharacterData.isOffsetAtEnd = function(node, offset)
{
	return offset === node.length;
};

/** https://developer.mozilla.org/en/Whitespace_in_the_DOM
 * Throughout, whitespace is defined as one of the characters
 *  "\t" TAB \u0009
 *  "\n" LF  \u000A
 *  "\r" CR  \u000D
 *  " "  SPC \u0020
 *
 * This does not use Javascript's "\s" because that includes non-breaking
 * spaces (and also some other characters).
 */


/**
 * Determine whether a node's text content is entirely whitespace.
 *
 * @param this  A node implementing the |CharacterData| interface (i.e.,
 *             a |Text|, |Comment|, or |CDATASection| node
 * @return     True if all of the text content of |nod| is whitespace,
 *             otherwise false.
 */
X.CharacterData.isAllWhiteSpace = function(node)
{
	// Use ECMA-262 Edition 3 String and RegExp features
	if (node.data.length == 0)
		return true;
	return !(/[^\t\n\r ]/.test(node.data));
};


/**
 * Determine if a node should be ignored by the iterator functions.
 *
 * @param this  An object implementing the DOM1 |Node| interface.
 * @return     true if the node is:
 *                1) A |Text| node that is all whitespace
 *                2) A |Comment| node
 *             and otherwise false.
 */

X.Node.isIgnoreable = function(node)
{
	return ( node.nodeType == Node.COMMENT_NODE) || // A comment node
	       ( (node.nodeType == Node.TEXT_NODE) && X.CharacterData.isAllWhiteSpace(node) ); // a text node, all ws
};

/**
 * Version of |previousSibling| that skips nodes that are entirely
 * whitespace or comments.  (Normally |previousSibling| is a property
 * of all DOM nodes that gives the sibling node, the node that is
 * a child of the same parent, that occurs immediately before the
 * reference node.)
 *
 * @param this  The reference node.
 * @return     Either:
 *               1) The closest previous sibling to |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
X.Node.previousUsefulSibling = function(node)
{
	var sib = node;
	while ((sib = sib.previousSibling)) 
	{
		if (!X.Node.isIgnoreable(sib))
			return sib;
	}
	return null;
};

/**
 * Version of |nextSibling| that skips nodes that are entirely
 * whitespace or comments.
 *
 * @param this  The reference node.
 * @return     Either:
 *               1) The closest next sibling to |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
X.Node.nextUsefulSibling = function(node)
{
	var sib = node;
	while ((sib = sib.nextSibling)) 
	{
		if (!X.Node.isIgnoreable(sib))
			return sib;
	}
	return null;
};

X.Node.previousSibling = function(node, nodeName)
{
	node = node.previousSibling;
	while (node)
	{
		if (!nodeName || node.nodeName.toLowerCase() == nodeName)
			return node;
	
		node = node.previousSibling;
	}
	return null;
};

X.Node.nextSibling = function(node, nodeName)
{
	node = node.nextSibling;
	while (node)
	{
		if (!nodeName || node.nodeName.toLowerCase() == nodeName)
			return node;
	
		node = node.nextSibling;
	}
	return null;
};

/**
 * Version of |lastChild| that skips nodes that are entirely
 * whitespace or comments.  (Normally |lastChild| is a property
 * of all DOM nodes that gives the last of the nodes contained
 * directly in the reference node.)
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The last child of |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
X.Element.lastUsefulChild = function(node)
{
	var res = node.lastChild;
	while (res) 
	{
		if (!X.Node.isIgnoreable(res))
			return res;
		res = res.previousSibling;
	}
	return null;
};

X.Element.isFirstUseful = function(node, otherNode)
{
	var sib;
	sib = node.firstChild;
	
	while(sib)
	{
		if (sib === otherNode)
			return true;
		
		if (!X.Node.isIgnoreable(sib))
			break;
		
		sib = sib.nextSibling;
	}
	return false;
};

X.Element.isLastUseful = function(node, otherNode)
{
	var sib = node.lastChild;
	while (sib)
	{
		if (sib === otherNode)
			return true;
	
		if (!X.Node.isIgnoreable(sib))
			break;
		
		sib = sib.previousSibling;
	}
	return false;
};

/**
 * Version of |firstChild| that skips nodes that are entirely
 * whitespace and comments.
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The first child of |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
X.Element.firstUsefulChild = function(node)
{
	var res = node.firstChild;
	while (res)
	{
		if (!X.Node.isIgnoreable(res))
			return res;
		res = res.nextSibling;
	}
	return null;
};

/**
 * Version of |data| that doesn't include whitespace at the beginning
 * and end and normalizes all whitespace to a single space.  (Normally
 * |data| is a property of text nodes that gives the text of the node.)
 *
 * @param txt  The text node whose data should be returned
 * @return     A string giving the contents of the text node with
 *             whitespace collapsed.
 */
X.CharacterData.trimmedData = function(node)
{
	var data = node.data;
	data = data.replace(/[\t\n\r ]+/g, " ");
	
	if (data.charAt(0) == " ")
		data = data.substring(1, data.length);
	
	if (data.charAt(data.length - 1) == " ")
		data = data.substring(0, data.length - 1);
	
	return data;
};

// removes the node and adds it content to the parent
X.Element.dissolveNode = function(node)
{
	var parent;
	
	parent = node.parentNode;
	if (!parent) return;
	
	while (node.firstChild)
	{
		parent.insertBefore(node.firstChild, node);
	}
	
	parent.removeChild(node);
};

X.Element.removeAllChildren = function(node)
{
	while (node.lastChild)
		node.removeChild(node.lastChild);
};

X.Node.removeThisNode = function(node)
{
	if (node.parentNode)
		node.parentNode.removeChild(node);
};

X.Element.replaceThisNode = function(node, newNode)
{
	if (!node.parentNode)
		return null;
	
	node.parentNode.insertBefore(newNode, node);
	while (node.firstChild)
		newNode.appendChild(node.firstChild);
	
	node.parentNode.removeChild(node);
	return newNode;
};

X.Element.insertBeforeMe = function(node, newNode)
{
	if (!node.parentNode)
		return;
	
	node.parentNode.insertBefore(newNode, node);
};

X.Element.removeStyleInside = function(node, prop)
{
	var a, nodes;
	nodes = node.getElementsByTagName('*');
	
	for (a = 0; a < nodes.length; a++)
	{
		nodes[a].style[prop] = null;
	}
	
};

X.Element.getLastNode = function(node, nodeName)
{
	var node;
	node = node.lastChild;
	while (node)
	{
		if (!nodeName || node.nodeName.toLowerCase() == nodeName)
			return node;
	
		node = node.previousSibling;
	}
	return null;
};

X.Element.getFirstNode = function(node, nodeName)
{
	var node;
	node = node.firstChild;
	while (node)
	{
		if (!nodeName || node.nodeName.toLowerCase() == nodeName)
			return node;
	
		node = node.nextSibling;
	}
	return null;
};



var hacks = {};
X.addHack = function(name)
{
	hacks[name] = localStorage['RMHACK_'+name] == '1';
	
	Object.__defineSetter__.call(window.hack, name,
	function(val)
	{
		hacks[name] = !!val;
		if (val)
			localStorage['RMHACK_'+name] = '1';
		else
			delete localStorage['RMHACK_'+name];
	});
	
	Object.__defineGetter__.call(window.hack, name, 
	function()
	{
		return !!hacks[name];
	});
	
	if (hacks[name])
	{
		d('%s hack activated', name);
	}
};

window.hack = function()
{
	var a, name;
	
	for (a in hacks)
	{
		name = a;
		if (name.length < 50) 
			name += Array(50-name.length).join(" ");
		
		console.log('%s = %b', name, hacks[a]);
	}
};

X.RandomKISS2007 = function() 
{
	/*
	George Marsaglia, 2007-06-23
	http://groups.google.com/group/comp.lang.fortran/msg/6edb8ad6ec5421a5
	http://baagoe.com/en/RandomMusings/javascript/
	*/
	return (function (args) 
	{
		var x = 123456789;
		var y = 362436069;
		var z =  21288629;
		var w =  14921776;
		var c =         0;
		
		function kiss() 
		{
			var t;
			
			x += 545925293;
			x >>>= 0;
			
			y ^= y << 13;
			y ^= y >>> 17;
			y ^= y << 5;
			
			t = z + w + c;
			z = w;
			c = t >>> 31;
			w = t & 0x7fffffff;
			
			return x + y + w >>> 0;
		}
		
		var mash = (function () 
		{
			var s = 0, c = 1;
			return function(data) 
			{
				var norm = Math.pow(2, -32);
				var a = 2095533;
				var t;
				data = data.toString();
				for (var i = 0; i < data.length; i++) 
				{
					s -= data.charCodeAt(i) * 65537 * norm;
					if (s < 0) {
						s += 1;
					}
					t = a * s + c * norm;
					s = t - (c = t | 0);
					t = a * s + c * norm;
					s = t - (c = t | 0);
				}
				return s;
			};
		}());
		
		if (args.length === 0 ) 
		{
			args = [+new Date()];
		}
		
		for (var i = 0; i < args.length; i++) 
		{
			x += mash(args[i]) * 0x100000000;
			y += mash(args[i]) * 0x100000000;
			z += mash(args[i]) * 0x100000000;
			w += mash(args[i]) * 0x100000000;
		}
		
		if (y === 0) 
		{
			y = 1;
		}
		
		c ^= z >>> 31;
		z &= 0x7fffffff;
		if ((z % 7559) === 0) 
		{
			z++;
		}
		
		w &= 0x7fffffff;
		if ((w % 7559) === 0) 
		{
			w++;
		}
		
		function frac() 
		{
			return kiss() * 2.3283064365386963e-10;
		}
		frac.uint32 = kiss;
		frac.frac53 = function() 
		{
			return kiss() * 2.3283064365386963e-10 +
			(kiss() & 0x1fffff) * 1.1102230246251565e-16;
		};
		frac.args = args;
		frac.version = 'KISS2007 0.7';
		
		return frac;
	}(Array.prototype.slice.call(arguments)));
}



})();
