Ext.define("Portfolio.util.Handler", {

	singleton: true,

	alternateClassName: [
		"ip2",
		"sp",
		"pf"
	],

	requires: [
		"Portfolio.state.global.Provider",
		"Portfolio.state.profile.Provider"
	],

	uses: [
		"Portfolio.Defaults.uirules",
		"Portfolio.Defaults.uitype"
	],

	config: {
		sajax: null,
		dmd: null,
		resource: null,
		util: null,
		session: null,
		profile: null,
		global: null,
		logoutFunctions: [],
		currentUser: null,
		access: null,
		preset: null,
		navigator: null,
		declared: {},
		uniqueNo: 1,
		user: null,
		workers: []
	},

	constructor: function(o_config) {

		var i_self = this;

		i_self.initConfig(o_config);

		i_self.initDynamicNs();
		i_self.initConnections();
		i_self.initDMD();
		i_self.initResources();
		i_self.initSession();
		i_self.initUtil();
		i_self.initXpd();
		i_self.initJSON();
		i_self.initNavigator();
		i_self.initDate();
		i_self.initQueryString();

		i_self.initComet();

		Ext.apply(i_self, {
			getUiTypes: Ext.Function.alias(i_self, "loadUItypes"),
			getUitypes: Ext.Function.alias(i_self, "loadUItypes"),
			getModel: Ext.Function.alias(i_self, "model"),
			extend: Ext.Function.alias(Ext.Object, "merge"),
			indexOf: Ext.Function.alias(Ext.Array, "indexOf"),
			App: new Ext.util.Observable({}),
			loadWidget: Ext.Function.alias(i_self, "getEvaled"),
		});

		Ext.apply(i_self.isEvalTpl, {
			NO: 0,
			MACRO: 1,
			REGULAR: 2
		});

		return i_self;
	},

	handleAction: function(o_data, o_config) {

		var i_view = o_config.view;
		var i_record = o_config.node;

		var i_panel = i_view.up("panel");
		var b_system = i_panel.hasListener("startapplication");

		while (!b_system && i_panel) {
			i_panel = i_panel.up("panel");
			if (i_panel) {
				b_system = i_panel.hasListener("startapplication");
			}
		}

		if (i_panel) {
			i_panel.fireEvent("startapplication", i_panel, i_record);
		}
	},

	declare: function(s_path, o_config) {

		var i_self = this;
		var o_declared = i_self.getDeclared();

		if (s_path) {

			o_declared[s_path] = o_config;

			var a_requires = o_config.requires || o_config.require || [];

			if (Ext.isString(a_requires)) {
				a_requires = Ext.Array.from(a_requires);
			}

			if (Ext.isArray(a_requires)) {

				var a_external = Ext.Array.filter(Ext.Array.map(a_requires, function(s_value) {
					if (s_value.indexOf("/") > -1) {
						return s_value;
					}
				}), function(value) {
					return value;
				});

				var a_internal = Ext.Array.filter(Ext.Array.map(a_requires, function(s_value) {
					if (s_value.indexOf("/") < 0) {
						return s_value;
					}
				}), function(value) {
					return value;
				});

				if (a_external.length > 0) {
					Ext.Loader.loadScriptsSync(a_external);
				}

				if (a_internal.length > 0) {
					Ext.syncRequire(a_internal);
				}
			}

			i_self.setDeclared(o_declared);
		}

		return o_config;
	},

	getEvaled: function(o_config) {

		if (Ext.isString(o_config)) {
			o_config = {
				url: o_config
			};
		}

		var o_result = null;

		var i_entry = Ext.Boot.getEntry(o_config.url);

		i_entry.fetch({
			async: false,
			complete: function(i_response) {

				if (i_response.status == 200) {

					var s_content = i_response.content;
					var s_class = o_config.class || "";

					s_content.trim();

					if (/(ip2|sp)\.declare\(("|')[\w*.]*("|')\s*,/g.test(s_content)) {

						var a_split = s_content.match(/("|')[\w*.]*("|')/gm)[0].split(/("|')/g);

						Ext.Array.each(a_split, function(s_value) {
							if (!Ext.isEmpty(s_value)) {
								if (s_value != "\"" && s_value != "'") {
									s_class = s_value;
									return false;
								}
							}
						});
					}
					else {

						if (Ext.String.endsWith(s_content, "\n")) {
							s_content = s_content.substring(0, s_content.length - 1);
						}

						if (Ext.String.endsWith(s_content, "\r")) {
							s_content = s_content.substring(0, s_content.length - 1);
						}

						if (Ext.String.endsWith(s_content, ";")) {

							s_content = s_content.substring(0, s_content.length - 1);
						}

						if (Ext.String.startsWith(s_content, "{") && Ext.String.endsWith(s_content, "}")) {

							s_class = s_class || i_entry.url.replace(/\//g, ".");

							i_response.content = s_content = "ip2.declare(\"".concat(s_class + "\", ").concat(s_content).concat(");");
						}

						if (Ext.String.startsWith(s_content, "[") && Ext.String.endsWith(s_content, "]")) {

							s_class = s_class || i_entry.url.replace(/\//g, ".");

							i_response.content = s_content = "ip2.declare(\"".concat(s_class + "\", ").concat(s_content).concat(");");
						}
					}

					i_entry.onContentLoaded(i_response);
					i_entry.evaluateContent();

					if (s_class) {
						o_result = ip2.getDeclared()[s_class];
					}
				}
				else if (i_response.status == 404) {
					o_result = false;
				}
			}
		});

		return o_result;
	},

	callWidget: function(o_attr) {

		var i_self = this;

		var i_componet = null;

		o_attr = o_attr || Ext.apply({}, {
			url: "",
			text: "",
			title: "",
			component: {},
			urlParams: {}
		});

		var o_options = o_options || Ext.apply({}, {
			node: {},
			view: {}
		});

		if (o_attr.url || o_attr.src) {

			var s_url = o_attr.src || o_attr.url || "";

			o_attr.urlParams = o_attr.urlParams || {};

			o_config = i_self.getEvaled(s_url);

			if (o_config) {

				if (s_url.indexOf("?") > -1) {
					var s_params = s_url.split("?").pop();
					o_attr.urlParams = Ext.Object.fromQueryString(s_params);
				}

				var a_requires = o_config.requires || o_config.require || [];

				if (Ext.isArray(a_requires)) {

					var a_external = Ext.Array.filter(Ext.Array.map(a_requires, function(s_value) {
						if (s_value.indexOf("/") > -1) {
							return s_value;
						}
					}), function(value) {
						return value;
					});

					var a_internal = Ext.Array.filter(Ext.Array.map(a_requires, function(s_value) {
						if (s_value.indexOf("/") < 0) {
							return s_value;
						}
					}), function(value) {
						return value;
					});

					if (a_external.length > 0) {
						Ext.Loader.loadScriptsSync(a_external);
					}

					if (a_internal.length > 0) {
						Ext.syncRequire(a_internal);
					}
				}

				i_componet = i_self.addDynComponent(o_config, o_attr);
			}
			else {

				var i_template = Ext.create("Ext.XTemplate",
					"{description} ",
					"{path}"
				);

				i_template.compile();

				var i_error = new Error();

				Ext.apply(i_error, {
					name: "callWidget",
					message: i_template.apply({
						path: o_attr.url,
						description: ip2.i18n(o_attr.title) + " - file missing"
					})
				});

				throw i_error;
			}
		}

		return i_componet;
	},

	addDynComponent: function (o_dynComponent, o_attributes) {

		var i_self = this;

		var i_componet;
		var i_result;

		o_dynComponent.attr = o_attributes;

		o_dynComponent.urlParams = o_attributes.urlParams;

		delete o_dynComponent.id;

		var f_initialize = o_dynComponent.onInitialize || Ext.emptyFn;

		if (o_dynComponent.hasOwnProperty("xtype")) {

			if (o_dynComponent.xtype === "container") {
				o_dynComponent.xtype = "panel";
			}

			if (!o_dynComponent.hasOwnProperty("layout")) {
				o_dynComponent.layout = "fit";
			}

			f_initComponent = o_dynComponent.initComponent || Ext.emptyFn;

			if (Ext.emptyFn != f_initComponent) {

				o_dynComponent.callInit = function() {

					var i_self = this;
					i_self.superclass.initComponent.call(i_self);
				};
			}

			i_componet = Ext.widget(o_dynComponent.xtype, o_dynComponent);

			if (o_dynComponent.config) {

				var i_instance = i_componet.self;
				var i_configurator = i_instance.getConfigurator();

				i_instance.addConfig(o_dynComponent.config, null);

				var f_beforeConfig = i_componet.beforeInitConfig;

				i_componet.beforeInitConfig = null;

				i_configurator.configure(i_componet);

				i_componet.beforeInitConfig = f_beforeConfig;
			}
		}

		try {

			if (i_componet) {

				i_result = f_initialize.call(i_componet, i_componet, o_attributes);
			}
			else {
				i_result = f_initialize.call(o_dynComponent, o_dynComponent, o_attributes);
			}

			if (i_result) {
				if (!o_dynComponent.xtype) {
					i_componet = i_result;
				}
				else {
					i_componet.add(i_result);
				}
			}
			else {
				if (!o_dynComponent.xtype) {
					i_componet = null;
				}
			}
		}
		finally {}

		return i_componet;
	},

	addChild: function(i_owner, i_record, s_action, o_config) {

		var i_self = this;

		var o_keys = {};

		o_keys[i_record.idProperty] = i_record.get(i_record.idProperty);

		Ext.apply(o_config, {
			keys: o_keys,
			action: s_action || "update",
			entity: o_config.entity || o_config.entityName,
			languages: wsh.getLanguages()
		});

		delete o_config.entityName;

		var i_entityForm = Ext.widget("entityform", o_config);

		i_owner.add(i_entityForm);

		return i_entityForm;
	},

	addTab: function(o_config) {

		var i_self = this;
		Portfolio.app.fireEvent("startapplication", o_config);
	},

	addChildPanel: function (i_owner, i_model, i_record, store, s_action, o_config) {

		s_action = s_action || "update";
		var title = ip2.translateTitle(i_model.title || "", i_owner.i_model);

		Portfolio.app.fireEvent("startapplication", {
			title: title,
			dmd: o_config.dmd,
			entity: o_config.entityName,
			view: o_config.uiName || "portfolio",
			keys: ip2.extractKeys(i_model, i_record),
			action: s_action,
			defaultCollection: i_owner.defaultCollection || "",
			transactiontype: i_owner.transactiontype || o_config.entityName,
			activeTabIndex: o_config.activeTabIndex || 0,
			oldData: i_record.getData()
		});
	},

	namespace: Ext.emptyFn,

	i18n: function(value) {

		var i_self = this;
		var s_value = "";

		var i_user = i_self.getUser();

		var s_language = i_user ? i_user.get("USRLNG") : "en";

		if (Ext.isObject(value)) {
			s_value = value[s_language];
		}

		if (Ext.String.startsWith(value, "{") && Ext.String.endsWith(value, "}")) {
			var o_value = Ext.decode(value, true) || {};
			s_value = o_value[s_language];
		}

		if (!s_value && ip2.I18N) {
			s_value = ip2.I18N[value] || value;
		}

		if (!s_value) {
			s_value = value;
		}
		if (Ext.isObject(s_value)) {
			s_value = value.en;
		}

		if (!s_value && Ext.isObject(value)) {
			s_value = value.da;
		}

		s_value = s_value || "";

		return s_value;
	},

	i18nWithLanguage: function (value, s_language) {

		var i_self = this;
		var s_value = "";

		var i_user = i_self.getUser();

		s_language = s_language || i_user ? i_user.get("USRLNG") : "en";

		if (Ext.isObject(value)) {
			s_value = value[s_language];
		}

		if (Ext.String.startsWith(value, "{") && Ext.String.endsWith(value, "}")) {
			var o_value = Ext.decode(value, true) || {};
			s_value = o_value[s_language];
		}

		if (!s_value && ip2.I18N) {
			s_value = ip2.I18N[value] || value;
		}

		if (!s_value) {
			s_value = value;
		}
		if (Ext.isObject(s_value)) {
			s_value = value.en;
		}

		if (!s_value && Ext.isObject(value)) {
			s_value = value.da;
		}

		if (!s_value) {
			s_value = value;
		}

		return s_value;
	},

	initConnections: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			request: function(o_params) {

				if (o_params.method != "POST") {
					o_params.method = "GET";
				}
				if (o_params.payload === undefined) {
					o_params.payload = null;
				}

				var o_result;

				var f_callback = o_params.callback || function(i_response) {

					if (o_params.asJson) {

						Ext.USE_NATIVE_JSON = false;

						try {
							o_result = Ext.decode(i_response.responseText, true);

							o_result = o_result || o_params.defaultValue || "";
						}
						catch (o_error) {
							o_result = {};
						}
						finally {
							//Ext.USE_NATIVE_JSON = true;
						}
					}
					else {
						o_result = i_response.responseText || o_params.defaultValue || "";
					}
				};

				Ext.Ajax.request({
					url: o_params.url,
					method: o_params.method,
					success: f_callback,
					jsonData: o_params.payload,
					async: false,
					failure: function(o_error) {
						o_result = o_error.message;
					}
				});

				return o_result;
			}
		});

		Ext.apply(i_self, {
			sajax: o_config
		});

		return o_config;
	},

	initProfile: function() {

		var i_self = this;

		if (!i_self.getProfile()) {
			var i_stateManager = Ext.widget("portfolio-state-profile-provider", {
				cache: {}
			});

			Ext.apply(i_self, {
				profile: i_stateManager
			});

			i_self.setProfile(i_stateManager);
		}
	},

	initGlobal: function() {

		var i_self = this;

		if (!i_self.getGlobal()) {

			var i_stateManager = Ext.widget("portfolio-state-global-provider", {
				cache: {}
			});

			Ext.apply(i_self, {
				global: i_stateManager
			});

			i_self.setGlobal(i_stateManager);
		}
	},

	initUtil: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			renderer: {
				countryFlags: function (value, n_height, n_width) {

					if (!Ext.isNumber(n_height) || isNaN(n_height)) {
						n_height = 10;
					}

					if (!Ext.isNumber(n_width) || isNaN(n_width)) {
						n_width = Math.ceil(n_height * 1.6);

						// Odd widths makes for ugly flags
						if (n_width % 2) {
							n_width--;
						}
					}

					// Value can be either a comma seperated string - or stringed array
					var a_value = [];

					if (Ext.isString(value)) {
						a_value = Ext.decode(value, true) || null;
					}
					else if (Ext.isObject(value)) {

						var o_value = Ext.apply({}, value);

						Ext.Object.each(o_value, function (s_key, b_active) {
							if (b_active) {
								a_value.push(s_key.toLowerCase());
							}
						});
					}

					if (!a_value) {
						try {
							a_value = value.split(',');
						}
						catch (e) {
							a_value = [];
						}
					}

					var s_flags = "";

					var i_template = Ext.create("Ext.XTemplate",
						"<div ",
							"style=\"",
								"height:{height}px; ",
								"width:{width}px; ",
								"display:inline-block; ",
								"margin:0px 2px 0px 0px; ",
								"padding:0px;\" ",
							"class=\"",
								"AIL-icon AIL-icon-{width} icon-flag_{language}\">",
						"</div>"
					);

					i_template.compile();

					var a_temp = [];

					Ext.Array.each(a_value, function (s_lang) {
						var s_flag = i_template.apply({
							height: n_height,
							width: n_width,
							language: s_lang
						});

						a_temp.push(s_flag);
					});

					return a_temp.join(" ");
				}
			}
		});

		Ext.apply(i_self, {
			util: o_config
		});

		return o_config;
	},

	initDMD: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			getInfo: function (o_config) {

				var a_id = [];

				var o_gotten = {
					dmd: null,
					entity: null,
					view: null,
					id: null
				};

				var o_dmd;
				var o_entity;

				o_config.context = o_config.context || "grid";
				o_config.view = o_config.view || "portfolio";

				if (o_config.dmd) {
					var s_path = "dmd/" + o_config.dmd.split("/").pop();

					o_dmd = ip2.resource.get(s_path);
					o_gotten.dmd = o_dmd;
					a_id.push(s_path);

					if (o_config.context && o_config.view) {
						o_gotten.proxy = o_dmd.proxy + "?model=" + [
							"dmdFile=" + s_path,
							"context=" + o_config.context,
							"uiName=" + o_config.view
						].join("&");
					}
				}

				if (o_dmd && o_config.entity) {

					o_entity = Ext.Array.findBy(o_dmd.model.entities, function(o_value) {
						return o_value.name == o_config.entity;
					});

					o_gotten.entity = o_entity;
					a_id.push(o_config.entity);
				}

				if (o_entity && o_config.context && o_config.view) {
					if (o_entity.xpdColumns) {
						ip2.buildXpdColumns(o_dmd, o_entity);
					}
					var s_context = o_config.context == "grid" ? "grids" : "forms";
					var o_view;
					if (o_entity.ui[s_context][o_config.view]) {
						o_view = o_entity.ui[s_context][o_config.view];
					}
					else {

						var a_test = ["portfolio", "common"];

						Ext.Array.each(a_test, function(s_key) {

							var o_test = o_entity.ui[s_context][s_key];

							if (o_test) {
								o_view = o_test;
								o_config.view = s_key;
								return false;
							}
						});
					}
					// Overloading the move of entity.ui.features -> entity.uiFeatures
					var o_features = o_entity.uiFeatures || o_entity.ui.features || {};

					if (!o_view) {

						o_view = Ext.apply({}, {
							columns: [],
							items: {
								columns: []
							},
							groups: {},
							relation: [],
							affiliations: [],
							widgets: {}
						});

						o_entity.ui[s_context][o_config.view] = o_view;
					}

					o_view.features = o_view.features || {};

					Ext.Object.each(o_features, function (s_key, o_feature) {
						if (o_view.features[s_key]) {
							o_view.features[s_key] = Ext.apply(o_feature, o_view.features[s_key]);
						}
						else {
							o_view.features[s_key] = Ext.clone(o_feature);
						}
					});

					o_gotten.view = o_view;
					a_id.push(o_config.context);
					a_id.push(o_config.view);
				}

				var s_id = a_id.join(":");

				if (s_id) {
					o_gotten.id = s_id;
				}

				return o_gotten;
			},
			getModelName: function (o_config) {

				// Generate the model name from a config object
				// using its [dmd, entity, context, view] properties
				// Example: webshop.WSHPAGET00_GRID_PORTFOLIO

				var s_entity = o_config.entity;
				var s_context = o_config.context;
				var s_view = o_config.view;

				var s_name = [s_entity, s_context, s_view].join("_");

				return s_name;
			},
			getView: function (o_config, o_dmd) {

				o_dmd = o_dmd || ip2.getDmd(o_config);

				// Context overload map
				var o_overload = {
					forms: "forms",
					form: "forms",
					grids: "grids",
					grid: "grids"
				};

				var o_entity = Ext.Array.findBy(o_dmd.model.entities, function(o_value) {
					return o_value.name == o_config.entity;
				});

				var s_context = o_overload[o_config.context];

				var o_context = o_entity.ui[s_context];
				var o_view = o_context[o_config.view] || o_context.portfolio;

				return o_view;
			},
			getXpdDefinitions: function (o_field, o_fields, o_entity) {

				var s_splitter = "_";
				var s_name = o_field.name;
				var a_name = s_name.split(s_splitter);

				// If we can not even split on the delimiter then return
				if (a_name.length == 1) {
					return;
				}

				var s_xpdField = a_name.shift();
				var s_prop = a_name.join(s_splitter);
				var s_xpdFile;

				// Passed definition already has a XPD attached
				if (o_field && o_field.xpd) {
					s_xpdFile = o_field.xpd;
				}

				// Has not - look in entity field definition for it
				else {
					var o_xpdField = o_entity.columns[s_xpdField];
					s_xpdFile = o_xpdField && o_xpdField.xpd ? o_xpdField.xpd : null;
				}

				// Make sure we get an extension on the xpd filename in case none is present
				if (s_xpdFile && s_xpdFile.split(".").length == 1) {
					s_xpdFile += ".xpd";
				}

				// Check if this gave us a XPD to use
				var o_xpd = s_xpdFile ? ip2.resource.get(s_xpdFile) : null;
				if (!o_xpd) {
					return;
				}

				var o_properties = o_xpd.properties || {};

				// Overload - somewhere before this all names are uppercased
				// so CURPROP_title becomes CURPROP_TITLE - this means
				// that camelcased XPD properties are not supported!
				// HACK: Lowercasing s_prop and matching against lowercased properties
				var o_definition = (function () {
					var o_return;
					Ext.Object.each(o_properties, function (s_key, o_prop) {
						if (s_key.toLowerCase() == s_prop.toLowerCase()) {
							// Use s_key as s_prop now, since it is the real prop key
							s_prop = s_key;
							o_return = o_prop;
						}
					});
					return o_return;
				})();

				if (!o_definition) {
					return;
				}

				// We may have stuff we need translated
				Ext.Object.each(o_definition, function (s_key, value) {
					if (Ext.isString(value)) {
						o_definition[s_key] = ip2.xpd.translate(o_xpd, value);
					}
				});

				return o_definition;
			},
			createModelFields: function (o_config, o_dmd) {

				o_dmd = o_dmd || ip2.getDmd(o_config);

				var o_entity = Ext.Array.findBy(o_dmd.model.entities, function(o_value) {
					return o_value.name == o_config.entity;
				});

				var o_view = ip2.dmd.getView(o_config, o_dmd);

				// Get a clone of the entity columns (the fields to be)

				var o_fields = {};
				var o_entityFields = {};

				if (Ext.isArray(o_entity.columns)) {
					Ext.Array.each(o_entity.columns, function(o_column) {
						o_entityFields[o_column.name] = Ext.apply({}, o_column);
					});
				}
				else {
					Ext.Object.each(o_entity.columns, function(s_key, o_value) {
						o_entityFields[s_key] = Ext.apply({}, o_value);
					});
				}

				// Merge the view columns on to the entity columns
				Ext.Array.each(o_view.columns, function (o_viewColumn) {

					var s_name = o_viewColumn.name;
					var o_field = o_entityFields[s_name] || {};
					var o_fieldColumn = Ext.Object.merge({}, o_field, o_viewColumn);

					o_fields[s_name] = o_fieldColumn;
				});

				// Create the fields array and process the field definitions
				var a_fields = [];

				Ext.Object.each(o_fields, function (s_key, o_field) {

					var o_clone = Ext.apply({}, o_field);
					o_clone.name = o_clone.name || s_key;

					// This field could be an XPD property field - so we
					// look for possible XPD definitions to apply
					var o_xpdDefinition = ip2.dmd.getXpdDefinitions(o_clone, o_fields, o_entity);

					if (o_xpdDefinition) {
						ip2.extend(o_clone, o_xpdDefinition);

						// Some overloading/sanitizing
						o_field.type = o_field.dbType; // in XPD type has another meaning
					}

					// Process the properties in this field definition so far
					// s_type = "dec(9, 2)" -> ["dec", "9", "2"];
					var a_type = ip2.getFieldTypeDefinition(o_clone.type || "");

					// [type, length, decPoint] = {type: type, length: length, decPoint: decPoint}
					Ext.apply(o_clone, {
						type: a_type[0],
						length: a_type[1] || o_field.length,
						decPoint: a_type[2] || o_field.decPoint
					});

					a_fields.push(o_clone);
				});

				return a_fields;
			},
			createExtraParams: function (o_config) {

				var o_extra = o_config.extraParams || {};

				var o_params = Ext.Object.merge({
					dmd: o_config.dmd || o_config.dmdFile, // Backwards compatibility
					entity: o_config.entity,
					context: o_config.context,
					uiName: o_config.view || o_config.uiName, // Backwards compatibility
					fullresponse: o_config.fullResponse || true,
					meta: o_config.meta || false,
					union: o_config.union,
					key: o_config.key,
					sort: o_config.sort
				}, o_extra);

				Ext.Object.each(o_params, function (s_key, value) {
					if (Ext.isEmpty(value)) {
						delete o_params[s_key];
					}
				});

				return o_params;
			},
			unionize: function (s_key, value) {

				var a_value = Ext.Array.from(value);
				var b_union = true;
				var a_union;

				if (!a_value.length) {
					b_union = false;
				}
				else {

					// Value can be a record - normalize
					Ext.Array.each(a_value, function (value, n_index, a_context) {
						if (ip2.isObject(value)) {
							a_context[n_index] = value.get(s_key);
						}
					});

					Ext.Array.some(a_value, function (val) {
						if (!val) {
							b_union = false;
							return true;
						}
					});
				}

				if (b_union) {
					a_union = [
						{
							property: s_key,
							value: a_value.join(", ")
						}
					];
				}

				return b_union ? a_union : [];
			},
			grid: {
				getItem: function (s_dmd, s_entity, s_view, s_name) {

					var o_dmd = ip2.resource.get(s_dmd);
					var o_entity = Ext.Array.findBy(o_dmd.model.entities, function(o_value) {
						return o_value.name == s_entity;
					});
					var o_view = o_entity.ui.grids[s_view];
					var o_definition;
					var o_field;

					if (Ext.isArray(o_entity.columns)) {
						Ext.Array.each(o_entity.columns, function(o_column) {
							if (o_column.name == s_name) {
								o_definition = Ext.clone(o_column);
								return false;
							}
						});
					}
					else if (Ext.isObject(o_entity.columns)) {
						o_definition = Ext.clone(o_entity.columns[s_name]);
					}

					Ext.Array.some(o_view.columns, function (o_column) {
						if (o_column.name == s_name) {
							o_field = o_column;
							return true;
						}
					});

					// Extend the defintion on to column using defaults
					ip2.defaults(o_field, o_definition);

					// Now sanitize a little

					// s_type = "dec(9, 2)" -> ["dec", "9", "2"];
					var a_type = ip2.getFieldTypeDefinition(o_field.type || "");

					// [type, length, decPoint] = {type: type, length: length, decPoint: decPoint}
					ip2.extend(o_field, {
						type: a_type[0],
						length: a_type[1],
						decPoint: a_type[2]
					});

					// Check if this field is an XPD field property
					// and extend the definition
					var o_xpdInfo = ip2.getFieldXpdInfo(s_name, o_entity, o_field);

					if (o_xpdInfo) {
						ip2.applyFieldXpdInfo(o_field, o_xpdInfo);
					}

					// Remove all undefined values, and translate some (again a hack for now!)
					Ext.Object.each(o_field, function (s_key, val) {
						if (val === null) {
							delete o_field[s_key];
						}
					});

					// Sanitize the title/header
					if (o_field.title && !o_field.header) {
						o_field.header = o_field.title;
					}
					delete o_field.title;
					o_field.header = ip2.i18n(o_field.header);

					// If we have a uitype we need to further the extension
					var s_uiType = o_field.uiType || o_field.uitype || "default";
					var uiTypes = ip2.getUiTypes();
					var o_uiType = uiTypes[s_uiType] || {};
					o_uiType = o_uiType ? o_uiType.grid : {};

					ip2.defaults(o_field, o_uiType);

					return o_field;
				},
				getItems: function (s_dmd, s_entity, s_view) {

					var o_dmd = ip2.resource.get(s_dmd);
					var o_entity = Ext.Array.findBy(o_dmd.model.entities, function(o_value) {
						return o_value.name == s_entity;
					});
					var o_view = o_entity.ui.grids[s_view];
					var a_columns = [];

					Ext.Object.each(o_view.columns, function (s_key, o_col) {
						var o_column = i_self.dmd.grid.getItem(s_dmd, s_entity, s_view, o_col.name);
						a_columns.push(o_column);
					});

					return a_columns;
				}
			}
		});

		Ext.apply(i_self, {
			dmd: o_config
		});

		return o_config;
	},

	initResources: function() {

		var i_self = this;

		var i_worker = i_self.createWorker();

		i_self.removeWorker(i_worker);

		var o_config = Ext.apply({}, {
			get: function(s_name, b_manipulated, a_manipulateArgs, save_cache, b_reload) {

				var i_resources = this;

				var s_extension = s_name.split(".").pop();
				var filePath = i_self.splitFileAndPath(s_name);
				var fileName = filePath.file.split(".").shift();
				var fullPath = i_self.resource.getFullPath(s_name);
				var o_baseConfig = i_self.resource.getBaseConfig(s_extension) || {};

				var s_cache = s_extension;

				b_reload = b_reload || false;

				var o_cache = o_baseConfig[s_cache];
				o_cache = o_cache || {};

				var o_saved = o_cache[fileName];
				var o_result = o_saved;
				var b_cache = false;

				if (!o_saved || b_reload) {

					b_cache = true;

					var s_url = "";

					if (s_extension == "dmd" || s_extension == "xpd" || s_extension == "ns") {

						var s_file = s_name;
						if (!/^(dmd|ns|xpd)\/.*/.test(s_name)) {
							s_file = [s_extension, s_name].join("/");

						}
						s_url = "/ip2-services/ip2dmdproxy.aspx?function=getdmd&dmd=" + s_file;

					}
					else {
						s_url = "/ip2-services/webBios.aspx?func=readstmf&file=" + i_resources.getFullPath(s_name);
					}

					Ext.Ajax.request({
						url: s_url,
						async: false,
						method: "GET",
						success: function(i_response) {
							Ext.USE_NATIVE_JSON = false;
							o_result = Ext.decode(i_response.responseText, true) || {}
						},
						failure: function(o_error) {
							o_result = o_error.message;
						}
					});
				}

				if (b_manipulated && o_result) {

					s_cache = s_extension + "_m";

					o_cache = o_baseConfig[s_cache] || {};

					if (!a_manipulateArgs) {
						a_manipulateArgs = [o_result];
					}
					else {
						a_manipulateArgs = [o_result].concat(a_manipulateArgs);
					}

					switch (s_extension) {
					case "xpd":
						o_result = i_self.xpd.flatten.apply(i_self.xpd, a_manipulateArgs);
						break;
					case "ns":
						o_result = new i_self.DynamicNS(a_manipulateArgs[0], a_manipulateArgs[1], a_manipulateArgs[2]);
					}
				}

				if (s_extension.toLowerCase() === "xpd") {
					if (!o_result.hasOwnProperty("allProperties")) {
						var o_all = Ext.apply({}, {});
						var o_properties = Ext.apply({}, o_result.properties || {});
						var o_collections = Ext.apply({}, o_result.collections || {});

						Ext.Object.each(o_properties, function(s_key, value) {
							o_all[s_key] = value;
						});

						Ext.Object.each(o_collections, function(s_key, o_collection) {
							Ext.Object.each(o_collection.properties, function(s_key, value) {
								if (!o_all.hasOwnProperty(s_key)) {
									o_all[s_key] = value;
								}
							});
						});

						o_result.allProperties = o_all;
					}
				}

				o_result._name = s_name;
				o_result._fullPath = o_result._path;
				o_result._path = fullPath;

				if (s_extension == "dmd" && !o_result._fixed) {
					i_self.resource.fix(o_result, s_name);
				}

				if (b_cache) {
					o_cache[fileName] = o_result;
				}

				o_baseConfig[s_cache] = o_cache;

				i_self.resource.baseConfig = o_baseConfig;

				return o_result;
			},
			getPath: function(s_name) {

				var s_extension = s_name.split(".").pop();
				var o_filePath = i_self.splitFileAndPath(s_name);
				var s_fileName = o_filePath.file.split(".").shift();

				var o_baseConfig = i_self.resource.getBaseConfig(s_extension) || {};

				if (o_baseConfig[s_extension] && o_baseConfig[s_extension][s_fileName]) {
					o_filePath = i_self.splitFileAndPath(o_baseConfig[s_extension][s_fileName]._path);
					return o_filePath.path;
				}
				else {
					return s_extension;
				}
			},
			getFullPath: function(s_name) {

				var o_filePath = i_self.splitFileAndPath(s_name);
				if (o_filePath.path.length === 0) {
					return i_self.resource.getPath(s_name) + "/" + o_filePath.file;
				}
				else {
					return o_filePath.path + "/" + o_filePath.file;
				}
			},
			getBaseConfig: function(s_extension) {

				var o_resource = i_self.resource;

				var o_baseConfig = o_resource.baseConfig || {};

				if (!o_baseConfig[s_extension]) {
					o_baseConfig[s_extension] = {};
				}

				o_baseConfig = o_baseConfig;

				return o_baseConfig;
			},
			reload: function(s_extension) {
				return this.get(s_extension, true, [null, true], true, true);
			},
			fix: function(o_config, s_path) {

				var o_model = o_config.model || {};

				o_model.relations = o_model.relations || [];
				o_model.entities = o_model.entities || [];

				if (Ext.isObject(o_model.relations)) {

					Ext.Object.each(o_model.relations, function(s_key, o_relation) {
						o_relation.name = s_key;
					});

					o_model.relations = Ext.Object.getValues(o_model.relations);
				}

				if (!Ext.isArray(o_model.entities)) {

					Ext.Object.each(o_model.entities, function(s_key, o_entity) {
						o_entity.name = s_key;
					});

					o_model.entities = Ext.Object.getValues(o_model.entities);
				}

				Ext.Array.each(o_model.entities, function(o_entity) {

					if (!Ext.isArray(o_entity.columns)) {

						Ext.Object.each(o_entity.columns, function(s_key, o_column) {
							o_column.name = s_key;
						});

						o_entity.columns = Ext.Object.getValues(o_entity.columns);
					}

					var o_ui = o_entity.ui || {};

					o_ui.grids = o_ui.grids || {};
					o_ui.forms = o_ui.forms || {};

					if (!Ext.isObject(o_ui.grids)) {

						var o_grids = {};

						Ext.Array.each(o_ui.grids, function(o_config) {
							o_grids[o_config.name] = o_config;
						});

						o_ui.grids = o_grids;
					}

					if (o_ui.grids.hasOwnProperty("common")) {
						o_ui.grids.portfolio = Ext.Object.merge({}, o_ui.grids.common, o_ui.grids.portfolio);
						delete o_ui.grids.common;
					}


					if (o_ui.grids.hasOwnProperty("commonAll")) {
						o_ui.grids.portfolio = Ext.Object.merge({}, o_ui.grids.commonAll, o_ui.grids.portfolio);
						delete o_ui.grids.commonAll;
					}

					if (!Ext.isObject(o_ui.forms)) {

						var o_forms = {};

						Ext.Array.each(o_ui.forms, function(o_config) {
							o_forms[o_config.name] = o_config;
						});

						o_ui.forms = o_forms;
					}

					if (o_ui.forms.hasOwnProperty("common")) {
						o_ui.forms.portfolio = Ext.Object.merge({}, o_ui.forms.common, o_ui.forms.portfolio);
						delete o_ui.forms.common;
					}

					if (o_ui.forms.hasOwnProperty("commonAll")) {
						o_ui.forms.portfolio = Ext.Object.merge({}, o_ui.forms.commonAll, o_ui.forms.portfolio);
						delete o_ui.forms.commonAll;
					}

					Ext.Object.each(o_ui.forms, function (s_key, o_form) {

						if (Ext.isObject(o_form.items)) {

							var o_items = o_form.items;
							var a_columns = [];

							if (o_items.portlet1) {
								a_columns = Ext.Array.from(o_items.portlet1.columns);
							}
							else {
								a_columns = Ext.Array.from(o_items.columns);
							}

							o_form.items = a_columns;
						}
					});

					o_entity.affiliations = o_entity.affiliations || o_entity.affilations || [];

					if (Ext.isObject(o_entity.affiliations)) {

						Ext.Object.each(o_entity.affiliations, function (s_key, o_affilation) {
							o_affilation.name = s_key;
						});

						o_entity.affiliations = Ext.Object.getValues(o_entity.affiliations);
					}

					o_entity.dmdFile = s_path;

					o_entity.primaryKey = o_entity.primaryKey || [];
				});

				var f_message = function(i_event) {

					var i_worker = this;

					var o_result = Ext.decode(i_event.data, true) || i_event.data;

					Ext.Object.each(o_result, function(s_key, o_value) {
						Portfolio.pd.util.Cache.set(s_key, Ext.clone(o_value));
					});

					i_worker.removeEventListener("message", f_message);

					i_self.removeWorker(i_worker);
				};

				Ext.Array.each(o_model.entities, function(o_entity) {

					var o_ui = o_entity.ui || {};

					var o_forms = o_ui.forms || {};

					var a_entity = Ext.Array.slice(o_entity.columns || []);

					Ext.Object.each(o_forms, function(s_key, o_view) {

						var a_view = Ext.Array.slice(o_view.columns || []);
						var a_extra = Ext.Array.slice(o_view.extraColumns || []);

						var a_result = a_view.concat(a_extra);

						a_result.sort(function(a, b) {
							var n_a = (Ext.isNumber(a.sequence) ? a.sequence : 0);
							var n_b = (Ext.isNumber(b.sequence) ? b.sequence : 0);

							return n_a - n_b;
						});

						Ext.Array.each(a_result, function(o_config) {

							var s_name = o_config.name;

							var o_found = Ext.Array.findBy(a_entity, function(o_search) {
								return o_search.name == s_name;
							});

							if (o_found) {
								Ext.Object.merge(o_config, Ext.clone(o_found));
							}

							if (o_config.pd) {
								
								var s_pd = [
									o_entity.name,
									s_key,
									o_config.name
								].join(".");

								var a_definitions = Portfolio.pd.util.Cache.get(s_pd);

								if (Ext.isEmpty(a_definitions)) {

									a_definitions = Portfolio.pd.util.Cache.load(o_config.pd, s_pd);

									var i_worker = i_self.createWorker();

									i_worker.addEventListener("message", f_message);

									i_worker.postMessage({
										definitions: a_definitions,
										view: o_view,
										entity: o_entity.name,
										property: s_name
									});
								}
							}
						});
					});
				});

				o_config._fixed = true;
			}
		});

		Ext.apply(i_self, {
			resource: o_config
		});

		return o_config;
	},

	initSession: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			getLanguage: function() {
				var i_user = i_self.getUser();
				return i_user.get("USRLNG");
			}
		});

		Ext.apply(i_self, {
			session: o_config
		});

		return o_config;
	},

	initXpd: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			flatten: function(xpd, against, includeName) {
				// Which property will we be flatten against defaults to id:
				against = against || "id";

				// Make sure includeName have to be set to `true to include the name when returning:
				includeName = (includeName === true);

				if (xpd && xpd.hasOwnProperty("against")) {
					if (xpd.against === against) {
						return xpd;
					}
					else {
						throw new Error(".flatten: The given XPD is flattened, but not against: " + against + ", but rather: " + xpd.against);
					}
				}
				else {

					// deepClone the xpd we are getting. This way we are allowed to modify each collection.
					xpd = Ext.apply({}, xpd);

					var collections = this.flattenItems({
						// map out collections and properties to our fake xpd collection
						collections: xpd.collections,
						properties: xpd.properties,
						layout: xpd.uilayout,
						widgets: xpd.widgets
					}, against, includeName);

					var all = {};

					Ext.Object.each(collections, function(s_key, coll) {
						Ext.Object.each(coll.properties, function(name, prop) {
							all[name] = prop;
						});
					});

					collections.all = all;

					// Process the xpd.layout object - sanitize first
					xpd.layout = xpd.layout || {};
					var layout = this.flattenLayout(collections, xpd.layout);

					// Return an object to the user:
					return {
						// What have the xpd been flatten against?
						against: against,

						// Did we include the name ({name: ...})?
						includeName: includeName,

						// Map the idProperty from the xpd object.
						idProperty: xpd.idProperty,

						// Map out the path if any is specified
						path: xpd.path || xpd._path,

						// Map the language from the xpd object.
						language: xpd.language,

						// Map out the propertyRulesPath if any is specified
						propertyRulesPath: xpd.propertyRulesPath,

						// Map out the groups if any is specified // TODO: Have another .xpd that holds the groups!!
						groups: xpd.groups,

						// Iterate each collection and its children.
						// Start with a fake xpd collection:
						collections: collections,

						// Layout object - added by Igor
						layout: layout,

						// Filter so we can filter the collection if needed
						filter: xpd.filter || "",

						// CID map if we need to place a cid to an inner properties
						cidMap: xpd.cidMap || {},

						// convert to pre convert properties in data before collection initialization
						convert: xpd.convert || undefined
					};
				}
			},
			flattenItems: function(parent, against, includeName) {
				// We return an object like this:
				// { 1: {properties}, 2: {properties} }
				var ret = {};

				if (parent.hasOwnProperty("collections")) {

					Ext.Object.each(parent.collections, function(key, child) {
						// Copy all parents properties to the child, if it doesn't allready have them.
						child.properties = child.properties || {};

						// ip2.defaults(child.properties, parent.properties);
						var child_properties = child.properties;
						var child_widgets = child.widgets = child.widgets || [];
						var child_layouts = child.layout = child.layout || {};
						var parent_properties = parent.properties;
						var parent_widgets = parent.widgets || [];
						var parent_layouts = parent.layout || {};

						Ext.Object.each(parent_properties, function(s_key, o_property) {

							var child_property = child_properties[s_key];

							if (!child_property) {
								child_properties[s_key] = o_property;
							}
							else {
								Ext.Object.each(o_property, function(s_key, value) {
									if (!child_property.hasOwnProperty(s_key)) {
										child_property[s_key] = value;
									}
								});
							}
						});

						if (Ext.isArray(parent_widgets) && Ext.isArray(child_widgets)) {

							Ext.Array.each(parent_widgets, function(o_parent) {

								var b_found = false;

								Ext.Array.each(child_widgets, function(o_child) {
									if (o_parent.name == o_child.name) {
										b_found = true;
										return false;
									}
								});

								if (!b_found) {
									child_widgets.push(o_parent);
								}
							});
						}

						Ext.Object.each(parent_layouts, function(s_key, o_layout) {

							var child_layout = child_layouts[s_key];

							if (!child_layout) {
								child_layouts[s_key] = o_layout;
							}
						});

						if (child.$delete) {
							Ext.Array.each(child.$delete, function(key, index) {
								delete child_properties[key];
							});
						}

						// Does the child have any collections itself then iterate through them
						if (child.hasOwnProperty("collections")) {
							// Copy all the created collections to the global return object.
							Ext.Object.merge(ret, i_self.xpd.flattenItems(child, against, includeName));
						}

						// Do we have to include the name?
						if (includeName) {
							child.name = key;
						}

						// push our child to the return object
						// If against is name we will have to use the key from the collection iteration:
						if (against === "name") {
							ret[key] = child;
						}
						else {
							ret[child[against]] = child;
						}
					});
				}

				return ret;
			},
			flattenLayout: function(collections, xpdLayout) {

				var defaultsId = "__default__";

				// First prepare the layout object - add default values
				Ext.Object.each(xpdLayout, function(contextKey, context) {

					var defaults = context[defaultsId] || {};

					// Itereate over the context for the layout object
					Ext.Object.each(context, function(propertyId, layout) {

						if (propertyId != defaultsId) {

							// Extend the layout for this collection id
							context[propertyId] = Ext.Object.merge({}, defaults, layout);
						}
					});
				});

				if (!Ext.isEmpty(xpdLayout)) {

					// Go through the flattened collections and extend layout on to
					Ext.Object.each(collections, function(s_key, collection) {

						var properties = collection.properties;

						Ext.Object.each(properties, function(name, field) {

							field.layout = field.layout || {};

							Ext.Object.each(xpdLayout, function(contextKey, context) {

								var defaults = context[name] || (context[defaultsId] || {});

								if (!field.layout[contextKey]) {
									field.layout[contextKey] = defaults;
								}
								else {
									field.layout[contextKey] = Ext.Object.merge({}, defaults, field.layout[contextKey]);
								}
							});
						});
					});
				}

				return xpdLayout;
			},
			translate: function(xpd, value, lang) {

				value = value || "";

				var defaultLanguage;

				if (xpd.defaultLanguage) {
					defaultLanguage = xpd.defaultLanguage;
				}

				if (!lang) {
					lang = ip2.session.getLanguage();
				}

				lang = defaultLanguage || XPD_DEFAULTLANGUAGE;

				var languages = xpd.language;
				var chkValue = value.toLowerCase();
				var ret;

				if (languages && languages[lang]) {
					ret = languages[lang].text[chkValue] != undefined ? languages[lang].text[chkValue] : value;
				} // TODO: This only checks if the wanted language exists, check for fallback to the default language for possible other value here!
				else {
					ret = value;
				}

				return ret;
			}
		});

		Ext.apply(i_self, {
			xpd: o_config
		});

		return o_config;
	},

	initJSON: function() {

		var i_self = this;

		var o_config = {
			parse: Ext.Function.alias(window.JSON, "parse"),
			stringify: Ext.Function.alias(window.JSON, "stringify")
		};

		i_self.JSON = o_config;
	},

	initNavigator: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			user: {
				id: null
			},
			setUrl: function(s_context, o_params) {

				var s_params = Ext.Object.toQueryString(o_params);

				var s_service = "/ip2-services/ip2navigator.aspx?func=" + s_context;

				if (s_params) {
					s_service = s_service + "&" + s_params;
				}

				return s_service;
			}
		});

		Ext.apply(i_self, {
			navigator: o_config
		});

		return o_config;
	},

	initPreset: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			config: "custom-config/application-config-lib"
		});

		Ext.apply(i_self, {
			preset: o_config
		});

		return o_config;
	},

	initDate: function() {

		var i_self = this;

		i_self.date2dbDate = function(dateObject, s_format) {
			s_format = s_format || ip2.date2dbDate.format;
			return Ext.Date.format(dateObject, s_format);
		};

		i_self.date2dbDate.format = "Y-m-d-H.i.s.u000";
		i_self.date2dbDate.parseFormat = "Y-m-d-H.i.s.u";
	},

	initQueryString: function () {

		var i_self = this;

		var f_qrystr = function() {

			var s_params = window.location.search.split("?").pop() || "";

			return s_params;
		};

		f_qrystr.getValue = function(s_property, s_qrystr) {
			var o_params = Ext.Object.fromQueryString(s_qrystr || window.location.search);

			return o_params[s_property] || "";
		};

		Ext.apply(i_self, {
			qrystr: f_qrystr
		});

		return f_qrystr;
	},

	initComet: function() {

		var i_self = this;

		var o_config = Ext.apply({}, {
			listeners: {},
			defaults: {},
			libl: "",
			connected: false,
			connection: null,
			since: null,
			listen: function(s_id, s_event, f_callback, f_default) {

				var i_self = this;
				var a_listeners = i_self.listeners[s_event] || [];
				var b_found = false;

				var o_return;

				Ext.Array.each(a_listeners, function(o_event) {
					if (o_event.id == s_id) {
						b_found = true;
						o_return = o_event;
						return false;
					}
				});

				if (!b_found) {
					o_return = {
						id: s_id,
						event: s_event,
						callback: f_callback
					};
					a_listeners.push(o_return);
				}

				i_self.listeners[s_event] = a_listeners;
				if (Ext.isFunction(f_default)) {
					i_self.defaults[s_id] = f_default;
				}

				if (!i_self.connected) {
					i_self.open();
				}
				return o_return;
			},
			open: function() {

				var i_self = this;

				var s_url = "/system/event_wait?lib=" + (i_self.libl ? i_self.libl : "*LIBL") + "&name=EVENTS&timeout=60&default=[]";

				if (i_self.since) {
					s_url = s_url + "&since=" + i_self.since;
				}

				i_self.connection =	Ext.Ajax.request({
					url: s_url,
					timeout: 300000,
					success: function (o_response) {

						var a_result = Ext.decode(o_response.responseText, true) || [];

						var s_header = o_response.getResponseHeader("x-eventtime");

						Ext.Array.each(a_result, function(o_event) {
							var a_listeners = i_self.listeners[o_event.name] || [];
							Ext.Array.each(a_listeners, function(o_listener) {
								var f_callback = o_listener.callback || Ext.emptyFn;
								f_callback(o_event.data);
							});
						});

						i_self.since = s_header;

						var a_keys = Ext.Object.getKeys(i_self.listeners);

						if (a_keys.length > 0) {
							i_self.doDefaults();
							i_self.open();
						}
						else {
							i_self.close();
						}
					},
					failure: function(o_response) {
						if (!o_response.aborted && o_response.status !== 0) {
							i_self.connected = false;
							i_self.doDefaults();
							i_self.reconnect();
						}
					}
				});

				if (!i_self.connected) {
					i_self.connected = true;
				}
			},
			unlisten: function(s_id, s_event) {

				var i_self = this;
				var a_listeners = i_self.listeners[s_event] || [];
				var n_remove = -1;
				Ext.Array.each(a_listeners, function(o_event, n_index) {
					if (o_event.id == s_id) {
						n_remove = n_index;
						return false;
					}
				});
				if (n_remove > -1) {
					a_listeners.splice(n_remove, 1);
					if (a_listeners.length === 0) {
						delete i_self.listeners[s_event];
					}
				}
				if (i_self.defaults[s_id]) {
					delete i_self.defaults[s_id];
				}
			},
			close: function() {

				var i_self = this;
				if (i_self.connection) {
					i_self.connected = false;
					Ext.Ajax.abort(i_self.connection);
					delete i_self.connection;
				}
			},
			doDefaults: function() {

				var i_self = this;
				Ext.Object.each(i_self.defaults, function(s_key) {
					var f_default = i_self.defaults[s_key];
					f_default();
				});
			},
			reconnect: function() {

				var i_self = this;
				setTimeout(function() {
					i_self.open();
				}, 1000);
			}
		});

		Ext.apply(i_self, {
			comet: o_config
		});

		return o_config;
	},

	initDynamicNs: function() {

		var i_self = this;

		var DynamicNS = function(opts) {
			if (!opts) {
				return;
			}

			this.ns = opts.ns;

			this.languages = opts.lang;

			this.lang = null;

			this.enums = new Enums(opts.enums, this);

			this.text = new Text(opts.text, this);
		};

		DynamicNS.prototype = {
			toJSON: function() {
				return {
					ns: this.ns,
					lang: this.languages,
					enums: this.enums.toJSON(),
					text: this.text.toJSON()
				};
			},

			setLanguage: function(lang) {
				this.lang = lang;
			},
			getLanguage: function() {
				return this.lang;
			},
			i18n: function(ls) {
				var self = this;

				var i18n = {
					toString: function() {

						if (self.lang) {
							return i18n[self.lang] || '';
						}

						for (var prop in ls) {
							return i18n[prop];
						}

						return '';
					}
				};

				if (ls) {
					for (var prop in ls) {
						i18n[prop] = ls[prop];
					}
				}

				return i18n;
			},
			tpl: function(value, data) {
				// tpl('Hello {{0}}', ['John']); // Hello John
				// tpl('Hello {{name}}', { name: 'John' }) // Hello John

				if (typeof (data) === 'object') {
					Ext.Object.each(this.languages, function(lang) {
						if (value[lang]) {
							value[lang] = value[lang].replace(/([{]{1,2})(.*?)([}]{1,2})/g, function(_, a, b, c) {
								// If we can find a matching key-value in data return it
								if (data[b] != null) {
									return data[b];
								}

								// If the key-value isn't in data return the selector:
								else {
									return a + b + c;
								}
							});
						}
					});
				}

				return value;
			},
			getValue: function(key, args) {
				var value = this.i18n(this.enums.getValue(key) || this.text.getValue(key));

				return this.tpl(value, args);
			},
			getKeys: function(key) {
				return [].concat(this.enums.getKeys(key), this.text.getKeys(key));
			},

			addEnum: function(key, i, value) {
				return this.enums.add(key, i, value);
			},
			getEnumValue: function(key, args) {
				var value = this.i18n(this.enums.getValue(key));

				return this.tpl(value, args);
			},
			getEnumKeys: function(value) {
				return this.enums.getKeys(value);
			},

			getEnumLayoutKeys: function(name, ignore) {
				return this.enums.getLayoutKeys(name, ignore);
			},

			addText: function(key, value) {
				return this.text.add(key, value);
			},
			getTextKeys: function(value) {
				return this.text.getKeys(value);
			},
			getTextValue: function(key, args) {
				var value = this.i18n(this.text.getValue(key));

				return this.tpl(value, args);
			}
		};

		var Enums = function(opts, ns) {
			this.ns = ns;
			this.createMaps(opts);
		};

		Enums.prototype = {
			createMaps: function(opts) {
				this.keyMap = {};
				this.valueMap = {};

				for (var name in opts) {
					for (var prop in opts[name].values) {
						var key = this.ns.ns + '~' + name + '~' + prop;
						var lang = opts[name].values[prop];

						this.keyMap[ key ] = lang;

						for (var l in lang) {

							var value = lang[l];
							var lValue;

							if (value && value.length > 0) {
								lValue = value.toLowerCase();

								if (!this.valueMap[ lValue ]) {
									this.valueMap[ lValue ] = [];
								}

								this.valueMap[ lValue ].push(key);
							}
						}
					}
				}
			},
			add: function(partialKey, i, lang) {
				if (ip2.isObject(i)) {
					lang = i;
					i = null;
				}

				if (!ip2.isObject(lang)) {
					var value = lang;
					lang = {};
					ip2.each(this.languages, function(key) {
						lang[key] = value;
					});
				}

				if (!i) {
					i = 0;
					while (this.keyMap[ this.ns.ns + '~' + partialKey + '~' + i ]) {
						i++;
					}
				}

				var key = this.ns.ns + '~' + partialKey + '~' + i;

				this.keyMap[ key ] = lang;

				for (var l in lang) {
					var lValue = lang[l].toLowerCase();

					if (!this.valueMap[ lValue ]) {
						this.valueMap[ lValue ] = [];
					}
					this.valueMap[ lValue ].push(key);
				}

				return key;
			},
			getValue: function(key) {
				return this.keyMap[key] || this.keyMap[this.ns.ns + '~' + key];
			},
			getKeys: function(v) {
				return this.valueMap[ v.toLowerCase() ] || [];
			},
			getLayoutKeys: function(name, ignore) {
				var r = [];
				name = name || "";
				name = name.indexOf(this.ns.ns + '~') === 0 ? name : this.ns.ns + '~' + name;

				if (name.slice(0, -1) != '~') {
					name += '~';
				}

				for (var prop in this.keyMap) {
					if (prop.indexOf(name) === 0 && (!ignore || prop.search(ignore) == -1)) {
						r.push(prop);
					}
				}
				return r;
			},
			toJSON: function() {
				var r = {};

				var lNS = function(f) {
					if (!r[f[1]]) {
						r[f[1]] = {
							values: {}
						};
					}

					return r[f[1]];
				};

				ip2.each(this.keyMap, function(value, k) {
					var f = k.split('~');

					lNS(f);

					r[f[1]].values[f[2]] = value;
				});

				return r;
			}
		};

		var Text = function(opts, ns) {
			this.ns = ns;
			this.createMaps(opts);
		};

		Text.prototype = {
			createMaps: function(opts) {
				this.keyMap = {};
				this.valueMap = {};

				for (var name in opts) {
					var key = this.ns.ns + '~' + name;
					var lang = opts[name].value;

					this.keyMap[ key ] = lang;

					for (var l in lang) {
						var value = lang[l];

						if (value == null) {
							value = '';
							console.error('ip2.DynamicNS: createMaps() -> Remember to add translations for all languages for: ', name);
						}

						var lValue = value.toLowerCase();

						if (!this.valueMap[ lValue ]) {
							this.valueMap[ lValue ] = [];
						}

						this.valueMap[ lValue ].push(key);
					}
				}
			},
			add: function(partialKey, lang) {
				if (!ip2.isObject(lang)) {
					var value = lang;
					lang = {};
					ip2.each(this.languages, function(key) {
						lang[key] = value;
					});
				}

				var key = this.ns.ns + '~' + partialKey;

				this.keyMap[ key ] = lang;

				for (var l in lang) {
					var lValue = lang[l].toLowerCase();

					if (!this.valueMap[ lValue ]) {
						this.valueMap[ lValue ] = [];
					}
					this.valueMap[ lValue ].push(key);
				}

				return key;
			},
			getValue: function(key) {
				return this.keyMap[key] || this.keyMap[this.ns.ns + '~' + key];
			},
			getKeys: function(v) {
				return this.valueMap[ v.toLowerCase() ] || [];
			},
			toJSON: function() {
				var r = {};

				var rL = (this.ns.ns + '~').length;

				ip2.each(this.keyMap, function(value, k) {
					var key = k.slice(rL);
					r[ key ] = {
						value: value
					};
				});

				return r;
			}
		};

		Ext.apply(i_self, {
			DynamicNS: DynamicNS
		});

		return DynamicNS;
	},

	isAccessible: function(s_path) {

		var i_self = this;
		var i_user = i_self.getUser();
		var o_access = i_user.get("ACCESS") || true;

		if (Ext.isBoolean(o_access)) {
			return o_access;
		}

		if (Ext.isBoolean(s_path)) {
			return s_path;
		}

		if (o_access.administrator) {
			o_access = o_access.administrator;
			return o_access;
		}

		if (s_path !== undefined || s_path !== null) {
			if (!Ext.isString(s_path)) {
				s_path = "";
			}
		}
		else {
			s_path = "";
		}

		var s_search = s_path.toLowerCase();

		var b_result = true;

		var a_split = s_search.split(".");
		var o_current = o_access;

		Ext.Array.each(a_split, function(s_part) {
			if (o_current.hasOwnProperty(s_part)) {
				o_current = o_current[s_part];
				if (Ext.isBoolean(o_current)) {
					b_result = o_current;
				}
			}
			else {
				b_result = false;
			}
			if (!b_result) {
				return false;
			}
		});
		return b_result;
	},

	hasUserRole: function(s_role) {

		var i_self = this;

		var i_user = i_self.getUser();

		var a_roles = Ext.Array.from(i_user.get("USERROLES") ||  []);

		var o_found;

		o_found = Ext.Array.findBy(a_roles, function (o_role) {
			return o_role.TYPE.toLowerCase() == s_role.toLowerCase();
		});

		return o_found ? o_found.SELECTED == 1 : false;
	},

	getRoles: function(f_callback) {

		var i_self = this;

		f_callback = f_callback || Ext.emptyFn;

		var i_user = i_self.getUser();

		Ext.Ajax.request({
			url: "/ip2-services/ip2transaction.aspx",
			jsonData: {
				action: "ip2NavProxy.listroles",
				token: i_user.get("USRUSRTKN"),
				entity: "NAVUSRET00"
			},
			success: function (i_response) {

				var a_roles = Ext.decode(i_response.responseText, true) || [];

				i_user.set("USERROLES", a_roles);
				f_callback(a_roles);
			}
		});

	},

	model: function (o_config, s_hive, b_tree) {

		var i_self = this;

		b_tree = Ext.isEmpty(b_tree) ? false : true;

		if (o_config.dmd) {
			return i_self.getDmd(o_config, s_hive, b_tree);
		}

		var i_model = Ext.ModelManager.getModel(o_config.name);

		if (!i_model) {
			i_model = Ext.define(o_config.name, {
				extend: "Ext.data.Model",
				fields: i_self.modelFields(o_config),
				proxy: {
					type: "ajax",
					url: o_config.url,
					simpleSortMode: true,
					reader: {
						type: "json"
					},
					writer: {
						type: "json"
					},
					listeners: {
						exception: i_self.message.ProxyException
					}
				}
			});
		}

		return i_model;
	},

	getDmd: function(o_config, s_hive, b_tree) {

		var i_self = this;

		var o_dmd = i_self.resource.get(o_config.dmd);
		var o_uiTypes = i_self.getUitypes();
		var i_template = i_self.modelTpl;

		if (!i_self.modelTpl) {
			i_template = new Ext.XTemplate("{dmd}.{entity}.{context}.{view}");
			i_template.compile();
			i_self.modelTpl = i_template;
		}

		var s_className = i_template.apply({
			dmd: o_config.dmd,
			entity: o_config.entityName || o_config.entity,
			context: o_config.context || "grid",
			view: o_config.uiName || o_config.view || "portfolio"
		});

		if (Ext.ClassManager.isCreated(s_className)) {
			return Ext.ClassManager.get(s_className);
		}

		var o_dmdClone = Ext.clone(o_dmd);

		var o_model = Ext.clone(o_dmdClone.model);

		if (o_model.uitypes) {

			Ext.Ajax.request({
				url: o_model.uitypes,
				async: false,
				success: function (i_response) {

					var o_types = Ext.decode(i_response.responseText);

					i_self.uitypes = Ext.apply(i_self.uitypes, o_types);
				}
			});
		}

		if (o_model.require) {

			Ext.Array.each(o_model.require, function(s_require) {

				var s_path = "dmd/" + s_require.split("/").pop();
				var o_required = i_self.resource.get(s_path);

				var o_reqModel = o_required.model || {
					entities: []
				};

				o_model.entities = o_model.entities.concat(o_reqModel.entities);
			});
		}

		Ext.Array.each(o_model.relations, function(o_relation) {

			if (i_self.formatRelationCardinality(o_relation.between, o_relation.and) == "many2many") {

				var o_between = Ext.Array.findBy(o_model.entities, function(o_value) {
					return o_value.name == o_relation.between.entity;
				});

				var o_and = Ext.Array.findBy(o_model.entities, function(o_value) {
					return o_value.name == o_relation.and.entity;
				});

				o_relation.between.on = i_self.getEntityPrimaryKey(o_between, true, false);

				o_relation.and.on = i_self.getEntityPrimaryKey(o_and, true, false);
			}
		});

		var s_entity = o_config.entityName || o_config.entity;

		o_config.entityName = s_entity;

		var o_entity = Ext.Array.findBy(o_model.entities, function(o_value) {
			return o_value.name == s_entity;
		});

		Ext.apply(o_entity, {
			name: s_entity
		});

		o_config.uiName = o_config.uiName || o_config.view || "portfolio";
		o_config.context = o_config.context || "grid";

		var o_result = i_self.dmdFields(o_model, o_entity, o_config, o_uiTypes);

		if (!o_entity.idColumn) {
			o_entity.idColumn = o_result.idField;
		}

		var a_fields = [];

		var a_columns = Ext.Object.getValues(o_result.columns);

		Ext.Array.each(a_columns, function(o_value) {
			if (!o_value.convert) {
				if (o_value.column) {
					if (o_value.column.convert) {
						o_value.convert = o_value.column.convert;
					}
				}
			}
			o_value.critical = o_value.critical || false;
			a_fields.push(o_value);
		});

		var i_model = Ext.define(s_className, {
			extend: b_tree ? "Ext.data.TreeModel" : "Ext.data.Model",
			fields: a_fields,
			columns: a_columns,
			idProperty: o_result.idField,
			entity: o_entity,
			dmdModel: o_model,
			dmdFile: o_config.dmd,
			view: o_config.uiName,
			$configStrict: false,
			parentModels: i_self.dmdParentModels(o_config, o_model),
			childModels: i_self.dmdChildModels(o_config, o_model),
			affiliations: i_self.dmdAffiliations(o_config, o_model)
		});

		i_model.dmdFile = o_config.dmd;

		return i_model;
	},

	isString: function(value) {

		return Ext.isString(value);
	},

	isStringedObject: function(value) {

		if (!value) {
			return false;
		}

		return Ext.String.startsWith(value, "{") && Ext.String.endsWith(value, "}");
	},

	isStringedArray: function(value) {

		if (!value) {
			return false;
		}

		return Ext.String.startsWith("[") && Ext.String.endsWith("]");
	},

	isNumber: function(value) {
		return Ext.isNumber(value);
	},

	isExternalUrl: function (s_url) {
		var r_externalUrl = /^(ftp|http(s?)):\/\//i;
		return r_externalUrl.test(s_url);
	},

	splitFileAndPath: function (s_file) {

		var n_index = s_file.lastIndexOf("/");

		if (n_index == -1) {
			return {
				path: "",
				file: s_file
			};
		}

		var a_split = s_file.split("/");
		var s_file = a_split.pop();

		return {
			path: a_split.join("/"),
			file: s_file
		};
	},

	defaults: function(obj, source) {

		return Ext.apply(obj, source);
	},

	getServerBaseConfig: function(f_callback) {

		var i_self = this;

		var o_config = i_self.baseconfig || {};

		f_callback = f_callback || Ext.emptyFn;

		if (Ext.Object.isEmpty(o_config)) {

			Ext.Ajax.request({
				url: "/custom-config/base-config-lib.lib",
				async: false,
				success: function(i_response) {

					var o_result = Ext.decode(i_response.responseText, true) || {};

					o_config = o_result;

					delete o_config.callback;
					i_self.baseconfig = o_config;

					i_self.baseconfig = o_config;

					Ext.applyIf(i_self.preset, o_config.session.preset);

					Ext.applyIf(i_self.session, o_config.session);

					f_callback(o_config);
				},
				failure: function(i_error) {
					f_callback(o_config);
				}
			});
		}
		else {
			f_callback(o_config);
		}
	},

	getI18nConfig: function(f_callback) {

		var i_self = this;

		f_callback = f_callback || Ext.emptyFn;

		i_self.getEvaled("/ip2-services/ip2i18n.aspx.js");

		f_callback.call();
	},

	loadUItypes: function () {

		var i_self = this;
		return i_self.default.uitypes;
	},

	loadUiTypeRules: function () {

		var i_self = this;
		return i_self.default.uirules;
	},

	formatRelationCardinality: function(parent, child) {

		return (parent.occurrence + "2" + child.occurrence);
	},

	getEntityFromRecord: function (i_record) {

		var o_info = ip2.getModelInformation(i_record);
		var o_model = ip2.getModelFromRecord(i_record);

		return Ext.Array.findBy(o_model.entities, function(o_value) {
			return o_value.name == o_info.entity;
		});
	},

	getEntityPrimaryKey: function(entity, asObject, toUpperCase) {
		var keyField;
		var keyFields = [];
		if (!Ext.isEmpty(entity.primaryKey)) {
			Ext.Array.each(entity.primaryKey, function(columnName) {
				if (asObject) {
					keyField = {};
					keyField.name = columnName;
				}
				else {
					keyField = columnName;
				}
				keyFields.push(keyField);
			});
		}
		else {
			for (var columnName in entity.columns) {
				if (entity.idColumn && entity.idColumn == columnName) {
					if (asObject) {
						keyField = {};
						keyField.name = columnName;
						keyFields.push(keyField);
					}
					else {
						keyField = columnName;
					}
					keyFields.push(keyField);
				}
			}
		}
		return keyFields;
	},

	getModelInformation: function(value) {

		var a_values = value.$className.split(".dmd");

		var a_info = a_values[1].split(".");

		a_info = Ext.Array.filter(a_info, function(value) {
			return value;
		});

		var s_file = value.dmdFile;

		return {
			path: s_file,
			name: s_file.split("/").pop(),
			entity: a_info[0],
			context: a_info[1],
			view: a_info[2]
		};
	},

	getKeysFromRecord: function(i_record) {

		var i_self = this;

		var o_info = i_self.getModelInformation(i_record);
		var o_model = i_self.getModelFromRecord(i_record);

		var o_entity = Ext.Array.findBy(o_model.entities, function(o_value) {
			return o_value.name == o_info.entity;
		});

		var o_keys = {};

		var s_idColumn = o_entity.idColumn;
		o_keys[s_idColumn] = i_record.get(s_idColumn) || null;

		return o_keys;
	},

	getModelFromRecord: function (i_record) {

		var o_info = ip2.getModelInformation(i_record);
		var o_model = ip2.resource.get(o_info.path).model;

		return o_model;
	},

	dmdFields: function (o_model, o_entity, o_dmdConfig, o_uiTypes) {

		var o_result = {
			idField: "",
			columns: []
		};

		o_uiTypes = o_uiTypes || ip2.loadUItypes();

		var o_clone = Ext.clone(o_entity);

		// get this entity parent relation
		var o_relations = ip2.getEntityForeignRelationByOccurrence(o_model, o_clone, "one");

		var o_entityResult = ip2.getEntityFields(o_clone, o_relations, o_dmdConfig, o_model);

		o_result.idField = o_entityResult.idField || o_entityResult.idColumn || o_clone.idColumn;

		o_result.columns = o_result.columns.concat(o_entityResult.columns);

		// add grid UI columns, which not exists as an entity column.

		if (o_dmdConfig.context != "form") {

			Ext.Object.each(o_clone.ui.grids, function(s_key, o_view) {

				var a_columns = Ext.Array.from(o_view.columns) || [];

				Ext.Array.each(a_columns, function(o_viewColumn) {

					if (Ext.isArray(o_viewColumn.from)) {
						o_viewColumn.from = o_viewColumn.from.join(" ");
					}

					if (Ext.String.startsWith(o_viewColumn.from, "(")) {

						var o_clone = Ext.clone(o_viewColumn);
						var s_name = o_clone.name;
						var b_found = false;

						var o_found = Ext.Array.findBy(o_result.columns, function(o_column) {
							return o_column.name == s_name;
						});

						if (!o_found) {

							var o_config = Ext.apply({},
								{
									dataIndex: s_name,
									hidden: false,
									header: o_clone.title
								}
							);

							// check uitype, and apply to datamodel field -& column editor
							var s_uiType = o_clone.uitype;
							var o_uitype = o_uiTypes[s_uiType];

							if (o_uitype) {

								// uitype require ?
								if (o_uitype.require || o_uitype.requires) {
									var requires = o_uitype.require || o_uitype.requires;
									Ext.require(requires);
								}

								o_clone = Ext.apply(o_clone, o_uitype.datamodel || {});
								o_clone.column = {};

								if (o_uitype.grid) {
									o_clone.column = Ext.apply(o_config, o_uitype.grid || {});
								}

								if (o_clone.column.editor) {
									if (o_clone.column.editor.renderer) {
										o_clone.column.renderer = o_clone.column.editor.renderer;
									}
								}
								if (o_uitype.convert) {
									o_clone.convert = o_uitype.convert;
								}
							}
							else {
								o_clone.column = o_config;
							}

							o_result.columns.push(o_clone);
						}
					}
				});
			});
		}

		// set primary key

		var s_idColumn = o_result.idField || "id";
		var b_found = false;

		Ext.Array.each(o_result.columns, function(o_column) {
			if (o_column.name == s_idColumn) {
				b_found = true;
			}
		});

		if (!b_found) {

			o_result.columns.push({
				name: s_idColumn,
				type: "auto"
			});
		}
		return o_result;
	},

	getEntityFields: function (o_entity, a_relations, o_dmdConfig, o_model, from) {

		var o_result = {
			columns: []
		};

		var o_clone = Ext.clone(o_entity);
		var a_temp = [];
		var a_result = [];
		var o_uiTypes = ip2.loadUItypes();

		if (o_dmdConfig.uiName && o_dmdConfig.context) {

			var s_type = o_dmdConfig.context + "s";
			var s_context = o_dmdConfig.context;
			var s_uiName = o_dmdConfig.uiName;
			var o_context;
			var a_viewColumns = [];
			var o_view;

			if (o_clone.ui) {
				o_context = o_clone.ui[s_type];
			}

			if (o_context) {
				o_view = o_context[s_uiName] || o_context.portfolio;
				a_viewColumns = Ext.Array.from(o_view.columns) || [];

				var a_extraColumns = Ext.Array.from(o_view.extraColumns);
				a_viewColumns = a_viewColumns.concat(a_extraColumns);
			}
			else {

				var o_stackEntry = ip2.getStackTrace(new Error());

				Ext.Error.raise({
					description: ip2.i18n({
						da: "DMD fejl ",
						en: "DMD Error"
					}),
					message: {
						message: ip2.i18n({
							da: "o_entity.ui." + s_type + " findes ikke",
							en: "o_entity.ui." + s_type + " does not exists"
						}),
						url: o_stackEntry.url,
						lineNo: o_stackEntry.lineNr
					},
					ignore: true,
					debug: "js"
				});

				return o_result;
			}

			var a_entityColumns = [];

			if (Ext.isObject()) {

				Ext.Object.each(o_clone.columns, function(s_key, o_column) {

					var o_temp = Ext.apply({
						name: s_key
					}, o_column);

					a_entityColumns.push(o_temp);
				});
			}
			else {
				a_entityColumns = Ext.Array.from(o_clone.columns);
			}

			Ext.Array.each(a_viewColumns, function(o_uiColumn) {

				var o_field = Ext.clone(o_uiColumn);
				var s_field = o_field.name;

				var o_found = Ext.Array.findBy(a_entityColumns, function(o_search) {
					return o_search.name == s_field;
				});

				if (o_found) {
					Ext.Object.merge(o_field, Ext.clone(o_found));
				}

				var o_xpdInfo = ip2.getFieldXpdInfo(s_field, o_clone, o_field);

				if (o_xpdInfo) {
					ip2.applyFieldXpdInfo(o_field, o_xpdInfo);
				}

				if (o_field.uitype) {

					var o_uitype = o_uiTypes[o_field.uitype];

					if (o_uitype) {
						if (o_uitype[s_context]) {
							Ext.apply(o_field, o_uitype[s_context] || {});
						}
						if (o_uitype.convert) {
							Ext.apply(o_field, {
								convert: o_uitype.convert
							});
						}
					}
				}

				if (Ext.Object.getKeys(o_field).length > 0) {
					a_temp.push(o_field);
				}
			});
		}

		if (a_temp.length === 0) {

			if (Ext.isObject(o_clone.columns)) {

				Ext.Object.each(o_clone.columns, function(s_key, o_column) {

					var o_temp = Ext.apply({
						name: s_key
					}, o_column);

					a_temp.push(o_temp);
				});
			}
		}

		var s_idColumn = o_clone.idColumn;

		Ext.Array.each(a_temp, function(o_column) {

			var s_name = o_column.name;

			if (o_column.like) {
				o_column = ip2.applyBaseDatatypeProperties(o_column);
			}

			var s_datatype = ip2.dbtypes2js(o_column);

			var o_field = Ext.apply(o_column, {
				name: s_name,
				type: s_datatype,
				len: ip2.dbtypeLength(o_column)
			});

			if (o_column.baseType == "isotimestamp") {
				Ext.apply(o_field, {
					allowNull: true,
					convert: function(value) {

						var i_field = this;

						if (i_field.native) {

							var i_converter = new Ext.data.field.Date();
							value = i_converter.convert(value);
						}

						return value || null;
					}
				});
			}

			// save id field name
			var b_isId = s_idColumn == s_name ? true : false;

			var o_config = {
				dataIndex: s_name,
				hidden: false,
				header: ip2.getEntityColumnTitle(o_field, o_model),
				isIdColumn: b_isId,
				editor: {},
				isTitle: o_field.isTitle,
				uiType: o_field.uiType
			};

			if (o_field.uiType && o_uiTypes.hasOwnProperty(o_field.uiType)) {

				var o_uitype = o_uiTypes[o_field.uiType];
				// uitype require ?
				if (o_uitype.require || o_uitype.requires) {
					var requires = o_uitype.require || o_uitype.requires;
					Ext.require(requires);
				}

				o_field = Ext.apply(o_field, o_uitype.datamodel || {});
				o_field.column = {};

				if (o_uitype.grid) {
					o_field.column = Ext.apply(o_config, o_uitype.grid || {});
				}

				if (!Ext.Object.isEmpty(o_field.column.editor)) {
					if (o_field.column.editor.renderer) {
						o_field.column.renderer = o_field.column.editor.renderer;
					}
				}
				if (o_uitype.convert) {
					o_field.convert = o_uitype.convert;
				}
			}
			else {
				o_field.column = o_config;
			}

			// set renderer for editcode -or word
			if (o_field.edtcde || o_field.edtword) {

				o_field.column.renderer = function(value, meta, record, rowiIndex, colIndex) {
					var field = record.fields.items[colIndex];
					return ip2.formatNumeric(value, field.len, field.edtcde, field.edtword);
				};
			}

			if (o_field.from) {

				var o_from = Ext.Array.findBy(o_model.entities, function(o_value) {
					return o_value.name == o_field.from;
				});

				if (o_from) {

					// var i_model = ip2.model({
					// 	dmd: o_clone.dmdFile,
					// 	entity: o_field.from,
					// 	context: s_context,
					// 	view: s_uiName
					// });

					// o_field.reference = i_model.$className;
				}
			}

			var o_found = Ext.Array.findBy(o_clone.columns, function(o_value) {
				return o_value.name == o_field.name;
			});

			if (!o_found) {
				Ext.apply(o_field, {
					type: "auto",
					allowNull: true,
					convert: function(value) {
						return value || null;
					},
					compare: function(value1, value2) {

						var s_value1 = value1;
						var s_value2 = value2;

						if (Ext.isObject(value1)) {
							s_value1 = JSON.stringify(value1);
						}

						if (Ext.isObject(value2)) {
							s_value2 = JSON.stringify(value2);
						}

						return (s_value1 === s_value2) ? 0 : ((s_value1 < s_value2) ? -1 : 1);
					}
				});
			}

			a_result.push(o_field);
		});

		if (!s_idColumn) {

			var a_primary = o_clone.primaryKey || [];

			o_result.idColumn = o_clone.idColumn = s_idColumn = a_primary.join("_");
		}

		o_result.columns = a_result;
		return o_result;
	},

	getEntityForeignRelationByOccurrence: function(model, entity, occurrence) {

		var relations = {};

		Ext.Array.each(model.relations, function(relation) {

			if (relation.between.entity == entity.name && relation.and.occurrence == occurrence) {

				var name = (relation.properties.as) ? relation.properties.as : relation.and.entity;

				relations[name] = {};
				relations[name].parentEntity = relation.and.entity;
				relations[name].childEntity = relation.between.entity;
				relations[name].parentKeys = ip2.getRelationOnArray(relation.and.on);
				relations[name].childKeys = ip2.getRelationOnArray(relation.between.on);
			}
			else if (relation.and.entity == entity.name && relation.between.occurrence == occurrence) {

				var name = (relation.properties.as) ? relation.properties.as : relation.between.entity;

				relations[name] = {};
				relations[name].parentEntity = relation.between.entity;
				relations[name].childEntity = relation.and.entity;
				relations[name].parentKeys = ip2.getRelationOnArray(relation.between.on);
				relations[name].childKeys = ip2.getRelationOnArray(relation.and.on);
			}
		});

		return relations;
	},

	getRelationOnArray: function (on) {
		var array = [];
		Ext.Array.each(on, function(obj) {
			array.push(obj.name);
		});
		return array;
	},

	getFieldTypeDefinition: function(s_type) {

		s_type = s_type.toLowerCase();

		// type = "dec (9 , 3)" -> dec(9,3)
		s_type = s_type.replace(/\s/g, "");

		// type = "integer" -> ["integer"]
		// type = "varchar(1000)" -> ["varchar", 1000]
		// type = "dec(9,3)" -> ["int", 9, 3]
		var a_typeDef = [];
		s_type.replace(/^([a-z]+)(?:\(([0-9]+)(?:,([0-9]+))?\))?$/, function(a, type, length, decPoint) {
			a_typeDef.push(type);

			if (length) {
				a_typeDef.push(+length);
			}
			if (decPoint) {
				a_typeDef.push(+decPoint);
			}
		});

		return a_typeDef;
	},

	getFieldXpdInfo: function (s_name, o_entity, o_column) {

		var s_splitter = "_";
		var s_field = "";
		var s_prop = "";

		var a_name = s_name.split(s_splitter);

		if (a_name.length > 1) {
			s_field = a_name.shift();
			s_prop = a_name.join(s_splitter);
		}
		else {
			s_field = s_prop = a_name.join("");
		}

		var s_xpd;

		// Passed definition already has a XPD attached
		if (o_column && o_column.xpd) {
			s_xpd = o_column.xpd;
		}

		// Has not - look in entity field definition for it
		else {

			var a_columns =  Ext.Array.from(o_entity.columns);

			var o_field = Ext.Array.findBy(a_columns, function(o_col) {
				return o_col.name.toLowerCase() == s_field.toLowerCase();
			});

			s_xpd = o_field && o_field.xpd ? o_field.xpd : null;
		}

		// Make sure we get an extension on the XPD in case none is present
		if (s_xpd && s_xpd.split(".").length == 1) {
			s_xpd += ".xpd";
		}

		// Check if this gave us a XPD to use
		var o_xpd = s_xpd ? ip2.resource.get(s_xpd) : null;
		if (!o_xpd) {
			return;
		}

		var o_properties = o_xpd.allProperties || {};

		// Overload - somewhere before this all names are uppercased
		// so CURPROP_title becomes CURPROP_TITLE - this means
		// that camelcased XPD properties are not supported!
		// HACK: Lowercasing s_prop and matching against lowercased properties
		var o_definition = (function () {
			var o_return;
			Ext.Object.each(o_properties, function (s_key, o_prop) {
				if (s_key.toLowerCase() == s_prop.toLowerCase()) {
					// Use s_key as s_prop now, since it is the real prop key
					s_prop = s_key;
					o_return = o_prop;
				}
			});
			return o_return;
		})();

		if (!o_definition) {
			return;
		}

		// We may have stuff we need translated
		Ext.Object.each(o_definition, function (s_key, value) {
			if (Ext.isString(value)) {
				o_definition[s_key] = ip2.xpd.translate(o_xpd, value);
			}
		});

		return {
			xpd: o_xpd,
			definition: o_definition,
			field: s_field,
			property: s_prop
		};
	},

	applyFieldXpdInfo: function (o_column, o_info) {

		// For now we pick certain properties from the XPD definition and
		// apply it to our column
		// TODO: This needs to be automated, so the XPD property definition
		// can create this column definition on its own
		var o = o_info.definition;
		var o_property = {};
		if (o.title) {
			o_property.title = o.title;
		}
		if (o["enum"]) {
			o_property["enum"] = o["enum"];
		}
		if (o.context && o.context.grid) {
			ip2.defaults(o_property, o.context.grid);
		}

		// We are forced to sanitize this - we need rules for this!
		Ext.Object.each(o_property, function (s_key, value) {
			switch (s_key) {
				case "title":
					o_property.header = value;
					break;
				default: break;
			}
		});

		// If no "from" definition is made, build the generic one
		/*if ( !o_column.from ) {
		o_column.from = "( jsonGetStr("+o_info.field+", ""+o_info.name+"") )";
		}*/

		ip2.defaults(o_column, o_property);
	},

	dbtypes2js: function (o_field) {
		o_field = ip2.dbFldInfo(o_field);
		if (!o_field.type) {
			return "string";
		}
		switch (o_field.baseType) {
			case "char" :
			case "varchar" :
				return "string";
			case "numeric" :
			case "dec" :
			case "decimal" :
				var n_per = o_field.prec || o_field.dec || 0;
				return n_per ? "number" : "int";
			case "float" :
				return "number";
			case "smallint" :
			case "bigint" :
			case "int" :
			case "integer" :
				return "int";
			case "time" :
				return "string";
			case "timestamp":
			case "isotimestamp":
			case "date" :
				return o_field.native ? "date" : "string";
			default :
				return "auto";
		}
	},

	dbFldInfo: function (o_field) {
		// Already done or can not be enhanced
		if (!o_field.type || o_field.formatLen) {
			return o_field;
		}

		var tix = o_field.type.indexOf("(");
		if (tix > 0) {
			o_field.baseType = o_field.type.substr(0, tix);
			o_field.lenDesc = o_field.type.slice(tix + 1, o_field.type.indexOf(")"));
		}
		else {
			o_field.baseType = o_field.type;
		}

		o_field.baseType = o_field.baseType.toLowerCase();

		if (o_field.lenDesc) {
			var comma = o_field.lenDesc.indexOf(",");
			if (comma > 0) {
				o_field.len = parseInt(o_field.lenDesc.slice(0, comma), 10);
				o_field.prec = parseInt(o_field.lenDesc.slice(comma + 1), 10);
			}
			else {
				o_field.len = parseInt(o_field.lenDesc, 10);
			}
		}

		switch (o_field.baseType) {
			case "smallint" :
				o_field.formatLen = 5;
				break;
			case "bigint" :
			case "int" :
			case "integer" :
				o_field.formatLen = 10;
				break;
			case "date" :
				o_field.formatLen = 10;
				break;
			case "time" :
				o_field.formatLen = 8;
				break;
			case "timestamp":
				o_field.formatLen = 26;
				break;
			default :
				o_field.formatLen = o_field.len;
		}

		// Todo "formatLen" need to derive lengt from edit codes and edit words

		return o_field;
	},

	dbtypeLength: function (o_field) {
		o_field = ip2.dbFldInfo(o_field);
		return o_field.formatLen;
	},

	getEntityColumnTitle: function(column, model) {
		var title = (column.title) ? column.title : column.name;
		return ip2.translateTitle(title, model);
	},

	translateTitle: function(title, model, language, exact) {
		if (!title) {
			return "";
		}
		if (typeof title == "object") {
			return ip2.i18n(title);
		}

		var userLanguage = (language) ? language : ip2.getUserLanguageCode();
		var exactLanguage = (exact) ? exact : false;
		var texts = ip2.getTextByLanguage(userLanguage, model);
		if (texts && texts[title.toLowerCase()]) {
			return texts[title.toLowerCase()];
		}
		else if (exactLanguage) {
			return "";
		}
		else if (userLanguage.indexOf("-") > -1) {
			texts = ip2.getTextByLanguage(userLanguage.split("-")[0], model);
			if (texts && texts[title.toLowerCase()]) {
				return texts[title.toLowerCase()];
			}
		}
		return title;
	},

	getTextByLanguage: function(language, model) {
		var texts;
		var key;
		var subKey;
		if (!model.languages) {
			return null;
		}
		if (language.indexOf("-") == -1) {
			key = language;
		}
		else {
			key = language.split("-")[0];
			subKey = language.split("-")[1];
		}

		if (subKey) {
			if (model.languages[key] && model.languages[key][subKey] && model.languages[key][subKey].text) {
				texts = model.languages[key][subKey].text;
			}
		}
		else if (model.languages[key] && model.languages[key].text) {
			texts = model.languages[key].text;
		}
		return texts;
	},

	getUserLanguageCode: function() {

		var i_self = this;
		var i_user = i_self.getUser();

		return i_user.get("USRLNG") || "en";
	},

	pick: function(obj) {

		var copy = {};
		var keys = Ext.Array.slice.apply(Ext.Array, Ext.Array.slice(arguments, 1));

		Ext.Array.each(keys, function(key) {
			if (obj.hasOwnProperty(key)) {
				copy[key] = obj[key];
			}
		});

		return copy;
	},

	dmdParentModels: function(cfg, model) {
		var models = {};
		models.dmd = cfg.dmd;
		models.items = [];
		Ext.Array.each(model.relations, function(relation) {
			if (relation.between.entity.toLowerCase() == cfg.entityName.toLowerCase() && relation.and == "many") {
				models.items.push(relation);
			}
			else if (relation.and.entity.toLowerCase() == cfg.entityName.toLowerCase() && relation.between == "many") {
				models.items.push(relation);
			}
		});
		return models;
	},

	dmdChildModels: function(o__config, model) {

		var o_models = {};
		o_models.dmd = o__config.dmd;
		o_models.items = [];

		Ext.Array.each(model.relations, function(o_relation) {
			if (o_relation.between.entity.toLowerCase() == o__config.entityName.toLowerCase() && o_relation.and != "many") {
				o_models.items.push(o_relation);
			}
			else if (o_relation.and.entity.toLowerCase() == o__config.entityName.toLowerCase() && o_relation.between != "many") {
				o_models.items.push(o_relation);
			}
		});
		return o_models;
	},

	dmdAffiliations: function(o_config, o_model) {

		var o_entity = Ext.Array.findBy(o_model.entities, function(o_value) {
			return o_value.name == o_config.entityName;
		});

		return o_entity ? o_entity.affiliations || [] : [];
	},

	dmdTranslate: function(dmd, key, language) {

		if (!key) {
			return "";
		}

		var i_user = ip2.getUser();

		language = language || i_user ? i_user.get("USRLNG") : ip2.session.getLanguage();

		if (Ext.isObject(key)) {
			key = key[Object.keys(key)[0]];
		}

		var languages;
		var chkValue = key.toLowerCase();
		var ret;

		if (dmd.languages) {

			languages = dmd.languages;
		}
		else if (dmd.dmdModel && dmd.dmdModel.languages) {

			languages = dmd.dmdModel.languages;
		}
		else {

			return key;
		}

		if (languages[language]) {
			ret = languages[language].text[chkValue] || key;
		}
		else {
			ret = key;
		}

		return ret;
	},

	getProxyByEntity: function(o_config) {

		var i_self = this;

		var o_model = null;

		if (!Ext.isObject(o_config.model)) {

			var o_dmd = o_config.dmd;

			if (Ext.isString(o_config.dmd)) {
				o_dmd = i_self.resource.get(o_config.dmd);
			}

			var o_clone = Ext.clone(o_dmd);

			o_model = Ext.clone(o_clone.model);
		}
		else {
			o_model = Ext.clone(o_config.model);
		}

		var s_entity = o_config.entityName || o_config.entity;

		var o_found = Ext.Array.findBy(o_model.entities, function(o_value) {
			return o_value.name == s_entity;
		});

		var o_entity = Ext.clone(o_found);

		Ext.apply(o_entity, {
			name: s_entity
		});

		o_config.uiName = o_config.view || o_config.uiName || "portfolio";
		o_config.context = o_config.context || "grid";

		var o_proxy = o_model.proxy;

		o_proxy.portfolio = o_proxy.hasOwnProperty("portfolio") ? o_proxy.portfolio : true;

		var o_reader = o_proxy.reader || {
			type: "json",
			rootProperty: "rows",
			totalProperty: "totalRows"
		};

		var o_writer = o_proxy.writer || {
			type: "json",
			writeAllFields: false
		};

		var s_xtype = o_proxy.xtype || "Ext.data.proxy.Ajax";

		var a_url = [];

		var s_url = o_proxy.url;

		var o_params = {};

		if (o_proxy.portfolio) {

			Ext.apply(o_params, {
				entity: o_entity.name,
				action: o_config.action || "getRows",
				dmdFile: o_config.dmd,
				uiName: o_config.uiName,
				context: o_config.context
			});

			if (o_config.dataLibrary) {
				Ext.apply(o_params, {
					dataLibrary: o_config.dataLibrary
				});
			}

			var s_hive = o_model.proxy.defaultHive || "ip2-services/";

			var a_match = s_url.match("ip2-services") || [];

			if (a_match.length == 0) {
				a_url.push(s_hive);
			}
		}
		else {

			Ext.apply(o_params, {
				dmdFile: o_config.dmd,
				uiName: o_config.uiName,
				context: o_config.context
			});

			var i_template = new Ext.XTemplate(s_url);
			i_template.compile();

			s_url = i_template.apply({
				entity: o_entity.name,
				dmd: o_config.dmd,
				view: o_config.uiName
			});

			i_template = new Ext.XTemplate(o_reader.rootProperty);
			i_template.compile();

			o_reader.rootProperty = i_template.apply({
				entity: o_entity.name
			});
		}

		a_url.push(s_url);

		a_url.filter(function(s_value) {
			return s_value;
		});

		var o_readerConfig = Ext.apply(o_reader, {
			type: o_reader.type || "json",
			rootProperty: o_reader.rootProperty || "rows",
			totalProperty: o_reader.totalProperty || "totalRows",
		});

		var o_writerConfig = Ext.apply(o_writer, {
			$configStrict: false,
			type: "json",
			writeAllFields: false,
			getRecordData: function(i_record) {

				var i_writer = this;
				var b_phantom = i_record.phantom === true;
				var b_writeAll = i_writer.writeAllFields || b_phantom;
				var a_fields = i_record.getFields();
				var o_data = {};

				if (b_writeAll) {

					Ext.Array.each(a_fields, function(i_field) {
						if (i_field.persist) {
							var s_key = i_field.name;
							var value = i_record.get(s_key);
							o_data[s_key] = i_writer.formatData(value, i_field);
						}
					});
				}
				else {

					var o_changes = i_record.getChanges();

					Ext.Object.each(o_changes, function(s_key, value) {

						Ext.Array.each(a_fields, function(i_field) {
							if (i_field.name == s_key) {
								value = i_writer.formatData(value, i_field);
								return false;
							}
						});

						o_changes[s_key] = value;
					});

					Ext.apply(o_data, o_changes);

					if (!b_phantom) {
						o_data[i_record.idProperty] = i_record.getId();
					}
				}

				return o_data;
			},
			formatData: function(value, i_field) {

				var i_writer = this;

				var o_column = i_field.column || {};
				var o_editor = o_column.editor || {};
				var f_converter = o_editor.converter || Ext.emptyFn;

				f_converter.call(i_writer, value, i_field);

				return value;
			}
		});

		var i_proxy = Ext.create(s_xtype, {
			url: a_url.join(""),
			simpleSortMode: true,
			$configStrict: false,
			limitParam: o_proxy.limitParam || "limit",
			extraParams: o_params,
			reader: o_readerConfig,
			writer: o_writerConfig,
			actionMethods: o_proxy.actionMethods || {},
			extractResponseData: function(o_response) {

				var i_proxy = this;

				var i_reader = i_proxy.getReader();

				var s_rootProperty = i_reader.getRootProperty();
				var s_totalProperty = i_reader.getTotalProperty();

				Ext.USE_NATIVE_JSON = false;

				var o_data = o_response.responseJson || Ext.decode(o_response.responseText, true) || {};

				var a_rows = o_data[s_rootProperty] || o_data || [];

				//Ext.USE_NATIVE_JSON = true;
				var a_result = Ext.Array.from(a_rows);

				var a_selected = Ext.Array.from(o_data.selected);

				var n_selected = a_selected.length;

				a_result = a_selected.concat(a_result);

				if (s_rootProperty) {
					o_data[s_rootProperty] = a_result;
					o_data[s_totalProperty] = o_data[s_totalProperty] + a_selected.length;
				}

				return o_data;
			},
			afterRequest: function(i_request, b_success) {

				var a_records = [];
				if (i_request.getOperation()) {
					a_records = i_request.getOperation().getRecords() || [];
				}

				Ext.Array.each(a_records, function(i_record) {
					i_record.commit(true);
				});
			},
			listeners: {
				exception: {
					fn: function(i_proxy, i_response, i_operation) {

						var s_type = i_response.responseType;

						Ext.USE_NATIVE_JSON = false;

						var o_result = Ext.decode(i_response.responseText, true);

						//Ext.USE_NATIVE_JSON = true;

						if (s_type == "json") {
							o_result = i_response.responseJson;
						}

						o_result = o_result || {
							stmt: i_response.aborted ? "request aborted" : "",
							message: i_response.aborted ? "request aborted" : ""
						};

						var i_requst = i_operation.getRequest();

						var o_params = i_requst.getParams();
						var s_url = i_requst.getUrl();

						var o_config = {
							statement: o_result.stmt,
							params: o_params,
							url: s_url
						};

						if (Portfolio.util.Message) {
							Portfolio.util.Message.Add("Proxy exception", {
								message: o_result.message,
								params: o_config
							}, "sql");
						}
					}
				}
			}
		});

		return i_proxy;
	},

	extractKeys: function (i_model, i_record) {

		var o_value = {};
		var i_proto = i_model.prototype;
		var o_entity = i_proto.entity;

		var keys = o_entity.idColumn || o_entity.primaryKey || [];

		if (Ext.isObject(keys)) {
			keys = Ext.Object.getValues(keys);
		}

		var a_keys = Ext.Array.from(keys);

		Ext.Array.each(a_keys, function (s_property) {
			o_value[s_property] = i_record.get(s_property);
		});

		return o_value;
	},

	isEqual: (function () {

		// Internal recursive comparison function for `isEqual`.
		var eq = function(a, b, aStack, bStack, path, handler) {
			// Compare `[[Class]]` names.
			var className = toString.call(a);
			var bClassName = toString.call(b);

			// Identical objects are equal. `0 === -0`, but they aren't identical.
			// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
			if (a === b) {
				return handler(a !== 0 || 1 / a == 1 / b, a, b, path, className, bClassName);
			}

			// A strict comparison is necessary because `null == undefined`.
			if (a == null || b == null) {
				return handler(a === b, a, b, path, className, bClassName);
			}

			// Unwrap any wrapped objects.
			if (className != bClassName) {
				return handler(false, a, b, path, className, bClassName);
			}

			switch (className) {
			// Strings, numbers, dates, and booleans are compared by value.
				case "[object String]":

					// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
					// equivalent to `new String("5")`.
					return handler(a == String(b), a, b, path, className, bClassName);
				case "[object Number]":

					// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
					// other numeric values.
					return handler(a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b), a, b, path, className, bClassName);
				case "[object Date]":
				case "[object Boolean]":

					// Coerce dates and booleans to numeric primitive values. Dates are compared by their
					// millisecond representations. Note that invalid dates with millisecond representations
					// of `NaN` are not equivalent.
					return handler(+a == +b, a, b, path, className, bClassName);

					// RegExps are compared by their source patterns and flags.
				case "[object RegExp]":
					return handler(a.source == b.source &&
					a.global == b.global &&
					a.multiline == b.multiline &&
					a.ignoreCase == b.ignoreCase, a, b, path, className, bClassName);
			}
			if (typeof a != "object" || typeof b != "object") {
				return false;
			}

			// Assume equality for cyclic structures. The algorithm for detecting cyclic
			// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
			var length = aStack.length;
			while (length--) {
				// Linear search. Performance is inversely proportional to the number of
				// unique nested structures.
				if (aStack[length] == a) {
					return handler(bStack[length] == b, bStack[length], b, path, className, bClassName);
				}
			}

			// Objects with different constructors are not equivalent, but `Object`s
			// from different frames are.
			var aCtor = a.constructor;
			var bCtor = b.constructor;

			if (aCtor !== bCtor && !(ip2.isFunction(aCtor) && (aCtor instanceof aCtor) &&
			ip2.isFunction(bCtor) && (bCtor instanceof bCtor))
			&& ("constructor" in a && "constructor" in b)) {
				return handler(false, a, b, path, className, bClassName);
			}

			// Add the first object to the stack of traversed objects.
			aStack.push(a);
			bStack.push(b);

			var size = 0;
			var result = true;

			// Recursively compare objects and arrays.
			if (className == "[object Array]") {
				// Compare array lengths to determine if a deep comparison is necessary.
				size = a.length;
				result = size == b.length;
				if (result) {
					// Deep compare the contents, ignoring non-numeric properties.
					while (size--) {
						if (!(result = eq(a[size], b[size], aStack, bStack, String(path ? path + "." + size : size), handler))) {
							break;
						}
					}
				}
			}
			else {
				// Deep compare objects.
				for (var key in a) {
					if (a.hasOwnProperty(key)) {
						// Count the expected number of properties.
						size++;

						// Deep compare each member.
						if (!(result = eq(a[key], b[key], aStack, bStack, String(path ? path + "." + key : key), handler))) {
							break;
						}
					}
				}

				// Ensure that both objects contain the same number of properties.
				/*if (result) {
						for (key in b) {
						  if (ip2.has(b, key) && !(size--)) break;
						}
						result = !size;
					  }*/
			}

			// Remove the first object from the stack of traversed objects.
			aStack.pop();
			bStack.pop();
			return result;
		};

		return function(a, b, handler) {

			return eq(a, b, [], [], "", handler || function(estimatedEq, a, b, path, aType, bType) {
				return estimatedEq;
			});
		};
	})(),

	map: function(obj, fn, context) {

		var ret = Ext.isArray(obj) ? [] : {};

		ip2.each(obj, function(val, key) {
			ret[key] = fn.call(context, val, key, obj);
		});

		return ret;
	},

	cleanPath: function(s_url) {

		return s_url == null ? '' : (s_url.indexOf('/') == 0 ? '/' : '') + Ext.Array.filter(s_url.split('/'), Boolean).join('/');
	},

	getContainerHive: function(container) {
		var hive;

		if (!container) {
			return ip2.previousHive;
		}

		if (container.attr && container.attr.hive) {
			hive = container.attr.hive;
		}
		else if (container.fromParentTab && container.fromParentTab.hive) {
			hive = container.fromParentTab.hive;
		}

		if (!hive) {
			return ip2.previousHive;
		}

		ip2.previousHive = hive;
		return hive;
	},

	clone: function(o_value) {

		return Ext.clone(o_value);
	},

	each: function(obj, fn, context) {

		var objectProto = Object.prototype;
		var arrayProto = Array.prototype;

		// Array proto:
		var nativeForEach = arrayProto.forEach;
		var nativeSlice = arrayProto.slice;
		var nativeConcat = arrayProto.concat;
		var nativeIndexOf = arrayProto.indexOf;

		// Object proto:
		var toString = objectProto.toString;
		var nativeKeys = objectProto.keys;
		var hasOwn = objectProto.hasOwnProperty;

		if (obj == null) {
			return null;
		}
		if (nativeForEach == obj.forEach) {
			obj.forEach(fn, context);
		}
		else if (toString.call(obj) == '[object Array]') {
			for (var i = 0, l = obj.length; i < l; i++) {
				fn.call(context, obj[i], i, obj);
			}
		}
		else {
			for (var key in obj) {
				if (hasOwn.call(obj, key)) {
					fn.call(context, obj[key], key, obj);
				}
			}
		}
	},

	unique: function(prefix) {

		var i_self = this;

		var n_number = i_self.getUniqueNo();
		var s_prefix = prefix || "";

		var a_result = [];

		if (s_prefix) {
			a_result.push(s_prefix);
		}

		a_result.push(n_number);

		var s_result = a_result.join("");

		n_number++;

		i_self.setUniqueNo(n_number);

		return s_result;
	},

	getDcTimestamp: function () {

		var i_date = new Date();
		var n_unique = ip2.unique();

		return [
			i_date.getYear(),
			i_date.getMonth(),
			i_date.getDay(),
			i_date.getHours(),
			i_date.getMinutes(),
			i_date.getMilliseconds(),
			i_date.getTime(),
			n_unique
		].join("");
	},

	createIsoTimeStamp: function () {
		return Ext.Date.format(new Date(), "Y-m-d-H.i.s.u") + "000";
	},

	getWindowSize: function() {

		return Ext.getBody().getSize();
	},

	storeId: function() {

		return ip2.unique("store-");
	},

	isEvalTpl: function(s_tpl) {

		var i_self = this;

		if (Ext.isString(s_tpl)) {

			if (s_tpl[0] == '!') {
				return i_self.isEvalTpl.MACRO;
			}

			if (/<%(.*?)%>/.test(s_tpl)) {
				return i_self.isEvalTpl.REGULAR;
			}
		}

		return i_self.isEvalTpl.NO;
	},

	evalTpl: function(tpl, data) {

		if (typeof tpl == 'string') {
			var returnType = null;
			var returnValue = null;

			if (tpl[0] == '!') {
				tpl = '<% !data.' + tpl.slice(1) + ' %>';
			}

			tpl = tpl.replace(/<%(.*?)%>/g, function(_, a) {
				var fn = new Function('data', 'with (data) { return (' + a + '); }');

				var ret = fn.call(data, data);

				returnType = (typeof ret);
				returnValue = (ret);

				return ret;
			});

			if (returnType && String(returnValue) === tpl) {
				return returnValue;
			}
			else {
				return tpl;
			}
		}

		return tpl;
	},

	microTemplate: function(s_value, o_data) {
		return s_value.replace(/\$([\w]+)/gi, function(_, a) {
			return o_data[a] || '';
		});
	},

	once: function(func) {
		var ran = false, memo;
		return function() {
		if (ran) return memo;
		ran = true;
		memo = func.apply(this, arguments);
		func = null;
		return memo;
		};
	},

	first: function(arr, fn, context) {

		var val;

		for (var i = 0, l = arr.length; i < l; i++) {
			val = fn.call(context, arr[i], i, arr);
			if (val != null) {
				return val;
			}
		}

		return null;
	},

	isObject: function(value) {

		return Ext.isObject(value);
	},

	isArray: function(value) {

		return Ext.isArray(value);
	},

	getProperty: function(path, context, handler) {

		function findObject(path, context, handler) {
			var isArray = toString.call(path) == '[object Array]',
				isString = toString.call(path) == '[object String]';

			if (!isString && !isArray)
				return path;

			var obj = context || window,

				// ['a', 'b', 'c'], "a.b.c", "a/b/c" -> ['a', 'b', 'c']
				// [], "", "////" -> []
				// "a//b" -> ['a', 'b']
				fragments = isArray ? path.slice() : path.match(/([^.\/]+)/g) || [],
				fragment;

			while ((obj !== null) && fragments.length) {
				fragment = fragments.shift();

				if (handler) {
					obj = handler(obj[fragment], fragment, obj);
				}
				else {
					obj = obj[fragment];
				}
			}

			return obj;
		}

		return findObject(path, context, toString.call(handler) == '[object Function]' ? handler : null);
	},

	getSiteConfig: function(f_callback) {

		f_callback = f_callback || Ext.emptyFn;

		Ext.Ajax.request({
			url: "/ip2-services/wshproxy.aspx?action=getsite",
			success: function(i_response) {

				var o_settings = Ext.decode(i_response.responseText, true);

				f_callback(o_settings);
			}
		});
	},

	getRelationKeysFromRecord: function (i_record, o_relation, b_between) {

		var o_keys = {};
		var o_value = b_between ? o_relation.between : o_relation.and;
		var o_opposit = b_between ? o_relation.and : o_relation.between;

		Ext.Array.each(o_value.on, function (o_on, n_index) {
			var s_relation = o_opposit.on[n_index].name;
			var s_field = o_on.name;

			o_keys[s_relation] = i_record.get(s_field);
		});

		return o_keys;
	},

	keysToFilter: function (keys) {

		var a_keys = [];
		var a_filter = [];

		// Sanitize the keys in an array - turn them into object if they are stringified
		if (Ext.isArray(keys)) {
			Ext.Array.each(keys, function (key, n_index) {
				if (Ext.isString(key)) {
					a_keys.push(Ext.decode(key));
				}
				else if (Ext.isObject(key)) {
					a_keys.push(key);
				}
			});
		}
		else {
			if (Ext.isString(keys)) {
				a_keys.push(Ext.decode(keys));
			}
			else if (Ext.isObject(keys)) {
				a_keys.push(keys);
			}
		}

		Ext.Array.each(a_keys, function (o_key) {
			Ext.Object.each(o_key, function (s_key, value) {
				a_filter.push({
					property: s_key,
					where: "in \("  + (Ext.isString(value) ? "\'" + value + "\'" : value)  + "\)"
				});
			});
		});

		return Ext.encode(a_filter);
	},

	getTitleFromRecord: function (i_record) {

		var o_entity = ip2.getEntityFromRecord(i_record);

		var a_title = [];

		var s_idColumn = o_entity.idColumn;

		Ext.Object.each(o_entity.columns, function (s_key, o_column) {

			if (o_column.isTitle) {

				var s_title = i_record.get(s_key);

				var o_test = Ext.decode(s_title, true);

				if (Ext.isObject(o_test)) {
					s_title = ip2.i18n(o_test);
				}

				a_title.push(s_title);
			}
		});

		a_title = Ext.Array.filter(a_title, function(s_value) {
			return s_value;
		});

		if (a_title.length === 0) {

			a_title.push(i_record.get(i_record.idProperty));
		}

		return a_title.join(" ");
	},

	messageBox: function (o_config) {

		o_config = o_config || {};

		Ext.applyIf(o_config, {
			message: "",
			icon: Ext.Msg.INFO,
			cancelFn: Ext.emptyFn,
			yesFn: Ext.emptyFn,
			noFn: Ext.emptyFn,
			yesText: "",
			noText: "",
			cancelText: "",
			title: "",
			closable: true
		});

		var s_msg = o_config.message;
		var s_icon = o_config.icon;

		var s_yes = o_config.yesText;
		var f_yes = o_config.yesFn;

		var s_cancel = o_config.cancelText;
		var f_cancel = o_config.cancelFn;

		var f_close = o_config.cancelFn;

		var s_no = o_config.noText;
		var f_no = o_config.noFn;

		var s_title = o_config.title;

		var b_closable = o_config.closable;

		var b_first = true;

		var o_text = {};
		var s_default = "";

		if (s_yes) {
			o_text.yes = s_yes;
			s_default = "button-yes";
		}

		if (s_no) {
			o_text.no = s_no;
			if (!s_default) {
				s_default = "button-no";
			}
		}

		if (s_cancel) {
			o_text.cancel = s_cancel;
			if (!s_default) {
				s_default = "button-no";
			}
		}


		var i_msgBox = Ext.widget("window", {
			title: s_title,
			modal: true,
			draggable: false,
			ui: "message",
			layout: "fit",
			listeners: {
				destroy: {
					fn: function () {
						f_close();
					}
				}
			}
		});



		var a_items = [];

		if (o_text.cancel) {

			a_items.push({
				tabIndex: 2,
				xtype: "button",
				itemId: "button-cancel",
				text: o_text.cancel,
				minWidth: 80,
				handler: function (i_button) {
					f_cancel();
					i_msgBox.close();
				}
			});

			b_first = false;
		}

		if (o_text.no) {

			a_items.push({
				tabIndex: 1,
				xtype: "button",
				itemId: "button-no",
				text: o_text.no,
				minWidth: 80,
				handler: function (i_button) {

					f_no();
					i_msgBox.close();
				}
			});
		}

		if (a_items.length > 0) {

			a_items.unshift({
				xtype: "tbspacer",
				width: 8
			});

			a_items.push({
				xtype: "tbfill"
			});
		}



		if (o_text.yes) {

			a_items.push({
				tabIndex: 0,
				xtype: "button",
				text: o_text.yes,
				itemId: "button-yes",
				ui: "primary-button",
				minWidth: 80,
				handler: function (i_button) {

					f_yes();
					i_msgBox.close();
				}
			});
		}

		if (a_items.length > 1) {

			a_items.push({
				xtype: "tbspacer",
				width: 8
			});
		}

		var i_component = Ext.widget("component", {
			hidden: true,
			cls: Ext.baseCSSPrefix + "message-box-icon"
		});

		i_msgBox.add({
			xtype: "panel",
			minWidth: 300,
			layout: {
				type: "hbox",
				align: "stretch"
			},
			items: [
				i_component,
				{
					xtype: "container",
					flex: 1,
					html: s_msg
				}
			]
		});

		var s_cls = i_msgBox.messageIconCls;

		if (s_cls) {
			i_component.removeCls(s_cls);
		}

		if (s_icon) {
			//i_component.show();
			i_component.addCls(Ext.baseCSSPrefix + 'dlg-icon');
			i_component.addCls(i_msgBox.messageIconCls = s_icon);
		}
		else {
			i_component.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
			//i_component.hide();
		}

		if (a_items.length > 0) {

			var i_toolbar = Ext.widget("toolbar", {
				ui: "footer",
				dock: "bottom",
				items: a_items,
				layout: a_items.length > 1 ? "hbox" : "center",
				listeners: {
					afterrender: {
						fn: function (i_toolbar) {

							if (s_default) {
								var i_button = i_toolbar.getComponent(s_default);

								if (i_button) {
									i_button.focus(false, 10);
								}
							}
						},
						buffer: 100
					}
				}
			});

			i_msgBox.addDocked(i_toolbar);
		}

		i_msgBox.show();


		return i_msgBox;
	},

	define: function(req, module) {
		if (ip2.isFunction(req)) {
			module = req;
			req = [];
		}

		module && module();
	},

	isFunction: function(value) {

		return Ext.isFunction(value);
	},

	getWepshopProxyUrl: function(widgetWrapper) {

		var serviceUrl;
		var proxyUrl = "resources/services/wshproxy.aspx";

		if (widgetWrapper) {
			var hive = ip2.getWidgetHive(widgetWrapper);

			if (hive && hive.length > 0) {

				serviceUrl = "/" + hive + "/" + proxyUrl;
			}
		}

		if (!serviceUrl) {

			serviceUrl = "/wsh-services/" + proxyUrl;
		}

		return serviceUrl;
	},

	getWidgetHive: function(widgetWrapper) {

		var hive;
		var container;

		if (widgetWrapper.attr || widgetWrapper.fromParentTab) {

			container = widgetWrapper;
		}
		else {

			var dataTrackerParent = widgetWrapper.getDataParent().self;

			container = (dataTrackerParent.fromTab) ? dataTrackerParent.fromTab : dataTrackerParent;
		}
		if (container) {

			var hive = ip2.getContainerHive(container);
		}
		return hive;
	},

	i18nFunctionFactory: function(val, handler) {
		function factory(handler_) {
			var handler = ip2.getProperty(handler_, window);
			return function(val) {
				if (ip2.isObject(val)) {
					return ip2.map(val, function(value) {
						return handler(value);
					});
				}
				else if (ip2.isStringedObject(val)) {
					// TODO should we return same type as we get?
					//   return ip2.JSON.stringify(...)
					return ip2.map(Ext.decode(val), function(value) {
						return handler(value);
					});
				}
				else {
					return handler(val);
				}
			};
		}
		if (arguments.length == 1) {
			return factory(val);
		}
		else {
			return factory(handler)(val);
		}
	},

	urlFriendly: function(s_value) {

		// All output will be in lower case so no need to take care of case here:
		var o_charMap = {
			"À": "a",
			"Á": "a",
			"Â": "a",
			"Ã": "a",
			"Ä": "a",
			"Å": "a",
			"Æ": "ae",
			"Ç": "c",
			"È": "e",
			"É": "e",
			"Ê": "e",
			"Ë": "e",
			"Ì": "i",
			"Í": "i",
			"Î": "i",
			"Ï": "i",
			"Ð": "d",
			"Ñ": "n",
			"Ò": "o",
			"Ó": "o",
			"Ô": "o",
			"Õ": "o",
			"Ö": "o",
			"×": "x",
			"Ø": "oe",
			"Ù": "u",
			"Ú": "u",
			"Û": "u",
			"Ü": "u",
			"Ý": "y",
			"Þ": "t",
			"à": "a",
			"ì": "i",
			"÷": "",
			"þ": "t",
			"š": "s",
			"œ": "oe",
			"µ": "u",
			"ž": "z",
			"ÿ": "y",
			"è": "e",
			"ð": "edh",
			"ò": "o",
			"ù": "u",
			"ý": "y",
			"á": "a",
			"é": "e",
			"í": "i",
			"ñ": "n",
			"ó": "o",
			"ø": "oe",
			"ú": "u",
			"â": "a",
			"ê": "e",
			"î": "i",
			"ô": "o",
			"û": "u",
			"ß": "ss",
			"ã": "a",
			"ë": "e",
			"ï": "i",
			"õ": "o",
			"ü": "u",
			"ä": "a",
			"ö": "oe",
			"å": "aa",
			"æ": "ae",
			"ç": "c"
		};

		if (Ext.isEmpty(s_value)) {
			s_value = "";
		}

		if (Ext.isObject(s_value)) {

			var o_copy = Ext.clone(s_value);

			Ext.Object.each(o_copy, function(s_key, value) {

				var s_value = String(value).toLowerCase();

				// Preburn the characters against the char_map

				Ext.Object.each(o_charMap, function(s_key, value) {

					var n_index = value.indexOf(s_key);
					if (n_index > -1) {
						value = value.slice(0, n_index) + value + value.slice(n_index + 1);
					}
				});

				// Dashed will get squized, i.e:
				// hello--world -> hello-world
				value = value.replace(/\W+/g, "-");

				o_copy[s_key] = value;
			})

			s_value = o_copy;
		}
		else {

			s_value = String(s_value).toLowerCase();

			// Preburn the characters against the char_map

			Ext.Object.each(o_charMap, function(s_key, value) {

				var n_index = s_value.indexOf(s_key);
				if (n_index > -1) {
					s_value = s_value.slice(0, n_index) + value + s_value.slice(n_index + 1);
				}
			});

			// Dashed will get squized, i.e:
			// hello--world -> hello-world
			s_value = s_value.replace(/\W+/g, "-");
		}


		return s_value;
	},

	sliceString: function(str, start, end) {
		str = String(str);
		return str.slice.apply(str, [].slice.call(arguments, 1));
	},

	removeHtmlTags: function (html) {
		html = html || "";
		var i_node = document.createElement("div");
		i_node.innerHTML = html;
		return i_node.textContent.replace(/[\n\s]+/g, " ");
	},

	markdownToHtml: function (markdown) {

		return Portfolio.util.Handler.markdownConverter.Converter().makeHtml(markdown);
	},

	mask: function(s_message) {

		var i_self = this;

		s_message = s_message || i_self.i18n({
			da: "Henter...",
			en: "Loading..."
		});

		var i_body = Ext.getBody();

		if (!i_body.isMasked()) {
			i_body.mask(s_message);
		}
	},

	unmask: function() {

		var i_body = Ext.getBody();

		if (i_body.isMasked()) {
			i_body.unmask();
		}
	},

	loadWidgets: function (o_config) {

		var i_self = this;
		var a_widgets = i_self.widgets || {};

		o_config = o_config || {};

		if (a_widgets && a_widgets.length > 0) {
			if (o_config && Ext.isFunction(o_config.success) && !o_config.success.fired) {
				o_config.success.fired = true;
				o_config.success.call(this, o_config);
			}
			return;
		}

		a_widgets = []; // To prefent extra load later

		var o_encode = Ext.apply({}, {
			func: "listDirByExtension",
			pathList: o_config.path || "/wsh-widgets/",
			extnList: "widget"
		});

		Ext.Ajax.request({
			url: 'wsh-services/webBios.aspx?' + Ext.urlEncode(o_encode),
			success: function (o_response) {

				var o_result = Ext.decode(o_response.responseText, true) || {
					rows: [],
					success: false
				};
				if (o_result.rows.length > 0) {
					a_widgets = o_result.rows;
				}

				if (o_config && Ext.isFunction(o_config.success) && !o_config.success.fired) {
					o_config.success.fired = true;
					o_config.success.call(this, o_config);
				}
			}
		});
	},

	addLogout: function(f_logout) {

		var i_self = this;

		var a_functions = i_self.getLogoutFunctions();

		a_functions.push(f_logout);

		i_self.setLogoutFunctions(a_functions);
	},

	openExternalLink: function(s_url) {
		var i_window = window.open(s_url, "_blank");
		i_window.focus();
	},

	humanFileSize: function (bytes, si) {
		var thresh = si ? 1000 : 1024;
		if (Math.abs(bytes) < thresh) {
			return bytes + ' B';
		}
		var units = si ? ['kB','MB','GB','TB','PB','EB','ZB','YB']
			: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
		var u = -1;
		do {
			bytes /= thresh;
			++u;
		} while (Math.abs(bytes) >= thresh && u < units.length - 1);
		return bytes.toFixed(1) + ' ' + units[u];
	},

	toLowerCase: function(value) {

		var s_value = String(value);
		return s_value.toLowerCase();
	},

	toUpperCase: function(value) {
		var s_value = String(value);
		return s_value.toUpperCase();
	},

	formatNumber: function(value, mask, decimalSeparator, thousandSeparator) {
		if (!mask || isNaN(+value)) {
			return value;
		}

		var currencySymbols = '?$?';
		var currencySymbol = '';

		var replaceMaskChar = function(char) {
			var result = {};
			result.skip = true;
			switch (char) {
				case '#':
				case '0':
					result.skip = false;
					result.char = '';
				break;
				case '&':
					result.char = '&nbsp;';
				break;
				default:
					result.char = char;
				break;
			}
			return result;
		};
		var replaceIntegerMask = function() {
			var result = (mask[0][mCnt]) ? replaceMaskChar(mask[0][mCnt]) : { char: '', skip: false };
			mCnt--;

			if (result.char.length > 0 && Ext.Array.indexOf(currencySymbols, result.char) > -1) {
				currencySymbol = result.char;
				result.char = '';
			}

			return result;
		};
		var replaceDecimalMask = function() {
			var result = (mask[1][mCnt]) ? replaceMaskChar(mask[1][mCnt]) : { char: '', skip: false };
			mCnt++;
			return result;
		};

		if (!decimalSeparator) {
			decimalSeparator = Ext.util.Format.decimalSeparator;
		}
		if (!thousandSeparator) {
			thousandSeparator = Ext.util.Format.thousandSeparator;
		}

		var sign = '';
		if (mask.lastIndexOf('CR') > -1) {
			sign = 'CR';
			mask = mask.substr(0, mask.lastIndexOf('CR'));
		}
		else if (mask.lastIndexOf('-') > -1) {
			if (mask.lastIndexOf('-') == 0 || mask.lastIndexOf('-') == mask.length - 1) {
				sign = '-';
				mask = mask.substr(0, mask.lastIndexOf('-'));
			}
		}

		value = mask.charAt(0) == '-' ? -value : +value;
		var isNegative = value < 0 ? value = -value : 0;

		var mask = mask.split(decimalSeparator);

		//var nbrDec = (!mask[1]) ? 0 : ((mask[1].match(/0/g) == null) ? 0 :
		//	mask[1].match(/0/g).length) + ((mask[1].match(/#/g) == null) ? 0 : mask[1].match(/#/g).length);
		var nbrDec = 0;
		if (mask[1]) {
			if (mask[1].match(/0/g)) {
				nbrDec = mask[1].match(/0/g).length;
			}
			if (mask[1].match(/#/g)) {
				nbrDec += mask[1].match(/#/g).length;
			}
		}

		value = value.toFixed(nbrDec);
		value = value.split('.');

		// concat integer
		var Integer = '';
		var currencySymbol = '';
		var posLeadZero = mask[0] && mask[0].indexOf('0');
		var mCnt = mask[0].length - 1;

		mask[0] = mask[0].split('');
		value[0] = value[0].split('');
		for (var i = value[0].length - 1; i >= 0; i--) {
			var data = replaceIntegerMask();
			Integer = data.char + Integer;
			if (data.skip) {
				i++;
			}
			else {
				if (data.char == thousandSeparator) {
					mCnt--;
				}
				Integer = ((value[0][i] == '0' && posLeadZero == -1) ? '' : value[0][i]) + Integer;
			}
		}

		// insert leading zero
		if (posLeadZero > -1) {
			while (Integer.length < (mask[0].length - posLeadZero)) {
				var data = replaceIntegerMask();
				Integer = data.char + Integer;
				if (data.skip) {
					i++;
				}
				else {
					if (data.char == thousandSeparator) {
						mCnt--;
					}
					Integer = '0' + Integer;
				}
			}
		}

		// insert extra characters from mask in front of integer
		if (Integer.length > 0) {
			var prevChar = '';
			for (; mCnt >= 0;) {
				var data = replaceIntegerMask();
				if (data.char != '' && data.char != thousandSeparator && prevChar == thousandSeparator) {
					Integer = prevChar + Integer;
				}
				if ((data.char == '' || data.char == thousandSeparator) && prevChar == '*') {
					data.char = prevChar;
				}
				if (data.char != thousandSeparator) {
					Integer = data.char + Integer;
				}
				prevChar = data.char;
			}

			// insert currency symbol
			if (currencySymbol.length > 0) {
				Integer = currencySymbol + Integer;
			}
		}

		// concat decimal
		var Decimal = '';
		if (mask[1] && value[1]) {
			mCnt = 0;
			posLeadZero = (posLeadZero == -1) ? mask[1] && mask[1].indexOf('0') : 0;
			for (var i = 0; i < value[1].length; i++) {
				var data = replaceDecimalMask();
				Decimal += data.char;
				Decimal += (value[1][i] == '0' && posLeadZero == -1) ? '' : value[1][i];
			}
			mCnt = i;
		}

		// insert extra characters from mask after decimals
		if (Decimal.length > 0) {
			for (; mCnt < mask[1].length;) {
				var data = replaceDecimalMask();
				if (data.char != '-') {
					Decimal += data.char;
				}
			}
		}

		return Integer + (Decimal.length > 0 ? decimalSeparator + Decimal : '') + (isNegative ? sign : '');
	},

	formatNumeric: function(value, fieldlength, editcode, editword) {

		if (editcode && editcode.length > 0) {
			return ip2.formatNumber(value, ip2.formatEditCode(editcode, fieldlength));
		}
		else if (editword && editword.length > 0) {
			return ip2.formatNumber(value, ip2.formatEditWord(editword, fieldlength));
		}
		else {
			return value;
		}
	},

	formatEditCode: function(editcode, length, qdecfmt) {
		var record = null;
		if (!record) {
			return '';
		}

		var format = '';
		var len = 0;
		var dec = 0;
		var cnt = 0;
		var chr = '';
		var decfmt = (qdecfmt) ? qdecfmt : (ip2.navigator && ip2.navigator.system.settings.qdecfmt) ? ip2.navigator.system.settings.qdecfmt : '2';

		if (length.split) {
			len = parseInt(length.split(',')[0]);
			dec = (isNaN(parseInt(length.split(',')[1]))) ? 0 : parseInt(length.split(',')[1]);
			len = len - dec;
		}
		else {
			len = length;
		}

		// build mask before decimal point
		cnt = 0;
		if (record.data.zero.toLowerCase() == 'yes') {
			if (decfmt == '2' && record.data.jValue.toLowerCase() == '0,00 or 0') {
				chr = '0';
			}
			else {
				chr = '#';
			}
		}
		else {
			chr = '#';
		}
		for (var i = len; i > 0; i--) {
			cnt++;
			if (cnt > 3 && record.data.comma.toLowerCase() == 'yes') {
				cnt = 1;
				format = Ext.util.Format.thousandSeparator + format;
			}
			format = chr + format;
			chr = '#';
		}

		// build mask after decimal point
		if (dec > 0 && record.data.decimal.toLowerCase() == 'yes') {
			if (record.data.zero.toLowerCase() == 'yes') {
				if (decfmt != '2' && (record.data.blank.toLowerCase() == '0,00 or 0' || record.data.iValue.toLowerCase() == '0,00 or 0')) {
					chr = '0';
				}
				else {
					chr = '#';
				}
			}
			else {
				chr = '#';
			}
			format += Ext.util.Format.decimalSeparator;
			for (var i = 0; i < dec; i++) {
				format += chr;
				chr = '#';
			}
		}

		if (record.data.sign.toLowerCase() == 'cr') {
			format += 'CR';
		}
		else if (record.data.sign.toLowerCase() == '-(minus)') {
			format += '-';
		}
		return format;
	},

	getStackTrace: function(i_error) {

		if (!i_error.stack) {
			try {
				// IE requires the Error to actually be thrown or else the
				// Error's 'stack' property is undefined.
				throw i_error;
			}
			catch (i_error) {
				if (!i_error.stack) {
					return 0; // IE < 10, likely
				}
			}
		}

		var a_stack = i_error.stack.toString().split(/\r\n|\n/);
		var s_entry = a_stack[1];

		var a_entry = s_entry.split(".js");
		var a_sub = a_entry[0].split("(");

		var s_url = a_sub[1] + ".js";
		var a_lines = a_entry[1].split(")")[0].split(":");

		a_lines = a_lines.filter(function(s_value) {
			return s_value;
		});

		var o_result = {
			url: s_url,
			lineNr: Number(a_lines[0])
		};

		return o_result;
	},

	getWindow: function(o_config) {

		var i_self = this;

		o_config = o_config || {};

		var o_size = i_self.getWindowSize();

		if (o_config.height == null) {
			o_config.height = o_size.height * 0.6;
		}

		if (o_config.height == null) {
			o_config.height = o_size.width * 0.6;
		}

		var i_window = Ext.widget("window", Ext.apply({
			layout: "fit",
			height: o_size.height * 0.6,
			width: o_size.width * 0.6,
			constrain: true,
			maximizable: true,
			listeners: {
				afterrender: {
					fn: function (i_window) {

						var i_header = i_window.header;

						if (i_header && !i_window.modal) {

							var i_tabTool = Ext.widget("tool", {
								iconCls: "pf-navigate_minus",
								tooltip: "To tab",
								handler: function () {

									var i_panel = i_window.down("panel");
									var o_size = i_window.getSize();

									var o_restoreState = {
										size: o_size,
										position: i_window.getPosition()
									};

									if (i_panel) {

										var o_stateData = i_window.stateData || {};

										Ext.apply(o_stateData, {
											restoreState: o_restoreState
										});

										i_panel.setTitle(i_window.title);

										Portfolio.app.fireEvent("toTab", i_panel, o_stateData);
										i_window.close();
									}
								}
							});

							i_header.add(1, i_tabTool);
						}
					}
				}
			}
		}, o_config));

		return i_window;

	},

	createWorker: function() {

		var i_self = this;
		var a_workers = i_self.getWorkers();
		var i_worker = new Worker("/portfolio/app/util/Worker.js");

		a_workers.push(i_worker);

		i_self.setWorkers(a_workers);

		return i_worker;
	},

	removeWorker: function(i_worker) {

		var i_self = this;
		var a_workers = i_self.getWorkers();

		i_worker.terminate();

		Ext.Array.remove(a_workers, i_worker);

		i_self.setWorkers(a_workers);

	}

}, function() {


	Portfolio.util.Handler.initProfile();
	Portfolio.util.Handler.initGlobal();

	Portfolio.util.Handler.initPreset();
	Portfolio.util.Handler.getServerBaseConfig();

	Ext.apply(Ext.form.field.VTypes, {
		//  vtype validation function
		phone: function (val, field) {
			return true;
		},

		// vtype Text property: The error text to display when the validation function returns false
		phoneText: ip2.i18n({
			da: "Ikke en valid værdi",
			en: "Not a valid value"
		}),

		ERP: function (value, field) {
			var n_value = Number(value);
			if (!isNaN(n_value)) {
				if (n_value === 0) {
					return false;
				}
				return true;
			}
			return true;
		},

		ERPText: ip2.i18n({
			da: "Ikke en valid værdi",
			en: "Not a valid value"
		}),

		password: function (val, i_field) {

			var b_valid = true;

			var s_password = i_field.initialPasswordField;

			if (s_password) {

				var i_container = i_field.up("container");

				if (i_container) {

					var i_passwordfield;

					if (Ext.isString(s_password)) {
						i_passwordfield = i_container.down("#" + s_password);
					}
					else {
						i_passwordfield = s_password;
					}

					if (!i_passwordfield) {
						i_passwordfield = Ext.getCmp(s_password);
					}

					if (i_passwordfield) {

						var s_passValue = "";

						if (i_passwordfield.processRawValue) {

							s_passValue = i_passwordfield.processRawValue(i_passwordfield.getRawValue());
							b_valid = val === s_passValue;
						}
					}
				}
			}
			return b_valid;
		},

		passwordText: ip2.i18n({
			da: "Kodeordne er ikke ens",
			en: "Passwords do not match"
		})

	});

	var a_svgs = Ext.getBody().dom.querySelectorAll("img[src='/system/shared/collection/flags/flags.svg']");

	if (a_svgs.length === 0) {

		Ext.getBody().createChild({
			tag: "img",
			role: "presentation",
			src: "/system/shared/collection/flags/flags.svg",
			style: {
				position: "absolute",
				left: "-10000px",
				top: "-10000px",
				visibility: "hidden"
			}
		});

		Ext.Loader.loadScriptsSync([
			"/portfolio/flags-svg.css"
		]);
	}

	var Markdown = {
		Converter: function () {
			var pluginHooks = this.hooks = new HookCollection();
			pluginHooks.addNoop("plainLinkText");  // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
			pluginHooks.addNoop("preConversion");  // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
			pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml

			//
			// Private state of the converter instance:
			//

			// Global hashes, used by various utility routines
			var g_urls;
			var g_titles;
			var g_html_blocks;

			// Used to track when we're inside an ordered or unordered list
			// (see _ProcessListItems() for details):
			var g_list_level;

			this.makeHtml = function (text) {
				text = text || "";

				//
				// Main function. The order in which other subs are called here is
				// essential. Link and image substitutions need to happen before
				// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
				// and <img> tags get encoded.
				//

				// This will only happen if makeHtml on the same converter instance is called from a plugin hook.
				// Don't do that.
				if (g_urls)
					throw new Error("Recursive call to converter.makeHtml");

				// Create the private state objects.
				g_urls = new SaveHash();
				g_titles = new SaveHash();
				g_html_blocks = [];
				g_list_level = 0;

				text = pluginHooks.preConversion(text);

				// attacklab: Replace ~ with ~T
				// This lets us use tilde as an escape char to avoid md5 hashes
				// The choice of character is arbitray; anything that isn't
				// magic in Markdown will work.
				text = text.replace(/~/g, "~T");

				// attacklab: Replace $ with ~D
				// RegExp interprets $ as a special character
				// when it's in a replacement string
				text = text.replace(/\$/g, "~D");

				// Standardize line endings
				text = text.replace(/\r\n/g, "\n"); // DOS to Unix
				text = text.replace(/\r/g, "\n"); // Mac to Unix

				// Make sure text begins and ends with a couple of newlines:
				text = "\n\n" + text + "\n\n";

				// Convert all tabs to spaces.
				text = _Detab(text);

				// Strip any lines consisting only of spaces and tabs.
				// This makes subsequent regexen easier to write, because we can
				// match consecutive blank lines with /\n+/ instead of something
				// contorted like /[ \t]*\n+/ .
				text = text.replace(/^[ \t]+$/mg, "");

				// Turn block-level HTML blocks into hash entries
				text = _HashHTMLBlocks(text);

				// Strip link definitions, store in hashes.
				text = _StripLinkDefinitions(text);

				text = _RunBlockGamut(text);

				text = _UnescapeSpecialChars(text);

				// attacklab: Restore dollar signs
				text = text.replace(/~D/g, "$$");

				// attacklab: Restore tildes
				text = text.replace(/~T/g, "~");

				text = pluginHooks.postConversion(text);

				g_html_blocks = g_titles = g_urls = null;

				return text;
			};

			/*
				this.makeMarkdown = function(string) {

				  var ELEMENTS = [
					{
					  patterns: 'p',
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '\n\n' + innerHTML + '\n' : '';
					  }
					},
					{
					  patterns: 'br',
					  type: 'void',
					  replacement: '\n'
					},
					{
					  patterns: 'h([1-6])',
					  replacement: function(str, hLevel, attrs, innerHTML) {
						var hPrefix = '';
						for(var i = 0; i < hLevel; i++) {
						  hPrefix += '#';
						}
						return '\n\n' + hPrefix + ' ' + innerHTML + '\n';
					  }
					},
					{
					  patterns: 'hr',
					  type: 'void',
					  replacement: '\n\n* * *\n'
					},
					{
					  patterns: 'a',
					  replacement: function(str, attrs, innerHTML) {
						var href = attrs.match(attrRegExp('href')),
							title = attrs.match(attrRegExp('title'));
						return href ? '[' + innerHTML + ']' + '(' + href[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : str;
					  }
					},
					{
					  patterns: ['b', 'strong'],
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '**' + innerHTML + '**' : '';
					  }
					},
					{
					  patterns: ['i', 'em'],
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '_' + innerHTML + '_' : '';
					  }
					},
					{
					  patterns: 'code',
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '`' + innerHTML + '`' : '';
					  }
					},
					{
					  patterns: 'img',
					  type: 'void',
					  replacement: function(str, attrs, innerHTML) {
						var src = attrs.match(attrRegExp('src')),
							alt = attrs.match(attrRegExp('alt')),
							title = attrs.match(attrRegExp('title'));
						return '![' + (alt && alt[1] ? alt[1] : '') + ']' + '(' + src[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')';
					  }
					}
				  ];

				  for(var i = 0, len = ELEMENTS.length; i < len; i++) {
					if(typeof ELEMENTS[i].patterns === 'string') {
					  string = replaceEls(string, { tag: ELEMENTS[i].patterns, replacement: ELEMENTS[i].replacement, type:  ELEMENTS[i].type });
					}
					else {
					  for(var j = 0, pLen = ELEMENTS[i].patterns.length; j < pLen; j++) {
						string = replaceEls(string, { tag: ELEMENTS[i].patterns[j], replacement: ELEMENTS[i].replacement, type:  ELEMENTS[i].type });
					  }
					}
				  }

				  function replaceEls(html, elProperties) {
					var pattern = elProperties.type === 'void' ? '<' + elProperties.tag + '\\b([^>]*)\\/?>' : '<' + elProperties.tag + '\\b([^>]*)>([\\s\\S]*?)<\\/' + elProperties.tag + '>',
						regex = new RegExp(pattern, 'gi'),
						markdown = '';
					if(typeof elProperties.replacement === 'string') {
					  markdown = html.replace(regex, elProperties.replacement);
					}
					else {
					  markdown = html.replace(regex, function(str, p1, p2, p3) {
						return elProperties.replacement.call(this, str, p1, p2, p3);
					  });
					}
					return markdown;
				  }

				  function attrRegExp(attr) {
					return new RegExp(attr + '\\s*=\\s*["\']?([^"\']*)["\']?', 'i');
				  }

				  // Pre code blocks

				  string = string.replace(/<pre\b[^>]*>`([\s\S]*)`<\/pre>/gi, function(str, innerHTML) {
					innerHTML = innerHTML.replace(/^\t+/g, '  '); // convert tabs to spaces (you know it makes sense)
					innerHTML = innerHTML.replace(/\n/g, '\n    ');
					return '\n\n    ' + innerHTML + '\n';
				  });

				  // Lists

				  // Escape numbers that could trigger an ol
				  // If there are more than three spaces before the code, it would be in a pre tag
				  // Make sure we are escaping the period not matching any character
				  string = string.replace(/^(\s{0,3}\d+)\. /g, '$1\\. ');

				  // Converts lists that have no child lists (of same type) first, then works it's way up
				  var noChildrenRegex = /<(ul|ol)\b[^>]*>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi;
				  while(string.match(noChildrenRegex)) {
					string = string.replace(noChildrenRegex, function(str) {
					  return replaceLists(str);
					});
				  }

				  function replaceLists(html) {

					html = html.replace(/<(ul|ol)\b[^>]*>([\s\S]*?)<\/\1>/gi, function(str, listType, innerHTML) {
					  var lis = innerHTML.split('</li>');
					  lis.splice(lis.length - 1, 1);

					  for(i = 0, len = lis.length; i < len; i++) {
						if(lis[i]) {
						  var prefix = (listType === 'ol') ? (i + 1) + ".  " : "*   ";
						  lis[i] = lis[i].replace(/\s*<li[^>]*>([\s\S]*)/i, function(str, innerHTML) {

							innerHTML = innerHTML.replace(/^\s+/, '');
							innerHTML = innerHTML.replace(/\n\n/g, '\n\n    ');
							// indent nested lists
							innerHTML = innerHTML.replace(/\n([ ]*)+(\*|\d+\.) /g, '\n$1    $2 ');
							return prefix + innerHTML;
						  });
						}
					  }
					  return lis.join('\n');
					});
					return '\n\n' + html.replace(/[ \t]+\n|\s+$/g, '');
				  }

				  // Blockquotes
				  var deepest = /<blockquote\b[^>]*>((?:(?!<blockquote)[\s\S])*?)<\/blockquote>/gi;
				  while(string.match(deepest)) {
					string = string.replace(deepest, function(str) {
					  return replaceBlockquotes(str);
					});
				  }

				  function replaceBlockquotes(html) {
					html = html.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, function(str, inner) {
					  inner = inner.replace(/^\s+|\s+$/g, '');
					  inner = cleanUp(inner);
					  inner = inner.replace(/^/gm, '> ');
					  inner = inner.replace(/^(>([ \t]{2,}>)+)/gm, '> >');
					  return inner;
					});
					return html;
				  }

				  function cleanUp(string) {
					string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
					string = string.replace(/\n\s+\n/g, '\n\n');
					string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
					return string;
				  }

				  return cleanUp(string);
				};
			*/
			this.makeMarkdown = function(string) {

				var ELEMENTS = [
				{
					patterns: 'p',
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '\n' + innerHTML + '\n' : '';
				  }
				},
				{
					patterns: 'br',
					type: 'void',
					replacement: '  \n'
				},
				{
					patterns: 'h([1-6])',
					replacement: function(str, hLevel, attrs, innerHTML) {
					var hPrefix = '';
					for (var i = 0; i < hLevel; i++) {
						hPrefix += '#';
					}
					return '\n' + hPrefix + ' ' + innerHTML + ' ' + hPrefix + '\n';
				  }
				},
				{
					patterns: 'hr',
					type: 'void',
					replacement: '\n-----\n'
				},
				{
					patterns: 'a',
					replacement: function(str, attrs, innerHTML) {
					var href = attrs.match(attrRegExp('href')),
						title = attrs.match(attrRegExp('title'));
					return href ? '[' + innerHTML + ']' + '(' + href[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : str;
				  }
				},
				{
					patterns: ['b', 'strong'],
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '**' + innerHTML + '**' : '';
				  }
				},
				{
					patterns: ['i', 'em'],
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '*' + innerHTML + '*' : '';
				  }
				},
				{
					patterns: 'code',
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '`' + innerHTML + '`' : '';
				  }
				},
				{
					patterns: 'img',
					type: 'void',
					replacement: function(str, attrs, innerHTML) {
					var src = attrs.match(attrRegExp('src')),
						alt = attrs.match(attrRegExp('alt')),
						title = attrs.match(attrRegExp('title'));
					return '![' + (alt && alt[1] ? alt[1] : '') + ']' + '(' + src[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')';
				  }
				}
			  ];

				for (var i = 0, len = ELEMENTS.length; i < len; i++) {
					if (typeof ELEMENTS[i].patterns === 'string') {
						string = replaceEls(string, { tag: ELEMENTS[i].patterns, replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type });
					}
					else {
						for (var j = 0, pLen = ELEMENTS[i].patterns.length; j < pLen; j++) {
							string = replaceEls(string, { tag: ELEMENTS[i].patterns[j], replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type });
						}
					}
				}

				function replaceEls(html, elProperties) {
				var pattern = elProperties.type === 'void' ? '<' + elProperties.tag + '\\b([^>]*)\\/?>' : '<' + elProperties.tag + '\\b([^>]*)>([\\s\\S]*?)<\\/' + elProperties.tag + '>',
					regex = new RegExp(pattern, 'gi'),
					markdown = '';
				if (typeof elProperties.replacement === 'string') {
					markdown = html.replace(regex, elProperties.replacement);
				}
				else {
					markdown = html.replace(regex, function(str, p1, p2, p3) {
					return elProperties.replacement.call(this, str, p1, p2, p3);
				  });
				}
				return markdown;
			  }

				function attrRegExp(attr) {
				return new RegExp(attr + '\\s*=\\s*["\']?([^"\']*)["\']?', 'i');
			  }

				// Pre code blocks

				string = string.replace(/<pre\b[^>]*>`([\s\S]*)`<\/pre>/gi, function(str, innerHTML) {
				innerHTML = innerHTML.replace(/^\t+/g, '  '); // convert tabs to spaces (you know it makes sense)
				innerHTML = innerHTML.replace(/\n/g, '\n    ');
				return '\n\n    ' + innerHTML + '\n';
			  });

				// Lists

				// Escape numbers that could trigger an ol
				// If there are more than three spaces before the code, it would be in a pre tag
				// Make sure we are escaping the period not matching any character
				string = string.replace(/^(\s{0,3}\d+)\. /g, '$1\\. ');

				// Converts lists that have no child lists (of same type) first, then works it's way up
				var noChildrenRegex = /<(ul|ol)\b[^>]*>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi;
				while (string.match(noChildrenRegex)) {
					string = string.replace(noChildrenRegex, function(str) {
						return replaceLists(str);
					});
				}

				function replaceLists(html) {

				html = html.replace(/<(ul|ol)\b[^>]*>([\s\S]*?)<\/\1>/gi, function(str, listType, innerHTML) {
					var lis = innerHTML.split('</li>');
					lis.splice(lis.length - 1, 1);

					for (i = 0, len = lis.length; i < len; i++) {
						if (lis[i]) {
							var prefix = (listType === 'ol') ? (i + 1) + ".  " : "*   ";
							lis[i] = lis[i].replace(/\s*<li[^>]*>([\s\S]*)/i, function(str, innerHTML) {

						innerHTML = innerHTML.replace(/^\s+/, '');
						innerHTML = innerHTML.replace(/\n\n/g, '\n\n    ');

						// indent nested lists
						innerHTML = innerHTML.replace(/\n([ ]*)+(\*|\d+\.) /g, '\n$1    $2 ');
						return prefix + innerHTML;
					  });
						}
					}
					return lis.join('\n');
				});
				return '\n\n' + html.replace(/[ \t]+\n|\s+$/g, '');
			  }

				// Blockquotes
				var deepest = /<blockquote\b[^>]*>((?:(?!<blockquote)[\s\S])*?)<\/blockquote>/gi;
				while (string.match(deepest)) {
					string = string.replace(deepest, function(str) {
						return replaceBlockquotes(str);
					});
				}

				function replaceBlockquotes(html) {
				html = html.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, function(str, inner) {
					inner = inner.replace(/^\s+|\s+$/g, '');
					inner = cleanUp(inner);
					inner = inner.replace(/^/gm, '> ');
					inner = inner.replace(/^(>([ \t]{2,}>)+)/gm, '> >');
					return inner;
				});
				return html;
			  }

				function cleanUp(string) {
				string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
				string = string.replace(/\n\s+\n/g, '\n\n');
				string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
				return string;
			  }

				return cleanUp(string);
			};

			function _StripLinkDefinitions(text) {
				//
				// Strips link definitions from text, stores the URLs and titles in
				// hash references.
				//

				// Link defs are in the form: ^[id]: url "optional title"

				/*
				text = text.replace(/
					^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
					[ \t]*
					\n?                 // maybe *one* newline
					[ \t]*
					<?(\S+?)>?          // url = $2
					(?=\s|$)            // lookahead for whitespace instead of the lookbehind removed below
					[ \t]*
					\n?                 // maybe one newline
					[ \t]*
					(                   // (potential) title = $3
						(\n*)           // any lines skipped = $4 attacklab: lookbehind removed
						[ \t]+
						["(]
						(.+?)           // title = $5
						[")]
						[ \t]*
					)?                  // title is optional
					(?:\n+|$)
				/gm, function(){...});
				*/

				text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,
					function (wholeMatch, m1, m2, m3, m4, m5) {
						m1 = m1.toLowerCase();
						g_urls.set(m1, _EncodeAmpsAndAngles(m2));  // Link IDs are case-insensitive
						if (m4) {
							// Oops, found blank lines, so it's not a title.
							// Put back the parenthetical statement we stole.
							return m3;
						}
						else if (m5) {
							g_titles.set(m1, m5.replace(/"/g, "&quot;"));
						}

						// Completely remove the definition from the text
						return "";
					}
				);

				return text;
			}

			function _HashHTMLBlocks(text) {

				// Hashify HTML blocks:
				// We only want to do this for block-level HTML tags, such as headers,
				// lists, and tables. That's because we still want to wrap <p>s around
				// "paragraphs" that are wrapped in non-block-level tags, such as anchors,
				// phrase emphasis, and spans. The list of tags we're looking for is
				// hard-coded:
				var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
				var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"

				// First, look for nested blocks, e.g.:
				//   <div>
				//     <div>
				//     tags for inner block must be indented.
				//     </div>
				//   </div>
				//
				// The outermost tags must start at the left margin for this to match, and
				// the inner nested divs must be indented.
				// We need to do this before the next, more liberal match, because the next
				// match will start at the first `<div>` and stop at the first `</div>`.

				// attacklab: This regex can be expensive when it fails.

				/*
				text = text.replace(/
					(                       // save in $1
						^                   // start of line  (with /m)
						<($block_tags_a)    // start tag = $2
						\b                  // word break
											// attacklab: hack around khtml/pcre bug...
						[^\r]*?\n           // any number of lines, minimally matching
						</\2>               // the matching end tag
						[ \t]*              // trailing spaces/tabs
						(?=\n+)             // followed by a newline
					)                       // attacklab: there are sentinel newlines at end of document
				/gm,function(){...}};
				*/
				text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);

				//
				// Now match more liberally, simply from `\n<tag>` to `</tag>\n`
				//

				/*
				text = text.replace(/
					(                       // save in $1
						^                   // start of line  (with /m)
						<($block_tags_b)    // start tag = $2
						\b                  // word break
											// attacklab: hack around khtml/pcre bug...
						[^\r]*?             // any number of lines, minimally matching
						.*</\2>             // the matching end tag
						[ \t]*              // trailing spaces/tabs
						(?=\n+)             // followed by a newline
					)                       // attacklab: there are sentinel newlines at end of document
				/gm,function(){...}};
				*/
				text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);

				// Special case just for <hr />. It was easier to make a special case than
				// to make the other regex more complicated.

				/*
				text = text.replace(/
					\n                  // Starting after a blank line
					[ ]{0,3}
					(                   // save in $1
						(<(hr)          // start tag = $2
							\b          // word break
							([^<>])*?
						\/?>)           // the matching end tag
						[ \t]*
						(?=\n{2,})      // followed by a blank line
					)
				/g,hashElement);
				*/
				text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);

				// Special case for standalone HTML comments:

				/*
				text = text.replace(/
					\n\n                                            // Starting after a blank line
					[ ]{0,3}                                        // attacklab: g_tab_width - 1
					(                                               // save in $1
						<!
						(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)   // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
						>
						[ \t]*
						(?=\n{2,})                                  // followed by a blank line
					)
				/g,hashElement);
				*/
				text = text.replace(/\n\n[ ]{0,3}(<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement);

				// PHP and ASP-style processor instructions (<?...?> and <%...%>)

				/*
				text = text.replace(/
					(?:
						\n\n            // Starting after a blank line
					)
					(                   // save in $1
						[ ]{0,3}        // attacklab: g_tab_width - 1
						(?:
							<([?%])     // $2
							[^\r]*?
							\2>
						)
						[ \t]*
						(?=\n{2,})      // followed by a blank line
					)
				/g,hashElement);
				*/
				text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);

				return text;
			}

			function hashElement(wholeMatch, m1) {
				var blockText = m1;

				// Undo double lines
				blockText = blockText.replace(/^\n+/, "");

				// strip trailing blank lines
				blockText = blockText.replace(/\n+$/g, "");

				// Replace the element text with a marker ("~KxK" where x is its key)
				blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";

				return blockText;
			}

			function _RunBlockGamut(text, doNotUnhash) {
				//
				// These are all the transformations that form block-level
				// tags like paragraphs, headers, and list items.
				//
				text = _DoHeaders(text);

				// Do Horizontal Rules:
				var replacement = "<hr />\n";
				text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);
				text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);
				text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);

				text = _DoLists(text);
				text = _DoCodeBlocks(text);
				text = _DoBlockQuotes(text);

				// We already ran _HashHTMLBlocks() before, in Markdown(), but that
				// was to escape raw HTML in the original Markdown source. This time,
				// we're escaping the markup we've just created, so that we don't wrap
				// <p> tags around block-level tags.
				text = _HashHTMLBlocks(text);
				text = _FormParagraphs(text, doNotUnhash);

				return text;
			}

			function _RunSpanGamut(text) {
				//
				// These are all the transformations that occur *within* block-level
				// tags like paragraphs, headers, and list items.
				//

				text = _DoCodeSpans(text);
				text = _EscapeSpecialCharsWithinTagAttributes(text);
				text = _EncodeBackslashEscapes(text);

				// Process anchor and image tags. Images must come first,
				// because ![foo][f] looks like an anchor.
				text = _DoImages(text);
				text = _DoAnchors(text);

				// Make links out of things like `<http://example.com/>`
				// Must come after _DoAnchors(), because you can use < and >
				// delimiters in inline links like [this](<url>).
				text = _DoAutoLinks(text);

				text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now

				text = _EncodeAmpsAndAngles(text);
				text = _DoItalicsAndBold(text);

				// Sysmet
				text = _DoSup(text);

				// Do hard breaks:
				text = text.replace(/  +\n/g, " <br>\n");

				return text;
			}

			/* Sysmet */
			function _DoSup(text) {

				text = text.replace(/([\s\n]|^)\^([^\s\n]*?)(\s|\n|$)/g, "$1<sup>$2</sup>$3");

				text = text.replace(/\\(\^)/g, "$1");

				return text;
			}
			/* end Sysmet */

			function _EscapeSpecialCharsWithinTagAttributes(text) {
				//
				// Within tags -- meaning between < and > -- encode [\ ` * _] so they
				// don't conflict with their use in Markdown for code, italics and strong.
				//

				// Build a regex to find HTML tags and comments.  See Friedl's
				// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.

				// SE: changed the comment part of the regex

				var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;

				text = text.replace(regex, function (wholeMatch) {
					var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
					tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987
					return tag;
				});

				return text;
			}

			function _DoAnchors(text) {
				//
				// Turn Markdown link shortcuts into XHTML <a> tags.
				//
				//
				// First, handle reference-style links: [link text] [id]
				//

				/*
				text = text.replace(/
					(                           // wrap whole match in $1
						\[
						(
							(?:
								\[[^\]]*\]      // allow brackets nested one level
								|
								[^\[]           // or anything else
							)*
						)
						\]

						[ ]?                    // one optional space
						(?:\n[ ]*)?             // one optional newline followed by spaces

						\[
						(.*?)                   // id = $3
						\]
					)
					()()()()                    // pad remaining backreferences
				/g, writeAnchorTag);
				*/
				text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);

				//
				// Next, inline-style links: [link text](url "optional title")
				//

				/*
				text = text.replace(/
					(                           // wrap whole match in $1
						\[
						(
							(?:
								\[[^\]]*\]      // allow brackets nested one level
								|
								[^\[\]]         // or anything else
							)*
						)
						\]
						\(                      // literal paren
						[ \t]*
						()                      // no id, so leave $3 empty
						<?(                     // href = $4
							(?:
								\([^)]*\)       // allow one level of (correctly nested) parens (think MSDN)
								|
								[^()]
							)*?
						)>?
						[ \t]*
						(                       // $5
							(['"])              // quote char = $6
							(.*?)               // Title = $7
							\6                  // matching quote
							[ \t]*              // ignore any spaces/tabs between closing quote and )
						)?                      // title is optional
						\)
					)
				/g, writeAnchorTag);
				*/

				text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);

				//
				// Last, handle reference-style shortcuts: [link text]
				// These must come last in case you've also got [link test][1]
				// or [link test](/foo)
				//

				/*
				text = text.replace(/
					(                   // wrap whole match in $1
						\[
						([^\[\]]+)      // link text = $2; can't contain '[' or ']'
						\]
					)
					()()()()()          // pad rest of backreferences
				/g, writeAnchorTag);
				*/
				text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);

				return text;
			}

			function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
				if (m7 == undefined) m7 = "";
				var whole_match = m1;
				var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs
				var link_id = m3.toLowerCase();
				var url = m4;
				var title = m7;

				if (url == "") {
					if (link_id == "") {
						// lower-case and turn embedded newlines into spaces
						link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");
					}
					url = "#" + link_id;

					if (g_urls.get(link_id) != undefined) {
						url = g_urls.get(link_id);
						if (g_titles.get(link_id) != undefined) {
							title = g_titles.get(link_id);
						}
					}
					else {
						if (whole_match.search(/\(\s*\)$/m) > -1) {
							// Special case for explicit empty url
							url = "";
						}
						else {
							return whole_match;
						}
					}
				}
				url = encodeProblemUrlChars(url);
				url = escapeCharacters(url, "*_");
				var result = "<a href=\"" + url + "\"";

				if (title != "") {
					title = attributeEncode(title);
					title = escapeCharacters(title, "*_");
					result += " title=\"" + title + "\"";
				}

				result += ">" + link_text + "</a>";

				return result;
			}

			function _DoImages(text) {
				//
				// Turn Markdown image shortcuts into <img> tags.
				//

				//
				// First, handle reference-style labeled images: ![alt text][id]
				//

				/*
				text = text.replace(/
					(                   // wrap whole match in $1
						!\[
						(.*?)           // alt text = $2
						\]

						[ ]?            // one optional space
						(?:\n[ ]*)?     // one optional newline followed by spaces

						\[
						(.*?)           // id = $3
						\]
					)
					()()()()            // pad rest of backreferences
				/g, writeImageTag);
				*/
				text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);

				//
				// Next, handle inline images:  ![alt text](url "optional title")
				// Don't forget: encode * and _

				/*
				text = text.replace(/
					(                   // wrap whole match in $1
						!\[
						(.*?)           // alt text = $2
						\]
						\s?             // One optional whitespace character
						\(              // literal paren
						[ \t]*
						()              // no id, so leave $3 empty
						<?(\S+?)>?      // src url = $4
						[ \t]*
						(               // $5
							(['"])      // quote char = $6
							(.*?)       // title = $7
							\6          // matching quote
							[ \t]*
						)?              // title is optional
						\)
					)
				/g, writeImageTag);
				*/
				text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);

				return text;
			}

			function attributeEncode(text) {
				// unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title)
				// never makes sense to have verbatim HTML in it (and the sanitizer would totally break it)
				return text.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
			}

			function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
				var whole_match = m1;
				var alt_text = m2;
				var link_id = m3.toLowerCase();
				var url = m4;
				var title = m7;

				if (!title) title = "";

				if (url == "") {
					if (link_id == "") {
						// lower-case and turn embedded newlines into spaces
						link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");
					}
					url = "#" + link_id;

					if (g_urls.get(link_id) != undefined) {
						url = g_urls.get(link_id);
						if (g_titles.get(link_id) != undefined) {
							title = g_titles.get(link_id);
						}
					}
					else {
						return whole_match;
					}
				}

				alt_text = escapeCharacters(attributeEncode(alt_text), "*_[]()");
				url = escapeCharacters(url, "*_");
				var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";

				// attacklab: Markdown.pl adds empty title attributes to images.
				// Replicate this bug.

				//if (title != "") {
				title = attributeEncode(title);
				title = escapeCharacters(title, "*_");
				result += " title=\"" + title + "\"";

				//}

				result += " />";

				return result;
			}

			function _DoHeaders(text) {

				// Setext-style headers:
				//  Header 1
				//  ========
				//
				//  Header 2
				//  --------
				//
				text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
					function (wholeMatch, m1) {
						return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n";
					}
								);

				text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
					function (matchFound, m1) {
						return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n";
					}
								);

				// atx-style headers:
				//  # Header 1
				//  ## Header 2
				//  ## Header 2 with closing hashes ##
				//  ...
				//  ###### Header 6
				//

				/*
				text = text.replace(/
					^(\#{1,6})      // $1 = string of #'s
					[ \t]*
					(.+?)           // $2 = Header text
					[ \t]*
					\#*             // optional closing #'s (not counted)
					\n+
				/gm, function() {...});
				*/

				text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
					function (wholeMatch, m1, m2) {
						var h_level = m1.length;
						return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
					}
				);

				text = text.replace(/^(\%{1,6})[ \t]*(.+?)[ \t]*\%*\n+/gm,
					function (wholeMatch, m1, m2) {
						var h_level = m1.length;
						return "<dd>" + _RunSpanGamut(m2) + "</dd>\n\n";
					}
				);

				return text;
			}

			function _DoLists(text) {
				//
				// Form HTML ordered (numbered) and unordered (bulleted) lists.
				//

				// attacklab: add sentinel to hack around khtml/safari bug:
				// http://bugs.webkit.org/show_bug.cgi?id=11231
				text += "~0";

				// Re-usable pattern to match any entirel ul or ol list:

				/*
				var whole_list = /
					(                                   // $1 = whole list
						(                               // $2
							[ ]{0,3}                    // attacklab: g_tab_width - 1
							([*+-]|\d+[.])              // $3 = first list item marker
							[ \t]+
						)
						[^\r]+?
						(                               // $4
							~0                          // sentinel for workaround; should be $
							|
							\n{2,}
							(?=\S)
							(?!                         // Negative lookahead for another list item marker
								[ \t]*
								(?:[*+-]|\d+[.])[ \t]+
							)
						)
					)
				/g
				*/
				var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;

				if (g_list_level) {
					text = text.replace(whole_list, function (wholeMatch, m1, m2) {
						var list = m1;
						var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";

						var result = _ProcessListItems(list, list_type);

						// Trim any trailing whitespace, to put the closing `</$list_type>`
						// up on the preceding line, to get it past the current stupid
						// HTML block parser. This is a hack to work around the terrible
						// hack that is the HTML block parser.
						result = result.replace(/\s+$/, "");
						result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
						return result;
					});
				}
				else {
					whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
					text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
						var runup = m1;
						var list = m2;

						var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
						var result = _ProcessListItems(list, list_type);
						result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
						return result;
					});
				}

				// attacklab: strip sentinel
				text = text.replace(/~0/, "");

				return text;
			}

			var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };

			function _ProcessListItems(list_str, list_type) {
				//
				//  Process the contents of a single ordered or unordered list, splitting it
				//  into individual list items.
				//
				//  list_type is either "ul" or "ol".

				// The $g_list_level global keeps track of when we're inside a list.
				// Each time we enter a list, we increment it; when we leave a list,
				// we decrement. If it's zero, we're not in a list anymore.
				//
				// We do this because when we're not inside a list, we want to treat
				// something like this:
				//
				//    I recommend upgrading to version
				//    8. Oops, now this line is treated
				//    as a sub-list.
				//
				// As a single paragraph, despite the fact that the second line starts
				// with a digit-period-space sequence.
				//
				// Whereas when we're inside a list (or sub-list), that line will be
				// treated as the start of a sub-list. What a kludge, huh? This is
				// an aspect of Markdown's syntax that's hard to parse perfectly
				// without resorting to mind-reading. Perhaps the solution is to
				// change the syntax rules such that sub-lists must start with a
				// starting cardinal number; e.g. "1." or "a.".

				g_list_level++;

				// trim trailing blank lines:
				list_str = list_str.replace(/\n{2,}$/, "\n");

				// attacklab: add sentinel to emulate \z
				list_str += "~0";

				// In the original attacklab showdown, list_type was not given to this function, and anything
				// that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:
				//
				//  Markdown          rendered by WMD        rendered by MarkdownSharp
				//  ------------------------------------------------------------------
				//  1. first          1. first               1. first
				//  2. second         2. second              2. second
				//  - third           3. third                   * third
				//
				// We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,
				// with {MARKER} being one of \d+[.] or [*+-], depending on list_type:

				/*
				list_str = list_str.replace(/
					(^[ \t]*)                       // leading whitespace = $1
					({MARKER}) [ \t]+               // list marker = $2
					([^\r]+?                        // list item text   = $3
						(\n+)
					)
					(?=
						(~0 | \2 ({MARKER}) [ \t]+)
					)
				/gm, function(){...});
				*/

				var marker = _listItemMarkers[list_type];
				var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");
				var last_item_had_a_double_newline = false;
				list_str = list_str.replace(re,
					function (wholeMatch, m1, m2, m3) {
						var item = m3;
						var leading_space = m1;
						var ends_with_double_newline = /\n\n$/.test(item);
						var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;

						if (contains_double_newline || last_item_had_a_double_newline) {
							item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);
						}
						else {
							// Recursion for sub-lists:
							item = _DoLists(_Outdent(item));
							item = item.replace(/\n$/, ""); // chomp(item)
							item = _RunSpanGamut(item);
						}
						last_item_had_a_double_newline = ends_with_double_newline;
						return "<li>" + item + "</li>\n";
					}
				);

				// attacklab: strip sentinel
				list_str = list_str.replace(/~0/g, "");

				g_list_level--;
				return list_str;
			}

			function _DoCodeBlocks(text) {
				//
				//  Process Markdown `<pre><code>` blocks.
				//

				/*
				text = text.replace(/
					(?:\n\n|^)
					(                               // $1 = the code block -- one or more lines, starting with a space/tab
						(?:
							(?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
							.*\n+
						)+
					)
					(\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
				/g ,function(){...});
				*/

				// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
				text += "~0";

				text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
					function (wholeMatch, m1, m2) {
						var codeblock = m1;
						var nextChar = m2;

						codeblock = _EncodeCode(_Outdent(codeblock));
						codeblock = _Detab(codeblock);
						codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
						codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace

						codeblock = '<pre><code>' + codeblock + '\n</code></pre>';

						return "\n\n" + codeblock + "\n\n" + nextChar;
					}
				);

				// attacklab: strip sentinel
				text = text.replace(/~0/, "");

				return text;
			}

			function hashBlock(text) {
				text = text.replace(/(^\n+|\n+$)/g, "");
				return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
			}

			function _DoCodeSpans(text) {
				//
				// * Backtick quotes are used for <code></code> spans.
				//
				// * You can use multiple backticks as the delimiters if you want to
				//   include literal backticks in the code span. So, this input:
				//
				//      Just type ``foo `bar` baz`` at the prompt.
				//
				//   Will translate to:
				//
				//      <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
				//
				//   There's no arbitrary limit to the number of backticks you
				//   can use as delimters. If you need three consecutive backticks
				//   in your code, use four for delimiters, etc.
				//
				// * You can use spaces to get literal backticks at the edges:
				//
				//      ... type `` `bar` `` ...
				//
				//   Turns to:
				//
				//      ... type <code>`bar`</code> ...
				//

				/*
				text = text.replace(/
					(^|[^\\])       // Character before opening ` can't be a backslash
					(`+)            // $2 = Opening run of `
					(               // $3 = The code block
						[^\r]*?
						[^`]        // attacklab: work around lack of lookbehind
					)
					\2              // Matching closer
					(?!`)
				/gm, function(){...});
				*/

				text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
					function (wholeMatch, m1, m2, m3, m4) {
						var c = m3;
						c = c.replace(/^([ \t]*)/g, ""); // leading whitespace
						c = c.replace(/[ \t]*$/g, ""); // trailing whitespace
						c = _EncodeCode(c);
						c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs.
						return m1 + "<code>" + c + "</code>";
					}
				);

				return text;
			}

			function _EncodeCode(text) {
				//
				// Encode/escape certain characters inside Markdown code runs.
				// The point is that in code, these characters are literals,
				// and lose their special Markdown meanings.
				//
				// Encode all ampersands; HTML entities are not
				// entities within a Markdown code span.
				text = text.replace(/&/g, "&amp;");

				// Do the angle bracket song and dance:
				text = text.replace(/</g, "&lt;");
				text = text.replace(/>/g, "&gt;");

				// Now, escape characters that are magic in Markdown:
				text = escapeCharacters(text, "\*_{}[]\\", false);

				// jj the line above breaks this:
				//---

				//* Item

				//   1. Subitem

				//            special char: *
				//---

				return text;
			}

			function _DoItalicsAndBold(text) {

				// <strong> must go first:
				text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,
				"$1<strong>$3</strong>$4");

				// <em>
				text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,
				"$1<em>$3</em>$4");

				return text;
			}

			function _DoBlockQuotes(text) {

				/*
				text = text.replace(/
					(                           // Wrap whole match in $1
						(
							^[ \t]*>[ \t]?      // '>' at the start of a line
							.+\n                // rest of the first line
							(.+\n)*             // subsequent consecutive lines
							\n*                 // blanks
						)+
					)
				/gm, function(){...});
				*/

				text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
					function (wholeMatch, m1) {
						var bq = m1;

						// attacklab: hack around Konqueror 3.5.4 bug:
						// "----------bug".replace(/^-/g,"") == "bug"

						bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting

						// attacklab: clean up hack
						bq = bq.replace(/~0/g, "");

						bq = bq.replace(/^[ \t]+$/gm, "");     // trim whitespace-only lines
						bq = _RunBlockGamut(bq);             // recurse

						bq = bq.replace(/(^|\n)/g, "$1  ");

						// These leading spaces screw with <pre> content, so we need to fix that:
						bq = bq.replace(
								/(\s*<pre>[^\r]+?<\/pre>)/gm,
							function (wholeMatch, m1) {
								var pre = m1;

								// attacklab: hack around Konqueror 3.5.4 bug:
								pre = pre.replace(/^  /mg, "~0");
								pre = pre.replace(/~0/g, "");
								return pre;
							});

						return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
					}
				);
				return text;
			}

			function _FormParagraphs(text, doNotUnhash) {
				//
				//  Params:
				//    $text - string to process with html <p> tags
				//

				// Strip leading and trailing lines:
				text = text.replace(/^\n+/g, "");
				text = text.replace(/\n+$/g, "");

				var grafs = text.split(/\n{2,}/g);
				var grafsOut = [];

				var markerRe = /~K(\d+)K/;

				//
				// Wrap <p> tags.
				//
				var end = grafs.length;
				for (var i = 0; i < end; i++) {
					var str = grafs[i];

					// if this is an HTML marker, copy it
					if (markerRe.test(str)) {
						grafsOut.push(str);
					}
					else if (/\S/.test(str)) {
						str = _RunSpanGamut(str);
						str = str.replace(/^([ \t]*)/g, "<p>");
						str += "</p>"
						grafsOut.push(str);
					}
				}

				//
				// Unhashify HTML blocks
				//
				if (!doNotUnhash) {
					end = grafsOut.length;
					for (var i = 0; i < end; i++) {
						var foundAny = true;
						while (foundAny) {
							// we may need several runs, since the data may be nested
							foundAny = false;
							grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) {
								foundAny = true;
								return g_html_blocks[id];
							});
						}
					}
				}
				return grafsOut.join("\n\n");
			}

			function _EncodeAmpsAndAngles(text) {
				// Smart processing for ampersands and angle brackets that need to be encoded.

				// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
				//   http://bumppo.net/projects/amputator/
				text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");

				// Encode naked <'s
				text = text.replace(/<(?![a-z\/?\$!])/gi, "&lt;");

				return text;
			}

			function _EncodeBackslashEscapes(text) {
				//
				//   Parameter:  String.
				//   Returns:    The string, with after processing the following backslash
				//               escape sequences.
				//

				// attacklab: The polite way to do this is with the new
				// escapeCharacters() function:
				//
				//     text = escapeCharacters(text,"\\",true);
				//     text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
				//
				// ...but we're sidestepping its use of the (slow) RegExp constructor
				// as an optimization for Firefox.  This function gets called a LOT.

				text = text.replace(/\\(\\)/g, escapeCharacters_callback);
				text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
				return text;
			}

			function _DoAutoLinks(text) {

				// note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
				// *except* for the <http://www.foo.com> case

				// automatically add < and > around unadorned raw hyperlinks
				// must be preceded by space/BOF and followed by non-word/EOF character
				text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4");

				//  autolink anything like <http://example.com>

				var replacer = function (wholematch, m1) {
					return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>";
				}
				text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);

				// Email addresses: <address@domain.foo>
				/*
				text = text.replace(/
					<
					(?:mailto:)?
					(
						[-.\w]+
						\@
						[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
					)
					>
				/gi, _DoAutoLinks_callback());
				*/

				var email_replacer = function(wholematch, m1) {
					var mailto = 'mailto:'
					var link
					var email
					if (m1.substring(0, mailto.length) != mailto) {
						link = mailto + m1;
						email = m1;
					}
					else {
						link = m1;
						email = m1.substring(mailto.length, m1.length);
					}
					return "<a href=\"" + link + "\">" + pluginHooks.plainLinkText(email) + "</a>";
				}
				text = text.replace(/<((?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+))>/gi, email_replacer);

				return text;
			}

			function _UnescapeSpecialChars(text) {
				//
				// Swap back in all the special characters we've hidden.
				//
				text = text.replace(/~E(\d+)E/g,
					function (wholeMatch, m1) {
						var charCodeToReplace = parseInt(m1);
						return String.fromCharCode(charCodeToReplace);
					}
				);
				return text;
			}

			function _Outdent(text) {
				//
				// Remove one level of line-leading tabs or spaces
				//

				// attacklab: hack around Konqueror 3.5.4 bug:
				// "----------bug".replace(/^-/g,"") == "bug"

				text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width

				// attacklab: clean up hack
				text = text.replace(/~0/g, "")

				return text;
			}

			function _Detab(text) {
				if (!/\t/.test(text))
					return text;

				var spaces = ["    ", "   ", "  ", " "],
				skew = 0,
				v;

				return text.replace(/[\n\t]/g, function (match, offset) {
					if (match === "\n") {
						skew = offset + 1;
						return match;
					}
					v = (offset - skew) % 4;
					skew = offset + 1;
					return spaces[v];
				});
			}

			//
			//  attacklab: Utility functions
			//

			var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;

			// hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems
			function encodeProblemUrlChars(url) {
				if (!url)
					return "";

				var len = url.length;

				return url.replace(_problemUrlChars, function (match, offset) {
					if (match == "~D") // escape for dollar
						return "%24";
					if (match == ":") {
						if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
							return ":";
						if (url.substring(0, 'mailto:'.length) === 'mailto:')
							return ":";
						if (url.substring(0, 'magnet:'.length) === 'magnet:')
							return ":";
					}
					return "%" + match.charCodeAt(0).toString(16);
				});
			}

			function escapeCharacters(text, charsToEscape, afterBackslash) {
				// First we have to escape the escape characters so that
				// we can build a character class out of them
				var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";

				if (afterBackslash) {
					regexString = "\\\\" + regexString;
				}

				var regex = new RegExp(regexString, "g");
				text = text.replace(regex, escapeCharacters_callback);

				return text;
			}

			function escapeCharacters_callback(wholeMatch, m1) {
				var charCodeToEscape = m1.charCodeAt(0);
				return "~E" + charCodeToEscape + "E";
			}
		}
	};

	(function () {

		// The following text is included for historical reasons, but should
		// be taken with a pinch of salt; it's not all true anymore.

		//
		// Wherever possible, Showdown is a straight, line-by-line port
		// of the Perl version of Markdown.
		//
		// This is not a normal parser design; it's basically just a
		// series of string substitutions.  It's hard to read and
		// maintain this way,  but keeping Showdown close to the original
		// design makes it easier to port new features.
		//
		// More importantly, Showdown behaves like markdown.pl in most
		// edge cases.  So web applications can do client-side preview
		// in Javascript, and then build identical HTML on the server.
		//
		// This port needs the new RegExp functionality of ECMA 262,
		// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
		// should do fine.  Even with the new regular expression features,
		// We do a lot of work to emulate Perl's regex functionality.
		// The tricky changes in this file mostly have the "attacklab:"
		// label.  Major or self-explanatory changes don't.
		//
		// Smart diff tools like Araxis Merge will be able to match up
		// this file with markdown.pl in a useful way.  A little tweaking
		// helps: in a copy of markdown.pl, replace "#" with "//" and
		// replace "$text" with "text".  Be sure to ignore whitespace
		// and line endings.
		//

		//
		// Usage:
		//
		//   var text = "Markdown *rocks*.";
		//
		//   var converter = new Markdown.Converter();
		//   var html = converter.makeHtml(text);
		//
		//   alert(html);
		//
		// Note: move the sample code to the bottom of this
		// file before uncommenting it.
		//

		function identity(x) {
			return x;
		}
		function returnFalse(x) {
			return false;
		}

		function HookCollection() { }

		HookCollection.prototype = {

			chain: function (hookname, func) {
				var original = this[hookname];
				if (!original)
					throw new Error("unknown hook " + hookname);

				if (original === identity)
					this[hookname] = func;
				else
					this[hookname] = function (x) {
						return func(original(x));
					}
			},
			set: function (hookname, func) {
				if (!this[hookname])
					throw new Error("unknown hook " + hookname);
				this[hookname] = func;
			},
			addNoop: function (hookname) {
				this[hookname] = identity;
			},
			addFalse: function (hookname) {
				this[hookname] = returnFalse;
			}
		};

		Markdown.HookCollection = HookCollection;

		// g_urls and g_titles allow arbitrary user-entered strings as keys. This
		// caused an exception (and hence stopped the rendering) when the user entered
		// e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this
		// (since no builtin property starts with "s_"). See
		// http://meta.stackoverflow.com/questions/64655/strange-wmd-bug
		// (granted, switching from Array() to Object() alone would have left only __proto__
		// to be a problem)
		function SaveHash() { }
		SaveHash.prototype = {
			set: function (key, value) {
				this["s_" + key] = value;
			},
			get: function (key) {
				return this["s_" + key];
			}
		};

		Markdown.Converter = function () {
			var pluginHooks = this.hooks = new HookCollection();
			pluginHooks.addNoop("plainLinkText");  // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
			pluginHooks.addNoop("preConversion");  // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
			pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml

			//
			// Private state of the converter instance:
			//

			// Global hashes, used by various utility routines
			var g_urls;
			var g_titles;
			var g_html_blocks;

			// Used to track when we're inside an ordered or unordered list
			// (see _ProcessListItems() for details):
			var g_list_level;

			this.makeHtml = function (text) {
				text = text || "";

				//
				// Main function. The order in which other subs are called here is
				// essential. Link and image substitutions need to happen before
				// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
				// and <img> tags get encoded.
				//

				// This will only happen if makeHtml on the same converter instance is called from a plugin hook.
				// Don't do that.
				if (g_urls)
					throw new Error("Recursive call to converter.makeHtml");

				// Create the private state objects.
				g_urls = new SaveHash();
				g_titles = new SaveHash();
				g_html_blocks = [];
				g_list_level = 0;

				text = pluginHooks.preConversion(text);

				// attacklab: Replace ~ with ~T
				// This lets us use tilde as an escape char to avoid md5 hashes
				// The choice of character is arbitray; anything that isn't
				// magic in Markdown will work.
				text = text.replace(/~/g, "~T");

				// attacklab: Replace $ with ~D
				// RegExp interprets $ as a special character
				// when it's in a replacement string
				text = text.replace(/\$/g, "~D");

				// Standardize line endings
				text = text.replace(/\r\n/g, "\n"); // DOS to Unix
				text = text.replace(/\r/g, "\n"); // Mac to Unix

				// Make sure text begins and ends with a couple of newlines:
				text = "\n\n" + text + "\n\n";

				// Convert all tabs to spaces.
				text = _Detab(text);

				// Strip any lines consisting only of spaces and tabs.
				// This makes subsequent regexen easier to write, because we can
				// match consecutive blank lines with /\n+/ instead of something
				// contorted like /[ \t]*\n+/ .
				text = text.replace(/^[ \t]+$/mg, "");

				// Turn block-level HTML blocks into hash entries
				text = _HashHTMLBlocks(text);

				// Strip link definitions, store in hashes.
				text = _StripLinkDefinitions(text);

				text = _RunBlockGamut(text);

				text = _UnescapeSpecialChars(text);

				// attacklab: Restore dollar signs
				text = text.replace(/~D/g, "$$");

				// attacklab: Restore tildes
				text = text.replace(/~T/g, "~");

				text = pluginHooks.postConversion(text);

				g_html_blocks = g_titles = g_urls = null;

				return text;
			};

			/*
				this.makeMarkdown = function(string) {

				  var ELEMENTS = [
					{
					  patterns: 'p',
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '\n\n' + innerHTML + '\n' : '';
					  }
					},
					{
					  patterns: 'br',
					  type: 'void',
					  replacement: '\n'
					},
					{
					  patterns: 'h([1-6])',
					  replacement: function(str, hLevel, attrs, innerHTML) {
						var hPrefix = '';
						for(var i = 0; i < hLevel; i++) {
						  hPrefix += '#';
						}
						return '\n\n' + hPrefix + ' ' + innerHTML + '\n';
					  }
					},
					{
					  patterns: 'hr',
					  type: 'void',
					  replacement: '\n\n* * *\n'
					},
					{
					  patterns: 'a',
					  replacement: function(str, attrs, innerHTML) {
						var href = attrs.match(attrRegExp('href')),
							title = attrs.match(attrRegExp('title'));
						return href ? '[' + innerHTML + ']' + '(' + href[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : str;
					  }
					},
					{
					  patterns: ['b', 'strong'],
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '**' + innerHTML + '**' : '';
					  }
					},
					{
					  patterns: ['i', 'em'],
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '_' + innerHTML + '_' : '';
					  }
					},
					{
					  patterns: 'code',
					  replacement: function(str, attrs, innerHTML) {
						return innerHTML ? '`' + innerHTML + '`' : '';
					  }
					},
					{
					  patterns: 'img',
					  type: 'void',
					  replacement: function(str, attrs, innerHTML) {
						var src = attrs.match(attrRegExp('src')),
							alt = attrs.match(attrRegExp('alt')),
							title = attrs.match(attrRegExp('title'));
						return '![' + (alt && alt[1] ? alt[1] : '') + ']' + '(' + src[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')';
					  }
					}
				  ];

				  for(var i = 0, len = ELEMENTS.length; i < len; i++) {
					if(typeof ELEMENTS[i].patterns === 'string') {
					  string = replaceEls(string, { tag: ELEMENTS[i].patterns, replacement: ELEMENTS[i].replacement, type:  ELEMENTS[i].type });
					}
					else {
					  for(var j = 0, pLen = ELEMENTS[i].patterns.length; j < pLen; j++) {
						string = replaceEls(string, { tag: ELEMENTS[i].patterns[j], replacement: ELEMENTS[i].replacement, type:  ELEMENTS[i].type });
					  }
					}
				  }

				  function replaceEls(html, elProperties) {
					var pattern = elProperties.type === 'void' ? '<' + elProperties.tag + '\\b([^>]*)\\/?>' : '<' + elProperties.tag + '\\b([^>]*)>([\\s\\S]*?)<\\/' + elProperties.tag + '>',
						regex = new RegExp(pattern, 'gi'),
						markdown = '';
					if(typeof elProperties.replacement === 'string') {
					  markdown = html.replace(regex, elProperties.replacement);
					}
					else {
					  markdown = html.replace(regex, function(str, p1, p2, p3) {
						return elProperties.replacement.call(this, str, p1, p2, p3);
					  });
					}
					return markdown;
				  }

				  function attrRegExp(attr) {
					return new RegExp(attr + '\\s*=\\s*["\']?([^"\']*)["\']?', 'i');
				  }

				  // Pre code blocks

				  string = string.replace(/<pre\b[^>]*>`([\s\S]*)`<\/pre>/gi, function(str, innerHTML) {
					innerHTML = innerHTML.replace(/^\t+/g, '  '); // convert tabs to spaces (you know it makes sense)
					innerHTML = innerHTML.replace(/\n/g, '\n    ');
					return '\n\n    ' + innerHTML + '\n';
				  });

				  // Lists

				  // Escape numbers that could trigger an ol
				  // If there are more than three spaces before the code, it would be in a pre tag
				  // Make sure we are escaping the period not matching any character
				  string = string.replace(/^(\s{0,3}\d+)\. /g, '$1\\. ');

				  // Converts lists that have no child lists (of same type) first, then works it's way up
				  var noChildrenRegex = /<(ul|ol)\b[^>]*>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi;
				  while(string.match(noChildrenRegex)) {
					string = string.replace(noChildrenRegex, function(str) {
					  return replaceLists(str);
					});
				  }

				  function replaceLists(html) {

					html = html.replace(/<(ul|ol)\b[^>]*>([\s\S]*?)<\/\1>/gi, function(str, listType, innerHTML) {
					  var lis = innerHTML.split('</li>');
					  lis.splice(lis.length - 1, 1);

					  for(i = 0, len = lis.length; i < len; i++) {
						if(lis[i]) {
						  var prefix = (listType === 'ol') ? (i + 1) + ".  " : "*   ";
						  lis[i] = lis[i].replace(/\s*<li[^>]*>([\s\S]*)/i, function(str, innerHTML) {

							innerHTML = innerHTML.replace(/^\s+/, '');
							innerHTML = innerHTML.replace(/\n\n/g, '\n\n    ');
							// indent nested lists
							innerHTML = innerHTML.replace(/\n([ ]*)+(\*|\d+\.) /g, '\n$1    $2 ');
							return prefix + innerHTML;
						  });
						}
					  }
					  return lis.join('\n');
					});
					return '\n\n' + html.replace(/[ \t]+\n|\s+$/g, '');
				  }

				  // Blockquotes
				  var deepest = /<blockquote\b[^>]*>((?:(?!<blockquote)[\s\S])*?)<\/blockquote>/gi;
				  while(string.match(deepest)) {
					string = string.replace(deepest, function(str) {
					  return replaceBlockquotes(str);
					});
				  }

				  function replaceBlockquotes(html) {
					html = html.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, function(str, inner) {
					  inner = inner.replace(/^\s+|\s+$/g, '');
					  inner = cleanUp(inner);
					  inner = inner.replace(/^/gm, '> ');
					  inner = inner.replace(/^(>([ \t]{2,}>)+)/gm, '> >');
					  return inner;
					});
					return html;
				  }

				  function cleanUp(string) {
					string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
					string = string.replace(/\n\s+\n/g, '\n\n');
					string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
					return string;
				  }

				  return cleanUp(string);
				};
			*/
			this.makeMarkdown = function(string) {

				var ELEMENTS = [
				{
					patterns: 'p',
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '\n' + innerHTML + '\n' : '';
				  }
				},
				{
					patterns: 'br',
					type: 'void',
					replacement: '  \n'
				},
				{
					patterns: 'h([1-6])',
					replacement: function(str, hLevel, attrs, innerHTML) {
					var hPrefix = '';
					for (var i = 0; i < hLevel; i++) {
						hPrefix += '#';
					}
					return '\n' + hPrefix + ' ' + innerHTML + ' ' + hPrefix + '\n';
				  }
				},
				{
					patterns: 'hr',
					type: 'void',
					replacement: '\n-----\n'
				},
				{
					patterns: 'a',
					replacement: function(str, attrs, innerHTML) {
					var href = attrs.match(attrRegExp('href')),
						title = attrs.match(attrRegExp('title'));
					return href ? '[' + innerHTML + ']' + '(' + href[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : str;
				  }
				},
				{
					patterns: ['b', 'strong'],
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '**' + innerHTML + '**' : '';
				  }
				},
				{
					patterns: ['i', 'em'],
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '*' + innerHTML + '*' : '';
				  }
				},
				{
					patterns: 'code',
					replacement: function(str, attrs, innerHTML) {
					return innerHTML ? '`' + innerHTML + '`' : '';
				  }
				},
				{
					patterns: 'img',
					type: 'void',
					replacement: function(str, attrs, innerHTML) {
					var src = attrs.match(attrRegExp('src')),
						alt = attrs.match(attrRegExp('alt')),
						title = attrs.match(attrRegExp('title'));
					return '![' + (alt && alt[1] ? alt[1] : '') + ']' + '(' + src[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')';
				  }
				}
			  ];

				for (var i = 0, len = ELEMENTS.length; i < len; i++) {
					if (typeof ELEMENTS[i].patterns === 'string') {
						string = replaceEls(string, { tag: ELEMENTS[i].patterns, replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type });
					}
					else {
						for (var j = 0, pLen = ELEMENTS[i].patterns.length; j < pLen; j++) {
							string = replaceEls(string, { tag: ELEMENTS[i].patterns[j], replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type });
						}
					}
				}

				function replaceEls(html, elProperties) {
				var pattern = elProperties.type === 'void' ? '<' + elProperties.tag + '\\b([^>]*)\\/?>' : '<' + elProperties.tag + '\\b([^>]*)>([\\s\\S]*?)<\\/' + elProperties.tag + '>',
					regex = new RegExp(pattern, 'gi'),
					markdown = '';
				if (typeof elProperties.replacement === 'string') {
					markdown = html.replace(regex, elProperties.replacement);
				}
				else {
					markdown = html.replace(regex, function(str, p1, p2, p3) {
					return elProperties.replacement.call(this, str, p1, p2, p3);
				  });
				}
				return markdown;
			  }

				function attrRegExp(attr) {
				return new RegExp(attr + '\\s*=\\s*["\']?([^"\']*)["\']?', 'i');
			  }

				// Pre code blocks

				string = string.replace(/<pre\b[^>]*>`([\s\S]*)`<\/pre>/gi, function(str, innerHTML) {
				innerHTML = innerHTML.replace(/^\t+/g, '  '); // convert tabs to spaces (you know it makes sense)
				innerHTML = innerHTML.replace(/\n/g, '\n    ');
				return '\n\n    ' + innerHTML + '\n';
			  });

				// Lists

				// Escape numbers that could trigger an ol
				// If there are more than three spaces before the code, it would be in a pre tag
				// Make sure we are escaping the period not matching any character
				string = string.replace(/^(\s{0,3}\d+)\. /g, '$1\\. ');

				// Converts lists that have no child lists (of same type) first, then works it's way up
				var noChildrenRegex = /<(ul|ol)\b[^>]*>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi;
				while (string.match(noChildrenRegex)) {
					string = string.replace(noChildrenRegex, function(str) {
						return replaceLists(str);
					});
				}

				function replaceLists(html) {

					html = html.replace(/<(ul|ol)\b[^>]*>([\s\S]*?)<\/\1>/gi, function(str, listType, innerHTML) {
						var lis = innerHTML.split('</li>');
						lis.splice(lis.length - 1, 1);

						for (i = 0, len = lis.length; i < len; i++) {
							if (lis[i]) {
								var prefix = (listType === 'ol') ? (i + 1) + ".  " : "*   ";
								lis[i] = lis[i].replace(/\s*<li[^>]*>([\s\S]*)/i, function(str, innerHTML) {

									innerHTML = innerHTML.replace(/^\s+/, '');
									innerHTML = innerHTML.replace(/\n\n/g, '\n\n    ');

									// indent nested lists
									innerHTML = innerHTML.replace(/\n([ ]*)+(\*|\d+\.) /g, '\n$1    $2 ');
									return prefix + innerHTML;
								});
							}
						}
						return lis.join('\n');
					});
					return '\n\n' + html.replace(/[ \t]+\n|\s+$/g, '');
				}

				// Blockquotes
				var deepest = /<blockquote\b[^>]*>((?:(?!<blockquote)[\s\S])*?)<\/blockquote>/gi;
				while (string.match(deepest)) {
					string = string.replace(deepest, function(str) {
						return replaceBlockquotes(str);
					});
				}

				function replaceBlockquotes(html) {
				html = html.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, function(str, inner) {
					inner = inner.replace(/^\s+|\s+$/g, '');
					inner = cleanUp(inner);
					inner = inner.replace(/^/gm, '> ');
					inner = inner.replace(/^(>([ \t]{2,}>)+)/gm, '> >');
					return inner;
				});
				return html;
			  }

				function cleanUp(string) {
				string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
				string = string.replace(/\n\s+\n/g, '\n\n');
				string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
				return string;
			  }

				return cleanUp(string);
			};

			function _StripLinkDefinitions(text) {
				//
				// Strips link definitions from text, stores the URLs and titles in
				// hash references.
				//

				// Link defs are in the form: ^[id]: url "optional title"

				/*
				text = text.replace(/
					^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
					[ \t]*
					\n?                 // maybe *one* newline
					[ \t]*
					<?(\S+?)>?          // url = $2
					(?=\s|$)            // lookahead for whitespace instead of the lookbehind removed below
					[ \t]*
					\n?                 // maybe one newline
					[ \t]*
					(                   // (potential) title = $3
						(\n*)           // any lines skipped = $4 attacklab: lookbehind removed
						[ \t]+
						["(]
						(.+?)           // title = $5
						[")]
						[ \t]*
					)?                  // title is optional
					(?:\n+|$)
				/gm, function(){...});
				*/

				text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,
					function (wholeMatch, m1, m2, m3, m4, m5) {
						m1 = m1.toLowerCase();
						g_urls.set(m1, _EncodeAmpsAndAngles(m2));  // Link IDs are case-insensitive
						if (m4) {
							// Oops, found blank lines, so it's not a title.
							// Put back the parenthetical statement we stole.
							return m3;
						}
						else if (m5) {
							g_titles.set(m1, m5.replace(/"/g, "&quot;"));
						}

						// Completely remove the definition from the text
						return "";
					}
				);

				return text;
			}

			function _HashHTMLBlocks(text) {

				// Hashify HTML blocks:
				// We only want to do this for block-level HTML tags, such as headers,
				// lists, and tables. That's because we still want to wrap <p>s around
				// "paragraphs" that are wrapped in non-block-level tags, such as anchors,
				// phrase emphasis, and spans. The list of tags we're looking for is
				// hard-coded:
				var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
				var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"

				// First, look for nested blocks, e.g.:
				//   <div>
				//     <div>
				//     tags for inner block must be indented.
				//     </div>
				//   </div>
				//
				// The outermost tags must start at the left margin for this to match, and
				// the inner nested divs must be indented.
				// We need to do this before the next, more liberal match, because the next
				// match will start at the first `<div>` and stop at the first `</div>`.

				// attacklab: This regex can be expensive when it fails.

				/*
				text = text.replace(/
					(                       // save in $1
						^                   // start of line  (with /m)
						<($block_tags_a)    // start tag = $2
						\b                  // word break
											// attacklab: hack around khtml/pcre bug...
						[^\r]*?\n           // any number of lines, minimally matching
						</\2>               // the matching end tag
						[ \t]*              // trailing spaces/tabs
						(?=\n+)             // followed by a newline
					)                       // attacklab: there are sentinel newlines at end of document
				/gm,function(){...}};
				*/
				text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);

				//
				// Now match more liberally, simply from `\n<tag>` to `</tag>\n`
				//

				/*
				text = text.replace(/
					(                       // save in $1
						^                   // start of line  (with /m)
						<($block_tags_b)    // start tag = $2
						\b                  // word break
											// attacklab: hack around khtml/pcre bug...
						[^\r]*?             // any number of lines, minimally matching
						.*</\2>             // the matching end tag
						[ \t]*              // trailing spaces/tabs
						(?=\n+)             // followed by a newline
					)                       // attacklab: there are sentinel newlines at end of document
				/gm,function(){...}};
				*/
				text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);

				// Special case just for <hr />. It was easier to make a special case than
				// to make the other regex more complicated.

				/*
				text = text.replace(/
					\n                  // Starting after a blank line
					[ ]{0,3}
					(                   // save in $1
						(<(hr)          // start tag = $2
							\b          // word break
							([^<>])*?
						\/?>)           // the matching end tag
						[ \t]*
						(?=\n{2,})      // followed by a blank line
					)
				/g,hashElement);
				*/
				text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);

				// Special case for standalone HTML comments:

				/*
				text = text.replace(/
					\n\n                                            // Starting after a blank line
					[ ]{0,3}                                        // attacklab: g_tab_width - 1
					(                                               // save in $1
						<!
						(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)   // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
						>
						[ \t]*
						(?=\n{2,})                                  // followed by a blank line
					)
				/g,hashElement);
				*/
				text = text.replace(/\n\n[ ]{0,3}(<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement);

				// PHP and ASP-style processor instructions (<?...?> and <%...%>)

				/*
				text = text.replace(/
					(?:
						\n\n            // Starting after a blank line
					)
					(                   // save in $1
						[ ]{0,3}        // attacklab: g_tab_width - 1
						(?:
							<([?%])     // $2
							[^\r]*?
							\2>
						)
						[ \t]*
						(?=\n{2,})      // followed by a blank line
					)
				/g,hashElement);
				*/
				text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);

				return text;
			}

			function hashElement(wholeMatch, m1) {
				var blockText = m1;

				// Undo double lines
				blockText = blockText.replace(/^\n+/, "");

				// strip trailing blank lines
				blockText = blockText.replace(/\n+$/g, "");

				// Replace the element text with a marker ("~KxK" where x is its key)
				blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";

				return blockText;
			}

			function _RunBlockGamut(text, doNotUnhash) {
				//
				// These are all the transformations that form block-level
				// tags like paragraphs, headers, and list items.
				//
				text = _DoHeaders(text);

				// Do Horizontal Rules:
				var replacement = "<hr />\n";
				text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);
				text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);
				text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);

				text = _DoLists(text);
				text = _DoCodeBlocks(text);
				text = _DoBlockQuotes(text);

				// We already ran _HashHTMLBlocks() before, in Markdown(), but that
				// was to escape raw HTML in the original Markdown source. This time,
				// we're escaping the markup we've just created, so that we don't wrap
				// <p> tags around block-level tags.
				text = _HashHTMLBlocks(text);
				text = _FormParagraphs(text, doNotUnhash);

				return text;
			}

			function _RunSpanGamut(text) {
				//
				// These are all the transformations that occur *within* block-level
				// tags like paragraphs, headers, and list items.
				//

				text = _DoCodeSpans(text);
				text = _EscapeSpecialCharsWithinTagAttributes(text);
				text = _EncodeBackslashEscapes(text);

				// Process anchor and image tags. Images must come first,
				// because ![foo][f] looks like an anchor.
				text = _DoImages(text);
				text = _DoAnchors(text);

				// Make links out of things like `<http://example.com/>`
				// Must come after _DoAnchors(), because you can use < and >
				// delimiters in inline links like [this](<url>).
				text = _DoAutoLinks(text);

				text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now

				text = _EncodeAmpsAndAngles(text);
				text = _DoItalicsAndBold(text);

				// Sysmet
				text = _DoSup(text);

				// Do hard breaks:
				text = text.replace(/  +\n/g, " <br>\n");

				return text;
			}

			/* Sysmet */
			function _DoSup(text) {

				text = text.replace(/([\s\n]|^)\^([^\s\n]*?)(\s|\n|$)/g, "$1<sup>$2</sup>$3");

				text = text.replace(/\\(\^)/g, "$1");

				return text;
			}
			/* end Sysmet */

			function _EscapeSpecialCharsWithinTagAttributes(text) {
				//
				// Within tags -- meaning between < and > -- encode [\ ` * _] so they
				// don't conflict with their use in Markdown for code, italics and strong.
				//

				// Build a regex to find HTML tags and comments.  See Friedl's
				// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.

				// SE: changed the comment part of the regex

				var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;

				text = text.replace(regex, function (wholeMatch) {
					var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
					tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987
					return tag;
				});

				return text;
			}

			function _DoAnchors(text) {
				//
				// Turn Markdown link shortcuts into XHTML <a> tags.
				//
				//
				// First, handle reference-style links: [link text] [id]
				//

				/*
				text = text.replace(/
					(                           // wrap whole match in $1
						\[
						(
							(?:
								\[[^\]]*\]      // allow brackets nested one level
								|
								[^\[]           // or anything else
							)*
						)
						\]

						[ ]?                    // one optional space
						(?:\n[ ]*)?             // one optional newline followed by spaces

						\[
						(.*?)                   // id = $3
						\]
					)
					()()()()                    // pad remaining backreferences
				/g, writeAnchorTag);
				*/
				text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);

				//
				// Next, inline-style links: [link text](url "optional title")
				//

				/*
				text = text.replace(/
					(                           // wrap whole match in $1
						\[
						(
							(?:
								\[[^\]]*\]      // allow brackets nested one level
								|
								[^\[\]]         // or anything else
							)*
						)
						\]
						\(                      // literal paren
						[ \t]*
						()                      // no id, so leave $3 empty
						<?(                     // href = $4
							(?:
								\([^)]*\)       // allow one level of (correctly nested) parens (think MSDN)
								|
								[^()]
							)*?
						)>?
						[ \t]*
						(                       // $5
							(['"])              // quote char = $6
							(.*?)               // Title = $7
							\6                  // matching quote
							[ \t]*              // ignore any spaces/tabs between closing quote and )
						)?                      // title is optional
						\)
					)
				/g, writeAnchorTag);
				*/

				text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);

				//
				// Last, handle reference-style shortcuts: [link text]
				// These must come last in case you've also got [link test][1]
				// or [link test](/foo)
				//

				/*
				text = text.replace(/
					(                   // wrap whole match in $1
						\[
						([^\[\]]+)      // link text = $2; can't contain '[' or ']'
						\]
					)
					()()()()()          // pad rest of backreferences
				/g, writeAnchorTag);
				*/
				text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);

				return text;
			}

			function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
				if (m7 == undefined) m7 = "";
				var whole_match = m1;
				var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs
				var link_id = m3.toLowerCase();
				var url = m4;
				var title = m7;

				if (url == "") {
					if (link_id == "") {
						// lower-case and turn embedded newlines into spaces
						link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");
					}
					url = "#" + link_id;

					if (g_urls.get(link_id) != undefined) {
						url = g_urls.get(link_id);
						if (g_titles.get(link_id) != undefined) {
							title = g_titles.get(link_id);
						}
					}
					else {
						if (whole_match.search(/\(\s*\)$/m) > -1) {
							// Special case for explicit empty url
							url = "";
						}
						else {
							return whole_match;
						}
					}
				}
				url = encodeProblemUrlChars(url);
				url = escapeCharacters(url, "*_");
				var result = "<a href=\"" + url + "\"";

				if (title != "") {
					title = attributeEncode(title);
					title = escapeCharacters(title, "*_");
					result += " title=\"" + title + "\"";
				}

				result += ">" + link_text + "</a>";

				return result;
			}

			function _DoImages(text) {
				//
				// Turn Markdown image shortcuts into <img> tags.
				//

				//
				// First, handle reference-style labeled images: ![alt text][id]
				//

				/*
				text = text.replace(/
					(                   // wrap whole match in $1
						!\[
						(.*?)           // alt text = $2
						\]

						[ ]?            // one optional space
						(?:\n[ ]*)?     // one optional newline followed by spaces

						\[
						(.*?)           // id = $3
						\]
					)
					()()()()            // pad rest of backreferences
				/g, writeImageTag);
				*/
				text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);

				//
				// Next, handle inline images:  ![alt text](url "optional title")
				// Don't forget: encode * and _

				/*
				text = text.replace(/
					(                   // wrap whole match in $1
						!\[
						(.*?)           // alt text = $2
						\]
						\s?             // One optional whitespace character
						\(              // literal paren
						[ \t]*
						()              // no id, so leave $3 empty
						<?(\S+?)>?      // src url = $4
						[ \t]*
						(               // $5
							(['"])      // quote char = $6
							(.*?)       // title = $7
							\6          // matching quote
							[ \t]*
						)?              // title is optional
						\)
					)
				/g, writeImageTag);
				*/
				text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);

				return text;
			}

			function attributeEncode(text) {
				// unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title)
				// never makes sense to have verbatim HTML in it (and the sanitizer would totally break it)
				return text.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
			}

			function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
				var whole_match = m1;
				var alt_text = m2;
				var link_id = m3.toLowerCase();
				var url = m4;
				var title = m7;

				if (!title) title = "";

				if (url == "") {
					if (link_id == "") {
						// lower-case and turn embedded newlines into spaces
						link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");
					}
					url = "#" + link_id;

					if (g_urls.get(link_id) != undefined) {
						url = g_urls.get(link_id);
						if (g_titles.get(link_id) != undefined) {
							title = g_titles.get(link_id);
						}
					}
					else {
						return whole_match;
					}
				}

				alt_text = escapeCharacters(attributeEncode(alt_text), "*_[]()");
				url = escapeCharacters(url, "*_");
				var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";

				// attacklab: Markdown.pl adds empty title attributes to images.
				// Replicate this bug.

				//if (title != "") {
				title = attributeEncode(title);
				title = escapeCharacters(title, "*_");
				result += " title=\"" + title + "\"";

				//}

				result += " />";

				return result;
			}

			function _DoHeaders(text) {

				// Setext-style headers:
				//  Header 1
				//  ========
				//
				//  Header 2
				//  --------
				//
				text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
					function (wholeMatch, m1) {
						return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n";
					}
											);

				text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
					function (matchFound, m1) {
						return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n";
					}
											);

				// atx-style headers:
				//  # Header 1
				//  ## Header 2
				//  ## Header 2 with closing hashes ##
				//  ...
				//  ###### Header 6
				//

				/*
				text = text.replace(/
					^(\#{1,6})      // $1 = string of #'s
					[ \t]*
					(.+?)           // $2 = Header text
					[ \t]*
					\#*             // optional closing #'s (not counted)
					\n+
				/gm, function() {...});
				*/

				text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
					function (wholeMatch, m1, m2) {
						var h_level = m1.length;
						return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
					}
				);

				text = text.replace(/^(\%{1,6})[ \t]*(.+?)[ \t]*\%*\n+/gm,
					function (wholeMatch, m1, m2) {
						var h_level = m1.length;
						return "<dd>" + _RunSpanGamut(m2) + "</dd>\n\n";
					}
				);

				return text;
			}

			function _DoLists(text) {
				//
				// Form HTML ordered (numbered) and unordered (bulleted) lists.
				//

				// attacklab: add sentinel to hack around khtml/safari bug:
				// http://bugs.webkit.org/show_bug.cgi?id=11231
				text += "~0";

				// Re-usable pattern to match any entirel ul or ol list:

				/*
				var whole_list = /
					(                                   // $1 = whole list
						(                               // $2
							[ ]{0,3}                    // attacklab: g_tab_width - 1
							([*+-]|\d+[.])              // $3 = first list item marker
							[ \t]+
						)
						[^\r]+?
						(                               // $4
							~0                          // sentinel for workaround; should be $
							|
							\n{2,}
							(?=\S)
							(?!                         // Negative lookahead for another list item marker
								[ \t]*
								(?:[*+-]|\d+[.])[ \t]+
							)
						)
					)
				/g
				*/
				var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;

				if (g_list_level) {
					text = text.replace(whole_list, function (wholeMatch, m1, m2) {
						var list = m1;
						var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";

						var result = _ProcessListItems(list, list_type);

						// Trim any trailing whitespace, to put the closing `</$list_type>`
						// up on the preceding line, to get it past the current stupid
						// HTML block parser. This is a hack to work around the terrible
						// hack that is the HTML block parser.
						result = result.replace(/\s+$/, "");
						result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
						return result;
					});
				}
				else {
					whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
					text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
						var runup = m1;
						var list = m2;

						var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
						var result = _ProcessListItems(list, list_type);
						result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
						return result;
					});
				}

				// attacklab: strip sentinel
				text = text.replace(/~0/, "");

				return text;
			}

			var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };

			function _ProcessListItems(list_str, list_type) {
				//
				//  Process the contents of a single ordered or unordered list, splitting it
				//  into individual list items.
				//
				//  list_type is either "ul" or "ol".

				// The $g_list_level global keeps track of when we're inside a list.
				// Each time we enter a list, we increment it; when we leave a list,
				// we decrement. If it's zero, we're not in a list anymore.
				//
				// We do this because when we're not inside a list, we want to treat
				// something like this:
				//
				//    I recommend upgrading to version
				//    8. Oops, now this line is treated
				//    as a sub-list.
				//
				// As a single paragraph, despite the fact that the second line starts
				// with a digit-period-space sequence.
				//
				// Whereas when we're inside a list (or sub-list), that line will be
				// treated as the start of a sub-list. What a kludge, huh? This is
				// an aspect of Markdown's syntax that's hard to parse perfectly
				// without resorting to mind-reading. Perhaps the solution is to
				// change the syntax rules such that sub-lists must start with a
				// starting cardinal number; e.g. "1." or "a.".

				g_list_level++;

				// trim trailing blank lines:
				list_str = list_str.replace(/\n{2,}$/, "\n");

				// attacklab: add sentinel to emulate \z
				list_str += "~0";

				// In the original attacklab showdown, list_type was not given to this function, and anything
				// that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:
				//
				//  Markdown          rendered by WMD        rendered by MarkdownSharp
				//  ------------------------------------------------------------------
				//  1. first          1. first               1. first
				//  2. second         2. second              2. second
				//  - third           3. third                   * third
				//
				// We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,
				// with {MARKER} being one of \d+[.] or [*+-], depending on list_type:

				/*
				list_str = list_str.replace(/
					(^[ \t]*)                       // leading whitespace = $1
					({MARKER}) [ \t]+               // list marker = $2
					([^\r]+?                        // list item text   = $3
						(\n+)
					)
					(?=
						(~0 | \2 ({MARKER}) [ \t]+)
					)
				/gm, function(){...});
				*/

				var marker = _listItemMarkers[list_type];
				var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");
				var last_item_had_a_double_newline = false;
				list_str = list_str.replace(re,
					function (wholeMatch, m1, m2, m3) {
						var item = m3;
						var leading_space = m1;
						var ends_with_double_newline = /\n\n$/.test(item);
						var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;

						if (contains_double_newline || last_item_had_a_double_newline) {
							item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);
						}
						else {
							// Recursion for sub-lists:
							item = _DoLists(_Outdent(item));
							item = item.replace(/\n$/, ""); // chomp(item)
							item = _RunSpanGamut(item);
						}
						last_item_had_a_double_newline = ends_with_double_newline;
						return "<li>" + item + "</li>\n";
					}
				);

				// attacklab: strip sentinel
				list_str = list_str.replace(/~0/g, "");

				g_list_level--;
				return list_str;
			}

			function _DoCodeBlocks(text) {
				//
				//  Process Markdown `<pre><code>` blocks.
				//

				/*
				text = text.replace(/
					(?:\n\n|^)
					(                               // $1 = the code block -- one or more lines, starting with a space/tab
						(?:
							(?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
							.*\n+
						)+
					)
					(\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
				/g ,function(){...});
				*/

				// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
				text += "~0";

				text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
					function (wholeMatch, m1, m2) {
						var codeblock = m1;
						var nextChar = m2;

						codeblock = _EncodeCode(_Outdent(codeblock));
						codeblock = _Detab(codeblock);
						codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
						codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace

						codeblock = '<pre><code>' + codeblock + '\n</code></pre>';

						return "\n\n" + codeblock + "\n\n" + nextChar;
					}
				);

				// attacklab: strip sentinel
				text = text.replace(/~0/, "");

				return text;
			}

			function hashBlock(text) {
				text = text.replace(/(^\n+|\n+$)/g, "");
				return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
			}

			function _DoCodeSpans(text) {
				//
				// * Backtick quotes are used for <code></code> spans.
				//
				// * You can use multiple backticks as the delimiters if you want to
				//   include literal backticks in the code span. So, this input:
				//
				//      Just type ``foo `bar` baz`` at the prompt.
				//
				//   Will translate to:
				//
				//      <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
				//
				//   There's no arbitrary limit to the number of backticks you
				//   can use as delimters. If you need three consecutive backticks
				//   in your code, use four for delimiters, etc.
				//
				// * You can use spaces to get literal backticks at the edges:
				//
				//      ... type `` `bar` `` ...
				//
				//   Turns to:
				//
				//      ... type <code>`bar`</code> ...
				//

				/*
				text = text.replace(/
					(^|[^\\])       // Character before opening ` can't be a backslash
					(`+)            // $2 = Opening run of `
					(               // $3 = The code block
						[^\r]*?
						[^`]        // attacklab: work around lack of lookbehind
					)
					\2              // Matching closer
					(?!`)
				/gm, function(){...});
				*/

				text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
					function (wholeMatch, m1, m2, m3, m4) {
						var c = m3;
						c = c.replace(/^([ \t]*)/g, ""); // leading whitespace
						c = c.replace(/[ \t]*$/g, ""); // trailing whitespace
						c = _EncodeCode(c);
						c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs.
						return m1 + "<code>" + c + "</code>";
					}
				);

				return text;
			}

			function _EncodeCode(text) {
				//
				// Encode/escape certain characters inside Markdown code runs.
				// The point is that in code, these characters are literals,
				// and lose their special Markdown meanings.
				//
				// Encode all ampersands; HTML entities are not
				// entities within a Markdown code span.
				text = text.replace(/&/g, "&amp;");

				// Do the angle bracket song and dance:
				text = text.replace(/</g, "&lt;");
				text = text.replace(/>/g, "&gt;");

				// Now, escape characters that are magic in Markdown:
				text = escapeCharacters(text, "\*_{}[]\\", false);

				// jj the line above breaks this:
				//---

				//* Item

				//   1. Subitem

				//            special char: *
				//---

				return text;
			}

			function _DoItalicsAndBold(text) {

				// <strong> must go first:
				text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,
				"$1<strong>$3</strong>$4");

				// <em>
				text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,
				"$1<em>$3</em>$4");

				return text;
			}

			function _DoBlockQuotes(text) {

				/*
				text = text.replace(/
					(                           // Wrap whole match in $1
						(
							^[ \t]*>[ \t]?      // '>' at the start of a line
							.+\n                // rest of the first line
							(.+\n)*             // subsequent consecutive lines
							\n*                 // blanks
						)+
					)
				/gm, function(){...});
				*/

				text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
					function (wholeMatch, m1) {
						var bq = m1;

						// attacklab: hack around Konqueror 3.5.4 bug:
						// "----------bug".replace(/^-/g,"") == "bug"

						bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting

						// attacklab: clean up hack
						bq = bq.replace(/~0/g, "");

						bq = bq.replace(/^[ \t]+$/gm, "");     // trim whitespace-only lines
						bq = _RunBlockGamut(bq);             // recurse

						bq = bq.replace(/(^|\n)/g, "$1  ");

						// These leading spaces screw with <pre> content, so we need to fix that:
						bq = bq.replace(
								/(\s*<pre>[^\r]+?<\/pre>)/gm,
							function (wholeMatch, m1) {
								var pre = m1;

								// attacklab: hack around Konqueror 3.5.4 bug:
								pre = pre.replace(/^  /mg, "~0");
								pre = pre.replace(/~0/g, "");
								return pre;
							});

						return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
					}
				);
				return text;
			}

			function _FormParagraphs(text, doNotUnhash) {
				//
				//  Params:
				//    $text - string to process with html <p> tags
				//

				// Strip leading and trailing lines:
				text = text.replace(/^\n+/g, "");
				text = text.replace(/\n+$/g, "");

				var grafs = text.split(/\n{2,}/g);
				var grafsOut = [];

				var markerRe = /~K(\d+)K/;

				//
				// Wrap <p> tags.
				//
				var end = grafs.length;
				for (var i = 0; i < end; i++) {
					var str = grafs[i];

					// if this is an HTML marker, copy it
					if (markerRe.test(str)) {
						grafsOut.push(str);
					}
					else if (/\S/.test(str)) {
						str = _RunSpanGamut(str);
						str = str.replace(/^([ \t]*)/g, "<p>");
						str += "</p>"
						grafsOut.push(str);
					}
				}

				//
				// Unhashify HTML blocks
				//
				if (!doNotUnhash) {
					end = grafsOut.length;
					for (var i = 0; i < end; i++) {
						var foundAny = true;
						while (foundAny) {
							// we may need several runs, since the data may be nested
							foundAny = false;
							grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) {
								foundAny = true;
								return g_html_blocks[id];
							});
						}
					}
				}
				return grafsOut.join("\n\n");
			}

			function _EncodeAmpsAndAngles(text) {
				// Smart processing for ampersands and angle brackets that need to be encoded.

				// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
				//   http://bumppo.net/projects/amputator/
				text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");

				// Encode naked <'s
				text = text.replace(/<(?![a-z\/?\$!])/gi, "&lt;");

				return text;
			}

			function _EncodeBackslashEscapes(text) {
				//
				//   Parameter:  String.
				//   Returns:    The string, with after processing the following backslash
				//               escape sequences.
				//

				// attacklab: The polite way to do this is with the new
				// escapeCharacters() function:
				//
				//     text = escapeCharacters(text,"\\",true);
				//     text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
				//
				// ...but we're sidestepping its use of the (slow) RegExp constructor
				// as an optimization for Firefox.  This function gets called a LOT.

				text = text.replace(/\\(\\)/g, escapeCharacters_callback);
				text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
				return text;
			}

			function _DoAutoLinks(text) {

				// note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
				// *except* for the <http://www.foo.com> case

				// automatically add < and > around unadorned raw hyperlinks
				// must be preceded by space/BOF and followed by non-word/EOF character
				text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4");

				//  autolink anything like <http://example.com>

				var replacer = function (wholematch, m1) {
					return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>";
				}
				text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);

				// Email addresses: <address@domain.foo>
				/*
				text = text.replace(/
					<
					(?:mailto:)?
					(
						[-.\w]+
						\@
						[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
					)
					>
				/gi, _DoAutoLinks_callback());
				*/

				var email_replacer = function(wholematch, m1) {
					var mailto = 'mailto:'
					var link
					var email
					if (m1.substring(0, mailto.length) != mailto) {
						link = mailto + m1;
						email = m1;
					}
					else {
						link = m1;
						email = m1.substring(mailto.length, m1.length);
					}
					return "<a href=\"" + link + "\">" + pluginHooks.plainLinkText(email) + "</a>";
				}
				text = text.replace(/<((?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+))>/gi, email_replacer);

				return text;
			}

			function _UnescapeSpecialChars(text) {
				//
				// Swap back in all the special characters we've hidden.
				//
				text = text.replace(/~E(\d+)E/g,
					function (wholeMatch, m1) {
						var charCodeToReplace = parseInt(m1);
						return String.fromCharCode(charCodeToReplace);
					}
				);
				return text;
			}

			function _Outdent(text) {
				//
				// Remove one level of line-leading tabs or spaces
				//

				// attacklab: hack around Konqueror 3.5.4 bug:
				// "----------bug".replace(/^-/g,"") == "bug"

				text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width

				// attacklab: clean up hack
				text = text.replace(/~0/g, "")

				return text;
			}

			function _Detab(text) {
				if (!/\t/.test(text))
					return text;

				var spaces = ["    ", "   ", "  ", " "],
				skew = 0,
				v;

				return text.replace(/[\n\t]/g, function (match, offset) {
					if (match === "\n") {
						skew = offset + 1;
						return match;
					}
					v = (offset - skew) % 4;
					skew = offset + 1;
					return spaces[v];
				});
			}

			//
			//  attacklab: Utility functions
			//

			var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;

			// hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems
			function encodeProblemUrlChars(url) {
				if (!url)
					return "";

				var len = url.length;

				return url.replace(_problemUrlChars, function (match, offset) {
					if (match == "~D") // escape for dollar
						return "%24";
					if (match == ":") {
						if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
							return ":";
						if (url.substring(0, 'mailto:'.length) === 'mailto:')
							return ":";
						if (url.substring(0, 'magnet:'.length) === 'magnet:')
							return ":";
					}
					return "%" + match.charCodeAt(0).toString(16);
				});
			}

			function escapeCharacters(text, charsToEscape, afterBackslash) {
				// First we have to escape the escape characters so that
				// we can build a character class out of them
				var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";

				if (afterBackslash) {
					regexString = "\\\\" + regexString;
				}

				var regex = new RegExp(regexString, "g");
				text = text.replace(regex, escapeCharacters_callback);

				return text;
			}

			function escapeCharacters_callback(wholeMatch, m1) {
				var charCodeToEscape = m1.charCodeAt(0);
				return "~E" + charCodeToEscape + "E";
			}
			return this;
		};
	})();

	Portfolio.util.Handler.markdownConverter = Markdown;
});
