/** * $Id$ * * @author Moxiecode * @copyright Copyright © 2004-2007, Moxiecode Systems AB, All rights reserved. * * Some of the contents of this file will be wrapped in a class later on it will also be replaced with the new cleanup logic. */ /**#@+ * @member TinyMCE_Engine * @method */ tinyMCE.add(TinyMCE_Engine, { /** * Makes some preprocessing cleanup routines on the specified HTML string. * This includes forcing some tags to be open so MSIE doesn't fail. Forcing other to close and * padding paragraphs with non breaking spaces. This function is used when the editor gets * initialized with content. * * @param {string} s HTML string to cleanup. * @return Cleaned HTML string. * @type string */ cleanupHTMLCode : function(s) { s = s.replace(new RegExp('

', 'gi'), '

 

'); s = s.replace(new RegExp('

\\s*<\\/p>', 'gi'), '

 

'); // Fix close BR elements s = s.replace(new RegExp('
\\s*<\\/br>', 'gi'), '
'); // Open closed tags like to s = s.replace(new RegExp('<(h[1-6]|p|div|address|pre|form|table|li|ol|ul|td|b|font|em|strong|i|strike|u|span|a|ul|ol|li|blockquote)([a-z]*)([^\\\\|>]*)\\/>', 'gi'), '<$1$2$3>'); // Remove trailing space to s = s.replace(new RegExp('\\s+> to s = s.replace(new RegExp('<(img|br|hr)([^>]*)><\\/(img|br|hr)>', 'gi'), '<$1$2 />'); // Weird MSIE bug,


breaks runtime? if (tinyMCE.isIE) s = s.replace(new RegExp('


<\\/p>', 'gi'), "
"); // Weird tags will make IE error #bug: 1538495 if (tinyMCE.isIE) s = s.replace(//g, ''); // Convert relative anchors to absolute URLs ex: #something to file.htm#something // Removed: Since local document anchors should never be forced absolute example edit.php?id=something //if (tinyMCE.getParam('convert_urls')) // s = s.replace(new RegExp('(href=\"{0,1})(\\s*#)', 'gi'), '$1' + tinyMCE.settings.document_base_url + "#"); return s; }, /** * Parses the specified HTML style data. This will parse for example * "border-left: 1px; background-color: red" into an key/value array. * * @param {string} str Style data to parse. * @return Name/Value array of style items. * @type Array */ parseStyle : function(str) { var ar = [], st, i, re, pa; if (str == null) return ar; st = str.split(';'); tinyMCE.clearArray(ar); for (i=0; i 1) val = "url('" + eval(tinyMCE.getParam('urlconverter_callback') + "(m[1], null, true);") + "')"; } // Force HEX colors if (tinyMCE.getParam("force_hex_style_colors")) val = tinyMCE.convertRGBToHex(val, true); val = val.replace(/\"/g, '\''); if (val != "url('')") str += key.toLowerCase() + ": " + val + "; "; } } if (new RegExp('; $').test(str)) str = str.substring(0, str.length - 2); return str; }, /** * Returns a hexadecimal version of the specified rgb(1,2,3) string. * * @param {string} s RGB string to parse, if this doesn't isn't a rgb(n,n,n) it will passthrough the string. * @param {boolean} k Keep before/after contents. If enabled contents before after the rgb(n,n,n) will be intact. * @return Hexadecimal version of the specified rgb(1,2,3) string. * @type string */ convertRGBToHex : function(s, k) { var re, rgb; if (s.toLowerCase().indexOf('rgb') != -1) { re = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi"); rgb = s.replace(re, "$1,$2,$3,$4,$5").split(','); if (rgb.length == 5) { r = parseInt(rgb[1]).toString(16); g = parseInt(rgb[2]).toString(16); b = parseInt(rgb[3]).toString(16); r = r.length == 1 ? '0' + r : r; g = g.length == 1 ? '0' + g : g; b = b.length == 1 ? '0' + b : b; s = "#" + r + g + b; if (k) s = rgb[0] + s + rgb[4]; } } return s; }, /** * Returns a rgb(n,n,n) string from a hexadecimal value. * * @param {string} s Hexadecimal string to parse. * @return rgb(n,n,n) string from a hexadecimal value. * @type string */ convertHexToRGB : function(s) { if (s.indexOf('#') != -1) { s = s.replace(new RegExp('[^0-9A-F]', 'gi'), ''); return "rgb(" + parseInt(s.substring(0, 2), 16) + "," + parseInt(s.substring(2, 4), 16) + "," + parseInt(s.substring(4, 6), 16) + ")"; } return s; }, /** * Converts span elements to font elements in the specified document instance. * Todo: Move this function into a XHTML plugin or simmilar. * * @param {DOMDocument} doc Document instance to convert spans in. */ convertSpansToFonts : function(doc) { var s, i, size, fSize, x, fFace, fColor, sizes = tinyMCE.getParam('font_size_style_values').replace(/\s+/, '').split(','); s = tinyMCE.selectElements(doc, 'span,font'); for (i=0; i 0) { tinyMCE.setAttrib(s[i], 'size', fSize); s[i].style.fontSize = ''; } fFace = s[i].style.fontFamily; if (fFace != null && fFace !== '') { tinyMCE.setAttrib(s[i], 'face', fFace); s[i].style.fontFamily = ''; } fColor = s[i].style.color; if (fColor != null && fColor !== '') { tinyMCE.setAttrib(s[i], 'color', tinyMCE.convertRGBToHex(fColor)); s[i].style.color = ''; } } }, /** * Convers fonts to spans in the specified document. * Todo: Move this function into a XHTML plugin or simmilar. * * @param {DOMDocument} doc Document instance to convert fonts in. */ convertFontsToSpans : function(doc) { var fsClasses, s, i, fSize, fFace, fColor, sizes = tinyMCE.getParam('font_size_style_values').replace(/\s+/, '').split(','); fsClasses = tinyMCE.getParam('font_size_classes'); if (fsClasses !== '') fsClasses = fsClasses.replace(/\s+/, '').split(','); else fsClasses = null; s = tinyMCE.selectElements(doc, 'span,font'); for (i=0; i 0 && fSize < 8) { if (fsClasses != null) tinyMCE.setAttrib(s[i], 'class', fsClasses[fSize-1]); else s[i].style.fontSize = sizes[fSize-1]; } s[i].removeAttribute('size'); } if (fFace !== '') { s[i].style.fontFamily = fFace; s[i].removeAttribute('face'); } if (fColor !== '') { s[i].style.color = fColor; s[i].removeAttribute('color'); } } }, /** * Moves the contents of a anchor outside and after the anchor. Only if the anchor doesn't * have a href. * * @param {DOMDocument} doc DOM document instance to fix anchors in. */ cleanupAnchors : function(doc) { var i, cn, x, an = doc.getElementsByTagName("a"); // Loops backwards due to bug #1467987 for (i=an.length-1; i>=0; i--) { if (tinyMCE.getAttrib(an[i], "name") !== '' && tinyMCE.getAttrib(an[i], "href") == '') { cn = an[i].childNodes; for (x=cn.length-1; x>=0; x--) tinyMCE.insertAfter(cn[x], an[i]); } } }, /** * Returns the HTML contents of the specified editor instance id. * * @param {string} editor_id Editor instance id to retrive HTML code from. * @return HTML contents of editor id or null if it wasn't found. * @type string */ getContent : function(editor_id) { if (typeof(editor_id) != "undefined") tinyMCE.getInstanceById(editor_id).select(); if (tinyMCE.selectedInstance) return tinyMCE.selectedInstance.getHTML(); return null; }, /** * Fixes invalid ul/ol elements so the document is more XHTML valid. * * @param {DOMDocument} d HTML DOM document to fix list elements in. * @private */ _fixListElements : function(d) { var nl, x, a = ['ol', 'ul'], i, n, p, r = new RegExp('^(OL|UL)$'), np; for (x=0; x]*>/gi, ''); h = h.replace(new RegExp(' (rowspan="1"|colspan="1")', 'g'), ''); h = h.replace(/


<\/p>/g, '
'); h = h.replace(/

( | )<\/p>


( | )<\/p>/g, '


'); h = h.replace(/\s*
\s*<\/td>/g, '' + nb + ''); h = h.replace(/

\s*
\s*<\/p>/g, '

' + nb + '

'); h = h.replace(/
$/, ''); // Remove last BR for Gecko h = h.replace(/
<\/p>/g, '

'); // Remove last BR in P tags for Gecko h = h.replace(/

\s*( | )\s*
\s*( | )\s*<\/p>/g, '

' + nb + '

'); h = h.replace(/

\s*( | )\s*
\s*<\/p>/g, '

' + nb + '

'); h = h.replace(/

\s*
\s* \s*<\/p>/g, '

' + nb + '

'); h = h.replace(new RegExp('(.*?)<\\/a>', 'g'), '$1'); h = h.replace(/]*)>\s*<\/p>/g, '' + nb + '

'); // Clean body if (/^\s*(
|

 <\/p>|

 <\/p>|

<\/p>)\s*$/.test(h)) h = ''; // If preformatted if (s.preformatted) { h = h.replace(/^

/, '');
			h = h.replace(/<\/pre>$/, '');
			h = '
' + h + '
'; } // Gecko specific processing if (tinyMCE.isGecko) { // Makes no sence but FF generates it!! h = h.replace(/
\s*<\/li>/g, ''); h = h.replace(/ \s*<\/(dd|dt)>/g, ''); h = h.replace(//g, ''); h = h.replace(/]*)>\s*
\s*<\/td>/g, '' + nb + ''); } if (s.force_br_newlines) h = h.replace(/

( | )<\/p>/g, '
'); // Call custom cleanup code h = tinyMCE._customCleanup(inst, on_save ? "get_from_editor" : "insert_to_editor", h); // Remove internal classes if (on_save) { h = h.replace(new RegExp(' ?(mceItem[a-zA-Z0-9]*|' + s.visual_table_class + ')', 'g'), ''); h = h.replace(new RegExp(' ?class=""', 'g'), ''); } if (s.remove_linebreaks && !c.settings.indent) h = h.replace(/\n|\r/g, ' '); if (d) t4 = new Date().getTime(); if (on_save && c.settings.indent) h = c.formatHTML(h); // If encoding (not recommended option) if (on_submit && (s.encoding == "xml" || s.encoding == "html")) h = c.xmlEncode(h); if (d) t5 = new Date().getTime(); if (c.settings.debug) tinyMCE.debug("Cleanup in ms: Pre=" + (t2-t1) + ", Serialize: " + (t3-t2) + ", Post: " + (t4-t3) + ", Format: " + (t5-t4) + ", Sum: " + (t5-t1) + "."); return h; } }); /**#@-*/ /** * TinyMCE_Cleanup class. * * @constructor */ function TinyMCE_Cleanup() { this.isIE = (navigator.appName == "Microsoft Internet Explorer"); this.rules = tinyMCE.clearArray([]); // Default config this.settings = { indent_elements : 'head,table,tbody,thead,tfoot,form,tr,ul,ol,blockquote,object', newline_before_elements : 'h1,h2,h3,h4,h5,h6,pre,address,div,ul,ol,li,meta,option,area,title,link,base,script,td', newline_after_elements : 'br,hr,p,pre,address,div,ul,ol,meta,option,area,link,base,script', newline_before_after_elements : 'html,head,body,table,thead,tbody,tfoot,tr,form,ul,ol,blockquote,p,object,param,hr,div', indent_char : '\t', indent_levels : 1, entity_encoding : 'raw', valid_elements : '*[*]', entities : '', url_converter : '', invalid_elements : '', verify_html : false }; this.vElements = tinyMCE.clearArray([]); this.vElementsRe = ''; this.closeElementsRe = /^(IMG|BR|HR|LINK|META|BASE|INPUT|AREA)$/; this.codeElementsRe = /^(SCRIPT|STYLE)$/; this.serializationId = 0; this.mceAttribs = { href : 'mce_href', src : 'mce_src', type : 'mce_type' }; } /**#@+ * @member TinyMCE_Cleanup */ TinyMCE_Cleanup.prototype = { /**#@+ * @method */ /** * Initializes the cleanup engine with the specified config. * * @param {Array} s Name/Value array with config settings. */ init : function(s) { var n, a, i, ir, or, st; for (n in s) this.settings[n] = s[n]; // Setup code formating s = this.settings; // Setup regexps this.inRe = this._arrayToRe(s.indent_elements.split(','), '', '^<(', ')[^>]*'); this.ouRe = this._arrayToRe(s.indent_elements.split(','), '', '^<\\/(', ')[^>]*'); this.nlBeforeRe = this._arrayToRe(s.newline_before_elements.split(','), 'gi', '<(', ')([^>]*)>'); this.nlAfterRe = this._arrayToRe(s.newline_after_elements.split(','), 'gi', '<(', ')([^>]*)>'); this.nlBeforeAfterRe = this._arrayToRe(s.newline_before_after_elements.split(','), 'gi', '<(\\/?)(', ')([^>]*)>'); this.serializedNodes = []; this.serializationId = 0; if (s.invalid_elements !== '') this.iveRe = this._arrayToRe(s.invalid_elements.toUpperCase().split(','), 'g', '^(', ')$'); else this.iveRe = null; // Setup separator st = ''; for (i=0; i&"]', 'g'); }, /** * Adds a cleanup rule string, for example: a[!href|!name|title=title|class=class1?class2?class3]. * These rules are then used when serializing the DOM tree as a HTML string, it gives the possibility * to control the valid elements and attributes and force attribute values or default them. * * @param {string} s Rule string to parse and add to the cleanup rules array. */ addRuleStr : function(s) { var r = this.parseRuleStr(s), n; for (n in r) { if (r[n]) this.rules[n] = r[n]; } this.vElements = tinyMCE.clearArray([]); for (n in this.rules) { if (this.rules[n]) this.vElements[this.vElements.length] = this.rules[n].tag; } this.vElementsRe = this._arrayToRe(this.vElements, ''); }, /** * Returns true/false if the element name if valid or not against the cleanup rules. * * @param {string} n Node name to validate. * @return {bool} True/false if the name is valid or not. */ isValid : function(n) { if (!this.rulesDone) this._setupRules(); // Will initialize cleanup rules // Empty is true since it removes formatting if (!n) return true; // Clean the name up a bit n = n.replace(/[^a-z0-9]+/gi, '').toUpperCase(); return !tinyMCE.getParam('cleanup') || this.vElementsRe.test(n); }, /** * * format: h1/h2/h3/h4/h5/h6[%inline_trans_no_a],table[thead|tbody|tfoot|tr|td],body[%btrans]=>p */ addChildRemoveRuleStr : function(s) { var x, y, p, i, t, tn, ta, cl, r; if (!s) return; ta = s.split(','); for (x=0; x 1) this.childRules[tn[y]].wrapTag = p[2]; } } }, /** * Parses a cleanup rule string, for example: a[!href|name|title=title|class=class1?class2?class3]. * These rules are then used when serializing the DOM tree as a HTML string, it gives the possibility * to control the valid elements and attributes and force attribute values or default them. * * @param {string} s Rule string to parse as a name/value rule array. * @return Parsed name/value rule array. * @type Array */ parseRuleStr : function(s) { var ta, p, r, a, i, x, px, t, tn, y, av, or = tinyMCE.clearArray([]), dv; if (s == null || s.length == 0) return or; ta = s.split(','); for (x=0; x 1) { r.vAttribsRe = '^('; a = this.split(/\|/, p[1]); for (i=0; i 0) { if (av[0].charAt(0) == ':') { if (!r.forceAttribs) r.forceAttribs = tinyMCE.clearArray([]); r.forceAttribs[t.toLowerCase()] = av[0].substring(1); } else if (av[0].charAt(0) == '=') { if (!r.defaultAttribs) r.defaultAttribs = tinyMCE.clearArray([]); dv = av[0].substring(1); r.defaultAttribs[t.toLowerCase()] = dv == '' ? "mce_empty" : dv; } else if (av[0].charAt(0) == '<') { if (!r.validAttribValues) r.validAttribValues = tinyMCE.clearArray([]); r.validAttribValues[t.toLowerCase()] = this._arrayToRe(this.split('?', av[0].substring(1)), 'i'); } } r.vAttribsRe += '' + t.toLowerCase() + (i != a.length - 1 ? '|' : ''); a[i] = t.toLowerCase(); } if (r.reqAttribsRe) r.reqAttribsRe = new RegExp(r.reqAttribsRe + ')=\"', 'g'); r.vAttribsRe += ')$'; r.vAttribsRe = this._wildcardToRe(r.vAttribsRe); r.vAttribsReIsWild = new RegExp('\\*|\\?|\\+', 'g').test(r.vAttribsRe); r.vAttribsRe = new RegExp(r.vAttribsRe); r.vAttribs = a.reverse(); //tinyMCE.debug(r.tag, r.oTagName, r.vAttribsRe, r.vAttribsReWC); } else { r.vAttribsRe = ''; r.vAttribs = tinyMCE.clearArray([]); r.vAttribsReIsWild = false; } or[r.tag] = r; } } return or; }, /** * Serializes the specified node as a HTML string. This uses the XML parser and serializer * to generate a XHTML string. * * @param {HTMLNode} n Node to serialize as a XHTML string. * @return Serialized XHTML string based on specified node. * @type string */ serializeNodeAsXML : function(n) { var s, b; if (!this.xmlDoc) { if (this.isIE) { try {this.xmlDoc = new ActiveXObject('MSXML2.DOMDocument');} catch (e) {} if (!this.xmlDoc) try {this.xmlDoc = new ActiveXObject('Microsoft.XmlDom');} catch (e) {} } else this.xmlDoc = document.implementation.createDocument('', '', null); if (!this.xmlDoc) alert("Error XML Parser could not be found."); } if (this.xmlDoc.firstChild) this.xmlDoc.removeChild(this.xmlDoc.firstChild); b = this.xmlDoc.createElement("html"); b = this.xmlDoc.appendChild(b); this._convertToXML(n, b); if (this.isIE) return this.xmlDoc.xml; else return new XMLSerializer().serializeToString(this.xmlDoc); }, /** * Converts and adds the specified HTML DOM node to a XML DOM node. * * @param {HTMLNode} n HTML Node to add as a XML node. * @param {XMLNode} xn XML Node to add the HTML node to. * @private */ _convertToXML : function(n, xn) { var xd, el, i, l, cn, at, no, hc = false; if (tinyMCE.isRealIE && this._isDuplicate(n)) return; xd = this.xmlDoc; switch (n.nodeType) { case 1: // Element hc = n.hasChildNodes(); el = xd.createElement(n.nodeName.toLowerCase()); at = n.attributes; for (i=at.length-1; i>-1; i--) { no = at[i]; if (no.specified && no.nodeValue) el.setAttribute(no.nodeName.toLowerCase(), no.nodeValue); } if (!hc && !this.closeElementsRe.test(n.nodeName)) el.appendChild(xd.createTextNode("")); xn = xn.appendChild(el); break; case 3: // Text xn.appendChild(xd.createTextNode(n.nodeValue)); return; case 8: // Comment xn.appendChild(xd.createComment(n.nodeValue)); return; } if (hc) { cn = n.childNodes; for (i=0, l=cn.length; i if (n.nodeName.indexOf('/') != -1) break; // MSIE has it's NS in a separate attrib if (n.scopeName && n.scopeName != 'HTML') nn = n.scopeName.toUpperCase() + ':' + nn.toUpperCase(); } else if (tinyMCE.isOpera && nn.indexOf(':') > 0) nn = nn.toUpperCase(); // Convert fonts to spans if (this.settings.convert_fonts_to_spans) { // On get content FONT -> SPAN if (this.settings.on_save && nn == 'FONT') nn = 'SPAN'; // On insert content SPAN -> FONT if (!this.settings.on_save && nn == 'SPAN') nn = 'FONT'; } if (this.vElementsRe.test(nn) && (!this.iveRe || !this.iveRe.test(nn)) && !inn) { va = true; r = this.rules[nn]; if (!r) { at = this.rules; for (no in at) { if (at[no] && at[no].validRe.test(nn)) { r = at[no]; break; } } } en = r.isWild ? nn.toLowerCase() : r.oTagName; f = r.fill; if (r.removeEmpty && !hc) return ""; t = '<' + en; if (r.vAttribsReIsWild) { // Serialize wildcard attributes at = n.attributes; for (i=at.length-1; i>-1; i--) { no = at[i]; if (no.specified && r.vAttribsRe.test(no.nodeName)) t += this._serializeAttribute(n, r, no.nodeName); } } else { // Serialize specific attributes for (i=r.vAttribs.length-1; i>-1; i--) t += this._serializeAttribute(n, r, r.vAttribs[i]); } // Serialize mce_ atts if (!this.settings.on_save) { at = this.mceAttribs; for (no in at) { if (at[no]) t += this._serializeAttribute(n, r, at[no]); } } // Check for required attribs if (r.reqAttribsRe && !t.match(r.reqAttribsRe)) t = null; // Close these if (t != null && this.closeElementsRe.test(nn)) return t + ' />'; if (t != null) h += t + '>'; if (this.isIE && this.codeElementsRe.test(nn)) h += n.innerHTML; } break; case 3: // Text if (st) break; if (n.parentNode && this.codeElementsRe.test(n.parentNode.nodeName)) return this.isIE ? '' : n.nodeValue; return this.xmlEncode(n.nodeValue); case 8: // Comment if (st) break; return ""; } if (hc) { cn = n.childNodes; for (i=0, l=cn.length; i'; return h; }, /** * Serializes the specified attribute as a XHTML string chunk. * * @param {HTMLNode} n HTML node to get attribute from. * @param {TinyMCE_CleanupRule} r Cleanup rule to use in serialization. * @param {string} an Attribute name to lookfor and serialize. * @return XHTML chunk containing attribute data if it was found. * @type string * @private */ _serializeAttribute : function(n, r, an) { var av = '', t, os = this.settings.on_save; if (os && (an.indexOf('mce_') == 0 || an.indexOf('_moz') == 0)) return ''; if (os && this.mceAttribs[an]) av = this._getAttrib(n, this.mceAttribs[an]); if (av.length == 0) av = this._getAttrib(n, an); if (av.length == 0 && r.defaultAttribs && (t = r.defaultAttribs[an])) { av = t; if (av == "mce_empty") return " " + an + '=""'; } if (r.forceAttribs && (t = r.forceAttribs[an])) av = t; if (os && av.length != 0 && /^(src|href|longdesc)$/.test(an)) av = this._urlConverter(this, n, av); if (av.length != 0 && r.validAttribValues && r.validAttribValues[an] && !r.validAttribValues[an].test(av)) return ""; if (av.length != 0 && av == "{$uid}") av = "uid_" + (this.idCount++); if (av.length != 0) { if (an.indexOf('on') != 0) av = this.xmlEncode(av, 1); return " " + an + "=" + '"' + av + '"'; } return ""; }, /** * Applies source formatting/indentation on the specified HTML string. * * @param {string} h HTML string to apply formatting to. * @return Formatted HTML string. * @type string */ formatHTML : function(h) { var s = this.settings, p = '', i = 0, li = 0, o = '', l; // Replace BR in pre elements to \n h = h.replace(/]*)>(.*?)<\/pre>/gi, function (a, b, c) { c = c.replace(//gi, '\n'); return '' + c + '

'; }); h = h.replace(/\r/g, ''); // Windows sux, isn't carriage return a thing of the past :) h = '\n' + h; h = h.replace(new RegExp('\\n\\s+', 'gi'), '\n'); // Remove previous formatting h = h.replace(this.nlBeforeRe, '\n<$1$2>'); h = h.replace(this.nlAfterRe, '<$1$2>\n'); h = h.replace(this.nlBeforeAfterRe, '\n<$1$2$3>\n'); h += '\n'; //tinyMCE.debug(h); while ((i = h.indexOf('\n', i + 1)) != -1) { if ((l = h.substring(li + 1, i)).length != 0) { if (this.ouRe.test(l) && p.length >= s.indent_levels) p = p.substring(s.indent_levels); o += p + l + '\n'; if (this.inRe.test(l)) p += this.inStr; } li = i; } //tinyMCE.debug(h); return o; }, /** * XML Encodes the specified string based on configured entity encoding. The entity encoding modes * are raw, numeric and named. Where raw is the fastest and named is default. * * @param {string} s String to convert to XML. * @return Encoded XML string based on configured entity encoding. * @type string */ xmlEncode : function(s) { var cl = this, re = this.xmlEncodeRe; if (!this.entitiesDone) this._setupEntities(); // Will intialize lookup table switch (this.settings.entity_encoding) { case "raw": return tinyMCE.xmlEncode(s); case "named": return s.replace(re, function (c) { var b = cl.entities[c.charCodeAt(0)]; return b ? '&' + b + ';' : c; }); case "numeric": return s.replace(re, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); } return s; }, /** * Splits the specified string and removed empty chunks. * * @param {RegEx} re RegEx to split string by. * @param {string} s String value to split. * @return Array with parts from specified string. * @type string */ split : function(re, s) { var i, l, o = [], c = s.split(re); for (i=0, l=c.length; i