var CHAMAELEON_CALENDAR = {
	// erster Tag der Woche (Deutschland: 1 (= Montag), USA: 0 (= Sonntag)), erlaubte Werte: 0 bis 6
	FirstDayOfWeekIndex: 1,

	// Januar-Tag, der immer in der ersten Woche des Jahres enthalten ist (Deutschland: 4, USA: 1), erlaubte Werte: 1 bis 7
	FirstWeekOfYearContains: 4,
	// ob Wochen zerteilt werden dürfen (falls true, muss FirstWeekOfYearContains = 1 sein!)
	SplitWeeksAcrossYears: false,

	langTable: {
		date: "%1$td.%1$tm.%1$tY",
		datetime: "%1$td.%1$tm.%1$tY %1$tH:%1$tM",
		day0: "Sonntag",
		day1: "Montag",
		day2: "Dienstag",
		day3: "Mittwoch",
		day4: "Donnerstag",
		day5: "Freitag",
		day6: "Samstag",
		dayabbr0: "So",
		dayabbr1: "Mo",
		dayabbr2: "Di",
		dayabbr3: "Mi",
		dayabbr4: "Do",
		dayabbr5: "Fr",
		dayabbr6: "Sa",
		month0:	"Januar",
		month1:	"Februar",
		month2:	"März",
		month3:	"April",
		month4:	"Mai",
		month5:	"Juni",
		month6:	"Juli",
		month7:	"August",
		month8:	"September",
		month9:	"Oktober",
		month10:"November",
		month11:"Dezember",
		time: "Uhrzeit"
	},

	activateDateField : function(calendarInput, calendarButton, calendarTime) {
		calendarInput.removeAttribute('disabled');
		calendarButton.removeAttribute('disabled');
		if (!calendarInput.value) {
			var d = new Date();
			calendarInput.value = this.dateToString(d, true);
			if (calendarTime) {
				calendarTime.value = d.getTime();
			}
		}
		calendarInput.focus();
		this.hide();
	},

	deactivateDateField : function(calendarInput, calendarButton, calendarTime) {
		calendarInput.setAttribute('disabled','disabled');
		calendarButton.setAttribute('disabled','disabled');
		calendarInput.value = '';
		if (calendarTime) {
			calendarTime.value = '';
		}
		this.hide();
	},	

	setLangTable: function (newLangTable) {
		this.langTable = newLangTable;
	},
	
	getDayOfWeek : function(date) {
		return (date.getDay() + 7 - this.FirstDayOfWeekIndex) % 7;
	},

	getDayOfWeekAbbr : function(date) {
		if (date instanceof Date) {
			date = this.getDayOfWeek(date);
		}
		return this.langTable["dayabbr" + (date + this.FirstDayOfWeekIndex) % 7];
	},
	
	getMonthName : function(month) {
		return this.langTable["month" + parseInt(month)];
	},

	isLeapYear : function(year) {
		return (year % 4) ? false : (year % 100 ? true : (year % 400 ? false : true));
	},

	// gibt das Datum als Array Wochentag (Montag=0...Sonntag=6), Wochennummer und Bezugsjahr (zu dem die Woche gehört, kann vom "echten" Jahr abweichen) zurueck
	getWeekOfYear : function(date) {
		var dayOfWeek = this.getDayOfWeek(date);

		var year = date.getFullYear();

		var jan1dayOfWeek = this.getDayOfWeek(new Date(year, 0, 1));

		var offset = (jan1dayOfWeek < this.FirstWeekOfYearContains) ? 1 : 0;

		var dayNumber = this.getDayOfYear(date);
		var weekOfYear = offset + Math.floor((dayNumber-1+jan1dayOfWeek)/7);

		// Fällt der Tag vielleicht schon ins nächste Jahr?
		if (!this.SplitWeeksAcrossYears && date.getMonth() == 11  && date.getDate() >= 24 + this.FirstWeekOfYearContains + dayOfWeek) {
			return [year+1, 1, dayOfWeek];
		}

		// ...oder ins vorherige Jahr? (Dann gilt es zu unterscheiden, ob das nun die 52. oder 53. Woche ist)
		if (weekOfYear == 0) {
			var lookForDoW = 7 - this.FirstWeekOfYearContains;
			if (this.getDayOfWeek(new Date(year-1, 0, 1)) == lookForDoW || this.getDayOfWeek(new Date(year-1, 11, 31)) == lookForDoW) {
				return [year-1, 53, dayOfWeek];
			} else {
				return [year-1, 52, dayOfWeek];
			}
		}

		return [year, weekOfYear, dayOfWeek];
	},

	getLengthOfMonth : function(month, year) {
		if(month==0 || month==2 || month==4 || month==6
			 || month==7 || month==9 || month==11){
			return 31;
		} else if(month==3 || month==5 || month==8 || month==10){
			return 30;
		} else if (month==1) {
			return 28 + (this.isLeapYear(year) ? 1 : 0);
		} else {
			throw new Error("Unknown month " + month);
		}
	},

	/* gibt eine Zahl zwischen 1 und 365 (bzw. 366 in Schaltjahren) mit der Nummer des Tages im laufenden Jahr zurueck */
	getDayOfYear : function(date) {
		var year = parseInt(date.getFullYear());
		var month = parseInt(date.getMonth());
		var day = parseInt(date.getDate());

		var number = 0;
		var monthNumber = 0;
		while(monthNumber<month){
			number += this.getLengthOfMonth(monthNumber, year);
			monthNumber++;
		}

		number += day;

		return number;
	},

	/**
	 *	Rendert den Kalender unter einem Element. Damit das funktioniert, müssen folgende Dinge 
	 *	gegeben sein:
	 *	* "position" des Elementes muss auf "relative" oder "absolute" stehen
	 *	* das Anhängen von Elementen darf nicht stören
	 */
	render : function(el, date, callback, includeTime, minYear, maxYear, options) {
		this.hide();
		this.__render(date, callback, includeTime, minYear, maxYear, options);

		var offset = this.getAbsoluteGeometry(el, document.body);
		var x = offset.left;
		var y = offset.top+offset.height;
		
		this.cDiv.style.left = x;
		this.cDiv.style.top = y;
		
		this.cDiv.style.zIndex = "100";
		this.cDiv.className = "CHAMAELEON_CALENDAR";
		if (this.IE6) {
			var l = document.getElementsByTagName("select");
			for (var i = 0; i < l.length; i++) {
				l[i].style.visibility = "hidden";
			} 
		}
		el.appendChild(this.cDiv);
	},
	
	IE6 : false /*@cc_on || @_jscript_version < 5.7 @*/,
	
	hide: function() {
		if (this.isVisible()) {
			if (this.__oldPosition != null) {
				this.cDiv.parentNode.style.position = this.__oldPosition;
				this.__oldPosition = null;
			} 
			this.cDiv.parentNode.removeChild(this.cDiv);
			this.cDiv = null;
			if (this.IE6) {
				var l = document.getElementsByTagName("select");
				for (var i = 0; i < l.length; i++) {
					l[i].style.visibility = "";
				} 
			}
		}
	},
	
	isVisible: function() {
		return this.cDiv != null && this.cDiv.parentNode != null;
	},

	// "verschiebt" ein Datum um die angegebene Anzahl Tage, Vorsicht: das kann bei größeren Differenzen schonmal was dauern
	addToDate : function(date, diff) {
		var day = date.getDate() + diff;
		var month = date.getMonth();
		var year = date.getFullYear();
		while (day < 0) {
			month = (month + 11) % 12;
			if (month == 11) {
				year--;
			}
			day += this.getLengthOfMonth(month, year);
		}
		while (day > 28 && day > this.getLengthOfMonth(month, year)) {
			day -= this.getLengthOfMonth(month, year);
			month = (month + 1) % 12;
			if (month == 0) {
				year++;
			}
		}
		return new Date(year, month, day);
	},

	renderMonth : function(tableData) {
		var kwList = tableData.kwList;
		var dayList = tableData.dayList;

		var month = tableData.month.value;
		var year = tableData.year.value;

		var date = new Date(year, month, 1);
		var diff = this.getDayOfWeek(date);

		var selectedDate = tableData.selectedDate;

		// Datum verschieben, um leere Plätze zu füllen...
		var noFillBefore = month == 0 && this.SplitWeeksAcrossYears;
		var noFillAfter = month == 11 && this.SplitWeeksAcrossYears;


		// Dies sind die Offsets innerhalb der 42 Tage umfassenden Ansicht,
		// die auch wirklich dargestellt werden (für bessere Unterstützung von SplitWeeksAcrossYears).


		var firstDayIndex = noFillBefore ? diff : 0;
		var lastDayIndex = noFillAfter ? diff + this.getLengthOfMonth(month, year) : 42;

		date = this.addToDate(date, -diff);

		for (var y = 0; y < 6; y++) {
			kwList[y].nodeValue = "";
			for (var x = 0; x < 7; x++) {
				var i = x + y*7;

				if (i < firstDayIndex || i >= lastDayIndex) {
					dayList[y][x].nodeValue = "";
				} else {
					var wOY = this.getWeekOfYear(date);
					kwList[y].parentNode.style.color = (wOY[0] == year) ? "black" : "#C0C0C0";
					kwList[y].nodeValue = wOY[1];
					dayList[y][x].parentNode.newDate = date;
					
					var off = date.getMonth() == month ? "In" : "Off";
					var sel = (date.getDate() == selectedDate.getDate() && date.getMonth() == selectedDate.getMonth() && date.getFullYear() == selectedDate.getFullYear()) ? "Sel" : "";
					var style = this.getStyle("dayCell" + off + sel);
					for (var i in style) {
						if (style[i] !== Object.prototype[i]) {
							dayList[y][x].parentNode.style[i] = style[i];
						}
					}
					
					dayList[y][x].nodeValue = date.getDate();
					if (wOY[2] != x) {
						throw new Error("Assertion failure: " + wOY + "[2] != " + x + " (year=" + year + ", month=" + month + ", date=" + date + ")");
					}

				}
				date = this.addToDate(date, 1);
			}
		}
	},
	
	dateToUTCString : function (date, includeTime) {
		var s = this.dateToString(date, includeTime);
		if (includeTime) {
			s = s.replace(/ /, 'T') + '.000';
			var tz = (date.getTimezoneOffset() / 60);
			var tzBetrag = -1 * tz;
			s = s + (tz < 0 ? '+' : '-');
			s = s + (tz.length > 1 ? tzBetrag : '0' + tzBetrag);
			s = s + '00';
		}
		return s;
	},

	intToString: function(i, l) {
		var r = Math.pow(10,l);
		return i < r
			? String(i + r).substring(1)
			: String(i);
	},
	
	dateToString : function(date, includeTime) {
		var format = includeTime ? this.langTable["datetime"] : this.langTable["date"];
		return this.sprintf(format, date);
	},

	dateFromString : function(dateStr) {
		for (var i = 0; i < 2; i++) {
			var s = this.langTable[i ? "date" : "datetime"];
			var parts = [];
			s = s.replace(/(\W)/g, "\\$1").replace(/\\%1\\\$t(\w)/g, function(all, type){ parts.push(type); return '(\\d{' + (type=='Y' ? 4 : 2) + '})'});
			var re = new RegExp(s);
			
			if (dateStr.match(re)) {
				var d = new Date(0);
				for (var i = 0; i < parts.length; i++) {
					var v = RegExp["$"+(i+1)];
					switch(parts[i]) {
						case 'Y': d.setFullYear(v*1); break;
						case 'm': d.setMonth(v*1-1); break;
						case 'd': d.setDate(v*1); break;
						case 'H': d.setHours(v*1); break;
						case 'M': d.setMinutes(v*1); break;
						case 'S': d.setSeconds(v*1); break;
						default: throw new Error("Unsupported date component " + parts[i]);
					}
				}
				return d;
			}
		}
		return null;
	},

	/*
		Legt das Design fest.
		cssOverride ist ein Hash, dessen Schlüssel denjenigen von dem internen css-Hash des Kalenders entsprechen, die überladen werden sollen.
		Standardmäßig wird der Original-Stil vollständig ersetzt, durch Angabe von "merge:true" kann aber auch eine Vereinigung erzwungen werden,
		so dass alle nicht explizit überladenen CSS-Eigenschaften vom Original in dem jeweiligen Stil erhalten bleiben.
	*/
	setDesign : function(cssOverride) {
		this.cssOverride = cssOverride;
		
		for (var i in cssOverride) {
			if (cssOverride[i] !== Object.prototype[i]) {
				if (cssOverride[i].merge && this.css[i]) {
					for (var j in this.css[i]) {
						if (this.css[i][j] !== Object.prototype[j] && !(j in cssOverride[i])) {
							cssOverride[i][j] = this.css[i][j];
						}
					}
					delete cssOverride[i].merge;
				}
			}
		}
	},
	
	css : {
		dialog0: {position: 'absolute', background: 'white', width: '182px', height: 120 + 0  + 'px' , border: '1px solid #808080', padding: '0px'},
		dialog1: {position: 'absolute', background: 'white', width: '182px', height: 120 + 16 + 'px' , border: '1px solid #808080', padding: '0px'},
		dialog0n:{position: 'absolute', background: 'white', width: '182px', height: 120 + 20 + 'px' , border: '1px solid #808080', padding: '0px'},
		dialog1n:{position: 'absolute', background: 'white', width: '182px', height: 120 + 36 + 'px' , border: '1px solid #808080', padding: '0px'},
		
		weekCell: {padding: "0px", fontWeight: "bold", background: "#DBDBDB", textAlign: "center", fontSize: "8pt"},
		
		dayCell: {cursor: "pointer", padding: "0px", textAlign: "center", fontSize: "8pt"},
		
		mainTable: {borderCollapse: "collapse", tableLayout: "fixed", font: "8pt tahoma,sans-serif", width: "182px"},
		
		headerCell:  {padding: "0px"},
		
		headerTable: {borderCollapse: "collapse", font: "8pt tahoma,sans-serif"},
		
		// die leere Zelle im Kreuzungspunkt von Tagen und Wochen
		emptyCell: {padding: "0px", fontWeight: "bold", textAlign: "center"},
		
		weekdayCell: {padding: "0px", fontWeight: "bold", background: "#DBDBDB", textAlign: "center", fontSize: "8pt"},
		
		dateSelect: {fontSize: "8pt"},
		
		timeSelect: {fontSize: "8pt"},
		
		dayCellIn: { color: "black", backgroundColor: "white" },
		dayCellOff: { color: "#C0C0C0", backgroundColor: "white" },
		dayCellInSel: { color: "black", backgroundColor: "#808080" },
		dayCellOffSel: { color: "#C0C0C0", backgroundColor: "#808080" },
		
		timeCell: {paddingTop: "2px", textAlign: "center", whiteSpace: "nowrap"},
		additionalCell: {textAlign: "center", whiteSpace: "nowrap"}
	},
	
	cssOverride: {},
	
	getStyle : function(name) {
		return this.cssOverride[name] || this.css[name];
	},
	
    __render: function(date, callback, includeTime, minYear, maxYear, options) {
		var that = this;
		options = options || {};
		var isNull = (date == null);
		if (isNull) date = new Date();

		if(!minYear){
			minYear = date.getFullYear() - 40;
		} else if(date && date.getFullYear() < minYear){
			minYear = date.getFullYear();
		}
		if(!maxYear){
			maxYear = date.getFullYear() + 25;
		} else if(date && maxYear < date.getFullYear()){
			maxYear = date.getFullYear();
		}
		date = date || new Date();

		var dlgStyle = this.getStyle('dialog' + (includeTime ? '1' : '0') + (options.nullValue ? 'n' : ''));

		var tableData = {selectedDate: date};

		var dateRows = [];
		
		var nullId = options.nullValue ? "id" + String(Math.random()).replace(/\D/g, "") : null;
		

		// 6 Zeilen, da sich jeder Monat auf maximal 6 Wochen verteilen kann (Verteilung: 1%: 4 Wochen, 78%: 5 Wochen, 21%: 6 Wochen)
		for (var y = 0; y < 6; y++) {
			dateRows[y] = ["tr", ["td", ["#r"+y], {style: this.getStyle("weekCell")}]];
			for (var x = 0; x < 7; x++) {
				dateRows[y].push(["td", [("#c" + y) + x], {style: this.getStyle("dayCell"), onclick: function(){
					tableData.selectedDate = this.newDate;
					if (includeTime) {
						tableData.selectedDate.setHours(tableData.time0 ? tableData.time0.value : 0);
						tableData.selectedDate.setMinutes(tableData.time1 ? tableData.time1.value : 0);
						tableData.selectedDate.setSeconds(tableData.time2 ? tableData.time2.value : 0);
					}
					that.renderMonth(tableData);
					if (nullId) {
						document.getElementById(nullId).checked = false;
					}
					if (callback) {
						callback.call(that, tableData.selectedDate);
					}
				}}]);
			}
		}
		
		var colCount = this.getStyle("weekCell").display == "none" ? 7 : 8;

		var timeRow = null;
		if (includeTime) {
			var granularity = options.timeGranularity || 1;
			
			var timeCell = ["td", {colspan: colCount, style: this.getStyle("timeCell")}, this.langTable["time"]+": "];
			
			for (var i = 0; i < 3; i++) {
				var factor = [3600, 60, 1][i];
				var range = [24, 60, 60][i];
				var name = ["Hours", "Minutes", "Seconds"][i];
				
				if (granularity >= range * factor) {
					date["set" + name](0);
					continue;
				}
				
				if (granularity >= factor && (granularity % factor != 0 || range % (granularity / factor) != 0)) {
					throw new Error(granularity + " is a senseless value for timeGranularity");
				}
				
				var localGranularity = granularity < factor ? 1 : granularity / factor;
				
				var onchange = new Function(
					"arguments.callee.tableData.selectedDate.set" + name + "(this.value);" +
					(nullId ? "document.getElementById('" + nullId + "').checked = false;" : "") +
					"if(arguments.callee.callback) arguments.callee.callback.call(arguments.callee.that, arguments.callee.tableData.selectedDate);"
				);
				onchange.tableData = tableData;
				onchange.that = this;
				onchange.callback = callback;
				var value = date["get" + name]();

				var box = ["select#time"+i, {value: value - value % localGranularity, onchange: onchange, style: this.getStyle("timeSelect")}];
				for (var j = 0; j < range; j += localGranularity) {
					box.push(["option", {value: j}, (j < 10 ? "0" : "") + j]);
				}
				if (i != 0) {
					timeCell.push(":");
				}
				timeCell.push(box);
			}

			timeRow = ["tr", timeCell];
		}
		
		var additionalRow = null;
		if (options.additionalElement) {
			additionalRow = ["tr", ["td#additionalCell", {colspan: colCount, style: this.getStyle("additionalCell")}]];
		}
		
		var nullRow = null;
		if (options.nullValue) {
			var onchange = new Function(
				"if(arguments.callee.callback) arguments.callee.callback.call(arguments.callee.that, this.checked ? null : arguments.callee.tableData.selectedDate);"
			);
			onchange.that = this;
			onchange.callback = callback;
			onchange.tableData = tableData;

			nullRow = ["tr", ["td#additionalCell",
				{colspan: colCount, style: this.getStyle("additionalCell")},
				["input#nullValue", {type: "checkbox", id: nullId, checked: isNull ? "checked" : null, onchange: onchange}],
				["label", {'for': nullId}, options.nullValue]
			]];
		}
		
		var monthDisplay, yearDisplay;
		
		if (options.noMonthYearSelect) {
			monthDisplay = ["td", ["select#month", {onchange: function(){ that.renderMonth(tableData); }, style: {display: 'none'}}],["div#monthName", this.getMonthName(date.getMonth())]];
			yearDisplay  = ["td", ["select#year",  {onchange: function(){ that.renderMonth(tableData); }, style: {display: 'none'}}],["div#yearName", date.getFullYear()]];
		} else {
			monthDisplay = ["td", ["select#month", {onchange: function(){ that.renderMonth(tableData); }, style: this.getStyle('dateSelect')}],["div#monthName", this.getMonthName(date.getMonth()), {style: {display: 'none'}}]];
			yearDisplay  = ["td", ["select#year",  {onchange: function(){ that.renderMonth(tableData); }, style: this.getStyle('dateSelect')}],["div#yearName", date.getFullYear(), {style: {display: 'none'}}]];
		}
		
		var table = this.createElement(
			["div", {style: dlgStyle},
				["table", {style: this.getStyle("mainTable")},
					["col", {span: colCount, width: (100/colCount) + "%"}],
					["tbody",
						["tr",
							["td", {colspan: colCount}, {style: this.getStyle("headerCell")},
								["table", {style: this.getStyle("headerTable")}, {width: "100%"}, ["tbody",["tr",
									["td", "<", {onclick: function(){
										if (tableData.month.value == 0) {
											tableData.month.value = 11;
											var newYear = parseInt(tableData.year.value) - 1;
											tableData.year.value = newYear;
											if (tableData.year.value != newYear) {
												var opt = tableData.year.ownerDocument.createElement("option");
												opt.value = newYear;
												opt.innerHTML = newYear;
												tableData.year.insertBefore(opt, tableData.year.firstChild);
												tableData.year.value = newYear;
											}
										} else {
											tableData.month.value = parseInt(tableData.month.value) - 1;
										}
										tableData.monthName.innerHTML = that.getMonthName(tableData.month.value);
										tableData.yearName.innerHTML = tableData.year.value;
										that.renderMonth(tableData);
									}, style: {cursor: "pointer"}}],
									monthDisplay,
									yearDisplay,
									["td", ">", {onclick: function(){
										if (tableData.month.value == 11) {
											tableData.month.value = 0;
											var newYear = parseInt(tableData.year.value) + 1;
											tableData.year.value = newYear;
											if (tableData.year.value != newYear) {
												var opt = tableData.year.ownerDocument.createElement("option");
												opt.value = newYear;
												opt.innerHTML = newYear;
												tableData.year.appendChild(opt);
												tableData.year.value = newYear;
											}
										} else {
											tableData.month.value = parseInt(tableData.month.value) + 1;
										}
										tableData.monthName.innerHTML = that.getMonthName(tableData.month.value);
										tableData.yearName.innerHTML = tableData.year.value;
										that.renderMonth(tableData);
									}, style: {cursor: "pointer"}}]
								]]]
							]
						],
						["tr#days",
							["td","", {style: this.getStyle("emptyCell")}]
						],
						dateRows[0],
						dateRows[1],
						dateRows[2],
						dateRows[3],
						dateRows[4],
						dateRows[5],
						timeRow,
						additionalRow,
						nullRow
					]
				]
			], tableData
		);

		if (options.additionalElement) {
			tableData.additionalCell.appendChild(options.additionalElement);
		}
		
		for (var x = 0; x < 7; x++) {
			tableData.days.appendChild(this.createElement(["td", {style: this.getStyle("weekdayCell")}, this.getDayOfWeekAbbr(x)]));
		}

		for (var m = 0; m < 12; m++) {
			tableData.month.appendChild(this.createElement(["option", {value: m, style: {fontSize: "8pt"}}, this.getMonthName(m)]));
		}
		for (var y = minYear; y <= maxYear; y++) {
			tableData.year.appendChild(this.createElement(["option", {value: y, style: {fontSize: "8pt"}}, y]));
		}

		tableData.month.value = date.getMonth();
		tableData.year.value = date.getFullYear();


		var kwList = [];
		var dayList = [];
		for (var y = 0; y < 6; y++) {
			kwList[y] = tableData["r"+y];
			dayList[y] = [];
			for (var x = 0; x < 7; x++) {
				dayList[y][x] = tableData[("c"+y)+x];
			}
		}

		tableData.kwList = kwList;
		tableData.dayList = dayList;

		this.renderMonth(tableData);
		
		this.cDiv = table;
	},
	
	createElement : function(def, returnElements) {
		/********************************************************************************
		/* @TITLE
		/*	HTML-Elemente erzeugen
		/*
		/* @DESCRIPTION
		/*	Erzeugt HTML-Elemente. Rückgabe ist das äußerste der erzeugten Elemente.
		/*
		/* @PARAM def
		/*	Defintion der Elemente
		/*	Syntax:
		/*		* Array:
		/*			* erster Eintrag:
		/*				* Elementname
		/*				* Elementname mit per "#" angehängter ID
		/*				* Elementname = Leerstring: Textknoten (alternative Syntax, mit der eine ID mitgegeben werden kann)
		/*			* weitere Einträge:
		/*				* Hash (Attributdefinitionen)
		/*					* möglich sind auch direkte Verwendung von Funktionen
		/*					* style muss als Hash deklariert werden
		/*				* Kindelemente (Arrays / Strings)
		/*		* String: ergibt einen Textknoten
		/*
		/* @PARAM returnElements
		/*	(optional) ein Hash, dem erzeugte Elemente hinzugefügt werden
		/********************************************************************************/
		
		if (def instanceof Array) {
			var name = def[0];
			var id = null;
			var i;
			if ((i = name.indexOf("#")) != -1) {
				id = name.substring(i+1);
				name = name.substring(0,i);
			}
			if (name == "") {
				if (def.length > 2) {
					throw new Error("Array too long for text node");
				}
			}
			var el = name == "" ? document.createTextNode(def[1] != null ? def[1] : "")
				: (document.documentElement.namespaceURI ? document.createElementNS(document.documentElement.namespaceURI, name) : document.createElement(name));
			if (id != null) {
				returnElements[id] = el;
			}
			if (name == "") {
				return el;
			}
			// Bei Select-Boxen muss der Wert NACH Erzeugung der Optionen zugewiesen werden, sonst klappt's nicht
			var value = null;
			for (var j = 1; j < def.length; j++) {
				if (def[j] instanceof Array || typeof(def[j]) != "object") {
					var childEl = this.createElement(def[j], returnElements);
					if (!(childEl instanceof Array)) {
						childEl = [childEl];
					}
					for (var i = 0; i < childEl.length; i++) {
						if (name == "") {
							el.push(childEl[i]);
						} else {
							el.appendChild(childEl[i]);
						}
					}
				} else {
					if (name == "") {
						throw new Error("Attributes are not permitted for groups");
					}
					// Attribut-Hash
					for (var attrName in def[j]) {
						var attrVal = def[j][attrName];
						if (attrVal == null) continue;
						if (name == "select" && attrName == "value") {
							value = attrVal;
						}
						if (!(attrName in Object.prototype) || attrVal != Object.prototype[attrName]) {
							if (attrName == "style") {
								if (typeof(attrVal) == "string") {
									el.style.cssText = attrVal;
								} else {
									for (var l in attrVal) {
										if (!(l in Object.prototype) || attrVal[l] != Object.prototype[l]) {
											if (el.style == null) {
												alert("el: " + el.nodeName + "\nl: " + l + "\nstyle: " + attrVal[l]);
											}
											el.style[l] = attrVal[l];
										}
									}
								}
							} else if (attrVal instanceof Function) {
								el[attrName] = attrVal;
							} else if (document.all && (attrName == "colspan" || attrName == "rowspan")) {
								el[attrName.substring(0,3) + "Span"] = attrVal;
							} else {
								el.setAttribute(attrName, attrVal);
							}
						}
					}
				}
			}
			if (value != null) {
				el.value = value;
			}
			return el;
		} else {
			return document.createTextNode(def);
		}
	},

	zerofill : function(n, digits) {
		var s = (n / Math.pow(10, digits-1)).toFixed(digits-1).replace(/\./, "");
		return s;
	},
	
	getAbsoluteGeometry : function (obj, relObj){
		if (!obj) {
			return { left: 0, top: 0, width: 0, height: 0 };
		}
		
		var geometry = {
			left : obj.offsetLeft,
			top : obj.offsetTop,
			height: obj.offsetHeight,
			width: obj.offsetWidth
		};

		obj = obj.offsetParent;
		
		//var s = "(" + obj.nodeName + ") left:" + geometry.left + " top:" + geometry.top + "\n";
		
		while (obj != null) {
			if (obj.currentStyle) {
				var y = obj.currentStyle['position'];
			} else if (window.getComputedStyle) {
				var y = document.defaultView.getComputedStyle(obj,null).getPropertyValue('position');
			}
			if (y != 'relative') {
				//s += "+(" + obj.nodeName + ") left:" + (obj.offsetLeft - (obj.scrollLeft || 0)) + " top:" + (obj.offsetTop - (obj.scrollTop || 0)) + "\n";
				geometry.left += obj.offsetLeft - (obj.scrollLeft || 0);
				geometry.top  += obj.offsetTop - (obj.scrollTop || 0);
				if (obj.nodeName == "BODY") {
					//s += "-(" + obj.parentNode.scrollLeft + "," + obj.parentNode.scrollTop + ")\n";
					geometry.left -= obj.parentNode.scrollLeft;
					geometry.top -= obj.parentNode.scrollTop;
				}
			}
			obj = obj.offsetParent;
		}
		
		//alert(s);
		if (relObj) {
			if (relObj.nodeName == "BODY") {
				geometry.left += relObj.scrollLeft + relObj.parentNode.scrollLeft;
				geometry.top += relObj.scrollTop + relObj.parentNode.scrollTop;
			} else {
				var othergeom = this.getAbsoluteGeometry(relObj);
				geometry.left -= othergeom.left - relObj.scrollLeft;
				geometry.top -= othergeom.left - relObj.scrollTop;
			}
		}
		
		return geometry;
	},
	
	sprintf : function() {
	 		// Siehe hierzu auch: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
			if (!arguments || arguments.length < 1 || !RegExp) {
				return;
			}
			var str = arguments[0];
			var re = /([^%]*)%(?:(\d+)\$)?('.|0|\x20)?(-)?(\d+|\*)?(?:\.(\d+|\*))?(%|b|c|d|u|f|o|s|[tT][HMSYmdy]|x|X)(.*)/; 	//' Kommentar nur damit der JDeveloper nicht spinnt
			var result = "";
			var argIndex = 0;
			var a;
			while (a = re.exec(str)) {
				var leftpart = a[1], pArgIndex = a[2], pPad = a[3], pJustify = a[4], pMinLength = a[5];
				var pPrecision = a[6], pType = a[7], rightPart = a[8];
				
				if (pType == '%') {
					subst = '%';
				} else {
					argIndex = pArgIndex ? parseInt(pArgIndex) : argIndex+1;	// Verhalten bei Mischformen ist nicht definiert
					if (argIndex >= arguments.length) {
						throw new Error(this.sprintf('sprintf: Argument at position %d does not exist, only %d arguments given in %s', argIndex, arguments.length - 1, arguments[0]));
					}
					var param = arguments[argIndex];
					var pad = !pPad ? ' ' : (pPad.length == 1 ? pPad : pPad.substr(1));
					var justifyRight = pJustify != "-";
					var minLength = pMinLength ? parseInt(pMinLength) : -1;
					var precision = (pPrecision && pType == 'f') ? parseInt(pPrecision) : -1;
					
					var subst = param;
					switch (pType.charAt(0)) {
						case 'b':
							subst = parseInt(param).toString(2);
							break;
						case 'c':
							subst = String.fromCharCode(parseInt(param));
							break;
						case 'd':
							subst = parseInt(param) ? parseInt(param) : 0;
							break;
						case 'u':
							subst = Math.abs(param);
							break;
						case 'f':
							subst = (precision > -1) ? Math.round(parseFloat(param) * Math.pow(10, precision)) / Math.pow(10, precision): parseFloat(param);
							break;
						case 'o':
							subst = parseInt(param).toString(8);
							break;
						case 's':
							subst = param;
							break;
						case 'x':
							subst = ('' + parseInt(param).toString(16)).toLowerCase();
							break;
						case 'X':
							subst = ('' + parseInt(param).toString(16)).toUpperCase();
							break;
						case 't':
						case 'T':
							var d = new Date(param);
							switch (pType.charAt(1)) {
								case 'S': 
									subst = this.zerofill(d.getSeconds(), 2); break;
								case 'M':
									subst = this.zerofill(d.getMinutes(), 2); break;
								case 'H':
									subst = this.zerofill(d.getHours(), 2); break;
								case 'Y':
									subst = d.getFullYear(); break;
								case 'm':
									subst = this.zerofill(d.getMonth() + 1, 2); break;
								case 'd':
									subst = this.zerofill(d.getDate(), 2); break;
								case 'y':
									subst = this.zerofill(d.getYear(), 2); break;
								default: 
									throw new Error("Unknown type " + pType + " in " + arguments[0]);
									
							}
							break;							
						default:
							throw new Error("Unknown type " + pType + " in " + arguments[0]);
					}
					
					subst = String(subst);
					while (subst.length < minLength) {
						if (justifyRight) {
							subst = pad + subst;
						} else {
							subst = subst + pad;
						} 
					}
				}
				result += leftpart + subst;
				str = rightPart;
			}
			return result + str;
		}	
};

