
// FIXME: api doc hier rein schreiben und einen generator fuer .JS sowie fuer APIDOC machen in JS

(function() {
	var global = window ? window : this;
	var createOrDestroy = function(doDelete) {
		return function(path, namespace) {
			if (!namespace) namespace = global;
			var names = path.split("."); // split path specification into individual package names.
			if (names.length > 0) {
				for (var i = 0; i < names.length; i++) { // iterate through all package names
					var name = names[i];
					if (doDelete && i + 1 == names.length) {
						// delete that namespace,
						// (Please note: We never do delete the global namespace, due to names.length > 0)
						delete(namespace[name]);
						return;
					}
					if (namespace[name]) {
						// if an intermediate namespace already exists, ensure that it is an object
						if (typeof namespace[name] != "object") {
							throw new Error(
								"jsix.js: namespace " + path +
								" conflicts with existing object " + names.slice(0,i).join('.')
							);
						}
					}
					else {
						// if the intermediate namespace doesn't exist,
						if (doDelete) return; // return in delete()
						namespace[name] = {}; // or create an empty namespace object for that package in create()
					}
					// proceed creating deeper package within that intermediate package
					namespace = namespace[name];
				}
			}
		}
	}

	createOrDestroy(false)('jsix.namespace', global);

	jsix.namespace.global = global; // the global namespace
	jsix.namespace.create = createOrDestroy(false); // function to create a namespace
	jsix.namespace.destroy = createOrDestroy(true); // function to delete a namespace
	
})();

(function() {
	var undef;
	var isDefined  = function(x) { return x !== undef && x !== null; }
	var isBoolean  = function(b) { return typeof(b) == "boolean"; }
	var isNumber   = function(n) { return typeof(n) == "number"; }
	var isString   = function(s) { return typeof(s) == "string"; }
	var isFunction = function(f) { return typeof(f) == "function"; }
	var isArray = function(a) {
		var it =
			typeof(a) == "object" &&
			typeof(a.length) == "number" &&
			(typeof(a.join) == "function" || !isDefined(a.nodeType));
		return it;
	}
	// FIXME: laeuft niocht im IE, weil .item keine function ist
	var isArrayNix    = function(a) {
		var it =
			typeof(a) == "object" &&
			typeof(a.length) == "number" &&
			(typeof(a.item) == "function" || typeof(a.join) == "function");
		return it;
/*
		FIXME:
		FIXME: testfaelle mit a. item ist eine nodelist und eine array
		if (typeof(a) != "object") return false;
		if (typeof(a.length) != "number") return false;
		if (typeof(a.item) == "function") return true;
		// if (typeof(a.nodeType) == "number") return false;
		if (typeof(a.join) == "function") return true;
		return false;
*/
	}
	var isRegExp   = function(r) { return isDefined(r) && typeof(r.exec) == "function"; }
	var isObject   = function(o) { return typeof(o) == "object"; }
	
	// DOCU: fuer einen iterator i darf auf i.next(jsix.undefined) keinen definierten Wert liefern,
	// sonst werden schleifen wie map in eine endlosschleife laufen
	var isIterator = function(i) {
		var type_of_i_get = typeof(i.get);
		return typeof(i) == "object"
			&& typeof(i.next) == "function"
			&& (type_of_i_get == "function" || type_of_i_get == "undefined");
	}

	jsix.undefined  = undef; // a undefined value, accessible in every web browser.
	jsix.isDefined  = isDefined;
	jsix.isBoolean  = isBoolean;
	jsix.isNumber   = isNumber;
	jsix.isString   = isString;
	jsix.isFunction = isFunction;
	jsix.isArray    = isArray;
	jsix.isRegExp   = isRegExp;
	jsix.isObject   = isObject;
	jsix.isIterator = isIterator;
})();

jsix.namespace.create("jsixoo");
(function() {

	var callMethod = function(o, m, argv) {
		if (argv.length == 0) return m(o);
		if (argv.length == 1) return m(o,argv[0]);
		if (argv.length == 2) return m(o,argv[0],argv[1]);
		if (argv.length == 3) return m(o,argv[0],argv[1],argv[2]);
		if (argv.length == 4) return m(o,argv[0],argv[1],argv[2],argv[3]);
		if (argv.length == 5) return m(o,argv[0],argv[1],argv[2],argv[3],argv[4]);
	}

	// FIXME: push funkt nicht, mit arguments[] weil wir jetzt lauter undefined uebergeben im jsixoo-modul
	// hier arguments[0],arguments[1],arguments[2],
	//
	var mkconstruct = function(f) {
		return (function(fn) {
			return function() {
				this.isConstructing = true; // DOCU wenn im debugger waehrend constructing, dann kein toString auswerten etc.
				callMethod(this, fn, arguments);
				delete(this.isConstructing);
			}
		})(f); // binde die function hier an die lokale variable in einen closure
	}
	
	var mkoo = function(namespace, construct) {
		construct = mkconstruct(construct);
		var proto = new Object();
		for (var key in namespace) {
			var val = namespace[key];
			if (jsix.isFunction(val)) {
				switch (key) {
				
					case 'toString':
						proto.toString = (function(fn) {
							return function() { return this.isConstructing ? "-initializing-" : callMethod(this, fn, arguments); }
						})(val); // DOCU: den wert von val - sprich die funktion - binden, nicht die variable val
						break;
	
					case 'valueOf':
						proto.valueOf = (function(fn) {
							return function() { return this.isConstructing ? jsix.undefined : callMethod(this, fn, arguments); }
						})(val);
						break;
	
					default:
						proto[key] = (function(fn) {
							return function() { return callMethod(this, fn, arguments); }
						})(val);
						break;
				}
			}
		}
		if (!construct) construct = mkconstruct(function(){}); // default constructor macht nichts
		construct.prototype = proto;
		return construct;
	}
	
	jsixoo.mkoo = mkoo;
	
})();

