Schema.js 30 KB

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