]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/dom/DOMUtils.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / dom / DOMUtils.js
1 /**
2  * DOMUtils.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         // Shorten names
13         var each = tinymce.each,
14                 is = tinymce.is,
15                 isWebKit = tinymce.isWebKit,
16                 isIE = tinymce.isIE,
17                 Entities = tinymce.html.Entities,
18                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
19                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
20                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
21
22         /**
23          * Utility class for various DOM manipulation and retrival functions.
24          *
25          * @class tinymce.dom.DOMUtils
26          * @example
27          * // Add a class to an element by id in the page
28          * tinymce.DOM.addClass('someid', 'someclass');
29          *
30          * // Add a class to an element by id inside the editor
31          * tinyMCE.activeEditor.dom.addClass('someid', 'someclass');
32          */
33         tinymce.create('tinymce.dom.DOMUtils', {
34                 doc : null,
35                 root : null,
36                 files : null,
37                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
38                 props : {
39                         "for" : "htmlFor",
40                         "class" : "className",
41                         className : "className",
42                         checked : "checked",
43                         disabled : "disabled",
44                         maxlength : "maxLength",
45                         readonly : "readOnly",
46                         selected : "selected",
47                         value : "value",
48                         id : "id",
49                         name : "name",
50                         type : "type"
51                 },
52
53                 /**
54                  * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
55                  *
56                  * @constructor
57                  * @method DOMUtils
58                  * @param {Document} d Document reference to bind the utility class to.
59                  * @param {settings} s Optional settings collection.
60                  */
61                 DOMUtils : function(d, s) {
62                         var t = this, globalStyle;
63
64                         t.doc = d;
65                         t.win = window;
66                         t.files = {};
67                         t.cssFlicker = false;
68                         t.counter = 0;
69                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
70                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
71                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
72
73                         t.settings = s = tinymce.extend({
74                                 keep_values : false,
75                                 hex_colors : 1
76                         }, s);
77                         
78                         t.schema = s.schema;
79                         t.styles = new tinymce.html.Styles({
80                                 url_converter : s.url_converter,
81                                 url_converter_scope : s.url_converter_scope
82                         }, s.schema);
83
84                         // Fix IE6SP2 flicker and check it failed for pre SP2
85                         if (tinymce.isIE6) {
86                                 try {
87                                         d.execCommand('BackgroundImageCache', false, true);
88                                 } catch (e) {
89                                         t.cssFlicker = true;
90                                 }
91                         }
92
93                         if (isIE) {
94                                 // Add missing HTML 4/5 elements to IE
95                                 ('abbr article aside audio canvas ' +
96                                 'details figcaption figure footer ' +
97                                 'header hgroup mark menu meter nav ' +
98                                 'output progress section summary ' +
99                                 'time video').replace(/\w+/g, function(name) {
100                                         d.createElement(name);
101                                 });
102                         }
103
104                         tinymce.addUnload(t.destroy, t);
105                 },
106
107                 /**
108                  * Returns the root node of the document this is normally the body but might be a DIV. Parents like getParent will not
109                  * go above the point of this root node.
110                  *
111                  * @method getRoot
112                  * @return {Element} Root element for the utility class.
113                  */
114                 getRoot : function() {
115                         var t = this, s = t.settings;
116
117                         return (s && t.get(s.root_element)) || t.doc.body;
118                 },
119
120                 /**
121                  * Returns the viewport of the window.
122                  *
123                  * @method getViewPort
124                  * @param {Window} w Optional window to get viewport of.
125                  * @return {Object} Viewport object with fields x, y, w and h.
126                  */
127                 getViewPort : function(w) {
128                         var d, b;
129
130                         w = !w ? this.win : w;
131                         d = w.document;
132                         b = this.boxModel ? d.documentElement : d.body;
133
134                         // Returns viewport size excluding scrollbars
135                         return {
136                                 x : w.pageXOffset || b.scrollLeft,
137                                 y : w.pageYOffset || b.scrollTop,
138                                 w : w.innerWidth || b.clientWidth,
139                                 h : w.innerHeight || b.clientHeight
140                         };
141                 },
142
143                 /**
144                  * Returns the rectangle for a specific element.
145                  *
146                  * @method getRect
147                  * @param {Element/String} e Element object or element ID to get rectange from.
148                  * @return {object} Rectange for specified element object with x, y, w, h fields.
149                  */
150                 getRect : function(e) {
151                         var p, t = this, sr;
152
153                         e = t.get(e);
154                         p = t.getPos(e);
155                         sr = t.getSize(e);
156
157                         return {
158                                 x : p.x,
159                                 y : p.y,
160                                 w : sr.w,
161                                 h : sr.h
162                         };
163                 },
164
165                 /**
166                  * Returns the size dimensions of the specified element.
167                  *
168                  * @method getSize
169                  * @param {Element/String} e Element object or element ID to get rectange from.
170                  * @return {object} Rectange for specified element object with w, h fields.
171                  */
172                 getSize : function(e) {
173                         var t = this, w, h;
174
175                         e = t.get(e);
176                         w = t.getStyle(e, 'width');
177                         h = t.getStyle(e, 'height');
178
179                         // Non pixel value, then force offset/clientWidth
180                         if (w.indexOf('px') === -1)
181                                 w = 0;
182
183                         // Non pixel value, then force offset/clientWidth
184                         if (h.indexOf('px') === -1)
185                                 h = 0;
186
187                         return {
188                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
189                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
190                         };
191                 },
192
193                 /**
194                  * Returns a node by the specified selector function. This function will
195                  * loop through all parent nodes and call the specified function for each node.
196                  * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
197                  * and the node it found will be returned.
198                  *
199                  * @method getParent
200                  * @param {Node/String} n DOM node to search parents on or ID string.
201                  * @param {function} f Selection function to execute on each node or CSS pattern.
202                  * @param {Node} r Optional root element, never go below this point.
203                  * @return {Node} DOM Node or null if it wasn't found.
204                  */
205                 getParent : function(n, f, r) {
206                         return this.getParents(n, f, r, false);
207                 },
208
209                 /**
210                  * Returns a node list of all parents matching the specified selector function or pattern.
211                  * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
212                  *
213                  * @method getParents
214                  * @param {Node/String} n DOM node to search parents on or ID string.
215                  * @param {function} f Selection function to execute on each node or CSS pattern.
216                  * @param {Node} r Optional root element, never go below this point.
217                  * @return {Array} Array of nodes or null if it wasn't found.
218                  */
219                 getParents : function(n, f, r, c) {
220                         var t = this, na, se = t.settings, o = [];
221
222                         n = t.get(n);
223                         c = c === undefined;
224
225                         if (se.strict_root)
226                                 r = r || t.getRoot();
227
228                         // Wrap node name as func
229                         if (is(f, 'string')) {
230                                 na = f;
231
232                                 if (f === '*') {
233                                         f = function(n) {return n.nodeType == 1;};
234                                 } else {
235                                         f = function(n) {
236                                                 return t.is(n, na);
237                                         };
238                                 }
239                         }
240
241                         while (n) {
242                                 if (n == r || !n.nodeType || n.nodeType === 9)
243                                         break;
244
245                                 if (!f || f(n)) {
246                                         if (c)
247                                                 o.push(n);
248                                         else
249                                                 return n;
250                                 }
251
252                                 n = n.parentNode;
253                         }
254
255                         return c ? o : null;
256                 },
257
258                 /**
259                  * Returns the specified element by ID or the input element if it isn't a string.
260                  *
261                  * @method get
262                  * @param {String/Element} n Element id to look for or element to just pass though.
263                  * @return {Element} Element matching the specified id or null if it wasn't found.
264                  */
265                 get : function(e) {
266                         var n;
267
268                         if (e && this.doc && typeof(e) == 'string') {
269                                 n = e;
270                                 e = this.doc.getElementById(e);
271
272                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
273                                 if (e && e.id !== n)
274                                         return this.doc.getElementsByName(n)[1];
275                         }
276
277                         return e;
278                 },
279
280                 /**
281                  * Returns the next node that matches selector or function
282                  *
283                  * @method getNext
284                  * @param {Node} node Node to find siblings from.
285                  * @param {String/function} selector Selector CSS expression or function.
286                  * @return {Node} Next node item matching the selector or null if it wasn't found.
287                  */
288                 getNext : function(node, selector) {
289                         return this._findSib(node, selector, 'nextSibling');
290                 },
291
292                 /**
293                  * Returns the previous node that matches selector or function
294                  *
295                  * @method getPrev
296                  * @param {Node} node Node to find siblings from.
297                  * @param {String/function} selector Selector CSS expression or function.
298                  * @return {Node} Previous node item matching the selector or null if it wasn't found.
299                  */
300                 getPrev : function(node, selector) {
301                         return this._findSib(node, selector, 'previousSibling');
302                 },
303
304                 // #ifndef jquery
305
306                 /**
307                  * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
308                  * This function is optimized for the most common patterns needed in TinyMCE but it also performes good enough
309                  * on more complex patterns.
310                  *
311                  * @method select
312                  * @param {String} p CSS level 1 pattern to select/find elements by.
313                  * @param {Object} s Optional root element/scope element to search in.
314                  * @return {Array} Array with all matched elements.
315                  * @example
316                  * // Adds a class to all paragraphs in the currently active editor
317                  * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass');
318                  * 
319                  * // Adds a class to all spans that has the test class in the currently active editor
320                  * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('span.test'), 'someclass')
321                  */
322                 select : function(pa, s) {
323                         var t = this;
324
325                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
326                 },
327
328                 /**
329                  * Returns true/false if the specified element matches the specified css pattern.
330                  *
331                  * @method is
332                  * @param {Node/NodeList} n DOM node to match or an array of nodes to match.
333                  * @param {String} selector CSS pattern to match the element agains.
334                  */
335                 is : function(n, selector) {
336                         var i;
337
338                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
339                         if (n.length === undefined) {
340                                 // Simple all selector
341                                 if (selector === '*')
342                                         return n.nodeType == 1;
343
344                                 // Simple selector just elements
345                                 if (simpleSelectorRe.test(selector)) {
346                                         selector = selector.toLowerCase().split(/,/);
347                                         n = n.nodeName.toLowerCase();
348
349                                         for (i = selector.length - 1; i >= 0; i--) {
350                                                 if (selector[i] == n)
351                                                         return true;
352                                         }
353
354                                         return false;
355                                 }
356                         }
357
358                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
359                 },
360
361                 // #endif
362
363                 /**
364                  * Adds the specified element to another element or elements.
365                  *
366                  * @method add
367                  * @param {String/Element/Array} Element id string, DOM node element or array of id's or elements to add to.
368                  * @param {String/Element} n Name of new element to add or existing element to add.
369                  * @param {Object} a Optional object collection with arguments to add to the new element(s).
370                  * @param {String} h Optional inner HTML contents to add for each element.
371                  * @param {Boolean} c Optional internal state to indicate if it should create or add.
372                  * @return {Element/Array} Element that got created or array with elements if multiple elements where passed.
373                  * @example
374                  * // Adds a new paragraph to the end of the active editor
375                  * tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'p', {title : 'my title'}, 'Some content');
376                  */
377                 add : function(p, n, a, h, c) {
378                         var t = this;
379
380                         return this.run(p, function(p) {
381                                 var e, k;
382
383                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
384                                 t.setAttribs(e, a);
385
386                                 if (h) {
387                                         if (h.nodeType)
388                                                 e.appendChild(h);
389                                         else
390                                                 t.setHTML(e, h);
391                                 }
392
393                                 return !c ? p.appendChild(e) : e;
394                         });
395                 },
396
397                 /**
398                  * Creates a new element.
399                  *
400                  * @method create
401                  * @param {String} n Name of new element.
402                  * @param {Object} a Optional object name/value collection with element attributes.
403                  * @param {String} h Optional HTML string to set as inner HTML of the element.
404                  * @return {Element} HTML DOM node element that got created.
405                  * @example
406                  * // Adds an element where the caret/selection is in the active editor
407                  * var el = tinyMCE.activeEditor.dom.create('div', {id : 'test', 'class' : 'myclass'}, 'some content');
408                  * tinyMCE.activeEditor.selection.setNode(el);
409                  */
410                 create : function(n, a, h) {
411                         return this.add(this.doc.createElement(n), n, a, h, 1);
412                 },
413
414                 /**
415                  * Create HTML string for element. The element will be closed unless an empty inner HTML string is passed.
416                  *
417                  * @method createHTML
418                  * @param {String} n Name of new element.
419                  * @param {Object} a Optional object name/value collection with element attributes.
420                  * @param {String} h Optional HTML string to set as inner HTML of the element.
421                  * @return {String} String with new HTML element like for example: <a href="#">test</a>.
422                  * @example
423                  * // Creates a html chunk and inserts it at the current selection/caret location
424                  * tinyMCE.activeEditor.selection.setContent(tinyMCE.activeEditor.dom.createHTML('a', {href : 'test.html'}, 'some line'));
425                  */
426                 createHTML : function(n, a, h) {
427                         var o = '', t = this, k;
428
429                         o += '<' + n;
430
431                         for (k in a) {
432                                 if (a.hasOwnProperty(k))
433                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
434                         }
435
436                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
437                         if (typeof(h) != "undefined")
438                                 return o + '>' + h + '</' + n + '>';
439
440                         return o + ' />';
441                 },
442
443                 /**
444                  * Removes/deletes the specified element(s) from the DOM.
445                  *
446                  * @method remove
447                  * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
448                  * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be placed at the location of the removed element.
449                  * @return {Element/Array} HTML DOM element that got removed or array of elements depending on input.
450                  * @example
451                  * // Removes all paragraphs in the active editor
452                  * tinyMCE.activeEditor.dom.remove(tinyMCE.activeEditor.dom.select('p'));
453                  * 
454                  * // Removes a element by id in the document
455                  * tinyMCE.DOM.remove('mydiv');
456                  */
457                 remove : function(node, keep_children) {
458                         return this.run(node, function(node) {
459                                 var child, parent = node.parentNode;
460
461                                 if (!parent)
462                                         return null;
463
464                                 if (keep_children) {
465                                         while (child = node.firstChild) {
466                                                 // IE 8 will crash if you don't remove completely empty text nodes
467                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
468                                                         parent.insertBefore(child, node);
469                                                 else
470                                                         node.removeChild(child);
471                                         }
472                                 }
473
474                                 return parent.removeChild(node);
475                         });
476                 },
477
478                 /**
479                  * Sets the CSS style value on a HTML element. The name can be a camelcase string
480                  * or the CSS style name like background-color.
481                  *
482                  * @method setStyle
483                  * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
484                  * @param {String} na Name of the style value to set.
485                  * @param {String} v Value to set on the style.
486                  * @example
487                  * // Sets a style value on all paragraphs in the currently active editor
488                  * tinyMCE.activeEditor.dom.setStyle(tinyMCE.activeEditor.dom.select('p'), 'background-color', 'red');
489                  * 
490                  * // Sets a style value to an element by id in the current document
491                  * tinyMCE.DOM.setStyle('mydiv', 'background-color', 'red');
492                  */
493                 setStyle : function(n, na, v) {
494                         var t = this;
495
496                         return t.run(n, function(e) {
497                                 var s, i;
498
499                                 s = e.style;
500
501                                 // Camelcase it, if needed
502                                 na = na.replace(/-(\D)/g, function(a, b){
503                                         return b.toUpperCase();
504                                 });
505
506                                 // Default px suffix on these
507                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
508                                         v += 'px';
509
510                                 switch (na) {
511                                         case 'opacity':
512                                                 // IE specific opacity
513                                                 if (isIE) {
514                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
515
516                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
517                                                                 s.display = 'inline-block';
518                                                 }
519
520                                                 // Fix for older browsers
521                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
522                                                 break;
523
524                                         case 'float':
525                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
526                                                 break;
527                                         
528                                         default:
529                                                 s[na] = v || '';
530                                 }
531
532                                 // Force update of the style data
533                                 if (t.settings.update_styles)
534                                         t.setAttrib(e, 'data-mce-style');
535                         });
536                 },
537
538                 /**
539                  * Returns the current style or runtime/computed value of a element.
540                  *
541                  * @method getStyle
542                  * @param {String/Element} n HTML element or element id string to get style from.
543                  * @param {String} na Style name to return.
544                  * @param {Boolean} c Computed style.
545                  * @return {String} Current style or computed style value of a element.
546                  */
547                 getStyle : function(n, na, c) {
548                         n = this.get(n);
549
550                         if (!n)
551                                 return;
552
553                         // Gecko
554                         if (this.doc.defaultView && c) {
555                                 // Remove camelcase
556                                 na = na.replace(/[A-Z]/g, function(a){
557                                         return '-' + a;
558                                 });
559
560                                 try {
561                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
562                                 } catch (ex) {
563                                         // Old safari might fail
564                                         return null;
565                                 }
566                         }
567
568                         // Camelcase it, if needed
569                         na = na.replace(/-(\D)/g, function(a, b){
570                                 return b.toUpperCase();
571                         });
572
573                         if (na == 'float')
574                                 na = isIE ? 'styleFloat' : 'cssFloat';
575
576                         // IE & Opera
577                         if (n.currentStyle && c)
578                                 return n.currentStyle[na];
579
580                         return n.style ? n.style[na] : undefined;
581                 },
582
583                 /**
584                  * Sets multiple styles on the specified element(s).
585                  *
586                  * @method setStyles
587                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
588                  * @param {Object} o Name/Value collection of style items to add to the element(s).
589                  * @example
590                  * // Sets styles on all paragraphs in the currently active editor
591                  * tinyMCE.activeEditor.dom.setStyles(tinyMCE.activeEditor.dom.select('p'), {'background-color' : 'red', 'color' : 'green'});
592                  * 
593                  * // Sets styles to an element by id in the current document
594                  * tinyMCE.DOM.setStyles('mydiv', {'background-color' : 'red', 'color' : 'green'});
595                  */
596                 setStyles : function(e, o) {
597                         var t = this, s = t.settings, ol;
598
599                         ol = s.update_styles;
600                         s.update_styles = 0;
601
602                         each(o, function(v, n) {
603                                 t.setStyle(e, n, v);
604                         });
605
606                         // Update style info
607                         s.update_styles = ol;
608                         if (s.update_styles)
609                                 t.setAttrib(e, s.cssText);
610                 },
611
612                 /**
613                  * Removes all attributes from an element or elements.
614                  * 
615                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
616                  */
617                 removeAllAttribs: function(e) {
618                         return this.run(e, function(e) {
619                                 var i, attrs = e.attributes;
620                                 for (i = attrs.length - 1; i >= 0; i--) {
621                                         e.removeAttributeNode(attrs.item(i));
622                                 }
623                         });
624                 },
625
626                 /**
627                  * Sets the specified attributes value of a element or elements.
628                  *
629                  * @method setAttrib
630                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
631                  * @param {String} n Name of attribute to set.
632                  * @param {String} v Value to set on the attribute of this value is falsy like null 0 or '' it will remove the attribute instead.
633                  * @example
634                  * // Sets an attribute to all paragraphs in the active editor
635                  * tinyMCE.activeEditor.dom.setAttrib(tinyMCE.activeEditor.dom.select('p'), 'class', 'myclass');
636                  * 
637                  * // Sets an attribute to a specific element in the current page
638                  * tinyMCE.dom.setAttrib('mydiv', 'class', 'myclass');
639                  */
640                 setAttrib : function(e, n, v) {
641                         var t = this;
642
643                         // Whats the point
644                         if (!e || !n)
645                                 return;
646
647                         // Strict XML mode
648                         if (t.settings.strict)
649                                 n = n.toLowerCase();
650
651                         return this.run(e, function(e) {
652                                 var s = t.settings;
653
654                                 switch (n) {
655                                         case "style":
656                                                 if (!is(v, 'string')) {
657                                                         each(v, function(v, n) {
658                                                                 t.setStyle(e, n, v);
659                                                         });
660
661                                                         return;
662                                                 }
663
664                                                 // No mce_style for elements with these since they might get resized by the user
665                                                 if (s.keep_values) {
666                                                         if (v && !t._isRes(v))
667                                                                 e.setAttribute('data-mce-style', v, 2);
668                                                         else
669                                                                 e.removeAttribute('data-mce-style', 2);
670                                                 }
671
672                                                 e.style.cssText = v;
673                                                 break;
674
675                                         case "class":
676                                                 e.className = v || ''; // Fix IE null bug
677                                                 break;
678
679                                         case "src":
680                                         case "href":
681                                                 if (s.keep_values) {
682                                                         if (s.url_converter)
683                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
684
685                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
686                                                 }
687
688                                                 break;
689
690                                         case "shape":
691                                                 e.setAttribute('data-mce-style', v);
692                                                 break;
693                                 }
694
695                                 if (is(v) && v !== null && v.length !== 0)
696                                         e.setAttribute(n, '' + v, 2);
697                                 else
698                                         e.removeAttribute(n, 2);
699                         });
700                 },
701
702                 /**
703                  * Sets the specified attributes of a element or elements.
704                  *
705                  * @method setAttribs
706                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attributes on.
707                  * @param {Object} o Name/Value collection of attribute items to add to the element(s).
708                  * @example
709                  * // Sets some attributes to all paragraphs in the active editor
710                  * tinyMCE.activeEditor.dom.setAttribs(tinyMCE.activeEditor.dom.select('p'), {'class' : 'myclass', title : 'some title'});
711                  * 
712                  * // Sets some attributes to a specific element in the current page
713                  * tinyMCE.DOM.setAttribs('mydiv', {'class' : 'myclass', title : 'some title'});
714                  */
715                 setAttribs : function(e, o) {
716                         var t = this;
717
718                         return this.run(e, function(e) {
719                                 each(o, function(v, n) {
720                                         t.setAttrib(e, n, v);
721                                 });
722                         });
723                 },
724
725                 /**
726                  * Returns the specified attribute by name.
727                  *
728                  * @method getAttrib
729                  * @param {String/Element} e Element string id or DOM element to get attribute from.
730                  * @param {String} n Name of attribute to get.
731                  * @param {String} dv Optional default value to return if the attribute didn't exist.
732                  * @return {String} Attribute value string, default value or null if the attribute wasn't found.
733                  */
734                 getAttrib : function(e, n, dv) {
735                         var v, t = this;
736
737                         e = t.get(e);
738
739                         if (!e || e.nodeType !== 1)
740                                 return false;
741
742                         if (!is(dv))
743                                 dv = '';
744
745                         // Try the mce variant for these
746                         if (/^(src|href|style|coords|shape)$/.test(n)) {
747                                 v = e.getAttribute("data-mce-" + n);
748
749                                 if (v)
750                                         return v;
751                         }
752
753                         if (isIE && t.props[n]) {
754                                 v = e[t.props[n]];
755                                 v = v && v.nodeValue ? v.nodeValue : v;
756                         }
757
758                         if (!v)
759                                 v = e.getAttribute(n, 2);
760
761                         // Check boolean attribs
762                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
763                                 if (e[t.props[n]] === true && v === '')
764                                         return n;
765
766                                 return v ? n : '';
767                         }
768
769                         // Inner input elements will override attributes on form elements
770                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
771                                 return e.getAttributeNode(n).nodeValue;
772
773                         if (n === 'style') {
774                                 v = v || e.style.cssText;
775
776                                 if (v) {
777                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
778
779                                         if (t.settings.keep_values && !t._isRes(v))
780                                                 e.setAttribute('data-mce-style', v);
781                                 }
782                         }
783
784                         // Remove Apple and WebKit stuff
785                         if (isWebKit && n === "class" && v)
786                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
787
788                         // Handle IE issues
789                         if (isIE) {
790                                 switch (n) {
791                                         case 'rowspan':
792                                         case 'colspan':
793                                                 // IE returns 1 as default value
794                                                 if (v === 1)
795                                                         v = '';
796
797                                                 break;
798
799                                         case 'size':
800                                                 // IE returns +0 as default value for size
801                                                 if (v === '+0' || v === 20 || v === 0)
802                                                         v = '';
803
804                                                 break;
805
806                                         case 'width':
807                                         case 'height':
808                                         case 'vspace':
809                                         case 'checked':
810                                         case 'disabled':
811                                         case 'readonly':
812                                                 if (v === 0)
813                                                         v = '';
814
815                                                 break;
816
817                                         case 'hspace':
818                                                 // IE returns -1 as default value
819                                                 if (v === -1)
820                                                         v = '';
821
822                                                 break;
823
824                                         case 'maxlength':
825                                         case 'tabindex':
826                                                 // IE returns default value
827                                                 if (v === 32768 || v === 2147483647 || v === '32768')
828                                                         v = '';
829
830                                                 break;
831
832                                         case 'multiple':
833                                         case 'compact':
834                                         case 'noshade':
835                                         case 'nowrap':
836                                                 if (v === 65535)
837                                                         return n;
838
839                                                 return dv;
840
841                                         case 'shape':
842                                                 v = v.toLowerCase();
843                                                 break;
844
845                                         default:
846                                                 // IE has odd anonymous function for event attributes
847                                                 if (n.indexOf('on') === 0 && v)
848                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
849                                 }
850                         }
851
852                         return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
853                 },
854
855                 /**
856                  * Returns the absolute x, y position of a node. The position will be returned in a object with x, y fields.
857                  *
858                  * @method getPos
859                  * @param {Element/String} n HTML element or element id to get x, y position from.
860                  * @param {Element} ro Optional root element to stop calculations at.
861                  * @return {object} Absolute position of the specified element object with x, y fields.
862                  */
863                 getPos : function(n, ro) {
864                         var t = this, x = 0, y = 0, e, d = t.doc, r;
865
866                         n = t.get(n);
867                         ro = ro || d.body;
868
869                         if (n) {
870                                 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
871                                 if (isIE && !t.stdMode) {
872                                         n = n.getBoundingClientRect();
873                                         e = t.boxModel ? d.documentElement : d.body;
874                                         x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
875                                         x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
876
877                                         return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
878                                 }
879
880                                 r = n;
881                                 while (r && r != ro && r.nodeType) {
882                                         x += r.offsetLeft || 0;
883                                         y += r.offsetTop || 0;
884                                         r = r.offsetParent;
885                                 }
886
887                                 r = n.parentNode;
888                                 while (r && r != ro && r.nodeType) {
889                                         x -= r.scrollLeft || 0;
890                                         y -= r.scrollTop || 0;
891                                         r = r.parentNode;
892                                 }
893                         }
894
895                         return {x : x, y : y};
896                 },
897
898                 /**
899                  * Parses the specified style value into an object collection. This parser will also
900                  * merge and remove any redundant items that browsers might have added. It will also convert non hex
901                  * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
902                  *
903                  * @method parseStyle
904                  * @param {String} st Style value to parse for example: border:1px solid red;.
905                  * @return {Object} Object representation of that style like {border : '1px solid red'}
906                  */
907                 parseStyle : function(st) {
908                         return this.styles.parse(st);
909                 },
910
911                 /**
912                  * Serializes the specified style object into a string.
913                  *
914                  * @method serializeStyle
915                  * @param {Object} o Object to serialize as string for example: {border : '1px solid red'}
916                  * @param {String} name Optional element name.
917                  * @return {String} String representation of the style object for example: border: 1px solid red.
918                  */
919                 serializeStyle : function(o, name) {
920                         return this.styles.serialize(o, name);
921                 },
922
923                 /**
924                  * Imports/loads the specified CSS file into the document bound to the class.
925                  *
926                  * @method loadCSS
927                  * @param {String} u URL to CSS file to load.
928                  * @example
929                  * // Loads a CSS file dynamically into the current document
930                  * tinymce.DOM.loadCSS('somepath/some.css');
931                  * 
932                  * // Loads a CSS file into the currently active editor instance
933                  * tinyMCE.activeEditor.dom.loadCSS('somepath/some.css');
934                  * 
935                  * // Loads a CSS file into an editor instance by id
936                  * tinyMCE.get('someid').dom.loadCSS('somepath/some.css');
937                  * 
938                  * // Loads multiple CSS files into the current document
939                  * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
940                  */
941                 loadCSS : function(u) {
942                         var t = this, d = t.doc, head;
943
944                         if (!u)
945                                 u = '';
946
947                         head = t.select('head')[0];
948
949                         each(u.split(','), function(u) {
950                                 var link;
951
952                                 if (t.files[u])
953                                         return;
954
955                                 t.files[u] = true;
956                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
957
958                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
959                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
960                                 // It's ugly but it seems to work fine.
961                                 if (isIE && d.documentMode && d.recalc) {
962                                         link.onload = function() {
963                                                 if (d.recalc)
964                                                         d.recalc();
965
966                                                 link.onload = null;
967                                         };
968                                 }
969
970                                 head.appendChild(link);
971                         });
972                 },
973
974                 /**
975                  * Adds a class to the specified element or elements.
976                  *
977                  * @method addClass
978                  * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
979                  * @param {String} c Class name to add to each element.
980                  * @return {String/Array} String with new class value or array with new class values for all elements.
981                  * @example
982                  * // Adds a class to all paragraphs in the active editor
983                  * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
984                  * 
985                  * // Adds a class to a specific element in the current page
986                  * tinyMCE.DOM.addClass('mydiv', 'myclass');
987                  */
988                 addClass : function(e, c) {
989                         return this.run(e, function(e) {
990                                 var o;
991
992                                 if (!c)
993                                         return 0;
994
995                                 if (this.hasClass(e, c))
996                                         return e.className;
997
998                                 o = this.removeClass(e, c);
999
1000                                 return e.className = (o != '' ? (o + ' ') : '') + c;
1001                         });
1002                 },
1003
1004                 /**
1005                  * Removes a class from the specified element or elements.
1006                  *
1007                  * @method removeClass
1008                  * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
1009                  * @param {String} c Class name to remove to each element.
1010                  * @return {String/Array} String with new class value or array with new class values for all elements.
1011                  * @example
1012                  * // Removes a class from all paragraphs in the active editor
1013                  * tinyMCE.activeEditor.dom.removeClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
1014                  * 
1015                  * // Removes a class from a specific element in the current page
1016                  * tinyMCE.DOM.removeClass('mydiv', 'myclass');
1017                  */
1018                 removeClass : function(e, c) {
1019                         var t = this, re;
1020
1021                         return t.run(e, function(e) {
1022                                 var v;
1023
1024                                 if (t.hasClass(e, c)) {
1025                                         if (!re)
1026                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
1027
1028                                         v = e.className.replace(re, ' ');
1029                                         v = tinymce.trim(v != ' ' ? v : '');
1030
1031                                         e.className = v;
1032
1033                                         // Empty class attr
1034                                         if (!v) {
1035                                                 e.removeAttribute('class');
1036                                                 e.removeAttribute('className');
1037                                         }
1038
1039                                         return v;
1040                                 }
1041
1042                                 return e.className;
1043                         });
1044                 },
1045
1046                 /**
1047                  * Returns true if the specified element has the specified class.
1048                  *
1049                  * @method hasClass
1050                  * @param {String/Element} n HTML element or element id string to check CSS class on.
1051                  * @param {String} c CSS class to check for.
1052                  * @return {Boolean} true/false if the specified element has the specified class.
1053                  */
1054                 hasClass : function(n, c) {
1055                         n = this.get(n);
1056
1057                         if (!n || !c)
1058                                 return false;
1059
1060                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
1061                 },
1062
1063                 /**
1064                  * Shows the specified element(s) by ID by setting the "display" style.
1065                  *
1066                  * @method show
1067                  * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to show.
1068                  */
1069                 show : function(e) {
1070                         return this.setStyle(e, 'display', 'block');
1071                 },
1072
1073                 /**
1074                  * Hides the specified element(s) by ID by setting the "display" style.
1075                  *
1076                  * @method hide
1077                  * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
1078                  * @example
1079                  * // Hides a element by id in the document
1080                  * tinymce.DOM.hide('myid');
1081                  */
1082                 hide : function(e) {
1083                         return this.setStyle(e, 'display', 'none');
1084                 },
1085
1086                 /**
1087                  * Returns true/false if the element is hidden or not by checking the "display" style.
1088                  *
1089                  * @method isHidden
1090                  * @param {String/Element} e Id or element to check display state on.
1091                  * @return {Boolean} true/false if the element is hidden or not.
1092                  */
1093                 isHidden : function(e) {
1094                         e = this.get(e);
1095
1096                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
1097                 },
1098
1099                 /**
1100                  * Returns a unique id. This can be useful when generating elements on the fly.
1101                  * This method will not check if the element allready exists.
1102                  *
1103                  * @method uniqueId
1104                  * @param {String} p Optional prefix to add infront of all ids defaults to "mce_".
1105                  * @return {String} Unique id.
1106                  */
1107                 uniqueId : function(p) {
1108                         return (!p ? 'mce_' : p) + (this.counter++);
1109                 },
1110
1111                 /**
1112                  * Sets the specified HTML content inside the element or elements. The HTML will first be processed this means
1113                  * URLs will get converted, hex color values fixed etc. Check processHTML for details.
1114                  *
1115                  * @method setHTML
1116                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside.
1117                  * @param {String} h HTML content to set as inner HTML of the element.
1118                  * @example
1119                  * // Sets the inner HTML of all paragraphs in the active editor
1120                  * tinyMCE.activeEditor.dom.setHTML(tinyMCE.activeEditor.dom.select('p'), 'some inner html');
1121                  * 
1122                  * // Sets the inner HTML of a element by id in the document
1123                  * tinyMCE.DOM.setHTML('mydiv', 'some inner html');
1124                  */
1125                 setHTML : function(element, html) {
1126                         var self = this;
1127
1128                         return self.run(element, function(element) {
1129                                 if (isIE) {
1130                                         // Remove all child nodes, IE keeps empty text nodes in DOM
1131                                         while (element.firstChild)
1132                                                 element.removeChild(element.firstChild);
1133
1134                                         try {
1135                                                 // IE will remove comments from the beginning
1136                                                 // unless you padd the contents with something
1137                                                 element.innerHTML = '<br />' + html;
1138                                                 element.removeChild(element.firstChild);
1139                                         } catch (ex) {
1140                                                 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
1141                                                 // This seems to fix this problem
1142
1143                                                 // Create new div with HTML contents and a BR infront to keep comments
1144                                                 element = self.create('div');
1145                                                 element.innerHTML = '<br />' + html;
1146
1147                                                 // Add all children from div to target
1148                                                 each (element.childNodes, function(node, i) {
1149                                                         // Skip br element
1150                                                         if (i)
1151                                                                 element.appendChild(node);
1152                                                 });
1153                                         }
1154                                 } else
1155                                         element.innerHTML = html;
1156
1157                                 return html;
1158                         });
1159                 },
1160
1161                 /**
1162                  * Returns the outer HTML of an element.
1163                  *
1164                  * @method getOuterHTML
1165                  * @param {String/Element} elm Element ID or element object to get outer HTML from.
1166                  * @return {String} Outer HTML string.
1167                  * @example
1168                  * tinymce.DOM.getOuterHTML(editorElement);
1169                  * tinyMCE.activeEditor.getOuterHTML(tinyMCE.activeEditor.getBody());
1170                  */
1171                 getOuterHTML : function(elm) {
1172                         var doc, self = this;
1173
1174                         elm = self.get(elm);
1175
1176                         if (!elm)
1177                                 return null;
1178
1179                         if (elm.nodeType === 1 && self.hasOuterHTML)
1180                                 return elm.outerHTML;
1181
1182                         doc = (elm.ownerDocument || self.doc).createElement("body");
1183                         doc.appendChild(elm.cloneNode(true));
1184
1185                         return doc.innerHTML;
1186                 },
1187
1188                 /**
1189                  * Sets the specified outer HTML on a element or elements.
1190                  *
1191                  * @method setOuterHTML
1192                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set outer HTML on.
1193                  * @param {Object} h HTML code to set as outer value for the element.
1194                  * @param {Document} d Optional document scope to use in this process defaults to the document of the DOM class.
1195                  * @example
1196                  * // Sets the outer HTML of all paragraphs in the active editor
1197                  * tinyMCE.activeEditor.dom.setOuterHTML(tinyMCE.activeEditor.dom.select('p'), '<div>some html</div>');
1198                  * 
1199                  * // Sets the outer HTML of a element by id in the document
1200                  * tinyMCE.DOM.setOuterHTML('mydiv', '<div>some html</div>');
1201                  */
1202                 setOuterHTML : function(e, h, d) {
1203                         var t = this;
1204
1205                         function setHTML(e, h, d) {
1206                                 var n, tp;
1207
1208                                 tp = d.createElement("body");
1209                                 tp.innerHTML = h;
1210
1211                                 n = tp.lastChild;
1212                                 while (n) {
1213                                         t.insertAfter(n.cloneNode(true), e);
1214                                         n = n.previousSibling;
1215                                 }
1216
1217                                 t.remove(e);
1218                         };
1219
1220                         return this.run(e, function(e) {
1221                                 e = t.get(e);
1222
1223                                 // Only set HTML on elements
1224                                 if (e.nodeType == 1) {
1225                                         d = d || e.ownerDocument || t.doc;
1226
1227                                         if (isIE) {
1228                                                 try {
1229                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
1230                                                         if (isIE && e.nodeType == 1)
1231                                                                 e.outerHTML = h;
1232                                                         else
1233                                                                 setHTML(e, h, d);
1234                                                 } catch (ex) {
1235                                                         // Fix for unknown runtime error
1236                                                         setHTML(e, h, d);
1237                                                 }
1238                                         } else
1239                                                 setHTML(e, h, d);
1240                                 }
1241                         });
1242                 },
1243
1244                 /**
1245                  * Entity decode a string, resolves any HTML entities like &aring;.
1246                  *
1247                  * @method decode
1248                  * @param {String} s String to decode entities on.
1249                  * @return {String} Entity decoded string.
1250                  */
1251                 decode : Entities.decode,
1252
1253                 /**
1254                  * Entity encodes a string, encodes the most common entities <>"& into entities.
1255                  *
1256                  * @method encode
1257                  * @param {String} text String to encode with entities.
1258                  * @return {String} Entity encoded string.
1259                  */
1260                 encode : Entities.encodeAllRaw,
1261
1262                 /**
1263                  * Inserts a element after the reference element.
1264                  *
1265                  * @method insertAfter
1266                  * @param {Element} node Element to insert after the reference.
1267                  * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
1268                  * @return {Element/Array} Element that got added or an array with elements. 
1269                  */
1270                 insertAfter : function(node, reference_node) {
1271                         reference_node = this.get(reference_node);
1272
1273                         return this.run(node, function(node) {
1274                                 var parent, nextSibling;
1275
1276                                 parent = reference_node.parentNode;
1277                                 nextSibling = reference_node.nextSibling;
1278
1279                                 if (nextSibling)
1280                                         parent.insertBefore(node, nextSibling);
1281                                 else
1282                                         parent.appendChild(node);
1283
1284                                 return node;
1285                         });
1286                 },
1287
1288                 /**
1289                  * Returns true/false if the specified element is a block element or not.
1290                  *
1291                  * @method isBlock
1292                  * @param {Node/String} node Element/Node to check.
1293                  * @return {Boolean} True/False state if the node is a block element or not.
1294                  */
1295                 isBlock : function(node) {
1296                         var type = node.nodeType;
1297
1298                         // If it's a node then check the type and use the nodeName
1299                         if (type)
1300                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
1301
1302                         return !!blockElementsMap[node];
1303                 },
1304
1305                 /**
1306                  * Replaces the specified element or elements with the specified element, the new element will
1307                  * be cloned if multiple inputs elements are passed.
1308                  *
1309                  * @method replace
1310                  * @param {Element} n New element to replace old ones with.
1311                  * @param {Element/String/Array} o Element DOM node, element id or array of elements or ids to replace.
1312                  * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
1313                  */
1314                 replace : function(n, o, k) {
1315                         var t = this;
1316
1317                         if (is(o, 'array'))
1318                                 n = n.cloneNode(true);
1319
1320                         return t.run(o, function(o) {
1321                                 if (k) {
1322                                         each(tinymce.grep(o.childNodes), function(c) {
1323                                                 n.appendChild(c);
1324                                         });
1325                                 }
1326
1327                                 return o.parentNode.replaceChild(n, o);
1328                         });
1329                 },
1330
1331                 /**
1332                  * Renames the specified element to a new name and keep it's attributes and children.
1333                  *
1334                  * @method rename
1335                  * @param {Element} elm Element to rename.
1336                  * @param {String} name Name of the new element.
1337                  * @return New element or the old element if it needed renaming.
1338                  */
1339                 rename : function(elm, name) {
1340                         var t = this, newElm;
1341
1342                         if (elm.nodeName != name.toUpperCase()) {
1343                                 // Rename block element
1344                                 newElm = t.create(name);
1345
1346                                 // Copy attribs to new block
1347                                 each(t.getAttribs(elm), function(attr_node) {
1348                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
1349                                 });
1350
1351                                 // Replace block
1352                                 t.replace(newElm, elm, 1);
1353                         }
1354
1355                         return newElm || elm;
1356                 },
1357
1358                 /**
1359                  * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
1360                  *
1361                  * @method findCommonAncestor
1362                  * @param {Element} a Element to find common ancestor of.
1363                  * @param {Element} b Element to find common ancestor of.
1364                  * @return {Element} Common ancestor element of the two input elements.
1365                  */
1366                 findCommonAncestor : function(a, b) {
1367                         var ps = a, pe;
1368
1369                         while (ps) {
1370                                 pe = b;
1371
1372                                 while (pe && ps != pe)
1373                                         pe = pe.parentNode;
1374
1375                                 if (ps == pe)
1376                                         break;
1377
1378                                 ps = ps.parentNode;
1379                         }
1380
1381                         if (!ps && a.ownerDocument)
1382                                 return a.ownerDocument.documentElement;
1383
1384                         return ps;
1385                 },
1386
1387                 /**
1388                  * Parses the specified RGB color value and returns a hex version of that color.
1389                  *
1390                  * @method toHex
1391                  * @param {String} s RGB string value like rgb(1,2,3)
1392                  * @return {String} Hex version of that RGB value like #FF00FF.
1393                  */
1394                 toHex : function(s) {
1395                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
1396
1397                         function hex(s) {
1398                                 s = parseInt(s).toString(16);
1399
1400                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
1401                         };
1402
1403                         if (c) {
1404                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
1405
1406                                 return s;
1407                         }
1408
1409                         return s;
1410                 },
1411
1412                 /**
1413                  * Returns a array of all single CSS classes in the document. A single CSS class is a simple
1414                  * rule like ".class" complex ones like "div td.class" will not be added to output.
1415                  *
1416                  * @method getClasses
1417                  * @return {Array} Array with class objects each object has a class field might be other fields in the future.
1418                  */
1419                 getClasses : function() {
1420                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
1421
1422                         if (t.classes)
1423                                 return t.classes;
1424
1425                         function addClasses(s) {
1426                                 // IE style imports
1427                                 each(s.imports, function(r) {
1428                                         addClasses(r);
1429                                 });
1430
1431                                 each(s.cssRules || s.rules, function(r) {
1432                                         // Real type or fake it on IE
1433                                         switch (r.type || 1) {
1434                                                 // Rule
1435                                                 case 1:
1436                                                         if (r.selectorText) {
1437                                                                 each(r.selectorText.split(','), function(v) {
1438                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
1439
1440                                                                         // Is internal or it doesn't contain a class
1441                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
1442                                                                                 return;
1443
1444                                                                         // Remove everything but class name
1445                                                                         ov = v;
1446                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
1447
1448                                                                         // Filter classes
1449                                                                         if (f && !(v = f(v, ov)))
1450                                                                                 return;
1451
1452                                                                         if (!lo[v]) {
1453                                                                                 cl.push({'class' : v});
1454                                                                                 lo[v] = 1;
1455                                                                         }
1456                                                                 });
1457                                                         }
1458                                                         break;
1459
1460                                                 // Import
1461                                                 case 3:
1462                                                         addClasses(r.styleSheet);
1463                                                         break;
1464                                         }
1465                                 });
1466                         };
1467
1468                         try {
1469                                 each(t.doc.styleSheets, addClasses);
1470                         } catch (ex) {
1471                                 // Ignore
1472                         }
1473
1474                         if (cl.length > 0)
1475                                 t.classes = cl;
1476
1477                         return cl;
1478                 },
1479
1480                 /**
1481                  * Executes the specified function on the element by id or dom element node or array of elements/id.
1482                  *
1483                  * @method run
1484                  * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
1485                  * @param {function} f Function to execute for each item.
1486                  * @param {Object} s Optional scope to execute the function in.
1487                  * @return {Object/Array} Single object or array with objects depending on multiple input or not.
1488                  */
1489                 run : function(e, f, s) {
1490                         var t = this, o;
1491
1492                         if (t.doc && typeof(e) === 'string')
1493                                 e = t.get(e);
1494
1495                         if (!e)
1496                                 return false;
1497
1498                         s = s || this;
1499                         if (!e.nodeType && (e.length || e.length === 0)) {
1500                                 o = [];
1501
1502                                 each(e, function(e, i) {
1503                                         if (e) {
1504                                                 if (typeof(e) == 'string')
1505                                                         e = t.doc.getElementById(e);
1506
1507                                                 o.push(f.call(s, e, i));
1508                                         }
1509                                 });
1510
1511                                 return o;
1512                         }
1513
1514                         return f.call(s, e);
1515                 },
1516
1517                 /**
1518                  * Returns an NodeList with attributes for the element.
1519                  *
1520                  * @method getAttribs
1521                  * @param {HTMLElement/string} n Element node or string id to get attributes from.
1522                  * @return {NodeList} NodeList with attributes.
1523                  */
1524                 getAttribs : function(n) {
1525                         var o;
1526
1527                         n = this.get(n);
1528
1529                         if (!n)
1530                                 return [];
1531
1532                         if (isIE) {
1533                                 o = [];
1534
1535                                 // Object will throw exception in IE
1536                                 if (n.nodeName == 'OBJECT')
1537                                         return n.attributes;
1538
1539                                 // IE doesn't keep the selected attribute if you clone option elements
1540                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
1541                                         o.push({specified : 1, nodeName : 'selected'});
1542
1543                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
1544                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
1545                                         o.push({specified : 1, nodeName : a});
1546                                 });
1547
1548                                 return o;
1549                         }
1550
1551                         return n.attributes;
1552                 },
1553
1554                 /**
1555                  * Returns true/false if the specified node is to be considered empty or not.
1556                  *
1557                  * @example
1558                  * tinymce.DOM.isEmpty(node, {img : true});
1559                  * @method isEmpty
1560                  * @param {Object} elements Optional name/value object with elements that are automatically treated as non empty elements.
1561                  * @return {Boolean} true/false if the node is empty or not.
1562                  */
1563                 isEmpty : function(node, elements) {
1564                         var self = this, i, attributes, type, walker, name;
1565
1566                         node = node.firstChild;
1567                         if (node) {
1568                                 walker = new tinymce.dom.TreeWalker(node);
1569                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
1570
1571                                 do {
1572                                         type = node.nodeType;
1573
1574                                         if (type === 1) {
1575                                                 // Ignore bogus elements
1576                                                 if (node.getAttribute('data-mce-bogus'))
1577                                                         continue;
1578
1579                                                 // Keep empty elements like <img />
1580                                                 if (elements && elements[node.nodeName.toLowerCase()])
1581                                                         return false;
1582
1583                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
1584                                                 attributes = self.getAttribs(node);
1585                                                 i = node.attributes.length;
1586                                                 while (i--) {
1587                                                         name = node.attributes[i].nodeName;
1588                                                         if (name === "name" || name.indexOf('data-') === 0)
1589                                                                 return false;
1590                                                 }
1591                                         }
1592
1593                                         // Keep non whitespace text nodes
1594                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
1595                                                 return false;
1596                                 } while (node = walker.next());
1597                         }
1598
1599                         return true;
1600                 },
1601
1602                 /**
1603                  * Destroys all internal references to the DOM to solve IE leak issues.
1604                  *
1605                  * @method destroy
1606                  */
1607                 destroy : function(s) {
1608                         var t = this;
1609
1610                         if (t.events)
1611                                 t.events.destroy();
1612
1613                         t.win = t.doc = t.root = t.events = null;
1614
1615                         // Manual destroy then remove unload handler
1616                         if (!s)
1617                                 tinymce.removeUnload(t.destroy);
1618                 },
1619
1620                 /**
1621                  * Created a new DOM Range object. This will use the native DOM Range API if it's
1622                  * available if it's not it will fallback to the custom TinyMCE implementation.
1623                  *
1624                  * @method createRng
1625                  * @return {DOMRange} DOM Range object.
1626                  * @example
1627                  * var rng = tinymce.DOM.createRng();
1628                  * alert(rng.startContainer + "," + rng.startOffset);
1629                  */
1630                 createRng : function() {
1631                         var d = this.doc;
1632
1633                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
1634                 },
1635
1636                 /**
1637                  * Returns the index of the specified node within it's parent.
1638                  *
1639                  * @param {Node} node Node to look for.
1640                  * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
1641                  * @return {Number} Index of the specified node.
1642                  */
1643                 nodeIndex : function(node, normalized) {
1644                         var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
1645
1646                         if (node) {
1647                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
1648                                         nodeType = node.nodeType;
1649
1650                                         // Normalize text nodes
1651                                         if (normalized && nodeType == 3) {
1652                                                 // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
1653                                                 // (the nodeValue attribute will not exist, and will error here).
1654                                                 nodeValueExists = false;
1655                                                 try {nodeValueExists = node.nodeValue.length} catch (c) {}
1656                                                 if (nodeType == lastNodeType || !nodeValueExists)
1657                                                         continue;
1658                                         }
1659                                         idx++;
1660                                         lastNodeType = nodeType;
1661                                 }
1662                         }
1663
1664                         return idx;
1665                 },
1666
1667                 /**
1668                  * Splits an element into two new elements and places the specified split
1669                  * element or element between the new ones. For example splitting the paragraph at the bold element in
1670                  * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>. 
1671                  *
1672                  * @method split
1673                  * @param {Element} pe Parent element to split.
1674                  * @param {Element} e Element to split at.
1675                  * @param {Element} re Optional replacement element to replace the split element by.
1676                  * @return {Element} Returns the split element or the replacement element if that is specified.
1677                  */
1678                 split : function(pe, e, re) {
1679                         var t = this, r = t.createRng(), bef, aft, pa;
1680
1681                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
1682                         // but we don't want that in our code since it serves no purpose for the end user
1683                         // For example if this is chopped:
1684                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
1685                         // would produce:
1686                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
1687                         // this function will then trim of empty edges and produce:
1688                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
1689                         function trim(node) {
1690                                 var i, children = node.childNodes, type = node.nodeType;
1691
1692                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
1693                                         return;
1694
1695                                 for (i = children.length - 1; i >= 0; i--)
1696                                         trim(children[i]);
1697
1698                                 if (type != 9) {
1699                                         // Keep non whitespace text nodes
1700                                         if (type == 3 && node.nodeValue.length > 0) {
1701                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
1702                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
1703                                                         return;
1704                                         } else if (type == 1) {
1705                                                 // If the only child is a bookmark then move it up
1706                                                 children = node.childNodes;
1707                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
1708                                                         node.parentNode.insertBefore(children[0], node);
1709
1710                                                 // Keep non empty elements or img, hr etc
1711                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
1712                                                         return;
1713                                         }
1714
1715                                         t.remove(node);
1716                                 }
1717
1718                                 return node;
1719                         };
1720
1721                         if (pe && e) {
1722                                 // Get before chunk
1723                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
1724                                 r.setEnd(e.parentNode, t.nodeIndex(e));
1725                                 bef = r.extractContents();
1726
1727                                 // Get after chunk
1728                                 r = t.createRng();
1729                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
1730                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
1731                                 aft = r.extractContents();
1732
1733                                 // Insert before chunk
1734                                 pa = pe.parentNode;
1735                                 pa.insertBefore(trim(bef), pe);
1736
1737                                 // Insert middle chunk
1738                                 if (re)
1739                                         pa.replaceChild(re, e);
1740                                 else
1741                                         pa.insertBefore(e, pe);
1742
1743                                 // Insert after chunk
1744                                 pa.insertBefore(trim(aft), pe);
1745                                 t.remove(pe);
1746
1747                                 return re || e;
1748                         }
1749                 },
1750
1751                 /**
1752                  * Adds an event handler to the specified object.
1753                  *
1754                  * @method bind
1755                  * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents.
1756                  * @param {String} n Name of event handler to add for example: click.
1757                  * @param {function} f Function to execute when the event occurs.
1758                  * @param {Object} s Optional scope to execute the function in.
1759                  * @return {function} Function callback handler the same as the one passed in.
1760                  */
1761                 bind : function(target, name, func, scope) {
1762                         var t = this;
1763
1764                         if (!t.events)
1765                                 t.events = new tinymce.dom.EventUtils();
1766
1767                         return t.events.add(target, name, func, scope || this);
1768                 },
1769
1770                 /**
1771                  * Removes the specified event handler by name and function from a element or collection of elements.
1772                  *
1773                  * @method unbind
1774                  * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from.
1775                  * @param {String} n Event handler name like for example: "click"
1776                  * @param {function} f Function to remove.
1777                  * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in.
1778                  */
1779                 unbind : function(target, name, func) {
1780                         var t = this;
1781
1782                         if (!t.events)
1783                                 t.events = new tinymce.dom.EventUtils();
1784
1785                         return t.events.remove(target, name, func);
1786                 },
1787
1788                 // #ifdef debug
1789
1790                 dumpRng : function(r) {
1791                         return 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset;
1792                 },
1793
1794                 // #endif
1795
1796                 _findSib : function(node, selector, name) {
1797                         var t = this, f = selector;
1798
1799                         if (node) {
1800                                 // If expression make a function of it using is
1801                                 if (is(f, 'string')) {
1802                                         f = function(node) {
1803                                                 return t.is(node, selector);
1804                                         };
1805                                 }
1806
1807                                 // Loop all siblings
1808                                 for (node = node[name]; node; node = node[name]) {
1809                                         if (f(node))
1810                                                 return node;
1811                                 }
1812                         }
1813
1814                         return null;
1815                 },
1816
1817                 _isRes : function(c) {
1818                         // Is live resizble element
1819                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
1820                 }
1821
1822                 /*
1823                 walk : function(n, f, s) {
1824                         var d = this.doc, w;
1825
1826                         if (d.createTreeWalker) {
1827                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
1828
1829                                 while ((n = w.nextNode()) != null)
1830                                         f.call(s || this, n);
1831                         } else
1832                                 tinymce.walk(n, f, 'childNodes', s);
1833                 }
1834                 */
1835
1836                 /*
1837                 toRGB : function(s) {
1838                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
1839
1840                         if (c) {
1841                                 // #FFF -> #FFFFFF
1842                                 if (!is(c[3]))
1843                                         c[3] = c[2] = c[1];
1844
1845                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
1846                         }
1847
1848                         return s;
1849                 }
1850                 */
1851         });
1852
1853         /**
1854          * Instance of DOMUtils for the current document.
1855          *
1856          * @property DOM
1857          * @member tinymce
1858          * @type tinymce.dom.DOMUtils
1859          * @example
1860          * // Example of how to add a class to some element by id
1861          * tinymce.DOM.addClass('someid', 'someclass');
1862          */
1863         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
1864 })(tinymce);