jsix.namespace.create("jsix.array");
(function() {

	// undefined wird auch drauf gepushed, achtung. push gab es in frueheren browsern nicht
	var push = function() {
		var array = arguments[0];
		if (jsix.isArray(array) && arguments.length > 1) {
			for (var i = 1; i < arguments.length; i++) {
				array[array.length++] = arguments[i];
			}
		}
		return array;
	}
	var pop = function(array) {
		var it;
		if (jsix.isArray(array) && array.length > 0) {
			it = array[array.length - 1];
			array.length--;
		}
		return it;
	}
	var shift = function(array) {
		var it;
		if (jsix.isArray(array) && array.length > 0) {
			it = array[0];
			var source = 1;
			var target = 0;
			while (source < array.length) array[target++] = array[source++];
			array.length--;
		}
		return it;
	}
	var unshift = function() {
		var array = arguments[0];
		if (jsix.isArray(array) && arguments.length > 1) {
			var argc = arguments.length - 1;
			if (argc > 0) {
				var source = array.length - 1;
				array.length += argc;
				var target = array.length - 1;
				while (source >= 0) array[target--] = array[source--];
				while (target >= 0) array[target--] = arguments[argc--];
			}
		}
		return array;
	}
	var filter = function(array, fn, from, to) {
		if (jsix.isArray(array)) {
			if (!jsix.isDefined(fn)) fn = jsix.isDefined; // kein filter angegeben -> filtere alle definierten values
			if (!jsix.isNumber(from) || from < 0) from = 0;
			if (!jsix.isNumber(to) || to > array.length) to = array.length;
			var source = from;
			var target = from;
			while (source < to) { // im angegebenen bereich filtern
				var obj = array[source];
				if (fn(obj)) {
					if (source > target) array[target] = obj;
					target++;
				}
				source++;
			}
			if (source > target) { // den rest einfach verschieben
				while (source < array.length) array[target++] = array[source++];
				array.length = target;
			}
		}
		return array;
	}

	//from, to ist optional	
	var count = function(array, fn, from, to) {
		if (!jsix.isDefined(array)) return 0;
		if (!jsix.isDefined(fn)) fn = jsix.isDefined; // kein filter angegeben -> filtere alle definierten values
		var n = 0;
		filter(array, function(o) { if (fn(o)) n++; return true; }, from, to);
		return n;
	}
	
	var copy = function(array, fn, from, to) {
		if (!jsix.isArray(array)) return [];
		if (!jsix.isFunction(fn)) fn = function(o){return true;}; // kein filter -> kopiere alle elemente
		if (!jsix.isNumber(from) || from < 0) from = 0;
		if (!jsix.isNumber(to) || to > array.length) to = array.length;
		var it = [];
		var source = from;
		var target = 0;
		while (source < to) {
			var o = array[source++];
			if (fn(o)) it[target++] = o;
		}
		return it;
	}

	// join mussten wir neu bauen weil das verhalten im standard .join im firefox nicht immer vorhersagbar ist:
	// succeeded, since (string) ((function(a){jsix.array.shift(a);return a.join(',');})([1,jsix.undefined])) == (string) ""
	// succeeded, since (string) ((function(a){jsix.array.shift(a);return a.join(',');})([1,2,jsix.undefined])) == (string) "2,"
	var join = function(array, separator, from, to) {
		/*
		FIXME: geht nicht wegen too-much-rekursion (type cast auf standard array gibt es nicht fuer unsere Objecte jsixoo.Array)
		return Array(filter(array, jsix.isDefined, from, to)).join(separator);
		*/
		if (!jsix.isArray(array)) return "";
		if (!jsix.isDefined(separator)) separator = ",";
		if (!jsix.isString(separator)) separator = String(separator);
		if (!jsix.isNumber(from) || from < 0) from = 0;
		if (!jsix.isNumber(to) || to > array.length) to = array.length;
		var n = count(array, jsix.isDefined, from, to);
		var sb = new Array(n);
		var source = from;
		var target = 0;
		while (source < to) {
			var o = array[source++];
			if (jsix.isDefined(o)) sb[target++] = String(o);
		}
		return sb.join(separator);
	}
	
	var serialize = function(array, undef) {
		if (!jsix.isArray(array)) return "";
		if (!jsix.isDefined(undef)) undef = "";
		var sb = new Array(2 * array.length + 2);
		var target = 0;
		sb[target++] = "[";
		for (var source = 0; source < array.length; source++) {
			if (source > 0) sb[target++] = ",";
			var o = array[source];
			if (!jsix.isDefined(o)) o = undef;
			if (jsix.isArray(o)) o = serialize(o, undef);
			sb[target++] = String(o);
		}
		sb[target] = "]";
		return sb.join("");
	}
	
	jsix.array.pop = pop;
	jsix.array.push = push;
	jsix.array.shift = shift;
	jsix.array.unshift = unshift;
	jsix.array.filter = filter;
	jsix.array.count = count;
	jsix.array.copy = copy;
	jsix.array.join = join;
	jsix.array.serialize = serialize;
	// FIXME. evtl mit isaArray() hier auch noch definieren?
	
	jsixoo.Array = jsixoo.mkoo(
		jsix.array,
		function(array) {
			array.length = 0;
			for (var i = 1; i < arguments.length; i++) {
				var arg = arguments[i];
				if (jsix.isDefined(arg)) array.push(arg);
			}
		}
	);
	
})();

