]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/tiny_mce_prototype_src.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / tiny_mce_prototype_src.js
1 (function(win) {
2         var whiteSpaceRe = /^\s*|\s*$/g,
3                 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
4
5         var tinymce = {
6                 majorVersion : '3',
7
8                 minorVersion : '4.2',
9
10                 releaseDate : '2011-04-07',
11
12                 _init : function() {
13                         var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
14
15                         t.isOpera = win.opera && opera.buildNumber;
16
17                         t.isWebKit = /WebKit/.test(ua);
18
19                         t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
20
21                         t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
22
23                         t.isGecko = !t.isWebKit && /Gecko/.test(ua);
24
25                         t.isMac = ua.indexOf('Mac') != -1;
26
27                         t.isAir = /adobeair/i.test(ua);
28
29                         t.isIDevice = /(iPad|iPhone)/.test(ua);
30
31                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE
32                         if (win.tinyMCEPreInit) {
33                                 t.suffix = tinyMCEPreInit.suffix;
34                                 t.baseURL = tinyMCEPreInit.base;
35                                 t.query = tinyMCEPreInit.query;
36                                 return;
37                         }
38
39                         // Get suffix and base
40                         t.suffix = '';
41
42                         // If base element found, add that infront of baseURL
43                         nl = d.getElementsByTagName('base');
44                         for (i=0; i<nl.length; i++) {
45                                 if (v = nl[i].href) {
46                                         // Host only value like http://site.com or http://site.com:8008
47                                         if (/^https?:\/\/[^\/]+$/.test(v))
48                                                 v += '/';
49
50                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory
51                                 }
52                         }
53
54                         function getBase(n) {
55                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
56                                         if (/_(src|dev)\.js/g.test(n.src))
57                                                 t.suffix = '_src';
58
59                                         if ((p = n.src.indexOf('?')) != -1)
60                                                 t.query = n.src.substring(p + 1);
61
62                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
63
64                                         // If path to script is relative and a base href was found add that one infront
65                                         // the src property will always be an absolute one on non IE browsers and IE 8
66                                         // so this logic will basically only be executed on older IE versions
67                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
68                                                 t.baseURL = base + t.baseURL;
69
70                                         return t.baseURL;
71                                 }
72
73                                 return null;
74                         };
75
76                         // Check document
77                         nl = d.getElementsByTagName('script');
78                         for (i=0; i<nl.length; i++) {
79                                 if (getBase(nl[i]))
80                                         return;
81                         }
82
83                         // Check head
84                         n = d.getElementsByTagName('head')[0];
85                         if (n) {
86                                 nl = n.getElementsByTagName('script');
87                                 for (i=0; i<nl.length; i++) {
88                                         if (getBase(nl[i]))
89                                                 return;
90                                 }
91                         }
92
93                         return;
94                 },
95
96                 is : function(o, t) {
97                         if (!t)
98                                 return o !== undefined;
99
100                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
101                                 return true;
102
103                         return typeof(o) == t;
104                 },
105
106                 makeMap : function(items, delim, map) {
107                         var i;
108
109                         items = items || [];
110                         delim = delim || ',';
111
112                         if (typeof(items) == "string")
113                                 items = items.split(delim);
114
115                         map = map || {};
116
117                         i = items.length;
118                         while (i--)
119                                 map[items[i]] = {};
120
121                         return map;
122                 },
123
124                 each : function(o, cb, s) {
125                         var n, l;
126
127                         if (!o)
128                                 return 0;
129
130                         s = s || o;
131
132                         if (o.length !== undefined) {
133                                 // Indexed arrays, needed for Safari
134                                 for (n=0, l = o.length; n < l; n++) {
135                                         if (cb.call(s, o[n], n, o) === false)
136                                                 return 0;
137                                 }
138                         } else {
139                                 // Hashtables
140                                 for (n in o) {
141                                         if (o.hasOwnProperty(n)) {
142                                                 if (cb.call(s, o[n], n, o) === false)
143                                                         return 0;
144                                         }
145                                 }
146                         }
147
148                         return 1;
149                 },
150
151
152                 map : function(a, f) {
153                         var o = [];
154
155                         tinymce.each(a, function(v) {
156                                 o.push(f(v));
157                         });
158
159                         return o;
160                 },
161
162                 grep : function(a, f) {
163                         var o = [];
164
165                         tinymce.each(a, function(v) {
166                                 if (!f || f(v))
167                                         o.push(v);
168                         });
169
170                         return o;
171                 },
172
173                 inArray : function(a, v) {
174                         var i, l;
175
176                         if (a) {
177                                 for (i = 0, l = a.length; i < l; i++) {
178                                         if (a[i] === v)
179                                                 return i;
180                                 }
181                         }
182
183                         return -1;
184                 },
185
186                 extend : function(o, e) {
187                         var i, l, a = arguments;
188
189                         for (i = 1, l = a.length; i < l; i++) {
190                                 e = a[i];
191
192                                 tinymce.each(e, function(v, n) {
193                                         if (v !== undefined)
194                                                 o[n] = v;
195                                 });
196                         }
197
198                         return o;
199                 },
200
201
202                 trim : function(s) {
203                         return (s ? '' + s : '').replace(whiteSpaceRe, '');
204                 },
205
206                 create : function(s, p, root) {
207                         var t = this, sp, ns, cn, scn, c, de = 0;
208
209                         // Parse : <prefix> <class>:<super class>
210                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
211                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
212
213                         // Create namespace for new class
214                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
215
216                         // Class already exists
217                         if (ns[cn])
218                                 return;
219
220                         // Make pure static class
221                         if (s[2] == 'static') {
222                                 ns[cn] = p;
223
224                                 if (this.onCreate)
225                                         this.onCreate(s[2], s[3], ns[cn]);
226
227                                 return;
228                         }
229
230                         // Create default constructor
231                         if (!p[cn]) {
232                                 p[cn] = function() {};
233                                 de = 1;
234                         }
235
236                         // Add constructor and methods
237                         ns[cn] = p[cn];
238                         t.extend(ns[cn].prototype, p);
239
240                         // Extend
241                         if (s[5]) {
242                                 sp = t.resolve(s[5]).prototype;
243                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
244
245                                 // Extend constructor
246                                 c = ns[cn];
247                                 if (de) {
248                                         // Add passthrough constructor
249                                         ns[cn] = function() {
250                                                 return sp[scn].apply(this, arguments);
251                                         };
252                                 } else {
253                                         // Add inherit constructor
254                                         ns[cn] = function() {
255                                                 this.parent = sp[scn];
256                                                 return c.apply(this, arguments);
257                                         };
258                                 }
259                                 ns[cn].prototype[cn] = ns[cn];
260
261                                 // Add super methods
262                                 t.each(sp, function(f, n) {
263                                         ns[cn].prototype[n] = sp[n];
264                                 });
265
266                                 // Add overridden methods
267                                 t.each(p, function(f, n) {
268                                         // Extend methods if needed
269                                         if (sp[n]) {
270                                                 ns[cn].prototype[n] = function() {
271                                                         this.parent = sp[n];
272                                                         return f.apply(this, arguments);
273                                                 };
274                                         } else {
275                                                 if (n != cn)
276                                                         ns[cn].prototype[n] = f;
277                                         }
278                                 });
279                         }
280
281                         // Add static methods
282                         t.each(p['static'], function(f, n) {
283                                 ns[cn][n] = f;
284                         });
285
286                         if (this.onCreate)
287                                 this.onCreate(s[2], s[3], ns[cn].prototype);
288                 },
289
290                 walk : function(o, f, n, s) {
291                         s = s || this;
292
293                         if (o) {
294                                 if (n)
295                                         o = o[n];
296
297                                 tinymce.each(o, function(o, i) {
298                                         if (f.call(s, o, i, n) === false)
299                                                 return false;
300
301                                         tinymce.walk(o, f, n, s);
302                                 });
303                         }
304                 },
305
306                 createNS : function(n, o) {
307                         var i, v;
308
309                         o = o || win;
310
311                         n = n.split('.');
312                         for (i=0; i<n.length; i++) {
313                                 v = n[i];
314
315                                 if (!o[v])
316                                         o[v] = {};
317
318                                 o = o[v];
319                         }
320
321                         return o;
322                 },
323
324                 resolve : function(n, o) {
325                         var i, l;
326
327                         o = o || win;
328
329                         n = n.split('.');
330                         for (i = 0, l = n.length; i < l; i++) {
331                                 o = o[n[i]];
332
333                                 if (!o)
334                                         break;
335                         }
336
337                         return o;
338                 },
339
340                 addUnload : function(f, s) {
341                         var t = this;
342
343                         f = {func : f, scope : s || this};
344
345                         if (!t.unloads) {
346                                 function unload() {
347                                         var li = t.unloads, o, n;
348
349                                         if (li) {
350                                                 // Call unload handlers
351                                                 for (n in li) {
352                                                         o = li[n];
353
354                                                         if (o && o.func)
355                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
356                                                 }
357
358                                                 // Detach unload function
359                                                 if (win.detachEvent) {
360                                                         win.detachEvent('onbeforeunload', fakeUnload);
361                                                         win.detachEvent('onunload', unload);
362                                                 } else if (win.removeEventListener)
363                                                         win.removeEventListener('unload', unload, false);
364
365                                                 // Destroy references
366                                                 t.unloads = o = li = w = unload = 0;
367
368                                                 // Run garbarge collector on IE
369                                                 if (win.CollectGarbage)
370                                                         CollectGarbage();
371                                         }
372                                 };
373
374                                 function fakeUnload() {
375                                         var d = document;
376
377                                         // Is there things still loading, then do some magic
378                                         if (d.readyState == 'interactive') {
379                                                 function stop() {
380                                                         // Prevent memory leak
381                                                         d.detachEvent('onstop', stop);
382
383                                                         // Call unload handler
384                                                         if (unload)
385                                                                 unload();
386
387                                                         d = 0;
388                                                 };
389
390                                                 // Fire unload when the currently loading page is stopped
391                                                 if (d)
392                                                         d.attachEvent('onstop', stop);
393
394                                                 // Remove onstop listener after a while to prevent the unload function
395                                                 // to execute if the user presses cancel in an onbeforeunload
396                                                 // confirm dialog and then presses the browser stop button
397                                                 win.setTimeout(function() {
398                                                         if (d)
399                                                                 d.detachEvent('onstop', stop);
400                                                 }, 0);
401                                         }
402                                 };
403
404                                 // Attach unload handler
405                                 if (win.attachEvent) {
406                                         win.attachEvent('onunload', unload);
407                                         win.attachEvent('onbeforeunload', fakeUnload);
408                                 } else if (win.addEventListener)
409                                         win.addEventListener('unload', unload, false);
410
411                                 // Setup initial unload handler array
412                                 t.unloads = [f];
413                         } else
414                                 t.unloads.push(f);
415
416                         return f;
417                 },
418
419                 removeUnload : function(f) {
420                         var u = this.unloads, r = null;
421
422                         tinymce.each(u, function(o, i) {
423                                 if (o && o.func == f) {
424                                         u.splice(i, 1);
425                                         r = f;
426                                         return false;
427                                 }
428                         });
429
430                         return r;
431                 },
432
433                 explode : function(s, d) {
434                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
435                 },
436
437                 _addVer : function(u) {
438                         var v;
439
440                         if (!this.query)
441                                 return u;
442
443                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
444
445                         if (u.indexOf('#') == -1)
446                                 return u + v;
447
448                         return u.replace('#', v + '#');
449                 },
450
451                 // Fix function for IE 9 where regexps isn't working correctly
452                 // Todo: remove me once MS fixes the bug
453                 _replace : function(find, replace, str) {
454                         // On IE9 we have to fake $x replacement
455                         if (isRegExpBroken) {
456                                 return str.replace(find, function() {
457                                         var val = replace, args = arguments, i;
458
459                                         for (i = 0; i < args.length - 2; i++) {
460                                                 if (args[i] === undefined) {
461                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');
462                                                 } else {
463                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
464                                                 }
465                                         }
466
467                                         return val;
468                                 });
469                         }
470
471                         return str.replace(find, replace);
472                 }
473
474                 };
475
476         // Initialize the API
477         tinymce._init();
478
479         // Expose tinymce namespace to the global namespace (window)
480         win.tinymce = win.tinyMCE = tinymce;
481
482         // Describe the different namespaces
483
484         })(window);
485
486
487 (function() {
488         if (!window.Prototype)
489                 return alert("Load prototype first!");
490
491         // Patch in core NS functions
492         tinymce.extend(tinymce, {
493                 trim : function(s) {return s ? s.strip() : '';},
494                 inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;}
495         });
496
497         // Patch in functions in various clases
498         // Add a "#ifndefjquery" statement around each core API function you add below
499         var patches = {
500                 'tinymce.util.JSON' : {
501                         /*serialize : function(o) {
502                                 return o.toJSON();
503                         }*/
504                 },
505         };
506
507         // Patch functions after a class is created
508         tinymce.onCreate = function(ty, c, p) {
509                 tinymce.extend(p, patches[c]);
510         };
511 })();
512
513
514 tinymce.create('tinymce.util.Dispatcher', {
515         scope : null,
516         listeners : null,
517
518         Dispatcher : function(s) {
519                 this.scope = s || this;
520                 this.listeners = [];
521         },
522
523         add : function(cb, s) {
524                 this.listeners.push({cb : cb, scope : s || this.scope});
525
526                 return cb;
527         },
528
529         addToTop : function(cb, s) {
530                 this.listeners.unshift({cb : cb, scope : s || this.scope});
531
532                 return cb;
533         },
534
535         remove : function(cb) {
536                 var l = this.listeners, o = null;
537
538                 tinymce.each(l, function(c, i) {
539                         if (cb == c.cb) {
540                                 o = cb;
541                                 l.splice(i, 1);
542                                 return false;
543                         }
544                 });
545
546                 return o;
547         },
548
549         dispatch : function() {
550                 var s, a = arguments, i, li = this.listeners, c;
551
552                 // Needs to be a real loop since the listener count might change while looping
553                 // And this is also more efficient
554                 for (i = 0; i<li.length; i++) {
555                         c = li[i];
556                         s = c.cb.apply(c.scope, a);
557
558                         if (s === false)
559                                 break;
560                 }
561
562                 return s;
563         }
564
565         });
566
567 (function() {
568         var each = tinymce.each;
569
570         tinymce.create('tinymce.util.URI', {
571                 URI : function(u, s) {
572                         var t = this, o, a, b;
573
574                         // Trim whitespace
575                         u = tinymce.trim(u);
576
577                         // Default settings
578                         s = t.settings = s || {};
579
580                         // Strange app protocol or local anchor
581                         if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
582                                 t.source = u;
583                                 return;
584                         }
585
586                         // Absolute path with no host, fake host and protocol
587                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
588                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
589
590                         // Relative path http:// or protocol relative //path
591                         if (!/^\w*:?\/\//.test(u))
592                                 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
593
594                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
595                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
596                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
597                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
598                                 var s = u[i];
599
600                                 // Zope 3 workaround, they use @@something
601                                 if (s)
602                                         s = s.replace(/\(mce_at\)/g, '@@');
603
604                                 t[v] = s;
605                         });
606
607                         if (b = s.base_uri) {
608                                 if (!t.protocol)
609                                         t.protocol = b.protocol;
610
611                                 if (!t.userInfo)
612                                         t.userInfo = b.userInfo;
613
614                                 if (!t.port && t.host == 'mce_host')
615                                         t.port = b.port;
616
617                                 if (!t.host || t.host == 'mce_host')
618                                         t.host = b.host;
619
620                                 t.source = '';
621                         }
622
623                         //t.path = t.path || '/';
624                 },
625
626                 setPath : function(p) {
627                         var t = this;
628
629                         p = /^(.*?)\/?(\w+)?$/.exec(p);
630
631                         // Update path parts
632                         t.path = p[0];
633                         t.directory = p[1];
634                         t.file = p[2];
635
636                         // Rebuild source
637                         t.source = '';
638                         t.getURI();
639                 },
640
641                 toRelative : function(u) {
642                         var t = this, o;
643
644                         if (u === "./")
645                                 return u;
646
647                         u = new tinymce.util.URI(u, {base_uri : t});
648
649                         // Not on same domain/port or protocol
650                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
651                                 return u.getURI();
652
653                         o = t.toRelPath(t.path, u.path);
654
655                         // Add query
656                         if (u.query)
657                                 o += '?' + u.query;
658
659                         // Add anchor
660                         if (u.anchor)
661                                 o += '#' + u.anchor;
662
663                         return o;
664                 },
665         
666                 toAbsolute : function(u, nh) {
667                         var u = new tinymce.util.URI(u, {base_uri : this});
668
669                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
670                 },
671
672                 toRelPath : function(base, path) {
673                         var items, bp = 0, out = '', i, l;
674
675                         // Split the paths
676                         base = base.substring(0, base.lastIndexOf('/'));
677                         base = base.split('/');
678                         items = path.split('/');
679
680                         if (base.length >= items.length) {
681                                 for (i = 0, l = base.length; i < l; i++) {
682                                         if (i >= items.length || base[i] != items[i]) {
683                                                 bp = i + 1;
684                                                 break;
685                                         }
686                                 }
687                         }
688
689                         if (base.length < items.length) {
690                                 for (i = 0, l = items.length; i < l; i++) {
691                                         if (i >= base.length || base[i] != items[i]) {
692                                                 bp = i + 1;
693                                                 break;
694                                         }
695                                 }
696                         }
697
698                         if (bp == 1)
699                                 return path;
700
701                         for (i = 0, l = base.length - (bp - 1); i < l; i++)
702                                 out += "../";
703
704                         for (i = bp - 1, l = items.length; i < l; i++) {
705                                 if (i != bp - 1)
706                                         out += "/" + items[i];
707                                 else
708                                         out += items[i];
709                         }
710
711                         return out;
712                 },
713
714                 toAbsPath : function(base, path) {
715                         var i, nb = 0, o = [], tr, outPath;
716
717                         // Split paths
718                         tr = /\/$/.test(path) ? '/' : '';
719                         base = base.split('/');
720                         path = path.split('/');
721
722                         // Remove empty chunks
723                         each(base, function(k) {
724                                 if (k)
725                                         o.push(k);
726                         });
727
728                         base = o;
729
730                         // Merge relURLParts chunks
731                         for (i = path.length - 1, o = []; i >= 0; i--) {
732                                 // Ignore empty or .
733                                 if (path[i].length == 0 || path[i] == ".")
734                                         continue;
735
736                                 // Is parent
737                                 if (path[i] == '..') {
738                                         nb++;
739                                         continue;
740                                 }
741
742                                 // Move up
743                                 if (nb > 0) {
744                                         nb--;
745                                         continue;
746                                 }
747
748                                 o.push(path[i]);
749                         }
750
751                         i = base.length - nb;
752
753                         // If /a/b/c or /
754                         if (i <= 0)
755                                 outPath = o.reverse().join('/');
756                         else
757                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
758
759                         // Add front / if it's needed
760                         if (outPath.indexOf('/') !== 0)
761                                 outPath = '/' + outPath;
762
763                         // Add traling / if it's needed
764                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
765                                 outPath += tr;
766
767                         return outPath;
768                 },
769
770                 getURI : function(nh) {
771                         var s, t = this;
772
773                         // Rebuild source
774                         if (!t.source || nh) {
775                                 s = '';
776
777                                 if (!nh) {
778                                         if (t.protocol)
779                                                 s += t.protocol + '://';
780
781                                         if (t.userInfo)
782                                                 s += t.userInfo + '@';
783
784                                         if (t.host)
785                                                 s += t.host;
786
787                                         if (t.port)
788                                                 s += ':' + t.port;
789                                 }
790
791                                 if (t.path)
792                                         s += t.path;
793
794                                 if (t.query)
795                                         s += '?' + t.query;
796
797                                 if (t.anchor)
798                                         s += '#' + t.anchor;
799
800                                 t.source = s;
801                         }
802
803                         return t.source;
804                 }
805         });
806 })();
807
808 (function() {
809         var each = tinymce.each;
810
811         tinymce.create('static tinymce.util.Cookie', {
812                 getHash : function(n) {
813                         var v = this.get(n), h;
814
815                         if (v) {
816                                 each(v.split('&'), function(v) {
817                                         v = v.split('=');
818                                         h = h || {};
819                                         h[unescape(v[0])] = unescape(v[1]);
820                                 });
821                         }
822
823                         return h;
824                 },
825
826                 setHash : function(n, v, e, p, d, s) {
827                         var o = '';
828
829                         each(v, function(v, k) {
830                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
831                         });
832
833                         this.set(n, o, e, p, d, s);
834                 },
835
836                 get : function(n) {
837                         var c = document.cookie, e, p = n + "=", b;
838
839                         // Strict mode
840                         if (!c)
841                                 return;
842
843                         b = c.indexOf("; " + p);
844
845                         if (b == -1) {
846                                 b = c.indexOf(p);
847
848                                 if (b != 0)
849                                         return null;
850                         } else
851                                 b += 2;
852
853                         e = c.indexOf(";", b);
854
855                         if (e == -1)
856                                 e = c.length;
857
858                         return unescape(c.substring(b + p.length, e));
859                 },
860
861                 set : function(n, v, e, p, d, s) {
862                         document.cookie = n + "=" + escape(v) +
863                                 ((e) ? "; expires=" + e.toGMTString() : "") +
864                                 ((p) ? "; path=" + escape(p) : "") +
865                                 ((d) ? "; domain=" + d : "") +
866                                 ((s) ? "; secure" : "");
867                 },
868
869                 remove : function(n, p) {
870                         var d = new Date();
871
872                         d.setTime(d.getTime() - 1000);
873
874                         this.set(n, '', d, p, d);
875                 }
876         });
877 })();
878
879 (function() {
880         function serialize(o, quote) {
881                 var i, v, t;
882
883                 quote = quote || '"';
884
885                 if (o == null)
886                         return 'null';
887
888                 t = typeof o;
889
890                 if (t == 'string') {
891                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
892
893                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
894                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
895                                 if (quote === '"' && a === "'")
896                                         return a;
897
898                                 i = v.indexOf(b);
899
900                                 if (i + 1)
901                                         return '\\' + v.charAt(i + 1);
902
903                                 a = b.charCodeAt().toString(16);
904
905                                 return '\\u' + '0000'.substring(a.length) + a;
906                         }) + quote;
907                 }
908
909                 if (t == 'object') {
910                         if (o.hasOwnProperty && o instanceof Array) {
911                                         for (i=0, v = '['; i<o.length; i++)
912                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
913
914                                         return v + ']';
915                                 }
916
917                                 v = '{';
918
919                                 for (i in o)
920                                         v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
921
922                                 return v + '}';
923                 }
924
925                 return '' + o;
926         };
927
928         tinymce.util.JSON = {
929                 serialize: serialize,
930
931                 parse: function(s) {
932                         try {
933                                 return eval('(' + s + ')');
934                         } catch (ex) {
935                                 // Ignore
936                         }
937                 }
938
939                 };
940 })();
941 tinymce.create('static tinymce.util.XHR', {
942         send : function(o) {
943                 var x, t, w = window, c = 0;
944
945                 // Default settings
946                 o.scope = o.scope || this;
947                 o.success_scope = o.success_scope || o.scope;
948                 o.error_scope = o.error_scope || o.scope;
949                 o.async = o.async === false ? false : true;
950                 o.data = o.data || '';
951
952                 function get(s) {
953                         x = 0;
954
955                         try {
956                                 x = new ActiveXObject(s);
957                         } catch (ex) {
958                         }
959
960                         return x;
961                 };
962
963                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
964
965                 if (x) {
966                         if (x.overrideMimeType)
967                                 x.overrideMimeType(o.content_type);
968
969                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
970
971                         if (o.content_type)
972                                 x.setRequestHeader('Content-Type', o.content_type);
973
974                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
975
976                         x.send(o.data);
977
978                         function ready() {
979                                 if (!o.async || x.readyState == 4 || c++ > 10000) {
980                                         if (o.success && c < 10000 && x.status == 200)
981                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);
982                                         else if (o.error)
983                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
984
985                                         x = null;
986                                 } else
987                                         w.setTimeout(ready, 10);
988                         };
989
990                         // Syncronous request
991                         if (!o.async)
992                                 return ready();
993
994                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
995                         t = w.setTimeout(ready, 10);
996                 }
997         }
998 });
999
1000 (function() {
1001         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1002
1003         tinymce.create('tinymce.util.JSONRequest', {
1004                 JSONRequest : function(s) {
1005                         this.settings = extend({
1006                         }, s);
1007                         this.count = 0;
1008                 },
1009
1010                 send : function(o) {
1011                         var ecb = o.error, scb = o.success;
1012
1013                         o = extend(this.settings, o);
1014
1015                         o.success = function(c, x) {
1016                                 c = JSON.parse(c);
1017
1018                                 if (typeof(c) == 'undefined') {
1019                                         c = {
1020                                                 error : 'JSON Parse error.'
1021                                         };
1022                                 }
1023
1024                                 if (c.error)
1025                                         ecb.call(o.error_scope || o.scope, c.error, x);
1026                                 else
1027                                         scb.call(o.success_scope || o.scope, c.result);
1028                         };
1029
1030                         o.error = function(ty, x) {
1031                                 if (ecb)
1032                                         ecb.call(o.error_scope || o.scope, ty, x);
1033                         };
1034
1035                         o.data = JSON.serialize({
1036                                 id : o.id || 'c' + (this.count++),
1037                                 method : o.method,
1038                                 params : o.params
1039                         });
1040
1041                         // JSON content type for Ruby on rails. Bug: #1883287
1042                         o.content_type = 'application/json';
1043
1044                         XHR.send(o);
1045                 },
1046
1047                 'static' : {
1048                         sendRPC : function(o) {
1049                                 return new tinymce.util.JSONRequest().send(o);
1050                         }
1051                 }
1052         });
1053 }());
1054 (function(tinymce) {
1055         var namedEntities, baseEntities, reverseEntities,
1056                 attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1057                 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1058                 rawCharsRegExp = /[<>&\"\']/g,
1059                 entityRegExp = /&(#)?([\w]+);/g,
1060                 asciiMap = {
1061                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1062                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1063                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1064                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1065                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1066                 };
1067
1068         // Raw entities
1069         baseEntities = {
1070                 '"' : '&quot;',
1071                 "'" : '&#39;',
1072                 '<' : '&lt;',
1073                 '>' : '&gt;',
1074                 '&' : '&amp;'
1075         };
1076
1077         // Reverse lookup table for raw entities
1078         reverseEntities = {
1079                 '&lt;' : '<',
1080                 '&gt;' : '>',
1081                 '&amp;' : '&',
1082                 '&quot;' : '"',
1083                 '&apos;' : "'"
1084         };
1085
1086         // Decodes text by using the browser
1087         function nativeDecode(text) {
1088                 var elm;
1089
1090                 elm = document.createElement("div");
1091                 elm.innerHTML = text;
1092
1093                 return elm.textContent || elm.innerText || text;
1094         };
1095
1096         // Build a two way lookup table for the entities
1097         function buildEntitiesLookup(items, radix) {
1098                 var i, chr, entity, lookup = {};
1099
1100                 if (items) {
1101                         items = items.split(',');
1102                         radix = radix || 10;
1103
1104                         // Build entities lookup table
1105                         for (i = 0; i < items.length; i += 2) {
1106                                 chr = String.fromCharCode(parseInt(items[i], radix));
1107
1108                                 // Only add non base entities
1109                                 if (!baseEntities[chr]) {
1110                                         entity = '&' + items[i + 1] + ';';
1111                                         lookup[chr] = entity;
1112                                         lookup[entity] = chr;
1113                                 }
1114                         }
1115
1116                         return lookup;
1117                 }
1118         };
1119
1120         // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1121         namedEntities = buildEntitiesLookup(
1122                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1123                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1124                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1125                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1126                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1127                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1128                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1129                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1130                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1131                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1132                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1133                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1134                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1135                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1136                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1137                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1138                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1139                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1140                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1141                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1142                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1143                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1144                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1145                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1146                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1147         , 32);
1148
1149         tinymce.html = tinymce.html || {};
1150
1151         tinymce.html.Entities = {
1152                 encodeRaw : function(text, attr) {
1153                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1154                                 return baseEntities[chr] || chr;
1155                         });
1156                 },
1157
1158                 encodeAllRaw : function(text) {
1159                         return ('' + text).replace(rawCharsRegExp, function(chr) {
1160                                 return baseEntities[chr] || chr;
1161                         });
1162                 },
1163
1164                 encodeNumeric : function(text, attr) {
1165                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1166                                 // Multi byte sequence convert it to a single entity
1167                                 if (chr.length > 1)
1168                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1169
1170                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1171                         });
1172                 },
1173
1174                 encodeNamed : function(text, attr, entities) {
1175                         entities = entities || namedEntities;
1176
1177                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1178                                 return baseEntities[chr] || entities[chr] || chr;
1179                         });
1180                 },
1181
1182                 getEncodeFunc : function(name, entities) {
1183                         var Entities = tinymce.html.Entities;
1184
1185                         entities = buildEntitiesLookup(entities) || namedEntities;
1186
1187                         function encodeNamedAndNumeric(text, attr) {
1188                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1189                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1190                                 });
1191                         };
1192
1193                         function encodeCustomNamed(text, attr) {
1194                                 return Entities.encodeNamed(text, attr, entities);
1195                         };
1196
1197                         // Replace + with , to be compatible with previous TinyMCE versions
1198                         name = tinymce.makeMap(name.replace(/\+/g, ','));
1199
1200                         // Named and numeric encoder
1201                         if (name.named && name.numeric)
1202                                 return encodeNamedAndNumeric;
1203
1204                         // Named encoder
1205                         if (name.named) {
1206                                 // Custom names
1207                                 if (entities)
1208                                         return encodeCustomNamed;
1209
1210                                 return Entities.encodeNamed;
1211                         }
1212
1213                         // Numeric
1214                         if (name.numeric)
1215                                 return Entities.encodeNumeric;
1216
1217                         // Raw encoder
1218                         return Entities.encodeRaw;
1219                 },
1220
1221                 decode : function(text) {
1222                         return text.replace(entityRegExp, function(all, numeric, value) {
1223                                 if (numeric) {
1224                                         value = parseInt(value);
1225
1226                                         // Support upper UTF
1227                                         if (value > 0xFFFF) {
1228                                                 value -= 0x10000;
1229
1230                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1231                                         } else
1232                                                 return asciiMap[value] || String.fromCharCode(value);
1233                                 }
1234
1235                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1236                         });
1237                 }
1238         };
1239 })(tinymce);
1240
1241 tinymce.html.Styles = function(settings, schema) {
1242         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1243                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1244                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1245                 trimRightRegExp = /\s+$/,
1246                 urlColorRegExp = /rgb/,
1247                 undef, i, encodingLookup = {}, encodingItems;
1248
1249         settings = settings || {};
1250
1251         encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
1252         for (i = 0; i < encodingItems.length; i++) {
1253                 encodingLookup[encodingItems[i]] = '_' + i;
1254                 encodingLookup['_' + i] = encodingItems[i];
1255         }
1256
1257         function toHex(match, r, g, b) {
1258                 function hex(val) {
1259                         val = parseInt(val).toString(16);
1260
1261                         return val.length > 1 ? val : '0' + val; // 0 -> 00
1262                 };
1263
1264                 return '#' + hex(r) + hex(g) + hex(b);
1265         };
1266
1267         return {
1268                 toHex : function(color) {
1269                         return color.replace(rgbRegExp, toHex);
1270                 },
1271
1272                 parse : function(css) {
1273                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1274
1275                         function compress(prefix, suffix) {
1276                                 var top, right, bottom, left;
1277
1278                                 // Get values and check it it needs compressing
1279                                 top = styles[prefix + '-top' + suffix];
1280                                 if (!top)
1281                                         return;
1282
1283                                 right = styles[prefix + '-right' + suffix];
1284                                 if (top != right)
1285                                         return;
1286
1287                                 bottom = styles[prefix + '-bottom' + suffix];
1288                                 if (right != bottom)
1289                                         return;
1290
1291                                 left = styles[prefix + '-left' + suffix];
1292                                 if (bottom != left)
1293                                         return;
1294
1295                                 // Compress
1296                                 styles[prefix + suffix] = left;
1297                                 delete styles[prefix + '-top' + suffix];
1298                                 delete styles[prefix + '-right' + suffix];
1299                                 delete styles[prefix + '-bottom' + suffix];
1300                                 delete styles[prefix + '-left' + suffix];
1301                         };
1302
1303                         function canCompress(key) {
1304                                 var value = styles[key], i;
1305
1306                                 if (!value || value.indexOf(' ') < 0)
1307                                         return;
1308
1309                                 value = value.split(' ');
1310                                 i = value.length;
1311                                 while (i--) {
1312                                         if (value[i] !== value[0])
1313                                                 return false;
1314                                 }
1315
1316                                 styles[key] = value[0];
1317
1318                                 return true;
1319                         };
1320
1321                         function compress2(target, a, b, c) {
1322                                 if (!canCompress(a))
1323                                         return;
1324
1325                                 if (!canCompress(b))
1326                                         return;
1327
1328                                 if (!canCompress(c))
1329                                         return;
1330
1331                                 // Compress
1332                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1333                                 delete styles[a];
1334                                 delete styles[b];
1335                                 delete styles[c];
1336                         };
1337
1338                         // Encodes the specified string by replacing all \" \' ; : with _<num>
1339                         function encode(str) {
1340                                 isEncoded = true;
1341
1342                                 return encodingLookup[str];
1343                         };
1344
1345                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1346                         // It will also decode the \" \' if keep_slashes is set to fale or omitted
1347                         function decode(str, keep_slashes) {
1348                                 if (isEncoded) {
1349                                         str = str.replace(/_[0-9]/g, function(str) {
1350                                                 return encodingLookup[str];
1351                                         });
1352                                 }
1353
1354                                 if (!keep_slashes)
1355                                         str = str.replace(/\\([\'\";:])/g, "$1");
1356
1357                                 return str;
1358                         }
1359
1360                         if (css) {
1361                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1362                                 css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1363                                         return str.replace(/[;:]/g, encode);
1364                                 });
1365
1366                                 // Parse styles
1367                                 while (matches = styleRegExp.exec(css)) {
1368                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1369                                         value = matches[2].replace(trimRightRegExp, '');
1370
1371                                         if (name && value.length > 0) {
1372                                                 // Opera will produce 700 instead of bold in their style values
1373                                                 if (name === 'font-weight' && value === '700')
1374                                                         value = 'bold';
1375                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1376                                                         value = value.toLowerCase();            
1377
1378                                                 // Convert RGB colors to HEX
1379                                                 value = value.replace(rgbRegExp, toHex);
1380
1381                                                 // Convert URLs and force them into url('value') format
1382                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1383                                                         str = str || str2;
1384
1385                                                         if (str) {
1386                                                                 str = decode(str);
1387
1388                                                                 // Force strings into single quote format
1389                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";
1390                                                         }
1391
1392                                                         url = decode(url || url2 || url3);
1393
1394                                                         // Convert the URL to relative/absolute depending on config
1395                                                         if (urlConverter)
1396                                                                 url = urlConverter.call(urlConverterScope, url, 'style');
1397
1398                                                         // Output new URL format
1399                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";
1400                                                 });
1401
1402                                                 styles[name] = isEncoded ? decode(value, true) : value;
1403                                         }
1404
1405                                         styleRegExp.lastIndex = matches.index + matches[0].length;
1406                                 }
1407
1408                                 // Compress the styles to reduce it's size for example IE will expand styles
1409                                 compress("border", "");
1410                                 compress("border", "-width");
1411                                 compress("border", "-color");
1412                                 compress("border", "-style");
1413                                 compress("padding", "");
1414                                 compress("margin", "");
1415                                 compress2('border', 'border-width', 'border-style', 'border-color');
1416
1417                                 // Remove pointless border, IE produces these
1418                                 if (styles.border === 'medium none')
1419                                         delete styles.border;
1420                         }
1421
1422                         return styles;
1423                 },
1424
1425                 serialize : function(styles, element_name) {
1426                         var css = '', name, value;
1427
1428                         function serializeStyles(name) {
1429                                 var styleList, i, l, name, value;
1430
1431                                 styleList = schema.styles[name];
1432                                 if (styleList) {
1433                                         for (i = 0, l = styleList.length; i < l; i++) {
1434                                                 name = styleList[i];
1435                                                 value = styles[name];
1436
1437                                                 if (value !== undef && value.length > 0)
1438                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1439                                         }
1440                                 }
1441                         };
1442
1443                         // Serialize styles according to schema
1444                         if (element_name && schema && schema.styles) {
1445                                 // Serialize global styles and element specific styles
1446                                 serializeStyles('*');
1447                                 serializeStyles(name);
1448                         } else {
1449                                 // Output the styles in the order they are inside the object
1450                                 for (name in styles) {
1451                                         value = styles[name];
1452
1453                                         if (value !== undef && value.length > 0)
1454                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1455                                 }
1456                         }
1457
1458                         return css;
1459                 }
1460         };
1461 };
1462
1463 (function(tinymce) {
1464         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
1465                 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1466
1467         function split(str, delim) {
1468                 return str.split(delim || ',');
1469         };
1470
1471         function unpack(lookup, data) {
1472                 var key, elements = {};
1473
1474                 function replace(value) {
1475                         return value.replace(/[A-Z]+/g, function(key) {
1476                                 return replace(lookup[key]);
1477                         });
1478                 };
1479
1480                 // Unpack lookup
1481                 for (key in lookup) {
1482                         if (lookup.hasOwnProperty(key))
1483                                 lookup[key] = replace(lookup[key]);
1484                 }
1485
1486                 // Unpack and parse data into object map
1487                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1488                         attributes = split(attributes, '|');
1489
1490                         elements[name] = {
1491                                 attributes : makeMap(attributes),
1492                                 attributesOrder : attributes,
1493                                 children : makeMap(children, '|', {'#comment' : {}})
1494                         }
1495                 });
1496
1497                 return elements;
1498         };
1499
1500         // Build a lookup table for block elements both lowercase and uppercase
1501         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + 
1502                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + 
1503                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1504         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1505
1506         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1507         transitional = unpack({
1508                 Z : 'H|K|N|O|P',
1509                 Y : 'X|form|R|Q',
1510                 ZG : 'E|span|width|align|char|charoff|valign',
1511                 X : 'p|T|div|U|W|isindex|fieldset|table',
1512                 ZF : 'E|align|char|charoff|valign',
1513                 W : 'pre|hr|blockquote|address|center|noframes',
1514                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1515                 ZD : '[E][S]',
1516                 U : 'ul|ol|dl|menu|dir',
1517                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1518                 T : 'h1|h2|h3|h4|h5|h6',
1519                 ZB : 'X|S|Q',
1520                 S : 'R|P',
1521                 ZA : 'a|G|J|M|O|P',
1522                 R : 'a|H|K|N|O',
1523                 Q : 'noscript|P',
1524                 P : 'ins|del|script',
1525                 O : 'input|select|textarea|label|button',
1526                 N : 'M|L',
1527                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1528                 L : 'sub|sup',
1529                 K : 'J|I',
1530                 J : 'tt|i|b|u|s|strike',
1531                 I : 'big|small|font|basefont',
1532                 H : 'G|F',
1533                 G : 'br|span|bdo',
1534                 F : 'object|applet|img|map|iframe',
1535                 E : 'A|B|C',
1536                 D : 'accesskey|tabindex|onfocus|onblur',
1537                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1538                 B : 'lang|xml:lang|dir',
1539                 A : 'id|class|style|title'
1540         }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
1541                 'style[B|id|type|media|title|xml:space][]' + 
1542                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
1543                 'param[id|name|value|valuetype|type][]' + 
1544                 'p[E|align][#|S]' + 
1545                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
1546                 'br[A|clear][]' + 
1547                 'span[E][#|S]' + 
1548                 'bdo[A|C|B][#|S]' + 
1549                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
1550                 'h1[E|align][#|S]' + 
1551                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
1552                 'map[B|C|A|name][X|form|Q|area]' + 
1553                 'h2[E|align][#|S]' + 
1554                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
1555                 'h3[E|align][#|S]' + 
1556                 'tt[E][#|S]' + 
1557                 'i[E][#|S]' + 
1558                 'b[E][#|S]' + 
1559                 'u[E][#|S]' + 
1560                 's[E][#|S]' + 
1561                 'strike[E][#|S]' + 
1562                 'big[E][#|S]' + 
1563                 'small[E][#|S]' + 
1564                 'font[A|B|size|color|face][#|S]' + 
1565                 'basefont[id|size|color|face][]' + 
1566                 'em[E][#|S]' + 
1567                 'strong[E][#|S]' + 
1568                 'dfn[E][#|S]' + 
1569                 'code[E][#|S]' + 
1570                 'q[E|cite][#|S]' + 
1571                 'samp[E][#|S]' + 
1572                 'kbd[E][#|S]' + 
1573                 'var[E][#|S]' + 
1574                 'cite[E][#|S]' + 
1575                 'abbr[E][#|S]' + 
1576                 'acronym[E][#|S]' + 
1577                 'sub[E][#|S]' + 
1578                 'sup[E][#|S]' + 
1579                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
1580                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
1581                 'optgroup[E|disabled|label][option]' + 
1582                 'option[E|selected|disabled|label|value][]' + 
1583                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
1584                 'label[E|for|accesskey|onfocus|onblur][#|S]' + 
1585                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
1586                 'h4[E|align][#|S]' + 
1587                 'ins[E|cite|datetime][#|Y]' + 
1588                 'h5[E|align][#|S]' + 
1589                 'del[E|cite|datetime][#|Y]' + 
1590                 'h6[E|align][#|S]' + 
1591                 'div[E|align][#|Y]' + 
1592                 'ul[E|type|compact][li]' + 
1593                 'li[E|type|value][#|Y]' + 
1594                 'ol[E|type|compact|start][li]' + 
1595                 'dl[E|compact][dt|dd]' + 
1596                 'dt[E][#|S]' + 
1597                 'dd[E][#|Y]' + 
1598                 'menu[E|compact][li]' + 
1599                 'dir[E|compact][li]' + 
1600                 'pre[E|width|xml:space][#|ZA]' + 
1601                 'hr[E|align|noshade|size|width][]' + 
1602                 'blockquote[E|cite][#|Y]' + 
1603                 'address[E][#|S|p]' + 
1604                 'center[E][#|Y]' + 
1605                 'noframes[E][#|Y]' + 
1606                 'isindex[A|B|prompt][]' + 
1607                 'fieldset[E][#|legend|Y]' + 
1608                 'legend[E|accesskey|align][#|S]' + 
1609                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
1610                 'caption[E|align][#|S]' + 
1611                 'col[ZG][]' + 
1612                 'colgroup[ZG][col]' + 
1613                 'thead[ZF][tr]' + 
1614                 'tr[ZF|bgcolor][th|td]' + 
1615                 'th[E|ZE][#|Y]' + 
1616                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
1617                 'noscript[E][#|Y]' + 
1618                 'td[E|ZE][#|Y]' + 
1619                 'tfoot[ZF][tr]' + 
1620                 'tbody[ZF][tr]' + 
1621                 'area[E|D|shape|coords|href|nohref|alt|target][]' + 
1622                 'base[id|href|target][]' + 
1623                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1624         );
1625
1626         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
1627         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1628         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
1629         whiteSpaceElementsMap = makeMap('pre,script,style');
1630         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1631
1632         tinymce.html.Schema = function(settings) {
1633                 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1634
1635                 settings = settings || {};
1636
1637                 // Allow all elements and attributes if verify_html is set to false
1638                 if (settings.verify_html === false)
1639                         settings.valid_elements = '*[*]';
1640
1641                 // Build styles list
1642                 if (settings.valid_styles) {
1643                         validStyles = {};
1644
1645                         // Convert styles into a rule list
1646                         each(settings.valid_styles, function(value, key) {
1647                                 validStyles[key] = tinymce.explode(value);
1648                         });
1649                 }
1650
1651                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1652                 function patternToRegExp(str) {
1653                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1654                 };
1655
1656                 // Parses the specified valid_elements string and adds to the current rules
1657                 // This function is a bit hard to read since it's heavily optimized for speed
1658                 function addValidElements(valid_elements) {
1659                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1660                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1661                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1662                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1663                                 hasPatternsRegExp = /[*?+]/;
1664
1665                         if (valid_elements) {
1666                                 // Split valid elements into an array with rules
1667                                 valid_elements = split(valid_elements);
1668
1669                                 if (elements['@']) {
1670                                         globalAttributes = elements['@'].attributes;
1671                                         globalAttributesOrder = elements['@'].attributesOrder;
1672                                 }
1673
1674                                 // Loop all rules
1675                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1676                                         // Parse element rule
1677                                         matches = elementRuleRegExp.exec(valid_elements[ei]);
1678                                         if (matches) {
1679                                                 // Setup local names for matches
1680                                                 prefix = matches[1];
1681                                                 elementName = matches[2];
1682                                                 outputName = matches[3];
1683                                                 attrData = matches[4];
1684
1685                                                 // Create new attributes and attributesOrder
1686                                                 attributes = {};
1687                                                 attributesOrder = [];
1688
1689                                                 // Create the new element
1690                                                 element = {
1691                                                         attributes : attributes,
1692                                                         attributesOrder : attributesOrder
1693                                                 };
1694
1695                                                 // Padd empty elements prefix
1696                                                 if (prefix === '#')
1697                                                         element.paddEmpty = true;
1698
1699                                                 // Remove empty elements prefix
1700                                                 if (prefix === '-')
1701                                                         element.removeEmpty = true;
1702
1703                                                 // Copy attributes from global rule into current rule
1704                                                 if (globalAttributes) {
1705                                                         for (key in globalAttributes)
1706                                                                 attributes[key] = globalAttributes[key];
1707
1708                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
1709                                                 }
1710
1711                                                 // Attributes defined
1712                                                 if (attrData) {
1713                                                         attrData = split(attrData, '|');
1714                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {
1715                                                                 matches = attrRuleRegExp.exec(attrData[ai]);
1716                                                                 if (matches) {
1717                                                                         attr = {};
1718                                                                         attrType = matches[1];
1719                                                                         attrName = matches[2].replace(/::/g, ':');
1720                                                                         prefix = matches[3];
1721                                                                         value = matches[4];
1722
1723                                                                         // Required
1724                                                                         if (attrType === '!') {
1725                                                                                 element.attributesRequired = element.attributesRequired || [];
1726                                                                                 element.attributesRequired.push(attrName);
1727                                                                                 attr.required = true;
1728                                                                         }
1729
1730                                                                         // Denied from global
1731                                                                         if (attrType === '-') {
1732                                                                                 delete attributes[attrName];
1733                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
1734                                                                                 continue;
1735                                                                         }
1736
1737                                                                         // Default value
1738                                                                         if (prefix) {
1739                                                                                 // Default value
1740                                                                                 if (prefix === '=') {
1741                                                                                         element.attributesDefault = element.attributesDefault || [];
1742                                                                                         element.attributesDefault.push({name: attrName, value: value});
1743                                                                                         attr.defaultValue = value;
1744                                                                                 }
1745
1746                                                                                 // Forced value
1747                                                                                 if (prefix === ':') {
1748                                                                                         element.attributesForced = element.attributesForced || [];
1749                                                                                         element.attributesForced.push({name: attrName, value: value});
1750                                                                                         attr.forcedValue = value;
1751                                                                                 }
1752
1753                                                                                 // Required values
1754                                                                                 if (prefix === '<')
1755                                                                                         attr.validValues = makeMap(value, '?');
1756                                                                         }
1757
1758                                                                         // Check for attribute patterns
1759                                                                         if (hasPatternsRegExp.test(attrName)) {
1760                                                                                 element.attributePatterns = element.attributePatterns || [];
1761                                                                                 attr.pattern = patternToRegExp(attrName);
1762                                                                                 element.attributePatterns.push(attr);
1763                                                                         } else {
1764                                                                                 // Add attribute to order list if it doesn't already exist
1765                                                                                 if (!attributes[attrName])
1766                                                                                         attributesOrder.push(attrName);
1767
1768                                                                                 attributes[attrName] = attr;
1769                                                                         }
1770                                                                 }
1771                                                         }
1772                                                 }
1773
1774                                                 // Global rule, store away these for later usage
1775                                                 if (!globalAttributes && elementName == '@') {
1776                                                         globalAttributes = attributes;
1777                                                         globalAttributesOrder = attributesOrder;
1778                                                 }
1779
1780                                                 // Handle substitute elements such as b/strong
1781                                                 if (outputName) {
1782                                                         element.outputName = elementName;
1783                                                         elements[outputName] = element;
1784                                                 }
1785
1786                                                 // Add pattern or exact element
1787                                                 if (hasPatternsRegExp.test(elementName)) {
1788                                                         element.pattern = patternToRegExp(elementName);
1789                                                         patternElements.push(element);
1790                                                 } else
1791                                                         elements[elementName] = element;
1792                                         }
1793                                 }
1794                         }
1795                 };
1796
1797                 function setValidElements(valid_elements) {
1798                         elements = {};
1799                         patternElements = [];
1800
1801                         addValidElements(valid_elements);
1802
1803                         each(transitional, function(element, name) {
1804                                 children[name] = element.children;
1805                         });
1806                 };
1807
1808                 // Adds custom non HTML elements to the schema
1809                 function addCustomElements(custom_elements) {
1810                         var customElementRegExp = /^(~)?(.+)$/;
1811
1812                         if (custom_elements) {
1813                                 each(split(custom_elements), function(rule) {
1814                                         var matches = customElementRegExp.exec(rule),
1815                                                 cloneName = matches[1] === '~' ? 'span' : 'div',
1816                                                 name = matches[2];
1817
1818                                         children[name] = children[cloneName];
1819
1820                                         // Add custom elements at span/div positions
1821                                         each(children, function(element, child) {
1822                                                 if (element[cloneName])
1823                                                         element[name] = element[cloneName];
1824                                         });
1825                                 });
1826                         }
1827                 };
1828
1829                 // Adds valid children to the schema object
1830                 function addValidChildren(valid_children) {
1831                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
1832
1833                         if (valid_children) {
1834                                 each(split(valid_children), function(rule) {
1835                                         var matches = childRuleRegExp.exec(rule), parent, prefix;
1836
1837                                         if (matches) {
1838                                                 prefix = matches[1];
1839
1840                                                 // Add/remove items from default
1841                                                 if (prefix)
1842                                                         parent = children[matches[2]];
1843                                                 else
1844                                                         parent = children[matches[2]] = {'#comment' : {}};
1845
1846                                                 parent = children[matches[2]];
1847
1848                                                 each(split(matches[3], '|'), function(child) {
1849                                                         if (prefix === '-')
1850                                                                 delete parent[child];
1851                                                         else
1852                                                                 parent[child] = {};
1853                                                 });
1854                                         }
1855                                 });
1856                         }
1857                 }
1858
1859                 if (!settings.valid_elements) {
1860                         // No valid elements defined then clone the elements from the transitional spec
1861                         each(transitional, function(element, name) {
1862                                 elements[name] = {
1863                                         attributes : element.attributes,
1864                                         attributesOrder : element.attributesOrder
1865                                 };
1866
1867                                 children[name] = element.children;
1868                         });
1869
1870                         // Switch these
1871                         each(split('strong/b,em/i'), function(item) {
1872                                 item = split(item, '/');
1873                                 elements[item[1]].outputName = item[0];
1874                         });
1875
1876                         // Add default alt attribute for images
1877                         elements.img.attributesDefault = [{name: 'alt', value: ''}];
1878
1879                         // Remove these if they are empty by default
1880                         each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
1881                                 elements[name].removeEmpty = true;
1882                         });
1883
1884                         // Padd these by default
1885                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
1886                                 elements[name].paddEmpty = true;
1887                         });
1888                 } else
1889                         setValidElements(settings.valid_elements);
1890
1891                 addCustomElements(settings.custom_elements);
1892                 addValidChildren(settings.valid_children);
1893                 addValidElements(settings.extended_valid_elements);
1894
1895                 // Todo: Remove this when we fix list handling to be valid
1896                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
1897
1898                 // Delete invalid elements
1899                 if (settings.invalid_elements) {
1900                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
1901                                 if (elements[item])
1902                                         delete elements[item];
1903                         });
1904                 }
1905
1906                 self.children = children;
1907
1908                 self.styles = validStyles;
1909
1910                 self.getBoolAttrs = function() {
1911                         return boolAttrMap;
1912                 };
1913
1914                 self.getBlockElements = function() {
1915                         return blockElementsMap;
1916                 };
1917
1918                 self.getShortEndedElements = function() {
1919                         return shortEndedElementsMap;
1920                 };
1921
1922                 self.getSelfClosingElements = function() {
1923                         return selfClosingElementsMap;
1924                 };
1925
1926                 self.getNonEmptyElements = function() {
1927                         return nonEmptyElementsMap;
1928                 };
1929
1930                 self.getWhiteSpaceElements = function() {
1931                         return whiteSpaceElementsMap;
1932                 };
1933
1934                 self.isValidChild = function(name, child) {
1935                         var parent = children[name];
1936
1937                         return !!(parent && parent[child]);
1938                 };
1939
1940                 self.getElementRule = function(name) {
1941                         var element = elements[name], i;
1942
1943                         // Exact match found
1944                         if (element)
1945                                 return element;
1946
1947                         // No exact match then try the patterns
1948                         i = patternElements.length;
1949                         while (i--) {
1950                                 element = patternElements[i];
1951
1952                                 if (element.pattern.test(name))
1953                                         return element;
1954                         }
1955                 };
1956
1957                 self.addValidElements = addValidElements;
1958
1959                 self.setValidElements = setValidElements;
1960
1961                 self.addCustomElements = addCustomElements;
1962
1963                 self.addValidChildren = addValidChildren;
1964         };
1965
1966         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
1967         tinymce.html.Schema.boolAttrMap = boolAttrMap;
1968         tinymce.html.Schema.blockElementsMap = blockElementsMap;
1969 })(tinymce);
1970
1971 (function(tinymce) {
1972         tinymce.html.SaxParser = function(settings, schema) {
1973                 var self = this, noop = function() {};
1974
1975                 settings = settings || {};
1976                 self.schema = schema = schema || new tinymce.html.Schema();
1977
1978                 if (settings.fix_self_closing !== false)
1979                         settings.fix_self_closing = true;
1980
1981                 // Add handler functions from settings and setup default handlers
1982                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
1983                         if (name)
1984                                 self[name] = settings[name] || noop;
1985                 });
1986
1987                 self.parse = function(html) {
1988                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
1989                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
1990                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
1991                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
1992
1993                         function processEndTag(name) {
1994                                 var pos, i;
1995
1996                                 // Find position of parent of the same type
1997                                 pos = stack.length;
1998                                 while (pos--) {
1999                                         if (stack[pos].name === name)
2000                                                 break;                                          
2001                                 }
2002
2003                                 // Found parent
2004                                 if (pos >= 0) {
2005                                         // Close all the open elements
2006                                         for (i = stack.length - 1; i >= pos; i--) {
2007                                                 name = stack[i];
2008
2009                                                 if (name.valid)
2010                                                         self.end(name.name);
2011                                         }
2012
2013                                         // Remove the open elements from the stack
2014                                         stack.length = pos;
2015                                 }
2016                         };
2017
2018                         // Precompile RegExps and map objects
2019                         tokenRegExp = new RegExp('<(?:' +
2020                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
2021                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2022                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2023                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2024                                 '(?:\\/([^>]+)>)|' + // End element
2025                                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
2026                         ')', 'g');
2027
2028                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2029                         specialElements = {
2030                                 'script' : /<\/script[^>]*>/gi,
2031                                 'style' : /<\/style[^>]*>/gi,
2032                                 'noscript' : /<\/noscript[^>]*>/gi
2033                         };
2034
2035                         // Setup lookup tables for empty elements and boolean attributes
2036                         shortEndedElements = schema.getShortEndedElements();
2037                         selfClosing = schema.getSelfClosingElements();
2038                         fillAttrsMap = schema.getBoolAttrs();
2039                         validate = settings.validate;
2040                         fixSelfClosing = settings.fix_self_closing;
2041
2042                         while (matches = tokenRegExp.exec(html)) {
2043                                 // Text
2044                                 if (index < matches.index)
2045                                         self.text(decode(html.substr(index, matches.index - index)));
2046
2047                                 if (value = matches[6]) { // End element
2048                                         processEndTag(value.toLowerCase());
2049                                 } else if (value = matches[7]) { // Start element
2050                                         value = value.toLowerCase();
2051                                         isShortEnded = value in shortEndedElements;
2052
2053                                         // Is self closing tag for example an <li> after an open <li>
2054                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2055                                                 processEndTag(value);
2056
2057                                         // Validate element
2058                                         if (!validate || (elementRule = schema.getElementRule(value))) {
2059                                                 isValidElement = true;
2060
2061                                                 // Grab attributes map and patters when validation is enabled
2062                                                 if (validate) {
2063                                                         validAttributesMap = elementRule.attributes;
2064                                                         validAttributePatterns = elementRule.attributePatterns;
2065                                                 }
2066
2067                                                 // Parse attributes
2068                                                 if (attribsValue = matches[8]) {
2069                                                         attrList = [];
2070                                                         attrList.map = {};
2071
2072                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2073                                                                 var attrRule, i;
2074
2075                                                                 name = name.toLowerCase();
2076                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2077
2078                                                                 // Validate name and value
2079                                                                 if (validate && name.indexOf('data-') !== 0) {
2080                                                                         attrRule = validAttributesMap[name];
2081
2082                                                                         // Find rule by pattern matching
2083                                                                         if (!attrRule && validAttributePatterns) {
2084                                                                                 i = validAttributePatterns.length;
2085                                                                                 while (i--) {
2086                                                                                         attrRule = validAttributePatterns[i];
2087                                                                                         if (attrRule.pattern.test(name))
2088                                                                                                 break;
2089                                                                                 }
2090
2091                                                                                 // No rule matched
2092                                                                                 if (i === -1)
2093                                                                                         attrRule = null;
2094                                                                         }
2095
2096                                                                         // No attribute rule found
2097                                                                         if (!attrRule)
2098                                                                                 return;
2099
2100                                                                         // Validate value
2101                                                                         if (attrRule.validValues && !(value in attrRule.validValues))
2102                                                                                 return;
2103                                                                 }
2104
2105                                                                 // Add attribute to list and map
2106                                                                 attrList.map[name] = value;
2107                                                                 attrList.push({
2108                                                                         name: name,
2109                                                                         value: value
2110                                                                 });
2111                                                         });
2112                                                 } else {
2113                                                         attrList = [];
2114                                                         attrList.map = {};
2115                                                 }
2116
2117                                                 // Process attributes if validation is enabled
2118                                                 if (validate) {
2119                                                         attributesRequired = elementRule.attributesRequired;
2120                                                         attributesDefault = elementRule.attributesDefault;
2121                                                         attributesForced = elementRule.attributesForced;
2122
2123                                                         // Handle forced attributes
2124                                                         if (attributesForced) {
2125                                                                 i = attributesForced.length;
2126                                                                 while (i--) {
2127                                                                         attr = attributesForced[i];
2128                                                                         name = attr.name;
2129                                                                         attrValue = attr.value;
2130
2131                                                                         if (attrValue === '{$uid}')
2132                                                                                 attrValue = 'mce_' + idCount++;
2133
2134                                                                         attrList.map[name] = attrValue;
2135                                                                         attrList.push({name: name, value: attrValue});
2136                                                                 }
2137                                                         }
2138
2139                                                         // Handle default attributes
2140                                                         if (attributesDefault) {
2141                                                                 i = attributesDefault.length;
2142                                                                 while (i--) {
2143                                                                         attr = attributesDefault[i];
2144                                                                         name = attr.name;
2145
2146                                                                         if (!(name in attrList.map)) {
2147                                                                                 attrValue = attr.value;
2148
2149                                                                                 if (attrValue === '{$uid}')
2150                                                                                         attrValue = 'mce_' + idCount++;
2151
2152                                                                                 attrList.map[name] = attrValue;
2153                                                                                 attrList.push({name: name, value: attrValue});
2154                                                                         }
2155                                                                 }
2156                                                         }
2157
2158                                                         // Handle required attributes
2159                                                         if (attributesRequired) {
2160                                                                 i = attributesRequired.length;
2161                                                                 while (i--) {
2162                                                                         if (attributesRequired[i] in attrList.map)
2163                                                                                 break;
2164                                                                 }
2165
2166                                                                 // None of the required attributes where found
2167                                                                 if (i === -1)
2168                                                                         isValidElement = false;
2169                                                         }
2170
2171                                                         // Invalidate element if it's marked as bogus
2172                                                         if (attrList.map['data-mce-bogus'])
2173                                                                 isValidElement = false;
2174                                                 }
2175
2176                                                 if (isValidElement)
2177                                                         self.start(value, attrList, isShortEnded);
2178                                         } else
2179                                                 isValidElement = false;
2180
2181                                         // Treat script, noscript and style a bit different since they may include code that looks like elements
2182                                         if (endRegExp = specialElements[value]) {
2183                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;
2184
2185                                                 if (matches = endRegExp.exec(html)) {
2186                                                         if (isValidElement)
2187                                                                 text = html.substr(index, matches.index - index);
2188
2189                                                         index = matches.index + matches[0].length;
2190                                                 } else {
2191                                                         text = html.substr(index);
2192                                                         index = html.length;
2193                                                 }
2194
2195                                                 if (isValidElement && text.length > 0)
2196                                                         self.text(text, true);
2197
2198                                                 if (isValidElement)
2199                                                         self.end(value);
2200
2201                                                 tokenRegExp.lastIndex = index;
2202                                                 continue;
2203                                         }
2204
2205                                         // Push value on to stack
2206                                         if (!isShortEnded) {
2207                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2208                                                         stack.push({name: value, valid: isValidElement});
2209                                                 else if (isValidElement)
2210                                                         self.end(value);
2211                                         }
2212                                 } else if (value = matches[1]) { // Comment
2213                                         self.comment(value);
2214                                 } else if (value = matches[2]) { // CDATA
2215                                         self.cdata(value);
2216                                 } else if (value = matches[3]) { // DOCTYPE
2217                                         self.doctype(value);
2218                                 } else if (value = matches[4]) { // PI
2219                                         self.pi(value, matches[5]);
2220                                 }
2221
2222                                 index = matches.index + matches[0].length;
2223                         }
2224
2225                         // Text
2226                         if (index < html.length)
2227                                 self.text(decode(html.substr(index)));
2228
2229                         // Close any open elements
2230                         for (i = stack.length - 1; i >= 0; i--) {
2231                                 value = stack[i];
2232
2233                                 if (value.valid)
2234                                         self.end(value.name);
2235                         }
2236                 };
2237         }
2238 })(tinymce);
2239
2240 (function(tinymce) {
2241         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2242                 '#text' : 3,
2243                 '#comment' : 8,
2244                 '#cdata' : 4,
2245                 '#pi' : 7,
2246                 '#doctype' : 10,
2247                 '#document-fragment' : 11
2248         };
2249
2250         // Walks the tree left/right
2251         function walk(node, root_node, prev) {
2252                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2253
2254                 // Walk into nodes if it has a start
2255                 if (node[startName])
2256                         return node[startName];
2257
2258                 // Return the sibling if it has one
2259                 if (node !== root_node) {
2260                         sibling = node[siblingName];
2261
2262                         if (sibling)
2263                                 return sibling;
2264
2265                         // Walk up the parents to look for siblings
2266                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2267                                 sibling = parent[siblingName];
2268
2269                                 if (sibling)
2270                                         return sibling;
2271                         }
2272                 }
2273         };
2274
2275         function Node(name, type) {
2276                 this.name = name;
2277                 this.type = type;
2278
2279                 if (type === 1) {
2280                         this.attributes = [];
2281                         this.attributes.map = {};
2282                 }
2283         }
2284
2285         tinymce.extend(Node.prototype, {
2286                 replace : function(node) {
2287                         var self = this;
2288
2289                         if (node.parent)
2290                                 node.remove();
2291
2292                         self.insert(node, self);
2293                         self.remove();
2294
2295                         return self;
2296                 },
2297
2298                 attr : function(name, value) {
2299                         var self = this, attrs, i, undef;
2300
2301                         if (typeof name !== "string") {
2302                                 for (i in name)
2303                                         self.attr(i, name[i]);
2304
2305                                 return self;
2306                         }
2307
2308                         if (attrs = self.attributes) {
2309                                 if (value !== undef) {
2310                                         // Remove attribute
2311                                         if (value === null) {
2312                                                 if (name in attrs.map) {
2313                                                         delete attrs.map[name];
2314
2315                                                         i = attrs.length;
2316                                                         while (i--) {
2317                                                                 if (attrs[i].name === name) {
2318                                                                         attrs = attrs.splice(i, 1);
2319                                                                         return self;
2320                                                                 }
2321                                                         }
2322                                                 }
2323
2324                                                 return self;
2325                                         }
2326
2327                                         // Set attribute
2328                                         if (name in attrs.map) {
2329                                                 // Set attribute
2330                                                 i = attrs.length;
2331                                                 while (i--) {
2332                                                         if (attrs[i].name === name) {
2333                                                                 attrs[i].value = value;
2334                                                                 break;
2335                                                         }
2336                                                 }
2337                                         } else
2338                                                 attrs.push({name: name, value: value});
2339
2340                                         attrs.map[name] = value;
2341
2342                                         return self;
2343                                 } else {
2344                                         return attrs.map[name];
2345                                 }
2346                         }
2347                 },
2348
2349                 clone : function() {
2350                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2351
2352                         // Clone element attributes
2353                         if (selfAttrs = self.attributes) {
2354                                 cloneAttrs = [];
2355                                 cloneAttrs.map = {};
2356
2357                                 for (i = 0, l = selfAttrs.length; i < l; i++) {
2358                                         selfAttr = selfAttrs[i];
2359
2360                                         // Clone everything except id
2361                                         if (selfAttr.name !== 'id') {
2362                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2363                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2364                                         }
2365                                 }
2366
2367                                 clone.attributes = cloneAttrs;
2368                         }
2369
2370                         clone.value = self.value;
2371                         clone.shortEnded = self.shortEnded;
2372
2373                         return clone;
2374                 },
2375
2376                 wrap : function(wrapper) {
2377                         var self = this;
2378
2379                         self.parent.insert(wrapper, self);
2380                         wrapper.append(self);
2381
2382                         return self;
2383                 },
2384
2385                 unwrap : function() {
2386                         var self = this, node, next;
2387
2388                         for (node = self.firstChild; node; ) {
2389                                 next = node.next;
2390                                 self.insert(node, self, true);
2391                                 node = next;
2392                         }
2393
2394                         self.remove();
2395                 },
2396
2397                 remove : function() {
2398                         var self = this, parent = self.parent, next = self.next, prev = self.prev;
2399
2400                         if (parent) {
2401                                 if (parent.firstChild === self) {
2402                                         parent.firstChild = next;
2403
2404                                         if (next)
2405                                                 next.prev = null;
2406                                 } else {
2407                                         prev.next = next;
2408                                 }
2409
2410                                 if (parent.lastChild === self) {
2411                                         parent.lastChild = prev;
2412
2413                                         if (prev)
2414                                                 prev.next = null;
2415                                 } else {
2416                                         next.prev = prev;
2417                                 }
2418
2419                                 self.parent = self.next = self.prev = null;
2420                         }
2421
2422                         return self;
2423                 },
2424
2425                 append : function(node) {
2426                         var self = this, last;
2427
2428                         if (node.parent)
2429                                 node.remove();
2430
2431                         last = self.lastChild;
2432                         if (last) {
2433                                 last.next = node;
2434                                 node.prev = last;
2435                                 self.lastChild = node;
2436                         } else
2437                                 self.lastChild = self.firstChild = node;
2438
2439                         node.parent = self;
2440
2441                         return node;
2442                 },
2443
2444                 insert : function(node, ref_node, before) {
2445                         var parent;
2446
2447                         if (node.parent)
2448                                 node.remove();
2449
2450                         parent = ref_node.parent || this;
2451
2452                         if (before) {
2453                                 if (ref_node === parent.firstChild)
2454                                         parent.firstChild = node;
2455                                 else
2456                                         ref_node.prev.next = node;
2457
2458                                 node.prev = ref_node.prev;
2459                                 node.next = ref_node;
2460                                 ref_node.prev = node;
2461                         } else {
2462                                 if (ref_node === parent.lastChild)
2463                                         parent.lastChild = node;
2464                                 else
2465                                         ref_node.next.prev = node;
2466
2467                                 node.next = ref_node.next;
2468                                 node.prev = ref_node;
2469                                 ref_node.next = node;
2470                         }
2471
2472                         node.parent = parent;
2473
2474                         return node;
2475                 },
2476
2477                 getAll : function(name) {
2478                         var self = this, node, collection = [];
2479
2480                         for (node = self.firstChild; node; node = walk(node, self)) {
2481                                 if (node.name === name)
2482                                         collection.push(node);
2483                         }
2484
2485                         return collection;
2486                 },
2487
2488                 empty : function() {
2489                         var self = this, nodes, i, node;
2490
2491                         // Remove all children
2492                         if (self.firstChild) {
2493                                 nodes = [];
2494
2495                                 // Collect the children
2496                                 for (node = self.firstChild; node; node = walk(node, self))
2497                                         nodes.push(node);
2498
2499                                 // Remove the children
2500                                 i = nodes.length;
2501                                 while (i--) {
2502                                         node = nodes[i];
2503                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2504                                 }
2505                         }
2506
2507                         self.firstChild = self.lastChild = null;
2508
2509                         return self;
2510                 },
2511
2512                 isEmpty : function(elements) {
2513                         var self = this, node = self.firstChild, i, name;
2514
2515                         if (node) {
2516                                 do {
2517                                         if (node.type === 1) {
2518                                                 // Ignore bogus elements
2519                                                 if (node.attributes.map['data-mce-bogus'])
2520                                                         continue;
2521
2522                                                 // Keep empty elements like <img />
2523                                                 if (elements[node.name])
2524                                                         return false;
2525
2526                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
2527                                                 i = node.attributes.length;
2528                                                 while (i--) {
2529                                                         name = node.attributes[i].name;
2530                                                         if (name === "name" || name.indexOf('data-') === 0)
2531                                                                 return false;
2532                                                 }
2533                                         }
2534
2535                                         // Keep non whitespace text nodes
2536                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2537                                                 return false;
2538                                 } while (node = walk(node, self));
2539                         }
2540
2541                         return true;
2542                 }
2543         });
2544
2545         tinymce.extend(Node, {
2546                 create : function(name, attrs) {
2547                         var node, attrName;
2548
2549                         // Create node
2550                         node = new Node(name, typeLookup[name] || 1);
2551
2552                         // Add attributes if needed
2553                         if (attrs) {
2554                                 for (attrName in attrs)
2555                                         node.attr(attrName, attrs[attrName]);
2556                         }
2557
2558                         return node;
2559                 }
2560         });
2561
2562         tinymce.html.Node = Node;
2563 })(tinymce);
2564
2565 (function(tinymce) {
2566         var Node = tinymce.html.Node;
2567
2568         tinymce.html.DomParser = function(settings, schema) {
2569                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2570
2571                 settings = settings || {};
2572                 settings.validate = "validate" in settings ? settings.validate : true;
2573                 settings.root_name = settings.root_name || 'body';
2574                 self.schema = schema = schema || new tinymce.html.Schema();
2575
2576                 function fixInvalidChildren(nodes) {
2577                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2578                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2579
2580                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2581                         nonEmptyElements = schema.getNonEmptyElements();
2582
2583                         for (ni = 0; ni < nodes.length; ni++) {
2584                                 node = nodes[ni];
2585
2586                                 // Already removed
2587                                 if (!node.parent)
2588                                         continue;
2589
2590                                 // Get list of all parent nodes until we find a valid parent to stick the child into
2591                                 parents = [node];
2592                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2593                                         parents.push(parent);
2594
2595                                 // Found a suitable parent
2596                                 if (parent && parents.length > 1) {
2597                                         // Reverse the array since it makes looping easier
2598                                         parents.reverse();
2599
2600                                         // Clone the related parent and insert that after the moved node
2601                                         newParent = currentNode = self.filterNode(parents[0].clone());
2602
2603                                         // Start cloning and moving children on the left side of the target node
2604                                         for (i = 0; i < parents.length - 1; i++) {
2605                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {
2606                                                         tempNode = self.filterNode(parents[i].clone());
2607                                                         currentNode.append(tempNode);
2608                                                 } else
2609                                                         tempNode = currentNode;
2610
2611                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2612                                                         nextNode = childNode.next;
2613                                                         tempNode.append(childNode);
2614                                                         childNode = nextNode;
2615                                                 }
2616
2617                                                 currentNode = tempNode;
2618                                         }
2619
2620                                         if (!newParent.isEmpty(nonEmptyElements)) {
2621                                                 parent.insert(newParent, parents[0], true);
2622                                                 parent.insert(node, newParent);
2623                                         } else {
2624                                                 parent.insert(node, parents[0], true);
2625                                         }
2626
2627                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2628                                         parent = parents[0];
2629                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2630                                                 parent.empty().remove();
2631                                         }
2632                                 } else if (node.parent) {
2633                                         // If it's an LI try to find a UL/OL for it or wrap it
2634                                         if (node.name === 'li') {
2635                                                 sibling = node.prev;
2636                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2637                                                         sibling.append(node);
2638                                                         continue;
2639                                                 }
2640
2641                                                 sibling = node.next;
2642                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2643                                                         sibling.insert(node, sibling.firstChild, true);
2644                                                         continue;
2645                                                 }
2646
2647                                                 node.wrap(self.filterNode(new Node('ul', 1)));
2648                                                 continue;
2649                                         }
2650
2651                                         // Try wrapping the element in a DIV
2652                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2653                                                 node.wrap(self.filterNode(new Node('div', 1)));
2654                                         } else {
2655                                                 // We failed wrapping it, then remove or unwrap it
2656                                                 if (node.name === 'style' || node.name === 'script')
2657                                                         node.empty().remove();
2658                                                 else
2659                                                         node.unwrap();
2660                                         }
2661                                 }
2662                         }
2663                 };
2664
2665                 self.filterNode = function(node) {
2666                         var i, name, list;
2667
2668                         // Run element filters
2669                         if (name in nodeFilters) {
2670                                 list = matchedNodes[name];
2671
2672                                 if (list)
2673                                         list.push(node);
2674                                 else
2675                                         matchedNodes[name] = [node];
2676                         }
2677
2678                         // Run attribute filters
2679                         i = attributeFilters.length;
2680                         while (i--) {
2681                                 name = attributeFilters[i].name;
2682
2683                                 if (name in node.attributes.map) {
2684                                         list = matchedAttributes[name];
2685
2686                                         if (list)
2687                                                 list.push(node);
2688                                         else
2689                                                 matchedAttributes[name] = [node];
2690                                 }
2691                         }
2692
2693                         return node;
2694                 };
2695
2696                 self.addNodeFilter = function(name, callback) {
2697                         tinymce.each(tinymce.explode(name), function(name) {
2698                                 var list = nodeFilters[name];
2699
2700                                 if (!list)
2701                                         nodeFilters[name] = list = [];
2702
2703                                 list.push(callback);
2704                         });
2705                 };
2706
2707                 self.addAttributeFilter = function(name, callback) {
2708                         tinymce.each(tinymce.explode(name), function(name) {
2709                                 var i;
2710
2711                                 for (i = 0; i < attributeFilters.length; i++) {
2712                                         if (attributeFilters[i].name === name) {
2713                                                 attributeFilters[i].callbacks.push(callback);
2714                                                 return;
2715                                         }
2716                                 }
2717
2718                                 attributeFilters.push({name: name, callbacks: [callback]});
2719                         });
2720                 };
2721
2722                 self.parse = function(html, args) {
2723                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
2724                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
2725                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
2726
2727                         args = args || {};
2728                         matchedNodes = {};
2729                         matchedAttributes = {};
2730                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
2731                         nonEmptyElements = schema.getNonEmptyElements();
2732                         children = schema.children;
2733                         validate = settings.validate;
2734
2735                         whiteSpaceElements = schema.getWhiteSpaceElements();
2736                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;
2737                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;
2738                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;
2739
2740                         function createNode(name, type) {
2741                                 var node = new Node(name, type), list;
2742
2743                                 if (name in nodeFilters) {
2744                                         list = matchedNodes[name];
2745
2746                                         if (list)
2747                                                 list.push(node);
2748                                         else
2749                                                 matchedNodes[name] = [node];
2750                                 }
2751
2752                                 return node;
2753                         };
2754
2755                         function removeWhitespaceBefore(node) {
2756                                 var textNode, textVal, sibling;
2757
2758                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
2759                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
2760
2761                                         if (textVal.length > 0) {
2762                                                 textNode.value = textVal;
2763                                                 textNode = textNode.prev;
2764                                         } else {
2765                                                 sibling = textNode.prev;
2766                                                 textNode.remove();
2767                                                 textNode = sibling;
2768                                         }
2769                                 }
2770                         };
2771
2772                         parser = new tinymce.html.SaxParser({
2773                                 validate : validate,
2774                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
2775
2776                                 cdata: function(text) {
2777                                         node.append(createNode('#cdata', 4)).value = text;
2778                                 },
2779
2780                                 text: function(text, raw) {
2781                                         var textNode;
2782
2783                                         // Trim all redundant whitespace on non white space elements
2784                                         if (!whiteSpaceElements[node.name]) {
2785                                                 text = text.replace(allWhiteSpaceRegExp, ' ');
2786
2787                                                 if (node.lastChild && blockElements[node.lastChild.name])
2788                                                         text = text.replace(startWhiteSpaceRegExp, '');
2789                                         }
2790
2791                                         // Do we need to create the node
2792                                         if (text.length !== 0) {
2793                                                 textNode = createNode('#text', 3);
2794                                                 textNode.raw = !!raw;
2795                                                 node.append(textNode).value = text;
2796                                         }
2797                                 },
2798
2799                                 comment: function(text) {
2800                                         node.append(createNode('#comment', 8)).value = text;
2801                                 },
2802
2803                                 pi: function(name, text) {
2804                                         node.append(createNode(name, 7)).value = text;
2805                                         removeWhitespaceBefore(node);
2806                                 },
2807
2808                                 doctype: function(text) {
2809                                         var newNode;
2810                 
2811                                         newNode = node.append(createNode('#doctype', 10));
2812                                         newNode.value = text;
2813                                         removeWhitespaceBefore(node);
2814                                 },
2815
2816                                 start: function(name, attrs, empty) {
2817                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
2818
2819                                         elementRule = validate ? schema.getElementRule(name) : {};
2820                                         if (elementRule) {
2821                                                 newNode = createNode(elementRule.outputName || name, 1);
2822                                                 newNode.attributes = attrs;
2823                                                 newNode.shortEnded = empty;
2824
2825                                                 node.append(newNode);
2826
2827                                                 // Check if node is valid child of the parent node is the child is
2828                                                 // unknown we don't collect it since it's probably a custom element
2829                                                 parent = children[node.name];
2830                                                 if (parent && children[newNode.name] && !parent[newNode.name])
2831                                                         invalidChildren.push(newNode);
2832
2833                                                 attrFiltersLen = attributeFilters.length;
2834                                                 while (attrFiltersLen--) {
2835                                                         attrName = attributeFilters[attrFiltersLen].name;
2836
2837                                                         if (attrName in attrs.map) {
2838                                                                 list = matchedAttributes[attrName];
2839
2840                                                                 if (list)
2841                                                                         list.push(newNode);
2842                                                                 else
2843                                                                         matchedAttributes[attrName] = [newNode];
2844                                                         }
2845                                                 }
2846
2847                                                 // Trim whitespace before block
2848                                                 if (blockElements[name])
2849                                                         removeWhitespaceBefore(newNode);
2850
2851                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />
2852                                                 if (!empty)
2853                                                         node = newNode;
2854                                         }
2855                                 },
2856
2857                                 end: function(name) {
2858                                         var textNode, elementRule, text, sibling, tempNode;
2859
2860                                         elementRule = validate ? schema.getElementRule(name) : {};
2861                                         if (elementRule) {
2862                                                 if (blockElements[name]) {
2863                                                         if (!whiteSpaceElements[node.name]) {
2864                                                                 // Trim whitespace at beginning of block
2865                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
2866                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');
2867
2868                                                                         if (text.length > 0) {
2869                                                                                 textNode.value = text;
2870                                                                                 textNode = textNode.next;
2871                                                                         } else {
2872                                                                                 sibling = textNode.next;
2873                                                                                 textNode.remove();
2874                                                                                 textNode = sibling;
2875                                                                         }
2876                                                                 }
2877
2878                                                                 // Trim whitespace at end of block
2879                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
2880                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');
2881
2882                                                                         if (text.length > 0) {
2883                                                                                 textNode.value = text;
2884                                                                                 textNode = textNode.prev;
2885                                                                         } else {
2886                                                                                 sibling = textNode.prev;
2887                                                                                 textNode.remove();
2888                                                                                 textNode = sibling;
2889                                                                         }
2890                                                                 }
2891                                                         }
2892
2893                                                         // Trim start white space
2894                                                         textNode = node.prev;
2895                                                         if (textNode && textNode.type === 3) {
2896                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
2897
2898                                                                 if (text.length > 0)
2899                                                                         textNode.value = text;
2900                                                                 else
2901                                                                         textNode.remove();
2902                                                         }
2903                                                 }
2904
2905                                                 // Handle empty nodes
2906                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {
2907                                                         if (node.isEmpty(nonEmptyElements)) {
2908                                                                 if (elementRule.paddEmpty)
2909                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';
2910                                                                 else {
2911                                                                         // Leave nodes that have a name like <a name="name">
2912                                                                         if (!node.attributes.map.name) {
2913                                                                                 tempNode = node.parent;
2914                                                                                 node.empty().remove();
2915                                                                                 node = tempNode;
2916                                                                                 return;
2917                                                                         }
2918                                                                 }
2919                                                         }
2920                                                 }
2921
2922                                                 node = node.parent;
2923                                         }
2924                                 }
2925                         }, schema);
2926
2927                         rootNode = node = new Node(settings.root_name, 11);
2928
2929                         parser.parse(html);
2930
2931                         if (validate)
2932                                 fixInvalidChildren(invalidChildren);
2933
2934                         // Run node filters
2935                         for (name in matchedNodes) {
2936                                 list = nodeFilters[name];
2937                                 nodes = matchedNodes[name];
2938
2939                                 // Remove already removed children
2940                                 fi = nodes.length;
2941                                 while (fi--) {
2942                                         if (!nodes[fi].parent)
2943                                                 nodes.splice(fi, 1);
2944                                 }
2945
2946                                 for (i = 0, l = list.length; i < l; i++)
2947                                         list[i](nodes, name, args);
2948                         }
2949
2950                         // Run attribute filters
2951                         for (i = 0, l = attributeFilters.length; i < l; i++) {
2952                                 list = attributeFilters[i];
2953
2954                                 if (list.name in matchedAttributes) {
2955                                         nodes = matchedAttributes[list.name];
2956
2957                                         // Remove already removed children
2958                                         fi = nodes.length;
2959                                         while (fi--) {
2960                                                 if (!nodes[fi].parent)
2961                                                         nodes.splice(fi, 1);
2962                                         }
2963
2964                                         for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
2965                                                 list.callbacks[fi](nodes, list.name, args);
2966                                 }
2967                         }
2968
2969                         return rootNode;
2970                 };
2971
2972                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
2973                 // make it possible to place the caret inside empty blocks. This logic tries to remove
2974                 // these elements and keep br elements that where intended to be there intact
2975                 if (settings.remove_trailing_brs) {
2976                         self.addNodeFilter('br', function(nodes, name) {
2977                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
2978                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
2979
2980                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
2981                                 for (i = 0; i < l; i++) {
2982                                         node = nodes[i];
2983                                         parent = node.parent;
2984
2985                                         if (blockElements[node.parent.name] && node === parent.lastChild) {
2986                                                 // Loop all nodes to the right of the current node and check for other BR elements
2987                                                 // excluding bookmarks since they are invisible
2988                                                 prev = node.prev;
2989                                                 while (prev) {
2990                                                         prevName = prev.name;
2991
2992                                                         // Ignore bookmarks
2993                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
2994                                                                 // Found a non BR element
2995                                                                 if (prevName !== "br")
2996                                                                         break;
2997         
2998                                                                 // Found another br it's a <br><br> structure then don't remove anything
2999                                                                 if (prevName === 'br') {
3000                                                                         node = null;
3001                                                                         break;
3002                                                                 }
3003                                                         }
3004
3005                                                         prev = prev.prev;
3006                                                 }
3007
3008                                                 if (node) {
3009                                                         node.remove();
3010
3011                                                         // Is the parent to be considered empty after we removed the BR
3012                                                         if (parent.isEmpty(nonEmptyElements)) {
3013                                                                 elementRule = schema.getElementRule(parent.name);
3014
3015                                                                 // Remove or padd the element depending on schema rule
3016                                                                 if (elementRule.removeEmpty)
3017                                                                         parent.remove();
3018                                                                 else if (elementRule.paddEmpty) 
3019                                                                         parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3020                                                         }
3021                                                 }
3022                                         }
3023                                 }
3024                         });
3025                 }
3026         }
3027 })(tinymce);
3028
3029 tinymce.html.Writer = function(settings) {
3030         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3031
3032         settings = settings || {};
3033         indent = settings.indent;
3034         indentBefore = tinymce.makeMap(settings.indent_before || '');
3035         indentAfter = tinymce.makeMap(settings.indent_after || '');
3036         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3037         htmlOutput = settings.element_format == "html";
3038
3039         return {
3040                 start: function(name, attrs, empty) {
3041                         var i, l, attr, value;
3042
3043                         if (indent && indentBefore[name] && html.length > 0) {
3044                                 value = html[html.length - 1];
3045
3046                                 if (value.length > 0 && value !== '\n')
3047                                         html.push('\n');
3048                         }
3049
3050                         html.push('<', name);
3051
3052                         if (attrs) {
3053                                 for (i = 0, l = attrs.length; i < l; i++) {
3054                                         attr = attrs[i];
3055                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3056                                 }
3057                         }
3058
3059                         if (!empty || htmlOutput)
3060                                 html[html.length] = '>';
3061                         else
3062                                 html[html.length] = ' />';
3063
3064                         if (empty && indent && indentAfter[name] && html.length > 0) {
3065                                 value = html[html.length - 1];
3066
3067                                 if (value.length > 0 && value !== '\n')
3068                                         html.push('\n');
3069                         }
3070                 },
3071
3072                 end: function(name) {
3073                         var value;
3074
3075                         /*if (indent && indentBefore[name] && html.length > 0) {
3076                                 value = html[html.length - 1];
3077
3078                                 if (value.length > 0 && value !== '\n')
3079                                         html.push('\n');
3080                         }*/
3081
3082                         html.push('</', name, '>');
3083
3084                         if (indent && indentAfter[name] && html.length > 0) {
3085                                 value = html[html.length - 1];
3086
3087                                 if (value.length > 0 && value !== '\n')
3088                                         html.push('\n');
3089                         }
3090                 },
3091
3092                 text: function(text, raw) {
3093                         if (text.length > 0)
3094                                 html[html.length] = raw ? text : encode(text);
3095                 },
3096
3097                 cdata: function(text) {
3098                         html.push('<![CDATA[', text, ']]>');
3099                 },
3100
3101                 comment: function(text) {
3102                         html.push('<!--', text, '-->');
3103                 },
3104
3105                 pi: function(name, text) {
3106                         if (text)
3107                                 html.push('<?', name, ' ', text, '?>');
3108                         else
3109                                 html.push('<?', name, '?>');
3110
3111                         if (indent)
3112                                 html.push('\n');
3113                 },
3114
3115                 doctype: function(text) {
3116                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3117                 },
3118
3119                 reset: function() {
3120                         html.length = 0;
3121                 },
3122
3123                 getContent: function() {
3124                         return html.join('').replace(/\n$/, '');
3125                 }
3126         };
3127 };
3128
3129 (function(tinymce) {
3130         tinymce.html.Serializer = function(settings, schema) {
3131                 var self = this, writer = new tinymce.html.Writer(settings);
3132
3133                 settings = settings || {};
3134                 settings.validate = "validate" in settings ? settings.validate : true;
3135
3136                 self.schema = schema = schema || new tinymce.html.Schema();
3137                 self.writer = writer;
3138
3139                 self.serialize = function(node) {
3140                         var handlers, validate;
3141
3142                         validate = settings.validate;
3143
3144                         handlers = {
3145                                 // #text
3146                                 3: function(node, raw) {
3147                                         writer.text(node.value, node.raw);
3148                                 },
3149
3150                                 // #comment
3151                                 8: function(node) {
3152                                         writer.comment(node.value);
3153                                 },
3154
3155                                 // Processing instruction
3156                                 7: function(node) {
3157                                         writer.pi(node.name, node.value);
3158                                 },
3159
3160                                 // Doctype
3161                                 10: function(node) {
3162                                         writer.doctype(node.value);
3163                                 },
3164
3165                                 // CDATA
3166                                 4: function(node) {
3167                                         writer.cdata(node.value);
3168                                 },
3169
3170                                 // Document fragment
3171                                 11: function(node) {
3172                                         if ((node = node.firstChild)) {
3173                                                 do {
3174                                                         walk(node);
3175                                                 } while (node = node.next);
3176                                         }
3177                                 }
3178                         };
3179
3180                         writer.reset();
3181
3182                         function walk(node) {
3183                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3184
3185                                 if (!handler) {
3186                                         name = node.name;
3187                                         isEmpty = node.shortEnded;
3188                                         attrs = node.attributes;
3189
3190                                         // Sort attributes
3191                                         if (validate && attrs && attrs.length > 1) {
3192                                                 sortedAttrs = [];
3193                                                 sortedAttrs.map = {};
3194
3195                                                 elementRule = schema.getElementRule(node.name);
3196                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3197                                                         attrName = elementRule.attributesOrder[i];
3198
3199                                                         if (attrName in attrs.map) {
3200                                                                 attrValue = attrs.map[attrName];
3201                                                                 sortedAttrs.map[attrName] = attrValue;
3202                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3203                                                         }
3204                                                 }
3205
3206                                                 for (i = 0, l = attrs.length; i < l; i++) {
3207                                                         attrName = attrs[i].name;
3208
3209                                                         if (!(attrName in sortedAttrs.map)) {
3210                                                                 attrValue = attrs.map[attrName];
3211                                                                 sortedAttrs.map[attrName] = attrValue;
3212                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3213                                                         }
3214                                                 }
3215
3216                                                 attrs = sortedAttrs;
3217                                         }
3218
3219                                         writer.start(node.name, attrs, isEmpty);
3220
3221                                         if (!isEmpty) {
3222                                                 if ((node = node.firstChild)) {
3223                                                         do {
3224                                                                 walk(node);
3225                                                         } while (node = node.next);
3226                                                 }
3227
3228                                                 writer.end(name);
3229                                         }
3230                                 } else
3231                                         handler(node);
3232                         }
3233
3234                         // Serialize element and treat all non elements as fragments
3235                         if (node.type == 1 && !settings.inner)
3236                                 walk(node);
3237                         else
3238                                 handlers[11](node);
3239
3240                         return writer.getContent();
3241                 };
3242         }
3243 })(tinymce);
3244
3245 (function(tinymce) {
3246         // Shorten names
3247         var each = tinymce.each,
3248                 is = tinymce.is,
3249                 isWebKit = tinymce.isWebKit,
3250                 isIE = tinymce.isIE,
3251                 Entities = tinymce.html.Entities,
3252                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
3253                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
3254                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
3255
3256         tinymce.create('tinymce.dom.DOMUtils', {
3257                 doc : null,
3258                 root : null,
3259                 files : null,
3260                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3261                 props : {
3262                         "for" : "htmlFor",
3263                         "class" : "className",
3264                         className : "className",
3265                         checked : "checked",
3266                         disabled : "disabled",
3267                         maxlength : "maxLength",
3268                         readonly : "readOnly",
3269                         selected : "selected",
3270                         value : "value",
3271                         id : "id",
3272                         name : "name",
3273                         type : "type"
3274                 },
3275
3276                 DOMUtils : function(d, s) {
3277                         var t = this, globalStyle;
3278
3279                         t.doc = d;
3280                         t.win = window;
3281                         t.files = {};
3282                         t.cssFlicker = false;
3283                         t.counter = 0;
3284                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3285                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3286                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
3287
3288                         t.settings = s = tinymce.extend({
3289                                 keep_values : false,
3290                                 hex_colors : 1
3291                         }, s);
3292                         
3293                         t.schema = s.schema;
3294                         t.styles = new tinymce.html.Styles({
3295                                 url_converter : s.url_converter,
3296                                 url_converter_scope : s.url_converter_scope
3297                         }, s.schema);
3298
3299                         // Fix IE6SP2 flicker and check it failed for pre SP2
3300                         if (tinymce.isIE6) {
3301                                 try {
3302                                         d.execCommand('BackgroundImageCache', false, true);
3303                                 } catch (e) {
3304                                         t.cssFlicker = true;
3305                                 }
3306                         }
3307
3308                         if (isIE) {
3309                                 // Add missing HTML 4/5 elements to IE
3310                                 ('abbr article aside audio canvas ' +
3311                                 'details figcaption figure footer ' +
3312                                 'header hgroup mark menu meter nav ' +
3313                                 'output progress section summary ' +
3314                                 'time video').replace(/\w+/g, function(name) {
3315                                         d.createElement(name);
3316                                 });
3317                         }
3318
3319                         tinymce.addUnload(t.destroy, t);
3320                 },
3321
3322                 getRoot : function() {
3323                         var t = this, s = t.settings;
3324
3325                         return (s && t.get(s.root_element)) || t.doc.body;
3326                 },
3327
3328                 getViewPort : function(w) {
3329                         var d, b;
3330
3331                         w = !w ? this.win : w;
3332                         d = w.document;
3333                         b = this.boxModel ? d.documentElement : d.body;
3334
3335                         // Returns viewport size excluding scrollbars
3336                         return {
3337                                 x : w.pageXOffset || b.scrollLeft,
3338                                 y : w.pageYOffset || b.scrollTop,
3339                                 w : w.innerWidth || b.clientWidth,
3340                                 h : w.innerHeight || b.clientHeight
3341                         };
3342                 },
3343
3344                 getRect : function(e) {
3345                         var p, t = this, sr;
3346
3347                         e = t.get(e);
3348                         p = t.getPos(e);
3349                         sr = t.getSize(e);
3350
3351                         return {
3352                                 x : p.x,
3353                                 y : p.y,
3354                                 w : sr.w,
3355                                 h : sr.h
3356                         };
3357                 },
3358
3359                 getSize : function(e) {
3360                         var t = this, w, h;
3361
3362                         e = t.get(e);
3363                         w = t.getStyle(e, 'width');
3364                         h = t.getStyle(e, 'height');
3365
3366                         // Non pixel value, then force offset/clientWidth
3367                         if (w.indexOf('px') === -1)
3368                                 w = 0;
3369
3370                         // Non pixel value, then force offset/clientWidth
3371                         if (h.indexOf('px') === -1)
3372                                 h = 0;
3373
3374                         return {
3375                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3376                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
3377                         };
3378                 },
3379
3380                 getParent : function(n, f, r) {
3381                         return this.getParents(n, f, r, false);
3382                 },
3383
3384                 getParents : function(n, f, r, c) {
3385                         var t = this, na, se = t.settings, o = [];
3386
3387                         n = t.get(n);
3388                         c = c === undefined;
3389
3390                         if (se.strict_root)
3391                                 r = r || t.getRoot();
3392
3393                         // Wrap node name as func
3394                         if (is(f, 'string')) {
3395                                 na = f;
3396
3397                                 if (f === '*') {
3398                                         f = function(n) {return n.nodeType == 1;};
3399                                 } else {
3400                                         f = function(n) {
3401                                                 return t.is(n, na);
3402                                         };
3403                                 }
3404                         }
3405
3406                         while (n) {
3407                                 if (n == r || !n.nodeType || n.nodeType === 9)
3408                                         break;
3409
3410                                 if (!f || f(n)) {
3411                                         if (c)
3412                                                 o.push(n);
3413                                         else
3414                                                 return n;
3415                                 }
3416
3417                                 n = n.parentNode;
3418                         }
3419
3420                         return c ? o : null;
3421                 },
3422
3423                 get : function(e) {
3424                         var n;
3425
3426                         if (e && this.doc && typeof(e) == 'string') {
3427                                 n = e;
3428                                 e = this.doc.getElementById(e);
3429
3430                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3431                                 if (e && e.id !== n)
3432                                         return this.doc.getElementsByName(n)[1];
3433                         }
3434
3435                         return e;
3436                 },
3437
3438                 getNext : function(node, selector) {
3439                         return this._findSib(node, selector, 'nextSibling');
3440                 },
3441
3442                 getPrev : function(node, selector) {
3443                         return this._findSib(node, selector, 'previousSibling');
3444                 },
3445
3446
3447                 select : function(pa, s) {
3448                         var t = this;
3449
3450                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
3451                 },
3452
3453                 is : function(n, selector) {
3454                         var i;
3455
3456                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
3457                         if (n.length === undefined) {
3458                                 // Simple all selector
3459                                 if (selector === '*')
3460                                         return n.nodeType == 1;
3461
3462                                 // Simple selector just elements
3463                                 if (simpleSelectorRe.test(selector)) {
3464                                         selector = selector.toLowerCase().split(/,/);
3465                                         n = n.nodeName.toLowerCase();
3466
3467                                         for (i = selector.length - 1; i >= 0; i--) {
3468                                                 if (selector[i] == n)
3469                                                         return true;
3470                                         }
3471
3472                                         return false;
3473                                 }
3474                         }
3475
3476                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
3477                 },
3478
3479
3480                 add : function(p, n, a, h, c) {
3481                         var t = this;
3482
3483                         return this.run(p, function(p) {
3484                                 var e, k;
3485
3486                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
3487                                 t.setAttribs(e, a);
3488
3489                                 if (h) {
3490                                         if (h.nodeType)
3491                                                 e.appendChild(h);
3492                                         else
3493                                                 t.setHTML(e, h);
3494                                 }
3495
3496                                 return !c ? p.appendChild(e) : e;
3497                         });
3498                 },
3499
3500                 create : function(n, a, h) {
3501                         return this.add(this.doc.createElement(n), n, a, h, 1);
3502                 },
3503
3504                 createHTML : function(n, a, h) {
3505                         var o = '', t = this, k;
3506
3507                         o += '<' + n;
3508
3509                         for (k in a) {
3510                                 if (a.hasOwnProperty(k))
3511                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
3512                         }
3513
3514                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3515                         if (typeof(h) != "undefined")
3516                                 return o + '>' + h + '</' + n + '>';
3517
3518                         return o + ' />';
3519                 },
3520
3521                 remove : function(node, keep_children) {
3522                         return this.run(node, function(node) {
3523                                 var child, parent = node.parentNode;
3524
3525                                 if (!parent)
3526                                         return null;
3527
3528                                 if (keep_children) {
3529                                         while (child = node.firstChild) {
3530                                                 // IE 8 will crash if you don't remove completely empty text nodes
3531                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3532                                                         parent.insertBefore(child, node);
3533                                                 else
3534                                                         node.removeChild(child);
3535                                         }
3536                                 }
3537
3538                                 return parent.removeChild(node);
3539                         });
3540                 },
3541
3542                 setStyle : function(n, na, v) {
3543                         var t = this;
3544
3545                         return t.run(n, function(e) {
3546                                 var s, i;
3547
3548                                 s = e.style;
3549
3550                                 // Camelcase it, if needed
3551                                 na = na.replace(/-(\D)/g, function(a, b){
3552                                         return b.toUpperCase();
3553                                 });
3554
3555                                 // Default px suffix on these
3556                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3557                                         v += 'px';
3558
3559                                 switch (na) {
3560                                         case 'opacity':
3561                                                 // IE specific opacity
3562                                                 if (isIE) {
3563                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3564
3565                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
3566                                                                 s.display = 'inline-block';
3567                                                 }
3568
3569                                                 // Fix for older browsers
3570                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3571                                                 break;
3572
3573                                         case 'float':
3574                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
3575                                                 break;
3576                                         
3577                                         default:
3578                                                 s[na] = v || '';
3579                                 }
3580
3581                                 // Force update of the style data
3582                                 if (t.settings.update_styles)
3583                                         t.setAttrib(e, 'data-mce-style');
3584                         });
3585                 },
3586
3587                 getStyle : function(n, na, c) {
3588                         n = this.get(n);
3589
3590                         if (!n)
3591                                 return;
3592
3593                         // Gecko
3594                         if (this.doc.defaultView && c) {
3595                                 // Remove camelcase
3596                                 na = na.replace(/[A-Z]/g, function(a){
3597                                         return '-' + a;
3598                                 });
3599
3600                                 try {
3601                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3602                                 } catch (ex) {
3603                                         // Old safari might fail
3604                                         return null;
3605                                 }
3606                         }
3607
3608                         // Camelcase it, if needed
3609                         na = na.replace(/-(\D)/g, function(a, b){
3610                                 return b.toUpperCase();
3611                         });
3612
3613                         if (na == 'float')
3614                                 na = isIE ? 'styleFloat' : 'cssFloat';
3615
3616                         // IE & Opera
3617                         if (n.currentStyle && c)
3618                                 return n.currentStyle[na];
3619
3620                         return n.style ? n.style[na] : undefined;
3621                 },
3622
3623                 setStyles : function(e, o) {
3624                         var t = this, s = t.settings, ol;
3625
3626                         ol = s.update_styles;
3627                         s.update_styles = 0;
3628
3629                         each(o, function(v, n) {
3630                                 t.setStyle(e, n, v);
3631                         });
3632
3633                         // Update style info
3634                         s.update_styles = ol;
3635                         if (s.update_styles)
3636                                 t.setAttrib(e, s.cssText);
3637                 },
3638
3639                 removeAllAttribs: function(e) {
3640                         return this.run(e, function(e) {
3641                                 var i, attrs = e.attributes;
3642                                 for (i = attrs.length - 1; i >= 0; i--) {
3643                                         e.removeAttributeNode(attrs.item(i));
3644                                 }
3645                         });
3646                 },
3647
3648                 setAttrib : function(e, n, v) {
3649                         var t = this;
3650
3651                         // Whats the point
3652                         if (!e || !n)
3653                                 return;
3654
3655                         // Strict XML mode
3656                         if (t.settings.strict)
3657                                 n = n.toLowerCase();
3658
3659                         return this.run(e, function(e) {
3660                                 var s = t.settings;
3661
3662                                 switch (n) {
3663                                         case "style":
3664                                                 if (!is(v, 'string')) {
3665                                                         each(v, function(v, n) {
3666                                                                 t.setStyle(e, n, v);
3667                                                         });
3668
3669                                                         return;
3670                                                 }
3671
3672                                                 // No mce_style for elements with these since they might get resized by the user
3673                                                 if (s.keep_values) {
3674                                                         if (v && !t._isRes(v))
3675                                                                 e.setAttribute('data-mce-style', v, 2);
3676                                                         else
3677                                                                 e.removeAttribute('data-mce-style', 2);
3678                                                 }
3679
3680                                                 e.style.cssText = v;
3681                                                 break;
3682
3683                                         case "class":
3684                                                 e.className = v || ''; // Fix IE null bug
3685                                                 break;
3686
3687                                         case "src":
3688                                         case "href":
3689                                                 if (s.keep_values) {
3690                                                         if (s.url_converter)
3691                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3692
3693                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
3694                                                 }
3695
3696                                                 break;
3697
3698                                         case "shape":
3699                                                 e.setAttribute('data-mce-style', v);
3700                                                 break;
3701                                 }
3702
3703                                 if (is(v) && v !== null && v.length !== 0)
3704                                         e.setAttribute(n, '' + v, 2);
3705                                 else
3706                                         e.removeAttribute(n, 2);
3707                         });
3708                 },
3709
3710                 setAttribs : function(e, o) {
3711                         var t = this;
3712
3713                         return this.run(e, function(e) {
3714                                 each(o, function(v, n) {
3715                                         t.setAttrib(e, n, v);
3716                                 });
3717                         });
3718                 },
3719
3720                 getAttrib : function(e, n, dv) {
3721                         var v, t = this;
3722
3723                         e = t.get(e);
3724
3725                         if (!e || e.nodeType !== 1)
3726                                 return false;
3727
3728                         if (!is(dv))
3729                                 dv = '';
3730
3731                         // Try the mce variant for these
3732                         if (/^(src|href|style|coords|shape)$/.test(n)) {
3733                                 v = e.getAttribute("data-mce-" + n);
3734
3735                                 if (v)
3736                                         return v;
3737                         }
3738
3739                         if (isIE && t.props[n]) {
3740                                 v = e[t.props[n]];
3741                                 v = v && v.nodeValue ? v.nodeValue : v;
3742                         }
3743
3744                         if (!v)
3745                                 v = e.getAttribute(n, 2);
3746
3747                         // Check boolean attribs
3748                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
3749                                 if (e[t.props[n]] === true && v === '')
3750                                         return n;
3751
3752                                 return v ? n : '';
3753                         }
3754
3755                         // Inner input elements will override attributes on form elements
3756                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
3757                                 return e.getAttributeNode(n).nodeValue;
3758
3759                         if (n === 'style') {
3760                                 v = v || e.style.cssText;
3761
3762                                 if (v) {
3763                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
3764
3765                                         if (t.settings.keep_values && !t._isRes(v))
3766                                                 e.setAttribute('data-mce-style', v);
3767                                 }
3768                         }
3769
3770                         // Remove Apple and WebKit stuff
3771                         if (isWebKit && n === "class" && v)
3772                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3773
3774                         // Handle IE issues
3775                         if (isIE) {
3776                                 switch (n) {
3777                                         case 'rowspan':
3778                                         case 'colspan':
3779                                                 // IE returns 1 as default value
3780                                                 if (v === 1)
3781                                                         v = '';
3782
3783                                                 break;
3784
3785                                         case 'size':
3786                                                 // IE returns +0 as default value for size
3787                                                 if (v === '+0' || v === 20 || v === 0)
3788                                                         v = '';
3789
3790                                                 break;
3791
3792                                         case 'width':
3793                                         case 'height':
3794                                         case 'vspace':
3795                                         case 'checked':
3796                                         case 'disabled':
3797                                         case 'readonly':
3798                                                 if (v === 0)
3799                                                         v = '';
3800
3801                                                 break;
3802
3803                                         case 'hspace':
3804                                                 // IE returns -1 as default value
3805                                                 if (v === -1)
3806                                                         v = '';
3807
3808                                                 break;
3809
3810                                         case 'maxlength':
3811                                         case 'tabindex':
3812                                                 // IE returns default value
3813                                                 if (v === 32768 || v === 2147483647 || v === '32768')
3814                                                         v = '';
3815
3816                                                 break;
3817
3818                                         case 'multiple':
3819                                         case 'compact':
3820                                         case 'noshade':
3821                                         case 'nowrap':
3822                                                 if (v === 65535)
3823                                                         return n;
3824
3825                                                 return dv;
3826
3827                                         case 'shape':
3828                                                 v = v.toLowerCase();
3829                                                 break;
3830
3831                                         default:
3832                                                 // IE has odd anonymous function for event attributes
3833                                                 if (n.indexOf('on') === 0 && v)
3834                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
3835                                 }
3836                         }
3837
3838                         return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
3839                 },
3840
3841                 getPos : function(n, ro) {
3842                         var t = this, x = 0, y = 0, e, d = t.doc, r;
3843
3844                         n = t.get(n);
3845                         ro = ro || d.body;
3846
3847                         if (n) {
3848                                 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
3849                                 if (isIE && !t.stdMode) {
3850                                         n = n.getBoundingClientRect();
3851                                         e = t.boxModel ? d.documentElement : d.body;
3852                                         x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
3853                                         x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
3854
3855                                         return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
3856                                 }
3857
3858                                 r = n;
3859                                 while (r && r != ro && r.nodeType) {
3860                                         x += r.offsetLeft || 0;
3861                                         y += r.offsetTop || 0;
3862                                         r = r.offsetParent;
3863                                 }
3864
3865                                 r = n.parentNode;
3866                                 while (r && r != ro && r.nodeType) {
3867                                         x -= r.scrollLeft || 0;
3868                                         y -= r.scrollTop || 0;
3869                                         r = r.parentNode;
3870                                 }
3871                         }
3872
3873                         return {x : x, y : y};
3874                 },
3875
3876                 parseStyle : function(st) {
3877                         return this.styles.parse(st);
3878                 },
3879
3880                 serializeStyle : function(o, name) {
3881                         return this.styles.serialize(o, name);
3882                 },
3883
3884                 loadCSS : function(u) {
3885                         var t = this, d = t.doc, head;
3886
3887                         if (!u)
3888                                 u = '';
3889
3890                         head = t.select('head')[0];
3891
3892                         each(u.split(','), function(u) {
3893                                 var link;
3894
3895                                 if (t.files[u])
3896                                         return;
3897
3898                                 t.files[u] = true;
3899                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
3900
3901                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
3902                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
3903                                 // It's ugly but it seems to work fine.
3904                                 if (isIE && d.documentMode && d.recalc) {
3905                                         link.onload = function() {
3906                                                 if (d.recalc)
3907                                                         d.recalc();
3908
3909                                                 link.onload = null;
3910                                         };
3911                                 }
3912
3913                                 head.appendChild(link);
3914                         });
3915                 },
3916
3917                 addClass : function(e, c) {
3918                         return this.run(e, function(e) {
3919                                 var o;
3920
3921                                 if (!c)
3922                                         return 0;
3923
3924                                 if (this.hasClass(e, c))
3925                                         return e.className;
3926
3927                                 o = this.removeClass(e, c);
3928
3929                                 return e.className = (o != '' ? (o + ' ') : '') + c;
3930                         });
3931                 },
3932
3933                 removeClass : function(e, c) {
3934                         var t = this, re;
3935
3936                         return t.run(e, function(e) {
3937                                 var v;
3938
3939                                 if (t.hasClass(e, c)) {
3940                                         if (!re)
3941                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
3942
3943                                         v = e.className.replace(re, ' ');
3944                                         v = tinymce.trim(v != ' ' ? v : '');
3945
3946                                         e.className = v;
3947
3948                                         // Empty class attr
3949                                         if (!v) {
3950                                                 e.removeAttribute('class');
3951                                                 e.removeAttribute('className');
3952                                         }
3953
3954                                         return v;
3955                                 }
3956
3957                                 return e.className;
3958                         });
3959                 },
3960
3961                 hasClass : function(n, c) {
3962                         n = this.get(n);
3963
3964                         if (!n || !c)
3965                                 return false;
3966
3967                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
3968                 },
3969
3970                 show : function(e) {
3971                         return this.setStyle(e, 'display', 'block');
3972                 },
3973
3974                 hide : function(e) {
3975                         return this.setStyle(e, 'display', 'none');
3976                 },
3977
3978                 isHidden : function(e) {
3979                         e = this.get(e);
3980
3981                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
3982                 },
3983
3984                 uniqueId : function(p) {
3985                         return (!p ? 'mce_' : p) + (this.counter++);
3986                 },
3987
3988                 setHTML : function(element, html) {
3989                         var self = this;
3990
3991                         return self.run(element, function(element) {
3992                                 if (isIE) {
3993                                         // Remove all child nodes, IE keeps empty text nodes in DOM
3994                                         while (element.firstChild)
3995                                                 element.removeChild(element.firstChild);
3996
3997                                         try {
3998                                                 // IE will remove comments from the beginning
3999                                                 // unless you padd the contents with something
4000                                                 element.innerHTML = '<br />' + html;
4001                                                 element.removeChild(element.firstChild);
4002                                         } catch (ex) {
4003                                                 // 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
4004                                                 // This seems to fix this problem
4005
4006                                                 // Create new div with HTML contents and a BR infront to keep comments
4007                                                 element = self.create('div');
4008                                                 element.innerHTML = '<br />' + html;
4009
4010                                                 // Add all children from div to target
4011                                                 each (element.childNodes, function(node, i) {
4012                                                         // Skip br element
4013                                                         if (i)
4014                                                                 element.appendChild(node);
4015                                                 });
4016                                         }
4017                                 } else
4018                                         element.innerHTML = html;
4019
4020                                 return html;
4021                         });
4022                 },
4023
4024                 getOuterHTML : function(elm) {
4025                         var doc, self = this;
4026
4027                         elm = self.get(elm);
4028
4029                         if (!elm)
4030                                 return null;
4031
4032                         if (elm.nodeType === 1 && self.hasOuterHTML)
4033                                 return elm.outerHTML;
4034
4035                         doc = (elm.ownerDocument || self.doc).createElement("body");
4036                         doc.appendChild(elm.cloneNode(true));
4037
4038                         return doc.innerHTML;
4039                 },
4040
4041                 setOuterHTML : function(e, h, d) {
4042                         var t = this;
4043
4044                         function setHTML(e, h, d) {
4045                                 var n, tp;
4046
4047                                 tp = d.createElement("body");
4048                                 tp.innerHTML = h;
4049
4050                                 n = tp.lastChild;
4051                                 while (n) {
4052                                         t.insertAfter(n.cloneNode(true), e);
4053                                         n = n.previousSibling;
4054                                 }
4055
4056                                 t.remove(e);
4057                         };
4058
4059                         return this.run(e, function(e) {
4060                                 e = t.get(e);
4061
4062                                 // Only set HTML on elements
4063                                 if (e.nodeType == 1) {
4064                                         d = d || e.ownerDocument || t.doc;
4065
4066                                         if (isIE) {
4067                                                 try {
4068                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
4069                                                         if (isIE && e.nodeType == 1)
4070                                                                 e.outerHTML = h;
4071                                                         else
4072                                                                 setHTML(e, h, d);
4073                                                 } catch (ex) {
4074                                                         // Fix for unknown runtime error
4075                                                         setHTML(e, h, d);
4076                                                 }
4077                                         } else
4078                                                 setHTML(e, h, d);
4079                                 }
4080                         });
4081                 },
4082
4083                 decode : Entities.decode,
4084
4085                 encode : Entities.encodeAllRaw,
4086
4087                 insertAfter : function(node, reference_node) {
4088                         reference_node = this.get(reference_node);
4089
4090                         return this.run(node, function(node) {
4091                                 var parent, nextSibling;
4092
4093                                 parent = reference_node.parentNode;
4094                                 nextSibling = reference_node.nextSibling;
4095
4096                                 if (nextSibling)
4097                                         parent.insertBefore(node, nextSibling);
4098                                 else
4099                                         parent.appendChild(node);
4100
4101                                 return node;
4102                         });
4103                 },
4104
4105                 isBlock : function(node) {
4106                         var type = node.nodeType;
4107
4108                         // If it's a node then check the type and use the nodeName
4109                         if (type)
4110                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
4111
4112                         return !!blockElementsMap[node];
4113                 },
4114
4115                 replace : function(n, o, k) {
4116                         var t = this;
4117
4118                         if (is(o, 'array'))
4119                                 n = n.cloneNode(true);
4120
4121                         return t.run(o, function(o) {
4122                                 if (k) {
4123                                         each(tinymce.grep(o.childNodes), function(c) {
4124                                                 n.appendChild(c);
4125                                         });
4126                                 }
4127
4128                                 return o.parentNode.replaceChild(n, o);
4129                         });
4130                 },
4131
4132                 rename : function(elm, name) {
4133                         var t = this, newElm;
4134
4135                         if (elm.nodeName != name.toUpperCase()) {
4136                                 // Rename block element
4137                                 newElm = t.create(name);
4138
4139                                 // Copy attribs to new block
4140                                 each(t.getAttribs(elm), function(attr_node) {
4141                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4142                                 });
4143
4144                                 // Replace block
4145                                 t.replace(newElm, elm, 1);
4146                         }
4147
4148                         return newElm || elm;
4149                 },
4150
4151                 findCommonAncestor : function(a, b) {
4152                         var ps = a, pe;
4153
4154                         while (ps) {
4155                                 pe = b;
4156
4157                                 while (pe && ps != pe)
4158                                         pe = pe.parentNode;
4159
4160                                 if (ps == pe)
4161                                         break;
4162
4163                                 ps = ps.parentNode;
4164                         }
4165
4166                         if (!ps && a.ownerDocument)
4167                                 return a.ownerDocument.documentElement;
4168
4169                         return ps;
4170                 },
4171
4172                 toHex : function(s) {
4173                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4174
4175                         function hex(s) {
4176                                 s = parseInt(s).toString(16);
4177
4178                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
4179                         };
4180
4181                         if (c) {
4182                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4183
4184                                 return s;
4185                         }
4186
4187                         return s;
4188                 },
4189
4190                 getClasses : function() {
4191                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4192
4193                         if (t.classes)
4194                                 return t.classes;
4195
4196                         function addClasses(s) {
4197                                 // IE style imports
4198                                 each(s.imports, function(r) {
4199                                         addClasses(r);
4200                                 });
4201
4202                                 each(s.cssRules || s.rules, function(r) {
4203                                         // Real type or fake it on IE
4204                                         switch (r.type || 1) {
4205                                                 // Rule
4206                                                 case 1:
4207                                                         if (r.selectorText) {
4208                                                                 each(r.selectorText.split(','), function(v) {
4209                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
4210
4211                                                                         // Is internal or it doesn't contain a class
4212                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4213                                                                                 return;
4214
4215                                                                         // Remove everything but class name
4216                                                                         ov = v;
4217                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
4218
4219                                                                         // Filter classes
4220                                                                         if (f && !(v = f(v, ov)))
4221                                                                                 return;
4222
4223                                                                         if (!lo[v]) {
4224                                                                                 cl.push({'class' : v});
4225                                                                                 lo[v] = 1;
4226                                                                         }
4227                                                                 });
4228                                                         }
4229                                                         break;
4230
4231                                                 // Import
4232                                                 case 3:
4233                                                         addClasses(r.styleSheet);
4234                                                         break;
4235                                         }
4236                                 });
4237                         };
4238
4239                         try {
4240                                 each(t.doc.styleSheets, addClasses);
4241                         } catch (ex) {
4242                                 // Ignore
4243                         }
4244
4245                         if (cl.length > 0)
4246                                 t.classes = cl;
4247
4248                         return cl;
4249                 },
4250
4251                 run : function(e, f, s) {
4252                         var t = this, o;
4253
4254                         if (t.doc && typeof(e) === 'string')
4255                                 e = t.get(e);
4256
4257                         if (!e)
4258                                 return false;
4259
4260                         s = s || this;
4261                         if (!e.nodeType && (e.length || e.length === 0)) {
4262                                 o = [];
4263
4264                                 each(e, function(e, i) {
4265                                         if (e) {
4266                                                 if (typeof(e) == 'string')
4267                                                         e = t.doc.getElementById(e);
4268
4269                                                 o.push(f.call(s, e, i));
4270                                         }
4271                                 });
4272
4273                                 return o;
4274                         }
4275
4276                         return f.call(s, e);
4277                 },
4278
4279                 getAttribs : function(n) {
4280                         var o;
4281
4282                         n = this.get(n);
4283
4284                         if (!n)
4285                                 return [];
4286
4287                         if (isIE) {
4288                                 o = [];
4289
4290                                 // Object will throw exception in IE
4291                                 if (n.nodeName == 'OBJECT')
4292                                         return n.attributes;
4293
4294                                 // IE doesn't keep the selected attribute if you clone option elements
4295                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4296                                         o.push({specified : 1, nodeName : 'selected'});
4297
4298                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
4299                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
4300                                         o.push({specified : 1, nodeName : a});
4301                                 });
4302
4303                                 return o;
4304                         }
4305
4306                         return n.attributes;
4307                 },
4308
4309                 isEmpty : function(node, elements) {
4310                         var self = this, i, attributes, type, walker, name;
4311
4312                         node = node.firstChild;
4313                         if (node) {
4314                                 walker = new tinymce.dom.TreeWalker(node);
4315                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4316
4317                                 do {
4318                                         type = node.nodeType;
4319
4320                                         if (type === 1) {
4321                                                 // Ignore bogus elements
4322                                                 if (node.getAttribute('data-mce-bogus'))
4323                                                         continue;
4324
4325                                                 // Keep empty elements like <img />
4326                                                 if (elements && elements[node.nodeName.toLowerCase()])
4327                                                         return false;
4328
4329                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
4330                                                 attributes = self.getAttribs(node);
4331                                                 i = node.attributes.length;
4332                                                 while (i--) {
4333                                                         name = node.attributes[i].nodeName;
4334                                                         if (name === "name" || name.indexOf('data-') === 0)
4335                                                                 return false;
4336                                                 }
4337                                         }
4338
4339                                         // Keep non whitespace text nodes
4340                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4341                                                 return false;
4342                                 } while (node = walker.next());
4343                         }
4344
4345                         return true;
4346                 },
4347
4348                 destroy : function(s) {
4349                         var t = this;
4350
4351                         if (t.events)
4352                                 t.events.destroy();
4353
4354                         t.win = t.doc = t.root = t.events = null;
4355
4356                         // Manual destroy then remove unload handler
4357                         if (!s)
4358                                 tinymce.removeUnload(t.destroy);
4359                 },
4360
4361                 createRng : function() {
4362                         var d = this.doc;
4363
4364                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4365                 },
4366
4367                 nodeIndex : function(node, normalized) {
4368                         var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
4369
4370                         if (node) {
4371                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4372                                         nodeType = node.nodeType;
4373
4374                                         // Normalize text nodes
4375                                         if (normalized && nodeType == 3) {
4376                                                 // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
4377                                                 // (the nodeValue attribute will not exist, and will error here).
4378                                                 nodeValueExists = false;
4379                                                 try {nodeValueExists = node.nodeValue.length} catch (c) {}
4380                                                 if (nodeType == lastNodeType || !nodeValueExists)
4381                                                         continue;
4382                                         }
4383                                         idx++;
4384                                         lastNodeType = nodeType;
4385                                 }
4386                         }
4387
4388                         return idx;
4389                 },
4390
4391                 split : function(pe, e, re) {
4392                         var t = this, r = t.createRng(), bef, aft, pa;
4393
4394                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
4395                         // but we don't want that in our code since it serves no purpose for the end user
4396                         // For example if this is chopped:
4397                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4398                         // would produce:
4399                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4400                         // this function will then trim of empty edges and produce:
4401                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4402                         function trim(node) {
4403                                 var i, children = node.childNodes, type = node.nodeType;
4404
4405                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4406                                         return;
4407
4408                                 for (i = children.length - 1; i >= 0; i--)
4409                                         trim(children[i]);
4410
4411                                 if (type != 9) {
4412                                         // Keep non whitespace text nodes
4413                                         if (type == 3 && node.nodeValue.length > 0) {
4414                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4415                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4416                                                         return;
4417                                         } else if (type == 1) {
4418                                                 // If the only child is a bookmark then move it up
4419                                                 children = node.childNodes;
4420                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
4421                                                         node.parentNode.insertBefore(children[0], node);
4422
4423                                                 // Keep non empty elements or img, hr etc
4424                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4425                                                         return;
4426                                         }
4427
4428                                         t.remove(node);
4429                                 }
4430
4431                                 return node;
4432                         };
4433
4434                         if (pe && e) {
4435                                 // Get before chunk
4436                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
4437                                 r.setEnd(e.parentNode, t.nodeIndex(e));
4438                                 bef = r.extractContents();
4439
4440                                 // Get after chunk
4441                                 r = t.createRng();
4442                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
4443                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
4444                                 aft = r.extractContents();
4445
4446                                 // Insert before chunk
4447                                 pa = pe.parentNode;
4448                                 pa.insertBefore(trim(bef), pe);
4449
4450                                 // Insert middle chunk
4451                                 if (re)
4452                                         pa.replaceChild(re, e);
4453                                 else
4454                                         pa.insertBefore(e, pe);
4455
4456                                 // Insert after chunk
4457                                 pa.insertBefore(trim(aft), pe);
4458                                 t.remove(pe);
4459
4460                                 return re || e;
4461                         }
4462                 },
4463
4464                 bind : function(target, name, func, scope) {
4465                         var t = this;
4466
4467                         if (!t.events)
4468                                 t.events = new tinymce.dom.EventUtils();
4469
4470                         return t.events.add(target, name, func, scope || this);
4471                 },
4472
4473                 unbind : function(target, name, func) {
4474                         var t = this;
4475
4476                         if (!t.events)
4477                                 t.events = new tinymce.dom.EventUtils();
4478
4479                         return t.events.remove(target, name, func);
4480                 },
4481
4482
4483                 _findSib : function(node, selector, name) {
4484                         var t = this, f = selector;
4485
4486                         if (node) {
4487                                 // If expression make a function of it using is
4488                                 if (is(f, 'string')) {
4489                                         f = function(node) {
4490                                                 return t.is(node, selector);
4491                                         };
4492                                 }
4493
4494                                 // Loop all siblings
4495                                 for (node = node[name]; node; node = node[name]) {
4496                                         if (f(node))
4497                                                 return node;
4498                                 }
4499                         }
4500
4501                         return null;
4502                 },
4503
4504                 _isRes : function(c) {
4505                         // Is live resizble element
4506                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
4507                 }
4508
4509                 /*
4510                 walk : function(n, f, s) {
4511                         var d = this.doc, w;
4512
4513                         if (d.createTreeWalker) {
4514                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4515
4516                                 while ((n = w.nextNode()) != null)
4517                                         f.call(s || this, n);
4518                         } else
4519                                 tinymce.walk(n, f, 'childNodes', s);
4520                 }
4521                 */
4522
4523                 /*
4524                 toRGB : function(s) {
4525                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4526
4527                         if (c) {
4528                                 // #FFF -> #FFFFFF
4529                                 if (!is(c[3]))
4530                                         c[3] = c[2] = c[1];
4531
4532                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4533                         }
4534
4535                         return s;
4536                 }
4537                 */
4538         });
4539
4540         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
4541 })(tinymce);
4542
4543 (function(ns) {
4544         // Range constructor
4545         function Range(dom) {
4546                 var t = this,
4547                         doc = dom.doc,
4548                         EXTRACT = 0,
4549                         CLONE = 1,
4550                         DELETE = 2,
4551                         TRUE = true,
4552                         FALSE = false,
4553                         START_OFFSET = 'startOffset',
4554                         START_CONTAINER = 'startContainer',
4555                         END_CONTAINER = 'endContainer',
4556                         END_OFFSET = 'endOffset',
4557                         extend = tinymce.extend,
4558                         nodeIndex = dom.nodeIndex;
4559
4560                 extend(t, {
4561                         // Inital states
4562                         startContainer : doc,
4563                         startOffset : 0,
4564                         endContainer : doc,
4565                         endOffset : 0,
4566                         collapsed : TRUE,
4567                         commonAncestorContainer : doc,
4568
4569                         // Range constants
4570                         START_TO_START : 0,
4571                         START_TO_END : 1,
4572                         END_TO_END : 2,
4573                         END_TO_START : 3,
4574
4575                         // Public methods
4576                         setStart : setStart,
4577                         setEnd : setEnd,
4578                         setStartBefore : setStartBefore,
4579                         setStartAfter : setStartAfter,
4580                         setEndBefore : setEndBefore,
4581                         setEndAfter : setEndAfter,
4582                         collapse : collapse,
4583                         selectNode : selectNode,
4584                         selectNodeContents : selectNodeContents,
4585                         compareBoundaryPoints : compareBoundaryPoints,
4586                         deleteContents : deleteContents,
4587                         extractContents : extractContents,
4588                         cloneContents : cloneContents,
4589                         insertNode : insertNode,
4590                         surroundContents : surroundContents,
4591                         cloneRange : cloneRange
4592                 });
4593
4594                 function setStart(n, o) {
4595                         _setEndPoint(TRUE, n, o);
4596                 };
4597
4598                 function setEnd(n, o) {
4599                         _setEndPoint(FALSE, n, o);
4600                 };
4601
4602                 function setStartBefore(n) {
4603                         setStart(n.parentNode, nodeIndex(n));
4604                 };
4605
4606                 function setStartAfter(n) {
4607                         setStart(n.parentNode, nodeIndex(n) + 1);
4608                 };
4609
4610                 function setEndBefore(n) {
4611                         setEnd(n.parentNode, nodeIndex(n));
4612                 };
4613
4614                 function setEndAfter(n) {
4615                         setEnd(n.parentNode, nodeIndex(n) + 1);
4616                 };
4617
4618                 function collapse(ts) {
4619                         if (ts) {
4620                                 t[END_CONTAINER] = t[START_CONTAINER];
4621                                 t[END_OFFSET] = t[START_OFFSET];
4622                         } else {
4623                                 t[START_CONTAINER] = t[END_CONTAINER];
4624                                 t[START_OFFSET] = t[END_OFFSET];
4625                         }
4626
4627                         t.collapsed = TRUE;
4628                 };
4629
4630                 function selectNode(n) {
4631                         setStartBefore(n);
4632                         setEndAfter(n);
4633                 };
4634
4635                 function selectNodeContents(n) {
4636                         setStart(n, 0);
4637                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
4638                 };
4639
4640                 function compareBoundaryPoints(h, r) {
4641                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
4642                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
4643
4644                         // Check START_TO_START
4645                         if (h === 0)
4646                                 return _compareBoundaryPoints(sc, so, rsc, rso);
4647         
4648                         // Check START_TO_END
4649                         if (h === 1)
4650                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
4651         
4652                         // Check END_TO_END
4653                         if (h === 2)
4654                                 return _compareBoundaryPoints(ec, eo, rec, reo);
4655         
4656                         // Check END_TO_START
4657                         if (h === 3) 
4658                                 return _compareBoundaryPoints(sc, so, rec, reo);
4659                 };
4660
4661                 function deleteContents() {
4662                         _traverse(DELETE);
4663                 };
4664
4665                 function extractContents() {
4666                         return _traverse(EXTRACT);
4667                 };
4668
4669                 function cloneContents() {
4670                         return _traverse(CLONE);
4671                 };
4672
4673                 function insertNode(n) {
4674                         var startContainer = this[START_CONTAINER],
4675                                 startOffset = this[START_OFFSET], nn, o;
4676
4677                         // Node is TEXT_NODE or CDATA
4678                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
4679                                 if (!startOffset) {
4680                                         // At the start of text
4681                                         startContainer.parentNode.insertBefore(n, startContainer);
4682                                 } else if (startOffset >= startContainer.nodeValue.length) {
4683                                         // At the end of text
4684                                         dom.insertAfter(n, startContainer);
4685                                 } else {
4686                                         // Middle, need to split
4687                                         nn = startContainer.splitText(startOffset);
4688                                         startContainer.parentNode.insertBefore(n, nn);
4689                                 }
4690                         } else {
4691                                 // Insert element node
4692                                 if (startContainer.childNodes.length > 0)
4693                                         o = startContainer.childNodes[startOffset];
4694
4695                                 if (o)
4696                                         startContainer.insertBefore(n, o);
4697                                 else
4698                                         startContainer.appendChild(n);
4699                         }
4700                 };
4701
4702                 function surroundContents(n) {
4703                         var f = t.extractContents();
4704
4705                         t.insertNode(n);
4706                         n.appendChild(f);
4707                         t.selectNode(n);
4708                 };
4709
4710                 function cloneRange() {
4711                         return extend(new Range(dom), {
4712                                 startContainer : t[START_CONTAINER],
4713                                 startOffset : t[START_OFFSET],
4714                                 endContainer : t[END_CONTAINER],
4715                                 endOffset : t[END_OFFSET],
4716                                 collapsed : t.collapsed,
4717                                 commonAncestorContainer : t.commonAncestorContainer
4718                         });
4719                 };
4720
4721                 // Private methods
4722
4723                 function _getSelectedNode(container, offset) {
4724                         var child;
4725
4726                         if (container.nodeType == 3 /* TEXT_NODE */)
4727                                 return container;
4728
4729                         if (offset < 0)
4730                                 return container;
4731
4732                         child = container.firstChild;
4733                         while (child && offset > 0) {
4734                                 --offset;
4735                                 child = child.nextSibling;
4736                         }
4737
4738                         if (child)
4739                                 return child;
4740
4741                         return container;
4742                 };
4743
4744                 function _isCollapsed() {
4745                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
4746                 };
4747
4748                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
4749                         var c, offsetC, n, cmnRoot, childA, childB;
4750                         
4751                         // In the first case the boundary-points have the same container. A is before B
4752                         // if its offset is less than the offset of B, A is equal to B if its offset is
4753                         // equal to the offset of B, and A is after B if its offset is greater than the
4754                         // offset of B.
4755                         if (containerA == containerB) {
4756                                 if (offsetA == offsetB)
4757                                         return 0; // equal
4758
4759                                 if (offsetA < offsetB)
4760                                         return -1; // before
4761
4762                                 return 1; // after
4763                         }
4764
4765                         // In the second case a child node C of the container of A is an ancestor
4766                         // container of B. In this case, A is before B if the offset of A is less than or
4767                         // equal to the index of the child node C and A is after B otherwise.
4768                         c = containerB;
4769                         while (c && c.parentNode != containerA)
4770                                 c = c.parentNode;
4771
4772                         if (c) {
4773                                 offsetC = 0;
4774                                 n = containerA.firstChild;
4775
4776                                 while (n != c && offsetC < offsetA) {
4777                                         offsetC++;
4778                                         n = n.nextSibling;
4779                                 }
4780
4781                                 if (offsetA <= offsetC)
4782                                         return -1; // before
4783
4784                                 return 1; // after
4785                         }
4786
4787                         // In the third case a child node C of the container of B is an ancestor container
4788                         // of A. In this case, A is before B if the index of the child node C is less than
4789                         // the offset of B and A is after B otherwise.
4790                         c = containerA;
4791                         while (c && c.parentNode != containerB) {
4792                                 c = c.parentNode;
4793                         }
4794
4795                         if (c) {
4796                                 offsetC = 0;
4797                                 n = containerB.firstChild;
4798
4799                                 while (n != c && offsetC < offsetB) {
4800                                         offsetC++;
4801                                         n = n.nextSibling;
4802                                 }
4803
4804                                 if (offsetC < offsetB)
4805                                         return -1; // before
4806
4807                                 return 1; // after
4808                         }
4809
4810                         // In the fourth case, none of three other cases hold: the containers of A and B
4811                         // are siblings or descendants of sibling nodes. In this case, A is before B if
4812                         // the container of A is before the container of B in a pre-order traversal of the
4813                         // Ranges' context tree and A is after B otherwise.
4814                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
4815                         childA = containerA;
4816
4817                         while (childA && childA.parentNode != cmnRoot)
4818                                 childA = childA.parentNode;
4819
4820                         if (!childA)
4821                                 childA = cmnRoot;
4822
4823                         childB = containerB;
4824                         while (childB && childB.parentNode != cmnRoot)
4825                                 childB = childB.parentNode;
4826
4827                         if (!childB)
4828                                 childB = cmnRoot;
4829
4830                         if (childA == childB)
4831                                 return 0; // equal
4832
4833                         n = cmnRoot.firstChild;
4834                         while (n) {
4835                                 if (n == childA)
4836                                         return -1; // before
4837
4838                                 if (n == childB)
4839                                         return 1; // after
4840
4841                                 n = n.nextSibling;
4842                         }
4843                 };
4844
4845                 function _setEndPoint(st, n, o) {
4846                         var ec, sc;
4847
4848                         if (st) {
4849                                 t[START_CONTAINER] = n;
4850                                 t[START_OFFSET] = o;
4851                         } else {
4852                                 t[END_CONTAINER] = n;
4853                                 t[END_OFFSET] = o;
4854                         }
4855
4856                         // If one boundary-point of a Range is set to have a root container
4857                         // other than the current one for the Range, the Range is collapsed to
4858                         // the new position. This enforces the restriction that both boundary-
4859                         // points of a Range must have the same root container.
4860                         ec = t[END_CONTAINER];
4861                         while (ec.parentNode)
4862                                 ec = ec.parentNode;
4863
4864                         sc = t[START_CONTAINER];
4865                         while (sc.parentNode)
4866                                 sc = sc.parentNode;
4867
4868                         if (sc == ec) {
4869                                 // The start position of a Range is guaranteed to never be after the
4870                                 // end position. To enforce this restriction, if the start is set to
4871                                 // be at a position after the end, the Range is collapsed to that
4872                                 // position.
4873                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
4874                                         t.collapse(st);
4875                         } else
4876                                 t.collapse(st);
4877
4878                         t.collapsed = _isCollapsed();
4879                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
4880                 };
4881
4882                 function _traverse(how) {
4883                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
4884
4885                         if (t[START_CONTAINER] == t[END_CONTAINER])
4886                                 return _traverseSameContainer(how);
4887
4888                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
4889                                 if (p == t[START_CONTAINER])
4890                                         return _traverseCommonStartContainer(c, how);
4891
4892                                 ++endContainerDepth;
4893                         }
4894
4895                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
4896                                 if (p == t[END_CONTAINER])
4897                                         return _traverseCommonEndContainer(c, how);
4898
4899                                 ++startContainerDepth;
4900                         }
4901
4902                         depthDiff = startContainerDepth - endContainerDepth;
4903
4904                         startNode = t[START_CONTAINER];
4905                         while (depthDiff > 0) {
4906                                 startNode = startNode.parentNode;
4907                                 depthDiff--;
4908                         }
4909
4910                         endNode = t[END_CONTAINER];
4911                         while (depthDiff < 0) {
4912                                 endNode = endNode.parentNode;
4913                                 depthDiff++;
4914                         }
4915
4916                         // ascend the ancestor hierarchy until we have a common parent.
4917                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
4918                                 startNode = sp;
4919                                 endNode = ep;
4920                         }
4921
4922                         return _traverseCommonAncestors(startNode, endNode, how);
4923                 };
4924
4925                  function _traverseSameContainer(how) {
4926                         var frag, s, sub, n, cnt, sibling, xferNode;
4927
4928                         if (how != DELETE)
4929                                 frag = doc.createDocumentFragment();
4930
4931                         // If selection is empty, just return the fragment
4932                         if (t[START_OFFSET] == t[END_OFFSET])
4933                                 return frag;
4934
4935                         // Text node needs special case handling
4936                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
4937                                 // get the substring
4938                                 s = t[START_CONTAINER].nodeValue;
4939                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
4940
4941                                 // set the original text node to its new value
4942                                 if (how != CLONE) {
4943                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
4944
4945                                         // Nothing is partially selected, so collapse to start point
4946                                         t.collapse(TRUE);
4947                                 }
4948
4949                                 if (how == DELETE)
4950                                         return;
4951
4952                                 frag.appendChild(doc.createTextNode(sub));
4953                                 return frag;
4954                         }
4955
4956                         // Copy nodes between the start/end offsets.
4957                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
4958                         cnt = t[END_OFFSET] - t[START_OFFSET];
4959
4960                         while (cnt > 0) {
4961                                 sibling = n.nextSibling;
4962                                 xferNode = _traverseFullySelected(n, how);
4963
4964                                 if (frag)
4965                                         frag.appendChild( xferNode );
4966
4967                                 --cnt;
4968                                 n = sibling;
4969                         }
4970
4971                         // Nothing is partially selected, so collapse to start point
4972                         if (how != CLONE)
4973                                 t.collapse(TRUE);
4974
4975                         return frag;
4976                 };
4977
4978                 function _traverseCommonStartContainer(endAncestor, how) {
4979                         var frag, n, endIdx, cnt, sibling, xferNode;
4980
4981                         if (how != DELETE)
4982                                 frag = doc.createDocumentFragment();
4983
4984                         n = _traverseRightBoundary(endAncestor, how);
4985
4986                         if (frag)
4987                                 frag.appendChild(n);
4988
4989                         endIdx = nodeIndex(endAncestor);
4990                         cnt = endIdx - t[START_OFFSET];
4991
4992                         if (cnt <= 0) {
4993                                 // Collapse to just before the endAncestor, which
4994                                 // is partially selected.
4995                                 if (how != CLONE) {
4996                                         t.setEndBefore(endAncestor);
4997                                         t.collapse(FALSE);
4998                                 }
4999
5000                                 return frag;
5001                         }
5002
5003                         n = endAncestor.previousSibling;
5004                         while (cnt > 0) {
5005                                 sibling = n.previousSibling;
5006                                 xferNode = _traverseFullySelected(n, how);
5007
5008                                 if (frag)
5009                                         frag.insertBefore(xferNode, frag.firstChild);
5010
5011                                 --cnt;
5012                                 n = sibling;
5013                         }
5014
5015                         // Collapse to just before the endAncestor, which
5016                         // is partially selected.
5017                         if (how != CLONE) {
5018                                 t.setEndBefore(endAncestor);
5019                                 t.collapse(FALSE);
5020                         }
5021
5022                         return frag;
5023                 };
5024
5025                 function _traverseCommonEndContainer(startAncestor, how) {
5026                         var frag, startIdx, n, cnt, sibling, xferNode;
5027
5028                         if (how != DELETE)
5029                                 frag = doc.createDocumentFragment();
5030
5031                         n = _traverseLeftBoundary(startAncestor, how);
5032                         if (frag)
5033                                 frag.appendChild(n);
5034
5035                         startIdx = nodeIndex(startAncestor);
5036                         ++startIdx; // Because we already traversed it
5037
5038                         cnt = t[END_OFFSET] - startIdx;
5039                         n = startAncestor.nextSibling;
5040                         while (cnt > 0) {
5041                                 sibling = n.nextSibling;
5042                                 xferNode = _traverseFullySelected(n, how);
5043
5044                                 if (frag)
5045                                         frag.appendChild(xferNode);
5046
5047                                 --cnt;
5048                                 n = sibling;
5049                         }
5050
5051                         if (how != CLONE) {
5052                                 t.setStartAfter(startAncestor);
5053                                 t.collapse(TRUE);
5054                         }
5055
5056                         return frag;
5057                 };
5058
5059                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
5060                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
5061
5062                         if (how != DELETE)
5063                                 frag = doc.createDocumentFragment();
5064
5065                         n = _traverseLeftBoundary(startAncestor, how);
5066                         if (frag)
5067                                 frag.appendChild(n);
5068
5069                         commonParent = startAncestor.parentNode;
5070                         startOffset = nodeIndex(startAncestor);
5071                         endOffset = nodeIndex(endAncestor);
5072                         ++startOffset;
5073
5074                         cnt = endOffset - startOffset;
5075                         sibling = startAncestor.nextSibling;
5076
5077                         while (cnt > 0) {
5078                                 nextSibling = sibling.nextSibling;
5079                                 n = _traverseFullySelected(sibling, how);
5080
5081                                 if (frag)
5082                                         frag.appendChild(n);
5083
5084                                 sibling = nextSibling;
5085                                 --cnt;
5086                         }
5087
5088                         n = _traverseRightBoundary(endAncestor, how);
5089
5090                         if (frag)
5091                                 frag.appendChild(n);
5092
5093                         if (how != CLONE) {
5094                                 t.setStartAfter(startAncestor);
5095                                 t.collapse(TRUE);
5096                         }
5097
5098                         return frag;
5099                 };
5100
5101                 function _traverseRightBoundary(root, how) {
5102                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
5103
5104                         if (next == root)
5105                                 return _traverseNode(next, isFullySelected, FALSE, how);
5106
5107                         parent = next.parentNode;
5108                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
5109
5110                         while (parent) {
5111                                 while (next) {
5112                                         prevSibling = next.previousSibling;
5113                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
5114
5115                                         if (how != DELETE)
5116                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5117
5118                                         isFullySelected = TRUE;
5119                                         next = prevSibling;
5120                                 }
5121
5122                                 if (parent == root)
5123                                         return clonedParent;
5124
5125                                 next = parent.previousSibling;
5126                                 parent = parent.parentNode;
5127
5128                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
5129
5130                                 if (how != DELETE)
5131                                         clonedGrandParent.appendChild(clonedParent);
5132
5133                                 clonedParent = clonedGrandParent;
5134                         }
5135                 };
5136
5137                 function _traverseLeftBoundary(root, how) {
5138                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
5139
5140                         if (next == root)
5141                                 return _traverseNode(next, isFullySelected, TRUE, how);
5142
5143                         parent = next.parentNode;
5144                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
5145
5146                         while (parent) {
5147                                 while (next) {
5148                                         nextSibling = next.nextSibling;
5149                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
5150
5151                                         if (how != DELETE)
5152                                                 clonedParent.appendChild(clonedChild);
5153
5154                                         isFullySelected = TRUE;
5155                                         next = nextSibling;
5156                                 }
5157
5158                                 if (parent == root)
5159                                         return clonedParent;
5160
5161                                 next = parent.nextSibling;
5162                                 parent = parent.parentNode;
5163
5164                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
5165
5166                                 if (how != DELETE)
5167                                         clonedGrandParent.appendChild(clonedParent);
5168
5169                                 clonedParent = clonedGrandParent;
5170                         }
5171                 };
5172
5173                 function _traverseNode(n, isFullySelected, isLeft, how) {
5174                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
5175
5176                         if (isFullySelected)
5177                                 return _traverseFullySelected(n, how);
5178
5179                         if (n.nodeType == 3 /* TEXT_NODE */) {
5180                                 txtValue = n.nodeValue;
5181
5182                                 if (isLeft) {
5183                                         offset = t[START_OFFSET];
5184                                         newNodeValue = txtValue.substring(offset);
5185                                         oldNodeValue = txtValue.substring(0, offset);
5186                                 } else {
5187                                         offset = t[END_OFFSET];
5188                                         newNodeValue = txtValue.substring(0, offset);
5189                                         oldNodeValue = txtValue.substring(offset);
5190                                 }
5191
5192                                 if (how != CLONE)
5193                                         n.nodeValue = oldNodeValue;
5194
5195                                 if (how == DELETE)
5196                                         return;
5197
5198                                 newNode = n.cloneNode(FALSE);
5199                                 newNode.nodeValue = newNodeValue;
5200
5201                                 return newNode;
5202                         }
5203
5204                         if (how == DELETE)
5205                                 return;
5206
5207                         return n.cloneNode(FALSE);
5208                 };
5209
5210                 function _traverseFullySelected(n, how) {
5211                         if (how != DELETE)
5212                                 return how == CLONE ? n.cloneNode(TRUE) : n;
5213
5214                         n.parentNode.removeChild(n);
5215                 };
5216         };
5217
5218         ns.Range = Range;
5219 })(tinymce.dom);
5220
5221 (function() {
5222         function Selection(selection) {
5223                 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
5224
5225                 // Returns a W3C DOM compatible range object by using the IE Range API
5226                 function getRange() {
5227                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
5228
5229                         // If selection is outside the current document just return an empty range
5230                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5231                         if (element.ownerDocument != dom.doc)
5232                                 return domRange;
5233
5234                         collapsed = selection.isCollapsed();
5235
5236                         // Handle control selection or text selection of a image
5237                         if (ieRange.item || !element.hasChildNodes()) {
5238                                 if (collapsed) {
5239                                         domRange.setStart(element, 0);
5240                                         domRange.setEnd(element, 0);
5241                                 } else {
5242                                         domRange.setStart(element.parentNode, dom.nodeIndex(element));
5243                                         domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5244                                 }
5245
5246                                 return domRange;
5247                         }
5248
5249                         function findEndPoint(start) {
5250                                 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
5251
5252                                 // Setup temp range and collapse it
5253                                 checkRng = ieRange.duplicate();
5254                                 checkRng.collapse(start);
5255
5256                                 // Create marker and insert it at the end of the endpoints parent
5257                                 marker = dom.create('a');
5258                                 parent = checkRng.parentElement();
5259
5260                                 // If parent doesn't have any children then set the container to that parent and the index to 0
5261                                 if (!parent.hasChildNodes()) {
5262                                         domRange[start ? 'setStart' : 'setEnd'](parent, 0);
5263                                         return;
5264                                 }
5265
5266                                 parent.appendChild(marker);
5267                                 checkRng.moveToElementText(marker);
5268                                 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5269                                 if (position > 0) {
5270                                         // The position is after the end of the parent element.
5271                                         // This is the case where IE puts the caret to the left edge of a table.
5272                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
5273                                         dom.remove(marker);
5274                                         return;
5275                                 }
5276
5277                                 // Setup node list and endIndex
5278                                 nodes = tinymce.grep(parent.childNodes);
5279                                 endIndex = nodes.length - 1;
5280                                 // Perform a binary search for the position
5281                                 while (startIndex <= endIndex) {
5282                                         index = Math.floor((startIndex + endIndex) / 2);
5283
5284                                         // Insert marker and check it's position relative to the selection
5285                                         parent.insertBefore(marker, nodes[index]);
5286                                         checkRng.moveToElementText(marker);
5287                                         position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5288                                         if (position > 0) {
5289                                                 // Marker is to the right
5290                                                 startIndex = index + 1;
5291                                         } else if (position < 0) {
5292                                                 // Marker is to the left
5293                                                 endIndex = index - 1;
5294                                         } else {
5295                                                 // Maker is where we are
5296                                                 found = true;
5297                                                 break;
5298                                         }
5299                                 }
5300
5301                                 // Setup container
5302                                 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
5303
5304                                 // Handle element selection
5305                                 if (container.nodeType == 1) {
5306                                         dom.remove(marker);
5307
5308                                         // Find offset and container
5309                                         offset = dom.nodeIndex(container);
5310                                         container = container.parentNode;
5311
5312                                         // Move the offset if we are setting the end or the position is after an element
5313                                         if (!start || index > 0)
5314                                                 offset++;
5315                                 } else {
5316                                         // Calculate offset within text node
5317                                         if (position > 0 || index == 0) {
5318                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
5319                                                 offset = checkRng.text.length;
5320                                         } else {
5321                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
5322                                                 offset = container.nodeValue.length - checkRng.text.length;
5323                                         }
5324
5325                                         dom.remove(marker);
5326                                 }
5327
5328                                 domRange[start ? 'setStart' : 'setEnd'](container, offset);
5329                         };
5330
5331                         // Find start point
5332                         findEndPoint(true);
5333
5334                         // Find end point if needed
5335                         if (!collapsed)
5336                                 findEndPoint();
5337
5338                         return domRange;
5339                 };
5340
5341                 this.addRange = function(rng) {
5342                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
5343
5344                         function setEndPoint(start) {
5345                                 var container, offset, marker, tmpRng, nodes;
5346
5347                                 marker = dom.create('a');
5348                                 container = start ? startContainer : endContainer;
5349                                 offset = start ? startOffset : endOffset;
5350                                 tmpRng = ieRng.duplicate();
5351
5352                                 if (container == doc || container == doc.documentElement) {
5353                                         container = body;
5354                                         offset = 0;
5355                                 }
5356
5357                                 if (container.nodeType == 3) {
5358                                         container.parentNode.insertBefore(marker, container);
5359                                         tmpRng.moveToElementText(marker);
5360                                         tmpRng.moveStart('character', offset);
5361                                         dom.remove(marker);
5362                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5363                                 } else {
5364                                         nodes = container.childNodes;
5365
5366                                         if (nodes.length) {
5367                                                 if (offset >= nodes.length) {
5368                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
5369                                                 } else {
5370                                                         container.insertBefore(marker, nodes[offset]);
5371                                                 }
5372
5373                                                 tmpRng.moveToElementText(marker);
5374                                         } else {
5375                                                 // Empty node selection for example <div>|</div>
5376                                                 marker = doc.createTextNode(invisibleChar);
5377                                                 container.appendChild(marker);
5378                                                 tmpRng.moveToElementText(marker.parentNode);
5379                                                 tmpRng.collapse(TRUE);
5380                                         }
5381
5382                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5383                                         dom.remove(marker);
5384                                 }
5385                         }
5386
5387                         // Destroy cached range
5388                         this.destroy();
5389
5390                         // Setup some shorter versions
5391                         startContainer = rng.startContainer;
5392                         startOffset = rng.startOffset;
5393                         endContainer = rng.endContainer;
5394                         endOffset = rng.endOffset;
5395                         ieRng = body.createTextRange();
5396
5397                         // If single element selection then try making a control selection out of it
5398                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
5399                                 if (startOffset == endOffset - 1) {
5400                                         try {
5401                                                 ctrlRng = body.createControlRange();
5402                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);
5403                                                 ctrlRng.select();
5404                                                 return;
5405                                         } catch (ex) {
5406                                                 // Ignore
5407                                         }
5408                                 }
5409                         }
5410
5411                         // Set start/end point of selection
5412                         setEndPoint(true);
5413                         setEndPoint();
5414
5415                         // Select the new range and scroll it into view
5416                         ieRng.select();
5417                 };
5418
5419                 this.getRangeAt = function() {
5420                         // Setup new range if the cache is empty
5421                         if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
5422                                 range = getRange();
5423
5424                                 // Store away text range for next call
5425                                 lastIERng = selection.getRng();
5426                         }
5427
5428                         // IE will say that the range is equal then produce an invalid argument exception
5429                         // if you perform specific operations in a keyup event. For example Ctrl+Del.
5430                         // This hack will invalidate the range cache if the exception occurs
5431                         try {
5432                                 range.startContainer.nextSibling;
5433                         } catch (ex) {
5434                                 range = getRange();
5435                                 lastIERng = null;
5436                         }
5437
5438                         // Return cached range
5439                         return range;
5440                 };
5441
5442                 this.destroy = function() {
5443                         // Destroy cached range and last IE range to avoid memory leaks
5444                         lastIERng = range = null;
5445                 };
5446         };
5447
5448         // Expose the selection object
5449         tinymce.dom.TridentSelection = Selection;
5450 })();
5451
5452
5453 /*
5454  * Sizzle CSS Selector Engine - v1.0
5455  *  Copyright 2009, The Dojo Foundation
5456  *  Released under the MIT, BSD, and GPL Licenses.
5457  *  More information: http://sizzlejs.com/
5458  */
5459 (function(){
5460
5461 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
5462         done = 0,
5463         toString = Object.prototype.toString,
5464         hasDuplicate = false,
5465         baseHasDuplicate = true;
5466
5467 // Here we check if the JavaScript engine is using some sort of
5468 // optimization where it does not always call our comparision
5469 // function. If that is the case, discard the hasDuplicate value.
5470 //   Thus far that includes Google Chrome.
5471 [0, 0].sort(function(){
5472         baseHasDuplicate = false;
5473         return 0;
5474 });
5475
5476 var Sizzle = function(selector, context, results, seed) {
5477         results = results || [];
5478         context = context || document;
5479
5480         var origContext = context;
5481
5482         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
5483                 return [];
5484         }
5485         
5486         if ( !selector || typeof selector !== "string" ) {
5487                 return results;
5488         }
5489
5490         var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
5491                 soFar = selector, ret, cur, pop, i;
5492         
5493         // Reset the position of the chunker regexp (start from head)
5494         do {
5495                 chunker.exec("");
5496                 m = chunker.exec(soFar);
5497
5498                 if ( m ) {
5499                         soFar = m[3];
5500                 
5501                         parts.push( m[1] );
5502                 
5503                         if ( m[2] ) {
5504                                 extra = m[3];
5505                                 break;
5506                         }
5507                 }
5508         } while ( m );
5509
5510         if ( parts.length > 1 && origPOS.exec( selector ) ) {
5511                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
5512                         set = posProcess( parts[0] + parts[1], context );
5513                 } else {
5514                         set = Expr.relative[ parts[0] ] ?
5515                                 [ context ] :
5516                                 Sizzle( parts.shift(), context );
5517
5518                         while ( parts.length ) {
5519                                 selector = parts.shift();
5520
5521                                 if ( Expr.relative[ selector ] ) {
5522                                         selector += parts.shift();
5523                                 }
5524                                 
5525                                 set = posProcess( selector, set );
5526                         }
5527                 }
5528         } else {
5529                 // Take a shortcut and set the context if the root selector is an ID
5530                 // (but not if it'll be faster if the inner selector is an ID)
5531                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
5532                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
5533                         ret = Sizzle.find( parts.shift(), context, contextXML );
5534                         context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
5535                 }
5536
5537                 if ( context ) {
5538                         ret = seed ?
5539                                 { expr: parts.pop(), set: makeArray(seed) } :
5540                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
5541                         set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
5542
5543                         if ( parts.length > 0 ) {
5544                                 checkSet = makeArray(set);
5545                         } else {
5546                                 prune = false;
5547                         }
5548
5549                         while ( parts.length ) {
5550                                 cur = parts.pop();
5551                                 pop = cur;
5552
5553                                 if ( !Expr.relative[ cur ] ) {
5554                                         cur = "";
5555                                 } else {
5556                                         pop = parts.pop();
5557                                 }
5558
5559                                 if ( pop == null ) {
5560                                         pop = context;
5561                                 }
5562
5563                                 Expr.relative[ cur ]( checkSet, pop, contextXML );
5564                         }
5565                 } else {
5566                         checkSet = parts = [];
5567                 }
5568         }
5569
5570         if ( !checkSet ) {
5571                 checkSet = set;
5572         }
5573
5574         if ( !checkSet ) {
5575                 Sizzle.error( cur || selector );
5576         }
5577
5578         if ( toString.call(checkSet) === "[object Array]" ) {
5579                 if ( !prune ) {
5580                         results.push.apply( results, checkSet );
5581                 } else if ( context && context.nodeType === 1 ) {
5582                         for ( i = 0; checkSet[i] != null; i++ ) {
5583                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
5584                                         results.push( set[i] );
5585                                 }
5586                         }
5587                 } else {
5588                         for ( i = 0; checkSet[i] != null; i++ ) {
5589                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
5590                                         results.push( set[i] );
5591                                 }
5592                         }
5593                 }
5594         } else {
5595                 makeArray( checkSet, results );
5596         }
5597
5598         if ( extra ) {
5599                 Sizzle( extra, origContext, results, seed );
5600                 Sizzle.uniqueSort( results );
5601         }
5602
5603         return results;
5604 };
5605
5606 Sizzle.uniqueSort = function(results){
5607         if ( sortOrder ) {
5608                 hasDuplicate = baseHasDuplicate;
5609                 results.sort(sortOrder);
5610
5611                 if ( hasDuplicate ) {
5612                         for ( var i = 1; i < results.length; i++ ) {
5613                                 if ( results[i] === results[i-1] ) {
5614                                         results.splice(i--, 1);
5615                                 }
5616                         }
5617                 }
5618         }
5619
5620         return results;
5621 };
5622
5623 Sizzle.matches = function(expr, set){
5624         return Sizzle(expr, null, null, set);
5625 };
5626
5627 Sizzle.find = function(expr, context, isXML){
5628         var set;
5629
5630         if ( !expr ) {
5631                 return [];
5632         }
5633
5634         for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
5635                 var type = Expr.order[i], match;
5636                 
5637                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
5638                         var left = match[1];
5639                         match.splice(1,1);
5640
5641                         if ( left.substr( left.length - 1 ) !== "\\" ) {
5642                                 match[1] = (match[1] || "").replace(/\\/g, "");
5643                                 set = Expr.find[ type ]( match, context, isXML );
5644                                 if ( set != null ) {
5645                                         expr = expr.replace( Expr.match[ type ], "" );
5646                                         break;
5647                                 }
5648                         }
5649                 }
5650         }
5651
5652         if ( !set ) {
5653                 set = context.getElementsByTagName("*");
5654         }
5655
5656         return {set: set, expr: expr};
5657 };
5658
5659 Sizzle.filter = function(expr, set, inplace, not){
5660         var old = expr, result = [], curLoop = set, match, anyFound,
5661                 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
5662
5663         while ( expr && set.length ) {
5664                 for ( var type in Expr.filter ) {
5665                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
5666                                 var filter = Expr.filter[ type ], found, item, left = match[1];
5667                                 anyFound = false;
5668
5669                                 match.splice(1,1);
5670
5671                                 if ( left.substr( left.length - 1 ) === "\\" ) {
5672                                         continue;
5673                                 }
5674
5675                                 if ( curLoop === result ) {
5676                                         result = [];
5677                                 }
5678
5679                                 if ( Expr.preFilter[ type ] ) {
5680                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
5681
5682                                         if ( !match ) {
5683                                                 anyFound = found = true;
5684                                         } else if ( match === true ) {
5685                                                 continue;
5686                                         }
5687                                 }
5688
5689                                 if ( match ) {
5690                                         for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
5691                                                 if ( item ) {
5692                                                         found = filter( item, match, i, curLoop );
5693                                                         var pass = not ^ !!found;
5694
5695                                                         if ( inplace && found != null ) {
5696                                                                 if ( pass ) {
5697                                                                         anyFound = true;
5698                                                                 } else {
5699                                                                         curLoop[i] = false;
5700                                                                 }
5701                                                         } else if ( pass ) {
5702                                                                 result.push( item );
5703                                                                 anyFound = true;
5704                                                         }
5705                                                 }
5706                                         }
5707                                 }
5708
5709                                 if ( found !== undefined ) {
5710                                         if ( !inplace ) {
5711                                                 curLoop = result;
5712                                         }
5713
5714                                         expr = expr.replace( Expr.match[ type ], "" );
5715
5716                                         if ( !anyFound ) {
5717                                                 return [];
5718                                         }
5719
5720                                         break;
5721                                 }
5722                         }
5723                 }
5724
5725                 // Improper expression
5726                 if ( expr === old ) {
5727                         if ( anyFound == null ) {
5728                                 Sizzle.error( expr );
5729                         } else {
5730                                 break;
5731                         }
5732                 }
5733
5734                 old = expr;
5735         }
5736
5737         return curLoop;
5738 };
5739
5740 Sizzle.error = function( msg ) {
5741         throw "Syntax error, unrecognized expression: " + msg;
5742 };
5743
5744 var Expr = Sizzle.selectors = {
5745         order: [ "ID", "NAME", "TAG" ],
5746         match: {
5747                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
5748                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
5749                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
5750                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
5751                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
5752                 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
5753                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
5754                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
5755         },
5756         leftMatch: {},
5757         attrMap: {
5758                 "class": "className",
5759                 "for": "htmlFor"
5760         },
5761         attrHandle: {
5762                 href: function(elem){
5763                         return elem.getAttribute("href");
5764                 }
5765         },
5766         relative: {
5767                 "+": function(checkSet, part){
5768                         var isPartStr = typeof part === "string",
5769                                 isTag = isPartStr && !/\W/.test(part),
5770                                 isPartStrNotTag = isPartStr && !isTag;
5771
5772                         if ( isTag ) {
5773                                 part = part.toLowerCase();
5774                         }
5775
5776                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
5777                                 if ( (elem = checkSet[i]) ) {
5778                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
5779
5780                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
5781                                                 elem || false :
5782                                                 elem === part;
5783                                 }
5784                         }
5785
5786                         if ( isPartStrNotTag ) {
5787                                 Sizzle.filter( part, checkSet, true );
5788                         }
5789                 },
5790                 ">": function(checkSet, part){
5791                         var isPartStr = typeof part === "string",
5792                                 elem, i = 0, l = checkSet.length;
5793
5794                         if ( isPartStr && !/\W/.test(part) ) {
5795                                 part = part.toLowerCase();
5796
5797                                 for ( ; i < l; i++ ) {
5798                                         elem = checkSet[i];
5799                                         if ( elem ) {
5800                                                 var parent = elem.parentNode;
5801                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
5802                                         }
5803                                 }
5804                         } else {
5805                                 for ( ; i < l; i++ ) {
5806                                         elem = checkSet[i];
5807                                         if ( elem ) {
5808                                                 checkSet[i] = isPartStr ?
5809                                                         elem.parentNode :
5810                                                         elem.parentNode === part;
5811                                         }
5812                                 }
5813
5814                                 if ( isPartStr ) {
5815                                         Sizzle.filter( part, checkSet, true );
5816                                 }
5817                         }
5818                 },
5819                 "": function(checkSet, part, isXML){
5820                         var doneName = done++, checkFn = dirCheck, nodeCheck;
5821
5822                         if ( typeof part === "string" && !/\W/.test(part) ) {
5823                                 part = part.toLowerCase();
5824                                 nodeCheck = part;
5825                                 checkFn = dirNodeCheck;
5826                         }
5827
5828                         checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
5829                 },
5830                 "~": function(checkSet, part, isXML){
5831                         var doneName = done++, checkFn = dirCheck, nodeCheck;
5832
5833                         if ( typeof part === "string" && !/\W/.test(part) ) {
5834                                 part = part.toLowerCase();
5835                                 nodeCheck = part;
5836                                 checkFn = dirNodeCheck;
5837                         }
5838
5839                         checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
5840                 }
5841         },
5842         find: {
5843                 ID: function(match, context, isXML){
5844                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
5845                                 var m = context.getElementById(match[1]);
5846                                 return m ? [m] : [];
5847                         }
5848                 },
5849                 NAME: function(match, context){
5850                         if ( typeof context.getElementsByName !== "undefined" ) {
5851                                 var ret = [], results = context.getElementsByName(match[1]);
5852
5853                                 for ( var i = 0, l = results.length; i < l; i++ ) {
5854                                         if ( results[i].getAttribute("name") === match[1] ) {
5855                                                 ret.push( results[i] );
5856                                         }
5857                                 }
5858
5859                                 return ret.length === 0 ? null : ret;
5860                         }
5861                 },
5862                 TAG: function(match, context){
5863                         return context.getElementsByTagName(match[1]);
5864                 }
5865         },
5866         preFilter: {
5867                 CLASS: function(match, curLoop, inplace, result, not, isXML){
5868                         match = " " + match[1].replace(/\\/g, "") + " ";
5869
5870                         if ( isXML ) {
5871                                 return match;
5872                         }
5873
5874                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
5875                                 if ( elem ) {
5876                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
5877                                                 if ( !inplace ) {
5878                                                         result.push( elem );
5879                                                 }
5880                                         } else if ( inplace ) {
5881                                                 curLoop[i] = false;
5882                                         }
5883                                 }
5884                         }
5885
5886                         return false;
5887                 },
5888                 ID: function(match){
5889                         return match[1].replace(/\\/g, "");
5890                 },
5891                 TAG: function(match, curLoop){
5892                         return match[1].toLowerCase();
5893                 },
5894                 CHILD: function(match){
5895                         if ( match[1] === "nth" ) {
5896                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
5897                                 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
5898                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
5899                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
5900
5901                                 // calculate the numbers (first)n+(last) including if they are negative
5902                                 match[2] = (test[1] + (test[2] || 1)) - 0;
5903                                 match[3] = test[3] - 0;
5904                         }
5905
5906                         // TODO: Move to normal caching system
5907                         match[0] = done++;
5908
5909                         return match;
5910                 },
5911                 ATTR: function(match, curLoop, inplace, result, not, isXML){
5912                         var name = match[1].replace(/\\/g, "");
5913                         
5914                         if ( !isXML && Expr.attrMap[name] ) {
5915                                 match[1] = Expr.attrMap[name];
5916                         }
5917
5918                         if ( match[2] === "~=" ) {
5919                                 match[4] = " " + match[4] + " ";
5920                         }
5921
5922                         return match;
5923                 },
5924                 PSEUDO: function(match, curLoop, inplace, result, not){
5925                         if ( match[1] === "not" ) {
5926                                 // If we're dealing with a complex expression, or a simple one
5927                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
5928                                         match[3] = Sizzle(match[3], null, null, curLoop);
5929                                 } else {
5930                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
5931                                         if ( !inplace ) {
5932                                                 result.push.apply( result, ret );
5933                                         }
5934                                         return false;
5935                                 }
5936                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
5937                                 return true;
5938                         }
5939                         
5940                         return match;
5941                 },
5942                 POS: function(match){
5943                         match.unshift( true );
5944                         return match;
5945                 }
5946         },
5947         filters: {
5948                 enabled: function(elem){
5949                         return elem.disabled === false && elem.type !== "hidden";
5950                 },
5951                 disabled: function(elem){
5952                         return elem.disabled === true;
5953                 },
5954                 checked: function(elem){
5955                         return elem.checked === true;
5956                 },
5957                 selected: function(elem){
5958                         // Accessing this property makes selected-by-default
5959                         // options in Safari work properly
5960                         elem.parentNode.selectedIndex;
5961                         return elem.selected === true;
5962                 },
5963                 parent: function(elem){
5964                         return !!elem.firstChild;
5965                 },
5966                 empty: function(elem){
5967                         return !elem.firstChild;
5968                 },
5969                 has: function(elem, i, match){
5970                         return !!Sizzle( match[3], elem ).length;
5971                 },
5972                 header: function(elem){
5973                         return (/h\d/i).test( elem.nodeName );
5974                 },
5975                 text: function(elem){
5976                         return "text" === elem.type;
5977                 },
5978                 radio: function(elem){
5979                         return "radio" === elem.type;
5980                 },
5981                 checkbox: function(elem){
5982                         return "checkbox" === elem.type;
5983                 },
5984                 file: function(elem){
5985                         return "file" === elem.type;
5986                 },
5987                 password: function(elem){
5988                         return "password" === elem.type;
5989                 },
5990                 submit: function(elem){
5991                         return "submit" === elem.type;
5992                 },
5993                 image: function(elem){
5994                         return "image" === elem.type;
5995                 },
5996                 reset: function(elem){
5997                         return "reset" === elem.type;
5998                 },
5999                 button: function(elem){
6000                         return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
6001                 },
6002                 input: function(elem){
6003                         return (/input|select|textarea|button/i).test(elem.nodeName);
6004                 }
6005         },
6006         setFilters: {
6007                 first: function(elem, i){
6008                         return i === 0;
6009                 },
6010                 last: function(elem, i, match, array){
6011                         return i === array.length - 1;
6012                 },
6013                 even: function(elem, i){
6014                         return i % 2 === 0;
6015                 },
6016                 odd: function(elem, i){
6017                         return i % 2 === 1;
6018                 },
6019                 lt: function(elem, i, match){
6020                         return i < match[3] - 0;
6021                 },
6022                 gt: function(elem, i, match){
6023                         return i > match[3] - 0;
6024                 },
6025                 nth: function(elem, i, match){
6026                         return match[3] - 0 === i;
6027                 },
6028                 eq: function(elem, i, match){
6029                         return match[3] - 0 === i;
6030                 }
6031         },
6032         filter: {
6033                 PSEUDO: function(elem, match, i, array){
6034                         var name = match[1], filter = Expr.filters[ name ];
6035
6036                         if ( filter ) {
6037                                 return filter( elem, i, match, array );
6038                         } else if ( name === "contains" ) {
6039                                 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
6040                         } else if ( name === "not" ) {
6041                                 var not = match[3];
6042
6043                                 for ( var j = 0, l = not.length; j < l; j++ ) {
6044                                         if ( not[j] === elem ) {
6045                                                 return false;
6046                                         }
6047                                 }
6048
6049                                 return true;
6050                         } else {
6051                                 Sizzle.error( "Syntax error, unrecognized expression: " + name );
6052                         }
6053                 },
6054                 CHILD: function(elem, match){
6055                         var type = match[1], node = elem;
6056                         switch (type) {
6057                                 case 'only':
6058                                 case 'first':
6059                                         while ( (node = node.previousSibling) )  {
6060                                                 if ( node.nodeType === 1 ) { 
6061                                                         return false; 
6062                                                 }
6063                                         }
6064                                         if ( type === "first" ) { 
6065                                                 return true; 
6066                                         }
6067                                         node = elem;
6068                                 case 'last':
6069                                         while ( (node = node.nextSibling) )      {
6070                                                 if ( node.nodeType === 1 ) { 
6071                                                         return false; 
6072                                                 }
6073                                         }
6074                                         return true;
6075                                 case 'nth':
6076                                         var first = match[2], last = match[3];
6077
6078                                         if ( first === 1 && last === 0 ) {
6079                                                 return true;
6080                                         }
6081                                         
6082                                         var doneName = match[0],
6083                                                 parent = elem.parentNode;
6084         
6085                                         if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
6086                                                 var count = 0;
6087                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {
6088                                                         if ( node.nodeType === 1 ) {
6089                                                                 node.nodeIndex = ++count;
6090                                                         }
6091                                                 } 
6092                                                 parent.sizcache = doneName;
6093                                         }
6094                                         
6095                                         var diff = elem.nodeIndex - last;
6096                                         if ( first === 0 ) {
6097                                                 return diff === 0;
6098                                         } else {
6099                                                 return ( diff % first === 0 && diff / first >= 0 );
6100                                         }
6101                         }
6102                 },
6103                 ID: function(elem, match){
6104                         return elem.nodeType === 1 && elem.getAttribute("id") === match;
6105                 },
6106                 TAG: function(elem, match){
6107                         return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
6108                 },
6109                 CLASS: function(elem, match){
6110                         return (" " + (elem.className || elem.getAttribute("class")) + " ")
6111                                 .indexOf( match ) > -1;
6112                 },
6113                 ATTR: function(elem, match){
6114                         var name = match[1],
6115                                 result = Expr.attrHandle[ name ] ?
6116                                         Expr.attrHandle[ name ]( elem ) :
6117                                         elem[ name ] != null ?
6118                                                 elem[ name ] :
6119                                                 elem.getAttribute( name ),
6120                                 value = result + "",
6121                                 type = match[2],
6122                                 check = match[4];
6123
6124                         return result == null ?
6125                                 type === "!=" :
6126                                 type === "=" ?
6127                                 value === check :
6128                                 type === "*=" ?
6129                                 value.indexOf(check) >= 0 :
6130                                 type === "~=" ?
6131                                 (" " + value + " ").indexOf(check) >= 0 :
6132                                 !check ?
6133                                 value && result !== false :
6134                                 type === "!=" ?
6135                                 value !== check :
6136                                 type === "^=" ?
6137                                 value.indexOf(check) === 0 :
6138                                 type === "$=" ?
6139                                 value.substr(value.length - check.length) === check :
6140                                 type === "|=" ?
6141                                 value === check || value.substr(0, check.length + 1) === check + "-" :
6142                                 false;
6143                 },
6144                 POS: function(elem, match, i, array){
6145                         var name = match[2], filter = Expr.setFilters[ name ];
6146
6147                         if ( filter ) {
6148                                 return filter( elem, i, match, array );
6149                         }
6150                 }
6151         }
6152 };
6153
6154 var origPOS = Expr.match.POS,
6155         fescape = function(all, num){
6156                 return "\\" + (num - 0 + 1);
6157         };
6158
6159 for ( var type in Expr.match ) {
6160         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
6161         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
6162 }
6163
6164 var makeArray = function(array, results) {
6165         array = Array.prototype.slice.call( array, 0 );
6166
6167         if ( results ) {
6168                 results.push.apply( results, array );
6169                 return results;
6170         }
6171         
6172         return array;
6173 };
6174
6175 // Perform a simple check to determine if the browser is capable of
6176 // converting a NodeList to an array using builtin methods.
6177 // Also verifies that the returned array holds DOM nodes
6178 // (which is not the case in the Blackberry browser)
6179 try {
6180         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
6181
6182 // Provide a fallback method if it does not work
6183 } catch(e){
6184         makeArray = function(array, results) {
6185                 var ret = results || [], i = 0;
6186
6187                 if ( toString.call(array) === "[object Array]" ) {
6188                         Array.prototype.push.apply( ret, array );
6189                 } else {
6190                         if ( typeof array.length === "number" ) {
6191                                 for ( var l = array.length; i < l; i++ ) {
6192                                         ret.push( array[i] );
6193                                 }
6194                         } else {
6195                                 for ( ; array[i]; i++ ) {
6196                                         ret.push( array[i] );
6197                                 }
6198                         }
6199                 }
6200
6201                 return ret;
6202         };
6203 }
6204
6205 var sortOrder;
6206
6207 if ( document.documentElement.compareDocumentPosition ) {
6208         sortOrder = function( a, b ) {
6209                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
6210                         if ( a == b ) {
6211                                 hasDuplicate = true;
6212                         }
6213                         return a.compareDocumentPosition ? -1 : 1;
6214                 }
6215
6216                 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
6217                 if ( ret === 0 ) {
6218                         hasDuplicate = true;
6219                 }
6220                 return ret;
6221         };
6222 } else if ( "sourceIndex" in document.documentElement ) {
6223         sortOrder = function( a, b ) {
6224                 if ( !a.sourceIndex || !b.sourceIndex ) {
6225                         if ( a == b ) {
6226                                 hasDuplicate = true;
6227                         }
6228                         return a.sourceIndex ? -1 : 1;
6229                 }
6230
6231                 var ret = a.sourceIndex - b.sourceIndex;
6232                 if ( ret === 0 ) {
6233                         hasDuplicate = true;
6234                 }
6235                 return ret;
6236         };
6237 } else if ( document.createRange ) {
6238         sortOrder = function( a, b ) {
6239                 if ( !a.ownerDocument || !b.ownerDocument ) {
6240                         if ( a == b ) {
6241                                 hasDuplicate = true;
6242                         }
6243                         return a.ownerDocument ? -1 : 1;
6244                 }
6245
6246                 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
6247                 aRange.setStart(a, 0);
6248                 aRange.setEnd(a, 0);
6249                 bRange.setStart(b, 0);
6250                 bRange.setEnd(b, 0);
6251                 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
6252                 if ( ret === 0 ) {
6253                         hasDuplicate = true;
6254                 }
6255                 return ret;
6256         };
6257 }
6258
6259 // Utility function for retreiving the text value of an array of DOM nodes
6260 Sizzle.getText = function( elems ) {
6261         var ret = "", elem;
6262
6263         for ( var i = 0; elems[i]; i++ ) {
6264                 elem = elems[i];
6265
6266                 // Get the text from text nodes and CDATA nodes
6267                 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
6268                         ret += elem.nodeValue;
6269
6270                 // Traverse everything else, except comment nodes
6271                 } else if ( elem.nodeType !== 8 ) {
6272                         ret += Sizzle.getText( elem.childNodes );
6273                 }
6274         }
6275
6276         return ret;
6277 };
6278
6279 // Check to see if the browser returns elements by name when
6280 // querying by getElementById (and provide a workaround)
6281 (function(){
6282         // We're going to inject a fake input element with a specified name
6283         var form = document.createElement("div"),
6284                 id = "script" + (new Date()).getTime();
6285         form.innerHTML = "<a name='" + id + "'/>";
6286
6287         // Inject it into the root element, check its status, and remove it quickly
6288         var root = document.documentElement;
6289         root.insertBefore( form, root.firstChild );
6290
6291         // The workaround has to do additional checks after a getElementById
6292         // Which slows things down for other browsers (hence the branching)
6293         if ( document.getElementById( id ) ) {
6294                 Expr.find.ID = function(match, context, isXML){
6295                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
6296                                 var m = context.getElementById(match[1]);
6297                                 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
6298                         }
6299                 };
6300
6301                 Expr.filter.ID = function(elem, match){
6302                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
6303                         return elem.nodeType === 1 && node && node.nodeValue === match;
6304                 };
6305         }
6306
6307         root.removeChild( form );
6308         root = form = null; // release memory in IE
6309 })();
6310
6311 (function(){
6312         // Check to see if the browser returns only elements
6313         // when doing getElementsByTagName("*")
6314
6315         // Create a fake element
6316         var div = document.createElement("div");
6317         div.appendChild( document.createComment("") );
6318
6319         // Make sure no comments are found
6320         if ( div.getElementsByTagName("*").length > 0 ) {
6321                 Expr.find.TAG = function(match, context){
6322                         var results = context.getElementsByTagName(match[1]);
6323
6324                         // Filter out possible comments
6325                         if ( match[1] === "*" ) {
6326                                 var tmp = [];
6327
6328                                 for ( var i = 0; results[i]; i++ ) {
6329                                         if ( results[i].nodeType === 1 ) {
6330                                                 tmp.push( results[i] );
6331                                         }
6332                                 }
6333
6334                                 results = tmp;
6335                         }
6336
6337                         return results;
6338                 };
6339         }
6340
6341         // Check to see if an attribute returns normalized href attributes
6342         div.innerHTML = "<a href='#'></a>";
6343         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
6344                         div.firstChild.getAttribute("href") !== "#" ) {
6345                 Expr.attrHandle.href = function(elem){
6346                         return elem.getAttribute("href", 2);
6347                 };
6348         }
6349
6350         div = null; // release memory in IE
6351 })();
6352
6353 if ( document.querySelectorAll ) {
6354         (function(){
6355                 var oldSizzle = Sizzle, div = document.createElement("div");
6356                 div.innerHTML = "<p class='TEST'></p>";
6357
6358                 // Safari can't handle uppercase or unicode characters when
6359                 // in quirks mode.
6360                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
6361                         return;
6362                 }
6363         
6364                 Sizzle = function(query, context, extra, seed){
6365                         context = context || document;
6366
6367                         // Only use querySelectorAll on non-XML documents
6368                         // (ID selectors don't work in non-HTML documents)
6369                         if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
6370                                 try {
6371                                         return makeArray( context.querySelectorAll(query), extra );
6372                                 } catch(e){}
6373                         }
6374                 
6375                         return oldSizzle(query, context, extra, seed);
6376                 };
6377
6378                 for ( var prop in oldSizzle ) {
6379                         Sizzle[ prop ] = oldSizzle[ prop ];
6380                 }
6381
6382                 div = null; // release memory in IE
6383         })();
6384 }
6385
6386 (function(){
6387         var div = document.createElement("div");
6388
6389         div.innerHTML = "<div class='test e'></div><div class='test'></div>";
6390
6391         // Opera can't find a second classname (in 9.6)
6392         // Also, make sure that getElementsByClassName actually exists
6393         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
6394                 return;
6395         }
6396
6397         // Safari caches class attributes, doesn't catch changes (in 3.2)
6398         div.lastChild.className = "e";
6399
6400         if ( div.getElementsByClassName("e").length === 1 ) {
6401                 return;
6402         }
6403         
6404         Expr.order.splice(1, 0, "CLASS");
6405         Expr.find.CLASS = function(match, context, isXML) {
6406                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
6407                         return context.getElementsByClassName(match[1]);
6408                 }
6409         };
6410
6411         div = null; // release memory in IE
6412 })();
6413
6414 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6415         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6416                 var elem = checkSet[i];
6417                 if ( elem ) {
6418                         elem = elem[dir];
6419                         var match = false;
6420
6421                         while ( elem ) {
6422                                 if ( elem.sizcache === doneName ) {
6423                                         match = checkSet[elem.sizset];
6424                                         break;
6425                                 }
6426
6427                                 if ( elem.nodeType === 1 && !isXML ){
6428                                         elem.sizcache = doneName;
6429                                         elem.sizset = i;
6430                                 }
6431
6432                                 if ( elem.nodeName.toLowerCase() === cur ) {
6433                                         match = elem;
6434                                         break;
6435                                 }
6436
6437                                 elem = elem[dir];
6438                         }
6439
6440                         checkSet[i] = match;
6441                 }
6442         }
6443 }
6444
6445 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6446         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6447                 var elem = checkSet[i];
6448                 if ( elem ) {
6449                         elem = elem[dir];
6450                         var match = false;
6451
6452                         while ( elem ) {
6453                                 if ( elem.sizcache === doneName ) {
6454                                         match = checkSet[elem.sizset];
6455                                         break;
6456                                 }
6457
6458                                 if ( elem.nodeType === 1 ) {
6459                                         if ( !isXML ) {
6460                                                 elem.sizcache = doneName;
6461                                                 elem.sizset = i;
6462                                         }
6463                                         if ( typeof cur !== "string" ) {
6464                                                 if ( elem === cur ) {
6465                                                         match = true;
6466                                                         break;
6467                                                 }
6468
6469                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
6470                                                 match = elem;
6471                                                 break;
6472                                         }
6473                                 }
6474
6475                                 elem = elem[dir];
6476                         }
6477
6478                         checkSet[i] = match;
6479                 }
6480         }
6481 }
6482
6483 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
6484         return !!(a.compareDocumentPosition(b) & 16);
6485 } : function(a, b){
6486         return a !== b && (a.contains ? a.contains(b) : true);
6487 };
6488
6489 Sizzle.isXML = function(elem){
6490         // documentElement is verified for cases where it doesn't yet exist
6491         // (such as loading iframes in IE - #4833) 
6492         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
6493         return documentElement ? documentElement.nodeName !== "HTML" : false;
6494 };
6495
6496 var posProcess = function(selector, context){
6497         var tmpSet = [], later = "", match,
6498                 root = context.nodeType ? [context] : context;
6499
6500         // Position selectors must be done after the filter
6501         // And so must :not(positional) so we move all PSEUDOs to the end
6502         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
6503                 later += match[0];
6504                 selector = selector.replace( Expr.match.PSEUDO, "" );
6505         }
6506
6507         selector = Expr.relative[selector] ? selector + "*" : selector;
6508
6509         for ( var i = 0, l = root.length; i < l; i++ ) {
6510                 Sizzle( selector, root[i], tmpSet );
6511         }
6512
6513         return Sizzle.filter( later, tmpSet );
6514 };
6515
6516 // EXPOSE
6517
6518 window.tinymce.dom.Sizzle = Sizzle;
6519
6520 })();
6521
6522
6523 (function(tinymce) {
6524         // Shorten names
6525         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
6526
6527         tinymce.create('tinymce.dom.EventUtils', {
6528                 EventUtils : function() {
6529                         this.inits = [];
6530                         this.events = [];
6531                 },
6532
6533                 add : function(o, n, f, s) {
6534                         var cb, t = this, el = t.events, r;
6535
6536                         if (n instanceof Array) {
6537                                 r = [];
6538
6539                                 each(n, function(n) {
6540                                         r.push(t.add(o, n, f, s));
6541                                 });
6542
6543                                 return r;
6544                         }
6545
6546                         // Handle array
6547                         if (o && o.hasOwnProperty && o instanceof Array) {
6548                                 r = [];
6549
6550                                 each(o, function(o) {
6551                                         o = DOM.get(o);
6552                                         r.push(t.add(o, n, f, s));
6553                                 });
6554
6555                                 return r;
6556                         }
6557
6558                         o = DOM.get(o);
6559
6560                         if (!o)
6561                                 return;
6562
6563                         // Setup event callback
6564                         cb = function(e) {
6565                                 // Is all events disabled
6566                                 if (t.disabled)
6567                                         return;
6568
6569                                 e = e || window.event;
6570
6571                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
6572                                 if (e && isIE) {
6573                                         if (!e.target)
6574                                                 e.target = e.srcElement;
6575
6576                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility
6577                                         tinymce.extend(e, t._stoppers);
6578                                 }
6579
6580                                 if (!s)
6581                                         return f(e);
6582
6583                                 return f.call(s, e);
6584                         };
6585
6586                         if (n == 'unload') {
6587                                 tinymce.unloads.unshift({func : cb});
6588                                 return cb;
6589                         }
6590
6591                         if (n == 'init') {
6592                                 if (t.domLoaded)
6593                                         cb();
6594                                 else
6595                                         t.inits.push(cb);
6596
6597                                 return cb;
6598                         }
6599
6600                         // Store away listener reference
6601                         el.push({
6602                                 obj : o,
6603                                 name : n,
6604                                 func : f,
6605                                 cfunc : cb,
6606                                 scope : s
6607                         });
6608
6609                         t._add(o, n, cb);
6610
6611                         return f;
6612                 },
6613
6614                 remove : function(o, n, f) {
6615                         var t = this, a = t.events, s = false, r;
6616
6617                         // Handle array
6618                         if (o && o.hasOwnProperty && o instanceof Array) {
6619                                 r = [];
6620
6621                                 each(o, function(o) {
6622                                         o = DOM.get(o);
6623                                         r.push(t.remove(o, n, f));
6624                                 });
6625
6626                                 return r;
6627                         }
6628
6629                         o = DOM.get(o);
6630
6631                         each(a, function(e, i) {
6632                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
6633                                         a.splice(i, 1);
6634                                         t._remove(o, n, e.cfunc);
6635                                         s = true;
6636                                         return false;
6637                                 }
6638                         });
6639
6640                         return s;
6641                 },
6642
6643                 clear : function(o) {
6644                         var t = this, a = t.events, i, e;
6645
6646                         if (o) {
6647                                 o = DOM.get(o);
6648
6649                                 for (i = a.length - 1; i >= 0; i--) {
6650                                         e = a[i];
6651
6652                                         if (e.obj === o) {
6653                                                 t._remove(e.obj, e.name, e.cfunc);
6654                                                 e.obj = e.cfunc = null;
6655                                                 a.splice(i, 1);
6656                                         }
6657                                 }
6658                         }
6659                 },
6660
6661                 cancel : function(e) {
6662                         if (!e)
6663                                 return false;
6664
6665                         this.stop(e);
6666
6667                         return this.prevent(e);
6668                 },
6669
6670                 stop : function(e) {
6671                         if (e.stopPropagation)
6672                                 e.stopPropagation();
6673                         else
6674                                 e.cancelBubble = true;
6675
6676                         return false;
6677                 },
6678
6679                 prevent : function(e) {
6680                         if (e.preventDefault)
6681                                 e.preventDefault();
6682                         else
6683                                 e.returnValue = false;
6684
6685                         return false;
6686                 },
6687
6688                 destroy : function() {
6689                         var t = this;
6690
6691                         each(t.events, function(e, i) {
6692                                 t._remove(e.obj, e.name, e.cfunc);
6693                                 e.obj = e.cfunc = null;
6694                         });
6695
6696                         t.events = [];
6697                         t = null;
6698                 },
6699
6700                 _add : function(o, n, f) {
6701                         if (o.attachEvent)
6702                                 o.attachEvent('on' + n, f);
6703                         else if (o.addEventListener)
6704                                 o.addEventListener(n, f, false);
6705                         else
6706                                 o['on' + n] = f;
6707                 },
6708
6709                 _remove : function(o, n, f) {
6710                         if (o) {
6711                                 try {
6712                                         if (o.detachEvent)
6713                                                 o.detachEvent('on' + n, f);
6714                                         else if (o.removeEventListener)
6715                                                 o.removeEventListener(n, f, false);
6716                                         else
6717                                                 o['on' + n] = null;
6718                                 } catch (ex) {
6719                                         // Might fail with permission denined on IE so we just ignore that
6720                                 }
6721                         }
6722                 },
6723
6724                 _pageInit : function(win) {
6725                         var t = this;
6726
6727                         // Keep it from running more than once
6728                         if (t.domLoaded)
6729                                 return;
6730
6731                         t.domLoaded = true;
6732
6733                         each(t.inits, function(c) {
6734                                 c();
6735                         });
6736
6737                         t.inits = [];
6738                 },
6739
6740                 _wait : function(win) {
6741                         var t = this, doc = win.document;
6742
6743                         // No need since the document is already loaded
6744                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
6745                                 t.domLoaded = 1;
6746                                 return;
6747                         }
6748
6749                         // Use IE method
6750                         if (doc.attachEvent) {
6751                                 doc.attachEvent("onreadystatechange", function() {
6752                                         if (doc.readyState === "complete") {
6753                                                 doc.detachEvent("onreadystatechange", arguments.callee);
6754                                                 t._pageInit(win);
6755                                         }
6756                                 });
6757
6758                                 if (doc.documentElement.doScroll && win == win.top) {
6759                                         (function() {
6760                                                 if (t.domLoaded)
6761                                                         return;
6762
6763                                                 try {
6764                                                         // If IE is used, use the trick by Diego Perini
6765                                                         // http://javascript.nwbox.com/IEContentLoaded/
6766                                                         doc.documentElement.doScroll("left");
6767                                                 } catch (ex) {
6768                                                         setTimeout(arguments.callee, 0);
6769                                                         return;
6770                                                 }
6771
6772                                                 t._pageInit(win);
6773                                         })();
6774                                 }
6775                         } else if (doc.addEventListener) {
6776                                 t._add(win, 'DOMContentLoaded', function() {
6777                                         t._pageInit(win);
6778                                 });
6779                         }
6780
6781                         t._add(win, 'load', function() {
6782                                 t._pageInit(win);
6783                         });
6784                 },
6785
6786                 _stoppers : {
6787                         preventDefault : function() {
6788                                 this.returnValue = false;
6789                         },
6790
6791                         stopPropagation : function() {
6792                                 this.cancelBubble = true;
6793                         }
6794                 }
6795         });
6796
6797         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
6798
6799         // Dispatch DOM content loaded event for IE and Safari
6800         Event._wait(window);
6801
6802         tinymce.addUnload(function() {
6803                 Event.destroy();
6804         });
6805 })(tinymce);
6806
6807 (function(tinymce) {
6808         tinymce.dom.Element = function(id, settings) {
6809                 var t = this, dom, el;
6810
6811                 t.settings = settings = settings || {};
6812                 t.id = id;
6813                 t.dom = dom = settings.dom || tinymce.DOM;
6814
6815                 // Only IE leaks DOM references, this is a lot faster
6816                 if (!tinymce.isIE)
6817                         el = dom.get(t.id);
6818
6819                 tinymce.each(
6820                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
6821                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
6822                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
6823                                 'isHidden,setHTML,get').split(/,/)
6824                         , function(k) {
6825                                 t[k] = function() {
6826                                         var a = [id], i;
6827
6828                                         for (i = 0; i < arguments.length; i++)
6829                                                 a.push(arguments[i]);
6830
6831                                         a = dom[k].apply(dom, a);
6832                                         t.update(k);
6833
6834                                         return a;
6835                                 };
6836                 });
6837
6838                 tinymce.extend(t, {
6839                         on : function(n, f, s) {
6840                                 return tinymce.dom.Event.add(t.id, n, f, s);
6841                         },
6842
6843                         getXY : function() {
6844                                 return {
6845                                         x : parseInt(t.getStyle('left')),
6846                                         y : parseInt(t.getStyle('top'))
6847                                 };
6848                         },
6849
6850                         getSize : function() {
6851                                 var n = dom.get(t.id);
6852
6853                                 return {
6854                                         w : parseInt(t.getStyle('width') || n.clientWidth),
6855                                         h : parseInt(t.getStyle('height') || n.clientHeight)
6856                                 };
6857                         },
6858
6859                         moveTo : function(x, y) {
6860                                 t.setStyles({left : x, top : y});
6861                         },
6862
6863                         moveBy : function(x, y) {
6864                                 var p = t.getXY();
6865
6866                                 t.moveTo(p.x + x, p.y + y);
6867                         },
6868
6869                         resizeTo : function(w, h) {
6870                                 t.setStyles({width : w, height : h});
6871                         },
6872
6873                         resizeBy : function(w, h) {
6874                                 var s = t.getSize();
6875
6876                                 t.resizeTo(s.w + w, s.h + h);
6877                         },
6878
6879                         update : function(k) {
6880                                 var b;
6881
6882                                 if (tinymce.isIE6 && settings.blocker) {
6883                                         k = k || '';
6884
6885                                         // Ignore getters
6886                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
6887                                                 return;
6888
6889                                         // Remove blocker on remove
6890                                         if (k == 'remove') {
6891                                                 dom.remove(t.blocker);
6892                                                 return;
6893                                         }
6894
6895                                         if (!t.blocker) {
6896                                                 t.blocker = dom.uniqueId();
6897                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
6898                                                 dom.setStyle(b, 'opacity', 0);
6899                                         } else
6900                                                 b = dom.get(t.blocker);
6901
6902                                         dom.setStyles(b, {
6903                                                 left : t.getStyle('left', 1),
6904                                                 top : t.getStyle('top', 1),
6905                                                 width : t.getStyle('width', 1),
6906                                                 height : t.getStyle('height', 1),
6907                                                 display : t.getStyle('display', 1),
6908                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
6909                                         });
6910                                 }
6911                         }
6912                 });
6913         };
6914 })(tinymce);
6915
6916 (function(tinymce) {
6917         function trimNl(s) {
6918                 return s.replace(/[\n\r]+/g, '');
6919         };
6920
6921         // Shorten names
6922         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
6923
6924         tinymce.create('tinymce.dom.Selection', {
6925                 Selection : function(dom, win, serializer) {
6926                         var t = this;
6927
6928                         t.dom = dom;
6929                         t.win = win;
6930                         t.serializer = serializer;
6931
6932                         // Add events
6933                         each([
6934                                 'onBeforeSetContent',
6935
6936                                 'onBeforeGetContent',
6937
6938                                 'onSetContent',
6939
6940                                 'onGetContent'
6941                         ], function(e) {
6942                                 t[e] = new tinymce.util.Dispatcher(t);
6943                         });
6944
6945                         // No W3C Range support
6946                         if (!t.win.getSelection)
6947                                 t.tridentSel = new tinymce.dom.TridentSelection(t);
6948
6949                         if (tinymce.isIE && dom.boxModel)
6950                                 this._fixIESelection();
6951
6952                         // Prevent leaks
6953                         tinymce.addUnload(t.destroy, t);
6954                 },
6955
6956                 getContent : function(s) {
6957                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
6958
6959                         s = s || {};
6960                         wb = wa = '';
6961                         s.get = true;
6962                         s.format = s.format || 'html';
6963                         t.onBeforeGetContent.dispatch(t, s);
6964
6965                         if (s.format == 'text')
6966                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
6967
6968                         if (r.cloneContents) {
6969                                 n = r.cloneContents();
6970
6971                                 if (n)
6972                                         e.appendChild(n);
6973                         } else if (is(r.item) || is(r.htmlText))
6974                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
6975                         else
6976                                 e.innerHTML = r.toString();
6977
6978                         // Keep whitespace before and after
6979                         if (/^\s/.test(e.innerHTML))
6980                                 wb = ' ';
6981
6982                         if (/\s+$/.test(e.innerHTML))
6983                                 wa = ' ';
6984
6985                         s.getInner = true;
6986
6987                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
6988                         t.onGetContent.dispatch(t, s);
6989
6990                         return s.content;
6991                 },
6992
6993                 setContent : function(content, args) {
6994                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
6995
6996                         args = args || {format : 'html'};
6997                         args.set = true;
6998                         content = args.content = content;
6999
7000                         // Dispatch before set content event
7001                         if (!args.no_events)
7002                                 self.onBeforeSetContent.dispatch(self, args);
7003
7004                         content = args.content;
7005
7006                         if (rng.insertNode) {
7007                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
7008                                 content += '<span id="__caret">_</span>';
7009
7010                                 // Delete and insert new node
7011                                 if (rng.startContainer == doc && rng.endContainer == doc) {
7012                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
7013                                         doc.body.innerHTML = content;
7014                                 } else {
7015                                         rng.deleteContents();
7016
7017                                         if (doc.body.childNodes.length == 0) {
7018                                                 doc.body.innerHTML = content;
7019                                         } else {
7020                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
7021                                                 if (rng.createContextualFragment) {
7022                                                         rng.insertNode(rng.createContextualFragment(content));
7023                                                 } else {
7024                                                         // Fake createContextualFragment call in IE 9
7025                                                         frag = doc.createDocumentFragment();
7026                                                         temp = doc.createElement('div');
7027
7028                                                         frag.appendChild(temp);
7029                                                         temp.outerHTML = content;
7030
7031                                                         rng.insertNode(frag);
7032                                                 }
7033                                         }
7034                                 }
7035
7036                                 // Move to caret marker
7037                                 caretNode = self.dom.get('__caret');
7038
7039                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
7040                                 rng = doc.createRange();
7041                                 rng.setStartBefore(caretNode);
7042                                 rng.setEndBefore(caretNode);
7043                                 self.setRng(rng);
7044
7045                                 // Remove the caret position
7046                                 self.dom.remove('__caret');
7047                                 self.setRng(rng);
7048                         } else {
7049                                 if (rng.item) {
7050                                         // Delete content and get caret text selection
7051                                         doc.execCommand('Delete', false, null);
7052                                         rng = self.getRng();
7053                                 }
7054
7055                                 rng.pasteHTML(content);
7056                         }
7057
7058                         // Dispatch set content event
7059                         if (!args.no_events)
7060                                 self.onSetContent.dispatch(self, args);
7061                 },
7062
7063                 getStart : function() {
7064                         var rng = this.getRng(), startElement, parentElement, checkRng, node;
7065
7066                         if (rng.duplicate || rng.item) {
7067                                 // Control selection, return first item
7068                                 if (rng.item)
7069                                         return rng.item(0);
7070
7071                                 // Get start element
7072                                 checkRng = rng.duplicate();
7073                                 checkRng.collapse(1);
7074                                 startElement = checkRng.parentElement();
7075
7076                                 // Check if range parent is inside the start element, then return the inner parent element
7077                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7078                                 parentElement = node = rng.parentElement();
7079                                 while (node = node.parentNode) {
7080                                         if (node == startElement) {
7081                                                 startElement = parentElement;
7082                                                 break;
7083                                         }
7084                                 }
7085
7086                                 return startElement;
7087                         } else {
7088                                 startElement = rng.startContainer;
7089
7090                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
7091                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
7092
7093                                 if (startElement && startElement.nodeType == 3)
7094                                         return startElement.parentNode;
7095
7096                                 return startElement;
7097                         }
7098                 },
7099
7100                 getEnd : function() {
7101                         var t = this, r = t.getRng(), e, eo;
7102
7103                         if (r.duplicate || r.item) {
7104                                 if (r.item)
7105                                         return r.item(0);
7106
7107                                 r = r.duplicate();
7108                                 r.collapse(0);
7109                                 e = r.parentElement();
7110
7111                                 if (e && e.nodeName == 'BODY')
7112                                         return e.lastChild || e;
7113
7114                                 return e;
7115                         } else {
7116                                 e = r.endContainer;
7117                                 eo = r.endOffset;
7118
7119                                 if (e.nodeType == 1 && e.hasChildNodes())
7120                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];
7121
7122                                 if (e && e.nodeType == 3)
7123                                         return e.parentNode;
7124
7125                                 return e;
7126                         }
7127                 },
7128
7129                 getBookmark : function(type, normalized) {
7130                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
7131
7132                         function findIndex(name, element) {
7133                                 var index = 0;
7134
7135                                 each(dom.select(name), function(node, i) {
7136                                         if (node == element)
7137                                                 index = i;
7138                                 });
7139
7140                                 return index;
7141                         };
7142
7143                         if (type == 2) {
7144                                 function getLocation() {
7145                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
7146
7147                                         function getPoint(rng, start) {
7148                                                 var container = rng[start ? 'startContainer' : 'endContainer'],
7149                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
7150
7151                                                 if (container.nodeType == 3) {
7152                                                         if (normalized) {
7153                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
7154                                                                         offset += node.nodeValue.length;
7155                                                         }
7156
7157                                                         point.push(offset);
7158                                                 } else {
7159                                                         childNodes = container.childNodes;
7160
7161                                                         if (offset >= childNodes.length && childNodes.length) {
7162                                                                 after = 1;
7163                                                                 offset = Math.max(0, childNodes.length - 1);
7164                                                         }
7165
7166                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
7167                                                 }
7168
7169                                                 for (; container && container != root; container = container.parentNode)
7170                                                         point.push(t.dom.nodeIndex(container, normalized));
7171
7172                                                 return point;
7173                                         };
7174
7175                                         bookmark.start = getPoint(rng, true);
7176
7177                                         if (!t.isCollapsed())
7178                                                 bookmark.end = getPoint(rng);
7179
7180                                         return bookmark;
7181                                 };
7182
7183                                 return getLocation();
7184                         }
7185
7186                         // Handle simple range
7187                         if (type)
7188                                 return {rng : t.getRng()};
7189
7190                         rng = t.getRng();
7191                         id = dom.uniqueId();
7192                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();
7193                         styles = 'overflow:hidden;line-height:0px';
7194
7195                         // Explorer method
7196                         if (rng.duplicate || rng.item) {
7197                                 // Text selection
7198                                 if (!rng.item) {
7199                                         rng2 = rng.duplicate();
7200
7201                                         try {
7202                                                 // Insert start marker
7203                                                 rng.collapse();
7204                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
7205
7206                                                 // Insert end marker
7207                                                 if (!collapsed) {
7208                                                         rng2.collapse(false);
7209
7210                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
7211                                                         rng.moveToElementText(rng2.parentElement());
7212                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)
7213                                                                 rng2.move('character', -1);
7214
7215                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
7216                                                 }
7217                                         } catch (ex) {
7218                                                 // IE might throw unspecified error so lets ignore it
7219                                                 return null;
7220                                         }
7221                                 } else {
7222                                         // Control selection
7223                                         element = rng.item(0);
7224                                         name = element.nodeName;
7225
7226                                         return {name : name, index : findIndex(name, element)};
7227                                 }
7228                         } else {
7229                                 element = t.getNode();
7230                                 name = element.nodeName;
7231                                 if (name == 'IMG')
7232                                         return {name : name, index : findIndex(name, element)};
7233
7234                                 // W3C method
7235                                 rng2 = rng.cloneRange();
7236
7237                                 // Insert end marker
7238                                 if (!collapsed) {
7239                                         rng2.collapse(false);
7240                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
7241                                 }
7242
7243                                 rng.collapse(true);
7244                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
7245                         }
7246
7247                         t.moveToBookmark({id : id, keep : 1});
7248
7249                         return {id : id};
7250                 },
7251
7252                 moveToBookmark : function(bookmark) {
7253                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
7254
7255                         // Clear selection cache
7256                         if (t.tridentSel)
7257                                 t.tridentSel.destroy();
7258
7259                         if (bookmark) {
7260                                 if (bookmark.start) {
7261                                         rng = dom.createRng();
7262                                         root = dom.getRoot();
7263
7264                                         function setEndPoint(start) {
7265                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
7266
7267                                                 if (point) {
7268                                                         offset = point[0];
7269
7270                                                         // Find container node
7271                                                         for (node = root, i = point.length - 1; i >= 1; i--) {
7272                                                                 children = node.childNodes;
7273
7274                                                                 if (point[i] > children.length - 1)
7275                                                                         return;
7276
7277                                                                 node = children[point[i]];
7278                                                         }
7279
7280                                                         // Move text offset to best suitable location
7281                                                         if (node.nodeType === 3)
7282                                                                 offset = Math.min(point[0], node.nodeValue.length);
7283
7284                                                         // Move element offset to best suitable location
7285                                                         if (node.nodeType === 1)
7286                                                                 offset = Math.min(point[0], node.childNodes.length);
7287
7288                                                         // Set offset within container node
7289                                                         if (start)
7290                                                                 rng.setStart(node, offset);
7291                                                         else
7292                                                                 rng.setEnd(node, offset);
7293                                                 }
7294
7295                                                 return true;
7296                                         };
7297
7298                                         if (setEndPoint(true) && setEndPoint()) {
7299                                                 t.setRng(rng);
7300                                         }
7301                                 } else if (bookmark.id) {
7302                                         function restoreEndPoint(suffix) {
7303                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
7304
7305                                                 if (marker) {
7306                                                         node = marker.parentNode;
7307
7308                                                         if (suffix == 'start') {
7309                                                                 if (!keep) {
7310                                                                         idx = dom.nodeIndex(marker);
7311                                                                 } else {
7312                                                                         node = marker.firstChild;
7313                                                                         idx = 1;
7314                                                                 }
7315
7316                                                                 startContainer = endContainer = node;
7317                                                                 startOffset = endOffset = idx;
7318                                                         } else {
7319                                                                 if (!keep) {
7320                                                                         idx = dom.nodeIndex(marker);
7321                                                                 } else {
7322                                                                         node = marker.firstChild;
7323                                                                         idx = 1;
7324                                                                 }
7325
7326                                                                 endContainer = node;
7327                                                                 endOffset = idx;
7328                                                         }
7329
7330                                                         if (!keep) {
7331                                                                 prev = marker.previousSibling;
7332                                                                 next = marker.nextSibling;
7333
7334                                                                 // Remove all marker text nodes
7335                                                                 each(tinymce.grep(marker.childNodes), function(node) {
7336                                                                         if (node.nodeType == 3)
7337                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
7338                                                                 });
7339
7340                                                                 // Remove marker but keep children if for example contents where inserted into the marker
7341                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
7342                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))
7343                                                                         dom.remove(marker, 1);
7344
7345                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
7346                                                                 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
7347                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
7348                                                                         idx = prev.nodeValue.length;
7349                                                                         prev.appendData(next.nodeValue);
7350                                                                         dom.remove(next);
7351
7352                                                                         if (suffix == 'start') {
7353                                                                                 startContainer = endContainer = prev;
7354                                                                                 startOffset = endOffset = idx;
7355                                                                         } else {
7356                                                                                 endContainer = prev;
7357                                                                                 endOffset = idx;
7358                                                                         }
7359                                                                 }
7360                                                         }
7361                                                 }
7362                                         };
7363
7364                                         function addBogus(node) {
7365                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
7366                                                 if (dom.isBlock(node) && !node.innerHTML)
7367                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
7368
7369                                                 return node;
7370                                         };
7371
7372                                         // Restore start/end points
7373                                         restoreEndPoint('start');
7374                                         restoreEndPoint('end');
7375
7376                                         if (startContainer) {
7377                                                 rng = dom.createRng();
7378                                                 rng.setStart(addBogus(startContainer), startOffset);
7379                                                 rng.setEnd(addBogus(endContainer), endOffset);
7380                                                 t.setRng(rng);
7381                                         }
7382                                 } else if (bookmark.name) {
7383                                         t.select(dom.select(bookmark.name)[bookmark.index]);
7384                                 } else if (bookmark.rng)
7385                                         t.setRng(bookmark.rng);
7386                         }
7387                 },
7388
7389                 select : function(node, content) {
7390                         var t = this, dom = t.dom, rng = dom.createRng(), idx;
7391
7392                         if (node) {
7393                                 idx = dom.nodeIndex(node);
7394                                 rng.setStart(node.parentNode, idx);
7395                                 rng.setEnd(node.parentNode, idx + 1);
7396
7397                                 // Find first/last text node or BR element
7398                                 if (content) {
7399                                         function setPoint(node, start) {
7400                                                 var walker = new tinymce.dom.TreeWalker(node, node);
7401
7402                                                 do {
7403                                                         // Text node
7404                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
7405                                                                 if (start)
7406                                                                         rng.setStart(node, 0);
7407                                                                 else
7408                                                                         rng.setEnd(node, node.nodeValue.length);
7409
7410                                                                 return;
7411                                                         }
7412
7413                                                         // BR element
7414                                                         if (node.nodeName == 'BR') {
7415                                                                 if (start)
7416                                                                         rng.setStartBefore(node);
7417                                                                 else
7418                                                                         rng.setEndBefore(node);
7419
7420                                                                 return;
7421                                                         }
7422                                                 } while (node = (start ? walker.next() : walker.prev()));
7423                                         };
7424
7425                                         setPoint(node, 1);
7426                                         setPoint(node);
7427                                 }
7428
7429                                 t.setRng(rng);
7430                         }
7431
7432                         return node;
7433                 },
7434
7435                 isCollapsed : function() {
7436                         var t = this, r = t.getRng(), s = t.getSel();
7437
7438                         if (!r || r.item)
7439                                 return false;
7440
7441                         if (r.compareEndPoints)
7442                                 return r.compareEndPoints('StartToEnd', r) === 0;
7443
7444                         return !s || r.collapsed;
7445                 },
7446
7447                 collapse : function(to_start) {
7448                         var self = this, rng = self.getRng(), node;
7449
7450                         // Control range on IE
7451                         if (rng.item) {
7452                                 node = rng.item(0);
7453                                 rng = self.win.document.body.createTextRange();
7454                                 rng.moveToElementText(node);
7455                         }
7456
7457                         rng.collapse(!!to_start);
7458                         self.setRng(rng);
7459                 },
7460
7461                 getSel : function() {
7462                         var t = this, w = this.win;
7463
7464                         return w.getSelection ? w.getSelection() : w.document.selection;
7465                 },
7466
7467                 getRng : function(w3c) {
7468                         var t = this, s, r, elm, doc = t.win.document;
7469
7470                         // Found tridentSel object then we need to use that one
7471                         if (w3c && t.tridentSel)
7472                                 return t.tridentSel.getRangeAt(0);
7473
7474                         try {
7475                                 if (s = t.getSel())
7476                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
7477                         } catch (ex) {
7478                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
7479                         }
7480
7481                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
7482                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
7483                                 elm = doc.selection.createRange().item(0);
7484                                 r = doc.createRange();
7485                                 r.setStartBefore(elm);
7486                                 r.setEndAfter(elm);
7487                         }
7488
7489                         // No range found then create an empty one
7490                         // This can occur when the editor is placed in a hidden container element on Gecko
7491                         // Or on IE when there was an exception
7492                         if (!r)
7493                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
7494
7495                         if (t.selectedRange && t.explicitRange) {
7496                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
7497                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
7498                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
7499                                         r = t.explicitRange;
7500                                 } else {
7501                                         t.selectedRange = null;
7502                                         t.explicitRange = null;
7503                                 }
7504                         }
7505
7506                         return r;
7507                 },
7508
7509                 setRng : function(r) {
7510                         var s, t = this;
7511                         
7512                         if (!t.tridentSel) {
7513                                 s = t.getSel();
7514
7515                                 if (s) {
7516                                         t.explicitRange = r;
7517
7518                                         try {
7519                                                 s.removeAllRanges();
7520                                         } catch (ex) {
7521                                                 // IE9 might throw errors here don't know why
7522                                         }
7523
7524                                         s.addRange(r);
7525                                         t.selectedRange = s.getRangeAt(0);
7526                                 }
7527                         } else {
7528                                 // Is W3C Range
7529                                 if (r.cloneRange) {
7530                                         t.tridentSel.addRange(r);
7531                                         return;
7532                                 }
7533
7534                                 // Is IE specific range
7535                                 try {
7536                                         r.select();
7537                                 } catch (ex) {
7538                                         // Needed for some odd IE bug #1843306
7539                                 }
7540                         }
7541                 },
7542
7543                 setNode : function(n) {
7544                         var t = this;
7545
7546                         t.setContent(t.dom.getOuterHTML(n));
7547
7548                         return n;
7549                 },
7550
7551                 getNode : function() {
7552                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
7553
7554                         // Range maybe lost after the editor is made visible again
7555                         if (!rng)
7556                                 return t.dom.getRoot();
7557
7558                         if (rng.setStart) {
7559                                 elm = rng.commonAncestorContainer;
7560
7561                                 // Handle selection a image or other control like element such as anchors
7562                                 if (!rng.collapsed) {
7563                                         if (rng.startContainer == rng.endContainer) {
7564                                                 if (rng.endOffset - rng.startOffset < 2) {
7565                                                         if (rng.startContainer.hasChildNodes())
7566                                                                 elm = rng.startContainer.childNodes[rng.startOffset];
7567                                                 }
7568                                         }
7569
7570                                         // If the anchor node is a element instead of a text node then return this element
7571                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
7572                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
7573
7574                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
7575                                         // This happens when you double click an underlined word in FireFox.
7576                                         if (start.nodeType === 3 && end.nodeType === 3) {
7577                                                 function skipEmptyTextNodes(n, forwards) {
7578                                                         var orig = n;
7579                                                         while (n && n.nodeType === 3 && n.length === 0) {
7580                                                                 n = forwards ? n.nextSibling : n.previousSibling;
7581                                                         }
7582                                                         return n || orig;
7583                                                 }
7584                                                 if (start.length === rng.startOffset) {
7585                                                         start = skipEmptyTextNodes(start.nextSibling, true);
7586                                                 } else {
7587                                                         start = start.parentNode;
7588                                                 }
7589                                                 if (rng.endOffset === 0) {
7590                                                         end = skipEmptyTextNodes(end.previousSibling, false);
7591                                                 } else {
7592                                                         end = end.parentNode;
7593                                                 }
7594
7595                                                 if (start && start === end)
7596                                                         return start;
7597                                         }
7598                                 }
7599
7600                                 if (elm && elm.nodeType == 3)
7601                                         return elm.parentNode;
7602
7603                                 return elm;
7604                         }
7605
7606                         return rng.item ? rng.item(0) : rng.parentElement();
7607                 },
7608
7609                 getSelectedBlocks : function(st, en) {
7610                         var t = this, dom = t.dom, sb, eb, n, bl = [];
7611
7612                         sb = dom.getParent(st || t.getStart(), dom.isBlock);
7613                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);
7614
7615                         if (sb)
7616                                 bl.push(sb);
7617
7618                         if (sb && eb && sb != eb) {
7619                                 n = sb;
7620
7621                                 while ((n = n.nextSibling) && n != eb) {
7622                                         if (dom.isBlock(n))
7623                                                 bl.push(n);
7624                                 }
7625                         }
7626
7627                         if (eb && sb != eb)
7628                                 bl.push(eb);
7629
7630                         return bl;
7631                 },
7632
7633                 destroy : function(s) {
7634                         var t = this;
7635
7636                         t.win = null;
7637
7638                         if (t.tridentSel)
7639                                 t.tridentSel.destroy();
7640
7641                         // Manual destroy then remove unload handler
7642                         if (!s)
7643                                 tinymce.removeUnload(t.destroy);
7644                 },
7645
7646                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
7647                 _fixIESelection : function() {
7648                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
7649
7650                         // Make HTML element unselectable since we are going to handle selection by hand
7651                         doc.documentElement.unselectable = true;
7652
7653                         // Return range from point or null if it failed
7654                         function rngFromPoint(x, y) {
7655                                 var rng = body.createTextRange();
7656
7657                                 try {
7658                                         rng.moveToPoint(x, y);
7659                                 } catch (ex) {
7660                                         // IE sometimes throws and exception, so lets just ignore it
7661                                         rng = null;
7662                                 }
7663
7664                                 return rng;
7665                         };
7666
7667                         // Fires while the selection is changing
7668                         function selectionChange(e) {
7669                                 var pointRng;
7670
7671                                 // Check if the button is down or not
7672                                 if (e.button) {
7673                                         // Create range from mouse position
7674                                         pointRng = rngFromPoint(e.x, e.y);
7675
7676                                         if (pointRng) {
7677                                                 // Check if pointRange is before/after selection then change the endPoint
7678                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
7679                                                         pointRng.setEndPoint('StartToStart', startRng);
7680                                                 else
7681                                                         pointRng.setEndPoint('EndToEnd', startRng);
7682
7683                                                 pointRng.select();
7684                                         }
7685                                 } else
7686                                         endSelection();
7687                         }
7688
7689                         // Removes listeners
7690                         function endSelection() {
7691                                 var rng = doc.selection.createRange();
7692
7693                                 // If the range is collapsed then use the last start range
7694                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
7695                                         startRng.select();
7696
7697                                 dom.unbind(doc, 'mouseup', endSelection);
7698                                 dom.unbind(doc, 'mousemove', selectionChange);
7699                                 startRng = started = 0;
7700                         };
7701
7702                         // Detect when user selects outside BODY
7703                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
7704                                 if (e.target.nodeName === 'HTML') {
7705                                         if (started)
7706                                                 endSelection();
7707
7708                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
7709                                         htmlElm = doc.documentElement;
7710                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)
7711                                                 return;
7712
7713                                         started = 1;
7714                                         // Setup start position
7715                                         startRng = rngFromPoint(e.x, e.y);
7716                                         if (startRng) {
7717                                                 // Listen for selection change events
7718                                                 dom.bind(doc, 'mouseup', endSelection);
7719                                                 dom.bind(doc, 'mousemove', selectionChange);
7720
7721                                                 dom.win.focus();
7722                                                 startRng.select();
7723                                         }
7724                                 }
7725                         });
7726                 }
7727         });
7728 })(tinymce);
7729
7730 (function(tinymce) {
7731         tinymce.dom.Serializer = function(settings, dom, schema) {
7732                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
7733
7734                 // Support the old apply_source_formatting option
7735                 if (!settings.apply_source_formatting)
7736                         settings.indent = false;
7737
7738                 settings.remove_trailing_brs = true;
7739
7740                 // Default DOM and Schema if they are undefined
7741                 dom = dom || tinymce.DOM;
7742                 schema = schema || new tinymce.html.Schema(settings);
7743                 settings.entity_encoding = settings.entity_encoding || 'named';
7744
7745                 onPreProcess = new tinymce.util.Dispatcher(self);
7746
7747                 onPostProcess = new tinymce.util.Dispatcher(self);
7748
7749                 htmlParser = new tinymce.html.DomParser(settings, schema);
7750
7751                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
7752                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
7753                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
7754
7755                         while (i--) {
7756                                 node = nodes[i];
7757
7758                                 value = node.attributes.map[internalName];
7759                                 if (value !== undef) {
7760                                         // Set external name to internal value and remove internal
7761                                         node.attr(name, value.length > 0 ? value : null);
7762                                         node.attr(internalName, null);
7763                                 } else {
7764                                         // No internal attribute found then convert the value we have in the DOM
7765                                         value = node.attributes.map[name];
7766
7767                                         if (name === "style")
7768                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
7769                                         else if (urlConverter)
7770                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
7771
7772                                         node.attr(name, value.length > 0 ? value : null);
7773                                 }
7774                         }
7775                 });
7776
7777                 // Remove internal classes mceItem<..>
7778                 htmlParser.addAttributeFilter('class', function(nodes, name) {
7779                         var i = nodes.length, node, value;
7780
7781                         while (i--) {
7782                                 node = nodes[i];
7783                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
7784                                 node.attr('class', value.length > 0 ? value : null);
7785                         }
7786                 });
7787
7788                 // Remove bookmark elements
7789                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
7790                         var i = nodes.length, node;
7791
7792                         while (i--) {
7793                                 node = nodes[i];
7794
7795                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
7796                                         node.remove();
7797                         }
7798                 });
7799
7800                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
7801                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
7802                         var i = nodes.length, node, value;
7803
7804                         function trim(value) {
7805                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
7806                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
7807                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
7808                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
7809                         };
7810
7811                         while (i--) {
7812                                 node = nodes[i];
7813                                 value = node.firstChild ? node.firstChild.value : '';
7814
7815                                 if (name === "script") {
7816                                         // Remove mce- prefix from script elements
7817                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
7818
7819                                         if (value.length > 0)
7820                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
7821                                 } else {
7822                                         if (value.length > 0)
7823                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
7824                                 }
7825                         }
7826                 });
7827
7828                 // Convert comments to cdata and handle protected comments
7829                 htmlParser.addNodeFilter('#comment', function(nodes, name) {
7830                         var i = nodes.length, node;
7831
7832                         while (i--) {
7833                                 node = nodes[i];
7834
7835                                 if (node.value.indexOf('[CDATA[') === 0) {
7836                                         node.name = '#cdata';
7837                                         node.type = 4;
7838                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
7839                                 } else if (node.value.indexOf('mce:protected ') === 0) {
7840                                         node.name = "#text";
7841                                         node.type = 3;
7842                                         node.raw = true;
7843                                         node.value = unescape(node.value).substr(14);
7844                                 }
7845                         }
7846                 });
7847
7848                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
7849                         var i = nodes.length, node;
7850
7851                         while (i--) {
7852                                 node = nodes[i];
7853                                 if (node.type === 7)
7854                                         node.remove();
7855                                 else if (node.type === 1) {
7856                                         if (name === "input" && !("type" in node.attributes.map))
7857                                                 node.attr('type', 'text');
7858                                 }
7859                         }
7860                 });
7861
7862                 // Fix list elements, TODO: Replace this later
7863                 if (settings.fix_list_elements) {
7864                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
7865                                 var i = nodes.length, node, parentNode;
7866
7867                                 while (i--) {
7868                                         node = nodes[i];
7869                                         parentNode = node.parent;
7870
7871                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
7872                                                 if (node.prev && node.prev.name === 'li') {
7873                                                         node.prev.append(node);
7874                                                 }
7875                                         }
7876                                 }
7877                         });
7878                 }
7879
7880                 // Remove internal data attributes
7881                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
7882                         var i = nodes.length;
7883
7884                         while (i--) {
7885                                 nodes[i].attr(name, null);
7886                         }
7887                 });
7888
7889                 // Return public methods
7890                 return {
7891                         schema : schema,
7892
7893                         addNodeFilter : htmlParser.addNodeFilter,
7894
7895                         addAttributeFilter : htmlParser.addAttributeFilter,
7896
7897                         onPreProcess : onPreProcess,
7898
7899                         onPostProcess : onPostProcess,
7900
7901                         serialize : function(node, args) {
7902                                 var impl, doc, oldDoc, htmlSerializer, content;
7903
7904                                 // Explorer won't clone contents of script and style and the
7905                                 // selected index of select elements are cleared on a clone operation.
7906                                 if (isIE && dom.select('script,style,select').length > 0) {
7907                                         content = node.innerHTML;
7908                                         node = node.cloneNode(false);
7909                                         dom.setHTML(node, content);
7910                                 } else
7911                                         node = node.cloneNode(true);
7912
7913                                 // Nodes needs to be attached to something in WebKit/Opera
7914                                 // Older builds of Opera crashes if you attach the node to an document created dynamically
7915                                 // and since we can't feature detect a crash we need to sniff the acutal build number
7916                                 // This fix will make DOM ranges and make Sizzle happy!
7917                                 impl = node.ownerDocument.implementation;
7918                                 if (impl.createHTMLDocument) {
7919                                         // Create an empty HTML document
7920                                         doc = impl.createHTMLDocument("");
7921
7922                                         // Add the element or it's children if it's a body element to the new document
7923                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
7924                                                 doc.body.appendChild(doc.importNode(node, true));
7925                                         });
7926
7927                                         // Grab first child or body element for serialization
7928                                         if (node.nodeName != 'BODY')
7929                                                 node = doc.body.firstChild;
7930                                         else
7931                                                 node = doc.body;
7932
7933                                         // set the new document in DOMUtils so createElement etc works
7934                                         oldDoc = dom.doc;
7935                                         dom.doc = doc;
7936                                 }
7937
7938                                 args = args || {};
7939                                 args.format = args.format || 'html';
7940
7941                                 // Pre process
7942                                 if (!args.no_events) {
7943                                         args.node = node;
7944                                         onPreProcess.dispatch(self, args);
7945                                 }
7946
7947                                 // Setup serializer
7948                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
7949
7950                                 // Parse and serialize HTML
7951                                 args.content = htmlSerializer.serialize(
7952                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
7953                                 );
7954
7955                                 // Replace all BOM characters for now until we can find a better solution
7956                                 if (!args.cleanup)
7957                                         args.content = args.content.replace(/\uFEFF/g, '');
7958
7959                                 // Post process
7960                                 if (!args.no_events)
7961                                         onPostProcess.dispatch(self, args);
7962
7963                                 // Restore the old document if it was changed
7964                                 if (oldDoc)
7965                                         dom.doc = oldDoc;
7966
7967                                 args.node = null;
7968
7969                                 return args.content;
7970                         },
7971
7972                         addRules : function(rules) {
7973                                 schema.addValidElements(rules);
7974                         },
7975
7976                         setRules : function(rules) {
7977                                 schema.setValidElements(rules);
7978                         }
7979                 };
7980         };
7981 })(tinymce);
7982 (function(tinymce) {
7983         tinymce.dom.ScriptLoader = function(settings) {
7984                 var QUEUED = 0,
7985                         LOADING = 1,
7986                         LOADED = 2,
7987                         states = {},
7988                         queue = [],
7989                         scriptLoadedCallbacks = {},
7990                         queueLoadedCallbacks = [],
7991                         loading = 0,
7992                         undefined;
7993
7994                 function loadScript(url, callback) {
7995                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;
7996
7997                         // Execute callback when script is loaded
7998                         function done() {
7999                                 dom.remove(id);
8000
8001                                 if (elm)
8002                                         elm.onreadystatechange = elm.onload = elm = null;
8003
8004                                 callback();
8005                         };
8006                         
8007                         function error() {
8008                                 // Report the error so it's easier for people to spot loading errors
8009                                 if (typeof(console) !== "undefined" && console.log)
8010                                         console.log("Failed to load: " + url);
8011
8012                                 // We can't mark it as done if there is a load error since
8013                                 // A) We don't want to produce 404 errors on the server and
8014                                 // B) the onerror event won't fire on all browsers.
8015                                 // done();
8016                         };
8017
8018                         id = dom.uniqueId();
8019
8020                         if (tinymce.isIE6) {
8021                                 uri = new tinymce.util.URI(url);
8022                                 loc = location;
8023
8024                                 // If script is from same domain and we
8025                                 // use IE 6 then use XHR since it's more reliable
8026                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
8027                                         tinymce.util.XHR.send({
8028                                                 url : tinymce._addVer(uri.getURI()),
8029                                                 success : function(content) {
8030                                                         // Create new temp script element
8031                                                         var script = dom.create('script', {
8032                                                                 type : 'text/javascript'
8033                                                         });
8034
8035                                                         // Evaluate script in global scope
8036                                                         script.text = content;
8037                                                         document.getElementsByTagName('head')[0].appendChild(script);
8038                                                         dom.remove(script);
8039
8040                                                         done();
8041                                                 },
8042                                                 
8043                                                 error : error
8044                                         });
8045
8046                                         return;
8047                                 }
8048                         }
8049
8050                         // Create new script element
8051                         elm = dom.create('script', {
8052                                 id : id,
8053                                 type : 'text/javascript',
8054                                 src : tinymce._addVer(url)
8055                         });
8056
8057                         // Add onload listener for non IE browsers since IE9
8058                         // fires onload event before the script is parsed and executed
8059                         if (!tinymce.isIE)
8060                                 elm.onload = done;
8061
8062                         // Add onerror event will get fired on some browsers but not all of them
8063                         elm.onerror = error;
8064
8065                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
8066                         if (!tinymce.isOpera) {
8067                                 elm.onreadystatechange = function() {
8068                                         var state = elm.readyState;
8069
8070                                         // Loaded state is passed on IE 6 however there
8071                                         // are known issues with this method but we can't use
8072                                         // XHR in a cross domain loading
8073                                         if (state == 'complete' || state == 'loaded')
8074                                                 done();
8075                                 };
8076                         }
8077
8078                         // Most browsers support this feature so we report errors
8079                         // for those at least to help users track their missing plugins etc
8080                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
8081                         /*elm.onerror = function() {
8082                                 alert('Failed to load: ' + url);
8083                         };*/
8084
8085                         // Add script to document
8086                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
8087                 };
8088
8089                 this.isDone = function(url) {
8090                         return states[url] == LOADED;
8091                 };
8092
8093                 this.markDone = function(url) {
8094                         states[url] = LOADED;
8095                 };
8096
8097                 this.add = this.load = function(url, callback, scope) {
8098                         var item, state = states[url];
8099
8100                         // Add url to load queue
8101                         if (state == undefined) {
8102                                 queue.push(url);
8103                                 states[url] = QUEUED;
8104                         }
8105
8106                         if (callback) {
8107                                 // Store away callback for later execution
8108                                 if (!scriptLoadedCallbacks[url])
8109                                         scriptLoadedCallbacks[url] = [];
8110
8111                                 scriptLoadedCallbacks[url].push({
8112                                         func : callback,
8113                                         scope : scope || this
8114                                 });
8115                         }
8116                 };
8117
8118                 this.loadQueue = function(callback, scope) {
8119                         this.loadScripts(queue, callback, scope);
8120                 };
8121
8122                 this.loadScripts = function(scripts, callback, scope) {
8123                         var loadScripts;
8124
8125                         function execScriptLoadedCallbacks(url) {
8126                                 // Execute URL callback functions
8127                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
8128                                         callback.func.call(callback.scope);
8129                                 });
8130
8131                                 scriptLoadedCallbacks[url] = undefined;
8132                         };
8133
8134                         queueLoadedCallbacks.push({
8135                                 func : callback,
8136                                 scope : scope || this
8137                         });
8138
8139                         loadScripts = function() {
8140                                 var loadingScripts = tinymce.grep(scripts);
8141
8142                                 // Current scripts has been handled
8143                                 scripts.length = 0;
8144
8145                                 // Load scripts that needs to be loaded
8146                                 tinymce.each(loadingScripts, function(url) {
8147                                         // Script is already loaded then execute script callbacks directly
8148                                         if (states[url] == LOADED) {
8149                                                 execScriptLoadedCallbacks(url);
8150                                                 return;
8151                                         }
8152
8153                                         // Is script not loading then start loading it
8154                                         if (states[url] != LOADING) {
8155                                                 states[url] = LOADING;
8156                                                 loading++;
8157
8158                                                 loadScript(url, function() {
8159                                                         states[url] = LOADED;
8160                                                         loading--;
8161
8162                                                         execScriptLoadedCallbacks(url);
8163
8164                                                         // Load more scripts if they where added by the recently loaded script
8165                                                         loadScripts();
8166                                                 });
8167                                         }
8168                                 });
8169
8170                                 // No scripts are currently loading then execute all pending queue loaded callbacks
8171                                 if (!loading) {
8172                                         tinymce.each(queueLoadedCallbacks, function(callback) {
8173                                                 callback.func.call(callback.scope);
8174                                         });
8175
8176                                         queueLoadedCallbacks.length = 0;
8177                                 }
8178                         };
8179
8180                         loadScripts();
8181                 };
8182         };
8183
8184         // Global script loader
8185         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
8186 })(tinymce);
8187
8188 tinymce.dom.TreeWalker = function(start_node, root_node) {
8189         var node = start_node;
8190
8191         function findSibling(node, start_name, sibling_name, shallow) {
8192                 var sibling, parent;
8193
8194                 if (node) {
8195                         // Walk into nodes if it has a start
8196                         if (!shallow && node[start_name])
8197                                 return node[start_name];
8198
8199                         // Return the sibling if it has one
8200                         if (node != root_node) {
8201                                 sibling = node[sibling_name];
8202                                 if (sibling)
8203                                         return sibling;
8204
8205                                 // Walk up the parents to look for siblings
8206                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
8207                                         sibling = parent[sibling_name];
8208                                         if (sibling)
8209                                                 return sibling;
8210                                 }
8211                         }
8212                 }
8213         };
8214
8215         this.current = function() {
8216                 return node;
8217         };
8218
8219         this.next = function(shallow) {
8220                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
8221         };
8222
8223         this.prev = function(shallow) {
8224                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
8225         };
8226 };
8227
8228 (function(tinymce) {
8229         tinymce.dom.RangeUtils = function(dom) {
8230                 var INVISIBLE_CHAR = '\uFEFF';
8231
8232                 this.walk = function(rng, callback) {
8233                         var startContainer = rng.startContainer,
8234                                 startOffset = rng.startOffset,
8235                                 endContainer = rng.endContainer,
8236                                 endOffset = rng.endOffset,
8237                                 ancestor, startPoint,
8238                                 endPoint, node, parent, siblings, nodes;
8239
8240                         // Handle table cell selection the table plugin enables
8241                         // you to fake select table cells and perform formatting actions on them
8242                         nodes = dom.select('td.mceSelected,th.mceSelected');
8243                         if (nodes.length > 0) {
8244                                 tinymce.each(nodes, function(node) {
8245                                         callback([node]);
8246                                 });
8247
8248                                 return;
8249                         }
8250
8251                         function collectSiblings(node, name, end_node) {
8252                                 var siblings = [];
8253
8254                                 for (; node && node != end_node; node = node[name])
8255                                         siblings.push(node);
8256
8257                                 return siblings;
8258                         };
8259
8260                         function findEndPoint(node, root) {
8261                                 do {
8262                                         if (node.parentNode == root)
8263                                                 return node;
8264
8265                                         node = node.parentNode;
8266                                 } while(node);
8267                         };
8268
8269                         function walkBoundary(start_node, end_node, next) {
8270                                 var siblingName = next ? 'nextSibling' : 'previousSibling';
8271
8272                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
8273                                         parent = node.parentNode;
8274                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
8275
8276                                         if (siblings.length) {
8277                                                 if (!next)
8278                                                         siblings.reverse();
8279
8280                                                 callback(siblings);
8281                                         }
8282                                 }
8283                         };
8284
8285                         // If index based start position then resolve it
8286                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
8287                                 startContainer = startContainer.childNodes[startOffset];
8288
8289                         // If index based end position then resolve it
8290                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
8291                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
8292
8293                         // Find common ancestor and end points
8294                         ancestor = dom.findCommonAncestor(startContainer, endContainer);
8295
8296                         // Same container
8297                         if (startContainer == endContainer)
8298                                 return callback([startContainer]);
8299
8300                         // Process left side
8301                         for (node = startContainer; node; node = node.parentNode) {
8302                                 if (node == endContainer)
8303                                         return walkBoundary(startContainer, ancestor, true);
8304
8305                                 if (node == ancestor)
8306                                         break;
8307                         }
8308
8309                         // Process right side
8310                         for (node = endContainer; node; node = node.parentNode) {
8311                                 if (node == startContainer)
8312                                         return walkBoundary(endContainer, ancestor);
8313
8314                                 if (node == ancestor)
8315                                         break;
8316                         }
8317
8318                         // Find start/end point
8319                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;
8320                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;
8321
8322                         // Walk left leaf
8323                         walkBoundary(startContainer, startPoint, true);
8324
8325                         // Walk the middle from start to end point
8326                         siblings = collectSiblings(
8327                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
8328                                 'nextSibling',
8329                                 endPoint == endContainer ? endPoint.nextSibling : endPoint
8330                         );
8331
8332                         if (siblings.length)
8333                                 callback(siblings);
8334
8335                         // Walk right leaf
8336                         walkBoundary(endContainer, endPoint);
8337                 };
8338
8339                 /*              this.split = function(rng) {
8340                         var startContainer = rng.startContainer,
8341                                 startOffset = rng.startOffset,
8342                                 endContainer = rng.endContainer,
8343                                 endOffset = rng.endOffset;
8344
8345                         function splitText(node, offset) {
8346                                 if (offset == node.nodeValue.length)
8347                                         node.appendData(INVISIBLE_CHAR);
8348
8349                                 node = node.splitText(offset);
8350
8351                                 if (node.nodeValue === INVISIBLE_CHAR)
8352                                         node.nodeValue = '';
8353
8354                                 return node;
8355                         };
8356
8357                         // Handle single text node
8358                         if (startContainer == endContainer) {
8359                                 if (startContainer.nodeType == 3) {
8360                                         if (startOffset != 0)
8361                                                 startContainer = endContainer = splitText(startContainer, startOffset);
8362
8363                                         if (endOffset - startOffset != startContainer.nodeValue.length)
8364                                                 splitText(startContainer, endOffset - startOffset);
8365                                 }
8366                         } else {
8367                                 // Split startContainer text node if needed
8368                                 if (startContainer.nodeType == 3 && startOffset != 0) {
8369                                         startContainer = splitText(startContainer, startOffset);
8370                                         startOffset = 0;
8371                                 }
8372
8373                                 // Split endContainer text node if needed
8374                                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
8375                                         endContainer = splitText(endContainer, endOffset).previousSibling;
8376                                         endOffset = endContainer.nodeValue.length;
8377                                 }
8378                         }
8379
8380                         return {
8381                                 startContainer : startContainer,
8382                                 startOffset : startOffset,
8383                                 endContainer : endContainer,
8384                                 endOffset : endOffset
8385                         };
8386                 };
8387 */
8388         };
8389
8390         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
8391                 if (rng1 && rng2) {
8392                         // Compare native IE ranges
8393                         if (rng1.item || rng1.duplicate) {
8394                                 // Both are control ranges and the selected element matches
8395                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
8396                                         return true;
8397
8398                                 // Both are text ranges and the range matches
8399                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
8400                                         return true;
8401                         } else {
8402                                 // Compare w3c ranges
8403                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
8404                         }
8405                 }
8406
8407                 return false;
8408         };
8409 })(tinymce);
8410
8411 (function(tinymce) {
8412         var Event = tinymce.dom.Event, each = tinymce.each;
8413
8414         tinymce.create('tinymce.ui.KeyboardNavigation', {
8415                 KeyboardNavigation: function(settings, dom) {
8416                         var t = this, root = settings.root, items = settings.items,
8417                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
8418                                         excludeFromTabOrder = settings.excludeFromTabOrder,
8419                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
8420
8421                         dom = dom || tinymce.DOM;
8422
8423                         itemFocussed = function(evt) {
8424                                 focussedId = evt.target.id;
8425                         };
8426                         
8427                         itemBlurred = function(evt) {
8428                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');
8429                         };
8430                         
8431                         rootFocussed = function(evt) {
8432                                 var item = dom.get(focussedId);
8433                                 dom.setAttrib(item, 'tabindex', '0');
8434                                 item.focus();
8435                         };
8436                         
8437                         t.focus = function() {
8438                                 dom.get(focussedId).focus();
8439                         };
8440
8441                         t.destroy = function() {
8442                                 each(items, function(item) {
8443                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);
8444                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);
8445                                 });
8446
8447                                 dom.unbind(dom.get(root), 'focus', rootFocussed);
8448                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);
8449
8450                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
8451                                 t.destroy = function() {};
8452                         };
8453                         
8454                         t.moveFocus = function(dir, evt) {
8455                                 var idx = -1, controls = t.controls, newFocus;
8456
8457                                 if (!focussedId)
8458                                         return;
8459
8460                                 each(items, function(item, index) {
8461                                         if (item.id === focussedId) {
8462                                                 idx = index;
8463                                                 return false;
8464                                         }
8465                                 });
8466
8467                                 idx += dir;
8468                                 if (idx < 0) {
8469                                         idx = items.length - 1;
8470                                 } else if (idx >= items.length) {
8471                                         idx = 0;
8472                                 }
8473                                 
8474                                 newFocus = items[idx];
8475                                 dom.setAttrib(focussedId, 'tabindex', '-1');
8476                                 dom.setAttrib(newFocus.id, 'tabindex', '0');
8477                                 dom.get(newFocus.id).focus();
8478
8479                                 if (settings.actOnFocus) {
8480                                         settings.onAction(newFocus.id);
8481                                 }
8482
8483                                 if (evt)
8484                                         Event.cancel(evt);
8485                         };
8486                         
8487                         rootKeydown = function(evt) {
8488                                 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
8489                                 
8490                                 switch (evt.keyCode) {
8491                                         case DOM_VK_LEFT:
8492                                                 if (enableLeftRight) t.moveFocus(-1);
8493                                                 break;
8494         
8495                                         case DOM_VK_RIGHT:
8496                                                 if (enableLeftRight) t.moveFocus(1);
8497                                                 break;
8498         
8499                                         case DOM_VK_UP:
8500                                                 if (enableUpDown) t.moveFocus(-1);
8501                                                 break;
8502
8503                                         case DOM_VK_DOWN:
8504                                                 if (enableUpDown) t.moveFocus(1);
8505                                                 break;
8506
8507                                         case DOM_VK_ESCAPE:
8508                                                 if (settings.onCancel) {
8509                                                         settings.onCancel();
8510                                                         Event.cancel(evt);
8511                                                 }
8512                                                 break;
8513
8514                                         case DOM_VK_ENTER:
8515                                         case DOM_VK_RETURN:
8516                                         case DOM_VK_SPACE:
8517                                                 if (settings.onAction) {
8518                                                         settings.onAction(focussedId);
8519                                                         Event.cancel(evt);
8520                                                 }
8521                                                 break;
8522                                 }
8523                         };
8524
8525                         // Set up state and listeners for each item.
8526                         each(items, function(item, idx) {
8527                                 var tabindex;
8528
8529                                 if (!item.id) {
8530                                         item.id = dom.uniqueId('_mce_item_');
8531                                 }
8532
8533                                 if (excludeFromTabOrder) {
8534                                         dom.bind(item.id, 'blur', itemBlurred);
8535                                         tabindex = '-1';
8536                                 } else {
8537                                         tabindex = (idx === 0 ? '0' : '-1');
8538                                 }
8539
8540                                 dom.setAttrib(item.id, 'tabindex', tabindex);
8541                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);
8542                         });
8543                         
8544                         // Setup initial state for root element.
8545                         if (items[0]){
8546                                 focussedId = items[0].id;
8547                         }
8548
8549                         dom.setAttrib(root, 'tabindex', '-1');
8550                         
8551                         // Setup listeners for root element.
8552                         dom.bind(dom.get(root), 'focus', rootFocussed);
8553                         dom.bind(dom.get(root), 'keydown', rootKeydown);
8554                 }
8555         });
8556 })(tinymce);
8557 (function(tinymce) {
8558         // Shorten class names
8559         var DOM = tinymce.DOM, is = tinymce.is;
8560
8561         tinymce.create('tinymce.ui.Control', {
8562                 Control : function(id, s, editor) {
8563                         this.id = id;
8564                         this.settings = s = s || {};
8565                         this.rendered = false;
8566                         this.onRender = new tinymce.util.Dispatcher(this);
8567                         this.classPrefix = '';
8568                         this.scope = s.scope || this;
8569                         this.disabled = 0;
8570                         this.active = 0;
8571                         this.editor = editor;
8572                 },
8573                 
8574                 setAriaProperty : function(property, value) {
8575                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
8576                         if (element) {
8577                                 DOM.setAttrib(element, 'aria-' + property, !!value);
8578                         }
8579                 },
8580                 
8581                 focus : function() {
8582                         DOM.get(this.id).focus();
8583                 },
8584
8585                 setDisabled : function(s) {
8586                         if (s != this.disabled) {
8587                                 this.setAriaProperty('disabled', s);
8588
8589                                 this.setState('Disabled', s);
8590                                 this.setState('Enabled', !s);
8591                                 this.disabled = s;
8592                         }
8593                 },
8594
8595                 isDisabled : function() {
8596                         return this.disabled;
8597                 },
8598
8599                 setActive : function(s) {
8600                         if (s != this.active) {
8601                                 this.setState('Active', s);
8602                                 this.active = s;
8603                                 this.setAriaProperty('pressed', s);
8604                         }
8605                 },
8606
8607                 isActive : function() {
8608                         return this.active;
8609                 },
8610
8611                 setState : function(c, s) {
8612                         var n = DOM.get(this.id);
8613
8614                         c = this.classPrefix + c;
8615
8616                         if (s)
8617                                 DOM.addClass(n, c);
8618                         else
8619                                 DOM.removeClass(n, c);
8620                 },
8621
8622                 isRendered : function() {
8623                         return this.rendered;
8624                 },
8625
8626                 renderHTML : function() {
8627                 },
8628
8629                 renderTo : function(n) {
8630                         DOM.setHTML(n, this.renderHTML());
8631                 },
8632
8633                 postRender : function() {
8634                         var t = this, b;
8635
8636                         // Set pending states
8637                         if (is(t.disabled)) {
8638                                 b = t.disabled;
8639                                 t.disabled = -1;
8640                                 t.setDisabled(b);
8641                         }
8642
8643                         if (is(t.active)) {
8644                                 b = t.active;
8645                                 t.active = -1;
8646                                 t.setActive(b);
8647                         }
8648                 },
8649
8650                 remove : function() {
8651                         DOM.remove(this.id);
8652                         this.destroy();
8653                 },
8654
8655                 destroy : function() {
8656                         tinymce.dom.Event.clear(this.id);
8657                 }
8658         });
8659 })(tinymce);
8660 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
8661         Container : function(id, s, editor) {
8662                 this.parent(id, s, editor);
8663
8664                 this.controls = [];
8665
8666                 this.lookup = {};
8667         },
8668
8669         add : function(c) {
8670                 this.lookup[c.id] = c;
8671                 this.controls.push(c);
8672
8673                 return c;
8674         },
8675
8676         get : function(n) {
8677                 return this.lookup[n];
8678         }
8679 });
8680
8681
8682 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
8683         Separator : function(id, s) {
8684                 this.parent(id, s);
8685                 this.classPrefix = 'mceSeparator';
8686                 this.setDisabled(true);
8687         },
8688
8689         renderHTML : function() {
8690                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
8691         }
8692 });
8693
8694 (function(tinymce) {
8695         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8696
8697         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
8698                 MenuItem : function(id, s) {
8699                         this.parent(id, s);
8700                         this.classPrefix = 'mceMenuItem';
8701                 },
8702
8703                 setSelected : function(s) {
8704                         this.setState('Selected', s);
8705                         this.setAriaProperty('checked', !!s);
8706                         this.selected = s;
8707                 },
8708
8709                 isSelected : function() {
8710                         return this.selected;
8711                 },
8712
8713                 postRender : function() {
8714                         var t = this;
8715                         
8716                         t.parent();
8717
8718                         // Set pending state
8719                         if (is(t.selected))
8720                                 t.setSelected(t.selected);
8721                 }
8722         });
8723 })(tinymce);
8724
8725 (function(tinymce) {
8726         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8727
8728         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
8729                 Menu : function(id, s) {
8730                         var t = this;
8731
8732                         t.parent(id, s);
8733                         t.items = {};
8734                         t.collapsed = false;
8735                         t.menuCount = 0;
8736                         t.onAddItem = new tinymce.util.Dispatcher(this);
8737                 },
8738
8739                 expand : function(d) {
8740                         var t = this;
8741
8742                         if (d) {
8743                                 walk(t, function(o) {
8744                                         if (o.expand)
8745                                                 o.expand();
8746                                 }, 'items', t);
8747                         }
8748
8749                         t.collapsed = false;
8750                 },
8751
8752                 collapse : function(d) {
8753                         var t = this;
8754
8755                         if (d) {
8756                                 walk(t, function(o) {
8757                                         if (o.collapse)
8758                                                 o.collapse();
8759                                 }, 'items', t);
8760                         }
8761
8762                         t.collapsed = true;
8763                 },
8764
8765                 isCollapsed : function() {
8766                         return this.collapsed;
8767                 },
8768
8769                 add : function(o) {
8770                         if (!o.settings)
8771                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
8772
8773                         this.onAddItem.dispatch(this, o);
8774
8775                         return this.items[o.id] = o;
8776                 },
8777
8778                 addSeparator : function() {
8779                         return this.add({separator : true});
8780                 },
8781
8782                 addMenu : function(o) {
8783                         if (!o.collapse)
8784                                 o = this.createMenu(o);
8785
8786                         this.menuCount++;
8787
8788                         return this.add(o);
8789                 },
8790
8791                 hasMenus : function() {
8792                         return this.menuCount !== 0;
8793                 },
8794
8795                 remove : function(o) {
8796                         delete this.items[o.id];
8797                 },
8798
8799                 removeAll : function() {
8800                         var t = this;
8801
8802                         walk(t, function(o) {
8803                                 if (o.removeAll)
8804                                         o.removeAll();
8805                                 else
8806                                         o.remove();
8807
8808                                 o.destroy();
8809                         }, 'items', t);
8810
8811                         t.items = {};
8812                 },
8813
8814                 createMenu : function(o) {
8815                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
8816
8817                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
8818
8819                         return m;
8820                 }
8821         });
8822 })(tinymce);
8823 (function(tinymce) {
8824         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
8825
8826         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
8827                 DropMenu : function(id, s) {
8828                         s = s || {};
8829                         s.container = s.container || DOM.doc.body;
8830                         s.offset_x = s.offset_x || 0;
8831                         s.offset_y = s.offset_y || 0;
8832                         s.vp_offset_x = s.vp_offset_x || 0;
8833                         s.vp_offset_y = s.vp_offset_y || 0;
8834
8835                         if (is(s.icons) && !s.icons)
8836                                 s['class'] += ' mceNoIcons';
8837
8838                         this.parent(id, s);
8839                         this.onShowMenu = new tinymce.util.Dispatcher(this);
8840                         this.onHideMenu = new tinymce.util.Dispatcher(this);
8841                         this.classPrefix = 'mceMenu';
8842                 },
8843
8844                 createMenu : function(s) {
8845                         var t = this, cs = t.settings, m;
8846
8847                         s.container = s.container || cs.container;
8848                         s.parent = t;
8849                         s.constrain = s.constrain || cs.constrain;
8850                         s['class'] = s['class'] || cs['class'];
8851                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
8852                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
8853                         s.keyboard_focus = cs.keyboard_focus;
8854                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
8855
8856                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
8857
8858                         return m;
8859                 },
8860                 
8861                 focus : function() {
8862                         var t = this;
8863                         if (t.keyboardNav) {
8864                                 t.keyboardNav.focus();
8865                         }
8866                 },
8867
8868                 update : function() {
8869                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
8870
8871                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
8872                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
8873
8874                         if (!DOM.boxModel)
8875                                 t.element.setStyles({width : tw + 2, height : th + 2});
8876                         else
8877                                 t.element.setStyles({width : tw, height : th});
8878
8879                         if (s.max_width)
8880                                 DOM.setStyle(co, 'width', tw);
8881
8882                         if (s.max_height) {
8883                                 DOM.setStyle(co, 'height', th);
8884
8885                                 if (tb.clientHeight < s.max_height)
8886                                         DOM.setStyle(co, 'overflow', 'hidden');
8887                         }
8888                 },
8889
8890                 showMenu : function(x, y, px) {
8891                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
8892
8893                         t.collapse(1);
8894
8895                         if (t.isMenuVisible)
8896                                 return;
8897
8898                         if (!t.rendered) {
8899                                 co = DOM.add(t.settings.container, t.renderNode());
8900
8901                                 each(t.items, function(o) {
8902                                         o.postRender();
8903                                 });
8904
8905                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8906                         } else
8907                                 co = DOM.get('menu_' + t.id);
8908
8909                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
8910                         if (!tinymce.isOpera)
8911                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
8912
8913                         DOM.show(co);
8914                         t.update();
8915
8916                         x += s.offset_x || 0;
8917                         y += s.offset_y || 0;
8918                         vp.w -= 4;
8919                         vp.h -= 4;
8920
8921                         // Move inside viewport if not submenu
8922                         if (s.constrain) {
8923                                 w = co.clientWidth - ot;
8924                                 h = co.clientHeight - ot;
8925                                 mx = vp.x + vp.w;
8926                                 my = vp.y + vp.h;
8927
8928                                 if ((x + s.vp_offset_x + w) > mx)
8929                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
8930
8931                                 if ((y + s.vp_offset_y + h) > my)
8932                                         y = Math.max(0, (my - s.vp_offset_y) - h);
8933                         }
8934
8935                         DOM.setStyles(co, {left : x , top : y});
8936                         t.element.update();
8937
8938                         t.isMenuVisible = 1;
8939                         t.mouseClickFunc = Event.add(co, 'click', function(e) {
8940                                 var m;
8941
8942                                 e = e.target;
8943
8944                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
8945                                         m = t.items[e.id];
8946
8947                                         if (m.isDisabled())
8948                                                 return;
8949
8950                                         dm = t;
8951
8952                                         while (dm) {
8953                                                 if (dm.hideMenu)
8954                                                         dm.hideMenu();
8955
8956                                                 dm = dm.settings.parent;
8957                                         }
8958
8959                                         if (m.settings.onclick)
8960                                                 m.settings.onclick(e);
8961
8962                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
8963                                 }
8964                         });
8965
8966                         if (t.hasMenus()) {
8967                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
8968                                         var m, r, mi;
8969
8970                                         e = e.target;
8971                                         if (e && (e = DOM.getParent(e, 'tr'))) {
8972                                                 m = t.items[e.id];
8973
8974                                                 if (t.lastMenu)
8975                                                         t.lastMenu.collapse(1);
8976
8977                                                 if (m.isDisabled())
8978                                                         return;
8979
8980                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
8981                                                         //p = DOM.getPos(s.container);
8982                                                         r = DOM.getRect(e);
8983                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
8984                                                         t.lastMenu = m;
8985                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
8986                                                 }
8987                                         }
8988                                 });
8989                         }
8990                         
8991                         Event.add(co, 'keydown', t._keyHandler, t);
8992
8993                         t.onShowMenu.dispatch(t);
8994
8995                         if (s.keyboard_focus) { 
8996                                 t._setupKeyboardNav(); 
8997                         }
8998                 },
8999
9000                 hideMenu : function(c) {
9001                         var t = this, co = DOM.get('menu_' + t.id), e;
9002
9003                         if (!t.isMenuVisible)
9004                                 return;
9005
9006                         if (t.keyboardNav) t.keyboardNav.destroy();
9007                         Event.remove(co, 'mouseover', t.mouseOverFunc);
9008                         Event.remove(co, 'click', t.mouseClickFunc);
9009                         Event.remove(co, 'keydown', t._keyHandler);
9010                         DOM.hide(co);
9011                         t.isMenuVisible = 0;
9012
9013                         if (!c)
9014                                 t.collapse(1);
9015
9016                         if (t.element)
9017                                 t.element.hide();
9018
9019                         if (e = DOM.get(t.id))
9020                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
9021
9022                         t.onHideMenu.dispatch(t);
9023                 },
9024
9025                 add : function(o) {
9026                         var t = this, co;
9027
9028                         o = t.parent(o);
9029
9030                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))
9031                                 t._add(DOM.select('tbody', co)[0], o);
9032
9033                         return o;
9034                 },
9035
9036                 collapse : function(d) {
9037                         this.parent(d);
9038                         this.hideMenu(1);
9039                 },
9040
9041                 remove : function(o) {
9042                         DOM.remove(o.id);
9043                         this.destroy();
9044
9045                         return this.parent(o);
9046                 },
9047
9048                 destroy : function() {
9049                         var t = this, co = DOM.get('menu_' + t.id);
9050
9051                         if (t.keyboardNav) t.keyboardNav.destroy();
9052                         Event.remove(co, 'mouseover', t.mouseOverFunc);
9053                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
9054                         Event.remove(co, 'click', t.mouseClickFunc);
9055                         Event.remove(co, 'keydown', t._keyHandler);
9056
9057                         if (t.element)
9058                                 t.element.remove();
9059
9060                         DOM.remove(co);
9061                 },
9062
9063                 renderNode : function() {
9064                         var t = this, s = t.settings, n, tb, co, w;
9065
9066                         w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
9067                         if (t.settings.parent) {
9068                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
9069                         }
9070                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
9071                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
9072
9073                         if (s.menu_line)
9074                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
9075
9076 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
9077                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
9078                         tb = DOM.add(n, 'tbody');
9079
9080                         each(t.items, function(o) {
9081                                 t._add(tb, o);
9082                         });
9083
9084                         t.rendered = true;
9085
9086                         return w;
9087                 },
9088
9089                 // Internal functions
9090                 _setupKeyboardNav : function(){
9091                         var contextMenu, menuItems, t=this; 
9092                         contextMenu = DOM.select('#menu_' + t.id)[0];
9093                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
9094                         menuItems.splice(0,0,contextMenu);
9095                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({
9096                                 root: 'menu_' + t.id,
9097                                 items: menuItems,
9098                                 onCancel: function() {
9099                                         t.hideMenu();
9100                                 },
9101                                 enableUpDown: true
9102                         });
9103                         contextMenu.focus();
9104                 },
9105
9106                 _keyHandler : function(evt) {
9107                         var t = this, e;
9108                         switch (evt.keyCode) {
9109                                 case 37: // Left
9110                                         if (t.settings.parent) {
9111                                                 t.hideMenu();
9112                                                 t.settings.parent.focus();
9113                                                 Event.cancel(evt);
9114                                         }
9115                                         break;
9116                                 case 39: // Right
9117                                         if (t.mouseOverFunc)
9118                                                 t.mouseOverFunc(evt);
9119                                         break;
9120                         }
9121                 },
9122
9123                 _add : function(tb, o) {
9124                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
9125
9126                         if (s.separator) {
9127                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
9128                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
9129
9130                                 if (n = ro.previousSibling)
9131                                         DOM.addClass(n, 'mceLast');
9132
9133                                 return;
9134                         }
9135
9136                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
9137                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
9138                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
9139
9140                         if (s.parent) {
9141                                 DOM.setAttrib(a, 'aria-haspopup', 'true');
9142                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
9143                         }
9144
9145                         DOM.addClass(it, s['class']);
9146 //                      n = DOM.add(n, 'span', {'class' : 'item'});
9147
9148                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
9149
9150                         if (s.icon_src)
9151                                 DOM.add(ic, 'img', {src : s.icon_src});
9152
9153                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
9154
9155                         if (o.settings.style)
9156                                 DOM.setAttrib(n, 'style', o.settings.style);
9157
9158                         if (tb.childNodes.length == 1)
9159                                 DOM.addClass(ro, 'mceFirst');
9160
9161                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
9162                                 DOM.addClass(ro, 'mceFirst');
9163
9164                         if (o.collapse)
9165                                 DOM.addClass(ro, cp + 'ItemSub');
9166
9167                         if (n = ro.previousSibling)
9168                                 DOM.removeClass(n, 'mceLast');
9169
9170                         DOM.addClass(ro, 'mceLast');
9171                 }
9172         });
9173 })(tinymce);
9174 (function(tinymce) {
9175         var DOM = tinymce.DOM;
9176
9177         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
9178                 Button : function(id, s, ed) {
9179                         this.parent(id, s, ed);
9180                         this.classPrefix = 'mceButton';
9181                 },
9182
9183                 renderHTML : function() {
9184                         var cp = this.classPrefix, s = this.settings, h, l;
9185
9186                         l = DOM.encode(s.label || '');
9187                         h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
9188
9189                         if (s.image)
9190                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
9191                         else
9192                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
9193
9194                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
9195                         h += '</a>';
9196                         return h;
9197                 },
9198
9199                 postRender : function() {
9200                         var t = this, s = t.settings;
9201
9202                         tinymce.dom.Event.add(t.id, 'click', function(e) {
9203                                 if (!t.isDisabled())
9204                                         return s.onclick.call(s.scope, e);
9205                         });
9206                 }
9207         });
9208 })(tinymce);
9209
9210 (function(tinymce) {
9211         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9212
9213         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
9214                 ListBox : function(id, s, ed) {
9215                         var t = this;
9216
9217                         t.parent(id, s, ed);
9218
9219                         t.items = [];
9220
9221                         t.onChange = new Dispatcher(t);
9222
9223                         t.onPostRender = new Dispatcher(t);
9224
9225                         t.onAdd = new Dispatcher(t);
9226
9227                         t.onRenderMenu = new tinymce.util.Dispatcher(this);
9228
9229                         t.classPrefix = 'mceListBox';
9230                 },
9231
9232                 select : function(va) {
9233                         var t = this, fv, f;
9234
9235                         if (va == undefined)
9236                                 return t.selectByIndex(-1);
9237
9238                         // Is string or number make function selector
9239                         if (va && va.call)
9240                                 f = va;
9241                         else {
9242                                 f = function(v) {
9243                                         return v == va;
9244                                 };
9245                         }
9246
9247                         // Do we need to do something?
9248                         if (va != t.selectedValue) {
9249                                 // Find item
9250                                 each(t.items, function(o, i) {
9251                                         if (f(o.value)) {
9252                                                 fv = 1;
9253                                                 t.selectByIndex(i);
9254                                                 return false;
9255                                         }
9256                                 });
9257
9258                                 if (!fv)
9259                                         t.selectByIndex(-1);
9260                         }
9261                 },
9262
9263                 selectByIndex : function(idx) {
9264                         var t = this, e, o;
9265
9266                         if (idx != t.selectedIndex) {
9267                                 e = DOM.get(t.id + '_text');
9268                                 o = t.items[idx];
9269
9270                                 if (o) {
9271                                         t.selectedValue = o.value;
9272                                         t.selectedIndex = idx;
9273                                         DOM.setHTML(e, DOM.encode(o.title));
9274                                         DOM.removeClass(e, 'mceTitle');
9275                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);
9276                                 } else {
9277                                         DOM.setHTML(e, DOM.encode(t.settings.title));
9278                                         DOM.addClass(e, 'mceTitle');
9279                                         t.selectedValue = t.selectedIndex = null;
9280                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
9281                                 }
9282                                 e = 0;
9283                         }
9284                 },
9285
9286                 add : function(n, v, o) {
9287                         var t = this;
9288
9289                         o = o || {};
9290                         o = tinymce.extend(o, {
9291                                 title : n,
9292                                 value : v
9293                         });
9294
9295                         t.items.push(o);
9296                         t.onAdd.dispatch(t, o);
9297                 },
9298
9299                 getLength : function() {
9300                         return this.items.length;
9301                 },
9302
9303                 renderHTML : function() {
9304                         var h = '', t = this, s = t.settings, cp = t.classPrefix;
9305
9306                         h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
9307                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
9308                         h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
9309                         h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
9310                         h += '</tr></tbody></table></span>';
9311
9312                         return h;
9313                 },
9314
9315                 showMenu : function() {
9316                         var t = this, p1, p2, e = DOM.get(this.id), m;
9317
9318                         if (t.isDisabled() || t.items.length == 0)
9319                                 return;
9320
9321                         if (t.menu && t.menu.isMenuVisible)
9322                                 return t.hideMenu();
9323
9324                         if (!t.isMenuRendered) {
9325                                 t.renderMenu();
9326                                 t.isMenuRendered = true;
9327                         }
9328
9329                         p1 = DOM.getPos(this.settings.menu_container);
9330                         p2 = DOM.getPos(e);
9331
9332                         m = t.menu;
9333                         m.settings.offset_x = p2.x;
9334                         m.settings.offset_y = p2.y;
9335                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
9336
9337                         // Select in menu
9338                         if (t.oldID)
9339                                 m.items[t.oldID].setSelected(0);
9340
9341                         each(t.items, function(o) {
9342                                 if (o.value === t.selectedValue) {
9343                                         m.items[o.id].setSelected(1);
9344                                         t.oldID = o.id;
9345                                 }
9346                         });
9347
9348                         m.showMenu(0, e.clientHeight);
9349
9350                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9351                         DOM.addClass(t.id, t.classPrefix + 'Selected');
9352
9353                         //DOM.get(t.id + '_text').focus();
9354                 },
9355
9356                 hideMenu : function(e) {
9357                         var t = this;
9358
9359                         if (t.menu && t.menu.isMenuVisible) {
9360                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');
9361
9362                                 // Prevent double toogles by canceling the mouse click event to the button
9363                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
9364                                         return;
9365
9366                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9367                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');
9368                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9369                                         t.menu.hideMenu();
9370                                 }
9371                         }
9372                 },
9373
9374                 renderMenu : function() {
9375                         var t = this, m;
9376
9377                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9378                                 menu_line : 1,
9379                                 'class' : t.classPrefix + 'Menu mceNoIcons',
9380                                 max_width : 150,
9381                                 max_height : 150
9382                         });
9383
9384                         m.onHideMenu.add(function() {
9385                                 t.hideMenu();
9386                                 t.focus();
9387                         });
9388
9389                         m.add({
9390                                 title : t.settings.title,
9391                                 'class' : 'mceMenuItemTitle',
9392                                 onclick : function() {
9393                                         if (t.settings.onselect('') !== false)
9394                                                 t.select(''); // Must be runned after
9395                                 }
9396                         });
9397
9398                         each(t.items, function(o) {
9399                                 // No value then treat it as a title
9400                                 if (o.value === undefined) {
9401                                         m.add({
9402                                                 title : o.title,
9403                                                 'class' : 'mceMenuItemTitle',
9404                                                 onclick : function() {
9405                                                         if (t.settings.onselect('') !== false)
9406                                                                 t.select(''); // Must be runned after
9407                                                 }
9408                                         });
9409                                 } else {
9410                                         o.id = DOM.uniqueId();
9411                                         o.onclick = function() {
9412                                                 if (t.settings.onselect(o.value) !== false)
9413                                                         t.select(o.value); // Must be runned after
9414                                         };
9415
9416                                         m.add(o);
9417                                 }
9418                         });
9419
9420                         t.onRenderMenu.dispatch(t, m);
9421                         t.menu = m;
9422                 },
9423
9424                 postRender : function() {
9425                         var t = this, cp = t.classPrefix;
9426
9427                         Event.add(t.id, 'click', t.showMenu, t);
9428                         Event.add(t.id, 'keydown', function(evt) {
9429                                 if (evt.keyCode == 32) { // Space
9430                                         t.showMenu(evt);
9431                                         Event.cancel(evt);
9432                                 }
9433                         });
9434                         Event.add(t.id, 'focus', function() {
9435                                 if (!t._focused) {
9436                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
9437                                                 if (e.keyCode == 40) {
9438                                                         t.showMenu();
9439                                                         Event.cancel(e);
9440                                                 }
9441                                         });
9442                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
9443                                                 var v;
9444                                                 if (e.keyCode == 13) {
9445                                                         // Fake select on enter
9446                                                         v = t.selectedValue;
9447                                                         t.selectedValue = null; // Needs to be null to fake change
9448                                                         Event.cancel(e);
9449                                                         t.settings.onselect(v);
9450                                                 }
9451                                         });
9452                                 }
9453
9454                                 t._focused = 1;
9455                         });
9456                         Event.add(t.id, 'blur', function() {
9457                                 Event.remove(t.id, 'keydown', t.keyDownHandler);
9458                                 Event.remove(t.id, 'keypress', t.keyPressHandler);
9459                                 t._focused = 0;
9460                         });
9461
9462                         // Old IE doesn't have hover on all elements
9463                         if (tinymce.isIE6 || !DOM.boxModel) {
9464                                 Event.add(t.id, 'mouseover', function() {
9465                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9466                                                 DOM.addClass(t.id, cp + 'Hover');
9467                                 });
9468
9469                                 Event.add(t.id, 'mouseout', function() {
9470                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9471                                                 DOM.removeClass(t.id, cp + 'Hover');
9472                                 });
9473                         }
9474
9475                         t.onPostRender.dispatch(t, DOM.get(t.id));
9476                 },
9477
9478                 destroy : function() {
9479                         this.parent();
9480
9481                         Event.clear(this.id + '_text');
9482                         Event.clear(this.id + '_open');
9483                 }
9484         });
9485 })(tinymce);
9486 (function(tinymce) {
9487         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9488
9489         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
9490                 NativeListBox : function(id, s) {
9491                         this.parent(id, s);
9492                         this.classPrefix = 'mceNativeListBox';
9493                 },
9494
9495                 setDisabled : function(s) {
9496                         DOM.get(this.id).disabled = s;
9497                         this.setAriaProperty('disabled', s);
9498                 },
9499
9500                 isDisabled : function() {
9501                         return DOM.get(this.id).disabled;
9502                 },
9503
9504                 select : function(va) {
9505                         var t = this, fv, f;
9506
9507                         if (va == undefined)
9508                                 return t.selectByIndex(-1);
9509
9510                         // Is string or number make function selector
9511                         if (va && va.call)
9512                                 f = va;
9513                         else {
9514                                 f = function(v) {
9515                                         return v == va;
9516                                 };
9517                         }
9518
9519                         // Do we need to do something?
9520                         if (va != t.selectedValue) {
9521                                 // Find item
9522                                 each(t.items, function(o, i) {
9523                                         if (f(o.value)) {
9524                                                 fv = 1;
9525                                                 t.selectByIndex(i);
9526                                                 return false;
9527                                         }
9528                                 });
9529
9530                                 if (!fv)
9531                                         t.selectByIndex(-1);
9532                         }
9533                 },
9534
9535                 selectByIndex : function(idx) {
9536                         DOM.get(this.id).selectedIndex = idx + 1;
9537                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;
9538                 },
9539
9540                 add : function(n, v, a) {
9541                         var o, t = this;
9542
9543                         a = a || {};
9544                         a.value = v;
9545
9546                         if (t.isRendered())
9547                                 DOM.add(DOM.get(this.id), 'option', a, n);
9548
9549                         o = {
9550                                 title : n,
9551                                 value : v,
9552                                 attribs : a
9553                         };
9554
9555                         t.items.push(o);
9556                         t.onAdd.dispatch(t, o);
9557                 },
9558
9559                 getLength : function() {
9560                         return this.items.length;
9561                 },
9562
9563                 renderHTML : function() {
9564                         var h, t = this;
9565
9566                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
9567
9568                         each(t.items, function(it) {
9569                                 h += DOM.createHTML('option', {value : it.value}, it.title);
9570                         });
9571
9572                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
9573                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
9574                         return h;
9575                 },
9576
9577                 postRender : function() {
9578                         var t = this, ch, changeListenerAdded = true;
9579
9580                         t.rendered = true;
9581
9582                         function onChange(e) {
9583                                 var v = t.items[e.target.selectedIndex - 1];
9584
9585                                 if (v && (v = v.value)) {
9586                                         t.onChange.dispatch(t, v);
9587
9588                                         if (t.settings.onselect)
9589                                                 t.settings.onselect(v);
9590                                 }
9591                         };
9592
9593                         Event.add(t.id, 'change', onChange);
9594
9595                         // Accessibility keyhandler
9596                         Event.add(t.id, 'keydown', function(e) {
9597                                 var bf;
9598
9599                                 Event.remove(t.id, 'change', ch);
9600                                 changeListenerAdded = false;
9601
9602                                 bf = Event.add(t.id, 'blur', function() {
9603                                         if (changeListenerAdded) return;
9604                                         changeListenerAdded = true;
9605                                         Event.add(t.id, 'change', onChange);
9606                                         Event.remove(t.id, 'blur', bf);
9607                                 });
9608
9609                                 if (e.keyCode == 13 || e.keyCode == 32) {
9610                                         onChange(e);
9611                                         return Event.cancel(e);
9612                                 }
9613                         });
9614
9615                         t.onPostRender.dispatch(t, DOM.get(t.id));
9616                 }
9617         });
9618 })(tinymce);
9619 (function(tinymce) {
9620         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9621
9622         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
9623                 MenuButton : function(id, s, ed) {
9624                         this.parent(id, s, ed);
9625
9626                         this.onRenderMenu = new tinymce.util.Dispatcher(this);
9627
9628                         s.menu_container = s.menu_container || DOM.doc.body;
9629                 },
9630
9631                 showMenu : function() {
9632                         var t = this, p1, p2, e = DOM.get(t.id), m;
9633
9634                         if (t.isDisabled())
9635                                 return;
9636
9637                         if (!t.isMenuRendered) {
9638                                 t.renderMenu();
9639                                 t.isMenuRendered = true;
9640                         }
9641
9642                         if (t.isMenuVisible)
9643                                 return t.hideMenu();
9644
9645                         p1 = DOM.getPos(t.settings.menu_container);
9646                         p2 = DOM.getPos(e);
9647
9648                         m = t.menu;
9649                         m.settings.offset_x = p2.x;
9650                         m.settings.offset_y = p2.y;
9651                         m.settings.vp_offset_x = p2.x;
9652                         m.settings.vp_offset_y = p2.y;
9653                         m.settings.keyboard_focus = t._focused;
9654                         m.showMenu(0, e.clientHeight);
9655
9656                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9657                         t.setState('Selected', 1);
9658
9659                         t.isMenuVisible = 1;
9660                 },
9661
9662                 renderMenu : function() {
9663                         var t = this, m;
9664
9665                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9666                                 menu_line : 1,
9667                                 'class' : this.classPrefix + 'Menu',
9668                                 icons : t.settings.icons
9669                         });
9670
9671                         m.onHideMenu.add(function() {
9672                                 t.hideMenu();
9673                                 t.focus();
9674                         });
9675
9676                         t.onRenderMenu.dispatch(t, m);
9677                         t.menu = m;
9678                 },
9679
9680                 hideMenu : function(e) {
9681                         var t = this;
9682
9683                         // Prevent double toogles by canceling the mouse click event to the button
9684                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
9685                                 return;
9686
9687                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9688                                 t.setState('Selected', 0);
9689                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9690                                 if (t.menu)
9691                                         t.menu.hideMenu();
9692                         }
9693
9694                         t.isMenuVisible = 0;
9695                 },
9696
9697                 postRender : function() {
9698                         var t = this, s = t.settings;
9699
9700                         Event.add(t.id, 'click', function() {
9701                                 if (!t.isDisabled()) {
9702                                         if (s.onclick)
9703                                                 s.onclick(t.value);
9704
9705                                         t.showMenu();
9706                                 }
9707                         });
9708                 }
9709         });
9710 })(tinymce);
9711
9712 (function(tinymce) {
9713         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9714
9715         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
9716                 SplitButton : function(id, s, ed) {
9717                         this.parent(id, s, ed);
9718                         this.classPrefix = 'mceSplitButton';
9719                 },
9720
9721                 renderHTML : function() {
9722                         var h, t = this, s = t.settings, h1;
9723
9724                         h = '<tbody><tr>';
9725
9726                         if (s.image)
9727                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
9728                         else
9729                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
9730
9731                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
9732                         h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
9733         
9734                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
9735                         h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
9736
9737                         h += '</tr></tbody>';
9738                         h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
9739                         return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
9740                 },
9741
9742                 postRender : function() {
9743                         var t = this, s = t.settings, activate;
9744
9745                         if (s.onclick) {
9746                                 activate = function(evt) {
9747                                         if (!t.isDisabled()) {
9748                                                 s.onclick(t.value);
9749                                                 Event.cancel(evt);
9750                                         }
9751                                 };
9752                                 Event.add(t.id + '_action', 'click', activate);
9753                                 Event.add(t.id, ['click', 'keydown'], function(evt) {
9754                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
9755                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
9756                                                 activate();
9757                                                 Event.cancel(evt);
9758                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
9759                                                 t.showMenu();
9760                                                 Event.cancel(evt);
9761                                         }
9762                                 });
9763                         }
9764
9765                         Event.add(t.id + '_open', 'click', function (evt) {
9766                                 t.showMenu();
9767                                 Event.cancel(evt);
9768                         });
9769                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
9770                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
9771
9772                         // Old IE doesn't have hover on all elements
9773                         if (tinymce.isIE6 || !DOM.boxModel) {
9774                                 Event.add(t.id, 'mouseover', function() {
9775                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9776                                                 DOM.addClass(t.id, 'mceSplitButtonHover');
9777                                 });
9778
9779                                 Event.add(t.id, 'mouseout', function() {
9780                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9781                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');
9782                                 });
9783                         }
9784                 },
9785
9786                 destroy : function() {
9787                         this.parent();
9788
9789                         Event.clear(this.id + '_action');
9790                         Event.clear(this.id + '_open');
9791                         Event.clear(this.id);
9792                 }
9793         });
9794 })(tinymce);
9795
9796 (function(tinymce) {
9797         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
9798
9799         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
9800                 ColorSplitButton : function(id, s, ed) {
9801                         var t = this;
9802
9803                         t.parent(id, s, ed);
9804
9805                         t.settings = s = tinymce.extend({
9806                                 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
9807                                 grid_width : 8,
9808                                 default_color : '#888888'
9809                         }, t.settings);
9810
9811                         t.onShowMenu = new tinymce.util.Dispatcher(t);
9812
9813                         t.onHideMenu = new tinymce.util.Dispatcher(t);
9814
9815                         t.value = s.default_color;
9816                 },
9817
9818                 showMenu : function() {
9819                         var t = this, r, p, e, p2;
9820
9821                         if (t.isDisabled())
9822                                 return;
9823
9824                         if (!t.isMenuRendered) {
9825                                 t.renderMenu();
9826                                 t.isMenuRendered = true;
9827                         }
9828
9829                         if (t.isMenuVisible)
9830                                 return t.hideMenu();
9831
9832                         e = DOM.get(t.id);
9833                         DOM.show(t.id + '_menu');
9834                         DOM.addClass(e, 'mceSplitButtonSelected');
9835                         p2 = DOM.getPos(e);
9836                         DOM.setStyles(t.id + '_menu', {
9837                                 left : p2.x,
9838                                 top : p2.y + e.clientHeight,
9839                                 zIndex : 200000
9840                         });
9841                         e = 0;
9842
9843                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9844                         t.onShowMenu.dispatch(t);
9845
9846                         if (t._focused) {
9847                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
9848                                         if (e.keyCode == 27)
9849                                                 t.hideMenu();
9850                                 });
9851
9852                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
9853                         }
9854
9855                         t.isMenuVisible = 1;
9856                 },
9857
9858                 hideMenu : function(e) {
9859                         var t = this;
9860
9861                         if (t.isMenuVisible) {
9862                                 // Prevent double toogles by canceling the mouse click event to the button
9863                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
9864                                         return;
9865
9866                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
9867                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');
9868                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9869                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
9870                                         DOM.hide(t.id + '_menu');
9871                                 }
9872
9873                                 t.isMenuVisible = 0;
9874                         }
9875                 },
9876
9877                 renderMenu : function() {
9878                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
9879
9880                         w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
9881                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
9882                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});
9883
9884                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
9885                         tb = DOM.add(n, 'tbody');
9886
9887                         // Generate color grid
9888                         i = 0;
9889                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
9890                                 c = c.replace(/^#/, '');
9891
9892                                 if (!i--) {
9893                                         tr = DOM.add(tb, 'tr');
9894                                         i = s.grid_width - 1;
9895                                 }
9896
9897                                 n = DOM.add(tr, 'td');
9898                                 n = DOM.add(n, 'a', {
9899                                         role : 'option',
9900                                         href : 'javascript:;',
9901                                         style : {
9902                                                 backgroundColor : '#' + c
9903                                         },
9904                                         'title': t.editor.getLang('colors.' + c, c),
9905                                         'data-mce-color' : '#' + c
9906                                 });
9907
9908                                 if (t.editor.forcedHighContrastMode) {
9909                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
9910                                         if (n.getContext && (context = n.getContext("2d"))) {
9911                                                 context.fillStyle = '#' + c;
9912                                                 context.fillRect(0, 0, 16, 16);
9913                                         } else {
9914                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.
9915                                                 DOM.remove(n);
9916                                         }
9917                                 }
9918                         });
9919
9920                         if (s.more_colors_func) {
9921                                 n = DOM.add(tb, 'tr');
9922                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
9923                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
9924
9925                                 Event.add(n, 'click', function(e) {
9926                                         s.more_colors_func.call(s.more_colors_scope || this);
9927                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
9928                                 });
9929                         }
9930
9931                         DOM.addClass(m, 'mceColorSplitMenu');
9932                         
9933                         new tinymce.ui.KeyboardNavigation({
9934                                 root: t.id + '_menu',
9935                                 items: DOM.select('a', t.id + '_menu'),
9936                                 onCancel: function() {
9937                                         t.hideMenu();
9938                                         t.focus();
9939                                 }
9940                         });
9941
9942                         // Prevent IE from scrolling and hindering click to occur #4019
9943                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
9944
9945                         Event.add(t.id + '_menu', 'click', function(e) {
9946                                 var c;
9947
9948                                 e = DOM.getParent(e.target, 'a', tb);
9949
9950                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
9951                                         t.setColor(c);
9952
9953                                 return Event.cancel(e); // Prevent IE auto save warning
9954                         });
9955
9956                         return w;
9957                 },
9958
9959                 setColor : function(c) {
9960                         this.displayColor(c);
9961                         this.hideMenu();
9962                         this.settings.onselect(c);
9963                 },
9964                 
9965                 displayColor : function(c) {
9966                         var t = this;
9967
9968                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
9969
9970                         t.value = c;
9971                 },
9972
9973                 postRender : function() {
9974                         var t = this, id = t.id;
9975
9976                         t.parent();
9977                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
9978                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
9979                 },
9980
9981                 destroy : function() {
9982                         this.parent();
9983
9984                         Event.clear(this.id + '_menu');
9985                         Event.clear(this.id + '_more');
9986                         DOM.remove(this.id + '_menu');
9987                 }
9988         });
9989 })(tinymce);
9990
9991 (function(tinymce) {
9992 // Shorten class names
9993 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
9994 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
9995         renderHTML : function() {
9996                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
9997
9998                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
9999                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
10000                 h.push("<span role='application'>");
10001                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
10002                 each(controls, function(toolbar) {
10003                         h.push(toolbar.renderHTML());
10004                 });
10005                 h.push("</span>");
10006                 h.push('</div>');
10007
10008                 return h.join('');
10009         },
10010         
10011         focus : function() {
10012                 this.keyNav.focus();
10013         },
10014         
10015         postRender : function() {
10016                 var t = this, items = [];
10017
10018                 each(t.controls, function(toolbar) {
10019                         each (toolbar.controls, function(control) {
10020                                 if (control.id) {
10021                                         items.push(control);
10022                                 }
10023                         });
10024                 });
10025
10026                 t.keyNav = new tinymce.ui.KeyboardNavigation({
10027                         root: t.id,
10028                         items: items,
10029                         onCancel: function() {
10030                                 t.editor.focus();
10031                         },
10032                         excludeFromTabOrder: !t.settings.tab_focus_toolbar
10033                 });
10034         },
10035         
10036         destroy : function() {
10037                 var self = this;
10038
10039                 self.parent();
10040                 self.keyNav.destroy();
10041                 Event.clear(self.id);
10042         }
10043 });
10044 })(tinymce);
10045
10046 (function(tinymce) {
10047 // Shorten class names
10048 var dom = tinymce.DOM, each = tinymce.each;
10049 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10050         renderHTML : function() {
10051                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
10052
10053                 cl = t.controls;
10054                 for (i=0; i<cl.length; i++) {
10055                         // Get current control, prev control, next control and if the control is a list box or not
10056                         co = cl[i];
10057                         pr = cl[i - 1];
10058                         nx = cl[i + 1];
10059
10060                         // Add toolbar start
10061                         if (i === 0) {
10062                                 c = 'mceToolbarStart';
10063
10064                                 if (co.Button)
10065                                         c += ' mceToolbarStartButton';
10066                                 else if (co.SplitButton)
10067                                         c += ' mceToolbarStartSplitButton';
10068                                 else if (co.ListBox)
10069                                         c += ' mceToolbarStartListBox';
10070
10071                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10072                         }
10073
10074                         // Add toolbar end before list box and after the previous button
10075                         // This is to fix the o2k7 editor skins
10076                         if (pr && co.ListBox) {
10077                                 if (pr.Button || pr.SplitButton)
10078                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
10079                         }
10080
10081                         // Render control HTML
10082
10083                         // IE 8 quick fix, needed to propertly generate a hit area for anchors
10084                         if (dom.stdMode)
10085                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
10086                         else
10087                                 h += '<td>' + co.renderHTML() + '</td>';
10088
10089                         // Add toolbar start after list box and before the next button
10090                         // This is to fix the o2k7 editor skins
10091                         if (nx && co.ListBox) {
10092                                 if (nx.Button || nx.SplitButton)
10093                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
10094                         }
10095                 }
10096
10097                 c = 'mceToolbarEnd';
10098
10099                 if (co.Button)
10100                         c += ' mceToolbarEndButton';
10101                 else if (co.SplitButton)
10102                         c += ' mceToolbarEndSplitButton';
10103                 else if (co.ListBox)
10104                         c += ' mceToolbarEndListBox';
10105
10106                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10107
10108                 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
10109         }
10110 });
10111 })(tinymce);
10112
10113 (function(tinymce) {
10114         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
10115
10116         tinymce.create('tinymce.AddOnManager', {
10117                 AddOnManager : function() {
10118                         var self = this;
10119
10120                         self.items = [];
10121                         self.urls = {};
10122                         self.lookup = {};
10123                         self.onAdd = new Dispatcher(self);
10124                 },
10125
10126                 get : function(n) {
10127                         return this.lookup[n];
10128                 },
10129
10130                 requireLangPack : function(n) {
10131                         var s = tinymce.settings;
10132
10133                         if (s && s.language && s.language_load !== false)
10134                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
10135                 },
10136
10137                 add : function(id, o) {
10138                         this.items.push(o);
10139                         this.lookup[id] = o;
10140                         this.onAdd.dispatch(this, id, o);
10141
10142                         return o;
10143                 },
10144
10145                 load : function(n, u, cb, s) {
10146                         var t = this;
10147
10148                         if (t.urls[n])
10149                                 return;
10150
10151                         if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
10152                                 u = tinymce.baseURL + '/' + u;
10153
10154                         t.urls[n] = u.substring(0, u.lastIndexOf('/'));
10155
10156                         if (!t.lookup[n])
10157                                 tinymce.ScriptLoader.add(u, cb, s);
10158                 }
10159         });
10160
10161         // Create plugin and theme managers
10162         tinymce.PluginManager = new tinymce.AddOnManager();
10163         tinymce.ThemeManager = new tinymce.AddOnManager();
10164 }(tinymce));
10165
10166 (function(tinymce) {
10167         // Shorten names
10168         var each = tinymce.each, extend = tinymce.extend,
10169                 DOM = tinymce.DOM, Event = tinymce.dom.Event,
10170                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10171                 explode = tinymce.explode,
10172                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
10173
10174         // Setup some URLs where the editor API is located and where the document is
10175         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
10176         if (!/[\/\\]$/.test(tinymce.documentBaseURL))
10177                 tinymce.documentBaseURL += '/';
10178
10179         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
10180
10181         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
10182
10183         // Add before unload listener
10184         // This was required since IE was leaking memory if you added and removed beforeunload listeners
10185         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
10186         tinymce.onBeforeUnload = new Dispatcher(tinymce);
10187
10188         // Must be on window or IE will leak if the editor is placed in frame or iframe
10189         Event.add(window, 'beforeunload', function(e) {
10190                 tinymce.onBeforeUnload.dispatch(tinymce, e);
10191         });
10192
10193         tinymce.onAddEditor = new Dispatcher(tinymce);
10194
10195         tinymce.onRemoveEditor = new Dispatcher(tinymce);
10196
10197         tinymce.EditorManager = extend(tinymce, {
10198                 editors : [],
10199
10200                 i18n : {},
10201
10202                 activeEditor : null,
10203
10204                 init : function(s) {
10205                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
10206
10207                         function execCallback(se, n, s) {
10208                                 var f = se[n];
10209
10210                                 if (!f)
10211                                         return;
10212
10213                                 if (tinymce.is(f, 'string')) {
10214                                         s = f.replace(/\.\w+$/, '');
10215                                         s = s ? tinymce.resolve(s) : 0;
10216                                         f = tinymce.resolve(f);
10217                                 }
10218
10219                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
10220                         };
10221
10222                         s = extend({
10223                                 theme : "simple",
10224                                 language : "en"
10225                         }, s);
10226
10227                         t.settings = s;
10228
10229                         // Legacy call
10230                         Event.add(document, 'init', function() {
10231                                 var l, co;
10232
10233                                 execCallback(s, 'onpageload');
10234
10235                                 switch (s.mode) {
10236                                         case "exact":
10237                                                 l = s.elements || '';
10238
10239                                                 if(l.length > 0) {
10240                                                         each(explode(l), function(v) {
10241                                                                 if (DOM.get(v)) {
10242                                                                         ed = new tinymce.Editor(v, s);
10243                                                                         el.push(ed);
10244                                                                         ed.render(1);
10245                                                                 } else {
10246                                                                         each(document.forms, function(f) {
10247                                                                                 each(f.elements, function(e) {
10248                                                                                         if (e.name === v) {
10249                                                                                                 v = 'mce_editor_' + instanceCounter++;
10250                                                                                                 DOM.setAttrib(e, 'id', v);
10251
10252                                                                                                 ed = new tinymce.Editor(v, s);
10253                                                                                                 el.push(ed);
10254                                                                                                 ed.render(1);
10255                                                                                         }
10256                                                                                 });
10257                                                                         });
10258                                                                 }
10259                                                         });
10260                                                 }
10261                                                 break;
10262
10263                                         case "textareas":
10264                                         case "specific_textareas":
10265                                                 function hasClass(n, c) {
10266                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
10267                                                 };
10268
10269                                                 each(DOM.select('textarea'), function(v) {
10270                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))
10271                                                                 return;
10272
10273                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {
10274                                                                 // Can we use the name
10275                                                                 e = DOM.get(v.name);
10276                                                                 if (!v.id && !e)
10277                                                                         v.id = v.name;
10278
10279                                                                 // Generate unique name if missing or already exists
10280                                                                 if (!v.id || t.get(v.id))
10281                                                                         v.id = DOM.uniqueId();
10282
10283                                                                 ed = new tinymce.Editor(v.id, s);
10284                                                                 el.push(ed);
10285                                                                 ed.render(1);
10286                                                         }
10287                                                 });
10288                                                 break;
10289                                 }
10290
10291                                 // Call onInit when all editors are initialized
10292                                 if (s.oninit) {
10293                                         l = co = 0;
10294
10295                                         each(el, function(ed) {
10296                                                 co++;
10297
10298                                                 if (!ed.initialized) {
10299                                                         // Wait for it
10300                                                         ed.onInit.add(function() {
10301                                                                 l++;
10302
10303                                                                 // All done
10304                                                                 if (l == co)
10305                                                                         execCallback(s, 'oninit');
10306                                                         });
10307                                                 } else
10308                                                         l++;
10309
10310                                                 // All done
10311                                                 if (l == co)
10312                                                         execCallback(s, 'oninit');                                      
10313                                         });
10314                                 }
10315                         });
10316                 },
10317
10318                 get : function(id) {
10319                         if (id === undefined)
10320                                 return this.editors;
10321
10322                         return this.editors[id];
10323                 },
10324
10325                 getInstanceById : function(id) {
10326                         return this.get(id);
10327                 },
10328
10329                 add : function(editor) {
10330                         var self = this, editors = self.editors;
10331
10332                         // Add named and index editor instance
10333                         editors[editor.id] = editor;
10334                         editors.push(editor);
10335
10336                         self._setActive(editor);
10337                         self.onAddEditor.dispatch(self, editor);
10338
10339
10340                         return editor;
10341                 },
10342
10343                 remove : function(editor) {
10344                         var t = this, i, editors = t.editors;
10345
10346                         // Not in the collection
10347                         if (!editors[editor.id])
10348                                 return null;
10349
10350                         delete editors[editor.id];
10351
10352                         for (i = 0; i < editors.length; i++) {
10353                                 if (editors[i] == editor) {
10354                                         editors.splice(i, 1);
10355                                         break;
10356                                 }
10357                         }
10358
10359                         // Select another editor since the active one was removed
10360                         if (t.activeEditor == editor)
10361                                 t._setActive(editors[0]);
10362
10363                         editor.destroy();
10364                         t.onRemoveEditor.dispatch(t, editor);
10365
10366                         return editor;
10367                 },
10368
10369                 execCommand : function(c, u, v) {
10370                         var t = this, ed = t.get(v), w;
10371
10372                         // Manager commands
10373                         switch (c) {
10374                                 case "mceFocus":
10375                                         ed.focus();
10376                                         return true;
10377
10378                                 case "mceAddEditor":
10379                                 case "mceAddControl":
10380                                         if (!t.get(v))
10381                                                 new tinymce.Editor(v, t.settings).render();
10382
10383                                         return true;
10384
10385                                 case "mceAddFrameControl":
10386                                         w = v.window;
10387
10388                                         // Add tinyMCE global instance and tinymce namespace to specified window
10389                                         w.tinyMCE = tinyMCE;
10390                                         w.tinymce = tinymce;
10391
10392                                         tinymce.DOM.doc = w.document;
10393                                         tinymce.DOM.win = w;
10394
10395                                         ed = new tinymce.Editor(v.element_id, v);
10396                                         ed.render();
10397
10398                                         // Fix IE memory leaks
10399                                         if (tinymce.isIE) {
10400                                                 function clr() {
10401                                                         ed.destroy();
10402                                                         w.detachEvent('onunload', clr);
10403                                                         w = w.tinyMCE = w.tinymce = null; // IE leak
10404                                                 };
10405
10406                                                 w.attachEvent('onunload', clr);
10407                                         }
10408
10409                                         v.page_window = null;
10410
10411                                         return true;
10412
10413                                 case "mceRemoveEditor":
10414                                 case "mceRemoveControl":
10415                                         if (ed)
10416                                                 ed.remove();
10417
10418                                         return true;
10419
10420                                 case 'mceToggleEditor':
10421                                         if (!ed) {
10422                                                 t.execCommand('mceAddControl', 0, v);
10423                                                 return true;
10424                                         }
10425
10426                                         if (ed.isHidden())
10427                                                 ed.show();
10428                                         else
10429                                                 ed.hide();
10430
10431                                         return true;
10432                         }
10433
10434                         // Run command on active editor
10435                         if (t.activeEditor)
10436                                 return t.activeEditor.execCommand(c, u, v);
10437
10438                         return false;
10439                 },
10440
10441                 execInstanceCommand : function(id, c, u, v) {
10442                         var ed = this.get(id);
10443
10444                         if (ed)
10445                                 return ed.execCommand(c, u, v);
10446
10447                         return false;
10448                 },
10449
10450                 triggerSave : function() {
10451                         each(this.editors, function(e) {
10452                                 e.save();
10453                         });
10454                 },
10455
10456                 addI18n : function(p, o) {
10457                         var lo, i18n = this.i18n;
10458
10459                         if (!tinymce.is(p, 'string')) {
10460                                 each(p, function(o, lc) {
10461                                         each(o, function(o, g) {
10462                                                 each(o, function(o, k) {
10463                                                         if (g === 'common')
10464                                                                 i18n[lc + '.' + k] = o;
10465                                                         else
10466                                                                 i18n[lc + '.' + g + '.' + k] = o;
10467                                                 });
10468                                         });
10469                                 });
10470                         } else {
10471                                 each(o, function(o, k) {
10472                                         i18n[p + '.' + k] = o;
10473                                 });
10474                         }
10475                 },
10476
10477                 // Private methods
10478
10479                 _setActive : function(editor) {
10480                         this.selectedInstance = this.activeEditor = editor;
10481                 }
10482         });
10483 })(tinymce);
10484
10485 (function(tinymce) {
10486         // Shorten these names
10487         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
10488                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10489                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10490                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10491                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
10492
10493         tinymce.create('tinymce.Editor', {
10494                 Editor : function(id, s) {
10495                         var t = this;
10496
10497                         t.id = t.editorId = id;
10498
10499                         t.execCommands = {};
10500                         t.queryStateCommands = {};
10501                         t.queryValueCommands = {};
10502
10503                         t.isNotDirty = false;
10504
10505                         t.plugins = {};
10506
10507                         // Add events to the editor
10508                         each([
10509                                 'onPreInit',
10510
10511                                 'onBeforeRenderUI',
10512
10513                                 'onPostRender',
10514
10515                                 'onInit',
10516
10517                                 'onRemove',
10518
10519                                 'onActivate',
10520
10521                                 'onDeactivate',
10522
10523                                 'onClick',
10524
10525                                 'onEvent',
10526
10527                                 'onMouseUp',
10528
10529                                 'onMouseDown',
10530
10531                                 'onDblClick',
10532
10533                                 'onKeyDown',
10534
10535                                 'onKeyUp',
10536
10537                                 'onKeyPress',
10538
10539                                 'onContextMenu',
10540
10541                                 'onSubmit',
10542
10543                                 'onReset',
10544
10545                                 'onPaste',
10546
10547                                 'onPreProcess',
10548
10549                                 'onPostProcess',
10550
10551                                 'onBeforeSetContent',
10552
10553                                 'onBeforeGetContent',
10554
10555                                 'onSetContent',
10556
10557                                 'onGetContent',
10558
10559                                 'onLoadContent',
10560
10561                                 'onSaveContent',
10562
10563                                 'onNodeChange',
10564
10565                                 'onChange',
10566
10567                                 'onBeforeExecCommand',
10568
10569                                 'onExecCommand',
10570
10571                                 'onUndo',
10572
10573                                 'onRedo',
10574
10575                                 'onVisualAid',
10576
10577                                 'onSetProgressState'
10578                         ], function(e) {
10579                                 t[e] = new Dispatcher(t);
10580                         });
10581
10582                         t.settings = s = extend({
10583                                 id : id,
10584                                 language : 'en',
10585                                 docs_language : 'en',
10586                                 theme : 'simple',
10587                                 skin : 'default',
10588                                 delta_width : 0,
10589                                 delta_height : 0,
10590                                 popup_css : '',
10591                                 plugins : '',
10592                                 document_base_url : tinymce.documentBaseURL,
10593                                 add_form_submit_trigger : 1,
10594                                 submit_patch : 1,
10595                                 add_unload_trigger : 1,
10596                                 convert_urls : 1,
10597                                 relative_urls : 1,
10598                                 remove_script_host : 1,
10599                                 table_inline_editing : 0,
10600                                 object_resizing : 1,
10601                                 cleanup : 1,
10602                                 accessibility_focus : 1,
10603                                 custom_shortcuts : 1,
10604                                 custom_undo_redo_keyboard_shortcuts : 1,
10605                                 custom_undo_redo_restore_selection : 1,
10606                                 custom_undo_redo : 1,
10607                                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
10608                                 visual_table_class : 'mceItemTable',
10609                                 visual : 1,
10610                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
10611                                 apply_source_formatting : 1,
10612                                 directionality : 'ltr',
10613                                 forced_root_block : 'p',
10614                                 hidden_input : 1,
10615                                 padd_empty_editor : 1,
10616                                 render_ui : 1,
10617                                 init_theme : 1,
10618                                 force_p_newlines : 1,
10619                                 indentation : '30px',
10620                                 keep_styles : 1,
10621                                 fix_table_elements : 1,
10622                                 inline_styles : 1,
10623                                 convert_fonts_to_spans : true,
10624                                 indent : 'simple',
10625                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10626                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10627                                 validate : true,
10628                                 entity_encoding : 'named',
10629                                 url_converter : t.convertURL,
10630                                 url_converter_scope : t,
10631                                 ie7_compat : true
10632                         }, s);
10633
10634                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
10635                                 base_uri : tinyMCE.baseURI
10636                         });
10637
10638                         t.baseURI = tinymce.baseURI;
10639
10640                         t.contentCSS = [];
10641
10642                         // Call setup
10643                         t.execCallback('setup', t);
10644                 },
10645
10646                 render : function(nst) {
10647                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
10648
10649                         // Page is not loaded yet, wait for it
10650                         if (!Event.domLoaded) {
10651                                 Event.add(document, 'init', function() {
10652                                         t.render();
10653                                 });
10654                                 return;
10655                         }
10656
10657                         tinyMCE.settings = s;
10658
10659                         // Element not found, then skip initialization
10660                         if (!t.getElement())
10661                                 return;
10662
10663                         // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
10664                         // browser says it has contentEditable support but there is no visible caret
10665                         // We will remove this check ones Apple implements full contentEditable support
10666                         if (tinymce.isIDevice)
10667                                 return;
10668
10669                         // Add hidden input for non input elements inside form elements
10670                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
10671                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
10672
10673                         if (tinymce.WindowManager)
10674                                 t.windowManager = new tinymce.WindowManager(t);
10675
10676                         if (s.encoding == 'xml') {
10677                                 t.onGetContent.add(function(ed, o) {
10678                                         if (o.save)
10679                                                 o.content = DOM.encode(o.content);
10680                                 });
10681                         }
10682
10683                         if (s.add_form_submit_trigger) {
10684                                 t.onSubmit.addToTop(function() {
10685                                         if (t.initialized) {
10686                                                 t.save();
10687                                                 t.isNotDirty = 1;
10688                                         }
10689                                 });
10690                         }
10691
10692                         if (s.add_unload_trigger) {
10693                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
10694                                         if (t.initialized && !t.destroyed && !t.isHidden())
10695                                                 t.save({format : 'raw', no_events : true});
10696                                 });
10697                         }
10698
10699                         tinymce.addUnload(t.destroy, t);
10700
10701                         if (s.submit_patch) {
10702                                 t.onBeforeRenderUI.add(function() {
10703                                         var n = t.getElement().form;
10704
10705                                         if (!n)
10706                                                 return;
10707
10708                                         // Already patched
10709                                         if (n._mceOldSubmit)
10710                                                 return;
10711
10712                                         // Check page uses id="submit" or name="submit" for it's submit button
10713                                         if (!n.submit.nodeType && !n.submit.length) {
10714                                                 t.formElement = n;
10715                                                 n._mceOldSubmit = n.submit;
10716                                                 n.submit = function() {
10717                                                         // Save all instances
10718                                                         tinymce.triggerSave();
10719                                                         t.isNotDirty = 1;
10720
10721                                                         return t.formElement._mceOldSubmit(t.formElement);
10722                                                 };
10723                                         }
10724
10725                                         n = null;
10726                                 });
10727                         }
10728
10729                         // Load scripts
10730                         function loadScripts() {
10731                                 if (s.language && s.language_load !== false)
10732                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
10733
10734                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
10735                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
10736
10737                                 each(explode(s.plugins), function(p) {
10738                                         if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
10739                                                 // Skip safari plugin, since it is removed as of 3.3b1
10740                                                 if (p == 'safari')
10741                                                         return;
10742
10743                                                 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
10744                                         }
10745                                 });
10746
10747                                 // Init when que is loaded
10748                                 sl.loadQueue(function() {
10749                                         if (!t.removed)
10750                                                 t.init();
10751                                 });
10752                         };
10753
10754                         loadScripts();
10755                 },
10756
10757                 init : function() {
10758                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i;
10759
10760                         tinymce.add(t);
10761
10762                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
10763
10764                         if (s.theme) {
10765                                 s.theme = s.theme.replace(/-/, '');
10766                                 o = ThemeManager.get(s.theme);
10767                                 t.theme = new o();
10768
10769                                 if (t.theme.init && s.init_theme)
10770                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
10771                         }
10772
10773                         // Create all plugins
10774                         each(explode(s.plugins.replace(/\-/g, '')), function(p) {
10775                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
10776
10777                                 if (c) {
10778                                         po = new c(t, u);
10779
10780                                         t.plugins[p] = po;
10781
10782                                         if (po.init)
10783                                                 po.init(t, u);
10784                                 }
10785                         });
10786
10787                         // Setup popup CSS path(s)
10788                         if (s.popup_css !== false) {
10789                                 if (s.popup_css)
10790                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
10791                                 else
10792                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
10793                         }
10794
10795                         if (s.popup_css_add)
10796                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
10797
10798                         t.controlManager = new tinymce.ControlManager(t);
10799
10800                         if (s.custom_undo_redo) {
10801                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
10802                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10803                                                 t.undoManager.beforeChange();
10804                                 });
10805
10806                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
10807                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10808                                                 t.undoManager.add();
10809                                 });
10810                         }
10811
10812                         t.onExecCommand.add(function(ed, c) {
10813                                 // Don't refresh the select lists until caret move
10814                                 if (!/^(FontName|FontSize)$/.test(c))
10815                                         t.nodeChanged();
10816                         });
10817
10818                         // Remove ghost selections on images and tables in Gecko
10819                         if (isGecko) {
10820                                 function repaint(a, o) {
10821                                         if (!o || !o.initial)
10822                                                 t.execCommand('mceRepaint');
10823                                 };
10824
10825                                 t.onUndo.add(repaint);
10826                                 t.onRedo.add(repaint);
10827                                 t.onSetContent.add(repaint);
10828                         }
10829
10830                         // Enables users to override the control factory
10831                         t.onBeforeRenderUI.dispatch(t, t.controlManager);
10832
10833                         // Measure box
10834                         if (s.render_ui) {
10835                                 w = s.width || e.style.width || e.offsetWidth;
10836                                 h = s.height || e.style.height || e.offsetHeight;
10837                                 t.orgDisplay = e.style.display;
10838                                 re = /^[0-9\.]+(|px)$/i;
10839
10840                                 if (re.test('' + w))
10841                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
10842
10843                                 if (re.test('' + h))
10844                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
10845
10846                                 // Render UI
10847                                 o = t.theme.renderUI({
10848                                         targetNode : e,
10849                                         width : w,
10850                                         height : h,
10851                                         deltaWidth : s.delta_width,
10852                                         deltaHeight : s.delta_height
10853                                 });
10854
10855                                 t.editorContainer = o.editorContainer;
10856                         }
10857
10858
10859                         // User specified a document.domain value
10860                         if (document.domain && location.hostname != document.domain)
10861                                 tinymce.relaxedDomain = document.domain;
10862
10863                         // Resize editor
10864                         DOM.setStyles(o.sizeContainer || o.editorContainer, {
10865                                 width : w,
10866                                 height : h
10867                         });
10868
10869                         // Load specified content CSS last
10870                         if (s.content_css) {
10871                                 tinymce.each(explode(s.content_css), function(u) {
10872                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
10873                                 });
10874                         }
10875
10876                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
10877                         if (h < 100)
10878                                 h = 100;
10879
10880                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
10881
10882                         // We only need to override paths if we have to
10883                         // IE has a bug where it remove site absolute urls to relative ones if this is specified
10884                         if (s.document_base_url != tinymce.documentBaseURL)
10885                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
10886
10887                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
10888                         if (s.ie7_compat)
10889                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
10890                         else
10891                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
10892
10893                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
10894
10895                         // Firefox 2 doesn't load stylesheets correctly this way
10896                         if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
10897                                 for (i = 0; i < t.contentCSS.length; i++)
10898                                         t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
10899
10900                                 t.contentCSS = [];
10901                         }
10902
10903                         bi = s.body_id || 'tinymce';
10904                         if (bi.indexOf('=') != -1) {
10905                                 bi = t.getParam('body_id', '', 'hash');
10906                                 bi = bi[t.id] || bi;
10907                         }
10908
10909                         bc = s.body_class || '';
10910                         if (bc.indexOf('=') != -1) {
10911                                 bc = t.getParam('body_class', '', 'hash');
10912                                 bc = bc[t.id] || '';
10913                         }
10914
10915                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
10916
10917                         // Domain relaxing enabled, then set document domain
10918                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
10919                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
10920                                 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';                         
10921                         }
10922
10923                         // Create iframe
10924                         // TODO: ACC add the appropriate description on this.
10925                         n = DOM.add(o.iframeContainer, 'iframe', { 
10926                                 id : t.id + "_ifr",
10927                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
10928                                 frameBorder : '0', 
10929                                 title : s.aria_label,
10930                                 style : {
10931                                         width : '100%',
10932                                         height : h
10933                                 }
10934                         });
10935
10936                         t.contentAreaContainer = o.iframeContainer;
10937                         DOM.get(o.editorContainer).style.display = t.orgDisplay;
10938                         DOM.get(t.id).style.display = 'none';
10939                         DOM.setAttrib(t.id, 'aria-hidden', true);
10940
10941                         if (!tinymce.relaxedDomain || !u)
10942                                 t.setupIframe();
10943
10944                         e = n = o = null; // Cleanup
10945                 },
10946
10947                 setupIframe : function() {
10948                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
10949
10950                         // Setup iframe body
10951                         if (!isIE || !tinymce.relaxedDomain) {
10952                                 d.open();
10953                                 d.write(t.iframeHTML);
10954                                 d.close();
10955
10956                                 if (tinymce.relaxedDomain)
10957                                         d.domain = tinymce.relaxedDomain;
10958                         }
10959
10960                         // Design mode needs to be added here Ctrl+A will fail otherwise
10961                         if (!isIE) {
10962                                 try {
10963                                         if (!s.readonly)
10964                                                 d.designMode = 'On';
10965                                 } catch (ex) {
10966                                         // Will fail on Gecko if the editor is placed in an hidden container element
10967                                         // The design mode will be set ones the editor is focused
10968                                 }
10969                         }
10970
10971                         // IE needs to use contentEditable or it will display non secure items for HTTPS
10972                         if (isIE) {
10973                                 // It will not steal focus if we hide it while setting contentEditable
10974                                 b = t.getBody();
10975                                 DOM.hide(b);
10976
10977                                 if (!s.readonly)
10978                                         b.contentEditable = true;
10979
10980                                 DOM.show(b);
10981                         }
10982
10983                         t.schema = new tinymce.html.Schema(s);
10984
10985                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
10986                                 keep_values : true,
10987                                 url_converter : t.convertURL,
10988                                 url_converter_scope : t,
10989                                 hex_colors : s.force_hex_style_colors,
10990                                 class_filter : s.class_filter,
10991                                 update_styles : 1,
10992                                 fix_ie_paragraphs : 1,
10993                                 schema : t.schema
10994                         });
10995
10996                         t.parser = new tinymce.html.DomParser(s, t.schema);
10997
10998                         // Force anchor names closed
10999                         t.parser.addAttributeFilter('name', function(nodes, name) {
11000                                 var i = nodes.length, sibling, prevSibling, parent, node;
11001
11002                                 while (i--) {
11003                                         node = nodes[i];
11004                                         if (node.name === 'a' && node.firstChild) {
11005                                                 parent = node.parent;
11006
11007                                                 // Move children after current node
11008                                                 sibling = node.lastChild;
11009                                                 do {
11010                                                         prevSibling = sibling.prev;
11011                                                         parent.insert(sibling, node);
11012                                                         sibling = prevSibling;
11013                                                 } while (sibling);
11014                                         }
11015                                 }
11016                         });
11017
11018                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style
11019                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
11020                                 var i = nodes.length, node, dom = t.dom, value;
11021
11022                                 while (i--) {
11023                                         node = nodes[i];
11024                                         value = node.attr(name);
11025
11026                                         if (name === "style")
11027                                                 node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name));
11028                                         else
11029                                                 node.attr('data-mce-' + name, t.convertURL(value, name, node.name));
11030                                 }
11031                         });
11032
11033                         // Keep scripts from executing
11034                         t.parser.addNodeFilter('script', function(nodes, name) {
11035                                 var i = nodes.length;
11036
11037                                 while (i--)
11038                                         nodes[i].attr('type', 'mce-text/javascript');
11039                         });
11040
11041                         t.parser.addNodeFilter('#cdata', function(nodes, name) {
11042                                 var i = nodes.length, node;
11043
11044                                 while (i--) {
11045                                         node = nodes[i];
11046                                         node.type = 8;
11047                                         node.name = '#comment';
11048                                         node.value = '[CDATA[' + node.value + ']]';
11049                                 }
11050                         });
11051
11052                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
11053                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
11054
11055                                 while (i--) {
11056                                         node = nodes[i];
11057
11058                                         if (node.isEmpty(nonEmptyElements))
11059                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
11060                                 }
11061                         });
11062
11063                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
11064
11065                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
11066
11067                         t.formatter = new tinymce.Formatter(this);
11068
11069                         // Register default formats
11070                         t.formatter.register({
11071                                 alignleft : [
11072                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
11073                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
11074                                 ],
11075
11076                                 aligncenter : [
11077                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
11078                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
11079                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
11080                                 ],
11081
11082                                 alignright : [
11083                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
11084                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
11085                                 ],
11086
11087                                 alignfull : [
11088                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
11089                                 ],
11090
11091                                 bold : [
11092                                         {inline : 'strong', remove : 'all'},
11093                                         {inline : 'span', styles : {fontWeight : 'bold'}},
11094                                         {inline : 'b', remove : 'all'}
11095                                 ],
11096
11097                                 italic : [
11098                                         {inline : 'em', remove : 'all'},
11099                                         {inline : 'span', styles : {fontStyle : 'italic'}},
11100                                         {inline : 'i', remove : 'all'}
11101                                 ],
11102
11103                                 underline : [
11104                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
11105                                         {inline : 'u', remove : 'all'}
11106                                 ],
11107
11108                                 strikethrough : [
11109                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
11110                                         {inline : 'strike', remove : 'all'}
11111                                 ],
11112
11113                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
11114                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
11115                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
11116                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
11117                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
11118                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
11119                                 subscript : {inline : 'sub'},
11120                                 superscript : {inline : 'sup'},
11121
11122                                 removeformat : [
11123                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
11124                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
11125                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
11126                                 ]
11127                         });
11128
11129                         // Register default block formats
11130                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
11131                                 t.formatter.register(name, {block : name, remove : 'all'});
11132                         });
11133
11134                         // Register user defined formats
11135                         t.formatter.register(t.settings.formats);
11136
11137                         t.undoManager = new tinymce.UndoManager(t);
11138
11139                         // Pass through
11140                         t.undoManager.onAdd.add(function(um, l) {
11141                                 if (um.hasUndo())
11142                                         return t.onChange.dispatch(t, l, um);
11143                         });
11144
11145                         t.undoManager.onUndo.add(function(um, l) {
11146                                 return t.onUndo.dispatch(t, l, um);
11147                         });
11148
11149                         t.undoManager.onRedo.add(function(um, l) {
11150                                 return t.onRedo.dispatch(t, l, um);
11151                         });
11152
11153                         t.forceBlocks = new tinymce.ForceBlocks(t, {
11154                                 forced_root_block : s.forced_root_block
11155                         });
11156
11157                         t.editorCommands = new tinymce.EditorCommands(t);
11158
11159                         // Pass through
11160                         t.serializer.onPreProcess.add(function(se, o) {
11161                                 return t.onPreProcess.dispatch(t, o, se);
11162                         });
11163
11164                         t.serializer.onPostProcess.add(function(se, o) {
11165                                 return t.onPostProcess.dispatch(t, o, se);
11166                         });
11167
11168                         t.onPreInit.dispatch(t);
11169
11170                         if (!s.gecko_spellcheck)
11171                                 t.getBody().spellcheck = 0;
11172
11173                         if (!s.readonly)
11174                                 t._addEvents();
11175
11176                         t.controlManager.onPostRender.dispatch(t, t.controlManager);
11177                         t.onPostRender.dispatch(t);
11178
11179                         if (s.directionality)
11180                                 t.getBody().dir = s.directionality;
11181
11182                         if (s.nowrap)
11183                                 t.getBody().style.whiteSpace = "nowrap";
11184
11185                         if (s.handle_node_change_callback) {
11186                                 t.onNodeChange.add(function(ed, cm, n) {
11187                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
11188                                 });
11189                         }
11190
11191                         if (s.save_callback) {
11192                                 t.onSaveContent.add(function(ed, o) {
11193                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
11194
11195                                         if (h)
11196                                                 o.content = h;
11197                                 });
11198                         }
11199
11200                         if (s.onchange_callback) {
11201                                 t.onChange.add(function(ed, l) {
11202                                         t.execCallback('onchange_callback', t, l);
11203                                 });
11204                         }
11205
11206                         if (s.protect) {
11207                                 t.onBeforeSetContent.add(function(ed, o) {
11208                                         if (s.protect) {
11209                                                 each(s.protect, function(pattern) {
11210                                                         o.content = o.content.replace(pattern, function(str) {
11211                                                                 return '<!--mce:protected ' + escape(str) + '-->';
11212                                                         });
11213                                                 });
11214                                         }
11215                                 });
11216                         }
11217
11218                         if (s.convert_newlines_to_brs) {
11219                                 t.onBeforeSetContent.add(function(ed, o) {
11220                                         if (o.initial)
11221                                                 o.content = o.content.replace(/\r?\n/g, '<br />');
11222                                 });
11223                         }
11224
11225                         if (s.preformatted) {
11226                                 t.onPostProcess.add(function(ed, o) {
11227                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');
11228                                         o.content = o.content.replace(/<\/pre>\s*$/, '');
11229
11230                                         if (o.set)
11231                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
11232                                 });
11233                         }
11234
11235                         if (s.verify_css_classes) {
11236                                 t.serializer.attribValueFilter = function(n, v) {
11237                                         var s, cl;
11238
11239                                         if (n == 'class') {
11240                                                 // Build regexp for classes
11241                                                 if (!t.classesRE) {
11242                                                         cl = t.dom.getClasses();
11243
11244                                                         if (cl.length > 0) {
11245                                                                 s = '';
11246
11247                                                                 each (cl, function(o) {
11248                                                                         s += (s ? '|' : '') + o['class'];
11249                                                                 });
11250
11251                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');
11252                                                         }
11253                                                 }
11254
11255                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
11256                                         }
11257
11258                                         return v;
11259                                 };
11260                         }
11261
11262                         if (s.cleanup_callback) {
11263                                 t.onBeforeSetContent.add(function(ed, o) {
11264                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11265                                 });
11266
11267                                 t.onPreProcess.add(function(ed, o) {
11268                                         if (o.set)
11269                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
11270
11271                                         if (o.get)
11272                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
11273                                 });
11274
11275                                 t.onPostProcess.add(function(ed, o) {
11276                                         if (o.set)
11277                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11278
11279                                         if (o.get)                                              
11280                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
11281                                 });
11282                         }
11283
11284                         if (s.save_callback) {
11285                                 t.onGetContent.add(function(ed, o) {
11286                                         if (o.save)
11287                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
11288                                 });
11289                         }
11290
11291                         if (s.handle_event_callback) {
11292                                 t.onEvent.add(function(ed, e, o) {
11293                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)
11294                                                 Event.cancel(e);
11295                                 });
11296                         }
11297
11298                         // Add visual aids when new contents is added
11299                         t.onSetContent.add(function() {
11300                                 t.addVisual(t.getBody());
11301                         });
11302
11303                         // Remove empty contents
11304                         if (s.padd_empty_editor) {
11305                                 t.onPostProcess.add(function(ed, o) {
11306                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
11307                                 });
11308                         }
11309
11310                         if (isGecko) {
11311                                 // Fix gecko link bug, when a link is placed at the end of block elements there is
11312                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link
11313                                 function fixLinks(ed, o) {
11314                                         each(ed.dom.select('a'), function(n) {
11315                                                 var pn = n.parentNode;
11316
11317                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)
11318                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
11319                                         });
11320                                 };
11321
11322                                 t.onExecCommand.add(function(ed, cmd) {
11323                                         if (cmd === 'CreateLink')
11324                                                 fixLinks(ed);
11325                                 });
11326
11327                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
11328
11329                                 if (!s.readonly) {
11330                                         try {
11331                                                 // Design mode must be set here once again to fix a bug where
11332                                                 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
11333                                                 d.designMode = 'Off';
11334                                                 d.designMode = 'On';
11335                                         } catch (ex) {
11336                                                 // Will fail on Gecko if the editor is placed in an hidden container element
11337                                                 // The design mode will be set ones the editor is focused
11338                                         }
11339                                 }
11340                         }
11341
11342                         // A small timeout was needed since firefox will remove. Bug: #1838304
11343                         setTimeout(function () {
11344                                 if (t.removed)
11345                                         return;
11346
11347                                 t.load({initial : true, format : 'html'});
11348                                 t.startContent = t.getContent({format : 'raw'});
11349                                 t.undoManager.add();
11350                                 t.initialized = true;
11351
11352                                 t.onInit.dispatch(t);
11353                                 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
11354                                 t.execCallback('init_instance_callback', t);
11355                                 t.focus(true);
11356                                 t.nodeChanged({initial : 1});
11357
11358                                 // Load specified content CSS last
11359                                 each(t.contentCSS, function(u) {
11360                                         t.dom.loadCSS(u);
11361                                 });
11362
11363                                 // Handle auto focus
11364                                 if (s.auto_focus) {
11365                                         setTimeout(function () {
11366                                                 var ed = tinymce.get(s.auto_focus);
11367
11368                                                 ed.selection.select(ed.getBody(), 1);
11369                                                 ed.selection.collapse(1);
11370                                                 ed.getWin().focus();
11371                                         }, 100);
11372                                 }
11373                         }, 1);
11374         
11375                         e = null;
11376                 },
11377
11378
11379                 focus : function(sf) {
11380                         var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
11381
11382                         if (!sf) {
11383                                 // Get selected control element
11384                                 ieRng = t.selection.getRng();
11385                                 if (ieRng.item) {
11386                                         controlElm = ieRng.item(0);
11387                                 }
11388
11389                                 // Is not content editable
11390                                 if (!ce)
11391                                         t.getWin().focus();
11392
11393                                 // Restore selected control element
11394                                 // This is needed when for example an image is selected within a
11395                                 // layer a call to focus will then remove the control selection
11396                                 if (controlElm && controlElm.ownerDocument == doc) {
11397                                         ieRng = doc.body.createControlRange();
11398                                         ieRng.addElement(controlElm);
11399                                         ieRng.select();
11400                                 }
11401
11402                         }
11403
11404                         if (tinymce.activeEditor != t) {
11405                                 if ((oed = tinymce.activeEditor) != null)
11406                                         oed.onDeactivate.dispatch(oed, t);
11407
11408                                 t.onActivate.dispatch(t, oed);
11409                         }
11410
11411                         tinymce._setActive(t);
11412                 },
11413
11414                 execCallback : function(n) {
11415                         var t = this, f = t.settings[n], s;
11416
11417                         if (!f)
11418                                 return;
11419
11420                         // Look through lookup
11421                         if (t.callbackLookup && (s = t.callbackLookup[n])) {
11422                                 f = s.func;
11423                                 s = s.scope;
11424                         }
11425
11426                         if (is(f, 'string')) {
11427                                 s = f.replace(/\.\w+$/, '');
11428                                 s = s ? tinymce.resolve(s) : 0;
11429                                 f = tinymce.resolve(f);
11430                                 t.callbackLookup = t.callbackLookup || {};
11431                                 t.callbackLookup[n] = {func : f, scope : s};
11432                         }
11433
11434                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
11435                 },
11436
11437                 translate : function(s) {
11438                         var c = this.settings.language || 'en', i18n = tinymce.i18n;
11439
11440                         if (!s)
11441                                 return '';
11442
11443                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
11444                                 return i18n[c + '.' + b] || '{#' + b + '}';
11445                         });
11446                 },
11447
11448                 getLang : function(n, dv) {
11449                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
11450                 },
11451
11452                 getParam : function(n, dv, ty) {
11453                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
11454
11455                         if (ty === 'hash') {
11456                                 o = {};
11457
11458                                 if (is(v, 'string')) {
11459                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
11460                                                 v = v.split('=');
11461
11462                                                 if (v.length > 1)
11463                                                         o[tr(v[0])] = tr(v[1]);
11464                                                 else
11465                                                         o[tr(v[0])] = tr(v);
11466                                         });
11467                                 } else
11468                                         o = v;
11469
11470                                 return o;
11471                         }
11472
11473                         return v;
11474                 },
11475
11476                 nodeChanged : function(o) {
11477                         var t = this, s = t.selection, n = s.getStart() || t.getBody();
11478
11479                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
11480                         if (t.initialized) {
11481                                 o = o || {};
11482                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
11483
11484                                 // Get parents and add them to object
11485                                 o.parents = [];
11486                                 t.dom.getParent(n, function(node) {
11487                                         if (node.nodeName == 'BODY')
11488                                                 return true;
11489
11490                                         o.parents.push(node);
11491                                 });
11492
11493                                 t.onNodeChange.dispatch(
11494                                         t,
11495                                         o ? o.controlManager || t.controlManager : t.controlManager,
11496                                         n,
11497                                         s.isCollapsed(),
11498                                         o
11499                                 );
11500                         }
11501                 },
11502
11503                 addButton : function(n, s) {
11504                         var t = this;
11505
11506                         t.buttons = t.buttons || {};
11507                         t.buttons[n] = s;
11508                 },
11509
11510                 addCommand : function(name, callback, scope) {
11511                         this.execCommands[name] = {func : callback, scope : scope || this};
11512                 },
11513
11514                 addQueryStateHandler : function(name, callback, scope) {
11515                         this.queryStateCommands[name] = {func : callback, scope : scope || this};
11516                 },
11517
11518                 addQueryValueHandler : function(name, callback, scope) {
11519                         this.queryValueCommands[name] = {func : callback, scope : scope || this};
11520                 },
11521
11522                 addShortcut : function(pa, desc, cmd_func, sc) {
11523                         var t = this, c;
11524
11525                         if (!t.settings.custom_shortcuts)
11526                                 return false;
11527
11528                         t.shortcuts = t.shortcuts || {};
11529
11530                         if (is(cmd_func, 'string')) {
11531                                 c = cmd_func;
11532
11533                                 cmd_func = function() {
11534                                         t.execCommand(c, false, null);
11535                                 };
11536                         }
11537
11538                         if (is(cmd_func, 'object')) {
11539                                 c = cmd_func;
11540
11541                                 cmd_func = function() {
11542                                         t.execCommand(c[0], c[1], c[2]);
11543                                 };
11544                         }
11545
11546                         each(explode(pa), function(pa) {
11547                                 var o = {
11548                                         func : cmd_func,
11549                                         scope : sc || this,
11550                                         desc : desc,
11551                                         alt : false,
11552                                         ctrl : false,
11553                                         shift : false
11554                                 };
11555
11556                                 each(explode(pa, '+'), function(v) {
11557                                         switch (v) {
11558                                                 case 'alt':
11559                                                 case 'ctrl':
11560                                                 case 'shift':
11561                                                         o[v] = true;
11562                                                         break;
11563
11564                                                 default:
11565                                                         o.charCode = v.charCodeAt(0);
11566                                                         o.keyCode = v.toUpperCase().charCodeAt(0);
11567                                         }
11568                                 });
11569
11570                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
11571                         });
11572
11573                         return true;
11574                 },
11575
11576                 execCommand : function(cmd, ui, val, a) {
11577                         var t = this, s = 0, o, st;
11578
11579                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
11580                                 t.focus();
11581
11582                         o = {};
11583                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
11584                         if (o.terminate)
11585                                 return false;
11586
11587                         // Command callback
11588                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
11589                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11590                                 return true;
11591                         }
11592
11593                         // Registred commands
11594                         if (o = t.execCommands[cmd]) {
11595                                 st = o.func.call(o.scope, ui, val);
11596
11597                                 // Fall through on true
11598                                 if (st !== true) {
11599                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11600                                         return st;
11601                                 }
11602                         }
11603
11604                         // Plugin commands
11605                         each(t.plugins, function(p) {
11606                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {
11607                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11608                                         s = 1;
11609                                         return false;
11610                                 }
11611                         });
11612
11613                         if (s)
11614                                 return true;
11615
11616                         // Theme commands
11617                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
11618                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11619                                 return true;
11620                         }
11621
11622                         // Editor commands
11623                         if (t.editorCommands.execCommand(cmd, ui, val)) {
11624                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11625                                 return true;
11626                         }
11627
11628                         // Browser commands
11629                         t.getDoc().execCommand(cmd, ui, val);
11630                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11631                 },
11632
11633                 queryCommandState : function(cmd) {
11634                         var t = this, o, s;
11635
11636                         // Is hidden then return undefined
11637                         if (t._isHidden())
11638                                 return;
11639
11640                         // Registred commands
11641                         if (o = t.queryStateCommands[cmd]) {
11642                                 s = o.func.call(o.scope);
11643
11644                                 // Fall though on true
11645                                 if (s !== true)
11646                                         return s;
11647                         }
11648
11649                         // Registred commands
11650                         o = t.editorCommands.queryCommandState(cmd);
11651                         if (o !== -1)
11652                                 return o;
11653
11654                         // Browser commands
11655                         try {
11656                                 return this.getDoc().queryCommandState(cmd);
11657                         } catch (ex) {
11658                                 // Fails sometimes see bug: 1896577
11659                         }
11660                 },
11661
11662                 queryCommandValue : function(c) {
11663                         var t = this, o, s;
11664
11665                         // Is hidden then return undefined
11666                         if (t._isHidden())
11667                                 return;
11668
11669                         // Registred commands
11670                         if (o = t.queryValueCommands[c]) {
11671                                 s = o.func.call(o.scope);
11672
11673                                 // Fall though on true
11674                                 if (s !== true)
11675                                         return s;
11676                         }
11677
11678                         // Registred commands
11679                         o = t.editorCommands.queryCommandValue(c);
11680                         if (is(o))
11681                                 return o;
11682
11683                         // Browser commands
11684                         try {
11685                                 return this.getDoc().queryCommandValue(c);
11686                         } catch (ex) {
11687                                 // Fails sometimes see bug: 1896577
11688                         }
11689                 },
11690
11691                 show : function() {
11692                         var t = this;
11693
11694                         DOM.show(t.getContainer());
11695                         DOM.hide(t.id);
11696                         t.load();
11697                 },
11698
11699                 hide : function() {
11700                         var t = this, d = t.getDoc();
11701
11702                         // Fixed bug where IE has a blinking cursor left from the editor
11703                         if (isIE && d)
11704                                 d.execCommand('SelectAll');
11705
11706                         // We must save before we hide so Safari doesn't crash
11707                         t.save();
11708                         DOM.hide(t.getContainer());
11709                         DOM.setStyle(t.id, 'display', t.orgDisplay);
11710                 },
11711
11712                 isHidden : function() {
11713                         return !DOM.isHidden(this.id);
11714                 },
11715
11716                 setProgressState : function(b, ti, o) {
11717                         this.onSetProgressState.dispatch(this, b, ti, o);
11718
11719                         return b;
11720                 },
11721
11722                 load : function(o) {
11723                         var t = this, e = t.getElement(), h;
11724
11725                         if (e) {
11726                                 o = o || {};
11727                                 o.load = true;
11728
11729                                 // Double encode existing entities in the value
11730                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
11731                                 o.element = e;
11732
11733                                 if (!o.no_events)
11734                                         t.onLoadContent.dispatch(t, o);
11735
11736                                 o.element = e = null;
11737
11738                                 return h;
11739                         }
11740                 },
11741
11742                 save : function(o) {
11743                         var t = this, e = t.getElement(), h, f;
11744
11745                         if (!e || !t.initialized)
11746                                 return;
11747
11748                         o = o || {};
11749                         o.save = true;
11750
11751                         // Add undo level will trigger onchange event
11752                         if (!o.no_events) {
11753                                 t.undoManager.typing = false;
11754                                 t.undoManager.add();
11755                         }
11756
11757                         o.element = e;
11758                         h = o.content = t.getContent(o);
11759
11760                         if (!o.no_events)
11761                                 t.onSaveContent.dispatch(t, o);
11762
11763                         h = o.content;
11764
11765                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
11766                                 e.innerHTML = h;
11767
11768                                 // Update hidden form element
11769                                 if (f = DOM.getParent(t.id, 'form')) {
11770                                         each(f.elements, function(e) {
11771                                                 if (e.name == t.id) {
11772                                                         e.value = h;
11773                                                         return false;
11774                                                 }
11775                                         });
11776                                 }
11777                         } else
11778                                 e.value = h;
11779
11780                         o.element = e = null;
11781
11782                         return h;
11783                 },
11784
11785                 setContent : function(content, args) {
11786                         var self = this, rootNode, body = self.getBody();
11787
11788                         // Setup args object
11789                         args = args || {};
11790                         args.format = args.format || 'html';
11791                         args.set = true;
11792                         args.content = content;
11793
11794                         // Do preprocessing
11795                         if (!args.no_events)
11796                                 self.onBeforeSetContent.dispatch(self, args);
11797
11798                         content = args.content;
11799
11800                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
11801                         // It will also be impossible to place the caret in the editor unless there is a BR element present
11802                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
11803                                 body.innerHTML = '<br data-mce-bogus="1" />';
11804                                 return;
11805                         }
11806
11807                         // Parse and serialize the html
11808                         if (args.format !== 'raw') {
11809                                 content = new tinymce.html.Serializer({}, self.schema).serialize(
11810                                         self.parser.parse(content)
11811                                 );
11812                         }
11813
11814                         // Set the new cleaned contents to the editor
11815                         args.content = tinymce.trim(content);
11816                         self.dom.setHTML(body, args.content);
11817
11818                         // Do post processing
11819                         if (!args.no_events)
11820                                 self.onSetContent.dispatch(self, args);
11821
11822                         return args.content;
11823                 },
11824
11825                 getContent : function(args) {
11826                         var self = this, content;
11827
11828                         // Setup args object
11829                         args = args || {};
11830                         args.format = args.format || 'html';
11831                         args.get = true;
11832
11833                         // Do preprocessing
11834                         if (!args.no_events)
11835                                 self.onBeforeGetContent.dispatch(self, args);
11836
11837                         // Get raw contents or by default the cleaned contents
11838                         if (args.format == 'raw')
11839                                 content = self.getBody().innerHTML;
11840                         else
11841                                 content = self.serializer.serialize(self.getBody(), args);
11842
11843                         args.content = tinymce.trim(content);
11844
11845                         // Do post processing
11846                         if (!args.no_events)
11847                                 self.onGetContent.dispatch(self, args);
11848
11849                         return args.content;
11850                 },
11851
11852                 isDirty : function() {
11853                         var self = this;
11854
11855                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
11856                 },
11857
11858                 getContainer : function() {
11859                         var t = this;
11860
11861                         if (!t.container)
11862                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');
11863
11864                         return t.container;
11865                 },
11866
11867                 getContentAreaContainer : function() {
11868                         return this.contentAreaContainer;
11869                 },
11870
11871                 getElement : function() {
11872                         return DOM.get(this.settings.content_element || this.id);
11873                 },
11874
11875                 getWin : function() {
11876                         var t = this, e;
11877
11878                         if (!t.contentWindow) {
11879                                 e = DOM.get(t.id + "_ifr");
11880
11881                                 if (e)
11882                                         t.contentWindow = e.contentWindow;
11883                         }
11884
11885                         return t.contentWindow;
11886                 },
11887
11888                 getDoc : function() {
11889                         var t = this, w;
11890
11891                         if (!t.contentDocument) {
11892                                 w = t.getWin();
11893
11894                                 if (w)
11895                                         t.contentDocument = w.document;
11896                         }
11897
11898                         return t.contentDocument;
11899                 },
11900
11901                 getBody : function() {
11902                         return this.bodyElement || this.getDoc().body;
11903                 },
11904
11905                 convertURL : function(u, n, e) {
11906                         var t = this, s = t.settings;
11907
11908                         // Use callback instead
11909                         if (s.urlconverter_callback)
11910                                 return t.execCallback('urlconverter_callback', u, e, true, n);
11911
11912                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
11913                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
11914                                 return u;
11915
11916                         // Convert to relative
11917                         if (s.relative_urls)
11918                                 return t.documentBaseURI.toRelative(u);
11919
11920                         // Convert to absolute
11921                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
11922
11923                         return u;
11924                 },
11925
11926                 addVisual : function(e) {
11927                         var t = this, s = t.settings;
11928
11929                         e = e || t.getBody();
11930
11931                         if (!is(t.hasVisual))
11932                                 t.hasVisual = s.visual;
11933
11934                         each(t.dom.select('table,a', e), function(e) {
11935                                 var v;
11936
11937                                 switch (e.nodeName) {
11938                                         case 'TABLE':
11939                                                 v = t.dom.getAttrib(e, 'border');
11940
11941                                                 if (!v || v == '0') {
11942                                                         if (t.hasVisual)
11943                                                                 t.dom.addClass(e, s.visual_table_class);
11944                                                         else
11945                                                                 t.dom.removeClass(e, s.visual_table_class);
11946                                                 }
11947
11948                                                 return;
11949
11950                                         case 'A':
11951                                                 v = t.dom.getAttrib(e, 'name');
11952
11953                                                 if (v) {
11954                                                         if (t.hasVisual)
11955                                                                 t.dom.addClass(e, 'mceItemAnchor');
11956                                                         else
11957                                                                 t.dom.removeClass(e, 'mceItemAnchor');
11958                                                 }
11959
11960                                                 return;
11961                                 }
11962                         });
11963
11964                         t.onVisualAid.dispatch(t, e, t.hasVisual);
11965                 },
11966
11967                 remove : function() {
11968                         var t = this, e = t.getContainer();
11969
11970                         t.removed = 1; // Cancels post remove event execution
11971                         t.hide();
11972
11973                         t.execCallback('remove_instance_callback', t);
11974                         t.onRemove.dispatch(t);
11975
11976                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
11977                         t.onExecCommand.listeners = [];
11978
11979                         tinymce.remove(t);
11980                         DOM.remove(e);
11981                 },
11982
11983                 destroy : function(s) {
11984                         var t = this;
11985
11986                         // One time is enough
11987                         if (t.destroyed)
11988                                 return;
11989
11990                         if (!s) {
11991                                 tinymce.removeUnload(t.destroy);
11992                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
11993
11994                                 // Manual destroy
11995                                 if (t.theme && t.theme.destroy)
11996                                         t.theme.destroy();
11997
11998                                 // Destroy controls, selection and dom
11999                                 t.controlManager.destroy();
12000                                 t.selection.destroy();
12001                                 t.dom.destroy();
12002
12003                                 // Remove all events
12004
12005                                 // Don't clear the window or document if content editable
12006                                 // is enabled since other instances might still be present
12007                                 if (!t.settings.content_editable) {
12008                                         Event.clear(t.getWin());
12009                                         Event.clear(t.getDoc());
12010                                 }
12011
12012                                 Event.clear(t.getBody());
12013                                 Event.clear(t.formElement);
12014                         }
12015
12016                         if (t.formElement) {
12017                                 t.formElement.submit = t.formElement._mceOldSubmit;
12018                                 t.formElement._mceOldSubmit = null;
12019                         }
12020
12021                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
12022
12023                         if (t.selection)
12024                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
12025
12026                         t.destroyed = 1;
12027                 },
12028
12029                 // Internal functions
12030
12031                 _addEvents : function() {
12032                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
12033                         var t = this, i, s = t.settings, dom = t.dom, lo = {
12034                                 mouseup : 'onMouseUp',
12035                                 mousedown : 'onMouseDown',
12036                                 click : 'onClick',
12037                                 keyup : 'onKeyUp',
12038                                 keydown : 'onKeyDown',
12039                                 keypress : 'onKeyPress',
12040                                 submit : 'onSubmit',
12041                                 reset : 'onReset',
12042                                 contextmenu : 'onContextMenu',
12043                                 dblclick : 'onDblClick',
12044                                 paste : 'onPaste' // Doesn't work in all browsers yet
12045                         };
12046
12047                         function eventHandler(e, o) {
12048                                 var ty = e.type;
12049
12050                                 // Don't fire events when it's removed
12051                                 if (t.removed)
12052                                         return;
12053
12054                                 // Generic event handler
12055                                 if (t.onEvent.dispatch(t, e, o) !== false) {
12056                                         // Specific event handler
12057                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);
12058                                 }
12059                         };
12060
12061                         // Add DOM events
12062                         each(lo, function(v, k) {
12063                                 switch (k) {
12064                                         case 'contextmenu':
12065                                                 dom.bind(t.getDoc(), k, eventHandler);
12066                                                 break;
12067
12068                                         case 'paste':
12069                                                 dom.bind(t.getBody(), k, function(e) {
12070                                                         eventHandler(e);
12071                                                 });
12072                                                 break;
12073
12074                                         case 'submit':
12075                                         case 'reset':
12076                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
12077                                                 break;
12078
12079                                         default:
12080                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
12081                                 }
12082                         });
12083
12084                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
12085                                 t.focus(true);
12086                         });
12087
12088
12089                         // Fixes bug where a specified document_base_uri could result in broken images
12090                         // This will also fix drag drop of images in Gecko
12091                         if (tinymce.isGecko) {
12092                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
12093                                         var v;
12094
12095                                         e = e.target;
12096
12097                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
12098                                                 e.src = t.documentBaseURI.toAbsolute(v);
12099                                 });
12100                         }
12101
12102                         // Set various midas options in Gecko
12103                         if (isGecko) {
12104                                 function setOpts() {
12105                                         var t = this, d = t.getDoc(), s = t.settings;
12106
12107                                         if (isGecko && !s.readonly) {
12108                                                 if (t._isHidden()) {
12109                                                         try {
12110                                                                 if (!s.content_editable)
12111                                                                         d.designMode = 'On';
12112                                                         } catch (ex) {
12113                                                                 // Fails if it's hidden
12114                                                         }
12115                                                 }
12116
12117                                                 try {
12118                                                         // Try new Gecko method
12119                                                         d.execCommand("styleWithCSS", 0, false);
12120                                                 } catch (ex) {
12121                                                         // Use old method
12122                                                         if (!t._isHidden())
12123                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
12124                                                 }
12125
12126                                                 if (!s.table_inline_editing)
12127                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
12128
12129                                                 if (!s.object_resizing)
12130                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
12131                                         }
12132                                 };
12133
12134                                 t.onBeforeExecCommand.add(setOpts);
12135                                 t.onMouseDown.add(setOpts);
12136                         }
12137
12138                         // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
12139                         // WebKit can't even do simple things like selecting an image
12140                         // This also fixes so it's possible to select mceItemAnchors
12141                         if (tinymce.isWebKit) {
12142                                 t.onClick.add(function(ed, e) {
12143                                         e = e.target;
12144
12145                                         // Needs tobe the setBaseAndExtend or it will fail to select floated images
12146                                         if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
12147                                                 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
12148                                                 t.nodeChanged();
12149                                         }
12150                                 });
12151                         }
12152
12153                         // Add node change handlers
12154                         t.onMouseUp.add(t.nodeChanged);
12155                         //t.onClick.add(t.nodeChanged);
12156                         t.onKeyUp.add(function(ed, e) {
12157                                 var c = e.keyCode;
12158
12159                                 if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
12160                                         t.nodeChanged();
12161                         });
12162
12163                         // Add reset handler
12164                         t.onReset.add(function() {
12165                                 t.setContent(t.startContent, {format : 'raw'});
12166                         });
12167
12168                         // Add shortcuts
12169                         if (s.custom_shortcuts) {
12170                                 if (s.custom_undo_redo_keyboard_shortcuts) {
12171                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
12172                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
12173                                 }
12174
12175                                 // Add default shortcuts for gecko
12176                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
12177                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
12178                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
12179
12180                                 // BlockFormat shortcuts keys
12181                                 for (i=1; i<=6; i++)
12182                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
12183
12184                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
12185                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
12186                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
12187
12188                                 function find(e) {
12189                                         var v = null;
12190
12191                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)
12192                                                 return v;
12193
12194                                         each(t.shortcuts, function(o) {
12195                                                 if (tinymce.isMac && o.ctrl != e.metaKey)
12196                                                         return;
12197                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
12198                                                         return;
12199
12200                                                 if (o.alt != e.altKey)
12201                                                         return;
12202
12203                                                 if (o.shift != e.shiftKey)
12204                                                         return;
12205
12206                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
12207                                                         v = o;
12208                                                         return false;
12209                                                 }
12210                                         });
12211
12212                                         return v;
12213                                 };
12214
12215                                 t.onKeyUp.add(function(ed, e) {
12216                                         var o = find(e);
12217
12218                                         if (o)
12219                                                 return Event.cancel(e);
12220                                 });
12221
12222                                 t.onKeyPress.add(function(ed, e) {
12223                                         var o = find(e);
12224
12225                                         if (o)
12226                                                 return Event.cancel(e);
12227                                 });
12228
12229                                 t.onKeyDown.add(function(ed, e) {
12230                                         var o = find(e);
12231
12232                                         if (o) {
12233                                                 o.func.call(o.scope);
12234                                                 return Event.cancel(e);
12235                                         }
12236                                 });
12237                         }
12238
12239                         if (tinymce.isIE) {
12240                                 // Fix so resize will only update the width and height attributes not the styles of an image
12241                                 // It will also block mceItemNoResize items
12242                                 dom.bind(t.getDoc(), 'controlselect', function(e) {
12243                                         var re = t.resizeInfo, cb;
12244
12245                                         e = e.target;
12246
12247                                         // Don't do this action for non image elements
12248                                         if (e.nodeName !== 'IMG')
12249                                                 return;
12250
12251                                         if (re)
12252                                                 dom.unbind(re.node, re.ev, re.cb);
12253
12254                                         if (!dom.hasClass(e, 'mceItemNoResize')) {
12255                                                 ev = 'resizeend';
12256                                                 cb = dom.bind(e, ev, function(e) {
12257                                                         var v;
12258
12259                                                         e = e.target;
12260
12261                                                         if (v = dom.getStyle(e, 'width')) {
12262                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
12263                                                                 dom.setStyle(e, 'width', '');
12264                                                         }
12265
12266                                                         if (v = dom.getStyle(e, 'height')) {
12267                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
12268                                                                 dom.setStyle(e, 'height', '');
12269                                                         }
12270                                                 });
12271                                         } else {
12272                                                 ev = 'resizestart';
12273                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
12274                                         }
12275
12276                                         re = t.resizeInfo = {
12277                                                 node : e,
12278                                                 ev : ev,
12279                                                 cb : cb
12280                                         };
12281                                 });
12282
12283                                 t.onKeyDown.add(function(ed, e) {
12284                                         var sel;
12285
12286                                         switch (e.keyCode) {
12287                                                 case 8:
12288                                                         sel = t.getDoc().selection;
12289
12290                                                         // Fix IE control + backspace browser bug
12291                                                         if (sel.createRange && sel.createRange().item) {
12292                                                                 ed.dom.remove(sel.createRange().item(0));
12293                                                                 return Event.cancel(e);
12294                                                         }
12295                                         }
12296                                 });
12297                         }
12298
12299                         if (tinymce.isOpera) {
12300                                 t.onClick.add(function(ed, e) {
12301                                         Event.prevent(e);
12302                                 });
12303                         }
12304
12305                         // Add custom undo/redo handlers
12306                         if (s.custom_undo_redo) {
12307                                 function addUndo() {
12308                                         t.undoManager.typing = false;
12309                                         t.undoManager.add();
12310                                 };
12311
12312                                 dom.bind(t.getDoc(), 'focusout', function(e) {
12313                                         if (!t.removed && t.undoManager.typing)
12314                                                 addUndo();
12315                                 });
12316
12317                                 // Add undo level when contents is drag/dropped within the editor
12318                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
12319                                         addUndo();
12320                                 });
12321
12322                                 t.onKeyUp.add(function(ed, e) {
12323                                         var rng, parent, bookmark;
12324
12325                                         // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node.
12326                                         if (isIE && e.keyCode == 8) {
12327                                                 rng = t.selection.getRng();
12328                                                 if (rng.parentElement) {
12329                                                         parent = rng.parentElement();
12330                                                         bookmark = t.selection.getBookmark();
12331                                                         parent.innerHTML = parent.innerHTML;
12332                                                         t.selection.moveToBookmark(bookmark);
12333                                                 }
12334                                         }
12335
12336                                         if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
12337                                                 addUndo();
12338                                 });
12339
12340                                 t.onKeyDown.add(function(ed, e) {
12341                                         var rng, parent, bookmark, keyCode = e.keyCode;
12342
12343                                         // IE has a really odd bug where the DOM might include an node that doesn't have
12344                                         // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
12345                                         // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
12346                                         // after you delete contents from it. See: #3008923
12347                                         if (isIE && keyCode == 46) {
12348                                                 rng = t.selection.getRng();
12349
12350                                                 if (rng.parentElement) {
12351                                                         parent = rng.parentElement();
12352
12353                                                         if (!t.undoManager.typing) {
12354                                                                 t.undoManager.beforeChange();
12355                                                                 t.undoManager.typing = true;
12356                                                                 t.undoManager.add();
12357                                                         }
12358
12359                                                         // Select next word when ctrl key is used in combo with delete
12360                                                         if (e.ctrlKey) {
12361                                                                 rng.moveEnd('word', 1);
12362                                                                 rng.select();
12363                                                         }
12364
12365                                                         // Delete contents
12366                                                         t.selection.getSel().clear();
12367
12368                                                         // Check if we are within the same parent
12369                                                         if (rng.parentElement() == parent) {
12370                                                                 bookmark = t.selection.getBookmark();
12371
12372                                                                 try {
12373                                                                         // Update the HTML and hopefully it will remove the artifacts
12374                                                                         parent.innerHTML = parent.innerHTML;
12375                                                                 } catch (ex) {
12376                                                                         // And since it's IE it can sometimes produce an unknown runtime error
12377                                                                 }
12378
12379                                                                 // Restore the caret position
12380                                                                 t.selection.moveToBookmark(bookmark);
12381                                                         }
12382
12383                                                         // Block the default delete behavior since it might be broken
12384                                                         e.preventDefault();
12385                                                         return;
12386                                                 }
12387                                         }
12388
12389                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
12390                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
12391                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
12392                                                 // Todo: Remove this once we normalize enter behavior on IE
12393                                                 if (tinymce.isIE && keyCode == 13)
12394                                                         t.undoManager.beforeChange();
12395
12396                                                 if (t.undoManager.typing)
12397                                                         addUndo();
12398
12399                                                 return;
12400                                         }
12401
12402                                         // If key isn't shift,ctrl,alt,capslock,metakey
12403                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
12404                                                 t.undoManager.beforeChange();
12405                                                 t.undoManager.add();
12406                                                 t.undoManager.typing = true;
12407                                         }
12408                                 });
12409
12410                                 t.onMouseDown.add(function() {
12411                                         if (t.undoManager.typing)
12412                                                 addUndo();
12413                                 });
12414                         }
12415                         
12416                         // Bug fix for FireFox keeping styles from end of selection instead of start.
12417                         if (tinymce.isGecko) {
12418                                 function getAttributeApplyFunction() {
12419                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
12420
12421                                         return function() {
12422                                                 var target = t.selection.getStart();
12423                                                 t.dom.removeAllAttribs(target);
12424                                                 each(template, function(attr) {
12425                                                         target.setAttributeNode(attr.cloneNode(true));
12426                                                 });
12427                                         };
12428                                 }
12429
12430                                 function isSelectionAcrossElements() {
12431                                         var s = t.selection;
12432
12433                                         return !s.isCollapsed() && s.getStart() != s.getEnd();
12434                                 }
12435
12436                                 t.onKeyPress.add(function(ed, e) {
12437                                         var applyAttributes;
12438
12439                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
12440                                                 applyAttributes = getAttributeApplyFunction();
12441                                                 t.getDoc().execCommand('delete', false, null);
12442                                                 applyAttributes();
12443
12444                                                 return Event.cancel(e);
12445                                         }
12446                                 });
12447
12448                                 t.dom.bind(t.getDoc(), 'cut', function(e) {
12449                                         var applyAttributes;
12450
12451                                         if (isSelectionAcrossElements()) {
12452                                                 applyAttributes = getAttributeApplyFunction();
12453                                                 t.onKeyUp.addToTop(Event.cancel, Event);
12454
12455                                                 setTimeout(function() {
12456                                                         applyAttributes();
12457                                                         t.onKeyUp.remove(Event.cancel, Event);
12458                                                 }, 0);
12459                                         }
12460                                 });
12461                         }
12462                 },
12463
12464                 _isHidden : function() {
12465                         var s;
12466
12467                         if (!isGecko)
12468                                 return 0;
12469
12470                         // Weird, wheres that cursor selection?
12471                         s = this.selection.getSel();
12472                         return (!s || !s.rangeCount || s.rangeCount == 0);
12473                 }
12474         });
12475 })(tinymce);
12476
12477 (function(tinymce) {
12478         // Added for compression purposes
12479         var each = tinymce.each, undefined, TRUE = true, FALSE = false;
12480
12481         tinymce.EditorCommands = function(editor) {
12482                 var dom = editor.dom,
12483                         selection = editor.selection,
12484                         commands = {state: {}, exec : {}, value : {}},
12485                         settings = editor.settings,
12486                         bookmark;
12487
12488                 function execCommand(command, ui, value) {
12489                         var func;
12490
12491                         command = command.toLowerCase();
12492                         if (func = commands.exec[command]) {
12493                                 func(command, ui, value);
12494                                 return TRUE;
12495                         }
12496
12497                         return FALSE;
12498                 };
12499
12500                 function queryCommandState(command) {
12501                         var func;
12502
12503                         command = command.toLowerCase();
12504                         if (func = commands.state[command])
12505                                 return func(command);
12506
12507                         return -1;
12508                 };
12509
12510                 function queryCommandValue(command) {
12511                         var func;
12512
12513                         command = command.toLowerCase();
12514                         if (func = commands.value[command])
12515                                 return func(command);
12516
12517                         return FALSE;
12518                 };
12519
12520                 function addCommands(command_list, type) {
12521                         type = type || 'exec';
12522
12523                         each(command_list, function(callback, command) {
12524                                 each(command.toLowerCase().split(','), function(command) {
12525                                         commands[type][command] = callback;
12526                                 });
12527                         });
12528                 };
12529
12530                 // Expose public methods
12531                 tinymce.extend(this, {
12532                         execCommand : execCommand,
12533                         queryCommandState : queryCommandState,
12534                         queryCommandValue : queryCommandValue,
12535                         addCommands : addCommands
12536                 });
12537
12538                 // Private methods
12539
12540                 function execNativeCommand(command, ui, value) {
12541                         if (ui === undefined)
12542                                 ui = FALSE;
12543
12544                         if (value === undefined)
12545                                 value = null;
12546
12547                         return editor.getDoc().execCommand(command, ui, value);
12548                 };
12549
12550                 function isFormatMatch(name) {
12551                         return editor.formatter.match(name);
12552                 };
12553
12554                 function toggleFormat(name, value) {
12555                         editor.formatter.toggle(name, value ? {value : value} : undefined);
12556                 };
12557
12558                 function storeSelection(type) {
12559                         bookmark = selection.getBookmark(type);
12560                 };
12561
12562                 function restoreSelection() {
12563                         selection.moveToBookmark(bookmark);
12564                 };
12565
12566                 // Add execCommand overrides
12567                 addCommands({
12568                         // Ignore these, added for compatibility
12569                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},
12570
12571                         // Add undo manager logic
12572                         'mceEndUndoLevel,mceAddUndoLevel' : function() {
12573                                 editor.undoManager.add();
12574                         },
12575
12576                         'Cut,Copy,Paste' : function(command) {
12577                                 var doc = editor.getDoc(), failed;
12578
12579                                 // Try executing the native command
12580                                 try {
12581                                         execNativeCommand(command);
12582                                 } catch (ex) {
12583                                         // Command failed
12584                                         failed = TRUE;
12585                                 }
12586
12587                                 // Present alert message about clipboard access not being available
12588                                 if (failed || !doc.queryCommandSupported(command)) {
12589                                         if (tinymce.isGecko) {
12590                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
12591                                                         if (state)
12592                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
12593                                                 });
12594                                         } else
12595                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
12596                                 }
12597                         },
12598
12599                         // Override unlink command
12600                         unlink : function(command) {
12601                                 if (selection.isCollapsed())
12602                                         selection.select(selection.getNode());
12603
12604                                 execNativeCommand(command);
12605                                 selection.collapse(FALSE);
12606                         },
12607
12608                         // Override justify commands to use the text formatter engine
12609                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12610                                 var align = command.substring(7);
12611
12612                                 // Remove all other alignments first
12613                                 each('left,center,right,full'.split(','), function(name) {
12614                                         if (align != name)
12615                                                 editor.formatter.remove('align' + name);
12616                                 });
12617
12618                                 toggleFormat('align' + align);
12619                                 execCommand('mceRepaint');
12620                         },
12621
12622                         // Override list commands to fix WebKit bug
12623                         'InsertUnorderedList,InsertOrderedList' : function(command) {
12624                                 var listElm, listParent;
12625
12626                                 execNativeCommand(command);
12627
12628                                 // WebKit produces lists within block elements so we need to split them
12629                                 // we will replace the native list creation logic to custom logic later on
12630                                 // TODO: Remove this when the list creation logic is removed
12631                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
12632                                 if (listElm) {
12633                                         listParent = listElm.parentNode;
12634
12635                                         // If list is within a text block then split that block
12636                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
12637                                                 storeSelection();
12638                                                 dom.split(listParent, listElm);
12639                                                 restoreSelection();
12640                                         }
12641                                 }
12642                         },
12643
12644                         // Override commands to use the text formatter engine
12645                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12646                                 toggleFormat(command);
12647                         },
12648
12649                         // Override commands to use the text formatter engine
12650                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
12651                                 toggleFormat(command, value);
12652                         },
12653
12654                         FontSize : function(command, ui, value) {
12655                                 var fontClasses, fontSizes;
12656
12657                                 // Convert font size 1-7 to styles
12658                                 if (value >= 1 && value <= 7) {
12659                                         fontSizes = tinymce.explode(settings.font_size_style_values);
12660                                         fontClasses = tinymce.explode(settings.font_size_classes);
12661
12662                                         if (fontClasses)
12663                                                 value = fontClasses[value - 1] || value;
12664                                         else
12665                                                 value = fontSizes[value - 1] || value;
12666                                 }
12667
12668                                 toggleFormat(command, value);
12669                         },
12670
12671                         RemoveFormat : function(command) {
12672                                 editor.formatter.remove(command);
12673                         },
12674
12675                         mceBlockQuote : function(command) {
12676                                 toggleFormat('blockquote');
12677                         },
12678
12679                         FormatBlock : function(command, ui, value) {
12680                                 return toggleFormat(value || 'p');
12681                         },
12682
12683                         mceCleanup : function() {
12684                                 var bookmark = selection.getBookmark();
12685
12686                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
12687
12688                                 selection.moveToBookmark(bookmark);
12689                         },
12690
12691                         mceRemoveNode : function(command, ui, value) {
12692                                 var node = value || selection.getNode();
12693
12694                                 // Make sure that the body node isn't removed
12695                                 if (node != editor.getBody()) {
12696                                         storeSelection();
12697                                         editor.dom.remove(node, TRUE);
12698                                         restoreSelection();
12699                                 }
12700                         },
12701
12702                         mceSelectNodeDepth : function(command, ui, value) {
12703                                 var counter = 0;
12704
12705                                 dom.getParent(selection.getNode(), function(node) {
12706                                         if (node.nodeType == 1 && counter++ == value) {
12707                                                 selection.select(node);
12708                                                 return FALSE;
12709                                         }
12710                                 }, editor.getBody());
12711                         },
12712
12713                         mceSelectNode : function(command, ui, value) {
12714                                 selection.select(value);
12715                         },
12716
12717                         mceInsertContent : function(command, ui, value) {
12718                                 var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
12719
12720                                 function findSuitableCaretNode(node, root_node, next) {
12721                                         var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node);
12722
12723                                         while ((node = walker.current())) {
12724                                                 if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
12725                                                         return node;
12726
12727                                                 if (next)
12728                                                         walker.next();
12729                                                 else
12730                                                         walker.prev();
12731                                         }
12732                                 };
12733
12734                                 args = {content: value, format: 'html'};
12735                                 selection.onBeforeSetContent.dispatch(selection, args);
12736                                 value = args.content;
12737
12738                                 // Add caret at end of contents if it's missing
12739                                 if (value.indexOf('{$caret}') == -1)
12740                                         value += '{$caret}';
12741
12742                                 // Set the content at selection to a span and replace it's contents with the value
12743                                 selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
12744                                 dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
12745
12746                                 caretNode = dom.select('#__mce')[0];
12747                                 rootNode = dom.getRoot();
12748
12749                                 // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
12750                                 if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
12751                                         node = findSuitableCaretNode(caretNode, rootNode);
12752                                         if (node) {
12753                                                 if (node.nodeName == 'BR')
12754                                                         node.parentNode.insertBefore(caretNode, node);
12755                                                 else
12756                                                         dom.insertAfter(caretNode, node);
12757                                         }
12758                                 }
12759
12760                                 // Find caret root parent and clean it up using the serializer to avoid nesting
12761                                 while (caretNode) {
12762                                         if (caretNode === rootNode) {
12763                                                 // Clean up the parent element by parsing and serializing it
12764                                                 // This will remove invalid elements/attributes and fix nesting issues
12765                                                 dom.setOuterHTML(parent, 
12766                                                         new tinymce.html.Serializer({}, editor.schema).serialize(
12767                                                                 editor.parser.parse(dom.getOuterHTML(parent))
12768                                                         )
12769                                                 );
12770
12771                                                 break;
12772                                         }
12773
12774                                         parent = caretNode;
12775                                         caretNode = caretNode.parentNode;
12776                                 }
12777
12778                                 // Find caret after cleanup and move selection to that location
12779                                 caretNode = dom.select('#__mce')[0];
12780                                 if (caretNode) {
12781                                         node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true);
12782                                         dom.remove(caretNode);
12783
12784                                         if (node) {
12785                                                 rng = dom.createRng();
12786
12787                                                 if (node.nodeType == 3) {
12788                                                         rng.setStart(node, node.length);
12789                                                         rng.setEnd(node, node.length);
12790                                                 } else {
12791                                                         if (node.nodeName == 'BR') {
12792                                                                 rng.setStartBefore(node);
12793                                                                 rng.setEndBefore(node);
12794                                                         } else {
12795                                                                 rng.setStartAfter(node);
12796                                                                 rng.setEndAfter(node);
12797                                                         }
12798                                                 }
12799
12800                                                 selection.setRng(rng);
12801
12802                                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
12803                                                 if (!tinymce.isIE) {
12804                                                         node = dom.create('span', null, '\u00a0');
12805                                                         rng.insertNode(node);
12806                                                         nodeRect = dom.getRect(node);
12807                                                         viewPortRect = dom.getViewPort(editor.getWin());
12808
12809                                                         // Check if node is out side the viewport if it is then scroll to it
12810                                                         if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
12811                                                                 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
12812                                                                 editor.getBody().scrollLeft = nodeRect.x;
12813                                                                 editor.getBody().scrollTop = nodeRect.y;
12814                                                         }
12815
12816                                                         dom.remove(node);
12817                                                 }
12818
12819                                                 // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
12820                                                 // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
12821                                                 selection.collapse(true);
12822                                         }
12823                                 }
12824
12825                                 selection.onSetContent.dispatch(selection, args);
12826                                 editor.addVisual();
12827                         },
12828
12829                         mceInsertRawHTML : function(command, ui, value) {
12830                                 selection.setContent('tiny_mce_marker');
12831                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
12832                         },
12833
12834                         mceSetContent : function(command, ui, value) {
12835                                 editor.setContent(value);
12836                         },
12837
12838                         'Indent,Outdent' : function(command) {
12839                                 var intentValue, indentUnit, value;
12840
12841                                 // Setup indent level
12842                                 intentValue = settings.indentation;
12843                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
12844                                 intentValue = parseInt(intentValue);
12845
12846                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
12847                                         each(selection.getSelectedBlocks(), function(element) {
12848                                                 if (command == 'outdent') {
12849                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
12850                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
12851                                                 } else
12852                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
12853                                         });
12854                                 } else
12855                                         execNativeCommand(command);
12856                         },
12857
12858                         mceRepaint : function() {
12859                                 var bookmark;
12860
12861                                 if (tinymce.isGecko) {
12862                                         try {
12863                                                 storeSelection(TRUE);
12864
12865                                                 if (selection.getSel())
12866                                                         selection.getSel().selectAllChildren(editor.getBody());
12867
12868                                                 selection.collapse(TRUE);
12869                                                 restoreSelection();
12870                                         } catch (ex) {
12871                                                 // Ignore
12872                                         }
12873                                 }
12874                         },
12875
12876                         mceToggleFormat : function(command, ui, value) {
12877                                 editor.formatter.toggle(value);
12878                         },
12879
12880                         InsertHorizontalRule : function() {
12881                                 editor.execCommand('mceInsertContent', false, '<hr />');
12882                         },
12883
12884                         mceToggleVisualAid : function() {
12885                                 editor.hasVisual = !editor.hasVisual;
12886                                 editor.addVisual();
12887                         },
12888
12889                         mceReplaceContent : function(command, ui, value) {
12890                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
12891                         },
12892
12893                         mceInsertLink : function(command, ui, value) {
12894                                 var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
12895
12896                                 if (tinymce.is(value, 'string'))
12897                                         value = {href : value};
12898
12899                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
12900                                 value.href = value.href.replace(' ', '%20');
12901
12902                                 if (!link) {
12903                                         // WebKit can't create links on float images for some odd reason so just remove it and restore it later
12904                                         if (tinymce.isWebKit) {
12905                                                 img = dom.getParent(selection.getNode(), 'img');
12906
12907                                                 if (img) {
12908                                                         floatVal = img.style.cssFloat;
12909                                                         img.style.cssFloat = null;
12910                                                 }
12911                                         }
12912
12913                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
12914
12915                                         // Restore float value
12916                                         if (floatVal)
12917                                                 img.style.cssFloat = floatVal;
12918
12919                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
12920                                                 dom.setAttribs(link, value);
12921                                         });
12922                                 } else {
12923                                         if (value.href)
12924                                                 dom.setAttribs(link, value);
12925                                         else
12926                                                 editor.dom.remove(link, TRUE);
12927                                 }
12928                         },
12929                         
12930                         selectAll : function() {
12931                                 var root = dom.getRoot(), rng = dom.createRng();
12932
12933                                 rng.setStart(root, 0);
12934                                 rng.setEnd(root, root.childNodes.length);
12935
12936                                 editor.selection.setRng(rng);
12937                         }
12938                 });
12939
12940                 // Add queryCommandState overrides
12941                 addCommands({
12942                         // Override justify commands
12943                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12944                                 return isFormatMatch('align' + command.substring(7));
12945                         },
12946
12947                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12948                                 return isFormatMatch(command);
12949                         },
12950
12951                         mceBlockQuote : function() {
12952                                 return isFormatMatch('blockquote');
12953                         },
12954
12955                         Outdent : function() {
12956                                 var node;
12957
12958                                 if (settings.inline_styles) {
12959                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12960                                                 return TRUE;
12961
12962                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12963                                                 return TRUE;
12964                                 }
12965
12966                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
12967                         },
12968
12969                         'InsertUnorderedList,InsertOrderedList' : function(command) {
12970                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
12971                         }
12972                 }, 'state');
12973
12974                 // Add queryCommandValue overrides
12975                 addCommands({
12976                         'FontSize,FontName' : function(command) {
12977                                 var value = 0, parent;
12978
12979                                 if (parent = dom.getParent(selection.getNode(), 'span')) {
12980                                         if (command == 'fontsize')
12981                                                 value = parent.style.fontSize;
12982                                         else
12983                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
12984                                 }
12985
12986                                 return value;
12987                         }
12988                 }, 'value');
12989
12990                 // Add undo manager logic
12991                 if (settings.custom_undo_redo) {
12992                         addCommands({
12993                                 Undo : function() {
12994                                         editor.undoManager.undo();
12995                                 },
12996
12997                                 Redo : function() {
12998                                         editor.undoManager.redo();
12999                                 }
13000                         });
13001                 }
13002         };
13003 })(tinymce);
13004
13005 (function(tinymce) {
13006         var Dispatcher = tinymce.util.Dispatcher;
13007
13008         tinymce.UndoManager = function(editor) {
13009                 var self, index = 0, data = [];
13010
13011                 function getContent() {
13012                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
13013                 };
13014
13015                 return self = {
13016                         typing : false,
13017
13018                         onAdd : new Dispatcher(self),
13019
13020                         onUndo : new Dispatcher(self),
13021
13022                         onRedo : new Dispatcher(self),
13023
13024                         beforeChange : function() {
13025                                 // Set before bookmark on previous level
13026                                 if (data[index])
13027                                         data[index].beforeBookmark = editor.selection.getBookmark(2, true);
13028                         },
13029
13030                         add : function(level) {
13031                                 var i, settings = editor.settings, lastLevel;
13032
13033                                 level = level || {};
13034                                 level.content = getContent();
13035
13036                                 // Add undo level if needed
13037                                 lastLevel = data[index];
13038                                 if (lastLevel && lastLevel.content == level.content)
13039                                         return null;
13040
13041                                 // Time to compress
13042                                 if (settings.custom_undo_redo_levels) {
13043                                         if (data.length > settings.custom_undo_redo_levels) {
13044                                                 for (i = 0; i < data.length - 1; i++)
13045                                                         data[i] = data[i + 1];
13046
13047                                                 data.length--;
13048                                                 index = data.length;
13049                                         }
13050                                 }
13051
13052                                 // Get a non intrusive normalized bookmark
13053                                 level.bookmark = editor.selection.getBookmark(2, true);
13054
13055                                 // Crop array if needed
13056                                 if (index < data.length - 1)
13057                                         data.length = index + 1;
13058
13059                                 data.push(level);
13060                                 index = data.length - 1;
13061
13062                                 self.onAdd.dispatch(self, level);
13063                                 editor.isNotDirty = 0;
13064
13065                                 return level;
13066                         },
13067
13068                         undo : function() {
13069                                 var level, i;
13070
13071                                 if (self.typing) {
13072                                         self.add();
13073                                         self.typing = false;
13074                                 }
13075
13076                                 if (index > 0) {
13077                                         level = data[--index];
13078
13079                                         editor.setContent(level.content, {format : 'raw'});
13080                                         editor.selection.moveToBookmark(level.beforeBookmark);
13081
13082                                         self.onUndo.dispatch(self, level);
13083                                 }
13084
13085                                 return level;
13086                         },
13087
13088                         redo : function() {
13089                                 var level;
13090
13091                                 if (index < data.length - 1) {
13092                                         level = data[++index];
13093
13094                                         editor.setContent(level.content, {format : 'raw'});
13095                                         editor.selection.moveToBookmark(level.bookmark);
13096
13097                                         self.onRedo.dispatch(self, level);
13098                                 }
13099
13100                                 return level;
13101                         },
13102
13103                         clear : function() {
13104                                 data = [];
13105                                 index = 0;
13106                                 self.typing = false;
13107                         },
13108
13109                         hasUndo : function() {
13110                                 return index > 0 || this.typing;
13111                         },
13112
13113                         hasRedo : function() {
13114                                 return index < data.length - 1 && !this.typing;
13115                         }
13116                 };
13117         };
13118 })(tinymce);
13119
13120 (function(tinymce) {
13121         // Shorten names
13122         var Event = tinymce.dom.Event,
13123                 isIE = tinymce.isIE,
13124                 isGecko = tinymce.isGecko,
13125                 isOpera = tinymce.isOpera,
13126                 each = tinymce.each,
13127                 extend = tinymce.extend,
13128                 TRUE = true,
13129                 FALSE = false;
13130
13131         function cloneFormats(node) {
13132                 var clone, temp, inner;
13133
13134                 do {
13135                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
13136                                 if (clone) {
13137                                         temp = node.cloneNode(false);
13138                                         temp.appendChild(clone);
13139                                         clone = temp;
13140                                 } else {
13141                                         clone = inner = node.cloneNode(false);
13142                                 }
13143
13144                                 clone.removeAttribute('id');
13145                         }
13146                 } while (node = node.parentNode);
13147
13148                 if (clone)
13149                         return {wrapper : clone, inner : inner};
13150         };
13151
13152         // Checks if the selection/caret is at the end of the specified block element
13153         function isAtEnd(rng, par) {
13154                 var rng2 = par.ownerDocument.createRange();
13155
13156                 rng2.setStart(rng.endContainer, rng.endOffset);
13157                 rng2.setEndAfter(par);
13158
13159                 // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
13160                 return rng2.cloneContents().textContent.length == 0;
13161         };
13162
13163         function splitList(selection, dom, li) {
13164                 var listBlock, block;
13165
13166                 if (dom.isEmpty(li)) {
13167                         listBlock = dom.getParent(li, 'ul,ol');
13168
13169                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
13170                                 dom.split(listBlock, li);
13171                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
13172                                 dom.replace(block, li);
13173                                 selection.select(block, 1);
13174                         }
13175
13176                         return FALSE;
13177                 }
13178
13179                 return TRUE;
13180         };
13181
13182         tinymce.create('tinymce.ForceBlocks', {
13183                 ForceBlocks : function(ed) {
13184                         var t = this, s = ed.settings, elm;
13185
13186                         t.editor = ed;
13187                         t.dom = ed.dom;
13188                         elm = (s.forced_root_block || 'p').toLowerCase();
13189                         s.element = elm.toUpperCase();
13190
13191                         ed.onPreInit.add(t.setup, t);
13192
13193                         if (s.forced_root_block) {
13194                                 ed.onInit.add(t.forceRoots, t);
13195                                 ed.onSetContent.add(t.forceRoots, t);
13196                                 ed.onBeforeGetContent.add(t.forceRoots, t);
13197                                 ed.onExecCommand.add(function(ed, cmd) {
13198                                         if (cmd == 'mceInsertContent') {
13199                                                 t.forceRoots();
13200                                                 ed.nodeChanged();
13201                                         }
13202                                 });
13203                         }
13204                 },
13205
13206                 setup : function() {
13207                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
13208
13209                         // Force root blocks when typing and when getting output
13210                         if (s.forced_root_block) {
13211                                 ed.onBeforeExecCommand.add(t.forceRoots, t);
13212                                 ed.onKeyUp.add(t.forceRoots, t);
13213                                 ed.onPreProcess.add(t.forceRoots, t);
13214                         }
13215
13216                         if (s.force_br_newlines) {
13217                                 // Force IE to produce BRs on enter
13218                                 if (isIE) {
13219                                         ed.onKeyPress.add(function(ed, e) {
13220                                                 var n;
13221
13222                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
13223                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});
13224                                                         n = dom.get('__');
13225                                                         n.removeAttribute('id');
13226                                                         selection.select(n);
13227                                                         selection.collapse();
13228                                                         return Event.cancel(e);
13229                                                 }
13230                                         });
13231                                 }
13232                         }
13233
13234                         if (s.force_p_newlines) {
13235                                 if (!isIE) {
13236                                         ed.onKeyPress.add(function(ed, e) {
13237                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
13238                                                         Event.cancel(e);
13239                                         });
13240                                 } else {
13241                                         // Ungly hack to for IE to preserve the formatting when you press
13242                                         // enter at the end of a block element with formatted contents
13243                                         // This logic overrides the browsers default logic with
13244                                         // custom logic that enables us to control the output
13245                                         tinymce.addUnload(function() {
13246                                                 t._previousFormats = 0; // Fix IE leak
13247                                         });
13248
13249                                         ed.onKeyPress.add(function(ed, e) {
13250                                                 t._previousFormats = 0;
13251
13252                                                 // Clone the current formats, this will later be applied to the new block contents
13253                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
13254                                                         t._previousFormats = cloneFormats(ed.selection.getStart());
13255                                         });
13256
13257                                         ed.onKeyUp.add(function(ed, e) {
13258                                                 // Let IE break the element and the wrap the new caret location in the previous formats
13259                                                 if (e.keyCode == 13 && !e.shiftKey) {
13260                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;
13261
13262                                                         // Parent is an empty block
13263                                                         if (!parent.hasChildNodes() && fmt) {
13264                                                                 parent = dom.getParent(parent, dom.isBlock);
13265
13266                                                                 if (parent && parent.nodeName != 'LI') {
13267                                                                         parent.innerHTML = '';
13268
13269                                                                         if (t._previousFormats) {
13270                                                                                 parent.appendChild(fmt.wrapper);
13271                                                                                 fmt.inner.innerHTML = '\uFEFF';
13272                                                                         } else
13273                                                                                 parent.innerHTML = '\uFEFF';
13274
13275                                                                         selection.select(parent, 1);
13276                                                                         selection.collapse(true);
13277                                                                         ed.getDoc().execCommand('Delete', false, null);
13278                                                                         t._previousFormats = 0;
13279                                                                 }
13280                                                         }
13281                                                 }
13282                                         });
13283                                 }
13284
13285                                 if (isGecko) {
13286                                         ed.onKeyDown.add(function(ed, e) {
13287                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
13288                                                         t.backspaceDelete(e, e.keyCode == 8);
13289                                         });
13290                                 }
13291                         }
13292
13293                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
13294                         if (tinymce.isWebKit) {
13295                                 function insertBr(ed) {
13296                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
13297
13298                                         // Insert BR element
13299                                         rng.insertNode(br = dom.create('br'));
13300
13301                                         // Place caret after BR
13302                                         rng.setStartAfter(br);
13303                                         rng.setEndAfter(br);
13304                                         selection.setRng(rng);
13305
13306                                         // Could not place caret after BR then insert an nbsp entity and move the caret
13307                                         if (selection.getSel().focusNode == br.previousSibling) {
13308                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
13309                                                 selection.collapse(TRUE);
13310                                         }
13311
13312                                         // Create a temporary DIV after the BR and get the position as it
13313                                         // seems like getPos() returns 0 for text nodes and BR elements.
13314                                         dom.insertAfter(div, br);
13315                                         divYPos = dom.getPos(div).y;
13316                                         dom.remove(div);
13317
13318                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
13319                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
13320                                                 ed.getWin().scrollTo(0, divYPos);
13321                                 };
13322
13323                                 ed.onKeyPress.add(function(ed, e) {
13324                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
13325                                                 insertBr(ed);
13326                                                 Event.cancel(e);
13327                                         }
13328                                 });
13329                         }
13330
13331                         // IE specific fixes
13332                         if (isIE) {
13333                                 // Replaces IE:s auto generated paragraphs with the specified element name
13334                                 if (s.element != 'P') {
13335                                         ed.onKeyPress.add(function(ed, e) {
13336                                                 t.lastElm = selection.getNode().nodeName;
13337                                         });
13338
13339                                         ed.onKeyUp.add(function(ed, e) {
13340                                                 var bl, n = selection.getNode(), b = ed.getBody();
13341
13342                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {
13343                                                         n = dom.rename(n, s.element);
13344                                                         selection.select(n);
13345                                                         selection.collapse();
13346                                                         ed.nodeChanged();
13347                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
13348                                                         bl = dom.getParent(n, 'p');
13349
13350                                                         if (bl) {
13351                                                                 dom.rename(bl, s.element);
13352                                                                 ed.nodeChanged();
13353                                                         }
13354                                                 }
13355                                         });
13356                                 }
13357                         }
13358                 },
13359
13360                 find : function(n, t, s) {
13361                         var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
13362
13363                         while (n = w.nextNode()) {
13364                                 c++;
13365
13366                                 // Index by node
13367                                 if (t == 0 && n == s)
13368                                         return c;
13369
13370                                 // Node by index
13371                                 if (t == 1 && c == s)
13372                                         return n;
13373                         }
13374
13375                         return -1;
13376                 },
13377
13378                 forceRoots : function(ed, e) {
13379                         var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
13380                         var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
13381
13382                         // Fix for bug #1863847
13383                         //if (e && e.keyCode == 13)
13384                         //      return TRUE;
13385
13386                         // Wrap non blocks into blocks
13387                         for (i = nl.length - 1; i >= 0; i--) {
13388                                 nx = nl[i];
13389
13390                                 // Ignore internal elements
13391                                 if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
13392                                         bl = null;
13393                                         continue;
13394                                 }
13395
13396                                 // Is text or non block element
13397                                 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
13398                                         if (!bl) {
13399                                                 // Create new block but ignore whitespace
13400                                                 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
13401                                                         // Store selection
13402                                                         if (si == -2 && r) {
13403                                                                 if (!isIE || r.setStart) {
13404                                                                         // If selection is element then mark it
13405                                                                         if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
13406                                                                                 // Save the id of the selected element
13407                                                                                 eid = n.getAttribute("id");
13408                                                                                 n.setAttribute("id", "__mce");
13409                                                                         } else {
13410                                                                                 // If element is inside body, might not be the case in contentEdiable mode
13411                                                                                 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
13412                                                                                         so = r.startOffset;
13413                                                                                         eo = r.endOffset;
13414                                                                                         si = t.find(b, 0, r.startContainer);
13415                                                                                         ei = t.find(b, 0, r.endContainer);
13416                                                                                 }
13417                                                                         }
13418                                                                 } else {
13419                                                                         // Force control range into text range
13420                                                                         if (r.item) {
13421                                                                                 tr = d.body.createTextRange();
13422                                                                                 tr.moveToElementText(r.item(0));
13423                                                                                 r = tr;
13424                                                                         }
13425
13426                                                                         tr = d.body.createTextRange();
13427                                                                         tr.moveToElementText(b);
13428                                                                         tr.collapse(1);
13429                                                                         bp = tr.move('character', c) * -1;
13430
13431                                                                         tr = r.duplicate();
13432                                                                         tr.collapse(1);
13433                                                                         sp = tr.move('character', c) * -1;
13434
13435                                                                         tr = r.duplicate();
13436                                                                         tr.collapse(0);
13437                                                                         le = (tr.move('character', c) * -1) - sp;
13438
13439                                                                         si = sp - bp;
13440                                                                         ei = le;
13441                                                                 }
13442                                                         }
13443
13444                                                         // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
13445                                                         // See: http://support.microsoft.com/kb/829907
13446                                                         bl = ed.dom.create(ed.settings.forced_root_block);
13447                                                         nx.parentNode.replaceChild(bl, nx);
13448                                                         bl.appendChild(nx);
13449                                                 }
13450                                         } else {
13451                                                 if (bl.hasChildNodes())
13452                                                         bl.insertBefore(nx, bl.firstChild);
13453                                                 else
13454                                                         bl.appendChild(nx);
13455                                         }
13456                                 } else
13457                                         bl = null; // Time to create new block
13458                         }
13459
13460                         // Restore selection
13461                         if (si != -2) {
13462                                 if (!isIE || r.setStart) {
13463                                         bl = b.getElementsByTagName(ed.settings.element)[0];
13464                                         r = d.createRange();
13465
13466                                         // Select last location or generated block
13467                                         if (si != -1)
13468                                                 r.setStart(t.find(b, 1, si), so);
13469                                         else
13470                                                 r.setStart(bl, 0);
13471
13472                                         // Select last location or generated block
13473                                         if (ei != -1)
13474                                                 r.setEnd(t.find(b, 1, ei), eo);
13475                                         else
13476                                                 r.setEnd(bl, 0);
13477
13478                                         if (s) {
13479                                                 s.removeAllRanges();
13480                                                 s.addRange(r);
13481                                         }
13482                                 } else {
13483                                         try {
13484                                                 r = s.createRange();
13485                                                 r.moveToElementText(b);
13486                                                 r.collapse(1);
13487                                                 r.moveStart('character', si);
13488                                                 r.moveEnd('character', ei);
13489                                                 r.select();
13490                                         } catch (ex) {
13491                                                 // Ignore
13492                                         }
13493                                 }
13494                         } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
13495                                 // Restore the id of the selected element
13496                                 if (eid)
13497                                         n.setAttribute('id', eid);
13498                                 else
13499                                         n.removeAttribute('id');
13500
13501                                 // Move caret before selected element
13502                                 r = d.createRange();
13503                                 r.setStartBefore(n);
13504                                 r.setEndBefore(n);
13505                                 se.setRng(r);
13506                         }
13507                 },
13508
13509                 getParentBlock : function(n) {
13510                         var d = this.dom;
13511
13512                         return d.getParent(n, d.isBlock);
13513                 },
13514
13515                 insertPara : function(e) {
13516                         var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
13517                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
13518
13519                         ed.undoManager.beforeChange();
13520
13521                         // If root blocks are forced then use Operas default behavior since it's really good
13522 // Removed due to bug: #1853816
13523 //                      if (se.forced_root_block && isOpera)
13524 //                              return TRUE;
13525
13526                         // Setup before range
13527                         rb = d.createRange();
13528
13529                         // If is before the first block element and in body, then move it into first block element
13530                         rb.setStart(s.anchorNode, s.anchorOffset);
13531                         rb.collapse(TRUE);
13532
13533                         // Setup after range
13534                         ra = d.createRange();
13535
13536                         // If is before the first block element and in body, then move it into first block element
13537                         ra.setStart(s.focusNode, s.focusOffset);
13538                         ra.collapse(TRUE);
13539
13540                         // Setup start/end points
13541                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
13542                         sn = dir ? s.anchorNode : s.focusNode;
13543                         so = dir ? s.anchorOffset : s.focusOffset;
13544                         en = dir ? s.focusNode : s.anchorNode;
13545                         eo = dir ? s.focusOffset : s.anchorOffset;
13546
13547                         // If selection is in empty table cell
13548                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
13549                                 if (sn.firstChild.nodeName == 'BR')
13550                                         dom.remove(sn.firstChild); // Remove BR
13551
13552                                 // Create two new block elements
13553                                 if (sn.childNodes.length == 0) {
13554                                         ed.dom.add(sn, se.element, null, '<br />');
13555                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13556                                 } else {
13557                                         n = sn.innerHTML;
13558                                         sn.innerHTML = '';
13559                                         ed.dom.add(sn, se.element, null, n);
13560                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13561                                 }
13562
13563                                 // Move caret into the last one
13564                                 r = d.createRange();
13565                                 r.selectNodeContents(aft);
13566                                 r.collapse(1);
13567                                 ed.selection.setRng(r);
13568
13569                                 return FALSE;
13570                         }
13571
13572                         // If the caret is in an invalid location in FF we need to move it into the first block
13573                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
13574                                 sn = en = sn.firstChild;
13575                                 so = eo = 0;
13576                                 rb = d.createRange();
13577                                 rb.setStart(sn, 0);
13578                                 ra = d.createRange();
13579                                 ra.setStart(en, 0);
13580                         }
13581
13582                         // Never use body as start or end node
13583                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13584                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
13585                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13586                         en = en.nodeName == "BODY" ? en.firstChild : en;
13587
13588                         // Get start and end blocks
13589                         sb = t.getParentBlock(sn);
13590                         eb = t.getParentBlock(en);
13591                         bn = sb ? sb.nodeName : se.element; // Get block name to create
13592
13593                         // Return inside list use default browser behavior
13594                         if (n = t.dom.getParent(sb, 'li,pre')) {
13595                                 if (n.nodeName == 'LI')
13596                                         return splitList(ed.selection, t.dom, n);
13597
13598                                 return TRUE;
13599                         }
13600
13601                         // If caption or absolute layers then always generate new blocks within
13602                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13603                                 bn = se.element;
13604                                 sb = null;
13605                         }
13606
13607                         // If caption or absolute layers then always generate new blocks within
13608                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13609                                 bn = se.element;
13610                                 eb = null;
13611                         }
13612
13613                         // Use P instead
13614                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
13615                                 bn = se.element;
13616                                 sb = eb = null;
13617                         }
13618
13619                         // Setup new before and after blocks
13620                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
13621                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
13622
13623                         // Remove id from after clone
13624                         aft.removeAttribute('id');
13625
13626                         // Is header and cursor is at the end, then force paragraph under
13627                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
13628                                 aft = ed.dom.create(se.element);
13629
13630                         // Find start chop node
13631                         n = sc = sn;
13632                         do {
13633                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13634                                         break;
13635
13636                                 sc = n;
13637                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
13638
13639                         // Find end chop node
13640                         n = ec = en;
13641                         do {
13642                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13643                                         break;
13644
13645                                 ec = n;
13646                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
13647
13648                         // Place first chop part into before block element
13649                         if (sc.nodeName == bn)
13650                                 rb.setStart(sc, 0);
13651                         else
13652                                 rb.setStartBefore(sc);
13653
13654                         rb.setEnd(sn, so);
13655                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13656
13657                         // Place secnd chop part within new block element
13658                         try {
13659                                 ra.setEndAfter(ec);
13660                         } catch(ex) {
13661                                 //console.debug(s.focusNode, s.focusOffset);
13662                         }
13663
13664                         ra.setStart(en, eo);
13665                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13666
13667                         // Create range around everything
13668                         r = d.createRange();
13669                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
13670                                 r.setStartBefore(sc.parentNode);
13671                         } else {
13672                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
13673                                         r.setStartBefore(rb.startContainer);
13674                                 else
13675                                         r.setStart(rb.startContainer, rb.startOffset);
13676                         }
13677
13678                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)
13679                                 r.setEndAfter(ec.parentNode);
13680                         else
13681                                 r.setEnd(ra.endContainer, ra.endOffset);
13682
13683                         // Delete and replace it with new block elements
13684                         r.deleteContents();
13685
13686                         if (isOpera)
13687                                 ed.getWin().scrollTo(0, vp.y);
13688
13689                         // Never wrap blocks in blocks
13690                         if (bef.firstChild && bef.firstChild.nodeName == bn)
13691                                 bef.innerHTML = bef.firstChild.innerHTML;
13692
13693                         if (aft.firstChild && aft.firstChild.nodeName == bn)
13694                                 aft.innerHTML = aft.firstChild.innerHTML;
13695
13696                         // Padd empty blocks
13697                         if (dom.isEmpty(bef))
13698                                 bef.innerHTML = '<br />';
13699
13700                         function appendStyles(e, en) {
13701                                 var nl = [], nn, n, i;
13702
13703                                 e.innerHTML = '';
13704
13705                                 // Make clones of style elements
13706                                 if (se.keep_styles) {
13707                                         n = en;
13708                                         do {
13709                                                 // We only want style specific elements
13710                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
13711                                                         nn = n.cloneNode(FALSE);
13712                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
13713                                                         nl.push(nn);
13714                                                 }
13715                                         } while (n = n.parentNode);
13716                                 }
13717
13718                                 // Append style elements to aft
13719                                 if (nl.length > 0) {
13720                                         for (i = nl.length - 1, nn = e; i >= 0; i--)
13721                                                 nn = nn.appendChild(nl[i]);
13722
13723                                         // Padd most inner style element
13724                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13725                                         return nl[0]; // Move caret to most inner element
13726                                 } else
13727                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13728                         };
13729
13730                         // Fill empty afterblook with current style
13731                         if (dom.isEmpty(aft))
13732                                 car = appendStyles(aft, en);
13733
13734                         // Opera needs this one backwards for older versions
13735                         if (isOpera && parseFloat(opera.version()) < 9.5) {
13736                                 r.insertNode(bef);
13737                                 r.insertNode(aft);
13738                         } else {
13739                                 r.insertNode(aft);
13740                                 r.insertNode(bef);
13741                         }
13742
13743                         // Normalize
13744                         aft.normalize();
13745                         bef.normalize();
13746
13747                         function first(n) {
13748                                 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
13749                         };
13750
13751                         // Move cursor and scroll into view
13752                         r = d.createRange();
13753                         r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
13754                         r.collapse(1);
13755                         s.removeAllRanges();
13756                         s.addRange(r);
13757
13758                         // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
13759                         y = ed.dom.getPos(aft).y;
13760                         //ch = aft.clientHeight;
13761
13762                         // Is element within viewport
13763                         if (y < vp.y || y + 25 > vp.y + vp.h) {
13764                                 ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
13765
13766                                 /*console.debug(
13767                                         'Element: y=' + y + ', h=' + ch + ', ' +
13768                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
13769                                 );*/
13770                         }
13771
13772                         ed.undoManager.add();
13773
13774                         return FALSE;
13775                 },
13776
13777                 backspaceDelete : function(e, bs) {
13778                         var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
13779
13780                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
13781                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
13782                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
13783
13784                                 // Walk the dom backwards until we find a text node
13785                                 for (n = sc.lastChild; n; n = walker.prev()) {
13786                                         if (n.nodeType == 3) {
13787                                                 r.setStart(n, n.nodeValue.length);
13788                                                 r.collapse(true);
13789                                                 se.setRng(r);
13790                                                 return;
13791                                         }
13792                                 }
13793                         }
13794
13795                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
13796                         // This workaround removes the element by hand and moves the caret to the previous element
13797                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
13798                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
13799                                         // Find previous block element
13800                                         n = sc;
13801                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
13802
13803                                         if (n) {
13804                                                 if (sc != b.firstChild) {
13805                                                         // Find last text node
13806                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
13807                                                         while (tn = w.nextNode())
13808                                                                 n = tn;
13809
13810                                                         // Place caret at the end of last text node
13811                                                         r = ed.getDoc().createRange();
13812                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
13813                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
13814                                                         se.setRng(r);
13815
13816                                                         // Remove the target container
13817                                                         ed.dom.remove(sc);
13818                                                 }
13819
13820                                                 return Event.cancel(e);
13821                                         }
13822                                 }
13823                         }
13824                 }
13825         });
13826 })(tinymce);
13827
13828 (function(tinymce) {
13829         // Shorten names
13830         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
13831
13832         tinymce.create('tinymce.ControlManager', {
13833                 ControlManager : function(ed, s) {
13834                         var t = this, i;
13835
13836                         s = s || {};
13837                         t.editor = ed;
13838                         t.controls = {};
13839                         t.onAdd = new tinymce.util.Dispatcher(t);
13840                         t.onPostRender = new tinymce.util.Dispatcher(t);
13841                         t.prefix = s.prefix || ed.id + '_';
13842                         t._cls = {};
13843
13844                         t.onPostRender.add(function() {
13845                                 each(t.controls, function(c) {
13846                                         c.postRender();
13847                                 });
13848                         });
13849                 },
13850
13851                 get : function(id) {
13852                         return this.controls[this.prefix + id] || this.controls[id];
13853                 },
13854
13855                 setActive : function(id, s) {
13856                         var c = null;
13857
13858                         if (c = this.get(id))
13859                                 c.setActive(s);
13860
13861                         return c;
13862                 },
13863
13864                 setDisabled : function(id, s) {
13865                         var c = null;
13866
13867                         if (c = this.get(id))
13868                                 c.setDisabled(s);
13869
13870                         return c;
13871                 },
13872
13873                 add : function(c) {
13874                         var t = this;
13875
13876                         if (c) {
13877                                 t.controls[c.id] = c;
13878                                 t.onAdd.dispatch(c, t);
13879                         }
13880
13881                         return c;
13882                 },
13883
13884                 createControl : function(n) {
13885                         var c, t = this, ed = t.editor;
13886
13887                         each(ed.plugins, function(p) {
13888                                 if (p.createControl) {
13889                                         c = p.createControl(n, t);
13890
13891                                         if (c)
13892                                                 return false;
13893                                 }
13894                         });
13895
13896                         switch (n) {
13897                                 case "|":
13898                                 case "separator":
13899                                         return t.createSeparator();
13900                         }
13901
13902                         if (!c && ed.buttons && (c = ed.buttons[n]))
13903                                 return t.createButton(n, c);
13904
13905                         return t.add(c);
13906                 },
13907
13908                 createDropMenu : function(id, s, cc) {
13909                         var t = this, ed = t.editor, c, bm, v, cls;
13910
13911                         s = extend({
13912                                 'class' : 'mceDropDown',
13913                                 constrain : ed.settings.constrain_menus
13914                         }, s);
13915
13916                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
13917                         if (v = ed.getParam('skin_variant'))
13918                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
13919
13920                         id = t.prefix + id;
13921                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
13922                         c = t.controls[id] = new cls(id, s);
13923                         c.onAddItem.add(function(c, o) {
13924                                 var s = o.settings;
13925
13926                                 s.title = ed.getLang(s.title, s.title);
13927
13928                                 if (!s.onclick) {
13929                                         s.onclick = function(v) {
13930                                                 if (s.cmd)
13931                                                         ed.execCommand(s.cmd, s.ui || false, s.value);
13932                                         };
13933                                 }
13934                         });
13935
13936                         ed.onRemove.add(function() {
13937                                 c.destroy();
13938                         });
13939
13940                         // Fix for bug #1897785, #1898007
13941                         if (tinymce.isIE) {
13942                                 c.onShowMenu.add(function() {
13943                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
13944                                         ed.focus();
13945
13946                                         bm = ed.selection.getBookmark(1);
13947                                 });
13948
13949                                 c.onHideMenu.add(function() {
13950                                         if (bm) {
13951                                                 ed.selection.moveToBookmark(bm);
13952                                                 bm = 0;
13953                                         }
13954                                 });
13955                         }
13956
13957                         return t.add(c);
13958                 },
13959
13960                 createListBox : function(id, s, cc) {
13961                         var t = this, ed = t.editor, cmd, c, cls;
13962
13963                         if (t.get(id))
13964                                 return null;
13965
13966                         s.title = ed.translate(s.title);
13967                         s.scope = s.scope || ed;
13968
13969                         if (!s.onselect) {
13970                                 s.onselect = function(v) {
13971                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13972                                 };
13973                         }
13974
13975                         s = extend({
13976                                 title : s.title,
13977                                 'class' : 'mce_' + id,
13978                                 scope : s.scope,
13979                                 control_manager : t
13980                         }, s);
13981
13982                         id = t.prefix + id;
13983
13984                         if (ed.settings.use_native_selects)
13985                                 c = new tinymce.ui.NativeListBox(id, s);
13986                         else {
13987                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
13988                                 c = new cls(id, s, ed);
13989                         }
13990
13991                         t.controls[id] = c;
13992
13993                         // Fix focus problem in Safari
13994                         if (tinymce.isWebKit) {
13995                                 c.onPostRender.add(function(c, n) {
13996                                         // Store bookmark on mousedown
13997                                         Event.add(n, 'mousedown', function() {
13998                                                 ed.bookmark = ed.selection.getBookmark(1);
13999                                         });
14000
14001                                         // Restore on focus, since it might be lost
14002                                         Event.add(n, 'focus', function() {
14003                                                 ed.selection.moveToBookmark(ed.bookmark);
14004                                                 ed.bookmark = null;
14005                                         });
14006                                 });
14007                         }
14008
14009                         if (c.hideMenu)
14010                                 ed.onMouseDown.add(c.hideMenu, c);
14011
14012                         return t.add(c);
14013                 },
14014
14015                 createButton : function(id, s, cc) {
14016                         var t = this, ed = t.editor, o, c, cls;
14017
14018                         if (t.get(id))
14019                                 return null;
14020
14021                         s.title = ed.translate(s.title);
14022                         s.label = ed.translate(s.label);
14023                         s.scope = s.scope || ed;
14024
14025                         if (!s.onclick && !s.menu_button) {
14026                                 s.onclick = function() {
14027                                         ed.execCommand(s.cmd, s.ui || false, s.value);
14028                                 };
14029                         }
14030
14031                         s = extend({
14032                                 title : s.title,
14033                                 'class' : 'mce_' + id,
14034                                 unavailable_prefix : ed.getLang('unavailable', ''),
14035                                 scope : s.scope,
14036                                 control_manager : t
14037                         }, s);
14038
14039                         id = t.prefix + id;
14040
14041                         if (s.menu_button) {
14042                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
14043                                 c = new cls(id, s, ed);
14044                                 ed.onMouseDown.add(c.hideMenu, c);
14045                         } else {
14046                                 cls = t._cls.button || tinymce.ui.Button;
14047                                 c = new cls(id, s);
14048                         }
14049
14050                         return t.add(c);
14051                 },
14052
14053                 createMenuButton : function(id, s, cc) {
14054                         s = s || {};
14055                         s.menu_button = 1;
14056
14057                         return this.createButton(id, s, cc);
14058                 },
14059
14060                 createSplitButton : function(id, s, cc) {
14061                         var t = this, ed = t.editor, cmd, c, cls;
14062
14063                         if (t.get(id))
14064                                 return null;
14065
14066                         s.title = ed.translate(s.title);
14067                         s.scope = s.scope || ed;
14068
14069                         if (!s.onclick) {
14070                                 s.onclick = function(v) {
14071                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14072                                 };
14073                         }
14074
14075                         if (!s.onselect) {
14076                                 s.onselect = function(v) {
14077                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14078                                 };
14079                         }
14080
14081                         s = extend({
14082                                 title : s.title,
14083                                 'class' : 'mce_' + id,
14084                                 scope : s.scope,
14085                                 control_manager : t
14086                         }, s);
14087
14088                         id = t.prefix + id;
14089                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
14090                         c = t.add(new cls(id, s, ed));
14091                         ed.onMouseDown.add(c.hideMenu, c);
14092
14093                         return c;
14094                 },
14095
14096                 createColorSplitButton : function(id, s, cc) {
14097                         var t = this, ed = t.editor, cmd, c, cls, bm;
14098
14099                         if (t.get(id))
14100                                 return null;
14101
14102                         s.title = ed.translate(s.title);
14103                         s.scope = s.scope || ed;
14104
14105                         if (!s.onclick) {
14106                                 s.onclick = function(v) {
14107                                         if (tinymce.isIE)
14108                                                 bm = ed.selection.getBookmark(1);
14109
14110                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14111                                 };
14112                         }
14113
14114                         if (!s.onselect) {
14115                                 s.onselect = function(v) {
14116                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14117                                 };
14118                         }
14119
14120                         s = extend({
14121                                 title : s.title,
14122                                 'class' : 'mce_' + id,
14123                                 'menu_class' : ed.getParam('skin') + 'Skin',
14124                                 scope : s.scope,
14125                                 more_colors_title : ed.getLang('more_colors')
14126                         }, s);
14127
14128                         id = t.prefix + id;
14129                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
14130                         c = new cls(id, s, ed);
14131                         ed.onMouseDown.add(c.hideMenu, c);
14132
14133                         // Remove the menu element when the editor is removed
14134                         ed.onRemove.add(function() {
14135                                 c.destroy();
14136                         });
14137
14138                         // Fix for bug #1897785, #1898007
14139                         if (tinymce.isIE) {
14140                                 c.onShowMenu.add(function() {
14141                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
14142                                         ed.focus();
14143                                         bm = ed.selection.getBookmark(1);
14144                                 });
14145
14146                                 c.onHideMenu.add(function() {
14147                                         if (bm) {
14148                                                 ed.selection.moveToBookmark(bm);
14149                                                 bm = 0;
14150                                         }
14151                                 });
14152                         }
14153
14154                         return t.add(c);
14155                 },
14156
14157                 createToolbar : function(id, s, cc) {
14158                         var c, t = this, cls;
14159
14160                         id = t.prefix + id;
14161                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
14162                         c = new cls(id, s, t.editor);
14163
14164                         if (t.get(id))
14165                                 return null;
14166
14167                         return t.add(c);
14168                 },
14169                 
14170                 createToolbarGroup : function(id, s, cc) {
14171                         var c, t = this, cls;
14172                         id = t.prefix + id;
14173                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14174                         c = new cls(id, s, t.editor);
14175                         
14176                         if (t.get(id))
14177                                 return null;
14178                         
14179                         return t.add(c);
14180                 },
14181
14182                 createSeparator : function(cc) {
14183                         var cls = cc || this._cls.separator || tinymce.ui.Separator;
14184
14185                         return new cls();
14186                 },
14187
14188                 setControlType : function(n, c) {
14189                         return this._cls[n.toLowerCase()] = c;
14190                 },
14191         
14192                 destroy : function() {
14193                         each(this.controls, function(c) {
14194                                 c.destroy();
14195                         });
14196
14197                         this.controls = null;
14198                 }
14199         });
14200 })(tinymce);
14201
14202 (function(tinymce) {
14203         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
14204
14205         tinymce.create('tinymce.WindowManager', {
14206                 WindowManager : function(ed) {
14207                         var t = this;
14208
14209                         t.editor = ed;
14210                         t.onOpen = new Dispatcher(t);
14211                         t.onClose = new Dispatcher(t);
14212                         t.params = {};
14213                         t.features = {};
14214                 },
14215
14216                 open : function(s, p) {
14217                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14218
14219                         // Default some options
14220                         s = s || {};
14221                         p = p || {};
14222                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14223                         sh = isOpera ? vp.h : screen.height;
14224                         s.name = s.name || 'mc_' + new Date().getTime();
14225                         s.width = parseInt(s.width || 320);
14226                         s.height = parseInt(s.height || 240);
14227                         s.resizable = true;
14228                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14229                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14230                         p.inline = false;
14231                         p.mce_width = s.width;
14232                         p.mce_height = s.height;
14233                         p.mce_auto_focus = s.auto_focus;
14234
14235                         if (mo) {
14236                                 if (isIE) {
14237                                         s.center = true;
14238                                         s.help = false;
14239                                         s.dialogWidth = s.width + 'px';
14240                                         s.dialogHeight = s.height + 'px';
14241                                         s.scroll = s.scrollbars || false;
14242                                 }
14243                         }
14244
14245                         // Build features string
14246                         each(s, function(v, k) {
14247                                 if (tinymce.is(v, 'boolean'))
14248                                         v = v ? 'yes' : 'no';
14249
14250                                 if (!/^(name|url)$/.test(k)) {
14251                                         if (isIE && mo)
14252                                                 f += (f ? ';' : '') + k + ':' + v;
14253                                         else
14254                                                 f += (f ? ',' : '') + k + '=' + v;
14255                                 }
14256                         });
14257
14258                         t.features = s;
14259                         t.params = p;
14260                         t.onOpen.dispatch(t, s, p);
14261
14262                         u = s.url || s.file;
14263                         u = tinymce._addVer(u);
14264
14265                         try {
14266                                 if (isIE && mo) {
14267                                         w = 1;
14268                                         window.showModalDialog(u, window, f);
14269                                 } else
14270                                         w = window.open(u, s.name, f);
14271                         } catch (ex) {
14272                                 // Ignore
14273                         }
14274
14275                         if (!w)
14276                                 alert(t.editor.getLang('popup_blocked'));
14277                 },
14278
14279                 close : function(w) {
14280                         w.close();
14281                         this.onClose.dispatch(this);
14282                 },
14283
14284                 createInstance : function(cl, a, b, c, d, e) {
14285                         var f = tinymce.resolve(cl);
14286
14287                         return new f(a, b, c, d, e);
14288                 },
14289
14290                 confirm : function(t, cb, s, w) {
14291                         w = w || window;
14292
14293                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
14294                 },
14295
14296                 alert : function(tx, cb, s, w) {
14297                         var t = this;
14298
14299                         w = w || window;
14300                         w.alert(t._decode(t.editor.getLang(tx, tx)));
14301
14302                         if (cb)
14303                                 cb.call(s || t);
14304                 },
14305
14306                 resizeBy : function(dw, dh, win) {
14307                         win.resizeBy(dw, dh);
14308                 },
14309
14310                 // Internal functions
14311
14312                 _decode : function(s) {
14313                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14314                 }
14315         });
14316 }(tinymce));
14317 (function(tinymce) {
14318         tinymce.Formatter = function(ed) {
14319                 var formats = {},
14320                         each = tinymce.each,
14321                         dom = ed.dom,
14322                         selection = ed.selection,
14323                         TreeWalker = tinymce.dom.TreeWalker,
14324                         rangeUtils = new tinymce.dom.RangeUtils(dom),
14325                         isValid = ed.schema.isValidChild,
14326                         isBlock = dom.isBlock,
14327                         forcedRootBlock = ed.settings.forced_root_block,
14328                         nodeIndex = dom.nodeIndex,
14329                         INVISIBLE_CHAR = '\uFEFF',
14330                         MCE_ATTR_RE = /^(src|href|style)$/,
14331                         FALSE = false,
14332                         TRUE = true,
14333                         undefined,
14334                         pendingFormats = {apply : [], remove : []};
14335
14336                 function isArray(obj) {
14337                         return obj instanceof Array;
14338                 };
14339
14340                 function getParents(node, selector) {
14341                         return dom.getParents(node, selector, dom.getRoot());
14342                 };
14343
14344                 function isCaretNode(node) {
14345                         return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
14346                 };
14347
14348                 // Public functions
14349
14350                 function get(name) {
14351                         return name ? formats[name] : formats;
14352                 };
14353
14354                 function register(name, format) {
14355                         if (name) {
14356                                 if (typeof(name) !== 'string') {
14357                                         each(name, function(format, name) {
14358                                                 register(name, format);
14359                                         });
14360                                 } else {
14361                                         // Force format into array and add it to internal collection
14362                                         format = format.length ? format : [format];
14363
14364                                         each(format, function(format) {
14365                                                 // Set deep to false by default on selector formats this to avoid removing
14366                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs
14367                                                 if (format.deep === undefined)
14368                                                         format.deep = !format.selector;
14369
14370                                                 // Default to true
14371                                                 if (format.split === undefined)
14372                                                         format.split = !format.selector || format.inline;
14373
14374                                                 // Default to true
14375                                                 if (format.remove === undefined && format.selector && !format.inline)
14376                                                         format.remove = 'none';
14377
14378                                                 // Mark format as a mixed format inline + block level
14379                                                 if (format.selector && format.inline) {
14380                                                         format.mixed = true;
14381                                                         format.block_expand = true;
14382                                                 }
14383
14384                                                 // Split classes if needed
14385                                                 if (typeof(format.classes) === 'string')
14386                                                         format.classes = format.classes.split(/\s+/);
14387                                         });
14388
14389                                         formats[name] = format;
14390                                 }
14391                         }
14392                 };
14393
14394                 var getTextDecoration = function(node) {
14395                         var decoration;
14396
14397                         ed.dom.getParent(node, function(n) {
14398                                 decoration = ed.dom.getStyle(n, 'text-decoration');
14399                                 return decoration && decoration !== 'none';
14400                         });
14401
14402                         return decoration;
14403                 };
14404
14405                 var processUnderlineAndColor = function(node) {
14406                         var textDecoration;
14407                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14408                                 textDecoration = getTextDecoration(node.parentNode);
14409                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
14410                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);
14411                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14412                                         ed.dom.setStyle(node, 'text-decoration', null);
14413                                 }
14414                         }
14415                 };
14416
14417                 function apply(name, vars, node) {
14418                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
14419
14420                         function moveStart(rng) {
14421                                 var container = rng.startContainer,
14422                                         offset = rng.startOffset,
14423                                         walker, node;
14424
14425                                 // Move startContainer/startOffset in to a suitable node
14426                                 if (container.nodeType == 1 || container.nodeValue === "") {
14427                                         container = container.nodeType == 1 ? container.childNodes[offset] : container;
14428
14429                                         // Might fail if the offset is behind the last element in it's container
14430                                         if (container) {
14431                                                 walker = new TreeWalker(container, container.parentNode);
14432                                                 for (node = walker.current(); node; node = walker.next()) {
14433                                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14434                                                                 rng.setStart(node, 0);
14435                                                                 break;
14436                                                         }
14437                                                 }
14438                                         }
14439                                 }
14440
14441                                 return rng;
14442                         };
14443
14444                         function setElementFormat(elm, fmt) {
14445                                 fmt = fmt || format;
14446
14447                                 if (elm) {
14448                                         each(fmt.styles, function(value, name) {
14449                                                 dom.setStyle(elm, name, replaceVars(value, vars));
14450                                         });
14451
14452                                         each(fmt.attributes, function(value, name) {
14453                                                 dom.setAttrib(elm, name, replaceVars(value, vars));
14454                                         });
14455
14456                                         each(fmt.classes, function(value) {
14457                                                 value = replaceVars(value, vars);
14458
14459                                                 if (!dom.hasClass(elm, value))
14460                                                         dom.addClass(elm, value);
14461                                         });
14462                                 }
14463                         };
14464
14465                         function applyRngStyle(rng) {
14466                                 var newWrappers = [], wrapName, wrapElm;
14467
14468                                 // Setup wrapper element
14469                                 wrapName = format.inline || format.block;
14470                                 wrapElm = dom.create(wrapName);
14471                                 setElementFormat(wrapElm);
14472
14473                                 rangeUtils.walk(rng, function(nodes) {
14474                                         var currentWrapElm;
14475
14476                                         function process(node) {
14477                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
14478
14479                                                 // Stop wrapping on br elements
14480                                                 if (isEq(nodeName, 'br')) {
14481                                                         currentWrapElm = 0;
14482
14483                                                         // Remove any br elements when we wrap things
14484                                                         if (format.block)
14485                                                                 dom.remove(node);
14486
14487                                                         return;
14488                                                 }
14489
14490                                                 // If node is wrapper type
14491                                                 if (format.wrapper && matchNode(node, name, vars)) {
14492                                                         currentWrapElm = 0;
14493                                                         return;
14494                                                 }
14495
14496                                                 // Can we rename the block
14497                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
14498                                                         node = dom.rename(node, wrapName);
14499                                                         setElementFormat(node);
14500                                                         newWrappers.push(node);
14501                                                         currentWrapElm = 0;
14502                                                         return;
14503                                                 }
14504
14505                                                 // Handle selector patterns
14506                                                 if (format.selector) {
14507                                                         // Look for matching formats
14508                                                         each(formatList, function(format) {
14509                                                                 // Check collapsed state if it exists
14510                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
14511                                                                         return;
14512                                                                 }
14513
14514                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
14515                                                                         setElementFormat(node, format);
14516                                                                         found = true;
14517                                                                 }
14518                                                         });
14519
14520                                                         // Continue processing if a selector match wasn't found and a inline element is defined
14521                                                         if (!format.inline || found) {
14522                                                                 currentWrapElm = 0;
14523                                                                 return;
14524                                                         }
14525                                                 }
14526
14527                                                 // Is it valid to wrap this item
14528                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
14529                                                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
14530                                                         // Start wrapping
14531                                                         if (!currentWrapElm) {
14532                                                                 // Wrap the node
14533                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14534                                                                 node.parentNode.insertBefore(currentWrapElm, node);
14535                                                                 newWrappers.push(currentWrapElm);
14536                                                         }
14537
14538                                                         currentWrapElm.appendChild(node);
14539                                                 } else {
14540                                                         // Start a new wrapper for possible children
14541                                                         currentWrapElm = 0;
14542
14543                                                         each(tinymce.grep(node.childNodes), process);
14544
14545                                                         // End the last wrapper
14546                                                         currentWrapElm = 0;
14547                                                 }
14548                                         };
14549
14550                                         // Process siblings from range
14551                                         each(nodes, process);
14552                                 });
14553
14554                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
14555                                 if (format.wrap_links === false) {
14556                                         each(newWrappers, function(node) {
14557                                                 function process(node) {
14558                                                         var i, currentWrapElm, children;
14559
14560                                                         if (node.nodeName === 'A') {
14561                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14562                                                                 newWrappers.push(currentWrapElm);
14563
14564                                                                 children = tinymce.grep(node.childNodes);
14565                                                                 for (i = 0; i < children.length; i++)
14566                                                                         currentWrapElm.appendChild(children[i]);
14567
14568                                                                 node.appendChild(currentWrapElm);
14569                                                         }
14570
14571                                                         each(tinymce.grep(node.childNodes), process);
14572                                                 };
14573
14574                                                 process(node);
14575                                         });
14576                                 }
14577
14578                                 // Cleanup
14579                                 each(newWrappers, function(node) {
14580                                         var childCount;
14581
14582                                         function getChildCount(node) {
14583                                                 var count = 0;
14584
14585                                                 each(node.childNodes, function(node) {
14586                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
14587                                                                 count++;
14588                                                 });
14589
14590                                                 return count;
14591                                         };
14592
14593                                         function mergeStyles(node) {
14594                                                 var child, clone;
14595
14596                                                 each(node.childNodes, function(node) {
14597                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
14598                                                                 child = node;
14599                                                                 return FALSE; // break loop
14600                                                         }
14601                                                 });
14602
14603                                                 // If child was found and of the same type as the current node
14604                                                 if (child && matchName(child, format)) {
14605                                                         clone = child.cloneNode(FALSE);
14606                                                         setElementFormat(clone);
14607
14608                                                         dom.replace(clone, node, TRUE);
14609                                                         dom.remove(child, 1);
14610                                                 }
14611
14612                                                 return clone || node;
14613                                         };
14614
14615                                         childCount = getChildCount(node);
14616
14617                                         // Remove empty nodes but only if there is multiple wrappers and they are not block
14618                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
14619                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
14620                                                 dom.remove(node, 1);
14621                                                 return;
14622                                         }
14623
14624                                         if (format.inline || format.wrapper) {
14625                                                 // Merges the current node with it's children of similar type to reduce the number of elements
14626                                                 if (!format.exact && childCount === 1)
14627                                                         node = mergeStyles(node);
14628
14629                                                 // Remove/merge children
14630                                                 each(formatList, function(format) {
14631                                                         // Merge all children of similar type will move styles from child to parent
14632                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
14633                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
14634                                                         each(dom.select(format.inline, node), function(child) {
14635                                                                 var parent;
14636
14637                                                                 // When wrap_links is set to false we don't want
14638                                                                 // to remove the format on children within links
14639                                                                 if (format.wrap_links === false) {
14640                                                                         parent = child.parentNode;
14641
14642                                                                         do {
14643                                                                                 if (parent.nodeName === 'A')
14644                                                                                         return;
14645                                                                         } while (parent = parent.parentNode);
14646                                                                 }
14647
14648                                                                 removeFormat(format, vars, child, format.exact ? child : null);
14649                                                         });
14650                                                 });
14651
14652                                                 // Remove child if direct parent is of same type
14653                                                 if (matchNode(node.parentNode, name, vars)) {
14654                                                         dom.remove(node, 1);
14655                                                         node = 0;
14656                                                         return TRUE;
14657                                                 }
14658
14659                                                 // Look for parent with similar style format
14660                                                 if (format.merge_with_parents) {
14661                                                         dom.getParent(node.parentNode, function(parent) {
14662                                                                 if (matchNode(parent, name, vars)) {
14663                                                                         dom.remove(node, 1);
14664                                                                         node = 0;
14665                                                                         return TRUE;
14666                                                                 }
14667                                                         });
14668                                                 }
14669
14670                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
14671                                                 if (node) {
14672                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
14673                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
14674                                                 }
14675                                         }
14676                                 });
14677                         };
14678
14679                         if (format) {
14680                                 if (node) {
14681                                         rng = dom.createRng();
14682
14683                                         rng.setStartBefore(node);
14684                                         rng.setEndAfter(node);
14685
14686                                         applyRngStyle(expandRng(rng, formatList));
14687                                 } else {
14688                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14689                                                 // Obtain selection node before selection is unselected by applyRngStyle()
14690                                                 var curSelNode = ed.selection.getNode();
14691
14692                                                 // Apply formatting to selection
14693                                                 bookmark = selection.getBookmark();
14694                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
14695
14696                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.
14697                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
14698                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
14699                                                         processUnderlineAndColor(curSelNode);
14700                                                 }
14701
14702                                                 selection.moveToBookmark(bookmark);
14703                                                 selection.setRng(moveStart(selection.getRng(TRUE)));
14704                                                 ed.nodeChanged();
14705                                         } else
14706                                                 performCaretAction('apply', name, vars);
14707                                 }
14708                         }
14709                 };
14710
14711                 function remove(name, vars, node) {
14712                         var formatList = get(name), format = formatList[0], bookmark, i, rng;
14713
14714                         function moveStart(rng) {
14715                                 var container = rng.startContainer,
14716                                         offset = rng.startOffset,
14717                                         walker, node, nodes, tmpNode;
14718
14719                                 // Convert text node into index if possible
14720                                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
14721                                         container = container.parentNode;
14722                                         offset = nodeIndex(container) + 1;
14723                                 }
14724
14725                                 // Move startContainer/startOffset in to a suitable node
14726                                 if (container.nodeType == 1) {
14727                                         nodes = container.childNodes;
14728                                         container = nodes[Math.min(offset, nodes.length - 1)];
14729                                         walker = new TreeWalker(container);
14730
14731                                         // If offset is at end of the parent node walk to the next one
14732                                         if (offset > nodes.length - 1)
14733                                                 walker.next();
14734
14735                                         for (node = walker.current(); node; node = walker.next()) {
14736                                                 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14737                                                         // IE has a "neat" feature where it moves the start node into the closest element
14738                                                         // we can avoid this by inserting an element before it and then remove it after we set the selection
14739                                                         tmpNode = dom.create('a', null, INVISIBLE_CHAR);
14740                                                         node.parentNode.insertBefore(tmpNode, node);
14741
14742                                                         // Set selection and remove tmpNode
14743                                                         rng.setStart(node, 0);
14744                                                         selection.setRng(rng);
14745                                                         dom.remove(tmpNode);
14746
14747                                                         return;
14748                                                 }
14749                                         }
14750                                 }
14751                         };
14752
14753                         // Merges the styles for each node
14754                         function process(node) {
14755                                 var children, i, l;
14756
14757                                 // Grab the children first since the nodelist might be changed
14758                                 children = tinymce.grep(node.childNodes);
14759
14760                                 // Process current node
14761                                 for (i = 0, l = formatList.length; i < l; i++) {
14762                                         if (removeFormat(formatList[i], vars, node, node))
14763                                                 break;
14764                                 }
14765
14766                                 // Process the children
14767                                 if (format.deep) {
14768                                         for (i = 0, l = children.length; i < l; i++)
14769                                                 process(children[i]);
14770                                 }
14771                         };
14772
14773                         function findFormatRoot(container) {
14774                                 var formatRoot;
14775
14776                                 // Find format root
14777                                 each(getParents(container.parentNode).reverse(), function(parent) {
14778                                         var format;
14779
14780                                         // Find format root element
14781                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
14782                                                 // Is the node matching the format we are looking for
14783                                                 format = matchNode(parent, name, vars);
14784                                                 if (format && format.split !== false)
14785                                                         formatRoot = parent;
14786                                         }
14787                                 });
14788
14789                                 return formatRoot;
14790                         };
14791
14792                         function wrapAndSplit(format_root, container, target, split) {
14793                                 var parent, clone, lastClone, firstClone, i, formatRootParent;
14794
14795                                 // Format root found then clone formats and split it
14796                                 if (format_root) {
14797                                         formatRootParent = format_root.parentNode;
14798
14799                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14800                                                 clone = parent.cloneNode(FALSE);
14801
14802                                                 for (i = 0; i < formatList.length; i++) {
14803                                                         if (removeFormat(formatList[i], vars, clone, clone)) {
14804                                                                 clone = 0;
14805                                                                 break;
14806                                                         }
14807                                                 }
14808
14809                                                 // Build wrapper node
14810                                                 if (clone) {
14811                                                         if (lastClone)
14812                                                                 clone.appendChild(lastClone);
14813
14814                                                         if (!firstClone)
14815                                                                 firstClone = clone;
14816
14817                                                         lastClone = clone;
14818                                                 }
14819                                         }
14820
14821                                         // Never split block elements if the format is mixed
14822                                         if (split && (!format.mixed || !isBlock(format_root)))
14823                                                 container = dom.split(format_root, container);
14824
14825                                         // Wrap container in cloned formats
14826                                         if (lastClone) {
14827                                                 target.parentNode.insertBefore(lastClone, target);
14828                                                 firstClone.appendChild(target);
14829                                         }
14830                                 }
14831
14832                                 return container;
14833                         };
14834
14835                         function splitToFormatRoot(container) {
14836                                 return wrapAndSplit(findFormatRoot(container), container, container, true);
14837                         };
14838
14839                         function unwrap(start) {
14840                                 var node = dom.get(start ? '_start' : '_end'),
14841                                         out = node[start ? 'firstChild' : 'lastChild'];
14842
14843                                 // If the end is placed within the start the result will be removed
14844                                 // So this checks if the out node is a bookmark node if it is it
14845                                 // checks for another more suitable node
14846                                 if (isBookmarkNode(out))
14847                                         out = out[start ? 'firstChild' : 'lastChild'];
14848
14849                                 dom.remove(node, true);
14850
14851                                 return out;
14852                         };
14853
14854                         function removeRngStyle(rng) {
14855                                 var startContainer, endContainer;
14856
14857                                 rng = expandRng(rng, formatList, TRUE);
14858
14859                                 if (format.split) {
14860                                         startContainer = getContainer(rng, TRUE);
14861                                         endContainer = getContainer(rng);
14862
14863                                         if (startContainer != endContainer) {
14864                                                 // Wrap start/end nodes in span element since these might be cloned/moved
14865                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
14866                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
14867
14868                                                 // Split start/end
14869                                                 splitToFormatRoot(startContainer);
14870                                                 splitToFormatRoot(endContainer);
14871
14872                                                 // Unwrap start/end to get real elements again
14873                                                 startContainer = unwrap(TRUE);
14874                                                 endContainer = unwrap();
14875                                         } else
14876                                                 startContainer = endContainer = splitToFormatRoot(startContainer);
14877
14878                                         // Update range positions since they might have changed after the split operations
14879                                         rng.startContainer = startContainer.parentNode;
14880                                         rng.startOffset = nodeIndex(startContainer);
14881                                         rng.endContainer = endContainer.parentNode;
14882                                         rng.endOffset = nodeIndex(endContainer) + 1;
14883                                 }
14884
14885                                 // Remove items between start/end
14886                                 rangeUtils.walk(rng, function(nodes) {
14887                                         each(nodes, function(node) {
14888                                                 process(node);
14889
14890                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14891                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14892                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
14893                                                 }
14894                                         });
14895                                 });
14896                         };
14897
14898                         // Handle node
14899                         if (node) {
14900                                 rng = dom.createRng();
14901                                 rng.setStartBefore(node);
14902                                 rng.setEndAfter(node);
14903                                 removeRngStyle(rng);
14904                                 return;
14905                         }
14906
14907                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14908                                 bookmark = selection.getBookmark();
14909                                 removeRngStyle(selection.getRng(TRUE));
14910                                 selection.moveToBookmark(bookmark);
14911
14912                                 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
14913                                 if (match(name, vars, selection.getStart())) {
14914                                         moveStart(selection.getRng(true));
14915                                 }
14916
14917                                 ed.nodeChanged();
14918                         } else
14919                                 performCaretAction('remove', name, vars);
14920                 };
14921
14922                 function toggle(name, vars, node) {
14923                         var fmt = get(name);
14924
14925                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
14926                                 remove(name, vars, node);
14927                         else
14928                                 apply(name, vars, node);
14929                 };
14930
14931                 function matchNode(node, name, vars, similar) {
14932                         var formatList = get(name), format, i, classes;
14933
14934                         function matchItems(node, format, item_name) {
14935                                 var key, value, items = format[item_name], i;
14936
14937                                 // Check all items
14938                                 if (items) {
14939                                         // Non indexed object
14940                                         if (items.length === undefined) {
14941                                                 for (key in items) {
14942                                                         if (items.hasOwnProperty(key)) {
14943                                                                 if (item_name === 'attributes')
14944                                                                         value = dom.getAttrib(node, key);
14945                                                                 else
14946                                                                         value = getStyle(node, key);
14947
14948                                                                 if (similar && !value && !format.exact)
14949                                                                         return;
14950
14951                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
14952                                                                         return;
14953                                                         }
14954                                                 }
14955                                         } else {
14956                                                 // Only one match needed for indexed arrays
14957                                                 for (i = 0; i < items.length; i++) {
14958                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
14959                                                                 return format;
14960                                                 }
14961                                         }
14962                                 }
14963
14964                                 return format;
14965                         };
14966
14967                         if (formatList && node) {
14968                                 // Check each format in list
14969                                 for (i = 0; i < formatList.length; i++) {
14970                                         format = formatList[i];
14971
14972                                         // Name name, attributes, styles and classes
14973                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14974                                                 // Match classes
14975                                                 if (classes = format.classes) {
14976                                                         for (i = 0; i < classes.length; i++) {
14977                                                                 if (!dom.hasClass(node, classes[i]))
14978                                                                         return;
14979                                                         }
14980                                                 }
14981
14982                                                 return format;
14983                                         }
14984                                 }
14985                         }
14986                 };
14987
14988                 function match(name, vars, node) {
14989                         var startNode, i;
14990
14991                         function matchParents(node) {
14992                                 // Find first node with similar format settings
14993                                 node = dom.getParent(node, function(node) {
14994                                         return !!matchNode(node, name, vars, true);
14995                                 });
14996
14997                                 // Do an exact check on the similar format element
14998                                 return matchNode(node, name, vars);
14999                         };
15000
15001                         // Check specified node
15002                         if (node)
15003                                 return matchParents(node);
15004
15005                         // Check pending formats
15006                         if (selection.isCollapsed()) {
15007                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15008                                         if (pendingFormats.apply[i].name == name)
15009                                                 return true;
15010                                 }
15011
15012                                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15013                                         if (pendingFormats.remove[i].name == name)
15014                                                 return false;
15015                                 }
15016
15017                                 return matchParents(selection.getNode());
15018                         }
15019
15020                         // Check selected node
15021                         node = selection.getNode();
15022                         if (matchParents(node))
15023                                 return TRUE;
15024
15025                         // Check start node if it's different
15026                         startNode = selection.getStart();
15027                         if (startNode != node) {
15028                                 if (matchParents(startNode))
15029                                         return TRUE;
15030                         }
15031
15032                         return FALSE;
15033                 };
15034
15035                 function matchAll(names, vars) {
15036                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15037
15038                         // If the selection is collapsed then check pending formats
15039                         if (selection.isCollapsed()) {
15040                                 for (ni = 0; ni < names.length; ni++) {
15041                                         // If the name is to be removed, then stop it from being added
15042                                         for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15043                                                 name = names[ni];
15044
15045                                                 if (pendingFormats.remove[i].name == name) {
15046                                                         checkedMap[name] = true;
15047                                                         break;
15048                                                 }
15049                                         }
15050                                 }
15051
15052                                 // If the format is to be applied
15053                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15054                                         for (ni = 0; ni < names.length; ni++) {
15055                                                 name = names[ni];
15056
15057                                                 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
15058                                                         checkedMap[name] = true;
15059                                                         matchedFormatNames.push(name);
15060                                                 }
15061                                         }
15062                                 }
15063                         }
15064
15065                         // Check start of selection for formats
15066                         startElement = selection.getStart();
15067                         dom.getParent(startElement, function(node) {
15068                                 var i, name;
15069
15070                                 for (i = 0; i < names.length; i++) {
15071                                         name = names[i];
15072
15073                                         if (!checkedMap[name] && matchNode(node, name, vars)) {
15074                                                 checkedMap[name] = true;
15075                                                 matchedFormatNames.push(name);
15076                                         }
15077                                 }
15078                         });
15079
15080                         return matchedFormatNames;
15081                 };
15082
15083                 function canApply(name) {
15084                         var formatList = get(name), startNode, parents, i, x, selector;
15085
15086                         if (formatList) {
15087                                 startNode = selection.getStart();
15088                                 parents = getParents(startNode);
15089
15090                                 for (x = formatList.length - 1; x >= 0; x--) {
15091                                         selector = formatList[x].selector;
15092
15093                                         // Format is not selector based, then always return TRUE
15094                                         if (!selector)
15095                                                 return TRUE;
15096
15097                                         for (i = parents.length - 1; i >= 0; i--) {
15098                                                 if (dom.is(parents[i], selector))
15099                                                         return TRUE;
15100                                         }
15101                                 }
15102                         }
15103
15104                         return FALSE;
15105                 };
15106
15107                 // Expose to public
15108                 tinymce.extend(this, {
15109                         get : get,
15110                         register : register,
15111                         apply : apply,
15112                         remove : remove,
15113                         toggle : toggle,
15114                         match : match,
15115                         matchAll : matchAll,
15116                         matchNode : matchNode,
15117                         canApply : canApply
15118                 });
15119
15120                 // Private functions
15121
15122                 function matchName(node, format) {
15123                         // Check for inline match
15124                         if (isEq(node, format.inline))
15125                                 return TRUE;
15126
15127                         // Check for block match
15128                         if (isEq(node, format.block))
15129                                 return TRUE;
15130
15131                         // Check for selector match
15132                         if (format.selector)
15133                                 return dom.is(node, format.selector);
15134                 };
15135
15136                 function isEq(str1, str2) {
15137                         str1 = str1 || '';
15138                         str2 = str2 || '';
15139
15140                         str1 = '' + (str1.nodeName || str1);
15141                         str2 = '' + (str2.nodeName || str2);
15142
15143                         return str1.toLowerCase() == str2.toLowerCase();
15144                 };
15145
15146                 function getStyle(node, name) {
15147                         var styleVal = dom.getStyle(node, name);
15148
15149                         // Force the format to hex
15150                         if (name == 'color' || name == 'backgroundColor')
15151                                 styleVal = dom.toHex(styleVal);
15152
15153                         // Opera will return bold as 700
15154                         if (name == 'fontWeight' && styleVal == 700)
15155                                 styleVal = 'bold';
15156
15157                         return '' + styleVal;
15158                 };
15159
15160                 function replaceVars(value, vars) {
15161                         if (typeof(value) != "string")
15162                                 value = value(vars);
15163                         else if (vars) {
15164                                 value = value.replace(/%(\w+)/g, function(str, name) {
15165                                         return vars[name] || str;
15166                                 });
15167                         }
15168
15169                         return value;
15170                 };
15171
15172                 function isWhiteSpaceNode(node) {
15173                         return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
15174                 };
15175
15176                 function wrap(node, name, attrs) {
15177                         var wrapper = dom.create(name, attrs);
15178
15179                         node.parentNode.insertBefore(wrapper, node);
15180                         wrapper.appendChild(node);
15181
15182                         return wrapper;
15183                 };
15184
15185                 function expandRng(rng, format, remove) {
15186                         var startContainer = rng.startContainer,
15187                                 startOffset = rng.startOffset,
15188                                 endContainer = rng.endContainer,
15189                                 endOffset = rng.endOffset, sibling, lastIdx, leaf;
15190
15191                         // This function walks up the tree if there is no siblings before/after the node
15192                         function findParentContainer(container, child_name, sibling_name, root) {
15193                                 var parent, child;
15194
15195                                 root = root || dom.getRoot();
15196
15197                                 for (;;) {
15198                                         // Check if we can move up are we at root level or body level
15199                                         parent = container.parentNode;
15200
15201                                         // Stop expanding on block elements or root depending on format
15202                                         if (parent == root || (!format[0].block_expand && isBlock(parent)))
15203                                                 return container;
15204
15205                                         for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
15206                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15207                                                         return container;
15208
15209                                                 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
15210                                                         return container;
15211                                         }
15212
15213                                         container = container.parentNode;
15214                                 }
15215
15216                                 return container;
15217                         };
15218
15219                         // This function walks down the tree to find the leaf at the selection.
15220                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15221                         function findLeaf(node, offset) {
15222                                 if (offset === undefined)
15223                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15224                                 while (node && node.hasChildNodes()) {
15225                                         node = node.childNodes[offset];
15226                                         if (node)
15227                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15228                                 }
15229                                 return { node: node, offset: offset };
15230                         }
15231
15232                         // If index based start position then resolve it
15233                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15234                                 lastIdx = startContainer.childNodes.length - 1;
15235                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15236
15237                                 if (startContainer.nodeType == 3)
15238                                         startOffset = 0;
15239                         }
15240
15241                         // If index based end position then resolve it
15242                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15243                                 lastIdx = endContainer.childNodes.length - 1;
15244                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15245
15246                                 if (endContainer.nodeType == 3)
15247                                         endOffset = endContainer.nodeValue.length;
15248                         }
15249
15250                         // Exclude bookmark nodes if possible
15251                         if (isBookmarkNode(startContainer.parentNode))
15252                                 startContainer = startContainer.parentNode;
15253
15254                         if (isBookmarkNode(startContainer))
15255                                 startContainer = startContainer.nextSibling || startContainer;
15256
15257                         if (isBookmarkNode(endContainer.parentNode)) {
15258                                 endOffset = dom.nodeIndex(endContainer);
15259                                 endContainer = endContainer.parentNode;
15260                         }
15261
15262                         if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
15263                                 endContainer = endContainer.previousSibling;
15264                                 endOffset = endContainer.length;
15265                         }
15266
15267                         if (format[0].inline) {
15268                                 // Avoid applying formatting to a trailing space.
15269                                 leaf = findLeaf(endContainer, endOffset);
15270                                 if (leaf.node) {
15271                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
15272                                                 leaf = findLeaf(leaf.node.previousSibling);
15273
15274                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15275                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15276
15277                                                 if (leaf.offset > 1) {
15278                                                         endContainer = leaf.node;
15279                                                         endContainer.splitText(leaf.offset - 1);
15280                                                 } else if (leaf.node.previousSibling) {
15281                                                         endContainer = leaf.node.previousSibling;
15282                                                 }
15283                                         }
15284                                 }
15285                         }
15286                         
15287                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers
15288                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15289                         // This will reduce the number of wrapper elements that needs to be created
15290                         // Move start point up the tree
15291                         if (format[0].inline || format[0].block_expand) {
15292                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15293                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15294                         }
15295
15296                         // Expand start/end container to matching selector
15297                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15298                                 function findSelectorEndPoint(container, sibling_name) {
15299                                         var parents, i, y, curFormat;
15300
15301                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
15302                                                 container = container[sibling_name];
15303
15304                                         parents = getParents(container);
15305                                         for (i = 0; i < parents.length; i++) {
15306                                                 for (y = 0; y < format.length; y++) {
15307                                                         curFormat = format[y];
15308
15309                                                         // If collapsed state is set then skip formats that doesn't match that
15310                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
15311                                                                 continue;
15312
15313                                                         if (dom.is(parents[i], curFormat.selector))
15314                                                                 return parents[i];
15315                                                 }
15316                                         }
15317
15318                                         return container;
15319                                 };
15320
15321                                 // Find new startContainer/endContainer if there is better one
15322                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15323                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15324                         }
15325
15326                         // Expand start/end container to matching block element or text node
15327                         if (format[0].block || format[0].selector) {
15328                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {
15329                                         var node;
15330
15331                                         // Expand to block of similar type
15332                                         if (!format[0].wrapper)
15333                                                 node = dom.getParent(container, format[0].block);
15334
15335                                         // Expand to first wrappable block element or any block element
15336                                         if (!node)
15337                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
15338
15339                                         // Exclude inner lists from wrapping
15340                                         if (node && format[0].wrapper)
15341                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;
15342
15343                                         // Didn't find a block element look for first/last wrappable element
15344                                         if (!node) {
15345                                                 node = container;
15346
15347                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {
15348                                                         node = node[sibling_name];
15349
15350                                                         // Break on BR but include it will be removed later on
15351                                                         // we can't remove it now since we need to check if it can be wrapped
15352                                                         if (isEq(node, 'br'))
15353                                                                 break;
15354                                                 }
15355                                         }
15356
15357                                         return node || container;
15358                                 };
15359
15360                                 // Find new startContainer/endContainer if there is better one
15361                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15362                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15363
15364                                 // Non block element then try to expand up the leaf
15365                                 if (format[0].block) {
15366                                         if (!isBlock(startContainer))
15367                                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15368
15369                                         if (!isBlock(endContainer))
15370                                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15371                                 }
15372                         }
15373
15374                         // Setup index for startContainer
15375                         if (startContainer.nodeType == 1) {
15376                                 startOffset = nodeIndex(startContainer);
15377                                 startContainer = startContainer.parentNode;
15378                         }
15379
15380                         // Setup index for endContainer
15381                         if (endContainer.nodeType == 1) {
15382                                 endOffset = nodeIndex(endContainer) + 1;
15383                                 endContainer = endContainer.parentNode;
15384                         }
15385
15386                         // Return new range like object
15387                         return {
15388                                 startContainer : startContainer,
15389                                 startOffset : startOffset,
15390                                 endContainer : endContainer,
15391                                 endOffset : endOffset
15392                         };
15393                 }
15394
15395                 function removeFormat(format, vars, node, compare_node) {
15396                         var i, attrs, stylesModified;
15397
15398                         // Check if node matches format
15399                         if (!matchName(node, format))
15400                                 return FALSE;
15401
15402                         // Should we compare with format attribs and styles
15403                         if (format.remove != 'all') {
15404                                 // Remove styles
15405                                 each(format.styles, function(value, name) {
15406                                         value = replaceVars(value, vars);
15407
15408                                         // Indexed array
15409                                         if (typeof(name) === 'number') {
15410                                                 name = value;
15411                                                 compare_node = 0;
15412                                         }
15413
15414                                         if (!compare_node || isEq(getStyle(compare_node, name), value))
15415                                                 dom.setStyle(node, name, '');
15416
15417                                         stylesModified = 1;
15418                                 });
15419
15420                                 // Remove style attribute if it's empty
15421                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {
15422                                         node.removeAttribute('style');
15423                                         node.removeAttribute('data-mce-style');
15424                                 }
15425
15426                                 // Remove attributes
15427                                 each(format.attributes, function(value, name) {
15428                                         var valueOut;
15429
15430                                         value = replaceVars(value, vars);
15431
15432                                         // Indexed array
15433                                         if (typeof(name) === 'number') {
15434                                                 name = value;
15435                                                 compare_node = 0;
15436                                         }
15437
15438                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15439                                                 // Keep internal classes
15440                                                 if (name == 'class') {
15441                                                         value = dom.getAttrib(node, name);
15442                                                         if (value) {
15443                                                                 // Build new class value where everything is removed except the internal prefixed classes
15444                                                                 valueOut = '';
15445                                                                 each(value.split(/\s+/), function(cls) {
15446                                                                         if (/mce\w+/.test(cls))
15447                                                                                 valueOut += (valueOut ? ' ' : '') + cls;
15448                                                                 });
15449
15450                                                                 // We got some internal classes left
15451                                                                 if (valueOut) {
15452                                                                         dom.setAttrib(node, name, valueOut);
15453                                                                         return;
15454                                                                 }
15455                                                         }
15456                                                 }
15457
15458                                                 // IE6 has a bug where the attribute doesn't get removed correctly
15459                                                 if (name == "class")
15460                                                         node.removeAttribute('className');
15461
15462                                                 // Remove mce prefixed attributes
15463                                                 if (MCE_ATTR_RE.test(name))
15464                                                         node.removeAttribute('data-mce-' + name);
15465
15466                                                 node.removeAttribute(name);
15467                                         }
15468                                 });
15469
15470                                 // Remove classes
15471                                 each(format.classes, function(value) {
15472                                         value = replaceVars(value, vars);
15473
15474                                         if (!compare_node || dom.hasClass(compare_node, value))
15475                                                 dom.removeClass(node, value);
15476                                 });
15477
15478                                 // Check for non internal attributes
15479                                 attrs = dom.getAttribs(node);
15480                                 for (i = 0; i < attrs.length; i++) {
15481                                         if (attrs[i].nodeName.indexOf('_') !== 0)
15482                                                 return FALSE;
15483                                 }
15484                         }
15485
15486                         // Remove the inline child if it's empty for example <b> or <span>
15487                         if (format.remove != 'none') {
15488                                 removeNode(node, format);
15489                                 return TRUE;
15490                         }
15491                 };
15492
15493                 function removeNode(node, format) {
15494                         var parentNode = node.parentNode, rootBlockElm;
15495
15496                         if (format.block) {
15497                                 if (!forcedRootBlock) {
15498                                         function find(node, next, inc) {
15499                                                 node = getNonWhiteSpaceSibling(node, next, inc);
15500
15501                                                 return !node || (node.nodeName == 'BR' || isBlock(node));
15502                                         };
15503
15504                                         // Append BR elements if needed before we remove the block
15505                                         if (isBlock(node) && !isBlock(parentNode)) {
15506                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
15507                                                         node.insertBefore(dom.create('br'), node.firstChild);
15508
15509                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
15510                                                         node.appendChild(dom.create('br'));
15511                                         }
15512                                 } else {
15513                                         // Wrap the block in a forcedRootBlock if we are at the root of document
15514                                         if (parentNode == dom.getRoot()) {
15515                                                 if (!format.list_block || !isEq(node, format.list_block)) {
15516                                                         each(tinymce.grep(node.childNodes), function(node) {
15517                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15518                                                                         if (!rootBlockElm)
15519                                                                                 rootBlockElm = wrap(node, forcedRootBlock);
15520                                                                         else
15521                                                                                 rootBlockElm.appendChild(node);
15522                                                                 } else
15523                                                                         rootBlockElm = 0;
15524                                                         });
15525                                                 }
15526                                         }
15527                                 }
15528                         }
15529
15530                         // Never remove nodes that isn't the specified inline element if a selector is specified too
15531                         if (format.selector && format.inline && !isEq(format.inline, node))
15532                                 return;
15533
15534                         dom.remove(node, 1);
15535                 };
15536
15537                 function getNonWhiteSpaceSibling(node, next, inc) {
15538                         if (node) {
15539                                 next = next ? 'nextSibling' : 'previousSibling';
15540
15541                                 for (node = inc ? node : node[next]; node; node = node[next]) {
15542                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))
15543                                                 return node;
15544                                 }
15545                         }
15546                 };
15547
15548                 function isBookmarkNode(node) {
15549                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
15550                 };
15551
15552                 function mergeSiblings(prev, next) {
15553                         var marker, sibling, tmpSibling;
15554
15555                         function compareElements(node1, node2) {
15556                                 // Not the same name
15557                                 if (node1.nodeName != node2.nodeName)
15558                                         return FALSE;
15559
15560                                 function getAttribs(node) {
15561                                         var attribs = {};
15562
15563                                         each(dom.getAttribs(node), function(attr) {
15564                                                 var name = attr.nodeName.toLowerCase();
15565
15566                                                 // Don't compare internal attributes or style
15567                                                 if (name.indexOf('_') !== 0 && name !== 'style')
15568                                                         attribs[name] = dom.getAttrib(node, name);
15569                                         });
15570
15571                                         return attribs;
15572                                 };
15573
15574                                 function compareObjects(obj1, obj2) {
15575                                         var value, name;
15576
15577                                         for (name in obj1) {
15578                                                 // Obj1 has item obj2 doesn't have
15579                                                 if (obj1.hasOwnProperty(name)) {
15580                                                         value = obj2[name];
15581
15582                                                         // Obj2 doesn't have obj1 item
15583                                                         if (value === undefined)
15584                                                                 return FALSE;
15585
15586                                                         // Obj2 item has a different value
15587                                                         if (obj1[name] != value)
15588                                                                 return FALSE;
15589
15590                                                         // Delete similar value
15591                                                         delete obj2[name];
15592                                                 }
15593                                         }
15594
15595                                         // Check if obj 2 has something obj 1 doesn't have
15596                                         for (name in obj2) {
15597                                                 // Obj2 has item obj1 doesn't have
15598                                                 if (obj2.hasOwnProperty(name))
15599                                                         return FALSE;
15600                                         }
15601
15602                                         return TRUE;
15603                                 };
15604
15605                                 // Attribs are not the same
15606                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
15607                                         return FALSE;
15608
15609                                 // Styles are not the same
15610                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
15611                                         return FALSE;
15612
15613                                 return TRUE;
15614                         };
15615
15616                         // Check if next/prev exists and that they are elements
15617                         if (prev && next) {
15618                                 function findElementSibling(node, sibling_name) {
15619                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {
15620                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
15621                                                         return node;
15622
15623                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15624                                                         return sibling;
15625                                         }
15626
15627                                         return node;
15628                                 };
15629
15630                                 // If previous sibling is empty then jump over it
15631                                 prev = findElementSibling(prev, 'previousSibling');
15632                                 next = findElementSibling(next, 'nextSibling');
15633
15634                                 // Compare next and previous nodes
15635                                 if (compareElements(prev, next)) {
15636                                         // Append nodes between
15637                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {
15638                                                 tmpSibling = sibling;
15639                                                 sibling = sibling.nextSibling;
15640                                                 prev.appendChild(tmpSibling);
15641                                         }
15642
15643                                         // Remove next node
15644                                         dom.remove(next);
15645
15646                                         // Move children into prev node
15647                                         each(tinymce.grep(next.childNodes), function(node) {
15648                                                 prev.appendChild(node);
15649                                         });
15650
15651                                         return prev;
15652                                 }
15653                         }
15654
15655                         return next;
15656                 };
15657
15658                 function isTextBlock(name) {
15659                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
15660                 };
15661
15662                 function getContainer(rng, start) {
15663                         var container, offset, lastIdx;
15664
15665                         container = rng[start ? 'startContainer' : 'endContainer'];
15666                         offset = rng[start ? 'startOffset' : 'endOffset'];
15667
15668                         if (container.nodeType == 1) {
15669                                 lastIdx = container.childNodes.length - 1;
15670
15671                                 if (!start && offset)
15672                                         offset--;
15673
15674                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
15675                         }
15676
15677                         return container;
15678                 };
15679
15680                 function performCaretAction(type, name, vars) {
15681                         var i, currentPendingFormats = pendingFormats[type],
15682                                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
15683
15684                         function hasPending() {
15685                                 return pendingFormats.apply.length || pendingFormats.remove.length;
15686                         };
15687
15688                         function resetPending() {
15689                                 pendingFormats.apply = [];
15690                                 pendingFormats.remove = [];
15691                         };
15692
15693                         function perform(caret_node) {
15694                                 // Apply pending formats
15695                                 each(pendingFormats.apply.reverse(), function(item) {
15696                                         apply(item.name, item.vars, caret_node);
15697
15698                                         // Colored nodes should be underlined so that the color of the underline matches the text color.
15699                                         if (item.name === 'forecolor' && item.vars.value)
15700                                                 processUnderlineAndColor(caret_node.parentNode);
15701                                 });
15702
15703                                 // Remove pending formats
15704                                 each(pendingFormats.remove.reverse(), function(item) {
15705                                         remove(item.name, item.vars, caret_node);
15706                                 });
15707
15708                                 dom.remove(caret_node, 1);
15709                                 resetPending();
15710                         };
15711
15712                         // Check if it already exists then ignore it
15713                         for (i = currentPendingFormats.length - 1; i >= 0; i--) {
15714                                 if (currentPendingFormats[i].name == name)
15715                                         return;
15716                         }
15717
15718                         currentPendingFormats.push({name : name, vars : vars});
15719
15720                         // Check if it's in the other type, then remove it
15721                         for (i = otherPendingFormats.length - 1; i >= 0; i--) {
15722                                 if (otherPendingFormats[i].name == name)
15723                                         otherPendingFormats.splice(i, 1);
15724                         }
15725
15726                         // Pending apply or remove formats
15727                         if (hasPending()) {
15728                                 ed.getDoc().execCommand('FontName', false, 'mceinline');
15729                                 pendingFormats.lastRng = selection.getRng();
15730
15731                                 // IE will convert the current word
15732                                 each(dom.select('font,span'), function(node) {
15733                                         var bookmark;
15734
15735                                         if (isCaretNode(node)) {
15736                                                 bookmark = selection.getBookmark();
15737                                                 perform(node);
15738                                                 selection.moveToBookmark(bookmark);
15739                                                 ed.nodeChanged();
15740                                         }
15741                                 });
15742
15743                                 // Only register listeners once if we need to
15744                                 if (!pendingFormats.isListening && hasPending()) {
15745                                         pendingFormats.isListening = true;
15746
15747                                         each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
15748                                                 ed[event].addToTop(function(ed, e) {
15749                                                         // Do we have pending formats and is the selection moved has moved
15750                                                         if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
15751                                                                 each(dom.select('font,span'), function(node) {
15752                                                                         var textNode, rng;
15753
15754                                                                         // Look for marker
15755                                                                         if (isCaretNode(node)) {
15756                                                                                 textNode = node.firstChild;
15757
15758                                                                                 if (textNode) {
15759                                                                                         perform(node);
15760
15761                                                                                         rng = dom.createRng();
15762                                                                                         rng.setStart(textNode, textNode.nodeValue.length);
15763                                                                                         rng.setEnd(textNode, textNode.nodeValue.length);
15764                                                                                         selection.setRng(rng);
15765                                                                                         ed.nodeChanged();
15766                                                                                 } else
15767                                                                                         dom.remove(node);
15768                                                                         }
15769                                                                 });
15770
15771                                                                 // Always unbind and clear pending styles on keyup
15772                                                                 if (e.type == 'keyup' || e.type == 'mouseup')
15773                                                                         resetPending();
15774                                                         }
15775                                                 });
15776                                         });
15777                                 }
15778                         }
15779                 };
15780         };
15781 })(tinymce);
15782
15783 tinymce.onAddEditor.add(function(tinymce, ed) {
15784         var filters, fontSizes, dom, settings = ed.settings;
15785
15786         if (settings.inline_styles) {
15787                 fontSizes = tinymce.explode(settings.font_size_style_values);
15788
15789                 function replaceWithSpan(node, styles) {
15790                         tinymce.each(styles, function(value, name) {
15791                                 if (value)
15792                                         dom.setStyle(node, name, value);
15793                         });
15794
15795                         dom.rename(node, 'span');
15796                 };
15797
15798                 filters = {
15799                         font : function(dom, node) {
15800                                 replaceWithSpan(node, {
15801                                         backgroundColor : node.style.backgroundColor,
15802                                         color : node.color,
15803                                         fontFamily : node.face,
15804                                         fontSize : fontSizes[parseInt(node.size) - 1]
15805                                 });
15806                         },
15807
15808                         u : function(dom, node) {
15809                                 replaceWithSpan(node, {
15810                                         textDecoration : 'underline'
15811                                 });
15812                         },
15813
15814                         strike : function(dom, node) {
15815                                 replaceWithSpan(node, {
15816                                         textDecoration : 'line-through'
15817                                 });
15818                         }
15819                 };
15820
15821                 function convert(editor, params) {
15822                         dom = editor.dom;
15823
15824                         if (settings.convert_fonts_to_spans) {
15825                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
15826                                         filters[node.nodeName.toLowerCase()](ed.dom, node);
15827                                 });
15828                         }
15829                 };
15830
15831                 ed.onPreProcess.add(convert);
15832                 ed.onSetContent.add(convert);
15833
15834                 ed.onInit.add(function() {
15835                         ed.selection.onSetContent.add(convert);
15836                 });
15837         }
15838 });
15839