| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1479 @@ |
| 1 |
+/** |
|
| 2 |
+ * $Id$ |
|
| 3 |
+ * |
|
| 4 |
+ * @author Moxiecode |
|
| 5 |
+ * @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved. |
|
| 6 |
+ * |
|
| 7 |
+ * 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. |
|
| 8 |
+ */ |
|
| 9 |
+ |
|
| 10 |
+/**#@+ |
|
| 11 |
+ * @member TinyMCE_Engine |
|
| 12 |
+ * @method |
|
| 13 |
+ */ |
|
| 14 |
+ |
|
| 15 |
+tinyMCE.add(TinyMCE_Engine, {
|
|
| 16 |
+ /** |
|
| 17 |
+ * Makes some preprocessing cleanup routines on the specified HTML string. |
|
| 18 |
+ * This includes forcing some tags to be open so MSIE doesn't fail. Forcing other to close and |
|
| 19 |
+ * padding paragraphs with non breaking spaces. This function is used when the editor gets |
|
| 20 |
+ * initialized with content. |
|
| 21 |
+ * |
|
| 22 |
+ * @param {string} s HTML string to cleanup.
|
|
| 23 |
+ * @return Cleaned HTML string. |
|
| 24 |
+ * @type string |
|
| 25 |
+ */ |
|
| 26 |
+ cleanupHTMLCode : function(s) {
|
|
| 27 |
+ s = s.replace(new RegExp('<p \\/>', 'gi'), '<p> </p>');
|
|
| 28 |
+ s = s.replace(new RegExp('<p>\\s*<\\/p>', 'gi'), '<p> </p>');
|
|
| 29 |
+ |
|
| 30 |
+ // Fix close BR elements |
|
| 31 |
+ s = s.replace(new RegExp('<br>\\s*<\\/br>', 'gi'), '<br />');
|
|
| 32 |
+ |
|
| 33 |
+ // Open closed tags like <b/> to <b></b> |
|
| 34 |
+ 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></$1$2>');
|
|
| 35 |
+ |
|
| 36 |
+ // Remove trailing space <b > to <b> |
|
| 37 |
+ s = s.replace(new RegExp('\\s+></', 'gi'), '></');
|
|
| 38 |
+ |
|
| 39 |
+ // Close tags <img></img> to <img/> |
|
| 40 |
+ s = s.replace(new RegExp('<(img|br|hr)([^>]*)><\\/(img|br|hr)>', 'gi'), '<$1$2 />');
|
|
| 41 |
+ |
|
| 42 |
+ // Weird MSIE bug, <p><hr /></p> breaks runtime? |
|
| 43 |
+ if (tinyMCE.isIE) |
|
| 44 |
+ s = s.replace(new RegExp('<p><hr \\/><\\/p>', 'gi'), "<hr>");
|
|
| 45 |
+ |
|
| 46 |
+ // Weird tags will make IE error #bug: 1538495 |
|
| 47 |
+ if (tinyMCE.isIE) |
|
| 48 |
+ s = s.replace(/<!(\s*)\/>/g, ''); |
|
| 49 |
+ |
|
| 50 |
+ // Convert relative anchors to absolute URLs ex: #something to file.htm#something |
|
| 51 |
+ // Removed: Since local document anchors should never be forced absolute example edit.php?id=something |
|
| 52 |
+ //if (tinyMCE.getParam('convert_urls'))
|
|
| 53 |
+ // s = s.replace(new RegExp('(href=\"{0,1})(\\s*#)', 'gi'), '$1' + tinyMCE.settings.document_base_url + "#");
|
|
| 54 |
+ |
|
| 55 |
+ return s; |
|
| 56 |
+ }, |
|
| 57 |
+ |
|
| 58 |
+ /** |
|
| 59 |
+ * Parses the specified HTML style data. This will parse for example |
|
| 60 |
+ * "border-left: 1px; background-color: red" into an key/value array. |
|
| 61 |
+ * |
|
| 62 |
+ * @param {string} str Style data to parse.
|
|
| 63 |
+ * @return Name/Value array of style items. |
|
| 64 |
+ * @type Array |
|
| 65 |
+ */ |
|
| 66 |
+ parseStyle : function(str) {
|
|
| 67 |
+ var ar = [], st, i, re, pa; |
|
| 68 |
+ |
|
| 69 |
+ if (str == null) |
|
| 70 |
+ return ar; |
|
| 71 |
+ |
|
| 72 |
+ st = str.split(';');
|
|
| 73 |
+ |
|
| 74 |
+ tinyMCE.clearArray(ar); |
|
| 75 |
+ |
|
| 76 |
+ for (i=0; i<st.length; i++) {
|
|
| 77 |
+ if (st[i] == '') |
|
| 78 |
+ continue; |
|
| 79 |
+ |
|
| 80 |
+ re = new RegExp('^\\s*([^:]*):\\s*(.*)\\s*$');
|
|
| 81 |
+ pa = st[i].replace(re, '$1||$2').split('||');
|
|
| 82 |
+ //tinyMCE.debug(str, pa[0] + "=" + pa[1], st[i].replace(re, '$1||$2')); |
|
| 83 |
+ if (pa.length == 2) |
|
| 84 |
+ ar[pa[0].toLowerCase()] = pa[1]; |
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ return ar; |
|
| 88 |
+ }, |
|
| 89 |
+ |
|
| 90 |
+ /** |
|
| 91 |
+ * Compresses larger styles into a smaller. Since MSIE automaticly converts |
|
| 92 |
+ * border: 1px solid red to border-left: 1px solid red, border-righ: 1px solid red and so forth.' |
|
| 93 |
+ * This will bundle them together again if the information is the same in each item. |
|
| 94 |
+ * |
|
| 95 |
+ * @param {Array} ar Style name/value array with items.
|
|
| 96 |
+ * @param {string} pr Style item prefix to bundle for example border.
|
|
| 97 |
+ * @param {string} sf Style item suffix to bunlde for example -width or -width.
|
|
| 98 |
+ * @param {string} res Result name, for example border-width.
|
|
| 99 |
+ */ |
|
| 100 |
+ compressStyle : function(ar, pr, sf, res) {
|
|
| 101 |
+ var box = [], i, a; |
|
| 102 |
+ |
|
| 103 |
+ box[0] = ar[pr + '-top' + sf]; |
|
| 104 |
+ box[1] = ar[pr + '-left' + sf]; |
|
| 105 |
+ box[2] = ar[pr + '-right' + sf]; |
|
| 106 |
+ box[3] = ar[pr + '-bottom' + sf]; |
|
| 107 |
+ |
|
| 108 |
+ for (i=0; i<box.length; i++) {
|
|
| 109 |
+ if (box[i] == null) |
|
| 110 |
+ return; |
|
| 111 |
+ |
|
| 112 |
+ if (i && box[i] != box[i-1]) |
|
| 113 |
+ return; |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ // They are all the same |
|
| 117 |
+ ar[res] = box[0]; |
|
| 118 |
+ ar[pr + '-top' + sf] = null; |
|
| 119 |
+ ar[pr + '-left' + sf] = null; |
|
| 120 |
+ ar[pr + '-right' + sf] = null; |
|
| 121 |
+ ar[pr + '-bottom' + sf] = null; |
|
| 122 |
+ }, |
|
| 123 |
+ |
|
| 124 |
+ /** |
|
| 125 |
+ * Serializes the specified style item name/value array into a HTML string. This function |
|
| 126 |
+ * will force HEX colors in Firefox and convert the URL items of a style correctly. |
|
| 127 |
+ * |
|
| 128 |
+ * @param {Array} ar Name/Value array of items to serialize.
|
|
| 129 |
+ * @return Serialized HTML string containing the items. |
|
| 130 |
+ * @type string |
|
| 131 |
+ */ |
|
| 132 |
+ serializeStyle : function(ar) {
|
|
| 133 |
+ var str = "", key, val, m; |
|
| 134 |
+ |
|
| 135 |
+ // Compress box |
|
| 136 |
+ tinyMCE.compressStyle(ar, "border", "", "border"); |
|
| 137 |
+ tinyMCE.compressStyle(ar, "border", "-width", "border-width"); |
|
| 138 |
+ tinyMCE.compressStyle(ar, "border", "-color", "border-color"); |
|
| 139 |
+ tinyMCE.compressStyle(ar, "border", "-style", "border-style"); |
|
| 140 |
+ tinyMCE.compressStyle(ar, "padding", "", "padding"); |
|
| 141 |
+ tinyMCE.compressStyle(ar, "margin", "", "margin"); |
|
| 142 |
+ |
|
| 143 |
+ for (key in ar) {
|
|
| 144 |
+ val = ar[key]; |
|
| 145 |
+ |
|
| 146 |
+ if (typeof(val) == 'function') |
|
| 147 |
+ continue; |
|
| 148 |
+ |
|
| 149 |
+ if (key.indexOf('mso-') == 0)
|
|
| 150 |
+ continue; |
|
| 151 |
+ |
|
| 152 |
+ if (val != null && val !== '') {
|
|
| 153 |
+ val = '' + val; // Force string |
|
| 154 |
+ |
|
| 155 |
+ // Fix style URL |
|
| 156 |
+ val = val.replace(new RegExp("url\\(\\'?([^\\']*)\\'?\\)", 'gi'), "url('$1')");
|
|
| 157 |
+ |
|
| 158 |
+ // Convert URL |
|
| 159 |
+ if (val.indexOf('url(') != -1 && tinyMCE.getParam('convert_urls')) {
|
|
| 160 |
+ m = new RegExp("url\\('(.*?)'\\)").exec(val);
|
|
| 161 |
+ |
|
| 162 |
+ if (m.length > 1) |
|
| 163 |
+ val = "url('" + eval(tinyMCE.getParam('urlconverter_callback') + "(m[1], null, true);") + "')";
|
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ // Force HEX colors |
|
| 167 |
+ if (tinyMCE.getParam("force_hex_style_colors"))
|
|
| 168 |
+ val = tinyMCE.convertRGBToHex(val, true); |
|
| 169 |
+ |
|
| 170 |
+ val = val.replace(/\"/g, '\''); |
|
| 171 |
+ |
|
| 172 |
+ if (val != "url('')")
|
|
| 173 |
+ str += key.toLowerCase() + ": " + val + "; "; |
|
| 174 |
+ } |
|
| 175 |
+ } |
|
| 176 |
+ |
|
| 177 |
+ if (new RegExp('; $').test(str))
|
|
| 178 |
+ str = str.substring(0, str.length - 2); |
|
| 179 |
+ |
|
| 180 |
+ return str; |
|
| 181 |
+ }, |
|
| 182 |
+ |
|
| 183 |
+ /** |
|
| 184 |
+ * Returns a hexadecimal version of the specified rgb(1,2,3) string. |
|
| 185 |
+ * |
|
| 186 |
+ * @param {string} s RGB string to parse, if this doesn't isn't a rgb(n,n,n) it will passthrough the string.
|
|
| 187 |
+ * @param {boolean} k Keep before/after contents. If enabled contents before after the rgb(n,n,n) will be intact.
|
|
| 188 |
+ * @return Hexadecimal version of the specified rgb(1,2,3) string. |
|
| 189 |
+ * @type string |
|
| 190 |
+ */ |
|
| 191 |
+ convertRGBToHex : function(s, k) {
|
|
| 192 |
+ var re, rgb; |
|
| 193 |
+ |
|
| 194 |
+ if (s.toLowerCase().indexOf('rgb') != -1) {
|
|
| 195 |
+ re = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
|
|
| 196 |
+ rgb = s.replace(re, "$1,$2,$3,$4,$5").split(',');
|
|
| 197 |
+ |
|
| 198 |
+ if (rgb.length == 5) {
|
|
| 199 |
+ r = parseInt(rgb[1]).toString(16); |
|
| 200 |
+ g = parseInt(rgb[2]).toString(16); |
|
| 201 |
+ b = parseInt(rgb[3]).toString(16); |
|
| 202 |
+ |
|
| 203 |
+ r = r.length == 1 ? '0' + r : r; |
|
| 204 |
+ g = g.length == 1 ? '0' + g : g; |
|
| 205 |
+ b = b.length == 1 ? '0' + b : b; |
|
| 206 |
+ |
|
| 207 |
+ s = "#" + r + g + b; |
|
| 208 |
+ |
|
| 209 |
+ if (k) |
|
| 210 |
+ s = rgb[0] + s + rgb[4]; |
|
| 211 |
+ } |
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ return s; |
|
| 215 |
+ }, |
|
| 216 |
+ |
|
| 217 |
+ /** |
|
| 218 |
+ * Returns a rgb(n,n,n) string from a hexadecimal value. |
|
| 219 |
+ * |
|
| 220 |
+ * @param {string} s Hexadecimal string to parse.
|
|
| 221 |
+ * @return rgb(n,n,n) string from a hexadecimal value. |
|
| 222 |
+ * @type string |
|
| 223 |
+ */ |
|
| 224 |
+ convertHexToRGB : function(s) {
|
|
| 225 |
+ if (s.indexOf('#') != -1) {
|
|
| 226 |
+ s = s.replace(new RegExp('[^0-9A-F]', 'gi'), '');
|
|
| 227 |
+ return "rgb(" + parseInt(s.substring(0, 2), 16) + "," + parseInt(s.substring(2, 4), 16) + "," + parseInt(s.substring(4, 6), 16) + ")";
|
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ return s; |
|
| 231 |
+ }, |
|
| 232 |
+ |
|
| 233 |
+ /** |
|
| 234 |
+ * Converts span elements to font elements in the specified document instance. |
|
| 235 |
+ * Todo: Move this function into a XHTML plugin or simmilar. |
|
| 236 |
+ * |
|
| 237 |
+ * @param {DOMDocument} doc Document instance to convert spans in.
|
|
| 238 |
+ */ |
|
| 239 |
+ convertSpansToFonts : function(doc) {
|
|
| 240 |
+ var s, i, size, fSize, x, fFace, fColor, sizes = tinyMCE.getParam('font_size_style_values').replace(/\s+/, '').split(',');
|
|
| 241 |
+ |
|
| 242 |
+ s = tinyMCE.selectElements(doc, 'span,font'); |
|
| 243 |
+ for (i=0; i<s.length; i++) {
|
|
| 244 |
+ size = tinyMCE.trim(s[i].style.fontSize).toLowerCase(); |
|
| 245 |
+ fSize = 0; |
|
| 246 |
+ |
|
| 247 |
+ for (x=0; x<sizes.length; x++) {
|
|
| 248 |
+ if (sizes[x] == size) {
|
|
| 249 |
+ fSize = x + 1; |
|
| 250 |
+ break; |
|
| 251 |
+ } |
|
| 252 |
+ } |
|
| 253 |
+ |
|
| 254 |
+ if (fSize > 0) {
|
|
| 255 |
+ tinyMCE.setAttrib(s[i], 'size', fSize); |
|
| 256 |
+ s[i].style.fontSize = ''; |
|
| 257 |
+ } |
|
| 258 |
+ |
|
| 259 |
+ fFace = s[i].style.fontFamily; |
|
| 260 |
+ if (fFace != null && fFace !== '') {
|
|
| 261 |
+ tinyMCE.setAttrib(s[i], 'face', fFace); |
|
| 262 |
+ s[i].style.fontFamily = ''; |
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ fColor = s[i].style.color; |
|
| 266 |
+ if (fColor != null && fColor !== '') {
|
|
| 267 |
+ tinyMCE.setAttrib(s[i], 'color', tinyMCE.convertRGBToHex(fColor)); |
|
| 268 |
+ s[i].style.color = ''; |
|
| 269 |
+ } |
|
| 270 |
+ } |
|
| 271 |
+ }, |
|
| 272 |
+ |
|
| 273 |
+ /** |
|
| 274 |
+ * Convers fonts to spans in the specified document. |
|
| 275 |
+ * Todo: Move this function into a XHTML plugin or simmilar. |
|
| 276 |
+ * |
|
| 277 |
+ * @param {DOMDocument} doc Document instance to convert fonts in.
|
|
| 278 |
+ */ |
|
| 279 |
+ convertFontsToSpans : function(doc) {
|
|
| 280 |
+ var fsClasses, s, i, fSize, fFace, fColor, sizes = tinyMCE.getParam('font_size_style_values').replace(/\s+/, '').split(',');
|
|
| 281 |
+ |
|
| 282 |
+ fsClasses = tinyMCE.getParam('font_size_classes');
|
|
| 283 |
+ if (fsClasses !== '') |
|
| 284 |
+ fsClasses = fsClasses.replace(/\s+/, '').split(',');
|
|
| 285 |
+ else |
|
| 286 |
+ fsClasses = null; |
|
| 287 |
+ |
|
| 288 |
+ s = tinyMCE.selectElements(doc, 'span,font'); |
|
| 289 |
+ for (i=0; i<s.length; i++) {
|
|
| 290 |
+ fSize = tinyMCE.getAttrib(s[i], 'size'); |
|
| 291 |
+ fFace = tinyMCE.getAttrib(s[i], 'face'); |
|
| 292 |
+ fColor = tinyMCE.getAttrib(s[i], 'color'); |
|
| 293 |
+ |
|
| 294 |
+ if (fSize !== '') {
|
|
| 295 |
+ fSize = parseInt(fSize); |
|
| 296 |
+ |
|
| 297 |
+ if (fSize > 0 && fSize < 8) {
|
|
| 298 |
+ if (fsClasses != null) |
|
| 299 |
+ tinyMCE.setAttrib(s[i], 'class', fsClasses[fSize-1]); |
|
| 300 |
+ else |
|
| 301 |
+ s[i].style.fontSize = sizes[fSize-1]; |
|
| 302 |
+ } |
|
| 303 |
+ |
|
| 304 |
+ s[i].removeAttribute('size');
|
|
| 305 |
+ } |
|
| 306 |
+ |
|
| 307 |
+ if (fFace !== '') {
|
|
| 308 |
+ s[i].style.fontFamily = fFace; |
|
| 309 |
+ s[i].removeAttribute('face');
|
|
| 310 |
+ } |
|
| 311 |
+ |
|
| 312 |
+ if (fColor !== '') {
|
|
| 313 |
+ s[i].style.color = fColor; |
|
| 314 |
+ s[i].removeAttribute('color');
|
|
| 315 |
+ } |
|
| 316 |
+ } |
|
| 317 |
+ }, |
|
| 318 |
+ |
|
| 319 |
+ /** |
|
| 320 |
+ * Moves the contents of a anchor outside and after the anchor. Only if the anchor doesn't |
|
| 321 |
+ * have a href. |
|
| 322 |
+ * |
|
| 323 |
+ * @param {DOMDocument} doc DOM document instance to fix anchors in.
|
|
| 324 |
+ */ |
|
| 325 |
+ cleanupAnchors : function(doc) {
|
|
| 326 |
+ var i, cn, x, an = doc.getElementsByTagName("a");
|
|
| 327 |
+ |
|
| 328 |
+ // Loops backwards due to bug #1467987 |
|
| 329 |
+ for (i=an.length-1; i>=0; i--) {
|
|
| 330 |
+ if (tinyMCE.getAttrib(an[i], "name") !== '' && tinyMCE.getAttrib(an[i], "href") == '') {
|
|
| 331 |
+ cn = an[i].childNodes; |
|
| 332 |
+ |
|
| 333 |
+ for (x=cn.length-1; x>=0; x--) |
|
| 334 |
+ tinyMCE.insertAfter(cn[x], an[i]); |
|
| 335 |
+ } |
|
| 336 |
+ } |
|
| 337 |
+ }, |
|
| 338 |
+ |
|
| 339 |
+ /** |
|
| 340 |
+ * Returns the HTML contents of the specified editor instance id. |
|
| 341 |
+ * |
|
| 342 |
+ * @param {string} editor_id Editor instance id to retrive HTML code from.
|
|
| 343 |
+ * @return HTML contents of editor id or null if it wasn't found. |
|
| 344 |
+ * @type string |
|
| 345 |
+ */ |
|
| 346 |
+ getContent : function(editor_id) {
|
|
| 347 |
+ if (typeof(editor_id) != "undefined") |
|
| 348 |
+ tinyMCE.getInstanceById(editor_id).select(); |
|
| 349 |
+ |
|
| 350 |
+ if (tinyMCE.selectedInstance) |
|
| 351 |
+ return tinyMCE.selectedInstance.getHTML(); |
|
| 352 |
+ |
|
| 353 |
+ return null; |
|
| 354 |
+ }, |
|
| 355 |
+ |
|
| 356 |
+ /** |
|
| 357 |
+ * Fixes invalid ul/ol elements so the document is more XHTML valid. |
|
| 358 |
+ * |
|
| 359 |
+ * @param {DOMDocument} d HTML DOM document to fix list elements in.
|
|
| 360 |
+ * @private |
|
| 361 |
+ */ |
|
| 362 |
+ _fixListElements : function(d) {
|
|
| 363 |
+ var nl, x, a = ['ol', 'ul'], i, n, p, r = new RegExp('^(OL|UL)$'), np;
|
|
| 364 |
+ |
|
| 365 |
+ for (x=0; x<a.length; x++) {
|
|
| 366 |
+ nl = d.getElementsByTagName(a[x]); |
|
| 367 |
+ |
|
| 368 |
+ for (i=0; i<nl.length; i++) {
|
|
| 369 |
+ n = nl[i]; |
|
| 370 |
+ p = n.parentNode; |
|
| 371 |
+ |
|
| 372 |
+ if (r.test(p.nodeName)) {
|
|
| 373 |
+ np = tinyMCE.prevNode(n, 'LI'); |
|
| 374 |
+ |
|
| 375 |
+ if (!np) {
|
|
| 376 |
+ np = d.createElement('li');
|
|
| 377 |
+ np.innerHTML = ' '; |
|
| 378 |
+ np.appendChild(n); |
|
| 379 |
+ p.insertBefore(np, p.firstChild); |
|
| 380 |
+ } else |
|
| 381 |
+ np.appendChild(n); |
|
| 382 |
+ } |
|
| 383 |
+ } |
|
| 384 |
+ } |
|
| 385 |
+ }, |
|
| 386 |
+ |
|
| 387 |
+ /** |
|
| 388 |
+ * Moves table elements out of block elements to produce more valid XHTML. |
|
| 389 |
+ * |
|
| 390 |
+ * @param {DOMDocument} d HTML DOM document to fix list elements in.
|
|
| 391 |
+ * @private |
|
| 392 |
+ */ |
|
| 393 |
+ _fixTables : function(d) {
|
|
| 394 |
+ var nl, i, n, p, np, x, t; |
|
| 395 |
+ |
|
| 396 |
+ nl = d.getElementsByTagName('table');
|
|
| 397 |
+ for (i=0; i<nl.length; i++) {
|
|
| 398 |
+ n = nl[i]; |
|
| 399 |
+ |
|
| 400 |
+ if ((p = tinyMCE.getParentElement(n, 'p,h1,h2,h3,h4,h5,h6')) != null) {
|
|
| 401 |
+ np = p.cloneNode(false); |
|
| 402 |
+ np.removeAttribute('id');
|
|
| 403 |
+ |
|
| 404 |
+ t = n; |
|
| 405 |
+ |
|
| 406 |
+ while ((n = n.nextSibling)) |
|
| 407 |
+ np.appendChild(n); |
|
| 408 |
+ |
|
| 409 |
+ tinyMCE.insertAfter(np, p); |
|
| 410 |
+ tinyMCE.insertAfter(t, p); |
|
| 411 |
+ } |
|
| 412 |
+ } |
|
| 413 |
+ }, |
|
| 414 |
+ |
|
| 415 |
+ /** |
|
| 416 |
+ * Performces cleanup of the contents of the specified instance. |
|
| 417 |
+ * Todo: Finish documentation, and remove useless parameters. |
|
| 418 |
+ * |
|
| 419 |
+ * @param {TinyMCE_Control} inst Editor instance.
|
|
| 420 |
+ * @param {DOMDocument} doc ...
|
|
| 421 |
+ * @param {Array} config ...
|
|
| 422 |
+ * @param {HTMLElement} elm ...
|
|
| 423 |
+ * @param {boolean} visual ...
|
|
| 424 |
+ * @param {boolean} on_save ...
|
|
| 425 |
+ * @param {boolean} on_submit ...
|
|
| 426 |
+ * @param {boolean} inn inner html.
|
|
| 427 |
+ * @return Cleaned HTML contents of editor instance. |
|
| 428 |
+ * @type string |
|
| 429 |
+ * @private |
|
| 430 |
+ */ |
|
| 431 |
+ _cleanupHTML : function(inst, doc, config, elm, visual, on_save, on_submit, inn) {
|
|
| 432 |
+ var h, d, t1, t2, t3, t4, t5, c, s, nb; |
|
| 433 |
+ |
|
| 434 |
+ if (!tinyMCE.getParam('cleanup'))
|
|
| 435 |
+ return elm.innerHTML; |
|
| 436 |
+ |
|
| 437 |
+ on_save = typeof(on_save) == 'undefined' ? false : on_save; |
|
| 438 |
+ |
|
| 439 |
+ c = inst.cleanup; |
|
| 440 |
+ s = inst.settings; |
|
| 441 |
+ d = c.settings.debug; |
|
| 442 |
+ |
|
| 443 |
+ if (d) |
|
| 444 |
+ t1 = new Date().getTime(); |
|
| 445 |
+ |
|
| 446 |
+ inst._fixRootBlocks(); |
|
| 447 |
+ |
|
| 448 |
+ if (tinyMCE.getParam("convert_fonts_to_spans"))
|
|
| 449 |
+ tinyMCE.convertFontsToSpans(doc); |
|
| 450 |
+ |
|
| 451 |
+ if (tinyMCE.getParam("fix_list_elements"))
|
|
| 452 |
+ tinyMCE._fixListElements(doc); |
|
| 453 |
+ |
|
| 454 |
+ if (tinyMCE.getParam("fix_table_elements"))
|
|
| 455 |
+ tinyMCE._fixTables(doc); |
|
| 456 |
+ |
|
| 457 |
+ // Call custom cleanup code |
|
| 458 |
+ tinyMCE._customCleanup(inst, on_save ? "get_from_editor_dom" : "insert_to_editor_dom", doc.body); |
|
| 459 |
+ |
|
| 460 |
+ if (d) |
|
| 461 |
+ t2 = new Date().getTime(); |
|
| 462 |
+ |
|
| 463 |
+ c.settings.on_save = on_save; |
|
| 464 |
+ |
|
| 465 |
+ c.idCount = 0; |
|
| 466 |
+ c.serializationId++; // Unique ID needed for the content duplication bug |
|
| 467 |
+ c.serializedNodes = []; |
|
| 468 |
+ c.sourceIndex = -1; |
|
| 469 |
+ |
|
| 470 |
+ if (s.cleanup_serializer == "xml") |
|
| 471 |
+ h = c.serializeNodeAsXML(elm, inn); |
|
| 472 |
+ else |
|
| 473 |
+ h = c.serializeNodeAsHTML(elm, inn); |
|
| 474 |
+ |
|
| 475 |
+ if (d) |
|
| 476 |
+ t3 = new Date().getTime(); |
|
| 477 |
+ |
|
| 478 |
+ // Post processing |
|
| 479 |
+ nb = tinyMCE.getParam('entity_encoding') == 'numeric' ? ' ' : ' ';
|
|
| 480 |
+ h = h.replace(/<\/?(body|head|html)[^>]*>/gi, ''); |
|
| 481 |
+ h = h.replace(new RegExp(' (rowspan="1"|colspan="1")', 'g'), '');
|
|
| 482 |
+ h = h.replace(/<p><hr \/><\/p>/g, '<hr />'); |
|
| 483 |
+ h = h.replace(/<p>( | )<\/p><hr \/><p>( | )<\/p>/g, '<hr />'); |
|
| 484 |
+ h = h.replace(/<td>\s*<br \/>\s*<\/td>/g, '<td>' + nb + '</td>'); |
|
| 485 |
+ h = h.replace(/<p>\s*<br \/>\s*<\/p>/g, '<p>' + nb + '</p>'); |
|
| 486 |
+ h = h.replace(/<br \/>$/, ''); // Remove last BR for Gecko |
|
| 487 |
+ h = h.replace(/<br \/><\/p>/g, '</p>'); // Remove last BR in P tags for Gecko |
|
| 488 |
+ h = h.replace(/<p>\s*( | )\s*<br \/>\s*( | )\s*<\/p>/g, '<p>' + nb + '</p>'); |
|
| 489 |
+ h = h.replace(/<p>\s*( | )\s*<br \/>\s*<\/p>/g, '<p>' + nb + '</p>'); |
|
| 490 |
+ h = h.replace(/<p>\s*<br \/>\s* \s*<\/p>/g, '<p>' + nb + '</p>'); |
|
| 491 |
+ h = h.replace(new RegExp('<a>(.*?)<\\/a>', 'g'), '$1');
|
|
| 492 |
+ h = h.replace(/<p([^>]*)>\s*<\/p>/g, '<p$1>' + nb + '</p>'); |
|
| 493 |
+ |
|
| 494 |
+ // Clean body |
|
| 495 |
+ if (/^\s*(<br \/>|<p> <\/p>|<p> <\/p>|<p><\/p>)\s*$/.test(h)) |
|
| 496 |
+ h = ''; |
|
| 497 |
+ |
|
| 498 |
+ // If preformatted |
|
| 499 |
+ if (s.preformatted) {
|
|
| 500 |
+ h = h.replace(/^<pre>/, ''); |
|
| 501 |
+ h = h.replace(/<\/pre>$/, ''); |
|
| 502 |
+ h = '<pre>' + h + '</pre>'; |
|
| 503 |
+ } |
|
| 504 |
+ |
|
| 505 |
+ // Gecko specific processing |
|
| 506 |
+ if (tinyMCE.isGecko) {
|
|
| 507 |
+ // Makes no sence but FF generates it!! |
|
| 508 |
+ h = h.replace(/<br \/>\s*<\/li>/g, '</li>'); |
|
| 509 |
+ h = h.replace(/ \s*<\/(dd|dt)>/g, '</$1>'); |
|
| 510 |
+ h = h.replace(/<o:p _moz-userdefined="" \/>/g, ''); |
|
| 511 |
+ h = h.replace(/<td([^>]*)>\s*<br \/>\s*<\/td>/g, '<td$1>' + nb + '</td>'); |
|
| 512 |
+ } |
|
| 513 |
+ |
|
| 514 |
+ if (s.force_br_newlines) |
|
| 515 |
+ h = h.replace(/<p>( | )<\/p>/g, '<br />'); |
|
| 516 |
+ |
|
| 517 |
+ // Call custom cleanup code |
|
| 518 |
+ h = tinyMCE._customCleanup(inst, on_save ? "get_from_editor" : "insert_to_editor", h); |
|
| 519 |
+ |
|
| 520 |
+ // Remove internal classes |
|
| 521 |
+ if (on_save) {
|
|
| 522 |
+ h = h.replace(new RegExp(' ?(mceItem[a-zA-Z0-9]*|' + s.visual_table_class + ')', 'g'), '');
|
|
| 523 |
+ h = h.replace(new RegExp(' ?class=""', 'g'), '');
|
|
| 524 |
+ } |
|
| 525 |
+ |
|
| 526 |
+ if (s.remove_linebreaks && !c.settings.indent) |
|
| 527 |
+ h = h.replace(/\n|\r/g, ' '); |
|
| 528 |
+ |
|
| 529 |
+ if (d) |
|
| 530 |
+ t4 = new Date().getTime(); |
|
| 531 |
+ |
|
| 532 |
+ if (on_save && c.settings.indent) |
|
| 533 |
+ h = c.formatHTML(h); |
|
| 534 |
+ |
|
| 535 |
+ // If encoding (not recommended option) |
|
| 536 |
+ if (on_submit && (s.encoding == "xml" || s.encoding == "html")) |
|
| 537 |
+ h = c.xmlEncode(h); |
|
| 538 |
+ |
|
| 539 |
+ if (d) |
|
| 540 |
+ t5 = new Date().getTime(); |
|
| 541 |
+ |
|
| 542 |
+ if (c.settings.debug) |
|
| 543 |
+ tinyMCE.debug("Cleanup in ms: Pre=" + (t2-t1) + ", Serialize: " + (t3-t2) + ", Post: " + (t4-t3) + ", Format: " + (t5-t4) + ", Sum: " + (t5-t1) + ".");
|
|
| 544 |
+ |
|
| 545 |
+ return h; |
|
| 546 |
+ } |
|
| 547 |
+}); |
|
| 548 |
+ |
|
| 549 |
+/**#@-*/ |
|
| 550 |
+ |
|
| 551 |
+/** |
|
| 552 |
+ * TinyMCE_Cleanup class. |
|
| 553 |
+ * |
|
| 554 |
+ * @constructor |
|
| 555 |
+ */ |
|
| 556 |
+function TinyMCE_Cleanup() {
|
|
| 557 |
+ this.isIE = (navigator.appName == "Microsoft Internet Explorer"); |
|
| 558 |
+ this.rules = tinyMCE.clearArray([]); |
|
| 559 |
+ |
|
| 560 |
+ // Default config |
|
| 561 |
+ this.settings = {
|
|
| 562 |
+ indent_elements : 'head,table,tbody,thead,tfoot,form,tr,ul,ol,blockquote,object', |
|
| 563 |
+ newline_before_elements : 'h1,h2,h3,h4,h5,h6,pre,address,div,ul,ol,li,meta,option,area,title,link,base,script,td', |
|
| 564 |
+ newline_after_elements : 'br,hr,p,pre,address,div,ul,ol,meta,option,area,link,base,script', |
|
| 565 |
+ newline_before_after_elements : 'html,head,body,table,thead,tbody,tfoot,tr,form,ul,ol,blockquote,p,object,param,hr,div', |
|
| 566 |
+ indent_char : '\t', |
|
| 567 |
+ indent_levels : 1, |
|
| 568 |
+ entity_encoding : 'raw', |
|
| 569 |
+ valid_elements : '*[*]', |
|
| 570 |
+ entities : '', |
|
| 571 |
+ url_converter : '', |
|
| 572 |
+ invalid_elements : '', |
|
| 573 |
+ verify_html : false |
|
| 574 |
+ }; |
|
| 575 |
+ |
|
| 576 |
+ this.vElements = tinyMCE.clearArray([]); |
|
| 577 |
+ this.vElementsRe = ''; |
|
| 578 |
+ this.closeElementsRe = /^(IMG|BR|HR|LINK|META|BASE|INPUT|AREA)$/; |
|
| 579 |
+ this.codeElementsRe = /^(SCRIPT|STYLE)$/; |
|
| 580 |
+ this.serializationId = 0; |
|
| 581 |
+ this.mceAttribs = {
|
|
| 582 |
+ href : 'mce_href', |
|
| 583 |
+ src : 'mce_src', |
|
| 584 |
+ type : 'mce_type' |
|
| 585 |
+ }; |
|
| 586 |
+} |
|
| 587 |
+ |
|
| 588 |
+/**#@+ |
|
| 589 |
+ * @member TinyMCE_Cleanup |
|
| 590 |
+ */ |
|
| 591 |
+TinyMCE_Cleanup.prototype = {
|
|
| 592 |
+ /**#@+ |
|
| 593 |
+ * @method |
|
| 594 |
+ */ |
|
| 595 |
+ |
|
| 596 |
+ /** |
|
| 597 |
+ * Initializes the cleanup engine with the specified config. |
|
| 598 |
+ * |
|
| 599 |
+ * @param {Array} s Name/Value array with config settings.
|
|
| 600 |
+ */ |
|
| 601 |
+ init : function(s) {
|
|
| 602 |
+ var n, a, i, ir, or, st; |
|
| 603 |
+ |
|
| 604 |
+ for (n in s) |
|
| 605 |
+ this.settings[n] = s[n]; |
|
| 606 |
+ |
|
| 607 |
+ // Setup code formating |
|
| 608 |
+ s = this.settings; |
|
| 609 |
+ |
|
| 610 |
+ // Setup regexps |
|
| 611 |
+ this.inRe = this._arrayToRe(s.indent_elements.split(','), '', '^<(', ')[^>]*');
|
|
| 612 |
+ this.ouRe = this._arrayToRe(s.indent_elements.split(','), '', '^<\\/(', ')[^>]*');
|
|
| 613 |
+ this.nlBeforeRe = this._arrayToRe(s.newline_before_elements.split(','), 'gi', '<(', ')([^>]*)>');
|
|
| 614 |
+ this.nlAfterRe = this._arrayToRe(s.newline_after_elements.split(','), 'gi', '<(', ')([^>]*)>');
|
|
| 615 |
+ this.nlBeforeAfterRe = this._arrayToRe(s.newline_before_after_elements.split(','), 'gi', '<(\\/?)(', ')([^>]*)>');
|
|
| 616 |
+ this.serializedNodes = []; |
|
| 617 |
+ this.serializationId = 0; |
|
| 618 |
+ |
|
| 619 |
+ if (s.invalid_elements !== '') |
|
| 620 |
+ this.iveRe = this._arrayToRe(s.invalid_elements.toUpperCase().split(','), 'g', '^(', ')$');
|
|
| 621 |
+ else |
|
| 622 |
+ this.iveRe = null; |
|
| 623 |
+ |
|
| 624 |
+ // Setup separator |
|
| 625 |
+ st = ''; |
|
| 626 |
+ for (i=0; i<s.indent_levels; i++) |
|
| 627 |
+ st += s.indent_char; |
|
| 628 |
+ |
|
| 629 |
+ this.inStr = st; |
|
| 630 |
+ |
|
| 631 |
+ // If verify_html if false force *[*] |
|
| 632 |
+ if (!s.verify_html) {
|
|
| 633 |
+ s.valid_elements = '*[*]'; |
|
| 634 |
+ s.extended_valid_elements = ''; |
|
| 635 |
+ } |
|
| 636 |
+ |
|
| 637 |
+ this.fillStr = s.entity_encoding == "named" ? " " : " "; |
|
| 638 |
+ this.idCount = 0; |
|
| 639 |
+ this.xmlEncodeRe = new RegExp('[\u007F-\uFFFF<>&"]', 'g');
|
|
| 640 |
+ }, |
|
| 641 |
+ |
|
| 642 |
+ /** |
|
| 643 |
+ * Adds a cleanup rule string, for example: a[!href|!name|title=title|class=class1?class2?class3]. |
|
| 644 |
+ * These rules are then used when serializing the DOM tree as a HTML string, it gives the possibility |
|
| 645 |
+ * to control the valid elements and attributes and force attribute values or default them. |
|
| 646 |
+ * |
|
| 647 |
+ * @param {string} s Rule string to parse and add to the cleanup rules array.
|
|
| 648 |
+ */ |
|
| 649 |
+ addRuleStr : function(s) {
|
|
| 650 |
+ var r = this.parseRuleStr(s), n; |
|
| 651 |
+ |
|
| 652 |
+ for (n in r) {
|
|
| 653 |
+ if (r[n]) |
|
| 654 |
+ this.rules[n] = r[n]; |
|
| 655 |
+ } |
|
| 656 |
+ |
|
| 657 |
+ this.vElements = tinyMCE.clearArray([]); |
|
| 658 |
+ |
|
| 659 |
+ for (n in this.rules) {
|
|
| 660 |
+ if (this.rules[n]) |
|
| 661 |
+ this.vElements[this.vElements.length] = this.rules[n].tag; |
|
| 662 |
+ } |
|
| 663 |
+ |
|
| 664 |
+ this.vElementsRe = this._arrayToRe(this.vElements, ''); |
|
| 665 |
+ }, |
|
| 666 |
+ |
|
| 667 |
+ /** |
|
| 668 |
+ * Returns true/false if the element name if valid or not against the cleanup rules. |
|
| 669 |
+ * |
|
| 670 |
+ * @param {string} n Node name to validate.
|
|
| 671 |
+ * @return {bool} True/false if the name is valid or not.
|
|
| 672 |
+ */ |
|
| 673 |
+ isValid : function(n) {
|
|
| 674 |
+ if (!this.rulesDone) |
|
| 675 |
+ this._setupRules(); // Will initialize cleanup rules |
|
| 676 |
+ |
|
| 677 |
+ // Empty is true since it removes formatting |
|
| 678 |
+ if (!n) |
|
| 679 |
+ return true; |
|
| 680 |
+ |
|
| 681 |
+ // Clean the name up a bit |
|
| 682 |
+ n = n.replace(/[^a-z0-9]+/gi, '').toUpperCase(); |
|
| 683 |
+ |
|
| 684 |
+ return !tinyMCE.getParam('cleanup') || this.vElementsRe.test(n);
|
|
| 685 |
+ }, |
|
| 686 |
+ |
|
| 687 |
+ /** |
|
| 688 |
+ * |
|
| 689 |
+ * format: h1/h2/h3/h4/h5/h6[%inline_trans_no_a],table[thead|tbody|tfoot|tr|td],body[%btrans]=>p |
|
| 690 |
+ */ |
|
| 691 |
+ addChildRemoveRuleStr : function(s) {
|
|
| 692 |
+ var x, y, p, i, t, tn, ta, cl, r; |
|
| 693 |
+ |
|
| 694 |
+ if (!s) |
|
| 695 |
+ return; |
|
| 696 |
+ |
|
| 697 |
+ ta = s.split(',');
|
|
| 698 |
+ for (x=0; x<ta.length; x++) {
|
|
| 699 |
+ s = ta[x]; |
|
| 700 |
+ |
|
| 701 |
+ // Split tag/children |
|
| 702 |
+ p = this.split(/\[|\]/, s); |
|
| 703 |
+ if (p == null || p.length < 1) |
|
| 704 |
+ t = s.toUpperCase(); |
|
| 705 |
+ else |
|
| 706 |
+ t = p[0].toUpperCase(); |
|
| 707 |
+ |
|
| 708 |
+ // Handle all tag names |
|
| 709 |
+ tn = this.split('/', t);
|
|
| 710 |
+ for (y=0; y<tn.length; y++) {
|
|
| 711 |
+ r = "^(";
|
|
| 712 |
+ |
|
| 713 |
+ // Build regex |
|
| 714 |
+ cl = this.split(/\|/, p[1]); |
|
| 715 |
+ for (i=0; i<cl.length; i++) {
|
|
| 716 |
+ if (cl[i] == '%istrict') |
|
| 717 |
+ r += tinyMCE.inlineStrict; |
|
| 718 |
+ else if (cl[i] == '%itrans') |
|
| 719 |
+ r += tinyMCE.inlineTransitional; |
|
| 720 |
+ else if (cl[i] == '%istrict_na') |
|
| 721 |
+ r += tinyMCE.inlineStrict.substring(2); |
|
| 722 |
+ else if (cl[i] == '%itrans_na') |
|
| 723 |
+ r += tinyMCE.inlineTransitional.substring(2); |
|
| 724 |
+ else if (cl[i] == '%btrans') |
|
| 725 |
+ r += tinyMCE.blockElms; |
|
| 726 |
+ else if (cl[i] == '%strict') |
|
| 727 |
+ r += tinyMCE.blockStrict; |
|
| 728 |
+ else |
|
| 729 |
+ r += (cl[i].charAt(0) != '#' ? cl[i].toUpperCase() : cl[i]); |
|
| 730 |
+ |
|
| 731 |
+ r += (i != cl.length - 1 ? '|' : ''); |
|
| 732 |
+ } |
|
| 733 |
+ |
|
| 734 |
+ r += ')$'; |
|
| 735 |
+ |
|
| 736 |
+ if (this.childRules == null) |
|
| 737 |
+ this.childRules = tinyMCE.clearArray([]); |
|
| 738 |
+ |
|
| 739 |
+ this.childRules[tn[y]] = new RegExp(r); |
|
| 740 |
+ |
|
| 741 |
+ if (p.length > 1) |
|
| 742 |
+ this.childRules[tn[y]].wrapTag = p[2]; |
|
| 743 |
+ } |
|
| 744 |
+ } |
|
| 745 |
+ }, |
|
| 746 |
+ |
|
| 747 |
+ /** |
|
| 748 |
+ * Parses a cleanup rule string, for example: a[!href|name|title=title|class=class1?class2?class3]. |
|
| 749 |
+ * These rules are then used when serializing the DOM tree as a HTML string, it gives the possibility |
|
| 750 |
+ * to control the valid elements and attributes and force attribute values or default them. |
|
| 751 |
+ * |
|
| 752 |
+ * @param {string} s Rule string to parse as a name/value rule array.
|
|
| 753 |
+ * @return Parsed name/value rule array. |
|
| 754 |
+ * @type Array |
|
| 755 |
+ */ |
|
| 756 |
+ parseRuleStr : function(s) {
|
|
| 757 |
+ var ta, p, r, a, i, x, px, t, tn, y, av, or = tinyMCE.clearArray([]), dv; |
|
| 758 |
+ |
|
| 759 |
+ if (s == null || s.length == 0) |
|
| 760 |
+ return or; |
|
| 761 |
+ |
|
| 762 |
+ ta = s.split(',');
|
|
| 763 |
+ for (x=0; x<ta.length; x++) {
|
|
| 764 |
+ s = ta[x]; |
|
| 765 |
+ if (s.length == 0) |
|
| 766 |
+ continue; |
|
| 767 |
+ |
|
| 768 |
+ // Split tag/attrs |
|
| 769 |
+ p = this.split(/\[|\]/, s); |
|
| 770 |
+ if (p == null || p.length < 1) |
|
| 771 |
+ t = s.toUpperCase(); |
|
| 772 |
+ else |
|
| 773 |
+ t = p[0].toUpperCase(); |
|
| 774 |
+ |
|
| 775 |
+ // Handle all tag names |
|
| 776 |
+ tn = this.split('/', t);
|
|
| 777 |
+ for (y=0; y<tn.length; y++) {
|
|
| 778 |
+ r = {};
|
|
| 779 |
+ |
|
| 780 |
+ r.tag = tn[y]; |
|
| 781 |
+ r.forceAttribs = null; |
|
| 782 |
+ r.defaultAttribs = null; |
|
| 783 |
+ r.validAttribValues = null; |
|
| 784 |
+ |
|
| 785 |
+ // Handle prefixes |
|
| 786 |
+ px = r.tag.charAt(0); |
|
| 787 |
+ r.forceOpen = px == '+'; |
|
| 788 |
+ r.removeEmpty = px == '-'; |
|
| 789 |
+ r.fill = px == '#'; |
|
| 790 |
+ r.tag = r.tag.replace(/\+|-|#/g, ''); |
|
| 791 |
+ r.oTagName = tn[0].replace(/\+|-|#/g, '').toLowerCase(); |
|
| 792 |
+ r.isWild = new RegExp('\\*|\\?|\\+', 'g').test(r.tag);
|
|
| 793 |
+ r.validRe = new RegExp(this._wildcardToRe('^' + r.tag + '$'));
|
|
| 794 |
+ |
|
| 795 |
+ // Setup valid attributes |
|
| 796 |
+ if (p.length > 1) {
|
|
| 797 |
+ r.vAttribsRe = '^(';
|
|
| 798 |
+ a = this.split(/\|/, p[1]); |
|
| 799 |
+ |
|
| 800 |
+ for (i=0; i<a.length; i++) {
|
|
| 801 |
+ t = a[i]; |
|
| 802 |
+ |
|
| 803 |
+ if (t.charAt(0) == '!') {
|
|
| 804 |
+ a[i] = t = t.substring(1); |
|
| 805 |
+ |
|
| 806 |
+ if (!r.reqAttribsRe) |
|
| 807 |
+ r.reqAttribsRe = '\\s+(' + t;
|
|
| 808 |
+ else |
|
| 809 |
+ r.reqAttribsRe += '|' + t; |
|
| 810 |
+ } |
|
| 811 |
+ |
|
| 812 |
+ av = new RegExp('(=|:|<)(.*?)$').exec(t);
|
|
| 813 |
+ t = t.replace(new RegExp('(=|:|<).*?$'), '');
|
|
| 814 |
+ if (av && av.length > 0) {
|
|
| 815 |
+ if (av[0].charAt(0) == ':') {
|
|
| 816 |
+ if (!r.forceAttribs) |
|
| 817 |
+ r.forceAttribs = tinyMCE.clearArray([]); |
|
| 818 |
+ |
|
| 819 |
+ r.forceAttribs[t.toLowerCase()] = av[0].substring(1); |
|
| 820 |
+ } else if (av[0].charAt(0) == '=') {
|
|
| 821 |
+ if (!r.defaultAttribs) |
|
| 822 |
+ r.defaultAttribs = tinyMCE.clearArray([]); |
|
| 823 |
+ |
|
| 824 |
+ dv = av[0].substring(1); |
|
| 825 |
+ |
|
| 826 |
+ r.defaultAttribs[t.toLowerCase()] = dv == '' ? "mce_empty" : dv; |
|
| 827 |
+ } else if (av[0].charAt(0) == '<') {
|
|
| 828 |
+ if (!r.validAttribValues) |
|
| 829 |
+ r.validAttribValues = tinyMCE.clearArray([]); |
|
| 830 |
+ |
|
| 831 |
+ r.validAttribValues[t.toLowerCase()] = this._arrayToRe(this.split('?', av[0].substring(1)), 'i');
|
|
| 832 |
+ } |
|
| 833 |
+ } |
|
| 834 |
+ |
|
| 835 |
+ r.vAttribsRe += '' + t.toLowerCase() + (i != a.length - 1 ? '|' : ''); |
|
| 836 |
+ |
|
| 837 |
+ a[i] = t.toLowerCase(); |
|
| 838 |
+ } |
|
| 839 |
+ |
|
| 840 |
+ if (r.reqAttribsRe) |
|
| 841 |
+ r.reqAttribsRe = new RegExp(r.reqAttribsRe + ')=\"', 'g'); |
|
| 842 |
+ |
|
| 843 |
+ r.vAttribsRe += ')$'; |
|
| 844 |
+ r.vAttribsRe = this._wildcardToRe(r.vAttribsRe); |
|
| 845 |
+ r.vAttribsReIsWild = new RegExp('\\*|\\?|\\+', 'g').test(r.vAttribsRe);
|
|
| 846 |
+ r.vAttribsRe = new RegExp(r.vAttribsRe); |
|
| 847 |
+ r.vAttribs = a.reverse(); |
|
| 848 |
+ |
|
| 849 |
+ //tinyMCE.debug(r.tag, r.oTagName, r.vAttribsRe, r.vAttribsReWC); |
|
| 850 |
+ } else {
|
|
| 851 |
+ r.vAttribsRe = ''; |
|
| 852 |
+ r.vAttribs = tinyMCE.clearArray([]); |
|
| 853 |
+ r.vAttribsReIsWild = false; |
|
| 854 |
+ } |
|
| 855 |
+ |
|
| 856 |
+ or[r.tag] = r; |
|
| 857 |
+ } |
|
| 858 |
+ } |
|
| 859 |
+ |
|
| 860 |
+ return or; |
|
| 861 |
+ }, |
|
| 862 |
+ |
|
| 863 |
+ /** |
|
| 864 |
+ * Serializes the specified node as a HTML string. This uses the XML parser and serializer |
|
| 865 |
+ * to generate a XHTML string. |
|
| 866 |
+ * |
|
| 867 |
+ * @param {HTMLNode} n Node to serialize as a XHTML string.
|
|
| 868 |
+ * @return Serialized XHTML string based on specified node. |
|
| 869 |
+ * @type string |
|
| 870 |
+ */ |
|
| 871 |
+ serializeNodeAsXML : function(n) {
|
|
| 872 |
+ var s, b; |
|
| 873 |
+ |
|
| 874 |
+ if (!this.xmlDoc) {
|
|
| 875 |
+ if (this.isIE) {
|
|
| 876 |
+ try {this.xmlDoc = new ActiveXObject('MSXML2.DOMDocument');} catch (e) {}
|
|
| 877 |
+ |
|
| 878 |
+ if (!this.xmlDoc) |
|
| 879 |
+ try {this.xmlDoc = new ActiveXObject('Microsoft.XmlDom');} catch (e) {}
|
|
| 880 |
+ } else |
|
| 881 |
+ this.xmlDoc = document.implementation.createDocument('', '', null);
|
|
| 882 |
+ |
|
| 883 |
+ if (!this.xmlDoc) |
|
| 884 |
+ alert("Error XML Parser could not be found.");
|
|
| 885 |
+ } |
|
| 886 |
+ |
|
| 887 |
+ if (this.xmlDoc.firstChild) |
|
| 888 |
+ this.xmlDoc.removeChild(this.xmlDoc.firstChild); |
|
| 889 |
+ |
|
| 890 |
+ b = this.xmlDoc.createElement("html");
|
|
| 891 |
+ b = this.xmlDoc.appendChild(b); |
|
| 892 |
+ |
|
| 893 |
+ this._convertToXML(n, b); |
|
| 894 |
+ |
|
| 895 |
+ if (this.isIE) |
|
| 896 |
+ return this.xmlDoc.xml; |
|
| 897 |
+ else |
|
| 898 |
+ return new XMLSerializer().serializeToString(this.xmlDoc); |
|
| 899 |
+ }, |
|
| 900 |
+ |
|
| 901 |
+ /** |
|
| 902 |
+ * Converts and adds the specified HTML DOM node to a XML DOM node. |
|
| 903 |
+ * |
|
| 904 |
+ * @param {HTMLNode} n HTML Node to add as a XML node.
|
|
| 905 |
+ * @param {XMLNode} xn XML Node to add the HTML node to.
|
|
| 906 |
+ * @private |
|
| 907 |
+ */ |
|
| 908 |
+ _convertToXML : function(n, xn) {
|
|
| 909 |
+ var xd, el, i, l, cn, at, no, hc = false; |
|
| 910 |
+ |
|
| 911 |
+ if (tinyMCE.isRealIE && this._isDuplicate(n)) |
|
| 912 |
+ return; |
|
| 913 |
+ |
|
| 914 |
+ xd = this.xmlDoc; |
|
| 915 |
+ |
|
| 916 |
+ switch (n.nodeType) {
|
|
| 917 |
+ case 1: // Element |
|
| 918 |
+ hc = n.hasChildNodes(); |
|
| 919 |
+ |
|
| 920 |
+ el = xd.createElement(n.nodeName.toLowerCase()); |
|
| 921 |
+ |
|
| 922 |
+ at = n.attributes; |
|
| 923 |
+ for (i=at.length-1; i>-1; i--) {
|
|
| 924 |
+ no = at[i]; |
|
| 925 |
+ |
|
| 926 |
+ if (no.specified && no.nodeValue) |
|
| 927 |
+ el.setAttribute(no.nodeName.toLowerCase(), no.nodeValue); |
|
| 928 |
+ } |
|
| 929 |
+ |
|
| 930 |
+ if (!hc && !this.closeElementsRe.test(n.nodeName)) |
|
| 931 |
+ el.appendChild(xd.createTextNode(""));
|
|
| 932 |
+ |
|
| 933 |
+ xn = xn.appendChild(el); |
|
| 934 |
+ break; |
|
| 935 |
+ |
|
| 936 |
+ case 3: // Text |
|
| 937 |
+ xn.appendChild(xd.createTextNode(n.nodeValue)); |
|
| 938 |
+ return; |
|
| 939 |
+ |
|
| 940 |
+ case 8: // Comment |
|
| 941 |
+ xn.appendChild(xd.createComment(n.nodeValue)); |
|
| 942 |
+ return; |
|
| 943 |
+ } |
|
| 944 |
+ |
|
| 945 |
+ if (hc) {
|
|
| 946 |
+ cn = n.childNodes; |
|
| 947 |
+ |
|
| 948 |
+ for (i=0, l=cn.length; i<l; i++) |
|
| 949 |
+ this._convertToXML(cn[i], xn); |
|
| 950 |
+ } |
|
| 951 |
+ }, |
|
| 952 |
+ |
|
| 953 |
+ /** |
|
| 954 |
+ * Serializes the specified node as a XHTML string. This uses the TinyMCE serializer logic since it gives more |
|
| 955 |
+ * control over the output than the build in browser XML serializer. |
|
| 956 |
+ * |
|
| 957 |
+ * @param {HTMLNode} n Node to serialize as a XHTML string.
|
|
| 958 |
+ * @param {bool} inn Optional inner HTML mode. Will only output child nodes and not the parent.
|
|
| 959 |
+ * @return Serialized XHTML string based on specified node. |
|
| 960 |
+ * @type string |
|
| 961 |
+ */ |
|
| 962 |
+ serializeNodeAsHTML : function(n, inn) {
|
|
| 963 |
+ var en, no, h = '', i, l, t, st, r, cn, va = false, f = false, at, hc, cr, nn; |
|
| 964 |
+ |
|
| 965 |
+ if (!this.rulesDone) |
|
| 966 |
+ this._setupRules(); // Will initialize cleanup rules |
|
| 967 |
+ |
|
| 968 |
+ if (tinyMCE.isRealIE && this._isDuplicate(n)) |
|
| 969 |
+ return ''; |
|
| 970 |
+ |
|
| 971 |
+ // Skip non valid child elements |
|
| 972 |
+ if (n.parentNode && this.childRules != null) {
|
|
| 973 |
+ cr = this.childRules[n.parentNode.nodeName]; |
|
| 974 |
+ |
|
| 975 |
+ if (typeof(cr) != "undefined" && !cr.test(n.nodeName)) {
|
|
| 976 |
+ st = true; |
|
| 977 |
+ t = null; |
|
| 978 |
+ } |
|
| 979 |
+ } |
|
| 980 |
+ |
|
| 981 |
+ switch (n.nodeType) {
|
|
| 982 |
+ case 1: // Element |
|
| 983 |
+ hc = n.hasChildNodes(); |
|
| 984 |
+ |
|
| 985 |
+ if (st) |
|
| 986 |
+ break; |
|
| 987 |
+ |
|
| 988 |
+ nn = n.nodeName; |
|
| 989 |
+ |
|
| 990 |
+ if (tinyMCE.isRealIE) {
|
|
| 991 |
+ // MSIE sometimes produces <//tag> |
|
| 992 |
+ if (n.nodeName.indexOf('/') != -1)
|
|
| 993 |
+ break; |
|
| 994 |
+ |
|
| 995 |
+ // MSIE has it's NS in a separate attrib |
|
| 996 |
+ if (n.scopeName && n.scopeName != 'HTML') |
|
| 997 |
+ nn = n.scopeName.toUpperCase() + ':' + nn.toUpperCase(); |
|
| 998 |
+ } else if (tinyMCE.isOpera && nn.indexOf(':') > 0)
|
|
| 999 |
+ nn = nn.toUpperCase(); |
|
| 1000 |
+ |
|
| 1001 |
+ // Convert fonts to spans |
|
| 1002 |
+ if (this.settings.convert_fonts_to_spans) {
|
|
| 1003 |
+ // On get content FONT -> SPAN |
|
| 1004 |
+ if (this.settings.on_save && nn == 'FONT') |
|
| 1005 |
+ nn = 'SPAN'; |
|
| 1006 |
+ |
|
| 1007 |
+ // On insert content SPAN -> FONT |
|
| 1008 |
+ if (!this.settings.on_save && nn == 'SPAN') |
|
| 1009 |
+ nn = 'FONT'; |
|
| 1010 |
+ } |
|
| 1011 |
+ |
|
| 1012 |
+ if (this.vElementsRe.test(nn) && (!this.iveRe || !this.iveRe.test(nn)) && !inn) {
|
|
| 1013 |
+ va = true; |
|
| 1014 |
+ |
|
| 1015 |
+ r = this.rules[nn]; |
|
| 1016 |
+ if (!r) {
|
|
| 1017 |
+ at = this.rules; |
|
| 1018 |
+ for (no in at) {
|
|
| 1019 |
+ if (at[no] && at[no].validRe.test(nn)) {
|
|
| 1020 |
+ r = at[no]; |
|
| 1021 |
+ break; |
|
| 1022 |
+ } |
|
| 1023 |
+ } |
|
| 1024 |
+ } |
|
| 1025 |
+ |
|
| 1026 |
+ en = r.isWild ? nn.toLowerCase() : r.oTagName; |
|
| 1027 |
+ f = r.fill; |
|
| 1028 |
+ |
|
| 1029 |
+ if (r.removeEmpty && !hc) |
|
| 1030 |
+ return ""; |
|
| 1031 |
+ |
|
| 1032 |
+ t = '<' + en; |
|
| 1033 |
+ |
|
| 1034 |
+ if (r.vAttribsReIsWild) {
|
|
| 1035 |
+ // Serialize wildcard attributes |
|
| 1036 |
+ at = n.attributes; |
|
| 1037 |
+ for (i=at.length-1; i>-1; i--) {
|
|
| 1038 |
+ no = at[i]; |
|
| 1039 |
+ if (no.specified && r.vAttribsRe.test(no.nodeName)) |
|
| 1040 |
+ t += this._serializeAttribute(n, r, no.nodeName); |
|
| 1041 |
+ } |
|
| 1042 |
+ } else {
|
|
| 1043 |
+ // Serialize specific attributes |
|
| 1044 |
+ for (i=r.vAttribs.length-1; i>-1; i--) |
|
| 1045 |
+ t += this._serializeAttribute(n, r, r.vAttribs[i]); |
|
| 1046 |
+ } |
|
| 1047 |
+ |
|
| 1048 |
+ // Serialize mce_ atts |
|
| 1049 |
+ if (!this.settings.on_save) {
|
|
| 1050 |
+ at = this.mceAttribs; |
|
| 1051 |
+ |
|
| 1052 |
+ for (no in at) {
|
|
| 1053 |
+ if (at[no]) |
|
| 1054 |
+ t += this._serializeAttribute(n, r, at[no]); |
|
| 1055 |
+ } |
|
| 1056 |
+ } |
|
| 1057 |
+ |
|
| 1058 |
+ // Check for required attribs |
|
| 1059 |
+ if (r.reqAttribsRe && !t.match(r.reqAttribsRe)) |
|
| 1060 |
+ t = null; |
|
| 1061 |
+ |
|
| 1062 |
+ // Close these |
|
| 1063 |
+ if (t != null && this.closeElementsRe.test(nn)) |
|
| 1064 |
+ return t + ' />'; |
|
| 1065 |
+ |
|
| 1066 |
+ if (t != null) |
|
| 1067 |
+ h += t + '>'; |
|
| 1068 |
+ |
|
| 1069 |
+ if (this.isIE && this.codeElementsRe.test(nn)) |
|
| 1070 |
+ h += n.innerHTML; |
|
| 1071 |
+ } |
|
| 1072 |
+ break; |
|
| 1073 |
+ |
|
| 1074 |
+ case 3: // Text |
|
| 1075 |
+ if (st) |
|
| 1076 |
+ break; |
|
| 1077 |
+ |
|
| 1078 |
+ if (n.parentNode && this.codeElementsRe.test(n.parentNode.nodeName)) |
|
| 1079 |
+ return this.isIE ? '' : n.nodeValue; |
|
| 1080 |
+ |
|
| 1081 |
+ return this.xmlEncode(n.nodeValue); |
|
| 1082 |
+ |
|
| 1083 |
+ case 8: // Comment |
|
| 1084 |
+ if (st) |
|
| 1085 |
+ break; |
|
| 1086 |
+ |
|
| 1087 |
+ return "<!--" + this._trimComment(n.nodeValue) + "-->"; |
|
| 1088 |
+ } |
|
| 1089 |
+ |
|
| 1090 |
+ if (hc) {
|
|
| 1091 |
+ cn = n.childNodes; |
|
| 1092 |
+ |
|
| 1093 |
+ for (i=0, l=cn.length; i<l; i++) |
|
| 1094 |
+ h += this.serializeNodeAsHTML(cn[i]); |
|
| 1095 |
+ } |
|
| 1096 |
+ |
|
| 1097 |
+ // Fill empty nodes |
|
| 1098 |
+ if (f && !hc) |
|
| 1099 |
+ h += this.fillStr; |
|
| 1100 |
+ |
|
| 1101 |
+ // End element |
|
| 1102 |
+ if (t != null && va) |
|
| 1103 |
+ h += '</' + en + '>'; |
|
| 1104 |
+ |
|
| 1105 |
+ return h; |
|
| 1106 |
+ }, |
|
| 1107 |
+ |
|
| 1108 |
+ /** |
|
| 1109 |
+ * Serializes the specified attribute as a XHTML string chunk. |
|
| 1110 |
+ * |
|
| 1111 |
+ * @param {HTMLNode} n HTML node to get attribute from.
|
|
| 1112 |
+ * @param {TinyMCE_CleanupRule} r Cleanup rule to use in serialization.
|
|
| 1113 |
+ * @param {string} an Attribute name to lookfor and serialize.
|
|
| 1114 |
+ * @return XHTML chunk containing attribute data if it was found. |
|
| 1115 |
+ * @type string |
|
| 1116 |
+ * @private |
|
| 1117 |
+ */ |
|
| 1118 |
+ _serializeAttribute : function(n, r, an) {
|
|
| 1119 |
+ var av = '', t, os = this.settings.on_save; |
|
| 1120 |
+ |
|
| 1121 |
+ if (os && (an.indexOf('mce_') == 0 || an.indexOf('_moz') == 0))
|
|
| 1122 |
+ return ''; |
|
| 1123 |
+ |
|
| 1124 |
+ if (os && this.mceAttribs[an]) |
|
| 1125 |
+ av = this._getAttrib(n, this.mceAttribs[an]); |
|
| 1126 |
+ |
|
| 1127 |
+ if (av.length == 0) |
|
| 1128 |
+ av = this._getAttrib(n, an); |
|
| 1129 |
+ |
|
| 1130 |
+ if (av.length == 0 && r.defaultAttribs && (t = r.defaultAttribs[an])) {
|
|
| 1131 |
+ av = t; |
|
| 1132 |
+ |
|
| 1133 |
+ if (av == "mce_empty") |
|
| 1134 |
+ return " " + an + '=""'; |
|
| 1135 |
+ } |
|
| 1136 |
+ |
|
| 1137 |
+ if (r.forceAttribs && (t = r.forceAttribs[an])) |
|
| 1138 |
+ av = t; |
|
| 1139 |
+ |
|
| 1140 |
+ if (os && av.length != 0 && /^(src|href|longdesc)$/.test(an)) |
|
| 1141 |
+ av = this._urlConverter(this, n, av); |
|
| 1142 |
+ |
|
| 1143 |
+ if (av.length != 0 && r.validAttribValues && r.validAttribValues[an] && !r.validAttribValues[an].test(av)) |
|
| 1144 |
+ return ""; |
|
| 1145 |
+ |
|
| 1146 |
+ if (av.length != 0 && av == "{$uid}")
|
|
| 1147 |
+ av = "uid_" + (this.idCount++); |
|
| 1148 |
+ |
|
| 1149 |
+ if (av.length != 0) {
|
|
| 1150 |
+ if (an.indexOf('on') != 0)
|
|
| 1151 |
+ av = this.xmlEncode(av, 1); |
|
| 1152 |
+ |
|
| 1153 |
+ return " " + an + "=" + '"' + av + '"'; |
|
| 1154 |
+ } |
|
| 1155 |
+ |
|
| 1156 |
+ return ""; |
|
| 1157 |
+ }, |
|
| 1158 |
+ |
|
| 1159 |
+ /** |
|
| 1160 |
+ * Applies source formatting/indentation on the specified HTML string. |
|
| 1161 |
+ * |
|
| 1162 |
+ * @param {string} h HTML string to apply formatting to.
|
|
| 1163 |
+ * @return Formatted HTML string. |
|
| 1164 |
+ * @type string |
|
| 1165 |
+ */ |
|
| 1166 |
+ formatHTML : function(h) {
|
|
| 1167 |
+ var s = this.settings, p = '', i = 0, li = 0, o = '', l; |
|
| 1168 |
+ |
|
| 1169 |
+ // Replace BR in pre elements to \n |
|
| 1170 |
+ h = h.replace(/<pre([^>]*)>(.*?)<\/pre>/gi, function (a, b, c) {
|
|
| 1171 |
+ c = c.replace(/<br\s*\/>/gi, '\n'); |
|
| 1172 |
+ return '<pre' + b + '>' + c + '</pre>'; |
|
| 1173 |
+ }); |
|
| 1174 |
+ |
|
| 1175 |
+ h = h.replace(/\r/g, ''); // Windows sux, isn't carriage return a thing of the past :) |
|
| 1176 |
+ h = '\n' + h; |
|
| 1177 |
+ h = h.replace(new RegExp('\\n\\s+', 'gi'), '\n'); // Remove previous formatting
|
|
| 1178 |
+ h = h.replace(this.nlBeforeRe, '\n<$1$2>'); |
|
| 1179 |
+ h = h.replace(this.nlAfterRe, '<$1$2>\n'); |
|
| 1180 |
+ h = h.replace(this.nlBeforeAfterRe, '\n<$1$2$3>\n'); |
|
| 1181 |
+ h += '\n'; |
|
| 1182 |
+ |
|
| 1183 |
+ //tinyMCE.debug(h); |
|
| 1184 |
+ |
|
| 1185 |
+ while ((i = h.indexOf('\n', i + 1)) != -1) {
|
|
| 1186 |
+ if ((l = h.substring(li + 1, i)).length != 0) {
|
|
| 1187 |
+ if (this.ouRe.test(l) && p.length >= s.indent_levels) |
|
| 1188 |
+ p = p.substring(s.indent_levels); |
|
| 1189 |
+ |
|
| 1190 |
+ o += p + l + '\n'; |
|
| 1191 |
+ |
|
| 1192 |
+ if (this.inRe.test(l)) |
|
| 1193 |
+ p += this.inStr; |
|
| 1194 |
+ } |
|
| 1195 |
+ |
|
| 1196 |
+ li = i; |
|
| 1197 |
+ } |
|
| 1198 |
+ |
|
| 1199 |
+ //tinyMCE.debug(h); |
|
| 1200 |
+ |
|
| 1201 |
+ return o; |
|
| 1202 |
+ }, |
|
| 1203 |
+ |
|
| 1204 |
+ /** |
|
| 1205 |
+ * XML Encodes the specified string based on configured entity encoding. The entity encoding modes |
|
| 1206 |
+ * are raw, numeric and named. Where raw is the fastest and named is default. |
|
| 1207 |
+ * |
|
| 1208 |
+ * @param {string} s String to convert to XML.
|
|
| 1209 |
+ * @return Encoded XML string based on configured entity encoding. |
|
| 1210 |
+ * @type string |
|
| 1211 |
+ */ |
|
| 1212 |
+ xmlEncode : function(s) {
|
|
| 1213 |
+ var cl = this, re = this.xmlEncodeRe; |
|
| 1214 |
+ |
|
| 1215 |
+ if (!this.entitiesDone) |
|
| 1216 |
+ this._setupEntities(); // Will intialize lookup table |
|
| 1217 |
+ |
|
| 1218 |
+ switch (this.settings.entity_encoding) {
|
|
| 1219 |
+ case "raw": |
|
| 1220 |
+ return tinyMCE.xmlEncode(s); |
|
| 1221 |
+ |
|
| 1222 |
+ case "named": |
|
| 1223 |
+ return s.replace(re, function (c) {
|
|
| 1224 |
+ var b = cl.entities[c.charCodeAt(0)]; |
|
| 1225 |
+ |
|
| 1226 |
+ return b ? '&' + b + ';' : c; |
|
| 1227 |
+ }); |
|
| 1228 |
+ |
|
| 1229 |
+ case "numeric": |
|
| 1230 |
+ return s.replace(re, function (c) {
|
|
| 1231 |
+ return '&#' + c.charCodeAt(0) + ';'; |
|
| 1232 |
+ }); |
|
| 1233 |
+ } |
|
| 1234 |
+ |
|
| 1235 |
+ return s; |
|
| 1236 |
+ }, |
|
| 1237 |
+ |
|
| 1238 |
+ /** |
|
| 1239 |
+ * Splits the specified string and removed empty chunks. |
|
| 1240 |
+ * |
|
| 1241 |
+ * @param {RegEx} re RegEx to split string by.
|
|
| 1242 |
+ * @param {string} s String value to split.
|
|
| 1243 |
+ * @return Array with parts from specified string. |
|
| 1244 |
+ * @type string |
|
| 1245 |
+ */ |
|
| 1246 |
+ split : function(re, s) {
|
|
| 1247 |
+ var i, l, o = [], c = s.split(re); |
|
| 1248 |
+ |
|
| 1249 |
+ for (i=0, l=c.length; i<l; i++) {
|
|
| 1250 |
+ if (c[i] !== '') |
|
| 1251 |
+ o[i] = c[i]; |
|
| 1252 |
+ } |
|
| 1253 |
+ |
|
| 1254 |
+ return o; |
|
| 1255 |
+ }, |
|
| 1256 |
+ |
|
| 1257 |
+ /** |
|
| 1258 |
+ * Removes contents that got added by TinyMCE to comments. |
|
| 1259 |
+ * |
|
| 1260 |
+ * @param {string} s Comment string data to trim.
|
|
| 1261 |
+ * @return Cleaned string from TinyMCE specific content. |
|
| 1262 |
+ * @type string |
|
| 1263 |
+ * @private |
|
| 1264 |
+ */ |
|
| 1265 |
+ _trimComment : function(s) {
|
|
| 1266 |
+ // Remove mce_src, mce_href |
|
| 1267 |
+ s = s.replace(new RegExp('\\smce_src=\"[^\"]*\"', 'gi'), "");
|
|
| 1268 |
+ s = s.replace(new RegExp('\\smce_href=\"[^\"]*\"', 'gi'), "");
|
|
| 1269 |
+ |
|
| 1270 |
+ return s; |
|
| 1271 |
+ }, |
|
| 1272 |
+ |
|
| 1273 |
+ /** |
|
| 1274 |
+ * Returns the value of the specified attribute name or default value if it wasn't found. |
|
| 1275 |
+ * |
|
| 1276 |
+ * @param {HTMLElement} e HTML element to get attribute from.
|
|
| 1277 |
+ * @param {string} n Attribute name to get from element.
|
|
| 1278 |
+ * @param {string} d Default value to return if attribute wasn't found.
|
|
| 1279 |
+ * @return Attribute value based on specified attribute name. |
|
| 1280 |
+ * @type string |
|
| 1281 |
+ * @private |
|
| 1282 |
+ */ |
|
| 1283 |
+ _getAttrib : function(e, n, d) {
|
|
| 1284 |
+ var v, ex, nn; |
|
| 1285 |
+ |
|
| 1286 |
+ if (typeof(d) == "undefined") |
|
| 1287 |
+ d = ""; |
|
| 1288 |
+ |
|
| 1289 |
+ if (!e || e.nodeType != 1) |
|
| 1290 |
+ return d; |
|
| 1291 |
+ |
|
| 1292 |
+ try {
|
|
| 1293 |
+ v = e.getAttribute(n, 0); |
|
| 1294 |
+ } catch (ex) {
|
|
| 1295 |
+ // IE 7 may cast exception on invalid attributes |
|
| 1296 |
+ v = e.getAttribute(n, 2); |
|
| 1297 |
+ } |
|
| 1298 |
+ |
|
| 1299 |
+ if (n == "class" && !v) |
|
| 1300 |
+ v = e.className; |
|
| 1301 |
+ |
|
| 1302 |
+ if (this.isIE) {
|
|
| 1303 |
+ if (n == "http-equiv") |
|
| 1304 |
+ v = e.httpEquiv; |
|
| 1305 |
+ |
|
| 1306 |
+ nn = e.nodeName; |
|
| 1307 |
+ |
|
| 1308 |
+ // Skip the default values that IE returns |
|
| 1309 |
+ if (nn == "FORM" && n == "enctype" && v == "application/x-www-form-urlencoded") |
|
| 1310 |
+ v = ""; |
|
| 1311 |
+ |
|
| 1312 |
+ if (nn == "INPUT" && n == "size" && v == "20") |
|
| 1313 |
+ v = ""; |
|
| 1314 |
+ |
|
| 1315 |
+ if (nn == "INPUT" && n == "maxlength" && v == "2147483647") |
|
| 1316 |
+ v = ""; |
|
| 1317 |
+ |
|
| 1318 |
+ // Images |
|
| 1319 |
+ if (n == "width" || n == "height") |
|
| 1320 |
+ v = e.getAttribute(n, 2); |
|
| 1321 |
+ } |
|
| 1322 |
+ |
|
| 1323 |
+ if (n == 'style' && v) {
|
|
| 1324 |
+ if (!tinyMCE.isOpera) |
|
| 1325 |
+ v = e.style.cssText; |
|
| 1326 |
+ |
|
| 1327 |
+ v = tinyMCE.serializeStyle(tinyMCE.parseStyle(v)); |
|
| 1328 |
+ } |
|
| 1329 |
+ |
|
| 1330 |
+ if (this.settings.on_save && n.indexOf('on') != -1 && this.settings.on_save && v && v !== '')
|
|
| 1331 |
+ v = tinyMCE.cleanupEventStr(v); |
|
| 1332 |
+ |
|
| 1333 |
+ return (v && v !== '') ? '' + v : d; |
|
| 1334 |
+ }, |
|
| 1335 |
+ |
|
| 1336 |
+ /** |
|
| 1337 |
+ * Internal URL converter callback function. This simply converts URLs based |
|
| 1338 |
+ * on some settings. |
|
| 1339 |
+ * |
|
| 1340 |
+ * @param {TinyMCE_Cleanup} c Cleanup instance.
|
|
| 1341 |
+ * @param {HTMLNode} n HTML node that holds the URL.
|
|
| 1342 |
+ * @param {string} v URL value to convert.
|
|
| 1343 |
+ * @return Converted URL value. |
|
| 1344 |
+ * @type string |
|
| 1345 |
+ * @private |
|
| 1346 |
+ */ |
|
| 1347 |
+ _urlConverter : function(c, n, v) {
|
|
| 1348 |
+ if (!c.settings.on_save) |
|
| 1349 |
+ return tinyMCE.convertRelativeToAbsoluteURL(tinyMCE.settings.base_href, v); |
|
| 1350 |
+ else if (tinyMCE.getParam('convert_urls')) {
|
|
| 1351 |
+ if (!this.urlConverter) |
|
| 1352 |
+ this.urlConverter = eval(tinyMCE.settings.urlconverter_callback); |
|
| 1353 |
+ |
|
| 1354 |
+ return this.urlConverter(v, n, true); |
|
| 1355 |
+ } |
|
| 1356 |
+ |
|
| 1357 |
+ return v; |
|
| 1358 |
+ }, |
|
| 1359 |
+ |
|
| 1360 |
+ /** |
|
| 1361 |
+ * Converts a array into a regex. |
|
| 1362 |
+ * |
|
| 1363 |
+ * @param {Array} a Array to convert into a regex.
|
|
| 1364 |
+ * @param {string} op RegEx options like, gi.
|
|
| 1365 |
+ * @param {string} be Before chunk, beginning of expression.
|
|
| 1366 |
+ * @param {string} af After chunk, end of expression.
|
|
| 1367 |
+ * @return RegEx instance based in input information. |
|
| 1368 |
+ * @type string |
|
| 1369 |
+ * @private |
|
| 1370 |
+ */ |
|
| 1371 |
+ _arrayToRe : function(a, op, be, af) {
|
|
| 1372 |
+ var i, r; |
|
| 1373 |
+ |
|
| 1374 |
+ op = typeof(op) == "undefined" ? "gi" : op; |
|
| 1375 |
+ be = typeof(be) == "undefined" ? "^(" : be;
|
|
| 1376 |
+ af = typeof(af) == "undefined" ? ")$" : af; |
|
| 1377 |
+ |
|
| 1378 |
+ r = be; |
|
| 1379 |
+ |
|
| 1380 |
+ for (i=0; i<a.length; i++) |
|
| 1381 |
+ r += this._wildcardToRe(a[i]) + (i != a.length-1 ? "|" : ""); |
|
| 1382 |
+ |
|
| 1383 |
+ r += af; |
|
| 1384 |
+ |
|
| 1385 |
+ return new RegExp(r, op); |
|
| 1386 |
+ }, |
|
| 1387 |
+ |
|
| 1388 |
+ /** |
|
| 1389 |
+ * Converts a wildcard string into a regex. |
|
| 1390 |
+ * |
|
| 1391 |
+ * @param {string} s Wildcard string to convert into RegEx.
|
|
| 1392 |
+ * @return RegEx string based on input. |
|
| 1393 |
+ * @type string |
|
| 1394 |
+ * @private |
|
| 1395 |
+ */ |
|
| 1396 |
+ _wildcardToRe : function(s) {
|
|
| 1397 |
+ s = s.replace(/\?/g, '(\\S?)'); |
|
| 1398 |
+ s = s.replace(/\+/g, '(\\S+)'); |
|
| 1399 |
+ s = s.replace(/\*/g, '(\\S*)'); |
|
| 1400 |
+ |
|
| 1401 |
+ return s; |
|
| 1402 |
+ }, |
|
| 1403 |
+ |
|
| 1404 |
+ /** |
|
| 1405 |
+ * Sets up the entity name lookup table ones. This moves the entity lookup pasing time |
|
| 1406 |
+ * from init to first xmlEncode call. |
|
| 1407 |
+ * |
|
| 1408 |
+ * @private |
|
| 1409 |
+ */ |
|
| 1410 |
+ _setupEntities : function() {
|
|
| 1411 |
+ var n, a, i, s = this.settings; |
|
| 1412 |
+ |
|
| 1413 |
+ // Setup entities |
|
| 1414 |
+ if (s.entity_encoding == "named") {
|
|
| 1415 |
+ n = tinyMCE.clearArray([]); |
|
| 1416 |
+ a = this.split(',', s.entities);
|
|
| 1417 |
+ for (i=0; i<a.length; i+=2) |
|
| 1418 |
+ n[a[i]] = a[i+1]; |
|
| 1419 |
+ |
|
| 1420 |
+ this.entities = n; |
|
| 1421 |
+ } |
|
| 1422 |
+ |
|
| 1423 |
+ this.entitiesDone = true; |
|
| 1424 |
+ }, |
|
| 1425 |
+ |
|
| 1426 |
+ /** |
|
| 1427 |
+ * Sets up the cleanup rules ones. This moves the cleanup rule pasing time |
|
| 1428 |
+ * from init to first cleanup call. |
|
| 1429 |
+ * |
|
| 1430 |
+ * @private |
|
| 1431 |
+ */ |
|
| 1432 |
+ _setupRules : function() {
|
|
| 1433 |
+ var s = this.settings; |
|
| 1434 |
+ |
|
| 1435 |
+ // Setup default rule |
|
| 1436 |
+ this.addRuleStr(s.valid_elements); |
|
| 1437 |
+ this.addRuleStr(s.extended_valid_elements); |
|
| 1438 |
+ this.addChildRemoveRuleStr(s.valid_child_elements); |
|
| 1439 |
+ |
|
| 1440 |
+ this.rulesDone = true; |
|
| 1441 |
+ }, |
|
| 1442 |
+ |
|
| 1443 |
+ /** |
|
| 1444 |
+ * Checks if the specified node is a duplicate in other words has it been processed/serialized before. |
|
| 1445 |
+ * |
|
| 1446 |
+ * @param {DOMNode} n DOM Node that is to be checked.
|
|
| 1447 |
+ * @return true/false if the node is a duplicate or not. |
|
| 1448 |
+ * @type boolean |
|
| 1449 |
+ * @private |
|
| 1450 |
+ */ |
|
| 1451 |
+ _isDuplicate : function(n) {
|
|
| 1452 |
+ var i, l, sn; |
|
| 1453 |
+ |
|
| 1454 |
+ if (!this.settings.fix_content_duplication) |
|
| 1455 |
+ return false; |
|
| 1456 |
+ |
|
| 1457 |
+ if (tinyMCE.isRealIE && n.nodeType == 1) {
|
|
| 1458 |
+ // Mark elements |
|
| 1459 |
+ if (n.mce_serialized == this.serializationId) |
|
| 1460 |
+ return true; |
|
| 1461 |
+ |
|
| 1462 |
+ n.setAttribute('mce_serialized', this.serializationId);
|
|
| 1463 |
+ } else {
|
|
| 1464 |
+ sn = this.serializedNodes; |
|
| 1465 |
+ |
|
| 1466 |
+ // Search lookup table for text nodes and comments |
|
| 1467 |
+ for (i=0, l = sn.length; i<l; i++) {
|
|
| 1468 |
+ if (sn[i] == n) |
|
| 1469 |
+ return true; |
|
| 1470 |
+ } |
|
| 1471 |
+ |
|
| 1472 |
+ sn.push(n); |
|
| 1473 |
+ } |
|
| 1474 |
+ |
|
| 1475 |
+ return false; |
|
| 1476 |
+ } |
|
| 1477 |
+ |
|
| 1478 |
+ /**#@-*/ |
|
| 1479 |
+}; |