jsix.namespace.create("jsix.lambda");
(function() {

	var identity = function(x) {
		return x;
	}

	var map_apply_iterate;
	var map_apply = function(l, fn) {
		return function(objs) {
			if (!objs) return l;
			if (!fn) fn = identity;
			if (jsix.isArray(objs)) {
				for (var i = 0; i < objs.length; i++) {
					var obj = fn(objs[i]);
					if (l && jsix.isDefined(obj)) jsix.array.push(l, obj);
				}
			}
			else {
				if (jsix.isIterator(objs)) {
					map_apply_iterate(l, fn, objs)(objs.first);
				}
				else {
					// assume, that objs is a single object
					var obj = fn(objs);
					if (l && jsix.isDefined(obj)) jsix.array.push(l, obj);
				}
			}
			return l;
		}
	}
	map_apply_iterate = function(l, fn, iterator) {
		var get = jsix.isDefined(iterator.get) ? iterator.get : identity;
		return function (i) {
			if (!jsix.isDefined(i)) return l;
			var me = arguments.callee;
			while (jsix.isDefined(i)) {
				if (jsix.isArray(i)) return map_apply(jsix.undefined, me)(i); // iterator elements must not be arrays, recurse into it and stop iterating
				if (jsix.isIterator(i)) return map_apply_iterate(l, fn, i)(i.first); // iterator elements must not be iterators, recurse into it and stop iterating
				var obj = fn(get(i));
				if (l && jsix.isDefined(obj)) jsix.array.push(l, obj);
				i = iterator.next(i);
			}
			return l;
		}
	}
	var map = function(fn) { return map_apply([], fn); }
	var apply = function(fn) { return map_apply(jsix.undefined, fn); }
	
	var filter = function(fn) {
		if (!jsix.isFunction(fn)) fn = function(o) { return false; }
		return map(
			function(o) {
				return fn(o) ? o : jsix.undefined;
			}
		);
	}

	var fold_foldr_iterate;
	var fold_foldr = function(r) {
		return function(fn) {
			return function (obj) {
				return function (objs) {
					if (!objs || !fn) return obj;
					if (jsix.isArray(objs)) {
						if (r) for (var i = objs.length - 1; i >= 0 ; i--) obj = fn(obj, objs[i]);
						else   for (var i = 0; i < objs.length; i++) obj = fn(obj, objs[i]);
					}
					else {
						if (jsix.isIterator(objs)) {
							if (r) obj = fold_foldr(r)(fn)(obj)(map(identity)(objs));
							else   obj = fold_foldr_iterate(r, fn)(obj, objs)(objs.first);
						}
						else {
							obj = fn(obj, objs);
						}
					}
					return obj;
				}
			}
		}
	}
	fold_foldr_iterate = function(r, fn) {
		return function (obj, iterator) {
			var me = arguments.callee;
			var get = jsix.isDefined(iterator.get) ? iterator.get : identity;
			return function (i) {
				var me2 = arguments.callee;
				while (jsix.isDefined(i)) {
					if (jsix.isArray(i)) { // return fold_foldr(r)(fn)(obj)(i); // iterator indexes must not be arrays, recurse into them
						for (var j = 0; j < i.length; j++) {
							obj = me2 (i[j]);
						}
						return obj;
					}
					else if (jsix.isIterator(i)) return me(obj, i)(i.first); 
					// return me(obj,i)(i.first); // iterator indexes must not be iterators, recurse into them
					else {
						var got = get(i);
						if (jsix.isDefined(got)) obj = fn(obj, got);
					}
					i = iterator.next(i);
				}
				return obj;
			}
		}
	}
	var fold = fold_foldr(false);
	var foldr = fold_foldr(true);

	jsix.lambda.identity = identity;
	jsix.lambda.map = map;
	jsix.lambda.filter = filter;
	jsix.lambda.fold = fold;
	jsix.lambda.foldr = foldr;

})();

