src/pyams_skin/resources/js/ext/tinymce/dev/classes/html/Schema.js
changeset 557 bca7a7e058a3
equal deleted inserted replaced
-1:000000000000 557:bca7a7e058a3
       
     1 /**
       
     2  * Schema.js
       
     3  *
       
     4  * Copyright, Moxiecode Systems AB
       
     5  * Released under LGPL License.
       
     6  *
       
     7  * License: http://www.tinymce.com/license
       
     8  * Contributing: http://www.tinymce.com/contributing
       
     9  */
       
    10 
       
    11 /**
       
    12  * Schema validator class.
       
    13  *
       
    14  * @class tinymce.html.Schema
       
    15  * @example
       
    16  *  if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
       
    17  *    alert('span is valid child of p.');
       
    18  *
       
    19  *  if (tinymce.activeEditor.schema.getElementRule('p'))
       
    20  *    alert('P is a valid element.');
       
    21  *
       
    22  * @class tinymce.html.Schema
       
    23  * @version 3.4
       
    24  */
       
    25 define("tinymce/html/Schema", [
       
    26 	"tinymce/util/Tools"
       
    27 ], function(Tools) {
       
    28 	var mapCache = {}, dummyObj = {};
       
    29 	var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
       
    30 
       
    31 	function split(items, delim) {
       
    32 		return items ? items.split(delim || ' ') : [];
       
    33 	}
       
    34 
       
    35 	/**
       
    36 	 * Builds a schema lookup table
       
    37 	 *
       
    38 	 * @private
       
    39 	 * @param {String} type html4, html5 or html5-strict schema type.
       
    40 	 * @return {Object} Schema lookup table.
       
    41 	 */
       
    42 	function compileSchema(type) {
       
    43 		var schema = {}, globalAttributes, blockContent;
       
    44 		var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
       
    45 
       
    46 		function add(name, attributes, children) {
       
    47 			var ni, i, attributesOrder, args = arguments;
       
    48 
       
    49 			function arrayToMap(array, obj) {
       
    50 				var map = {}, i, l;
       
    51 
       
    52 				for (i = 0, l = array.length; i < l; i++) {
       
    53 					map[array[i]] = obj || {};
       
    54 				}
       
    55 
       
    56 				return map;
       
    57 			}
       
    58 
       
    59 			children = children || [];
       
    60 			attributes = attributes || "";
       
    61 
       
    62 			if (typeof children === "string") {
       
    63 				children = split(children);
       
    64 			}
       
    65 
       
    66 			// Split string children
       
    67 			for (i = 3; i < args.length; i++) {
       
    68 				if (typeof args[i] === "string") {
       
    69 					args[i] = split(args[i]);
       
    70 				}
       
    71 
       
    72 				children.push.apply(children, args[i]);
       
    73 			}
       
    74 
       
    75 			name = split(name);
       
    76 			ni = name.length;
       
    77 			while (ni--) {
       
    78 				attributesOrder = [].concat(globalAttributes, split(attributes));
       
    79 				schema[name[ni]] = {
       
    80 					attributes: arrayToMap(attributesOrder),
       
    81 					attributesOrder: attributesOrder,
       
    82 					children: arrayToMap(children, dummyObj)
       
    83 				};
       
    84 			}
       
    85 		}
       
    86 
       
    87 		function addAttrs(name, attributes) {
       
    88 			var ni, schemaItem, i, l;
       
    89 
       
    90 			name = split(name);
       
    91 			ni = name.length;
       
    92 			attributes = split(attributes);
       
    93 			while (ni--) {
       
    94 				schemaItem = schema[name[ni]];
       
    95 				for (i = 0, l = attributes.length; i < l; i++) {
       
    96 					schemaItem.attributes[attributes[i]] = {};
       
    97 					schemaItem.attributesOrder.push(attributes[i]);
       
    98 				}
       
    99 			}
       
   100 		}
       
   101 
       
   102 		// Use cached schema
       
   103 		if (mapCache[type]) {
       
   104 			return mapCache[type];
       
   105 		}
       
   106 
       
   107 		// Attributes present on all elements
       
   108 		globalAttributes = split("id accesskey class dir lang style tabindex title");
       
   109 
       
   110 		// Event attributes can be opt-in/opt-out
       
   111 		/*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
       
   112 				"ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
       
   113 				"onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
       
   114 				"onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
       
   115 				"onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
       
   116 				"onwaiting"
       
   117 		);*/
       
   118 
       
   119 		// Block content elements
       
   120 		blockContent = split(
       
   121 			"address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
       
   122 		);
       
   123 
       
   124 		// Phrasing content elements from the HTML5 spec (inline)
       
   125 		phrasingContent = split(
       
   126 			"a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
       
   127 			"label map noscript object q s samp script select small span strong sub sup " +
       
   128 			"textarea u var #text #comment"
       
   129 		);
       
   130 
       
   131 		// Add HTML5 items to globalAttributes, blockContent, phrasingContent
       
   132 		if (type != "html4") {
       
   133 			globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
       
   134 				"hidden spellcheck translate"));
       
   135 			blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
       
   136 			phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
       
   137 				"video ruby bdi keygen"));
       
   138 		}
       
   139 
       
   140 		// Add HTML4 elements unless it's html5-strict
       
   141 		if (type != "html5-strict") {
       
   142 			globalAttributes.push("xml:lang");
       
   143 
       
   144 			html4PhrasingContent = split("acronym applet basefont big font strike tt");
       
   145 			phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
       
   146 
       
   147 			each(html4PhrasingContent, function(name) {
       
   148 				add(name, "", phrasingContent);
       
   149 			});
       
   150 
       
   151 			html4BlockContent = split("center dir isindex noframes");
       
   152 			blockContent.push.apply(blockContent, html4BlockContent);
       
   153 
       
   154 			// Flow content elements from the HTML5 spec (block+inline)
       
   155 			flowContent = [].concat(blockContent, phrasingContent);
       
   156 
       
   157 			each(html4BlockContent, function(name) {
       
   158 				add(name, "", flowContent);
       
   159 			});
       
   160 		}
       
   161 
       
   162 		// Flow content elements from the HTML5 spec (block+inline)
       
   163 		flowContent = flowContent || [].concat(blockContent, phrasingContent);
       
   164 
       
   165 		// HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
       
   166 		// Schema items <element name>, <specific attributes>, <children ..>
       
   167 		add("html", "manifest", "head body");
       
   168 		add("head", "", "base command link meta noscript script style title");
       
   169 		add("title hr noscript br");
       
   170 		add("base", "href target");
       
   171 		add("link", "href rel media hreflang type sizes hreflang");
       
   172 		add("meta", "name http-equiv content charset");
       
   173 		add("style", "media type scoped");
       
   174 		add("script", "src async defer type charset");
       
   175 		add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
       
   176 				"onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
       
   177 				"onpopstate onresize onscroll onstorage onunload", flowContent);
       
   178 		add("address dt dd div caption", "", flowContent);
       
   179 		add("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn", "", phrasingContent);
       
   180 		add("blockquote", "cite", flowContent);
       
   181 		add("ol", "reversed start type", "li");
       
   182 		add("ul", "", "li");
       
   183 		add("li", "value", flowContent);
       
   184 		add("dl", "", "dt dd");
       
   185 		add("a", "href target rel media hreflang type", phrasingContent);
       
   186 		add("q", "cite", phrasingContent);
       
   187 		add("ins del", "cite datetime", flowContent);
       
   188 		add("img", "src sizes srcset alt usemap ismap width height");
       
   189 		add("iframe", "src name width height", flowContent);
       
   190 		add("embed", "src type width height");
       
   191 		add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
       
   192 		add("param", "name value");
       
   193 		add("map", "name", flowContent, "area");
       
   194 		add("area", "alt coords shape href target rel media hreflang type");
       
   195 		add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
       
   196 		add("colgroup", "span", "col");
       
   197 		add("col", "span");
       
   198 		add("tbody thead tfoot", "", "tr");
       
   199 		add("tr", "", "td th");
       
   200 		add("td", "colspan rowspan headers", flowContent);
       
   201 		add("th", "colspan rowspan headers scope abbr", flowContent);
       
   202 		add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
       
   203 		add("fieldset", "disabled form name", flowContent, "legend");
       
   204 		add("label", "form for", phrasingContent);
       
   205 		add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
       
   206 				"formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
       
   207 		);
       
   208 		add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
       
   209 			type == "html4" ? flowContent : phrasingContent);
       
   210 		add("select", "disabled form multiple name required size", "option optgroup");
       
   211 		add("optgroup", "disabled label", "option");
       
   212 		add("option", "disabled label selected value");
       
   213 		add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
       
   214 		add("menu", "type label", flowContent, "li");
       
   215 		add("noscript", "", flowContent);
       
   216 
       
   217 		// Extend with HTML5 elements
       
   218 		if (type != "html4") {
       
   219 			add("wbr");
       
   220 			add("ruby", "", phrasingContent, "rt rp");
       
   221 			add("figcaption", "", flowContent);
       
   222 			add("mark rt rp summary bdi", "", phrasingContent);
       
   223 			add("canvas", "width height", flowContent);
       
   224 			add("video", "src crossorigin poster preload autoplay mediagroup loop " +
       
   225 				"muted controls width height buffered", flowContent, "track source");
       
   226 			add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
       
   227 			add("picture", "", "img source");
       
   228 			add("source", "src srcset type media sizes");
       
   229 			add("track", "kind src srclang label default");
       
   230 			add("datalist", "", phrasingContent, "option");
       
   231 			add("article section nav aside header footer", "", flowContent);
       
   232 			add("hgroup", "", "h1 h2 h3 h4 h5 h6");
       
   233 			add("figure", "", flowContent, "figcaption");
       
   234 			add("time", "datetime", phrasingContent);
       
   235 			add("dialog", "open", flowContent);
       
   236 			add("command", "type label icon disabled checked radiogroup command");
       
   237 			add("output", "for form name", phrasingContent);
       
   238 			add("progress", "value max", phrasingContent);
       
   239 			add("meter", "value min max low high optimum", phrasingContent);
       
   240 			add("details", "open", flowContent, "summary");
       
   241 			add("keygen", "autofocus challenge disabled form keytype name");
       
   242 		}
       
   243 
       
   244 		// Extend with HTML4 attributes unless it's html5-strict
       
   245 		if (type != "html5-strict") {
       
   246 			addAttrs("script", "language xml:space");
       
   247 			addAttrs("style", "xml:space");
       
   248 			addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
       
   249 			addAttrs("embed", "align name hspace vspace");
       
   250 			addAttrs("param", "valuetype type");
       
   251 			addAttrs("a", "charset name rev shape coords");
       
   252 			addAttrs("br", "clear");
       
   253 			addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
       
   254 			addAttrs("img", "name longdesc align border hspace vspace");
       
   255 			addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
       
   256 			addAttrs("font basefont", "size color face");
       
   257 			addAttrs("input", "usemap align");
       
   258 			addAttrs("select", "onchange");
       
   259 			addAttrs("textarea");
       
   260 			addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
       
   261 			addAttrs("ul", "type compact");
       
   262 			addAttrs("li", "type");
       
   263 			addAttrs("ol dl menu dir", "compact");
       
   264 			addAttrs("pre", "width xml:space");
       
   265 			addAttrs("hr", "align noshade size width");
       
   266 			addAttrs("isindex", "prompt");
       
   267 			addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
       
   268 			addAttrs("col", "width align char charoff valign");
       
   269 			addAttrs("colgroup", "width align char charoff valign");
       
   270 			addAttrs("thead", "align char charoff valign");
       
   271 			addAttrs("tr", "align char charoff valign bgcolor");
       
   272 			addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
       
   273 			addAttrs("form", "accept");
       
   274 			addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
       
   275 			addAttrs("tfoot", "align char charoff valign");
       
   276 			addAttrs("tbody", "align char charoff valign");
       
   277 			addAttrs("area", "nohref");
       
   278 			addAttrs("body", "background bgcolor text link vlink alink");
       
   279 		}
       
   280 
       
   281 		// Extend with HTML5 attributes unless it's html4
       
   282 		if (type != "html4") {
       
   283 			addAttrs("input button select textarea", "autofocus");
       
   284 			addAttrs("input textarea", "placeholder");
       
   285 			addAttrs("a", "download");
       
   286 			addAttrs("link script img", "crossorigin");
       
   287 			addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
       
   288 		}
       
   289 
       
   290 		// Special: iframe, ruby, video, audio, label
       
   291 
       
   292 		// Delete children of the same name from it's parent
       
   293 		// For example: form can't have a child of the name form
       
   294 		each(split('a form meter progress dfn'), function(name) {
       
   295 			if (schema[name]) {
       
   296 				delete schema[name].children[name];
       
   297 			}
       
   298 		});
       
   299 
       
   300 		// Delete header, footer, sectioning and heading content descendants
       
   301 		/*each('dt th address', function(name) {
       
   302 			delete schema[name].children[name];
       
   303 		});*/
       
   304 
       
   305 		// Caption can't have tables
       
   306 		delete schema.caption.children.table;
       
   307 
       
   308 		// TODO: LI:s can only have value if parent is OL
       
   309 
       
   310 		// TODO: Handle transparent elements
       
   311 		// a ins del canvas map
       
   312 
       
   313 		mapCache[type] = schema;
       
   314 
       
   315 		return schema;
       
   316 	}
       
   317 
       
   318 	function compileElementMap(value, mode) {
       
   319 		var styles;
       
   320 
       
   321 		if (value) {
       
   322 			styles = {};
       
   323 
       
   324 			if (typeof value == 'string') {
       
   325 				value = {
       
   326 					'*': value
       
   327 				};
       
   328 			}
       
   329 
       
   330 			// Convert styles into a rule list
       
   331 			each(value, function(value, key) {
       
   332 				styles[key] = styles[key.toUpperCase()] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
       
   333 			});
       
   334 		}
       
   335 
       
   336 		return styles;
       
   337 	}
       
   338 
       
   339 	/**
       
   340 	 * Constructs a new Schema instance.
       
   341 	 *
       
   342 	 * @constructor
       
   343 	 * @method Schema
       
   344 	 * @param {Object} settings Name/value settings object.
       
   345 	 */
       
   346 	return function(settings) {
       
   347 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
       
   348 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
       
   349 		var blockElementsMap, nonEmptyElementsMap, moveCaretBeforeOnEnterElementsMap, textBlockElementsMap, textInlineElementsMap;
       
   350 		var customElementsMap = {}, specialElements = {};
       
   351 
       
   352 		// Creates an lookup table map object for the specified option or the default value
       
   353 		function createLookupTable(option, default_value, extendWith) {
       
   354 			var value = settings[option];
       
   355 
       
   356 			if (!value) {
       
   357 				// Get cached default map or make it if needed
       
   358 				value = mapCache[option];
       
   359 
       
   360 				if (!value) {
       
   361 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
       
   362 					value = extend(value, extendWith);
       
   363 
       
   364 					mapCache[option] = value;
       
   365 				}
       
   366 			} else {
       
   367 				// Create custom map
       
   368 				value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
       
   369 			}
       
   370 
       
   371 			return value;
       
   372 		}
       
   373 
       
   374 		settings = settings || {};
       
   375 		schemaItems = compileSchema(settings.schema);
       
   376 
       
   377 		// Allow all elements and attributes if verify_html is set to false
       
   378 		if (settings.verify_html === false) {
       
   379 			settings.valid_elements = '*[*]';
       
   380 		}
       
   381 
       
   382 		validStyles = compileElementMap(settings.valid_styles);
       
   383 		invalidStyles = compileElementMap(settings.invalid_styles, 'map');
       
   384 		validClasses = compileElementMap(settings.valid_classes, 'map');
       
   385 
       
   386 		// Setup map objects
       
   387 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
       
   388 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
       
   389 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
       
   390 			'meta param embed source wbr track');
       
   391 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
       
   392 			'noshade nowrap readonly selected autoplay loop controls');
       
   393 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
       
   394 		moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', 'table', nonEmptyElementsMap);
       
   395 		textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
       
   396 						'blockquote center dir fieldset header footer article section hgroup aside nav figure');
       
   397 		blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
       
   398 						'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
       
   399 						'datalist select optgroup', textBlockElementsMap);
       
   400 		textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
       
   401 										'dfn code mark q sup sub samp');
       
   402 
       
   403 		each((settings.special || 'script noscript style textarea').split(' '), function(name) {
       
   404 			specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
       
   405 		});
       
   406 
       
   407 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
       
   408 		function patternToRegExp(str) {
       
   409 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
       
   410 		}
       
   411 
       
   412 		// Parses the specified valid_elements string and adds to the current rules
       
   413 		// This function is a bit hard to read since it's heavily optimized for speed
       
   414 		function addValidElements(validElements) {
       
   415 			var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
       
   416 				prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
       
   417 				elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
       
   418 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
       
   419 				hasPatternsRegExp = /[*?+]/;
       
   420 
       
   421 			if (validElements) {
       
   422 				// Split valid elements into an array with rules
       
   423 				validElements = split(validElements, ',');
       
   424 
       
   425 				if (elements['@']) {
       
   426 					globalAttributes = elements['@'].attributes;
       
   427 					globalAttributesOrder = elements['@'].attributesOrder;
       
   428 				}
       
   429 
       
   430 				// Loop all rules
       
   431 				for (ei = 0, el = validElements.length; ei < el; ei++) {
       
   432 					// Parse element rule
       
   433 					matches = elementRuleRegExp.exec(validElements[ei]);
       
   434 					if (matches) {
       
   435 						// Setup local names for matches
       
   436 						prefix = matches[1];
       
   437 						elementName = matches[2];
       
   438 						outputName = matches[3];
       
   439 						attrData = matches[5];
       
   440 
       
   441 						// Create new attributes and attributesOrder
       
   442 						attributes = {};
       
   443 						attributesOrder = [];
       
   444 
       
   445 						// Create the new element
       
   446 						element = {
       
   447 							attributes: attributes,
       
   448 							attributesOrder: attributesOrder
       
   449 						};
       
   450 
       
   451 						// Padd empty elements prefix
       
   452 						if (prefix === '#') {
       
   453 							element.paddEmpty = true;
       
   454 						}
       
   455 
       
   456 						// Remove empty elements prefix
       
   457 						if (prefix === '-') {
       
   458 							element.removeEmpty = true;
       
   459 						}
       
   460 
       
   461 						if (matches[4] === '!') {
       
   462 							element.removeEmptyAttrs = true;
       
   463 						}
       
   464 
       
   465 						// Copy attributes from global rule into current rule
       
   466 						if (globalAttributes) {
       
   467 							for (key in globalAttributes) {
       
   468 								attributes[key] = globalAttributes[key];
       
   469 							}
       
   470 
       
   471 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
       
   472 						}
       
   473 
       
   474 						// Attributes defined
       
   475 						if (attrData) {
       
   476 							attrData = split(attrData, '|');
       
   477 							for (ai = 0, al = attrData.length; ai < al; ai++) {
       
   478 								matches = attrRuleRegExp.exec(attrData[ai]);
       
   479 								if (matches) {
       
   480 									attr = {};
       
   481 									attrType = matches[1];
       
   482 									attrName = matches[2].replace(/::/g, ':');
       
   483 									prefix = matches[3];
       
   484 									value = matches[4];
       
   485 
       
   486 									// Required
       
   487 									if (attrType === '!') {
       
   488 										element.attributesRequired = element.attributesRequired || [];
       
   489 										element.attributesRequired.push(attrName);
       
   490 										attr.required = true;
       
   491 									}
       
   492 
       
   493 									// Denied from global
       
   494 									if (attrType === '-') {
       
   495 										delete attributes[attrName];
       
   496 										attributesOrder.splice(inArray(attributesOrder, attrName), 1);
       
   497 										continue;
       
   498 									}
       
   499 
       
   500 									// Default value
       
   501 									if (prefix) {
       
   502 										// Default value
       
   503 										if (prefix === '=') {
       
   504 											element.attributesDefault = element.attributesDefault || [];
       
   505 											element.attributesDefault.push({name: attrName, value: value});
       
   506 											attr.defaultValue = value;
       
   507 										}
       
   508 
       
   509 										// Forced value
       
   510 										if (prefix === ':') {
       
   511 											element.attributesForced = element.attributesForced || [];
       
   512 											element.attributesForced.push({name: attrName, value: value});
       
   513 											attr.forcedValue = value;
       
   514 										}
       
   515 
       
   516 										// Required values
       
   517 										if (prefix === '<') {
       
   518 											attr.validValues = makeMap(value, '?');
       
   519 										}
       
   520 									}
       
   521 
       
   522 									// Check for attribute patterns
       
   523 									if (hasPatternsRegExp.test(attrName)) {
       
   524 										element.attributePatterns = element.attributePatterns || [];
       
   525 										attr.pattern = patternToRegExp(attrName);
       
   526 										element.attributePatterns.push(attr);
       
   527 									} else {
       
   528 										// Add attribute to order list if it doesn't already exist
       
   529 										if (!attributes[attrName]) {
       
   530 											attributesOrder.push(attrName);
       
   531 										}
       
   532 
       
   533 										attributes[attrName] = attr;
       
   534 									}
       
   535 								}
       
   536 							}
       
   537 						}
       
   538 
       
   539 						// Global rule, store away these for later usage
       
   540 						if (!globalAttributes && elementName == '@') {
       
   541 							globalAttributes = attributes;
       
   542 							globalAttributesOrder = attributesOrder;
       
   543 						}
       
   544 
       
   545 						// Handle substitute elements such as b/strong
       
   546 						if (outputName) {
       
   547 							element.outputName = elementName;
       
   548 							elements[outputName] = element;
       
   549 						}
       
   550 
       
   551 						// Add pattern or exact element
       
   552 						if (hasPatternsRegExp.test(elementName)) {
       
   553 							element.pattern = patternToRegExp(elementName);
       
   554 							patternElements.push(element);
       
   555 						} else {
       
   556 							elements[elementName] = element;
       
   557 						}
       
   558 					}
       
   559 				}
       
   560 			}
       
   561 		}
       
   562 
       
   563 		function setValidElements(validElements) {
       
   564 			elements = {};
       
   565 			patternElements = [];
       
   566 
       
   567 			addValidElements(validElements);
       
   568 
       
   569 			each(schemaItems, function(element, name) {
       
   570 				children[name] = element.children;
       
   571 			});
       
   572 		}
       
   573 
       
   574 		// Adds custom non HTML elements to the schema
       
   575 		function addCustomElements(customElements) {
       
   576 			var customElementRegExp = /^(~)?(.+)$/;
       
   577 
       
   578 			if (customElements) {
       
   579 				// Flush cached items since we are altering the default maps
       
   580 				mapCache.text_block_elements = mapCache.block_elements = null;
       
   581 
       
   582 				each(split(customElements, ','), function(rule) {
       
   583 					var matches = customElementRegExp.exec(rule),
       
   584 						inline = matches[1] === '~',
       
   585 						cloneName = inline ? 'span' : 'div',
       
   586 						name = matches[2];
       
   587 
       
   588 					children[name] = children[cloneName];
       
   589 					customElementsMap[name] = cloneName;
       
   590 
       
   591 					// If it's not marked as inline then add it to valid block elements
       
   592 					if (!inline) {
       
   593 						blockElementsMap[name.toUpperCase()] = {};
       
   594 						blockElementsMap[name] = {};
       
   595 					}
       
   596 
       
   597 					// Add elements clone if needed
       
   598 					if (!elements[name]) {
       
   599 						var customRule = elements[cloneName];
       
   600 
       
   601 						customRule = extend({}, customRule);
       
   602 						delete customRule.removeEmptyAttrs;
       
   603 						delete customRule.removeEmpty;
       
   604 
       
   605 						elements[name] = customRule;
       
   606 					}
       
   607 
       
   608 					// Add custom elements at span/div positions
       
   609 					each(children, function(element, elmName) {
       
   610 						if (element[cloneName]) {
       
   611 							children[elmName] = element = extend({}, children[elmName]);
       
   612 							element[name] = element[cloneName];
       
   613 						}
       
   614 					});
       
   615 				});
       
   616 			}
       
   617 		}
       
   618 
       
   619 		// Adds valid children to the schema object
       
   620 		function addValidChildren(validChildren) {
       
   621 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
       
   622 
       
   623 			if (validChildren) {
       
   624 				each(split(validChildren, ','), function(rule) {
       
   625 					var matches = childRuleRegExp.exec(rule), parent, prefix;
       
   626 
       
   627 					if (matches) {
       
   628 						prefix = matches[1];
       
   629 
       
   630 						// Add/remove items from default
       
   631 						if (prefix) {
       
   632 							parent = children[matches[2]];
       
   633 						} else {
       
   634 							parent = children[matches[2]] = {'#comment': {}};
       
   635 						}
       
   636 
       
   637 						parent = children[matches[2]];
       
   638 
       
   639 						each(split(matches[3], '|'), function(child) {
       
   640 							if (prefix === '-') {
       
   641 								// Clone the element before we delete
       
   642 								// things in it to not mess up default schemas
       
   643 								children[matches[2]] = parent = extend({}, children[matches[2]]);
       
   644 
       
   645 								delete parent[child];
       
   646 							} else {
       
   647 								parent[child] = {};
       
   648 							}
       
   649 						});
       
   650 					}
       
   651 				});
       
   652 			}
       
   653 		}
       
   654 
       
   655 		function getElementRule(name) {
       
   656 			var element = elements[name], i;
       
   657 
       
   658 			// Exact match found
       
   659 			if (element) {
       
   660 				return element;
       
   661 			}
       
   662 
       
   663 			// No exact match then try the patterns
       
   664 			i = patternElements.length;
       
   665 			while (i--) {
       
   666 				element = patternElements[i];
       
   667 
       
   668 				if (element.pattern.test(name)) {
       
   669 					return element;
       
   670 				}
       
   671 			}
       
   672 		}
       
   673 
       
   674 		if (!settings.valid_elements) {
       
   675 			// No valid elements defined then clone the elements from the schema spec
       
   676 			each(schemaItems, function(element, name) {
       
   677 				elements[name] = {
       
   678 					attributes: element.attributes,
       
   679 					attributesOrder: element.attributesOrder
       
   680 				};
       
   681 
       
   682 				children[name] = element.children;
       
   683 			});
       
   684 
       
   685 			// Switch these on HTML4
       
   686 			if (settings.schema != "html5") {
       
   687 				each(split('strong/b em/i'), function(item) {
       
   688 					item = split(item, '/');
       
   689 					elements[item[1]].outputName = item[0];
       
   690 				});
       
   691 			}
       
   692 
       
   693 			// Add default alt attribute for images
       
   694 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
       
   695 
       
   696 			// Remove these if they are empty by default
       
   697 			each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
       
   698 				if (elements[name]) {
       
   699 					elements[name].removeEmpty = true;
       
   700 				}
       
   701 			});
       
   702 
       
   703 			// Padd these by default
       
   704 			each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
       
   705 				elements[name].paddEmpty = true;
       
   706 			});
       
   707 
       
   708 			// Remove these if they have no attributes
       
   709 			each(split('span'), function(name) {
       
   710 				elements[name].removeEmptyAttrs = true;
       
   711 			});
       
   712 
       
   713 			// Remove these by default
       
   714 			// TODO: Reenable in 4.1
       
   715 			/*each(split('script style'), function(name) {
       
   716 				delete elements[name];
       
   717 			});*/
       
   718 		} else {
       
   719 			setValidElements(settings.valid_elements);
       
   720 		}
       
   721 
       
   722 		addCustomElements(settings.custom_elements);
       
   723 		addValidChildren(settings.valid_children);
       
   724 		addValidElements(settings.extended_valid_elements);
       
   725 
       
   726 		// Todo: Remove this when we fix list handling to be valid
       
   727 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
       
   728 
       
   729 		// Delete invalid elements
       
   730 		if (settings.invalid_elements) {
       
   731 			each(explode(settings.invalid_elements), function(item) {
       
   732 				if (elements[item]) {
       
   733 					delete elements[item];
       
   734 				}
       
   735 			});
       
   736 		}
       
   737 
       
   738 		// If the user didn't allow span only allow internal spans
       
   739 		if (!getElementRule('span')) {
       
   740 			addValidElements('span[!data-mce-type|*]');
       
   741 		}
       
   742 
       
   743 		/**
       
   744 		 * Name/value map object with valid parents and children to those parents.
       
   745 		 *
       
   746 		 * @example
       
   747 		 * children = {
       
   748 		 *    div:{p:{}, h1:{}}
       
   749 		 * };
       
   750 		 * @field children
       
   751 		 * @type Object
       
   752 		 */
       
   753 		self.children = children;
       
   754 
       
   755 		/**
       
   756 		 * Name/value map object with valid styles for each element.
       
   757 		 *
       
   758 		 * @method getValidStyles
       
   759 		 * @type Object
       
   760 		 */
       
   761 		self.getValidStyles = function() {
       
   762 			return validStyles;
       
   763 		};
       
   764 
       
   765 		/**
       
   766 		 * Name/value map object with valid styles for each element.
       
   767 		 *
       
   768 		 * @method getInvalidStyles
       
   769 		 * @type Object
       
   770 		 */
       
   771 		self.getInvalidStyles = function() {
       
   772 			return invalidStyles;
       
   773 		};
       
   774 
       
   775 		/**
       
   776 		 * Name/value map object with valid classes for each element.
       
   777 		 *
       
   778 		 * @method getValidClasses
       
   779 		 * @type Object
       
   780 		 */
       
   781 		self.getValidClasses = function() {
       
   782 			return validClasses;
       
   783 		};
       
   784 
       
   785 		/**
       
   786 		 * Returns a map with boolean attributes.
       
   787 		 *
       
   788 		 * @method getBoolAttrs
       
   789 		 * @return {Object} Name/value lookup map for boolean attributes.
       
   790 		 */
       
   791 		self.getBoolAttrs = function() {
       
   792 			return boolAttrMap;
       
   793 		};
       
   794 
       
   795 		/**
       
   796 		 * Returns a map with block elements.
       
   797 		 *
       
   798 		 * @method getBlockElements
       
   799 		 * @return {Object} Name/value lookup map for block elements.
       
   800 		 */
       
   801 		self.getBlockElements = function() {
       
   802 			return blockElementsMap;
       
   803 		};
       
   804 
       
   805 		/**
       
   806 		 * Returns a map with text block elements. Such as: p,h1-h6,div,address
       
   807 		 *
       
   808 		 * @method getTextBlockElements
       
   809 		 * @return {Object} Name/value lookup map for block elements.
       
   810 		 */
       
   811 		self.getTextBlockElements = function() {
       
   812 			return textBlockElementsMap;
       
   813 		};
       
   814 
       
   815 		/**
       
   816 		 * Returns a map of inline text format nodes for example strong/span or ins.
       
   817 		 *
       
   818 		 * @method getTextInlineElements
       
   819 		 * @return {Object} Name/value lookup map for text format elements.
       
   820 		 */
       
   821 		self.getTextInlineElements = function() {
       
   822 			return textInlineElementsMap;
       
   823 		};
       
   824 
       
   825 		/**
       
   826 		 * Returns a map with short ended elements such as BR or IMG.
       
   827 		 *
       
   828 		 * @method getShortEndedElements
       
   829 		 * @return {Object} Name/value lookup map for short ended elements.
       
   830 		 */
       
   831 		self.getShortEndedElements = function() {
       
   832 			return shortEndedElementsMap;
       
   833 		};
       
   834 
       
   835 		/**
       
   836 		 * Returns a map with self closing tags such as <li>.
       
   837 		 *
       
   838 		 * @method getSelfClosingElements
       
   839 		 * @return {Object} Name/value lookup map for self closing tags elements.
       
   840 		 */
       
   841 		self.getSelfClosingElements = function() {
       
   842 			return selfClosingElementsMap;
       
   843 		};
       
   844 
       
   845 		/**
       
   846 		 * Returns a map with elements that should be treated as contents regardless if it has text
       
   847 		 * content in them or not such as TD, VIDEO or IMG.
       
   848 		 *
       
   849 		 * @method getNonEmptyElements
       
   850 		 * @return {Object} Name/value lookup map for non empty elements.
       
   851 		 */
       
   852 		self.getNonEmptyElements = function() {
       
   853 			return nonEmptyElementsMap;
       
   854 		};
       
   855 
       
   856 		/**
       
   857 		 * Returns a map with elements that the caret should be moved in front of after enter is
       
   858 		 * pressed
       
   859 		 *
       
   860 		 * @method getMoveCaretBeforeOnEnterElements
       
   861 		 * @return {Object} Name/value lookup map for elements to place the caret in front of.
       
   862 		 */
       
   863 		self.getMoveCaretBeforeOnEnterElements = function() {
       
   864 			return moveCaretBeforeOnEnterElementsMap;
       
   865 		};
       
   866 
       
   867 		/**
       
   868 		 * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
       
   869 		 *
       
   870 		 * @method getWhiteSpaceElements
       
   871 		 * @return {Object} Name/value lookup map for white space elements.
       
   872 		 */
       
   873 		self.getWhiteSpaceElements = function() {
       
   874 			return whiteSpaceElementsMap;
       
   875 		};
       
   876 
       
   877 		/**
       
   878 		 * Returns a map with special elements. These are elements that needs to be parsed
       
   879 		 * in a special way such as script, style, textarea etc. The map object values
       
   880 		 * are regexps used to find the end of the element.
       
   881 		 *
       
   882 		 * @method getSpecialElements
       
   883 		 * @return {Object} Name/value lookup map for special elements.
       
   884 		 */
       
   885 		self.getSpecialElements = function() {
       
   886 			return specialElements;
       
   887 		};
       
   888 
       
   889 		/**
       
   890 		 * Returns true/false if the specified element and it's child is valid or not
       
   891 		 * according to the schema.
       
   892 		 *
       
   893 		 * @method isValidChild
       
   894 		 * @param {String} name Element name to check for.
       
   895 		 * @param {String} child Element child to verify.
       
   896 		 * @return {Boolean} True/false if the element is a valid child of the specified parent.
       
   897 		 */
       
   898 		self.isValidChild = function(name, child) {
       
   899 			var parent = children[name];
       
   900 
       
   901 			return !!(parent && parent[child]);
       
   902 		};
       
   903 
       
   904 		/**
       
   905 		 * Returns true/false if the specified element name and optional attribute is
       
   906 		 * valid according to the schema.
       
   907 		 *
       
   908 		 * @method isValid
       
   909 		 * @param {String} name Name of element to check.
       
   910 		 * @param {String} attr Optional attribute name to check for.
       
   911 		 * @return {Boolean} True/false if the element and attribute is valid.
       
   912 		 */
       
   913 		self.isValid = function(name, attr) {
       
   914 			var attrPatterns, i, rule = getElementRule(name);
       
   915 
       
   916 			// Check if it's a valid element
       
   917 			if (rule) {
       
   918 				if (attr) {
       
   919 					// Check if attribute name exists
       
   920 					if (rule.attributes[attr]) {
       
   921 						return true;
       
   922 					}
       
   923 
       
   924 					// Check if attribute matches a regexp pattern
       
   925 					attrPatterns = rule.attributePatterns;
       
   926 					if (attrPatterns) {
       
   927 						i = attrPatterns.length;
       
   928 						while (i--) {
       
   929 							if (attrPatterns[i].pattern.test(name)) {
       
   930 								return true;
       
   931 							}
       
   932 						}
       
   933 					}
       
   934 				} else {
       
   935 					return true;
       
   936 				}
       
   937 			}
       
   938 
       
   939 			// No match
       
   940 			return false;
       
   941 		};
       
   942 
       
   943 		/**
       
   944 		 * Returns true/false if the specified element is valid or not
       
   945 		 * according to the schema.
       
   946 		 *
       
   947 		 * @method getElementRule
       
   948 		 * @param {String} name Element name to check for.
       
   949 		 * @return {Object} Element object or undefined if the element isn't valid.
       
   950 		 */
       
   951 		self.getElementRule = getElementRule;
       
   952 
       
   953 		/**
       
   954 		 * Returns an map object of all custom elements.
       
   955 		 *
       
   956 		 * @method getCustomElements
       
   957 		 * @return {Object} Name/value map object of all custom elements.
       
   958 		 */
       
   959 		self.getCustomElements = function() {
       
   960 			return customElementsMap;
       
   961 		};
       
   962 
       
   963 		/**
       
   964 		 * Parses a valid elements string and adds it to the schema. The valid elements
       
   965 		 * format is for example "element[attr=default|otherattr]".
       
   966 		 * Existing rules will be replaced with the ones specified, so this extends the schema.
       
   967 		 *
       
   968 		 * @method addValidElements
       
   969 		 * @param {String} valid_elements String in the valid elements format to be parsed.
       
   970 		 */
       
   971 		self.addValidElements = addValidElements;
       
   972 
       
   973 		/**
       
   974 		 * Parses a valid elements string and sets it to the schema. The valid elements
       
   975 		 * format is for example "element[attr=default|otherattr]".
       
   976 		 * Existing rules will be replaced with the ones specified, so this extends the schema.
       
   977 		 *
       
   978 		 * @method setValidElements
       
   979 		 * @param {String} valid_elements String in the valid elements format to be parsed.
       
   980 		 */
       
   981 		self.setValidElements = setValidElements;
       
   982 
       
   983 		/**
       
   984 		 * Adds custom non HTML elements to the schema.
       
   985 		 *
       
   986 		 * @method addCustomElements
       
   987 		 * @param {String} custom_elements Comma separated list of custom elements to add.
       
   988 		 */
       
   989 		self.addCustomElements = addCustomElements;
       
   990 
       
   991 		/**
       
   992 		 * Parses a valid children string and adds them to the schema structure. The valid children
       
   993 		 * format is for example: "element[child1|child2]".
       
   994 		 *
       
   995 		 * @method addValidChildren
       
   996 		 * @param {String} valid_children Valid children elements string to parse
       
   997 		 */
       
   998 		self.addValidChildren = addValidChildren;
       
   999 
       
  1000 		self.elements = elements;
       
  1001 	};
       
  1002 });