Browse code

tinyMCE '2' is now 2.1.2 - Seems tinyMCE '2' was actually 3.0.9 (my fault for not checking), so '2' is now 2.1.2, and '3' is now 3.4.8... eFiction 3.4.3 used tinyMCE 2.1.0, and 3.5 upgraded that to 3.0.9.

Clarissa Walker authored on 2026/07/28 07:20:33
Showing 1 changed files
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>&nbsp;</p>');
28
+		s = s.replace(new RegExp('<p>\\s*<\\/p>', 'gi'), '<p>&nbsp;</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 = '&nbsp;';
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' ? '&#160;' : '&nbsp;';
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>(&nbsp;|&#160;)<\/p><hr \/><p>(&nbsp;|&#160;)<\/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*(&nbsp;|&#160;)\s*<br \/>\s*(&nbsp;|&#160;)\s*<\/p>/g, '<p>' + nb + '</p>');
489
+		h = h.replace(/<p>\s*(&nbsp;|&#160;)\s*<br \/>\s*<\/p>/g, '<p>' + nb + '</p>');
490
+		h = h.replace(/<p>\s*<br \/>\s*&nbsp;\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>&nbsp;<\/p>|<p>&#160;<\/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(/&nbsp;\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>(&nbsp;|&#160;)<\/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" ? "&nbsp;" : "&#160;";
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
+};