jsix.namespace.create("jsix.init");
(function() {

	var listeners = [];
	var run = function() {
		if (listeners && listeners.length > 0) {
			for (var i = 0; i < listeners.length; i++) {
				try { listeners[i](); } catch(e) { alert(e); }
			}
		}
		listeners = jsix.undefined;
	};
	var register = function(listener) {
		if (listeners) {
			// FIXME. window.onload musste nur genau einmal aufrufenm wenn listeners.length == 0 ist oder sowas
			 if (window) {
				if (window.addEventListener) { // W3C DOM standard event registration
						window.addEventListener("load", run, false);
				}
				else {
					if (window.attachEvent) { // IE5+ event registration
							window.attachEvent("onload", run);
					}
					else {
						// IE4 and old browsers
						// sorry, but we must overwrite the <body onload=""> listener,
						// since we cannot access it during load time:
						// jsix.array.push(listeners, window.onload); doesn't work
						window.onload = run;
					}
				}
			}
			jsix.array.push(listeners, listener);
		}
	};
	jsix.init.register = register;
})();

jsix.namespace.create("jsix.string");
(function() {

	var trim = function(s) {
		return jsix.isDefined(s) ? String(s).replace(/^\s+/,"").replace(/\s+$/,"") : s;
	}

	var equalsIgnoreCase = function(s,t) {
		return jsix.isDefined(s) ? String(s).toLowerCase() == String(t).toLowerCase() : !jsix.isDefined(t);
	}
	
	var startsWith = function(s, t) {
		if (!s || !t || s.length < t.length) return false;
		if (s.length == t.length) return s == t;
		return t == s.substr(0,t.length);
	}

	var startsWithIgnoreCase = function(s, t) {
		if (!s || !t || s.length < t.length) return false;
		if (s.length == t.length) return equalsIgnoreCase(s, t);
		return equalsIgnoreCase(t, s.substr(0,t.length));
	}

	var regexpPattern = new RegExp("^/(.*?)/([igm]*)$");
	var regexpWhitespace = /\s+/g;
	
	// wenn par ein regexp ist, dann muss /g angeschaltet sein! (NEE, stimmt nicht, siehe tests)
	var split = function(s, pat, count) {
		if (!jsix.isString(s) || !(s.length > 0)) return [];
		if (!jsix.isRegExp(pat) && !jsix.isString(pat)) pat = regexpWhitespace;
		if (!jsix.isNumber(count)) count = s.length;
		var it = [];
		var pos = 0;
		var matchPattern;
		if (!jsix.isRegExp(pat) && !(matchPattern = regexpPattern.exec(pat))) {
			if (pat == "") {
				for (pos = 0; pos < s.length; pos++) it.push(s.charAt(pos));
			}
			else {
				// FIXME: evtl kuerzer mit map?
				var matchpos = 0;
				while (count > 0 && pos < s.length && (matchpos = s.indexOf(pat, pos)) >= 0) {
					var endpos = matchpos + pat.length;
					if (pos < matchpos) it.push(s.substring(pos, matchpos));
					it.push(s.substring(matchpos, endpos));
					pos = endpos;
					count--;
				}
			}
		}
		else {
			if (matchPattern) {
				var purePattern = matchPattern[1];
				var pureParams = matchPattern[2];
				if (!pureParams) pureParams = "g";
				else if (pureParams.indexOf('g') < 0) pureParams = pureParams + "g";
				pat = new RegExp(purePattern, pureParams);
			}
			
			var match;
			if (pat.exec("")) { // pattern matches empty string as well! let's ensure termination
				var prefix = "";
				while (count-- > 0 && s.length > 0 && (match = pat.exec(s))) {
					var separator = match[0];
					if (separator.length == 0) {
						prefix += s.charAt(0);
						s = s.substring(1);
					}
					else {
						var endpos = match.index + separator.length;
						if (match.index > 0) prefix += s.substring(0, match.index);
						if (prefix.length > 0) it.push(prefix);
						it.push(s.substring(match.index, endpos));
						s = s.substring(endpos);
						prefix = "";
					}
				}
				if (prefix.length > 0) it.push(prefix);
			}
			else {
				while (count-- > 0 && s.length > 0 && (match = pat.exec(s))) {
					var endpos = match.index + match[0].length;
					if (pos < match.index) it.push(s.substring(pos, match.index));
					it.push(s.substring(match.index, endpos));
					pos = endpos;
				}
			}
		}
		if (pos < s.length) it.push(s.substring(pos));
		return it;
	}

	jsix.string.trim = trim;
	jsix.string.startsWith = startsWith;
	jsix.string.startsWithIgnoreCase = startsWithIgnoreCase;
	jsix.string.equalsIgnoreCase = equalsIgnoreCase;
	jsix.string.split = split;

})();

