Difference between revisions of "MediaWiki:JKeyEditor.js"

From Species-ID
Jump to: navigation, search
Line 286: Line 286:
  
 
function jedtParsedDataCoupletMemory_CreateItemEntry(itemID, newItemID, uniqueKeyID, coupletID, createNewCoupletInMemory) {
 
function jedtParsedDataCoupletMemory_CreateItemEntry(itemID, newItemID, uniqueKeyID, coupletID, createNewCoupletInMemory) {
var newItemData = {wasOrigin: false, wasEdited: true, originItemID: jedtParsedDataCoupletMemory_ExtractOriginItemID(itemID)};
+
var newItemData = {wasOrigin: false, wasEdited: true};
 
if (createNewCoupletInMemory) {
 
if (createNewCoupletInMemory) {
 
jedtParsedPageData[uniqueKeyID].coupletMemory[coupletID] = {}; // clean
 
jedtParsedPageData[uniqueKeyID].coupletMemory[coupletID] = {}; // clean
Line 867: Line 867:
 
if (coupletMemory[coupletID][itemID]) { // filter unwanted properties from the prototype
 
if (coupletMemory[coupletID][itemID]) { // filter unwanted properties from the prototype
 
itemData = coupletMemory[coupletID][itemID];
 
itemData = coupletMemory[coupletID][itemID];
parsedDataStructure = (itemData.wasOrigin) ? jedtParsedPageData[itemID] : jedtParsedPageData[itemData.originItemID];
+
parsedDataStructure = (itemData.wasOrigin) ? jedtParsedPageData[itemID] : jedtParsedPageData[jedtParsedDataCoupletMemory_ExtractOriginItemID(itemID)];
 
if (itemData.wasEdited) {
 
if (itemData.wasEdited) {
 
// get values from inputs
 
// get values from inputs

Revision as of 11:09, 2 December 2009

/* Copyright (c) 2009, Stephan Opitz, JKI Berlin Dahlem
This program is free software; you can redistribute it and/or modify it under the terms of the EUPL v.1.1 or (at your option) the GNU General Public License as published by the Free Software Foundation; either GPL v.3 or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License (http://www.gnu.org/licenses/) for more details. */

/*global $j, wgContentLanguage, wgPageName, wgScript, wgScriptPath, wgServer, wgUserName, alert, console, document, Image, location, setTimeout, window, JKeyException, jkeyExceptionAlert  */

//// JKey editor global vars ////

"use strict";

var localTstMode = false;
var jedtI18n, jedtImgs; // resrc holders
var pastelColors = ["#FFFDD8", "#FFE9CC", "#FFE4D9", "#FFDCDF", "#FFDCF8", "#DFE1F6", "#D8E7FF", "#D8F5FF", "#D8FFE1", "#ECFFD8"];
var jkeyParser; // parser class
var jedtPageCharArray; // holds complete content (need for content between)
var jedtParsedPageData; // parsed tpl & child data
var jedtHtmlEditTpls = {}; // cache for editor tpls
var jedtEditFormVars = {}; // cache for saving

/*
 * Description: Get resource string (text, image URLs) for a given language, based on a string-key 
 *	If, for a key, resource is missing in a given language, the resource for "en" will be returned, if this is missing as well an error message.
 * resrcKey: key for the resource (string)
 */
function jedtInitResourceDict() {
	return {
		en: {
			edtInstantSave: "Instant save",
			edtSave: "Save",
			edtRightsFault: "No edit rights!",
			edtActionEditTitle: "Edit item(s)",
			edtActionRemoveTitle: "Remove item",
			edtActionMoveUpTitle: "Move item up",
			edtActionMoveDownTitle: "Move item down",
			edtActionAddNewTitle: "Add new item below",
			edtActionRemoveFault: "Two decision alternatives are minimum required!",
			edtCoupletDescription: "Couplet",
			edtSaving: "Please wait, saving ...",
			edtSavingFault: "Stopped saving, because no couplets were changed into to edit mode!",
			edtWpSummary: "Saved using JKey Editor",
			jkeyAjaxSuccessFault : "Response from server was empty!",
			edtSysExNfo1: "No coupletID can be extracted!",
			edtSysExNfo2: "No itemID found in coupletMemory!",
			edtParsExNfo1: "No defined ID was found. Check this!",
			edtParsExNfo2A: "Key before key with ID: ",
			edtParsExNfo2B: " was not finished by a 'Key End' template!",
			edtParsExNfo3: "Couplet definition is not parseable!",
			edtParsExReas1: "Empty field",
			edtParsExReas2: "Structure not valid!",
			edtParsExReas3: "No valid id found!",
			edtInitNfoEx: "Extraction of coupletID failed",
			edtTplExNfo1: "No jedtCoupletBacklink as input field found!",
			edtTplExNfo2: "No jedtCoupletAltID as input field found!",
			edtTplExNfo3: "No jedtParseLeadID as input field found!",
			edtNiExNfoA: "toggleMode: ",
			edtNiExNfoB: " is not available!"
		},
		de: {
			edtInstantSave: "Zwischenspeichern",
			edtSave: "Speichern",
			edtRightsFault: "Keine Rechte zum Bearbeiten!",
			edtActionEditTitle: "Bearbeite Entscheidungspaar(e)",
			edtActionRemoveTitle: "Lösche Element",
			edtActionMoveUpTitle: "Bewege Element nach oben",
			edtActionMoveDownTitle: "Bewege Element nach unten",
			edtActionAddNewTitle: "Füge neues Element darunter hinzu",
			edtActionRemoveFault: "Zwei Entscheidungsalternativen werden mindestens benötigt!",
			edtCoupletDescription: "Entscheidungspaar",
			edtSaving: "Bitte warten, es wird gespeichert ...",
			edtSavingFault: "Speichern gestoppt, da keine Entscheidungspaare in den Bearbeiten-Modus geändert wurden!",
			edtWpSummary: "Gepeichert durch den JKey Editor"
		}
	};
}

function jedtResource(resrcKey) {
	if (!jedtI18n) { 
		jedtI18n = jedtInitResourceDict();
	}
    var lang = wgContentLanguage.split("-")[0]; // language: "pt-BR", "de-formal", etc.
    return (jedtI18n[lang] && jedtI18n[lang][resrcKey] ? 
            jedtI18n[lang][resrcKey] : 
            (jedtI18n.en[resrcKey]) ? jedtI18n.en[resrcKey] : "MISSING RESOURCE");			
}

function jedtImgPreloader(resrcKey) {
	var imgUrls = {
		edtIconActionEdit: "http://upload.wikimedia.org/wikipedia/commons/thumb/8/8f/View-so_edit_simple.svg/25px-View-so_edit_simple.svg.png",
		edtIconActionRemove: "http://upload.wikimedia.org/wikipedia/commons/thumb/0/06/View-so_remove_simple.svg/20px-View-so_remove_simple.svg.png",
		edtIconActionMoveUp: "http://upload.wikimedia.org/wikipedia/commons/thumb/b/b5/View-so_move_up_simple.svg/20px-View-so_move_up_simple.svg.png",
		edtIconActionMoveDown: "http://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/View-so_move_down_simple.svg/20px-View-so_move_down_simple.svg.png",
		edtIconActionAddNew: "http://upload.wikimedia.org/wikipedia/commons/thumb/5/52/View-so_add_new_simple.svg/20px-View-so_add_new_simple.svg.png",
		edtIconLoading: "http://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif"
	}, key, newImg;
	jedtImgs = {}; // init holder
	for (key in imgUrls) {
		if (imgUrls[key]) { // filter unwanted properties from the prototype
			newImg = new Image();
			jedtImgs[key] = $j(newImg).attr({src: imgUrls[key]});
		}
	}
}

function jedtImgResource(resrcKey) {
	return jedtImgs[resrcKey].clone();
}

//// JKey common ////

function jkeyAjax(callingScript, parameters, typeGet, async, successFct, errorFct) {
	var respObj = $j.ajax({ // execute ajax call
		url: callingScript,
		data: parameters,
		type: (typeGet) ? "GET" : "POST",
		async: async,
		success: successFct,
		error: errorFct
	});
	if (!async) { // return result
		return (parameters.format && (parameters.format === "xml")) ? respObj.responseXML : respObj.responseText;
	}
}

function jkeyAjaxSuccess(content) {
	if (!content) {
		throw (new JKeyException("ConnectionException", {info: jedtResource("jkeyAjaxSuccessFault")}));
	}
}

function jkeyAjaxError(xhr, ajaxOptions, thrownError) {
	throw (new JKeyException("ConnectionException", {status: xhr.status, statusText: xhr.statusText, thrownError: thrownError}));
}

function jkeyCoupletAsCollection(firstDtRow) {
	var startIdx, endIdx, sibblingRow, gtFilter, ltFilter,
		parentTableRows = $j(firstDtRow.closest("table").get(0).rows);
	// starIdx
	startIdx = parentTableRows.index(firstDtRow.prevAll("tr:not(.dt-hspacer,.dt-subheading):first").next("tr"));
	// endIdx
	sibblingRow = firstDtRow;
	while (sibblingRow.length) {
		sibblingRow = sibblingRow.next("tr");
		if (sibblingRow.find("td.actionCell:first").length) { // rowHasActionCell
			sibblingRow = sibblingRow.prevAll("tr:not(.dt-hspacer,.dt-subheading):first"); // end of couplet
			break;
		}
	}
	endIdx = parentTableRows.index(sibblingRow.get(0));
	// filter
	gtFilter = (startIdx > 0) ? ":gt(" + (startIdx - 1) + ")" : "";
	ltFilter = (endIdx !== -1) ? (":lt(" + (endIdx - ((startIdx === -1) ? 0 : startIdx) + 1) + ")") : ""; // lt with gt means here count/number of next
	return parentTableRows.filter("tr" + gtFilter + ltFilter);
}

function jkeyExtractCombinedCoupletLeadID(combinedCoupletLeadID) {
	var retData = {coupletID: "", alt: "", backlink: ""},
		regExpResult = /^(\d+)(.*)/.exec(combinedCoupletLeadID); // extract "coupletID" & "alt" - .* because of special chars
	if (regExpResult) {
		retData.coupletID = regExpResult[1];
		retData.alt = $j.trim(regExpResult[2]);
		// extract "backlink", which can be in "alt"
		regExpResult = /.*\((\d+)\)/.exec(retData.alt); // - .* because of special chars
		if (regExpResult) {
			retData.backlink = $j.trim(regExpResult[1]);
			retData.alt = $j.trim(retData.alt.replace("(" + retData.backlink + ")", ""));
		}
	}
	else {
		throw (new JKeyException("SystemException", {info: jedtResource("edtSysExNfo1")}));
	}
	return retData;
}

function jkeyScrollToElement(elem) {
	function getElemOffset(elem) {
		var left = !!elem.offsetLeft ? elem.offsetLeft : 0,
			top = !!elem.offsetTop ? elem.offsetTop : 0;
		while ((elem = elem.offsetParent)) {
			left += !!elem.offsetLeft ? elem.offsetLeft : 0;
			top += !!elem.offsetTop ? elem.offsetTop : 0;
		}
		return {x: left, y: top};
	}
	var pos = getElemOffset(elem);
	window.scrollTo(pos.x, pos.y);
}

//// jedtBase ////

function jedtBase_CountKeys(base) {
	// some objs have problems with length (imitate it)
	var counter = 0, key;
	for (key in base) {
		if (base[key]) { // filter unwanted properties from the prototype
			counter += 1;
		}
	}
	return counter;
}

function jedtBase_CreateNewAfter(base, targetKey, newInsertKey, newInsertValue) {
	var baseCounter = 0, siblingOriginPos = 0, // parameters for wrong tpl definition case
		newBase = {}, afterOriginAction = false, // reorganize parsed data
		key;
	// iterate to find correct position & insert it there
	for (key in base) {
		if (base[key]) { // filter unwanted properties from the prototype
			baseCounter += 1;
			// handle insert process of new sibling
			if (afterOriginAction) {
				newBase[newInsertKey] = newInsertValue;
				afterOriginAction = false; // no more needed
			}
			// default insert itself
			if (!newBase[key]) { // add if not exists (no overwriting of afterOriginAction item can happen)
				newBase[key] = base[key];
			}
			// check current if already was target parent
			if (key === targetKey) {
				afterOriginAction = true;
				siblingOriginPos = baseCounter;
			}
		}
	}
	// handle special case - targetKey was last key in base (its like an simple appending)
	if (siblingOriginPos === baseCounter) {
		newBase[newInsertKey] = newInsertValue;
	}
	// return new
	return newBase;
}

function jedtBase_MoveKeyOnceUpOrDown(base, targetKey, moveUpOrDown) {
	// a key within an object is moved
	var keysCounter = 0, keyPosition = 0, // 0 is also a position
		newKeysCounter = 0, wasAdded = false, newBase = {},
		key;
	for (key in base) {
		if (base[key]) { // filter unwanted properties from the prototype
			if (key === targetKey) {
				keyPosition = keysCounter;
			}
			keysCounter += 1;
		}
	}
	// special cases: already an edge region position
	if (moveUpOrDown && (keyPosition === 0)) { // move up, but already first pos
		return base;
	}
	if (!moveUpOrDown && (keyPosition === (keysCounter - 1))) { // move down, but already last pos
		return base;
	}
	// perform move
	for (key in base) {
		if (base[key]) { // filter unwanted properties from the prototype
			// add specific key
			if (newKeysCounter === (moveUpOrDown ? (keyPosition - 1) : (keyPosition + 1))) {
				wasAdded = true;
				newBase[targetKey] = base[targetKey];
			}
			// add all other
			if (key !== targetKey) {
				newKeysCounter += 1;
				newBase[key] = base[key];
			}
		}
	}
	// special case - moved to last item
	if (!wasAdded) {
		newBase[targetKey] = base[targetKey];
	}
	return newBase;
}

function jedtBase_RemoveKey(base, key) {
	try {
		delete base[key];
	} catch (err) {}
}

//// jedtParsedDataCoupletMemory ////

function jedtParsedDataCoupletMemory_ExtractOriginItemID(itemID) {
	var regExpResult = /(\d*)n(\d+)/.exec(itemID); // check if originItemID is already a combined id
	return !regExpResult ? itemID : regExpResult[1];
}

function jedtParsedDataCoupletMemory_CreateItemEntry(itemID, newItemID, uniqueKeyID, coupletID, createNewCoupletInMemory) {
	var newItemData = {wasOrigin: false, wasEdited: true};
	if (createNewCoupletInMemory) {
		jedtParsedPageData[uniqueKeyID].coupletMemory[coupletID] = {}; // clean
		jedtParsedPageData[uniqueKeyID].coupletMemory[coupletID][newItemID] = newItemData;
	}
	else {
		jedtParsedPageData[uniqueKeyID].coupletMemory[coupletID] = jedtBase_CreateNewAfter(jedtParsedPageData[uniqueKeyID].coupletMemory[coupletID], itemID, newItemID, newItemData);
	}
}

function jedtParsedDataCoupletMemory_CalculateNewItemID(uniqueKeyID, originID) {
	var regExpResult, regExpSearch = /(\d*)n(\d+)/,
		basePartID = originID, sibblingPartID = 1,
		coupletMemory, newSibblingPartID, coupletID, itemID;
	// check if originID is already a combined id & extract parts
	regExpResult = regExpSearch.exec(originID);
	if (regExpResult) {
		basePartID = parseInt(regExpResult[1], 10);
		sibblingPartID = parseInt(regExpResult[2], 10);
	}
	// extract highest sibblingPartID
	coupletMemory = jedtParsedPageData[uniqueKeyID].coupletMemory;
	for (coupletID in coupletMemory) {
		if (coupletMemory[coupletID]) { // filter unwanted properties from the prototype
			for (itemID in coupletMemory[coupletID]) {
				if (coupletMemory[coupletID][itemID]) { // filter unwanted properties from the prototype
					// extract highest belonging sibling id
					regExpResult = regExpSearch.exec(itemID);
					if (regExpResult && (parseInt(regExpResult[1], 10) === basePartID)) {
						newSibblingPartID = parseInt(regExpResult[2], 10);
						if (newSibblingPartID >= sibblingPartID) {
							sibblingPartID = (newSibblingPartID + 1); // set to next higher
						}
					}
				}
			}
		}
	}
	// return new id
	return basePartID + "n" + sibblingPartID;
}

function jedtParsedDataCoupletMemory_FindItemID(uniqueKeyID, itemID) {
	var retObj = {coupletID: "", itemsCounter: 0},
		coupletMemory = jedtParsedPageData[uniqueKeyID].coupletMemory,
		coupletIDKey, coupletStructure, itemsCounter, hasFound, itemIDKey;
	for (coupletIDKey in coupletMemory) {
		if (coupletMemory[coupletIDKey]) { // filter unwanted properties from the prototype
			coupletStructure = coupletMemory[coupletIDKey];
			// init loop variables
			itemsCounter = 0;
			hasFound = false;
			// look for itemID
			for (itemIDKey in coupletStructure) {
				if (coupletStructure[itemIDKey]) { // filter unwanted properties from the prototype
					if (itemIDKey === itemID) { // no break; to get correct itemsCounter
						hasFound = true;
					}
					itemsCounter += 1;
				}
			}
			if (hasFound) {
				retObj.coupletID = coupletIDKey; // key is the ID
				retObj.itemsCounter = itemsCounter;
				return retObj;
			}
		}
	}
	return retObj;
}

function jedtParsedDataCoupletMemory_GenerateNewCoupletID(uniqueKeyID, otherCoupletID) {
	// get collection which holds all couplet parent ids (defined in "Key Start" parsed tpl data)
	var coupletMemory = jedtParsedPageData[uniqueKeyID].coupletMemory,
		newCoupletID = -1, coupletID, currCoupletID; 
	for (coupletID in coupletMemory) {
		if (coupletMemory[coupletID]) { // filter unwanted properties from the prototype
			currCoupletID = parseInt(coupletID, 10);
			if (currCoupletID > newCoupletID) {
				newCoupletID = currCoupletID;
			}
		}
	}
	// next highest
	newCoupletID += 1;
	// add that further addings are possible
	if (!coupletMemory[newCoupletID]) { // lazy init
		jedtParsedPageData[uniqueKeyID].coupletMemory = jedtBase_CreateNewAfter(coupletMemory, otherCoupletID, newCoupletID, {});
	}
	return newCoupletID;
}

function jedtParsedDataCoupletMemory_RemoveItemID(uniqueKeyID, itemID) {
	var foundItemData = jedtParsedDataCoupletMemory_FindItemID(uniqueKeyID, itemID);
	// remove itemID & parent (if only one entry)
	if (foundItemData.coupletID.length) {
		jedtBase_RemoveKey(jedtParsedPageData[uniqueKeyID].coupletMemory[foundItemData.coupletID], itemID);
		if (foundItemData.itemsCounter === 1) { // remove also couplet - was last decision
			jedtBase_RemoveKey(jedtParsedPageData[uniqueKeyID].coupletMemory, foundItemData.coupletID);
		}
	}
	else {
		throw (new JKeyException("SystemException", {info: jedtResource("edtSysExNfo2")}));
	}
}

//// jedtParsedData ////

function jedtParsedData_ExtractIDFromChilds(childData) {
	var namedID = "", firstUnnamed = "", // look for id (prio has field 'id' - alternative try first unnamed)
		fieldKey, currField, fieldsConcated;
	for (fieldKey in childData) {
		if (childData[fieldKey]) { // filter unwanted properties from the prototype
			currField = childData[fieldKey];
			if (currField.name === 1) {
				firstUnnamed = currField.content;
			}
			if ((typeof(currField.name) === 'string') && (currField.name.toLowerCase() === "id")) {
				namedID = currField.content;
				break;
			}
		}
	}
	if ((namedID.length === 0) && (firstUnnamed.length === 0)) { // nothing found
		fieldsConcated = "\n{{... ";
		for (fieldKey in childData) {
			if (childData[fieldKey]) { // filter unwanted properties from the prototype
				currField = childData[fieldKey];
				fieldsConcated += currField.name + "=" + currField.content + "\n" + currField.closingType + " ";
			}
		}
		throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo1"), position: fieldsConcated}));
	}
	return (namedID.length > 0) ? namedID : firstUnnamed;
}

function jedtParsedData_InitCoupletMemoryForKey(keyDiv) {
	// get all keys with this id from page
	var keyDivs = $j("#" + keyDiv.id),
		keyPosInData = 0, keyPosInDOM = 1, // default the should be only one key with the id
		withinAnyKey = false, withinCorrectKey = false, finishInit = false,
		keyStartRef, parseDataKey, currTpl, currTplName, foundKeyStart, currKeyID,
		unextractedCoupletID, extracted;
	if (keyDivs.length > 1) { // more keys with same id
		keyDivs.each(function () {
			if (this !== keyDiv) {
				keyPosInDOM += 1;
			}
		});
	}
	// navigate through parsed data & create coupletMemory
	for (parseDataKey in jedtParsedPageData) {
		if (jedtParsedPageData[parseDataKey] && (parseDataKey !== 'tplNames') && (jedtParsedPageData[parseDataKey].type === "{{")) { // is a parsed tpl
			currTpl = jedtParsedPageData[parseDataKey];
			currTplName = currTpl.name;
			foundKeyStart = false; // ignore "Key Start" adding to coupletMemory
			// check key start & end to find correct key
			if (currTplName === "Key Start") {
				currKeyID = jedtParsedData_ExtractIDFromChilds(currTpl.fields);
				if (withinAnyKey) { // new key starts, even though old hasn't finished
					throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo2A") + currKeyID + jedtResource("edtParsExNfo2B")}));
				}
				// set flag
				withinAnyKey = true;
				// key is same as from current clicked player
				if (currKeyID === keyDiv.id) {
					keyPosInData += 1; // found a key with this id
				}
				// found correct key
				if (keyPosInDOM === keyPosInData) {
					foundKeyStart = true;
					withinCorrectKey = true;
					currTpl.wasEdited = true; // set action flag
					currTpl.domRef = keyDiv;
					keyStartRef = currTpl;
					$j.data(keyDiv, "keyData", {uniqueKeyID: parseDataKey});
				}				
			}
			else if (currTplName === "Key End") {
				withinAnyKey = false; // set flag back
				if (withinCorrectKey) { // handled already the right key data
					finishInit = true;
				}
			}
			// add to return object
			if (withinCorrectKey && !foundKeyStart && !finishInit) {
				if (!keyStartRef.coupletMemory) {
					keyStartRef.coupletMemory = {};
				}
				unextractedCoupletID = jedtParsedData_ExtractIDFromChilds(currTpl.fields);
				extracted = jkeyExtractCombinedCoupletLeadID(unextractedCoupletID);
				if (extracted.coupletID.length === 0) {
					throw (new JKeyException("InitException", {info: jedtResource("edtInitNfoEx")}));
				}
				if (!keyStartRef.coupletMemory[extracted.coupletID]) {
					keyStartRef.coupletMemory[extracted.coupletID] = {};
				}
				keyStartRef.coupletMemory[extracted.coupletID][parseDataKey] = {wasOrigin: true, wasEdited: false};
			}
			// handled already the right key data
			if (finishInit) {
				break;
			}
		}
	}
}

//// jedtTemplate ////

function jedtTemplate_LegendInputs() { // TODO: tdLeadextras is to specific (but with it the "Lead" tpl looks better)
	var splittedID = this.id.split("_"),
		action = splittedID[splittedID.length - 1],
		parent = $j(this).closest("fieldset"),
		tdLeadextras, collection, isOtherOff, isCurrentOff;
	// perform action
	switch (action) {
	case ("moreAll"):
		if (this.checked) {
			// disable & activate checkbox for neigbhours
			$j(this).nextAll("input").attr("checked", true).attr("disabled", "disabled");
			// show all affected elements
			parent.find("tr.moreSubheading, tr.moreResultExtras").show();
			tdLeadextras = parent.find("td.leadextras");
			tdLeadextras.show();
			tdLeadextras.find("table.moreText,table.moreImages").show();
		} else {
			// enable & reset checkbox for neigbhours
			$j(this).nextAll("input").attr("checked", false).attr("disabled", false);
			// hide all affected elements
			parent.find("tr.moreSubheading, tr.moreResultExtras").hide();
			tdLeadextras = parent.find("td.leadextras");
			tdLeadextras.hide();
			tdLeadextras.find("table.moreText,table.moreImages").hide();
		}
		break;
	case ("moreResultExtras"):
	case ("moreSubheading"):
		collection = parent.find("tr." + action);
		if (collection.get(0).style.display === "none") { // toggle() has problems in IE
			collection.show();
		}
		else {
			collection.hide(); 
		}
		break;
	case ("moreText"):
	case ("moreImages"):
		tdLeadextras = parent.find("td.leadextras"); // has to noted because of style
		// get other status
		isOtherOff = (tdLeadextras.find("table." + ((action === "moreText") ? "moreImages" : "moreText")).get(0).style.display === "none");
		// toggle current
		tdLeadextras.find("table." + action).toggle();
		isCurrentOff = (tdLeadextras.find("table." + action).get(0).style.display === "none");
		// switch parent-td display
		if (isOtherOff && isCurrentOff) {
			tdLeadextras.hide();
		} else {
			tdLeadextras.show();
		}
		break;
	}
}

function jedtTemplate_AddItemRowID(tpl, itemRowID) {
	tpl.find("[id],[for]").each(function () {
		var jThis = $j(this);
		if (this.id.length !== 0) {
			jThis.attr("id", itemRowID + "_" + this.id);
			jThis.attr("name", this.id);
			return;
		}
		jThis.attr("for", itemRowID + "_" + jThis.attr("for"));
	});
}

function jedtTemplate_AddLegendInputAction(tpl) {
	if (tpl.hasClass("jedtHandleLegendInputs")) {
		tpl.find("legend input").click(jedtTemplate_LegendInputs);
	}
}

function jedtTemplate_FillParseLeadID(currTpl) {
	var parseLeadIDElem = currTpl.find(".jedtParseLeadID").get(0),
		extracted, parseCoupletBacklinkElem, parseCoupletAltElem;
	if (parseLeadIDElem) { // handle jedtParseLeadID
		extracted = jkeyExtractCombinedCoupletLeadID(parseLeadIDElem.value);
		parseLeadIDElem.value = extracted.coupletID;
		// set jedtCoupletBacklink (also can be empty)
		parseCoupletBacklinkElem = currTpl.find(".jedtCoupletBacklink").get(0);
		if (parseCoupletBacklinkElem) {
			parseCoupletBacklinkElem.value = extracted.backlink;
		} else {
			throw (new JKeyException("TemplateException", {info: jedtResource("edtTplExNfo1")}));
		}
		// set jedtCoupletAltID (also can be empty)
		parseCoupletAltElem = currTpl.find(".jedtCoupletAltID").get(0);
		if (parseCoupletAltElem) {
			parseCoupletAltElem.value = extracted.alt;
		} else {
			throw (new JKeyException("TemplateException", {info: jedtResource("edtTplExNfo2")}));
		}
	}
	else {
		throw (new JKeyException("TemplateException", {info: jedtResource("edtTplExNfo3")}));
	}
}

function jedtTemplate_FillValues(tpl, itemRowID) {
	var clonedTpl = jedtHtmlEditTpls[tpl.name].clone().closest("fieldset"),
		unknownFieldsPointer = clonedTpl.find("#unknownFields"),
		unknownFieldsCounter = 0,
		fieldKey, currField, searchID, currElem;
	// change holder for unknownField
	unknownFieldsPointer.append($j("<table/>").addClass("unknownFields"));
	unknownFieldsPointer = unknownFieldsPointer.find("table"); // set new
	// fill tpl
	for (fieldKey in tpl.fields) {
		if (tpl.fields[fieldKey]) { // filter unwanted properties from the prototype
			currField = tpl.fields[fieldKey];
			searchID = currField.isUnnamedField ? ("unnamed_" + currField.name) : currField.name;
			// looks for element in cloned to fill it
			currElem = clonedTpl.find("#" + searchID.replace(' ', '_'));
			if (currElem.length === 1) { // found
				currElem.attr({value: currField.content});
			} else { // append to unknownFields
				unknownFieldsPointer.append($j("<tr/>")
						.append($j("<td/>").html(searchID))
						.append($j("<td/>").append($j("<input/>").attr({type: "text", id: searchID.replace(' ', '_'), size: "120", value: currField.content})))									
						);
				unknownFieldsCounter += 1;
			}
		}
	}
	// show unknown fields area
	if (unknownFieldsCounter > 0) {
		$j(unknownFieldsPointer).closest("div").show();
	}
	// change all "id" & "for" & add itemRowID before
	jedtTemplate_AddItemRowID(clonedTpl, itemRowID);
	// add function for clicking the inputs
	jedtTemplate_AddLegendInputAction(clonedTpl);
	return clonedTpl;
}

//// jedtContainer ////

function jedtContainer_CreateHtml(coupletID, coupletRowID, inEdit, colspanCells) {
	return $j("<tr class='edtCouplet' id=" + coupletRowID + " />")
			.append($j("<td align='right' />")
					.addClass("actionCell")
					.append(jedtActionCell_CreatePrimary(inEdit)))
			.append($j("<td class='actionContent' " + ((colspanCells > 1) ? ("colspan='" + colspanCells + "'") : "") + "/>")
					.append($j("<fieldset />")
							.addClass("jedtFieldset")
							.append($j("<legend />")
									.append("<b>" + jedtResource("edtCoupletDescription") + " " + coupletID + "</b>")
							)));
}

function jedtContainer_CreateItemRow(id, data, unknownTplMode, addMaxWidth, color) {
	if (!unknownTplMode) {
		data.find("td.actionCell").append(jedtActionCell_CreateSecondary());
		data.css(color ? {backgroundColor: color} : {});
	}
	return $j("<tr/>").addClass("edtItem").attr(addMaxWidth ? {id: id, width: "100%"} : {id: id}).append($j("<td/>").append(data));
}

function jedtContainer_CreateItemRowForUnknownTpl(tplName, tplContent) {
	return $j("<fieldset/>")
		.addClass("jedtFieldset")
		.append($j("<legend/>").append("<b>" + tplName + "</b>"))
		.append($j("<table/>")
				.append($j("<tr/>")
						.append($j("<td/>").css({width: "100%"}).append($j("<textarea rows=\"5\" cols=\"50\" />").css({width: "99%"}).attr({value: tplContent})))
						.append($j("<td/>").addClass("actionCell"))
						)
				);
}

function jedtContainer_CreateItemRowID(keyID, uniqueKeyID, itemIDOrCoupletID, belonging) {
	return "jkey_" + keyID + "_edt_uqID" + uniqueKeyID + "_" + belonging + itemIDOrCoupletID;
}

function jedtContainer_CreateItemsContainerHtml(itemRowID) {
	return $j("<table/>").addClass("coupletItems")
			.attr({width: "100%", cellspacing: "0", cellpadding: "0", id: itemRowID});
}

function jedtContainer_CreateItemsContainerContent(keyDiv, coupletID) {
	var uniqueKeyID = $j.data(keyDiv, "keyData").uniqueKeyID,
		itemsContainer = jedtContainer_CreateItemsContainerHtml(jedtContainer_CreateItemRowID(keyDiv.id, uniqueKeyID, coupletID, "coupletItems")),
		coupletInMemory = jedtParsedPageData[uniqueKeyID].coupletMemory[coupletID],
		unknownTplMode = false, backgroundColorCounter = 0, data,
		itemID, currTpl, itemRowID, color;
	// add all children in keyData - add them to specific holder depending on mode
	for (itemID in coupletInMemory) {
		if (coupletInMemory[itemID]) { // filter unwanted properties from the prototype
			currTpl = jedtParsedPageData[itemID];
			itemRowID = jedtContainer_CreateItemRowID(keyDiv.id, uniqueKeyID, itemID, "itemID");
			// get editor tpl & fill with current values
			if (jedtHtmlEditTpls[currTpl.name]) { // tpl exists
				data = jedtTemplate_FillValues(currTpl, itemRowID);
				jedtTemplate_FillParseLeadID(data);
			} else { // tpl does not exists - create unknown fieldset with legend, textarea & actionCell
				unknownTplMode = (jedtBase_CountKeys(coupletInMemory) === 1);
				data = jedtContainer_CreateItemRowForUnknownTpl(currTpl.name, jkeyParser.buildString(jedtPageCharArray, currTpl.startIndex, jkeyParser.indexAfterToken(currTpl.closingType, currTpl.endIndex)));
			}
			// choose color & append to container
			color = pastelColors[backgroundColorCounter];
			backgroundColorCounter = (backgroundColorCounter === (pastelColors.length - 1)) ? 0 : (backgroundColorCounter + 1);
			itemsContainer.append(jedtContainer_CreateItemRow(itemRowID, data, unknownTplMode, false, color));
		}
	}
	return itemsContainer;
}

function jedtContainer_ItemizeRowID(itemRowID) {
	var regExpResult = /jkey_(\w+)_edt_uqID(\w+)_(itemID|couplet|coupletItems)(\w+)/.exec(itemRowID);
	return (regExpResult[3] === "itemID") ? 
		{keyID: regExpResult[1], uniqueKeyID: regExpResult[2], itemID: regExpResult[4]} : 
		{keyID: regExpResult[1], uniqueKeyID: regExpResult[2], coupletID: regExpResult[4]};
}

//// jedtSerialize ////

function jedtSerialize_UnknownEdtTpl(inputValue) {
	var contentCharArray = inputValue.split(''),
		parsedTpl = null, countedKeys, extracted;
	// is empty
	if (contentCharArray.length === 0) {
		throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3"), reason: jedtResource("edtParsExReas1")}));
	}
	// is it parseable
	try {
		parsedTpl = jkeyParser.initialize(contentCharArray);
	}
	catch (err) {
		throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3") + "\n", expectedToke: err.variables.expectedToken + "\n", contentBeforeErrorPosition: err.variables.contentBeforeErrorPosition + "\n", contentAfterErrorPosition: err.variables.contentAfterErrorPosition, content: inputValue}));
	}
	// is parsed result only an empty obj
	if (parsedTpl["0"].fields) {
		countedKeys = jedtBase_CountKeys(parsedTpl["0"].fields);
		if (countedKeys === 0) {
			throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3"), reason: jedtResource("edtParsExReas2"), content: inputValue}));
		}
	} else {
		throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3"), reason: jedtResource("edtParsExReas2"), content: inputValue}));
	}
	// has it a parseable id
	try {
		extracted = jkeyExtractCombinedCoupletLeadID(jedtParsedData_ExtractIDFromChilds(parsedTpl["0"].fields));
	}
	catch (err2) {
		throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3"), reason: jedtResource("edtParsExReas3"), content: inputValue}));
	}
	if (extracted.coupletID.length === 0) {
		throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3"), reason: jedtResource("edtParsExReas3"), content: inputValue}));							
	}
	// if all valid add to new parse data
	return inputValue;
}

function jedtSerialize_KnownEdtTpl(inputs, itemRowID) {
	var fields = {unnamed: {}, named: {}},
		inputsLength = inputs.length,
		i, currInput, tagName, validInput, name, contentCharArray, parsedTpl, key, currContent,
		regExpResult, origMultipleFieldFirstPart = "", multipleFieldMiddlePartContent, multipleFieldLastPartContent,
		multipleFieldKey = "", checkID = "", unnamed = "", named = "";
	for (i = 0; i < inputsLength; i += 1) { // not each - is slower
		currInput = $j(inputs.get(i));
		tagName = currInput.attr("tagName").toLowerCase();
		// check if correct input
		validInput = false;
		if (((tagName === "input") && (currInput.attr("type") === "text")) || (tagName === "textarea")) {
			validInput = true;
		}
		// check field
		if (validInput) {
			// extract field name & content
			name = currInput.attr("id").replace(itemRowID + "_", "");
			contentCharArray = currInput.attr("value").split('');
			// check content is valid (parse like page to find missing closing types)
			parsedTpl = null;
			try {
				parsedTpl = jkeyParser.initialize(contentCharArray);
			} catch (err) {
				throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3") + "\n", expectedToken: err.variables.expectedToken + "\n", contentBeforeErrorPosition: err.variables.contentBeforeErrorPosition + "\n", contentAfterErrorPosition: err.variables.contentAfterErrorPosition}));
			}
			// sort field
			if (name.search(/unnamed_/) !== -1) {
				fields.unnamed[name.replace("unnamed_", "")] = {content: currInput.attr("value")};
			} else {
				fields.named[name] = currInput.attr("value");
			}
		}
	}
	// loop through fields & find unamed_x with b || c
	for (key in fields.unnamed) {
		if (!fields.unnamed[key].alreadyExtracted) { // not handled yet
			regExpResult = /(\d*)(b|c)?/.exec(key);
			if (regExpResult[2] && (regExpResult[2].length > 0)) {
				origMultipleFieldFirstPart = fields.unnamed[regExpResult[1]].content;
				multipleFieldKey = regExpResult[1];
				// concate fields
				multipleFieldMiddlePartContent = fields.unnamed[regExpResult[1] + "b"].content;
				multipleFieldLastPartContent = fields.unnamed[regExpResult[1] + "c"].content;
				fields.unnamed[regExpResult[1]].content = fields.unnamed[regExpResult[1]].content + 
					((multipleFieldMiddlePartContent.search(/'|\*|\"|\^|\`|\°|\´|\ˆ|\ˇ|\ˉ|\˘|\˙|\˚|\˜ |\‘|\’|\“|\”|[a-z]+/) !== -1) ? " " : "") +  
					multipleFieldMiddlePartContent + ((multipleFieldLastPartContent.length !== 0) ? " (" + multipleFieldLastPartContent + ")" : "");
				fields.unnamed[regExpResult[1]].alreadyExtracted = true;
				fields.unnamed[regExpResult[1] + "b"].alreadyExtracted = true;
				fields.unnamed[regExpResult[1] + "c"].alreadyExtracted = true;
			}
		}
	}
	// remove b & c if exists
	if (multipleFieldKey.length !== 0) {
		jedtBase_RemoveKey(fields.unnamed, multipleFieldKey + "b");
		jedtBase_RemoveKey(fields.unnamed, multipleFieldKey + "c");
	}
	// check if a field with id exists or in unnamed (& check if valid)
	if (fields.named.id) {
		checkID = fields.named.id;
	}
	if (fields.unnamed[multipleFieldKey]) {
		checkID = ((origMultipleFieldFirstPart.length !== 0) ? origMultipleFieldFirstPart : ((fields.unnamed["1"]) ? fields.unnamed["1"].content : "")); // last chance try content "1"
	}								
	if (jkeyExtractCombinedCoupletLeadID(checkID).coupletID.length === 0) {
		throw (new JKeyException("ParserException", {info: jedtResource("edtParsExNfo3"), reason: jedtResource("edtParsExReas3")}));
	}								
	// concatenate fields
	for (key in fields.unnamed) {
		if (fields.unnamed[key]) { // filter unwanted properties from the prototype
			currContent = fields.unnamed[key].content;
			if (currContent.length > 0) {
				unnamed += " | " + currContent;
			}
		}
	}	
	for (key in fields.named) {
		if (fields.named[key]) { // filter unwanted properties from the prototype
			currContent = fields.named[key];
			if (currContent.length > 0) {
				named += "\n | " + key.replace("_", " ") + " = " + currContent;
			}
		}
	}								
	// create new concatenated with all necessary fields
	return unnamed + ((named.length !== 0) ? (named + "\n") : " ");
}

function jedtSerialize() {
	var newPage = "", // retValue
		ignoreUntilNextKeyEnd = false,
		parseDataKey, currParseData, doTemplateParse, 
		coupletMemory, coupletID, itemID, itemData, parsedDataStructure, itemRowID, inputs;
	for (parseDataKey in jedtParsedPageData) { // handle the internal memory flag
		if (jedtParsedPageData[parseDataKey] && (parseDataKey !== 'tplNames')) { // filter unwanted properties from the prototype
			currParseData = jedtParsedPageData[parseDataKey];
			doTemplateParse = false;
			if ((currParseData.type === "contentGap") && !ignoreUntilNextKeyEnd) { // handle text gaps
				newPage += jkeyParser.buildString(jedtPageCharArray, currParseData.startIndex, currParseData.endIndex);
			}
			else if (currParseData.type === "{{") { // is a tpl
				if (currParseData.name === "Key Start") { // is a starting tpl
					doTemplateParse = true;
					// check whether key was edited
					if (currParseData.wasEdited) {
						ignoreUntilNextKeyEnd = true; // key will parsed here (ignore all until next "Key End" in jedtParsedPageData is reached)
						doTemplateParse = false; // set back will handled here
						// parse "Key Start" itself
						newPage += jkeyParser.buildString(jedtPageCharArray, currParseData.startIndex, jkeyParser.indexAfterToken(currParseData.closingType, currParseData.endIndex)) + "\n";
						// parse this complete key (use state of the the "coupletMemory")
						coupletMemory = currParseData.coupletMemory;
						// iterate through all couplets saved there
						for (coupletID in coupletMemory) {
							if (coupletMemory[coupletID]) { // filter unwanted properties from the prototype
								// the couplets have key-children which are itemID data objs 
								for (itemID in coupletMemory[coupletID]) {
									if (coupletMemory[coupletID][itemID]) { // filter unwanted properties from the prototype
										itemData = coupletMemory[coupletID][itemID];
										parsedDataStructure = (itemData.wasOrigin) ? jedtParsedPageData[itemID] : jedtParsedPageData[jedtParsedDataCoupletMemory_ExtractOriginItemID(itemID)];
										if (itemData.wasEdited) {
											// get values from inputs
											itemRowID = jedtContainer_CreateItemRowID(currParseData.domRef.id, parseDataKey, itemID, "itemID");
											inputs = $j(currParseData.domRef).find("#" + itemRowID).find("input,textarea");
											if (inputs.length > 1) {
												newPage += parsedDataStructure.type + parsedDataStructure.name + jedtSerialize_KnownEdtTpl(inputs, itemRowID) + parsedDataStructure.closingType;
											}
											else if (inputs.length === 1) { // was an unknown only textarea possible
												newPage += jedtSerialize_UnknownEdtTpl(inputs.attr("value"));
											}
										} else {
											newPage += jkeyParser.buildString(jedtPageCharArray, parsedDataStructure.startIndex, jkeyParser.indexAfterToken(parsedDataStructure.closingType, parsedDataStructure.endIndex));
										}
										newPage += "\n";
									}
								}
							}
						}
					}
				} else if (currParseData.name === "Key End") {
					doTemplateParse = true;
					ignoreUntilNextKeyEnd = false;
				} else { // is tpl within an unedited key
					if (!ignoreUntilNextKeyEnd) {
						doTemplateParse = true;
					}
				}
			}
			// default parsing
			if (doTemplateParse) {
				newPage += jkeyParser.buildString(jedtPageCharArray, currParseData.startIndex, jkeyParser.indexAfterToken(currParseData.closingType, currParseData.endIndex));
			}			
		}	
	}
	return newPage;
}

//// jEditor ActionCell ////

function jedtActionCell_CreateNewTplData(tplName, coupletID, newItemID, newItemRowID) {
	var data, originItemID;
	if (jedtHtmlEditTpls[tplName]) {
		data = jedtHtmlEditTpls[tplName].clone().closest("fieldset");
		data.find(".jedtParseLeadID").val(coupletID); // fill couplet id
		// add empty field for alt & backlink (if needed)
		jedtTemplate_FillParseLeadID(data);
		// change all "id" & "for" & add itemRowID
		jedtTemplate_AddItemRowID(data, newItemRowID);
		// add function for clicking the inputs
		jedtTemplate_AddLegendInputAction(data);
	} else { // use open_token, tplname & close_token
		originItemID = jedtParsedDataCoupletMemory_ExtractOriginItemID(newItemID);
		data = jedtContainer_CreateItemRowForUnknownTpl(tplName, jedtParsedPageData[originItemID].type + tplName + "\n" + jedtParsedPageData[originItemID].closingType);
	}
	return data;
}

function jedtActionCell_ActionAddNew() {
	var itemizedRowID, tplName,
		actionCellParentRow = $j(this).closest("td.actionCell").parent(),
		coupletID, itemizedLastItem, unknownTplMode, newItemID, newItemRowID, newItemID2, newItemRowID2, 
		itemsContainer, container, currItemRow;		
	if (actionCellParentRow.hasClass("edtCouplet")) { // is a couplet-container
		// extract generated coupletID part & uniqueID from row
		itemizedRowID = jedtContainer_ItemizeRowID(actionCellParentRow.get(0).id);
		// calculate id for a new couplet container
		coupletID = jedtParsedDataCoupletMemory_GenerateNewCoupletID(itemizedRowID.uniqueKeyID, itemizedRowID.coupletID);
		// find last itemRow in the coupletItems container
		itemizedLastItem = jedtContainer_ItemizeRowID(actionCellParentRow.find("td.actionContent:first tr.edtItem:last").get(0).id);
		// tpl name from origin (last lead of current couplet container) - is same for new items
		tplName = jedtParsedPageData[jedtParsedDataCoupletMemory_ExtractOriginItemID(itemizedLastItem.itemID)].name;
		unknownTplMode = (!jedtHtmlEditTpls[tplName] && (actionCellParentRow.find("table.coupletItems tr.edtItem").length === 1));
		// create first new item id & add to coupletMemory
		newItemID = jedtParsedDataCoupletMemory_CalculateNewItemID(itemizedLastItem.uniqueKeyID, itemizedLastItem.itemID);
		jedtParsedDataCoupletMemory_CreateItemEntry(itemizedLastItem.itemID, newItemID, itemizedRowID.uniqueKeyID, coupletID, true);
		newItemRowID = jedtContainer_CreateItemRowID(itemizedRowID.keyID, itemizedRowID.uniqueKeyID, newItemID, "itemID");
		// create second new item id & add to coupletMemory
		if (!unknownTplMode) {
			newItemID2 = jedtParsedDataCoupletMemory_CalculateNewItemID(itemizedLastItem.uniqueKeyID, newItemID);
			jedtParsedDataCoupletMemory_CreateItemEntry(newItemID, newItemID2, itemizedRowID.uniqueKeyID, coupletID, false);
			newItemRowID2 = jedtContainer_CreateItemRowID(itemizedRowID.keyID, itemizedRowID.uniqueKeyID, newItemID2, "itemID");
		}
		// create itemsContainer & add couplet
		itemsContainer = jedtContainer_CreateItemsContainerHtml(jedtContainer_CreateItemRowID(itemizedRowID.keyID, itemizedRowID.uniqueKeyID, coupletID, "coupletItems"));
		itemsContainer.append(jedtContainer_CreateItemRow(newItemRowID, jedtActionCell_CreateNewTplData(tplName, coupletID, newItemID, newItemRowID), unknownTplMode, false, pastelColors[0])) // first new item
			.append(!unknownTplMode ? jedtContainer_CreateItemRow(newItemRowID2, jedtActionCell_CreateNewTplData(tplName, coupletID, newItemID2, newItemRowID2), unknownTplMode, false, pastelColors[1]) : ""); // second new item
		container = jedtContainer_CreateHtml(coupletID, jedtContainer_CreateItemRowID(itemizedRowID.keyID, itemizedRowID.uniqueKeyID, coupletID, "couplet"), true, (actionCellParentRow.find("td.actionContent:first").attr("colspan") ? actionCellParentRow.find("td.actionContent:first").attr("colspan") : 0));
		container.find("td.actionContent fieldset").append(itemsContainer);
		// add after current row
		actionCellParentRow.after(container);
	} else {
		currItemRow = actionCellParentRow.closest("tr.edtItem");
		itemizedRowID = jedtContainer_ItemizeRowID(currItemRow.get(0).id);
		// create item id & structure in coupletMemory
		newItemID = jedtParsedDataCoupletMemory_CalculateNewItemID(itemizedRowID.uniqueKeyID, itemizedRowID.itemID);
		coupletID = jedtParsedDataCoupletMemory_FindItemID(itemizedRowID.uniqueKeyID, itemizedRowID.itemID).coupletID;
		jedtParsedDataCoupletMemory_CreateItemEntry(itemizedRowID.itemID, newItemID, itemizedRowID.uniqueKeyID, coupletID, false);
		// create new tpl
		tplName = jedtParsedPageData[jedtParsedDataCoupletMemory_ExtractOriginItemID(itemizedRowID.itemID)].name; // same as from current row
		// create new newEditorItemRowID
		newItemRowID = jedtContainer_CreateItemRowID(itemizedRowID.keyID, itemizedRowID.uniqueKeyID, newItemID, "itemID");
		// add after current row
		currItemRow.after(jedtContainer_CreateItemRow(newItemRowID, jedtActionCell_CreateNewTplData(tplName, coupletID, newItemID, newItemRowID), false, false, pastelColors[$j.random(0, pastelColors.length - 1)]));
	}
}

function jedtActionCell_ActionEdit() {
	var jThis = $j(this),
		currDtRow = jThis.closest("tr.dt-row"),
		parentTable = currDtRow.closest("table"),
		keyDiv = jThis.closest("div.decisiontree").get(0),
		changeCouplets = parseInt(jThis.closest("td.actionCell").find("select:first").val(), 10),
		changedCounter = 0, cells = 0,
	cellCountEachFct = function () {
		cells += (($j(this).attr("colspan")) ? parseInt($j(this).attr("colspan"), 10) : 1);
	},
	itemsContainer, newRowID, extracted, coupletDataInMemory, itemID, container, 
	coupletRows, untransformedRows;
	while (currDtRow.length) {
		changedCounter += 1;
		if (changedCounter > changeCouplets) {
			break;
		}
		// count cells for colspan of container
		cells = 0; // init again
		currDtRow.find("td:first").nextAll("td, th").andSelf().each(cellCountEachFct);
		cells -= 1; // subtract action cell
		itemsContainer = jedtContainer_CreateItemsContainerContent(keyDiv, parseInt($j.trim(currDtRow.find("td.actionCell").next("td").text()), 10));
		newRowID = itemsContainer.attr("id").replace("coupletItems", "couplet");
		// set edit flag for all belonging decision leads in coupletMemory
		extracted = jedtContainer_ItemizeRowID(newRowID);
		coupletDataInMemory = jedtParsedPageData[extracted.uniqueKeyID].coupletMemory[extracted.coupletID];
		for (itemID in coupletDataInMemory) {
			if (coupletDataInMemory[itemID]) { // filter unwanted properties from the prototype
				coupletDataInMemory[itemID].wasEdited = true;
			}
		}
		// create transformed couplet
		container = jedtContainer_CreateHtml(extracted.coupletID, newRowID, true, cells);
		container.find("td.actionContent fieldset").append(itemsContainer);
		// append container
		coupletRows = jkeyCoupletAsCollection(currDtRow);
		coupletRows.filter(":first").before(container);
		// set for next loop
		currDtRow = coupletRows.filter(":last").nextAll("tr.dt-row:first");
		// remove old
		coupletRows.remove();
	}
	// if no more unchanged rows - change colspan layout stuff to all rows
	untransformedRows = parentTable.find("tr:first").nextAll("tr.dt-row").length;
	if (!parentTable.find("tr:first").hasClass("edtCouplet")) {
		untransformedRows += 1;
	}
	if (untransformedRows === 0) { // all are transformed - change colspan
		parentTable.find("tr:first").nextAll("tr").andSelf().each(function () {
			$j(this).find("td.actionContent:first").removeAttr("colspan");
		});
	}
}

function jedtActionCell_ActionMove() {
	var itemizedRowID, done,
		moveUpOrDown = ($j(this).hasClass("edtIconActionMoveUp")),
		actionCellParentRow = $j(this).closest("td.actionCell").parent(),
		sibblingFirstDtRow, keyDiv, itemRow, coupletID, coupletMemory;
	if (actionCellParentRow.hasClass("edtCouplet") || (actionCellParentRow.hasClass("dt-row") && actionCellParentRow.attr("id"))) { // is a coupletContainer || untransformed couplet
		// find neighbours start row
		sibblingFirstDtRow = actionCellParentRow; // start loop at current
		while (sibblingFirstDtRow.length) {
			sibblingFirstDtRow = moveUpOrDown ? sibblingFirstDtRow.prev("tr") : sibblingFirstDtRow.next("tr");
			if (sibblingFirstDtRow.find("td.actionCell:first").length) { // rowHasActionCell
				break;
			}
		}
		// move current if neighbour
		if (sibblingFirstDtRow.length) { // no neighbour means edge region table
			keyDiv = actionCellParentRow.closest("div.decisiontree").get(0);
			itemizedRowID = (actionCellParentRow.hasClass("edtCouplet")) ? 
				jedtContainer_ItemizeRowID(actionCellParentRow.get(0).id) : // itemize infos
				{keyID: keyDiv.id, uniqueKeyID: $j.data(keyDiv, "keyData").uniqueKeyID, coupletID: $j.trim(actionCellParentRow.find("td.actionCell").next("td").text())}; // collect infos
			// move in coupletMemory (whole will be rewritten) & DOM
			jedtParsedPageData[itemizedRowID.uniqueKeyID].coupletMemory = jedtBase_MoveKeyOnceUpOrDown(jedtParsedPageData[itemizedRowID.uniqueKeyID].coupletMemory, itemizedRowID.coupletID, moveUpOrDown);
			done = moveUpOrDown ? // move coupletRows before || after neighboursCoupletRows
				jkeyCoupletAsCollection(sibblingFirstDtRow).filter(":first").before(jkeyCoupletAsCollection(actionCellParentRow)) : 
				jkeyCoupletAsCollection(sibblingFirstDtRow).filter(":last").after(jkeyCoupletAsCollection(actionCellParentRow));
		}
	}
	else { // is within a coupletContainer
		itemRow = actionCellParentRow.closest("tr.edtItem");
		itemizedRowID = jedtContainer_ItemizeRowID(itemRow.get(0).id);
		coupletID = jedtParsedDataCoupletMemory_FindItemID(itemizedRowID.uniqueKeyID, itemizedRowID.itemID).coupletID;
		// move in coupletMemory & DOM
		coupletMemory = jedtParsedPageData[itemizedRowID.uniqueKeyID].coupletMemory;
		coupletMemory[coupletID] = jedtBase_MoveKeyOnceUpOrDown(coupletMemory[coupletID], itemizedRowID.itemID, moveUpOrDown);
		done = moveUpOrDown ? itemRow.prev("tr").before(itemRow) : itemRow.next("tr").after(itemRow);
	}
}

function jedtActionCell_ActionRemove() {
	var actionCellParentRow = $j(this).closest("td.actionCell").parent(),
		itemizedRowID, itemRow, coupletContainer, coupletRow, itemizedParentRowID;
	if (actionCellParentRow.hasClass("edtCouplet")) { // is a couplet-container
		itemizedRowID = jedtContainer_ItemizeRowID(actionCellParentRow.get(0).id);
		jedtBase_RemoveKey(jedtParsedPageData[itemizedRowID.uniqueKeyID].coupletMemory, itemizedRowID.coupletID);
		actionCellParentRow.remove();		
	} else {
		itemRow = actionCellParentRow.closest("tr.edtItem");
		itemizedRowID = jedtContainer_ItemizeRowID(itemRow.get(0).id);
		// rescue container ref
		coupletContainer = itemRow.closest("table.coupletItems");
		if (coupletContainer.find("tr.edtItem").length === 2) {
			alert(jedtResource("edtActionRemoveFault"));
		}
		else { // contains more than two decisions
			itemRow.remove();
			// check if container is obsolete
			if (!coupletContainer.find("tr.edtItem").length) { // if last entry in coupletItems -> remove (never reached, because min 2)
				coupletRow = coupletContainer.closest("tr.edtCouplet");
				// remove from view & parsedData
				itemizedParentRowID = jedtContainer_ItemizeRowID(coupletRow.get(0).id);
				jedtBase_RemoveKey(jedtParsedPageData[itemizedParentRowID.uniqueKeyID].coupletMemory, itemizedParentRowID.coupletID); // remove coupletID & last itemID
				coupletRow.remove();
			} else {
				jedtParsedDataCoupletMemory_RemoveItemID(itemizedRowID.uniqueKeyID, itemizedRowID.itemID);
			}
		}
	}
}

function jedtActionCell_CreatePrimary(inEdit) {
	return $j("<table " + (inEdit ? "style='margin-top:1em;'" : "") + "/>")
			.attr({"cellspacing": "0", "cellpadding": "0"})
			.append($j("<tr/>")
					.append(!inEdit ? $j("<td />")
							.append($j("<select><option>1</option><option>2</option><option>5</option><option>9</option></select>")) : "")
					.append(inEdit ? $j("<td />")
							.addClass("actionRemove")
							.append(jedtImgResource("edtIconActionRemove")
									.addClass("edtIconActionRemove")
									.attr({border: "0", title: jedtResource("edtActionRemoveTitle")})
									.click(jedtActionCell_ActionRemove)) : "")
					.append($j("<td />")
							.addClass("actionMove")
							.append(jedtImgResource("edtIconActionMoveUp")
									.addClass("edtIconActionMoveUp")
									.attr({border: "0", title: jedtResource("edtActionMoveUpTitle")})
									.click(jedtActionCell_ActionMove)))
			)
			.append($j("<tr/>")
					.append(!inEdit ? $j("<td />")
							.addClass("actionEdit")
							.append(jedtImgResource("edtIconActionEdit")
									.attr({border: "0", title: jedtResource("edtActionEditTitle")})
									.click(jedtActionCell_ActionEdit)) : "")		
					.append(inEdit ? $j("<td />")
							.addClass("actionAddNew")
							.append(jedtImgResource("edtIconActionAddNew")
									.addClass("edtIconActionAddNew")
									.attr({border: "0", title: jedtResource("edtActionAddNewTitle")})
									.click(jedtActionCell_ActionAddNew)) : "")
					.append($j("<td />").addClass("actionMove")
							.append(jedtImgResource("edtIconActionMoveDown")
									.addClass("edtIconActionMoveDown")
									.attr({border: "0", title: jedtResource("edtActionMoveDownTitle")})
									.click(jedtActionCell_ActionMove)))
			);
}

function jedtActionCell_CreateSecondary() {
	return $j("<table />")
			.append($j("<tr/>").append($j("<td/>").addClass("actionRemove")
					.append(jedtImgResource("edtIconActionRemove")
						.attr({border: "0", title: jedtResource("edtActionRemoveTitle")})
						.click(jedtActionCell_ActionRemove)
						)))
			.append($j("<tr/>").append($j("<td/>").addClass("actionMove")
					.append(jedtImgResource("edtIconActionMoveUp")
						.addClass("edtIconActionMoveUp")
						.attr({border: "0", title: jedtResource("edtActionMoveUpTitle")})
						.click(jedtActionCell_ActionMove)
						)))
			.append($j("<tr/>").append($j("<td/>").addClass("actionMove")
					.append(jedtImgResource("edtIconActionMoveDown")
						.addClass("edtIconActionMoveDown")
						.attr({border: "0", title: jedtResource("edtActionMoveDownTitle")})
						.click(jedtActionCell_ActionMove)
						)))
			.append($j("<tr/>").append($j("<td/>").addClass("actionAddNew")
					.append(jedtImgResource("edtIconActionAddNew")
						.attr({border: "0", title: jedtResource("edtActionAddNewTitle")})
						.click(jedtActionCell_ActionAddNew)
					)));
}

//// JKey parser ////

function JKeyPageParser() {
	/*
	 * description: main function, which parses first level tpls + non-tpl content between
	 * and skips all lower level tpls as well as links, img, comments, nowiki, etc.
	 * charArray: complete content of the page as char array
	 * returns: object with parsed content
	 */
	this.initialize = function (charArray) {		
		if (!charArray) { // abort main fct 
			return {}; 
		}
		// data container
		var parsedData = {}, // loop variables 
			token, index = 0, retValue, tplCounter = -1,
			addContentGap = false, gapStartIndex = 0, gapEndIndex = 0, // space between tpls (nonparsable gap)
			currTplName, lastParsed;
		// parses the whole text looking for tpls, comments
		do {
			token = this.extractToken(charArray, index);
			index = token.indexAfter;
			// choose function
			switch (token.type) {
			case "{{":
				gapEndIndex = this.indexBeforeToken(token.type, index);
				retValue = this.parseTpl(charArray, index);
				if ((gapEndIndex - gapStartIndex) > 0) { // is a gap
					addContentGap = true;
				}
				break;
			case "[[":
				index = this.skipLinkOrImg(charArray, index);
				break;
			case "<!--":
			case "<nowiki>":
				index = this.skipSpecial(charArray, index, token.type, (token.type === "<!--") ? "-->" : "</nowiki>");
				break;
			case " ": // tokenNone
			case "": // tokenEnd
				retValue = null;
				break;
			}
			// memorize content gap between tpls
			if (addContentGap) {
				parsedData[tplCounter += 1] = {startIndex: gapStartIndex, endIndex: gapEndIndex, type: "contentGap"};
			}			
			// add valid value
			if (retValue) {
				// set index to new value
				index = this.indexAfterToken(token.type, retValue.endIndex);
				// gap handling
				addContentGap = false;
				gapStartIndex = index;
				// add tpl name
				if (typeof(parsedData.tplNames) !== 'object') {
					parsedData.tplNames = {};
				}
				currTplName = retValue.name; 
				// set frequency
				if (!parsedData.tplNames[currTplName]) {
					parsedData.tplNames[currTplName] = 0;
				}
				parsedData.tplNames[currTplName] += 1;
				// add parsed tpl structure to data
				tplCounter += 1;
				parsedData[tplCounter] = retValue;
				// reset
				retValue = null;
			}	
		}
		while (token.type !== "");
		// add content gap after last tpl if exists
		if (parsedData[tplCounter]) {
			lastParsed = parsedData[tplCounter];
			if ((lastParsed.type === "{{") && (jkeyParser.indexAfterToken(lastParsed.closingType, lastParsed.endIndex) < index)) {
				tplCounter += 1;
				parsedData[tplCounter] = {startIndex: jkeyParser.indexAfterToken(lastParsed.closingType, lastParsed.endIndex), endIndex: index, type: "contentGap"};
			}
		}
		return parsedData;
	};	
	/*
	 * description: skip whitespace
	 * charArray: complete content of the page as array of chars
	 * index: current position within charArray
	 * returns: index of first non-whitespace
	 */
	this.skipWhitespace = function (charArray, index) {
		for (; index < charArray.length; index += 1) {
			if (" \t\n\r".indexOf(charArray[index]) === -1) { 
				return index;
			}	
		}		
		// no whitespace
		return index;
	};	
	/*
	 * description: skip whitespace form right to left
	 * charArray: complete content of the page as array of chars
	 * index: current position within charArray
	 * returns: index (depending on found whitespace)
	 */
	this.skipWhitespaceBackward = function (charArray, index) {
		// perform whitespace jump back
		for (; index > 0; index -= 1) {
			if (" \t\n\r".indexOf(charArray[index - 1]) === -1) {
				return index;
			}	
		}		
		// no whitespace
		return index;
	};	
	/*
	 * description: looks for current token
	 * charArray: complete content of the page as array of chars
	 * index: current position within charArray
	 * returns: token type & next index position
	 */
	this.extractToken = function (charArray, index) {
		// ignore empty or system chars
		index = this.skipWhitespace(charArray, index);		
		// check tokens with one char
		if (index >= charArray.length) { // main abort condition
			return {type: "", indexAfter: index};
		}		
		// variables are set character strings & will compared
		var s = {}, i, prev = "", token = " "; // tokenNone
		for (i = 0; i < 9; i += 1) {
			s[i] = ((index + i) >= charArray.length) ? "" : prev + charArray[index + i];
			prev = s[i];
		}			
		if (s[8] === "</nowiki>") {
			token = s[8];
		} else if (s[7] === "<nowiki>") {
			token = s[7];
		} else if (s[3] === "<!--") {
			token = s[3];
		} else if (s[2] === "-->") {
			token = s[2];
		} else if (s[1] === "{{" || s[1] === "}}" || s[1] === "[[" || s[1] === "]]" || s[1] === "]]") {
			token = s[1];
		} else if (s[0] === "|" || s[0] === "=") {
			token = s[0];
		}
		return {type: token, indexAfter: this.indexAfterToken(token, index)};		
	};
	/*
	 * description: return a slice of the char array as string
	 * charArray: complete content of the page as array of chars
	 * index: start index
	 * endIndex: end index
	 * returns: string
	 */
	this.buildString = function (charArray, index, endIndex) {
		var retString = "";
		for (; index < endIndex; index += 1) { 
			if (charArray[index]) { // does char exists
				retString += charArray[index]; 
			}
		}
		return retString;
	};	
	/*
	 * description: return a slice of the char array as string
	 * charArray: complete content of the page as array of chars
	 * index: start index
	 * endIndex: end index
	 * children: object including parsed-children to be skipped
	 * returns: string
	 */
	this.buildStringWithoutChilds = function (charArray, index, endIndex, chields) {
		var retString = "", e, i, childrenLength = 0, writeChar = true;
		for (e in chields) { 
			if (chields.hasOwnProperty(e)) { 
				childrenLength += 1;
			}
		}
		for (; index < endIndex; index += 1) { 
			writeChar = true;
			for (i = 0; i < childrenLength; i += 1) {
				if (index === chields[i].startIndex) {
					index = this.indexAfterToken(chields[i].closingType, chields[i].endIndex) - 1; // -1 because to add itself & will checked in next loop
					writeChar = false;
					break;
				}
			}			
			if (writeChar) { 
				retString += charArray[index];
			} 
		}
		return retString;
	};
	/*
	 * description: main tpl parsing, after {{ was found
	 * charArray: complete content of the page as array of chars
	 * index: current position within charArray
	 * returns: object with parsed tpl content
	 */
	this.parseTpl = function (charArray, index) {
		// initialize tpl container
		var tplData = {type: "{{", startIndex: this.indexBeforeToken("{{", index), closingType: "}}"},		
			token, endIndexName, whitespaceAtEndStartIndex, fieldCounter, unnamedFieldName;
		// iterate through whole tpl
		while (true) {
			token = this.extractToken(charArray, index);
			index = token.indexAfter;
			// extract tpl name, then test for fields:
			if ((token.type === "}}") || (token.type === "|")) {
				// calculate end index
				endIndexName = this.indexBeforeToken(token.type, index);
				// get tpl name
				whitespaceAtEndStartIndex = this.skipWhitespaceBackward(charArray, endIndexName);
				tplData.whitespaceAtEnd = this.buildString(charArray, whitespaceAtEndStartIndex, endIndexName);
				tplData.name = this.buildString(charArray, this.indexAfterToken("{{", tplData.startIndex), whitespaceAtEndStartIndex);
				tplData.name.replace("_", " ");				
				// tpl immediately closed, no fields available
				if (token.type === "}}") { 
					tplData.endIndex = endIndexName;
					return tplData;
				}
				// else (implied) has tokenPipe: initialize field counter, field container & check further fields
				fieldCounter = 0;
				unnamedFieldName = 1;
				tplData.fields = {};
				while (true) {
					// parse token field
					tplData.fields[fieldCounter] = this.parseOneTplField(charArray, index, unnamedFieldName);				
					// already finished tpl definition
					if (tplData.fields[fieldCounter].isLastField === true) {
						// reset last index
						tplData.endIndex = tplData.fields[fieldCounter].endIndex;		
						// finish current tpl
						return tplData;
					}
					// prepare token loop
					index = this.indexAfterToken("|", tplData.fields[fieldCounter].endIndex); // only pipe possible
					if (tplData.fields[fieldCounter].isUnnamedField === true) { // check special fields 
						unnamedFieldName += 1;
					}
					fieldCounter += 1;
				}		
			}
			else if (token.type === "{{") { // ignore tpls within
				index = this.skipTpl(charArray, index);
			}
			else if ((token.type === "<!--") || (token.type === "<nowiki>")) { // ignore unnecessary areas
				index = this.skipSpecial(charArray, index, token.type, (token.type === "<!--") ? "-->" : "</nowiki>");
			}
			else if (token.type === "") { // no closing token found means syntax fault
				throw (new JKeyException("ParserException", {errorPosition: tplData.startIndex, contentBeforeErrorPosition: this.buildString(charArray, (tplData.startIndex - 50), tplData.startIndex), contentAfterErrorPosition: this.buildString(charArray, tplData.startIndex, (tplData.startIndex + 50)), expectedToken: "}}"}));
			}
		}
	};
	/*
	 * description: handles parsing of a field (mediawiki: "tpl parameter") of a tpl
	 * charArray: complete content of the page as array of chars
	 * index: current position within charArray
	 * returns: object with parsed field content
	 */
	this.parseOneTplField = function (charArray, index, unnamedFieldName) {
		// initialize new field container
		var fieldData = {startIndex: this.indexBeforeToken("|", index), isLastField: false, isUnnamedField: false},
			token, // loop variables
			childCounter = 0,
			startIndexAfterToken, whitespaceAtStartEndIndex, whitespaceAtEndStartIndex,
			wholeField, isUnnamed, firstEqualSignPos, fieldUntilFirstEqual;
		while (true) {
			token = this.extractToken(charArray, index);
			index = token.indexAfter;
			if ((token.type === "}}") || (token.type === "|")) {
				// calculate end idx & set closing
				fieldData.endIndex = this.indexBeforeToken(token.type, index);
				fieldData.closingType = token.type;				
				startIndexAfterToken = this.indexAfterToken(token.type, fieldData.startIndex);
				whitespaceAtStartEndIndex = this.skipWhitespace(charArray, startIndexAfterToken);
				whitespaceAtEndStartIndex = this.skipWhitespaceBackward(charArray, fieldData.endIndex);
				fieldData.whitespaceAtStart = this.buildString(charArray, startIndexAfterToken, whitespaceAtStartEndIndex);
				fieldData.whitespaceAtEnd = this.buildString(charArray, whitespaceAtEndStartIndex, fieldData.endIndex);
				// get position where name ends and rest starts
				wholeField = this.buildString(charArray, whitespaceAtStartEndIndex, whitespaceAtEndStartIndex);
				isUnnamed = true; 
				firstEqualSignPos = wholeField.indexOf('=');
				if (firstEqualSignPos !== -1) { // extract named
					fieldUntilFirstEqual = this.buildString(charArray, whitespaceAtStartEndIndex, whitespaceAtStartEndIndex + firstEqualSignPos);
					// check name has complex structure
					try {
						this.initialize(fieldUntilFirstEqual.split(''));
						isUnnamed = false; // parsing was successful
						fieldData.name = this.buildStringWithoutChilds(charArray, whitespaceAtStartEndIndex, this.skipWhitespaceBackward(charArray, (whitespaceAtStartEndIndex + firstEqualSignPos)), fieldData.children); // skip space between named and = & ignore children like comments
						fieldData.content = this.buildString(charArray, this.skipWhitespace(charArray, (whitespaceAtStartEndIndex + firstEqualSignPos + 1)), whitespaceAtEndStartIndex); //+1 because start after equal sign
					}
					catch (exception) {
						isUnnamed = true;
					}
				}
				// handle as unnamed
				if (isUnnamed) {
					fieldData.name = unnamedFieldName;
					fieldData.content = wholeField;
					fieldData.isUnnamedField = true;
				}
				// already reached tpl definition
				if (token.type === "}}") { 
					fieldData.isLastField = true;
				}
				// finish current tpl
				return fieldData;
			}
			else if (token.type === "{{") { // ignore tpls within
				index = this.skipTpl(charArray, index);
			}
			if (token.type === "[[") { // ignore links within
				index = this.skipLinkOrImg(charArray, index);
			}
			else if ((token.type === "<!--") || (token.type === "<nowiki>")) {
				// add container if necessary
				if (childCounter === 0) { 
					fieldData.children = {};
				}
				// parse unnecessary areas
				fieldData.children[childCounter] = this.parseSpecial(charArray, index, token.type, (token.type === "<!--") ? "-->" : "</nowiki>");
				// ignore comments within
				index = fieldData.children[childCounter].endIndex;
				childCounter += 1;
			}
			else if (token.type === "") { // no closing token found means syntax fault
				throw (new JKeyException("ParserException", {errorPosition: fieldData.startIndex, contentBeforeErrorPosition: this.buildString(charArray, (fieldData.startIndex - 50), fieldData.startIndex), contentAfterErrorPosition: this.buildString(charArray, fieldData.startIndex, (fieldData.startIndex + 50)), expectedToken: "}}"}));
			}
		}	
	};
	/*
	 * description: handles "[[" to "]]", used in mediawiki for link, categories, images, etc.
	 * charArray: complete content of the page as array of chars
	 * index: current position within charArray
	 * returns: object with parsed link content
	 */
	this.parseLinkOrImg = function (charArray, index) {
		// initialize comment container
		var linkData = {type: "[[", startIndex: this.indexBeforeToken("[[", index), closingType: "]]"},
			token; // loop variables
		while (true) {
			token = this.extractToken(charArray, index);
			index = token.indexAfter;
			if (token.type === "]]") {
				linkData.endIndex = this.indexBeforeToken(token.type, index);
				return linkData; // all fine return link
			}
			else if (token.type === "{{") { // ignore tpls within
				index = this.skipTpl(charArray, index);
			}
			else if (token.type === "[[") { // ignore links within
				index = this.skipLinkOrImg(charArray, index);
			}
			else if ((token.type === "<!--") || (token.type === "<nowiki>")) { // ignore unnecessary areas
				index = this.skipSpecial(charArray, index, token.type, (token.type === "<!--") ? "-->" : "</nowiki>");
			}
			else if (token.type === "") { // no closing token found means syntax fault
				throw (new JKeyException("ParserException", {errorPosition: linkData.startIndex, contentBeforeErrorPosition: this.buildString(charArray, (linkData.startIndex - 50), linkData.startIndex), contentAfterErrorPosition: this.buildString(charArray, linkData.startIndex, (linkData.startIndex + 50)), expectedToken: "]]"}));
			}
		}
	};
	/*
	 * description: handles an area defined by two limiter-tokens
	 * charArray: complete content of the page as array of chars
	 * index: current position within charArray
	 * startToken: defined token, with which the area starts
	 * endToken: defined token, with which the area ends
	 * returns: object with parsed content
	 */
	this.parseSpecial = function (charArray, index, startToken, endToken) {
		// initialize comment container
		var definedAreaData = {type: startToken, startIndex: this.indexBeforeToken(startToken, index), closingType: endToken},
			token; // loop variables
		while (true) {
			token = this.extractToken(charArray, index);
			index = token.indexAfter;			
			if (token.type === endToken) {
				definedAreaData.endIndex = this.indexBeforeToken(token.type, index);
				definedAreaData.content = this.buildString(charArray, this.indexAfterToken(startToken, definedAreaData.startIndex), this.indexBeforeToken(token.type, token.indexAfter));
				// all fine return comment
				return definedAreaData;
			}
			else if (token.type === "") { // no closing token found means syntax fault
				throw (new JKeyException("ParserException", {errorPosition: definedAreaData.startIndex, contentBeforeErrorPosition: this.buildString(charArray, (definedAreaData.startIndex - 50), definedAreaData.startIndex), contentAfterErrorPosition: this.buildString(charArray, definedAreaData.startIndex, (definedAreaData.startIndex + 50)), expectedToken: endToken}));
			}
		}
	};
	/*
	 * description: skip functions: reduce token parser to a skip functionality, set index to end index
	 * Compare associate parse-functions for parameter details
	 */
	this.skipTpl = function (charArray, index) {
		return this.indexAfterToken("}}", this.parseTpl(charArray, index).endIndex);
	};
	this.skipLinkOrImg = function (charArray, index) {
		return this.indexAfterToken("]]", this.parseLinkOrImg(charArray, index).endIndex);
	};
	this.skipSpecial = function (charArray, index, startToken, endToken) {
		return this.indexAfterToken(endToken, this.parseSpecial(charArray, index, startToken, endToken).endIndex);
	};	
	/*
	 * description: jump before/after a token
	 * token: current token
	 * index: current position within charArray
	 * returns: index
	 */
	this.indexBeforeToken = function (token, index) {
		return (index - token.length);
	};	
	this.indexAfterToken = function (token, index) {
		return (token.length + index);
	};
}

//// JKey editor local ////

//jQueryLocalFileProxy.php
//<?php
//	// built up url with parameters
//	$fileName = $_GET['fileName'];
//	// print file
//	if (strlen($fileName) > 1) {
//		$file = fopen("local/".$fileName, "r") or exit("Unable to open file!");
//		while(!feof($file)) {
//			echo fgets($file);
//		}
//		fclose($file);
//	}
//?>
 
function localAjaxProxy(fileName) {
	var targetUrl = "/jQueryLocalFileProxy.php",
		parameters = {fileName: fileName},
		ajaxResponse;
	$j.ajax({
		type: "GET",
		async: false,
		url: targetUrl,
		data: parameters,
		success: function (content) {
			ajaxResponse = content;
		},
		error: function () {
			alert("Error from local proxy!");
		}
	});
	return ajaxResponse;
}

//// JKey editor sub ////

function jedtInitEditor() {
	var jKeyDiv = $j("div.decisiontree"), // exist valid keys
		apiResult, apiScript = wgServer + wgScriptPath + "/api.php";
	if (jKeyDiv.length > 0) {
		try { // avoid a fault of the loaded site
			if (!localTstMode) {
				apiResult = $j(jkeyAjax(apiScript, {action: "query", prop: "info|revisions", intoken: "edit", format: "xml", titles: wgPageName}, true, false, jkeyAjaxSuccess, jkeyAjaxError));
			}
		} catch (err) {}
		// initialize editor
		try { // avoid a fault of the loaded site
			// fill edit form variables
			jedtEditFormVars.wpSave = "";
			jedtEditFormVars.wpSection = "";
			jedtEditFormVars.wpSummary = jedtResource("edtWpSummary");
			if (apiResult && (apiResult.find("rev").length === 1)) { // add dynamic values
				jedtEditFormVars.wpEditToken = apiResult.find("page").attr("edittoken");
				jedtEditFormVars.wpEdittime = apiResult.find("rev").attr("timestamp").replace(/-|:|T|Z/g, "");
				jedtEditFormVars.wpStarttime = apiResult.find("page").attr("starttimestamp").replace(/-|:|T|Z/g, "");
			}
			// add editor container stuff (has to exists already in dom else wait symbol won't work)
			jKeyDiv.find("table.dt-body:last").before($j('<div class="jedt" />').append($j("<form/>")
					.append($j("<div/>").addClass("jedtWait").append(jedtImgResource("edtIconLoading")))
					.append($j("<div/>").addClass("jedtContent"))
					.append($j("<div/>").addClass("jedtControls")
							.append("<span class='jedtInstantSave' style='display:none;'>&nbsp; <input class='editbtn' type='button' style='font-size:1.25em;vertical-align:middle;' value='" + jedtResource("edtInstantSave") + "' onclick='jedtSave(this, true)' /></span>")
							.append("<span class='jedtSave' style='display:none;'>&nbsp; <input class='editbtn' type='button' style='font-size:1.25em;vertical-align:middle;' value='" + jedtResource("edtSave") + "' onclick='jedtSave(this, false)' /></span>")
							)
					.append($j("<br style=\"clear:both;\"/>").addClass("jedtBreak"))
			));
			// add editor styles
			$j("head").append("<style type=\"text/css\">" + 
				"fieldset.jedtFieldset {margin: 0;} \n" + 
				"td.actionRemove, td.actionAddNew, td.actionMove, td.actionEdit {cursor: pointer;} \n" + 
				"td.actionCell {vertical-align: top; width: 1%;} \n" + 
				"td.actionCell table td {padding-right: 0.3em;} \n" + 
				"td.actionContent {width: 99%;} \n" + 
				"div.jedtWait {display: none; text-align: center; margin-top: 1em;} \n" + 
				"div.jedtWait img {height: 40px; width: 40px;} \n" + 
				"div.jedtContent, br.jedtBreak {display: none;} \n" + 
				"div.jedtControls {display: none; float: right; padding-top: 0.4em} \n" + 
				"table.unknownFields tr td {padding-right: 0.2em;} \n" + 
			"</style>");
		} catch (err2) {}
	}
}

function jedtInitGlobalVars() {
	// get raw data of current page
	if (!jedtPageCharArray) {
		jedtPageCharArray = (localTstMode) ? 
			localAjaxProxy("rawTemplateDecisionHorizontalDoc.tpl").split('') : 
			jkeyAjax(wgScript, {title: wgPageName, action: "raw", random: $j.random(0, 10000)}, true, false, jkeyAjaxSuccess, jkeyAjaxError).split('');
	}
	// parse page data
	if (!jedtParsedPageData) {
		// initialize parser
		if (!jkeyParser) { // lazy loading
			jkeyParser = new JKeyPageParser();
		}
		// parse raw data of current page
		jedtParsedPageData = jkeyParser.initialize(jedtPageCharArray);
	}
	// retrieve all editor tpls
	if (localTstMode) {
		if (!jedtHtmlEditTpls.Lead) { // only "Lead" for testing
			jedtHtmlEditTpls.Lead = $j(localAjaxProxy("rawLeadHtmlEditorTpl.htm"));
		}
	} else {
		if (jedtBase_CountKeys(jedtHtmlEditTpls) === 0) { // nothing loaded, yet
			for (var currTplName in jedtParsedPageData.tplNames) {
				if (jedtParsedPageData.tplNames[currTplName] && (typeof(currTplName) === 'string') && (!jedtHtmlEditTpls[currTplName])) {
					try {
						jedtHtmlEditTpls[currTplName] = $j(jkeyAjax(wgScript, {title: "MediaWiki:" + currTplName.replace(' ', '_') + "/HtmlEditorTemplate", action: "raw", random: $j.random(0, 10000)}, true, false, jkeyAjaxSuccess, jkeyAjaxError));
					} catch (err) {
						if (err.variables.status !== 404) { // only "page not found" will be accepted
							throw (err); // otherwise exception will be forwarded
						}
					}
				}
			}
		}
	}
}

function jedtChangeCtrls(toggleMode, jKeyOrDoc) {
	switch (toggleMode) {
	case ("hideEditBtn"):
		jKeyOrDoc.find("div.jkeyLCtrls input.editbtn").hide();
		break;
	case ("showEditControls"):
		jKeyOrDoc.find("span.jedtSave").show();
		jKeyOrDoc.find("span.jedtInstantSave").show();
		break;
	case ("disableInstantSave"):
		jKeyOrDoc.find("span.jedtInstantSave input").attr("disabled", "disabled");
		break;
	case ("enableInstantSave"):
		jKeyOrDoc.find("span.jedtInstantSave input").removeAttr("disabled");
		break;
	case ("finalSave"):
		jKeyOrDoc.find("div.jkeyLCtrls input.editbtn").hide();
		jKeyOrDoc.find("span.jedtInstantSave input, span.jedtSave input").attr("disabled", "disabled");
		break;
	case ("rollback"):
		jKeyOrDoc.find("div.jkeyLCtrls input.editbtn").remove();
		break;
	default:
		throw (new JKeyException("NotImplementedException", {info: jedtResource("edtNiExNfoA") + toggleMode + jedtResource("edtNiExNfoB")}));
	}
}

//// JKey editor main ////

function jedtSave(caller, instantSave) {
	try {
		var jKeyDiv = $j(caller).closest("div.decisiontree"),
			jedtDiv = jKeyDiv.find("div.jedt"), doc;
		// check edit initialized
		if (!$j(jedtDiv.find("table.dt-body").get(0).rows).filter(".edtCouplet").length) {
			alert(jedtResource("edtSavingFault")); // show msg
			return false; // avoid href call
		}
		// show wait while instant save
		jedtDiv.find("div.jedtWait").show();
		jkeyScrollToElement(jKeyDiv.get(0));
		// serialize & validate inputs
		try {
			jedtEditFormVars.wpTextbox1 = jedtSerialize();
		} catch (err) {
			jedtDiv.find("div.jedtWait").hide();
			alert(err); // show msg
			return false; // avoid href call
		}		
		// handle save mode
		if (instantSave) {
			jedtChangeCtrls("disableInstantSave", jKeyDiv);
			// collect parameters
			if (!localTstMode) { // do post
				jkeyAjax(wgScript + "?title=" + wgPageName + "&action=submit", jedtEditFormVars, false, true, function () {
					jedtChangeCtrls("enableInstantSave", jKeyDiv);
					jedtDiv.find("div.jedtWait").hide();
				}, jkeyAjaxError);
			}
		}
		else {
			// hide all editor & player links on the page
			doc = $j(); // set to whole document
			jedtChangeCtrls("finalSave", doc);
			doc.find("td.jkeyControls").hide();
			// delete whole editorDiv for start (also jedtContent will be deleted)
			doc.find("div.jedt").remove();
			// collect parameters
			doc.find("div.dt-header").append($j('<div/>').addClass("jkeyResultMsg").html(jedtResource("edtSaving")));
			if (!localTstMode) { // do post
				jkeyAjax(wgScript + "?title=" + wgPageName + "&action=submit", jedtEditFormVars, false, true, function () {
					doc.find("div.jedtWait").hide();
					location.reload(true);			
				}, jkeyAjaxError);
			}
		}
	} catch (err2) {
		jkeyExceptionAlert(err2);
	}
	return false; // avoid href call
}

function jedtInitKey(caller) {
	var jKeyDiv = $j(caller).closest("div.decisiontree"),
		jedtDiv = jKeyDiv.find("div.jedt"),
		jKeyTable = jKeyDiv.find("table.dt-body:last"); // :last - player also can contain one
	try {
		// scroll to begin
		jkeyScrollToElement(jKeyDiv.get(0));
		// has user edit rights
		if (!localTstMode && (!jedtEditFormVars.wpEditToken || (jedtEditFormVars.wpEditToken.length === 0))) {
			jKeyDiv.find("div.jkeyLCtrls input.editbtn").remove();
			alert(jedtResource("edtRightsFault"));
			return false;
		}
		// hide table
		jKeyTable.hide();
		// disable all player links
		jKeyDiv.find("td.jkeyControls").hide();
		// change editor links
		jedtChangeCtrls("hideEditBtn", jKeyDiv); // because following preloading & preparing needs time
		// show wait & break
		jedtDiv.find("div.jedtWait").show();
		jedtDiv.find("br.jedtBreak").show();
		// initialize global & couplet memory for this key
		jedtInitGlobalVars();
		jedtParsedData_InitCoupletMemoryForKey(jKeyDiv.get(0));
		// move key table into editor
		jKeyTable.css("width", "100%");
		jedtDiv.find("div.jedtContent").append(jKeyTable);
		// initialize edit action images
		jKeyTable.find("tr:first").nextAll("tr").andSelf().each(function () {
			var jThis = $j(this),
				rowHasID = (jThis.find("td:first").attr("id").length);
			jThis.find("td:first").before($j("<td " + (!rowHasID ? "" : "class='actionCell'") + "/>").append(!rowHasID ? "" : jedtActionCell_CreatePrimary(false)));						
		});
		// handle jedt visibilities
		jKeyTable.show();
		jedtDiv.find("div.jedtWait").hide();
		jedtDiv.find("div.jedtContent").show();
		jedtDiv.find("div.jedtControls").show();
		jedtChangeCtrls("showEditControls", jKeyDiv);
		// scroll to begin
		jkeyScrollToElement(jKeyDiv.get(0));
	} catch (err) {
		// rollback player
		jKeyDiv.append(jKeyTable.show());
		jKeyDiv.find("td.jkeyControls").show();
		jKeyDiv.find("div.jkeyPlayer").remove();
		// remove edt
		jedtChangeCtrls("rollback", $j());
		jedtDiv.remove();
		// throw fault
		jkeyExceptionAlert(err);
	}
	return false; // avoid href call
}

/*
 * description: constructor, which is called after the html document was loaded
 */
$j(document).ready(function () {
	jedtImgPreloader();
	if (document.URL.toString().search("LocalTestFile.htm") !== -1) {
		localTstMode = true;
	}
	if (wgUserName !== null) {
		setTimeout(jedtInitEditor, 10); // start after page was loaded
	}
});