jsix.namespace.create("jsix.string.js");
(function() {

	// FIXME: parser fuer Klammern bauen {...} um Javascript-Files schneller zu parsen (fuer coverage)
	
	var quote_regexp = new RegExp("\"", "g");
	var quote = function(s) {
		return '"' + s.replace(quote_regexp, "\\\"") + '"';
	}

	var split_quote_regexp = new RegExp("[\"']", "g");
	var splitJavascript = function(src, commentsAndStrings) {
		var snippets = [];

		var push = function(s) {
			if (commentsAndStrings) {
				snippets.push(commentsAndStrings.length);
				commentsAndStrings.push(s);
			}
			else {
				snippets.push(s);
			}
		}
	
		var splits = jsix.string.split(src, split_quote_regexp); // splitte bei " ' /* //
		var i = 0;
		while (i < splits.length) {
			var split = splits[i++];
			switch (split) {
				case '"':
				case "'":
					var quote = split;
					var proceed = true;
					while (proceed && i + 1 < splits.length) {
						var nextsplit = splits[i++];
						var pos = nextsplit.indexOf(quote);
						if (pos == 0 || (pos > 0 && nextsplit.charAt(pos - 1) != '\\')) {
							proceed = false;
							var to = pos + 1;
							push(split + nextsplit.substring(0, to));
							if (to < nextsplit.length) snippets.push(nextsplit.substring(to));
						}
						else {
							split += nextsplit;
						}
					}
					if (proceed) push(split);
					break;
				case "//":
				case "/*":
					var expect = split == "//" ? "\n" : "*/";
					var proceed = true;
					while (proceed && i + 1 < splits.length) {
						var nextsplit = splits[i++];
						var pos = nextsplit.indexOf(expect);
						if (pos >= 0) {
							proceed = false;
							var to = pos + expect.length;
							push(split + nextsplit.substring(0, to));
							if (to < nextsplit.length) snippets.push(nextsplit.substring(to));
						}
						else {
							split += nextsplit;
						}
					}
					if (proceed) push(split);
					break;
				default:
					snippets.push(split);
					break;
			}
		}
		return snippets;
	}

	jsix.string.js.quote = quote;
	jsix.string.js.splitJavascript = splitJavascript;

})();

// DOCU: jsixoo.mkoo macht automatisch aus dem modul eine klasse, bzw, einen constructor.
// konvention: alle functionen im modul haben die signatur (object, args)
// functionales currying im modul kannste bei der zuweisung zum modul umsetzen
jsix.namespace.create("jsix.string.buffer");
(function() {

	var join = function(sb) {
		if (sb.load > 1) {
			sb.buf[0] = jsix.array.join(sb.buf, "", 0, sb.load);
			for (var i = sb.load - 1; i > 0; i--) sb.buf[i] = 0;
			sb.load = 1;
		}
		return sb.load;
	}

	var append = function(sb, o) {
		var s = String(o);
		if (sb.load == sb.buf.length) sb.load = join(sb);
		sb.buf[sb.load++] = s;
		sb.length += s.length;
		return sb;
	}

	var toString = function(sb) {
		if (sb.load > 1) sb.load = join(sb);
		return sb.buf[0];
	}
	
	jsix.string.buffer.append = append;
	jsix.string.buffer.toString = toString;
	
	jsixoo.StringBuffer = jsixoo.mkoo(
		jsix.string.buffer,
		function(sb, capacity) {
			if (!jsix.isArray(sb.buf)) {
				if (!jsix.isDefined(capacity)) capacity = 32;
				sb.buf = new Array(capacity);
			}
			sb.load = 0;
			sb.length = 0;
		}
	);

})();

jsix.namespace.create("jsix.util");
(function() {

})();

jsix.namespace.create("jsix.xml");
(function() {

		var encode = function(s) {
			if (!s) return s;
			s = String(s);
//			s = s.replace(/&/g,"&amp;");
			s = s.replace(/>/g,"&gt;");
			s = s.replace(/</g,"&lt;");
//			s = s.replace(/"/g,"&quote;");
//			s = s.replace(/'/g,"&quote;"); // FIXME: was ist ein single quote?
			s = s.replace(/&/g,"&amp;");
			return s;
		}
		
		// FIXME evtl == "object" also jsix.isObject(DOMParser)??
		var domParser = typeof(DOMParser) != "undefined" ? new DOMParser() : jsix.undefined;
		
		/** Parse the XML document contained in the string argument and return 
		 * a DOM Document object that represents it.
		 * From FiXME Oreilly 21.1.3
		 */
		var parse = function(s) {
		    if (domParser) { // Mozilla, Firefox, and related browsers
		        return domParser.parseFromString(s, "application/xml");
		    }
		    else {
		    	// FIXME isObject()??
			    if (typeof(ActiveXObject) != "undefined") {
			        // Internet Explorer.
			        var doc = XML.newDocument();  // Create an empty document
			        doc.loadXML(text);            // Parse text into it
			        return doc;                   // Return it
			    }
			    else {
			        // As a last resort, try loading the document from a data: URL
			        // This is supposed to work in Safari.  Thanks to Manos Batsis and
			        // his Sarissa library (sarissa.sourceforge.net) for this technique.
			        var url = "data:text/xml;charset=utf-8," + encodeURIComponent(text);
			        var request = new XMLHttpRequest();
			        request.open("GET", url, false);
			        request.send(null);
			        return request.responseXML;
			    }
			}
		}
		
		jsix.xml.parse = parse;
		jsix.xml.encode = encode;
})();

jsix.namespace.create("jsix.html.nodetype");
jsix.namespace.create("jsix.html.eventtype");
(function() {

	var NT = jsix.html.nodetype;
		NT.ELEMENT = 1;
		NT.ATTRIBUTE = 2;
		NT.TEXT = 3;
		NT.CDATA = 4;
		NT.ENTITY_REFERENCE = 5;
		NT.ENTITY = 6;
		NT.PROCESSING_INSTRUCTION = 7;
		NT.COMMENT = 8;
		NT.DOCUMENT = 9;
		NT.DOCUMENT_TYPE = 10;
		NT.DOCUMENT_FRAGMENT = 11;
		NT.NOTATION = 12;

/*
	FIXME besser so:
	jsix.html.eventtype = {
		MOUSEOVER: 4,
		MOUSEOUT: 8,
		CLICK: 64
	}
*/
	var ET = jsix.html.eventtype;
		ET.MOUSEOVER = 4;
		ET.MOUSEOUT = 8;
		ET.CLICK = 64;

	var isNode = function(node)  {
		return (node && node.nodeType && typeof(node.nodeType) == "number");
	}

	var getFiltered = function(fn) {
		var getFilteredIntern = function(node) {
			var nodes = [];
			if (isNode(node)) {
				if (node = fn(node)) jsix.array.push(nodes, node);
			}
			else {
				if (jsix.isString(node)) {
					var nodeById = document.getElementById(node);
					if (nodeById) {
						if (nodeById = fn(nodeById)) jsix.array.push(nodes, nodeById);
					}
					else {
						var nodesByName = document.getElementsByName(node);
						if (nodesByName) {
							for (var i = 0; i < nodesByName.length; i++) {
								var nodeByName = nodesByName[i];
								if (nodeByName = fn(nodeByName)) jsix.array.push(nodes, nodeByName);
							}
						}
					}
				}
				else {
					if (jsix.isArray(node)) {
						nodes = jsix.lambda.map(getFilteredIntern)(node);
					}
				}
			}
			return nodes;
		}
		return getFilteredIntern;
	}
	//
	//FIXME: get liefert im IE keinen knoten, solange das document noch nicht fertig gerendert ist.
	//// daher diese zusaetzliche funktion:
	var findMostRecent = function(arg) {
		var div;
		var i = document.all.length - 1;
		while (i >= 0) {
			var n = document.all[i--];
			if (jsix.string.equalsIgnoreCase(arg.nodeName, n.nodeName)) return n;
		}
		return div;
	}

	var get = function(arg) {
		if (jsix.isFunction(arg)) {
			// arg ist ein filter
			return getFiltered(arg);
		}
		else {
			// arg ist kein filter, vermutlich ein string oder ein node.
			return getFiltered(jsix.lambda.identity)(arg);
		}
	}

	// FIXME: find kannste durch unser maechtiges map() einfacher abbilden!!
	var find = function(argv) {
		var rootnodes;
		if (argv['root']) {
			rootnodes = get(argv['root']);
		}
		else {
			rootnodes = jsix.namespace.global.document;
			if (!isNode(rootnodes)) {
				// Im IE 5.5 ist window.document kein geeigneter nodeset
				rootnodes = jsix.namespace.global.document.getElementsByTagName("body");
			}
		}
		if (isNode(rootnodes)) rootnodes = [rootnodes];
//		var rootnodes = get(argv['root'] ? argv['root'] : jsix.namespace.global.document);
		if (rootnodes.length == 0) return jsix.undefined;

		var prMatch = argv['match'];
		if (!jsix.isFunction(prMatch)) prMatch = jsix.lambda.identity;

		var prPrune = argv['prune'];
		if (!jsix.isFunction(prPrune)) prPrune = function(node) { return false; };

		var fnExec = argv['exec'];

		var maxdepth = Number(argv['depth']);
		if (isNaN(maxdepth)) maxdepth = -1;

		var findAttributes = findAttributes = jsix.isBoolean(argv['attributes']) ? argv['attributes'] : false; // default: no attributes are found
		var fnTraverse =
			jsix.isFunction(argv['traverse'])
				? argv['traverse']
				: fnTraverse = function(node) { return node.childNodes; };
				// traverse the DOM tree in depth first order, if no traverse function has been specified

		var firstMatchOnly = jsix.isBoolean(argv['first-match-only']) ? argv['first-match-only'] : false; // default: all matches are found

		var nodes = [];
		var stack = [];
		var depth = [];

		for (var i = 0; i < rootnodes.length; i++) {
			var root = rootnodes[i];
			if (!prPrune(root, 0)) {
				jsix.array.push(stack, root);
				jsix.array.push(depth, 0);
			}
		}

		while (stack.length > 0) {
			var n = stack.shift();
			var d = depth.shift();
			var d1 = d + 1;
			if (maxdepth < 0 || d1 <= maxdepth) {
				if (!prPrune(n, d)) {
					if (findAttributes && n.attributes && n.attributes.length > 0) {
						for (var i = n.attributes.length - 1; i >= 0; i--) {
							var attr = n.attributes[i];
							if (attr.nodeValue) {
								// In IE, .nodeValue contains a lot of attributes with undefined value
								jsix.array.push(stack, attr);
								jsix.array.push(depth, d1);
							}
						}
					}
					if (fnTraverse) {
						var more = fnTraverse(n);
						if (more) {
							if (isNode(more)) {
								jsix.array.push(stack, more);
								jsix.array.push(depth, d1);
							}
							else {
								if (jsix.isArray(more)) {
								// FIXME achtung: order ist depth first, left first
								// wieso hatteste das umgedreht?
									for (var i = 0; i < more.length; i++) {
//									for (var i = more.length - 1; i >= 0; i--) {
										jsix.array.push(stack, more[i]);
										jsix.array.push(depth, d1);
									}
								}
							}
						}
					}
				}
			}
			if (n = prMatch(n, d)) {
				if (fnExec) fnExec(n, d);
				if (firstMatchOnly) return n;
				jsix.array.push(nodes, n);
			}
		}
		return firstMatchOnly ? jsix.undefined : nodes;
	}

	var toStringBuffer = function (sb, node) {
		if (isNode(node)) {
			switch (node.nodeType) {
				case NT.ELEMENT:
					jsix.array.push(sb, "<");
					jsix.array.push(sb, node.nodeName);
					for (var i = 0; i < node.attributes.length; i++) {
						toStringBuffer(sb, node.attributes[i], "");
					}
					if (node.childNodes && node.childNodes.length > 0) {
						jsix.array.push(sb, ">");
						for (var i = 0; i < node.childNodes.length; i++) {
							toStringBuffer(sb, node.childNodes[i]);
						}
						jsix.array.push(sb, "</");
						jsix.array.push(sb, node.nodeName);
						jsix.array.push(sb, ">");
					}
					else {
						jsix.array.push(sb, "/>");
					}
					return;

				case NT.ATTRIBUTE:
					if (node.nodeValue) {
						jsix.array.push(sb, " "); // FIXME verkennen push.push ist kuerzer
						jsix.array.push(sb, node.nodeName);
						jsix.array.push(sb, "='");
						jsix.array.push(sb, node.nodeValue);
						jsix.array.push(sb, "'");
					}
					return;

				case NT.TEXT:
					jsix.array.push(sb, node.nodeValue);
					return;

				case NT.CDATA:
					jsix.array.push(sb, "<![CDATA[");
					jsix.array.push(sb, node.nodeValue);
					jsix.array.push(sb, "]]>");
					return;

				default:
					return;
			}
		}
		else {
			if (jsix.isArray(node)) {
				for (var i = 0; i < node.length; i++) {
					toStringBuffer(sb, node[i]);
				}
				return;
			}
			else {
				jsix.array.push(sb, String(node));
			}
		}
	}

	var serialize = function (node) {
		var sb = [];
		if (isNode(node)) {
			toStringBuffer(sb, node);
		}
		else {
			for (var i = 0; i < node.length; i++) {
				if (i > 0) jsix.array.push(sb, "\n");
				toStringBuffer(sb, node[i]);
			}
		}
		return sb.join("");
	}

	// FIXME: parameterstruktur anpassen mach eine hash!!
	var foldCssClass = function (fn) {
		return function (node, arg1, arg2) {
			if (!node) return 0;

			var cssClass = jsix.string.trim(jsix.isString(arg1) ? arg1 : jsix.isString(arg2) ? arg2 : jsix.undefined);

			// create regular expression, if not specified as a parameter
			var cssClassRegexp = jsix.isRegExp(arg1) ? arg1 : jsix.isRegExp(arg2) ? arg2 : new RegExp("\\b" + cssClass + "\\b");

			if (isNode(node)) {
				return fn(cssClass, cssClassRegexp, node, jsix.string.trim(node.className));
			}
			else {
				return jsix.lambda.fold(
					function(count, node) { return count + fn(cssClass, cssClassRegexp, node, jsix.string.trim(node.className)); }
				)(0)(node);
			}
		}
	}

// FIXME umbenennen, ist interne funktion
	var hasCssClass = function (cssClass, cssClassRegexp, node, nodeClassName) {
		if ((!cssClass) && (!cssClassRegexp)) return 0;
		if (!nodeClassName || nodeClassName.length == 0) return 0;
		return cssClassRegexp.exec(nodeClassName) ? 1 : 0; // node.className contains cssClass
	}

				// FIXME. addCssClass kann seine klassen mit split teilen, wenn er mehrere bekommt class="a b c"
	var addCssClass = function (cssClass, cssClassRegexp, node, nodeClassName) {
		if (!cssClass) return 0;
		if (!nodeClassName || nodeClassName.length == 0) {
			node.className = cssClass;
			return 1;
		}
		if (cssClassRegexp.exec(nodeClassName)) return 0; // className already contains cssClass
		node.className = nodeClassName + " " + cssClass;
		return 1;
	}

	var delCssClass = function (cssClass, cssClassRegexp, node, nodeClassName) {
		if ((!cssClass) && (!cssClassRegexp)) return 0;

		if (!nodeClassName || nodeClassName.length == 0) return 0;
		var nodeClassNameOrig = nodeClassName;

		nodeClassName = jsix.string.trim(nodeClassName.replace(cssClassRegexp, "").replace(/  */g, " "));

		if (nodeClassNameOrig != nodeClassName) {
			node.className = nodeClassName;
			return 1;
		}
		else {
			return 0;
		}
	}
	
	var toggleCssClass = function (cssClass, cssClassRegexp, node, nodeClassName) {
		if ((!cssClass) && (!cssClassRegexp)) return 0;

		if (hasCssClass(cssClass, cssClassRegexp, node, nodeClassName)) {
			delCssClass(cssClass, cssClassRegexp, node, nodeClassName);
			return 0;
		}
		else {
			addCssClass(cssClass, cssClassRegexp, node, nodeClassName);
			return 1;
		}
	}

	var addListener = function(node, eventType, fn) {
		if (!node) return;
		if (!fn) return;
		switch (eventType) {

			case ET.CLICK:
				if (node.captureEvents) node.captureEvents(Event.CLICK);
				node.onclick = function(n) { return function(event){fn(n);} }(node);
				return true;

			case ET.MOUSEOVER:
				if (node.captureEvents) node.captureEvents(Event.MOUSEOVER);
				node.onmouseover = function(n) { return function(event){fn(n);} }(node);
				return true;

			case ET.MOUSEOUT:
				if (node.captureEvents) node.captureEvents(Event.MOUSEOUT);
				node.onmouseout = function(n) { return function(event){fn(n);} }(node);
				return true;

			default:
				return false;
		}
	}
	
			// FIXME: xml parseerror als Error werfen., gibt er momentan im html aus.
	
	var appendXhtmlNode;
	var appendXhtmlNodes = function(node) {
		return jsix.lambda.map(function(n) { return appendXhtmlNode(node,n); });
	}
	appendXhtmlNode = function(node, xhtmlNode) {
		var n = jsix.undefined;
		switch (xhtmlNode.nodeType) {
		
			case NT.ELEMENT:
				n = document.createElement(xhtmlNode.nodeName);
				appendXhtmlNodes(n)(xhtmlNode.childNodes);
				appendXhtmlNodes(n)(xhtmlNode.attributes);
				break;
				
			case NT.ATTRIBUTE:
				node.setAttribute(xhtmlNode.nodeName, xhtmlNode.nodeValue);
				break;
				
			case NT.TEXT:
			case NT.CDATA:
			case NT.ENTITY_REFERENCE:
				n = document.createTextNode(xhtmlNode.nodeValue);
				break;
				
			case NT.DOCUMENT:
				appendXhtmlNodes(node)(xhtmlNode.childNodes);
		  		break;
			
			case NT.ENTITY:
			case NT.COMMENT:
			case NT.DOCUMENT_TYPE:
			case NT.DOCUMENT_FRAGMENT:
			case NT.PROCESSING_INSTRUCTION:
			case NT.NOTATION:
			default:
		  		break;
		}

		if (n && node && node.nodeType == NT.ELEMENT) node.appendChild(n);
		return n;
	}
	
	var appendXhtml = function(node, xhtml) {
		if (jsix.isString(xhtml)) xhtml = jsix.xml.parse("<jsix>"+xhtml+"</jsix>");
		// wir koennen auch liste von elementen parsen, wenn wir ein tag rumschreiben
		return appendXhtmlNodes(node)(xhtml.childNodes[0].childNodes);
		// childNodes[0] entfernt das document
		// .childNodes entfernt unser element <jsix>
	}
	
	var setImageSrcDelayed = function(url, ms) {
		if (!jsix.isDefined(document.images)) return;
		if (document.images.length == 0) return;
		var n = document.images[document.images.length-1]; // find the img just before the call
		if (ms <= 0) jsix.init.register(function () { n.src = url; });
		else jsix.init.register(function () { window.setTimeout(function() { n.src = url; }, ms); });
	}

	// left-most-inner-most
	var iteratorLMIM = function(nodes) {
		if (!jsix.isDefined(nodes)) nodes = document;
		return {
			first: nodes,
			next: function(n) { return n.childNodes; }
		}
	};
	
	jsix.html.iteratorLMIM = iteratorLMIM;
	jsix.html.setImageSrcDelayed = setImageSrcDelayed;

	jsix.html.get = get;
	jsix.html.findMostRecent = findMostRecent;
	jsix.html.find = find;
	jsix.html.serialize = serialize;
	jsix.html.hasCssClass = foldCssClass(hasCssClass);
	jsix.html.addCssClass = foldCssClass(addCssClass);
	jsix.html.delCssClass = foldCssClass(delCssClass);
	jsix.html.toggleCssClass = foldCssClass(toggleCssClass);
	jsix.html.addListener = addListener;
	jsix.html.appendXhtml = appendXhtml;

})();



