MDL-27890 tinymce - allow it to be smaller
[moodle.git] / lib / editor / tinymce / tiny_mce / 3.4.2 / tiny_mce_src.js
CommitLineData
abd7557e
AB
1(function(win) {
2 var whiteSpaceRe = /^\s*|\s*$/g,
3 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
4
5 var tinymce = {
6 majorVersion : '@@tinymce_major_version@@',
7
8 minorVersion : '@@tinymce_minor_version@@',
9
10 releaseDate : '@@tinymce_release_date@@',
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);
485tinymce.create('tinymce.util.Dispatcher', {
486 scope : null,
487 listeners : null,
488
489 Dispatcher : function(s) {
490 this.scope = s || this;
491 this.listeners = [];
492 },
493
494 add : function(cb, s) {
495 this.listeners.push({cb : cb, scope : s || this.scope});
496
497 return cb;
498 },
499
500 addToTop : function(cb, s) {
501 this.listeners.unshift({cb : cb, scope : s || this.scope});
502
503 return cb;
504 },
505
506 remove : function(cb) {
507 var l = this.listeners, o = null;
508
509 tinymce.each(l, function(c, i) {
510 if (cb == c.cb) {
511 o = cb;
512 l.splice(i, 1);
513 return false;
514 }
515 });
516
517 return o;
518 },
519
520 dispatch : function() {
521 var s, a = arguments, i, li = this.listeners, c;
522
523 // Needs to be a real loop since the listener count might change while looping
524 // And this is also more efficient
525 for (i = 0; i<li.length; i++) {
526 c = li[i];
527 s = c.cb.apply(c.scope, a);
528
529 if (s === false)
530 break;
531 }
532
533 return s;
534 }
535
536 });
537(function() {
538 var each = tinymce.each;
539
540 tinymce.create('tinymce.util.URI', {
541 URI : function(u, s) {
542 var t = this, o, a, b;
543
544 // Trim whitespace
545 u = tinymce.trim(u);
546
547 // Default settings
548 s = t.settings = s || {};
549
550 // Strange app protocol or local anchor
551 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
552 t.source = u;
553 return;
554 }
555
556 // Absolute path with no host, fake host and protocol
557 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
558 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
559
560 // Relative path http:// or protocol relative //path
561 if (!/^\w*:?\/\//.test(u))
562 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
563
564 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
565 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
566 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
567 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
568 var s = u[i];
569
570 // Zope 3 workaround, they use @@something
571 if (s)
572 s = s.replace(/\(mce_at\)/g, '@@');
573
574 t[v] = s;
575 });
576
577 if (b = s.base_uri) {
578 if (!t.protocol)
579 t.protocol = b.protocol;
580
581 if (!t.userInfo)
582 t.userInfo = b.userInfo;
583
584 if (!t.port && t.host == 'mce_host')
585 t.port = b.port;
586
587 if (!t.host || t.host == 'mce_host')
588 t.host = b.host;
589
590 t.source = '';
591 }
592
593 //t.path = t.path || '/';
594 },
595
596 setPath : function(p) {
597 var t = this;
598
599 p = /^(.*?)\/?(\w+)?$/.exec(p);
600
601 // Update path parts
602 t.path = p[0];
603 t.directory = p[1];
604 t.file = p[2];
605
606 // Rebuild source
607 t.source = '';
608 t.getURI();
609 },
610
611 toRelative : function(u) {
612 var t = this, o;
613
614 if (u === "./")
615 return u;
616
617 u = new tinymce.util.URI(u, {base_uri : t});
618
619 // Not on same domain/port or protocol
620 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
621 return u.getURI();
622
623 o = t.toRelPath(t.path, u.path);
624
625 // Add query
626 if (u.query)
627 o += '?' + u.query;
628
629 // Add anchor
630 if (u.anchor)
631 o += '#' + u.anchor;
632
633 return o;
634 },
635
636 toAbsolute : function(u, nh) {
637 var u = new tinymce.util.URI(u, {base_uri : this});
638
639 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
640 },
641
642 toRelPath : function(base, path) {
643 var items, bp = 0, out = '', i, l;
644
645 // Split the paths
646 base = base.substring(0, base.lastIndexOf('/'));
647 base = base.split('/');
648 items = path.split('/');
649
650 if (base.length >= items.length) {
651 for (i = 0, l = base.length; i < l; i++) {
652 if (i >= items.length || base[i] != items[i]) {
653 bp = i + 1;
654 break;
655 }
656 }
657 }
658
659 if (base.length < items.length) {
660 for (i = 0, l = items.length; i < l; i++) {
661 if (i >= base.length || base[i] != items[i]) {
662 bp = i + 1;
663 break;
664 }
665 }
666 }
667
668 if (bp == 1)
669 return path;
670
671 for (i = 0, l = base.length - (bp - 1); i < l; i++)
672 out += "../";
673
674 for (i = bp - 1, l = items.length; i < l; i++) {
675 if (i != bp - 1)
676 out += "/" + items[i];
677 else
678 out += items[i];
679 }
680
681 return out;
682 },
683
684 toAbsPath : function(base, path) {
685 var i, nb = 0, o = [], tr, outPath;
686
687 // Split paths
688 tr = /\/$/.test(path) ? '/' : '';
689 base = base.split('/');
690 path = path.split('/');
691
692 // Remove empty chunks
693 each(base, function(k) {
694 if (k)
695 o.push(k);
696 });
697
698 base = o;
699
700 // Merge relURLParts chunks
701 for (i = path.length - 1, o = []; i >= 0; i--) {
702 // Ignore empty or .
703 if (path[i].length == 0 || path[i] == ".")
704 continue;
705
706 // Is parent
707 if (path[i] == '..') {
708 nb++;
709 continue;
710 }
711
712 // Move up
713 if (nb > 0) {
714 nb--;
715 continue;
716 }
717
718 o.push(path[i]);
719 }
720
721 i = base.length - nb;
722
723 // If /a/b/c or /
724 if (i <= 0)
725 outPath = o.reverse().join('/');
726 else
727 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
728
729 // Add front / if it's needed
730 if (outPath.indexOf('/') !== 0)
731 outPath = '/' + outPath;
732
733 // Add traling / if it's needed
734 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
735 outPath += tr;
736
737 return outPath;
738 },
739
740 getURI : function(nh) {
741 var s, t = this;
742
743 // Rebuild source
744 if (!t.source || nh) {
745 s = '';
746
747 if (!nh) {
748 if (t.protocol)
749 s += t.protocol + '://';
750
751 if (t.userInfo)
752 s += t.userInfo + '@';
753
754 if (t.host)
755 s += t.host;
756
757 if (t.port)
758 s += ':' + t.port;
759 }
760
761 if (t.path)
762 s += t.path;
763
764 if (t.query)
765 s += '?' + t.query;
766
767 if (t.anchor)
768 s += '#' + t.anchor;
769
770 t.source = s;
771 }
772
773 return t.source;
774 }
775 });
776})();
777(function() {
778 var each = tinymce.each;
779
780 tinymce.create('static tinymce.util.Cookie', {
781 getHash : function(n) {
782 var v = this.get(n), h;
783
784 if (v) {
785 each(v.split('&'), function(v) {
786 v = v.split('=');
787 h = h || {};
788 h[unescape(v[0])] = unescape(v[1]);
789 });
790 }
791
792 return h;
793 },
794
795 setHash : function(n, v, e, p, d, s) {
796 var o = '';
797
798 each(v, function(v, k) {
799 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
800 });
801
802 this.set(n, o, e, p, d, s);
803 },
804
805 get : function(n) {
806 var c = document.cookie, e, p = n + "=", b;
807
808 // Strict mode
809 if (!c)
810 return;
811
812 b = c.indexOf("; " + p);
813
814 if (b == -1) {
815 b = c.indexOf(p);
816
817 if (b != 0)
818 return null;
819 } else
820 b += 2;
821
822 e = c.indexOf(";", b);
823
824 if (e == -1)
825 e = c.length;
826
827 return unescape(c.substring(b + p.length, e));
828 },
829
830 set : function(n, v, e, p, d, s) {
831 document.cookie = n + "=" + escape(v) +
832 ((e) ? "; expires=" + e.toGMTString() : "") +
833 ((p) ? "; path=" + escape(p) : "") +
834 ((d) ? "; domain=" + d : "") +
835 ((s) ? "; secure" : "");
836 },
837
838 remove : function(n, p) {
839 var d = new Date();
840
841 d.setTime(d.getTime() - 1000);
842
843 this.set(n, '', d, p, d);
844 }
845 });
846})();
847(function() {
848 function serialize(o, quote) {
849 var i, v, t;
850
851 quote = quote || '"';
852
853 if (o == null)
854 return 'null';
855
856 t = typeof o;
857
858 if (t == 'string') {
859 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
860
861 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
862 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
863 if (quote === '"' && a === "'")
864 return a;
865
866 i = v.indexOf(b);
867
868 if (i + 1)
869 return '\\' + v.charAt(i + 1);
870
871 a = b.charCodeAt().toString(16);
872
873 return '\\u' + '0000'.substring(a.length) + a;
874 }) + quote;
875 }
876
877 if (t == 'object') {
878 if (o.hasOwnProperty && o instanceof Array) {
879 for (i=0, v = '['; i<o.length; i++)
880 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
881
882 return v + ']';
883 }
884
885 v = '{';
886
887 for (i in o)
888 v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
889
890 return v + '}';
891 }
892
893 return '' + o;
894 };
895
896 tinymce.util.JSON = {
897 serialize: serialize,
898
899 parse: function(s) {
900 try {
901 return eval('(' + s + ')');
902 } catch (ex) {
903 // Ignore
904 }
905 }
906
907 };
908})();
909tinymce.create('static tinymce.util.XHR', {
910 send : function(o) {
911 var x, t, w = window, c = 0;
912
913 // Default settings
914 o.scope = o.scope || this;
915 o.success_scope = o.success_scope || o.scope;
916 o.error_scope = o.error_scope || o.scope;
917 o.async = o.async === false ? false : true;
918 o.data = o.data || '';
919
920 function get(s) {
921 x = 0;
922
923 try {
924 x = new ActiveXObject(s);
925 } catch (ex) {
926 }
927
928 return x;
929 };
930
931 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
932
933 if (x) {
934 if (x.overrideMimeType)
935 x.overrideMimeType(o.content_type);
936
937 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
938
939 if (o.content_type)
940 x.setRequestHeader('Content-Type', o.content_type);
941
942 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
943
944 x.send(o.data);
945
946 function ready() {
947 if (!o.async || x.readyState == 4 || c++ > 10000) {
948 if (o.success && c < 10000 && x.status == 200)
949 o.success.call(o.success_scope, '' + x.responseText, x, o);
950 else if (o.error)
951 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
952
953 x = null;
954 } else
955 w.setTimeout(ready, 10);
956 };
957
958 // Syncronous request
959 if (!o.async)
960 return ready();
961
962 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
963 t = w.setTimeout(ready, 10);
964 }
965 }
966});
967(function() {
968 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
969
970 tinymce.create('tinymce.util.JSONRequest', {
971 JSONRequest : function(s) {
972 this.settings = extend({
973 }, s);
974 this.count = 0;
975 },
976
977 send : function(o) {
978 var ecb = o.error, scb = o.success;
979
980 o = extend(this.settings, o);
981
982 o.success = function(c, x) {
983 c = JSON.parse(c);
984
985 if (typeof(c) == 'undefined') {
986 c = {
987 error : 'JSON Parse error.'
988 };
989 }
990
991 if (c.error)
992 ecb.call(o.error_scope || o.scope, c.error, x);
993 else
994 scb.call(o.success_scope || o.scope, c.result);
995 };
996
997 o.error = function(ty, x) {
998 if (ecb)
999 ecb.call(o.error_scope || o.scope, ty, x);
1000 };
1001
1002 o.data = JSON.serialize({
1003 id : o.id || 'c' + (this.count++),
1004 method : o.method,
1005 params : o.params
1006 });
1007
1008 // JSON content type for Ruby on rails. Bug: #1883287
1009 o.content_type = 'application/json';
1010
1011 XHR.send(o);
1012 },
1013
1014 'static' : {
1015 sendRPC : function(o) {
1016 return new tinymce.util.JSONRequest().send(o);
1017 }
1018 }
1019 });
1020}());
1021(function(tinymce) {
1022 var namedEntities, baseEntities, reverseEntities,
1023 attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1024 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1025 rawCharsRegExp = /[<>&\"\']/g,
1026 entityRegExp = /&(#)?([\w]+);/g,
1027 asciiMap = {
1028 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1029 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1030 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1031 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1032 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1033 };
1034
1035 // Raw entities
1036 baseEntities = {
1037 '"' : '&quot;',
1038 "'" : '&#39;',
1039 '<' : '&lt;',
1040 '>' : '&gt;',
1041 '&' : '&amp;'
1042 };
1043
1044 // Reverse lookup table for raw entities
1045 reverseEntities = {
1046 '&lt;' : '<',
1047 '&gt;' : '>',
1048 '&amp;' : '&',
1049 '&quot;' : '"',
1050 '&apos;' : "'"
1051 };
1052
1053 // Decodes text by using the browser
1054 function nativeDecode(text) {
1055 var elm;
1056
1057 elm = document.createElement("div");
1058 elm.innerHTML = text;
1059
1060 return elm.textContent || elm.innerText || text;
1061 };
1062
1063 // Build a two way lookup table for the entities
1064 function buildEntitiesLookup(items, radix) {
1065 var i, chr, entity, lookup = {};
1066
1067 if (items) {
1068 items = items.split(',');
1069 radix = radix || 10;
1070
1071 // Build entities lookup table
1072 for (i = 0; i < items.length; i += 2) {
1073 chr = String.fromCharCode(parseInt(items[i], radix));
1074
1075 // Only add non base entities
1076 if (!baseEntities[chr]) {
1077 entity = '&' + items[i + 1] + ';';
1078 lookup[chr] = entity;
1079 lookup[entity] = chr;
1080 }
1081 }
1082
1083 return lookup;
1084 }
1085 };
1086
1087 // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1088 namedEntities = buildEntitiesLookup(
1089 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1090 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1091 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1092 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1093 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1094 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1095 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1096 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1097 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1098 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1099 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1100 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1101 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1102 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1103 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1104 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1105 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1106 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1107 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1108 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1109 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1110 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1111 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1112 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1113 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1114 , 32);
1115
1116 tinymce.html = tinymce.html || {};
1117
1118 tinymce.html.Entities = {
1119 encodeRaw : function(text, attr) {
1120 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1121 return baseEntities[chr] || chr;
1122 });
1123 },
1124
1125 encodeAllRaw : function(text) {
1126 return ('' + text).replace(rawCharsRegExp, function(chr) {
1127 return baseEntities[chr] || chr;
1128 });
1129 },
1130
1131 encodeNumeric : function(text, attr) {
1132 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1133 // Multi byte sequence convert it to a single entity
1134 if (chr.length > 1)
1135 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1136
1137 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1138 });
1139 },
1140
1141 encodeNamed : function(text, attr, entities) {
1142 entities = entities || namedEntities;
1143
1144 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1145 return baseEntities[chr] || entities[chr] || chr;
1146 });
1147 },
1148
1149 getEncodeFunc : function(name, entities) {
1150 var Entities = tinymce.html.Entities;
1151
1152 entities = buildEntitiesLookup(entities) || namedEntities;
1153
1154 function encodeNamedAndNumeric(text, attr) {
1155 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1156 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1157 });
1158 };
1159
1160 function encodeCustomNamed(text, attr) {
1161 return Entities.encodeNamed(text, attr, entities);
1162 };
1163
1164 // Replace + with , to be compatible with previous TinyMCE versions
1165 name = tinymce.makeMap(name.replace(/\+/g, ','));
1166
1167 // Named and numeric encoder
1168 if (name.named && name.numeric)
1169 return encodeNamedAndNumeric;
1170
1171 // Named encoder
1172 if (name.named) {
1173 // Custom names
1174 if (entities)
1175 return encodeCustomNamed;
1176
1177 return Entities.encodeNamed;
1178 }
1179
1180 // Numeric
1181 if (name.numeric)
1182 return Entities.encodeNumeric;
1183
1184 // Raw encoder
1185 return Entities.encodeRaw;
1186 },
1187
1188 decode : function(text) {
1189 return text.replace(entityRegExp, function(all, numeric, value) {
1190 if (numeric) {
1191 value = parseInt(value);
1192
1193 // Support upper UTF
1194 if (value > 0xFFFF) {
1195 value -= 0x10000;
1196
1197 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1198 } else
1199 return asciiMap[value] || String.fromCharCode(value);
1200 }
1201
1202 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1203 });
1204 }
1205 };
1206})(tinymce);
1207tinymce.html.Styles = function(settings, schema) {
1208 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1209 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1210 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1211 trimRightRegExp = /\s+$/,
1212 urlColorRegExp = /rgb/,
1213 undef, i, encodingLookup = {}, encodingItems;
1214
1215 settings = settings || {};
1216
1217 encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
1218 for (i = 0; i < encodingItems.length; i++) {
1219 encodingLookup[encodingItems[i]] = '_' + i;
1220 encodingLookup['_' + i] = encodingItems[i];
1221 }
1222
1223 function toHex(match, r, g, b) {
1224 function hex(val) {
1225 val = parseInt(val).toString(16);
1226
1227 return val.length > 1 ? val : '0' + val; // 0 -> 00
1228 };
1229
1230 return '#' + hex(r) + hex(g) + hex(b);
1231 };
1232
1233 return {
1234 toHex : function(color) {
1235 return color.replace(rgbRegExp, toHex);
1236 },
1237
1238 parse : function(css) {
1239 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1240
1241 function compress(prefix, suffix) {
1242 var top, right, bottom, left;
1243
1244 // Get values and check it it needs compressing
1245 top = styles[prefix + '-top' + suffix];
1246 if (!top)
1247 return;
1248
1249 right = styles[prefix + '-right' + suffix];
1250 if (top != right)
1251 return;
1252
1253 bottom = styles[prefix + '-bottom' + suffix];
1254 if (right != bottom)
1255 return;
1256
1257 left = styles[prefix + '-left' + suffix];
1258 if (bottom != left)
1259 return;
1260
1261 // Compress
1262 styles[prefix + suffix] = left;
1263 delete styles[prefix + '-top' + suffix];
1264 delete styles[prefix + '-right' + suffix];
1265 delete styles[prefix + '-bottom' + suffix];
1266 delete styles[prefix + '-left' + suffix];
1267 };
1268
1269 function canCompress(key) {
1270 var value = styles[key], i;
1271
1272 if (!value || value.indexOf(' ') < 0)
1273 return;
1274
1275 value = value.split(' ');
1276 i = value.length;
1277 while (i--) {
1278 if (value[i] !== value[0])
1279 return false;
1280 }
1281
1282 styles[key] = value[0];
1283
1284 return true;
1285 };
1286
1287 function compress2(target, a, b, c) {
1288 if (!canCompress(a))
1289 return;
1290
1291 if (!canCompress(b))
1292 return;
1293
1294 if (!canCompress(c))
1295 return;
1296
1297 // Compress
1298 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1299 delete styles[a];
1300 delete styles[b];
1301 delete styles[c];
1302 };
1303
1304 // Encodes the specified string by replacing all \" \' ; : with _<num>
1305 function encode(str) {
1306 isEncoded = true;
1307
1308 return encodingLookup[str];
1309 };
1310
1311 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1312 // It will also decode the \" \' if keep_slashes is set to fale or omitted
1313 function decode(str, keep_slashes) {
1314 if (isEncoded) {
1315 str = str.replace(/_[0-9]/g, function(str) {
1316 return encodingLookup[str];
1317 });
1318 }
1319
1320 if (!keep_slashes)
1321 str = str.replace(/\\([\'\";:])/g, "$1");
1322
1323 return str;
1324 }
1325
1326 if (css) {
1327 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1328 css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1329 return str.replace(/[;:]/g, encode);
1330 });
1331
1332 // Parse styles
1333 while (matches = styleRegExp.exec(css)) {
1334 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1335 value = matches[2].replace(trimRightRegExp, '');
1336
1337 if (name && value.length > 0) {
1338 // Opera will produce 700 instead of bold in their style values
1339 if (name === 'font-weight' && value === '700')
1340 value = 'bold';
1341 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1342 value = value.toLowerCase();
1343
1344 // Convert RGB colors to HEX
1345 value = value.replace(rgbRegExp, toHex);
1346
1347 // Convert URLs and force them into url('value') format
1348 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1349 str = str || str2;
1350
1351 if (str) {
1352 str = decode(str);
1353
1354 // Force strings into single quote format
1355 return "'" + str.replace(/\'/g, "\\'") + "'";
1356 }
1357
1358 url = decode(url || url2 || url3);
1359
1360 // Convert the URL to relative/absolute depending on config
1361 if (urlConverter)
1362 url = urlConverter.call(urlConverterScope, url, 'style');
1363
1364 // Output new URL format
1365 return "url('" + url.replace(/\'/g, "\\'") + "')";
1366 });
1367
1368 styles[name] = isEncoded ? decode(value, true) : value;
1369 }
1370
1371 styleRegExp.lastIndex = matches.index + matches[0].length;
1372 }
1373
1374 // Compress the styles to reduce it's size for example IE will expand styles
1375 compress("border", "");
1376 compress("border", "-width");
1377 compress("border", "-color");
1378 compress("border", "-style");
1379 compress("padding", "");
1380 compress("margin", "");
1381 compress2('border', 'border-width', 'border-style', 'border-color');
1382
1383 // Remove pointless border, IE produces these
1384 if (styles.border === 'medium none')
1385 delete styles.border;
1386 }
1387
1388 return styles;
1389 },
1390
1391 serialize : function(styles, element_name) {
1392 var css = '', name, value;
1393
1394 function serializeStyles(name) {
1395 var styleList, i, l, name, value;
1396
1397 styleList = schema.styles[name];
1398 if (styleList) {
1399 for (i = 0, l = styleList.length; i < l; i++) {
1400 name = styleList[i];
1401 value = styles[name];
1402
1403 if (value !== undef && value.length > 0)
1404 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1405 }
1406 }
1407 };
1408
1409 // Serialize styles according to schema
1410 if (element_name && schema && schema.styles) {
1411 // Serialize global styles and element specific styles
1412 serializeStyles('*');
1413 serializeStyles(name);
1414 } else {
1415 // Output the styles in the order they are inside the object
1416 for (name in styles) {
1417 value = styles[name];
1418
1419 if (value !== undef && value.length > 0)
1420 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1421 }
1422 }
1423
1424 return css;
1425 }
1426 };
1427};
1428(function(tinymce) {
1429 var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
1430 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1431
1432 function split(str, delim) {
1433 return str.split(delim || ',');
1434 };
1435
1436 function unpack(lookup, data) {
1437 var key, elements = {};
1438
1439 function replace(value) {
1440 return value.replace(/[A-Z]+/g, function(key) {
1441 return replace(lookup[key]);
1442 });
1443 };
1444
1445 // Unpack lookup
1446 for (key in lookup) {
1447 if (lookup.hasOwnProperty(key))
1448 lookup[key] = replace(lookup[key]);
1449 }
1450
1451 // Unpack and parse data into object map
1452 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1453 attributes = split(attributes, '|');
1454
1455 elements[name] = {
1456 attributes : makeMap(attributes),
1457 attributesOrder : attributes,
1458 children : makeMap(children, '|', {'#comment' : {}})
1459 }
1460 });
1461
1462 return elements;
1463 };
1464
1465 // Build a lookup table for block elements both lowercase and uppercase
1466 blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' +
1467 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' +
1468 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1469 blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1470
1471 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1472 transitional = unpack({
1473 Z : 'H|K|N|O|P',
1474 Y : 'X|form|R|Q',
1475 ZG : 'E|span|width|align|char|charoff|valign',
1476 X : 'p|T|div|U|W|isindex|fieldset|table',
1477 ZF : 'E|align|char|charoff|valign',
1478 W : 'pre|hr|blockquote|address|center|noframes',
1479 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1480 ZD : '[E][S]',
1481 U : 'ul|ol|dl|menu|dir',
1482 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1483 T : 'h1|h2|h3|h4|h5|h6',
1484 ZB : 'X|S|Q',
1485 S : 'R|P',
1486 ZA : 'a|G|J|M|O|P',
1487 R : 'a|H|K|N|O',
1488 Q : 'noscript|P',
1489 P : 'ins|del|script',
1490 O : 'input|select|textarea|label|button',
1491 N : 'M|L',
1492 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1493 L : 'sub|sup',
1494 K : 'J|I',
1495 J : 'tt|i|b|u|s|strike',
1496 I : 'big|small|font|basefont',
1497 H : 'G|F',
1498 G : 'br|span|bdo',
1499 F : 'object|applet|img|map|iframe',
1500 E : 'A|B|C',
1501 D : 'accesskey|tabindex|onfocus|onblur',
1502 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1503 B : 'lang|xml:lang|dir',
1504 A : 'id|class|style|title'
1505 }, 'script[id|charset|type|language|src|defer|xml:space][]' +
1506 'style[B|id|type|media|title|xml:space][]' +
1507 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
1508 'param[id|name|value|valuetype|type][]' +
1509 'p[E|align][#|S]' +
1510 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
1511 'br[A|clear][]' +
1512 'span[E][#|S]' +
1513 'bdo[A|C|B][#|S]' +
1514 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
1515 'h1[E|align][#|S]' +
1516 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
1517 'map[B|C|A|name][X|form|Q|area]' +
1518 'h2[E|align][#|S]' +
1519 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
1520 'h3[E|align][#|S]' +
1521 'tt[E][#|S]' +
1522 'i[E][#|S]' +
1523 'b[E][#|S]' +
1524 'u[E][#|S]' +
1525 's[E][#|S]' +
1526 'strike[E][#|S]' +
1527 'big[E][#|S]' +
1528 'small[E][#|S]' +
1529 'font[A|B|size|color|face][#|S]' +
1530 'basefont[id|size|color|face][]' +
1531 'em[E][#|S]' +
1532 'strong[E][#|S]' +
1533 'dfn[E][#|S]' +
1534 'code[E][#|S]' +
1535 'q[E|cite][#|S]' +
1536 'samp[E][#|S]' +
1537 'kbd[E][#|S]' +
1538 'var[E][#|S]' +
1539 'cite[E][#|S]' +
1540 'abbr[E][#|S]' +
1541 'acronym[E][#|S]' +
1542 'sub[E][#|S]' +
1543 'sup[E][#|S]' +
1544 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
1545 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
1546 'optgroup[E|disabled|label][option]' +
1547 'option[E|selected|disabled|label|value][]' +
1548 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
1549 'label[E|for|accesskey|onfocus|onblur][#|S]' +
1550 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
1551 'h4[E|align][#|S]' +
1552 'ins[E|cite|datetime][#|Y]' +
1553 'h5[E|align][#|S]' +
1554 'del[E|cite|datetime][#|Y]' +
1555 'h6[E|align][#|S]' +
1556 'div[E|align][#|Y]' +
1557 'ul[E|type|compact][li]' +
1558 'li[E|type|value][#|Y]' +
1559 'ol[E|type|compact|start][li]' +
1560 'dl[E|compact][dt|dd]' +
1561 'dt[E][#|S]' +
1562 'dd[E][#|Y]' +
1563 'menu[E|compact][li]' +
1564 'dir[E|compact][li]' +
1565 'pre[E|width|xml:space][#|ZA]' +
1566 'hr[E|align|noshade|size|width][]' +
1567 'blockquote[E|cite][#|Y]' +
1568 'address[E][#|S|p]' +
1569 'center[E][#|Y]' +
1570 'noframes[E][#|Y]' +
1571 'isindex[A|B|prompt][]' +
1572 'fieldset[E][#|legend|Y]' +
1573 'legend[E|accesskey|align][#|S]' +
1574 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
1575 'caption[E|align][#|S]' +
1576 'col[ZG][]' +
1577 'colgroup[ZG][col]' +
1578 'thead[ZF][tr]' +
1579 'tr[ZF|bgcolor][th|td]' +
1580 'th[E|ZE][#|Y]' +
1581 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
1582 'noscript[E][#|Y]' +
1583 'td[E|ZE][#|Y]' +
1584 'tfoot[ZF][tr]' +
1585 'tbody[ZF][tr]' +
1586 'area[E|D|shape|coords|href|nohref|alt|target][]' +
1587 'base[id|href|target][]' +
1588 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1589 );
1590
1591 boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
1592 shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1593 nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
1594 whiteSpaceElementsMap = makeMap('pre,script,style');
1595 selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1596
1597 tinymce.html.Schema = function(settings) {
1598 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1599
1600 settings = settings || {};
1601
1602 // Allow all elements and attributes if verify_html is set to false
1603 if (settings.verify_html === false)
1604 settings.valid_elements = '*[*]';
1605
1606 // Build styles list
1607 if (settings.valid_styles) {
1608 validStyles = {};
1609
1610 // Convert styles into a rule list
1611 each(settings.valid_styles, function(value, key) {
1612 validStyles[key] = tinymce.explode(value);
1613 });
1614 }
1615
1616 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1617 function patternToRegExp(str) {
1618 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1619 };
1620
1621 // Parses the specified valid_elements string and adds to the current rules
1622 // This function is a bit hard to read since it's heavily optimized for speed
1623 function addValidElements(valid_elements) {
1624 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1625 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1626 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1627 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1628 hasPatternsRegExp = /[*?+]/;
1629
1630 if (valid_elements) {
1631 // Split valid elements into an array with rules
1632 valid_elements = split(valid_elements);
1633
1634 if (elements['@']) {
1635 globalAttributes = elements['@'].attributes;
1636 globalAttributesOrder = elements['@'].attributesOrder;
1637 }
1638
1639 // Loop all rules
1640 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1641 // Parse element rule
1642 matches = elementRuleRegExp.exec(valid_elements[ei]);
1643 if (matches) {
1644 // Setup local names for matches
1645 prefix = matches[1];
1646 elementName = matches[2];
1647 outputName = matches[3];
1648 attrData = matches[4];
1649
1650 // Create new attributes and attributesOrder
1651 attributes = {};
1652 attributesOrder = [];
1653
1654 // Create the new element
1655 element = {
1656 attributes : attributes,
1657 attributesOrder : attributesOrder
1658 };
1659
1660 // Padd empty elements prefix
1661 if (prefix === '#')
1662 element.paddEmpty = true;
1663
1664 // Remove empty elements prefix
1665 if (prefix === '-')
1666 element.removeEmpty = true;
1667
1668 // Copy attributes from global rule into current rule
1669 if (globalAttributes) {
1670 for (key in globalAttributes)
1671 attributes[key] = globalAttributes[key];
1672
1673 attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
1674 }
1675
1676 // Attributes defined
1677 if (attrData) {
1678 attrData = split(attrData, '|');
1679 for (ai = 0, al = attrData.length; ai < al; ai++) {
1680 matches = attrRuleRegExp.exec(attrData[ai]);
1681 if (matches) {
1682 attr = {};
1683 attrType = matches[1];
1684 attrName = matches[2].replace(/::/g, ':');
1685 prefix = matches[3];
1686 value = matches[4];
1687
1688 // Required
1689 if (attrType === '!') {
1690 element.attributesRequired = element.attributesRequired || [];
1691 element.attributesRequired.push(attrName);
1692 attr.required = true;
1693 }
1694
1695 // Denied from global
1696 if (attrType === '-') {
1697 delete attributes[attrName];
1698 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
1699 continue;
1700 }
1701
1702 // Default value
1703 if (prefix) {
1704 // Default value
1705 if (prefix === '=') {
1706 element.attributesDefault = element.attributesDefault || [];
1707 element.attributesDefault.push({name: attrName, value: value});
1708 attr.defaultValue = value;
1709 }
1710
1711 // Forced value
1712 if (prefix === ':') {
1713 element.attributesForced = element.attributesForced || [];
1714 element.attributesForced.push({name: attrName, value: value});
1715 attr.forcedValue = value;
1716 }
1717
1718 // Required values
1719 if (prefix === '<')
1720 attr.validValues = makeMap(value, '?');
1721 }
1722
1723 // Check for attribute patterns
1724 if (hasPatternsRegExp.test(attrName)) {
1725 element.attributePatterns = element.attributePatterns || [];
1726 attr.pattern = patternToRegExp(attrName);
1727 element.attributePatterns.push(attr);
1728 } else {
1729 // Add attribute to order list if it doesn't already exist
1730 if (!attributes[attrName])
1731 attributesOrder.push(attrName);
1732
1733 attributes[attrName] = attr;
1734 }
1735 }
1736 }
1737 }
1738
1739 // Global rule, store away these for later usage
1740 if (!globalAttributes && elementName == '@') {
1741 globalAttributes = attributes;
1742 globalAttributesOrder = attributesOrder;
1743 }
1744
1745 // Handle substitute elements such as b/strong
1746 if (outputName) {
1747 element.outputName = elementName;
1748 elements[outputName] = element;
1749 }
1750
1751 // Add pattern or exact element
1752 if (hasPatternsRegExp.test(elementName)) {
1753 element.pattern = patternToRegExp(elementName);
1754 patternElements.push(element);
1755 } else
1756 elements[elementName] = element;
1757 }
1758 }
1759 }
1760 };
1761
1762 function setValidElements(valid_elements) {
1763 elements = {};
1764 patternElements = [];
1765
1766 addValidElements(valid_elements);
1767
1768 each(transitional, function(element, name) {
1769 children[name] = element.children;
1770 });
1771 };
1772
1773 // Adds custom non HTML elements to the schema
1774 function addCustomElements(custom_elements) {
1775 var customElementRegExp = /^(~)?(.+)$/;
1776
1777 if (custom_elements) {
1778 each(split(custom_elements), function(rule) {
1779 var matches = customElementRegExp.exec(rule),
1780 cloneName = matches[1] === '~' ? 'span' : 'div',
1781 name = matches[2];
1782
1783 children[name] = children[cloneName];
1784
1785 // Add custom elements at span/div positions
1786 each(children, function(element, child) {
1787 if (element[cloneName])
1788 element[name] = element[cloneName];
1789 });
1790 });
1791 }
1792 };
1793
1794 // Adds valid children to the schema object
1795 function addValidChildren(valid_children) {
1796 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
1797
1798 if (valid_children) {
1799 each(split(valid_children), function(rule) {
1800 var matches = childRuleRegExp.exec(rule), parent, prefix;
1801
1802 if (matches) {
1803 prefix = matches[1];
1804
1805 // Add/remove items from default
1806 if (prefix)
1807 parent = children[matches[2]];
1808 else
1809 parent = children[matches[2]] = {'#comment' : {}};
1810
1811 parent = children[matches[2]];
1812
1813 each(split(matches[3], '|'), function(child) {
1814 if (prefix === '-')
1815 delete parent[child];
1816 else
1817 parent[child] = {};
1818 });
1819 }
1820 });
1821 }
1822 }
1823
1824 if (!settings.valid_elements) {
1825 // No valid elements defined then clone the elements from the transitional spec
1826 each(transitional, function(element, name) {
1827 elements[name] = {
1828 attributes : element.attributes,
1829 attributesOrder : element.attributesOrder
1830 };
1831
1832 children[name] = element.children;
1833 });
1834
1835 // Switch these
1836 each(split('strong/b,em/i'), function(item) {
1837 item = split(item, '/');
1838 elements[item[1]].outputName = item[0];
1839 });
1840
1841 // Add default alt attribute for images
1842 elements.img.attributesDefault = [{name: 'alt', value: ''}];
1843
1844 // Remove these if they are empty by default
1845 each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
1846 elements[name].removeEmpty = true;
1847 });
1848
1849 // Padd these by default
1850 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
1851 elements[name].paddEmpty = true;
1852 });
1853 } else
1854 setValidElements(settings.valid_elements);
1855
1856 addCustomElements(settings.custom_elements);
1857 addValidChildren(settings.valid_children);
1858 addValidElements(settings.extended_valid_elements);
1859
1860 // Todo: Remove this when we fix list handling to be valid
1861 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
1862
1863 // Delete invalid elements
1864 if (settings.invalid_elements) {
1865 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
1866 if (elements[item])
1867 delete elements[item];
1868 });
1869 }
1870
1871 self.children = children;
1872
1873 self.styles = validStyles;
1874
1875 self.getBoolAttrs = function() {
1876 return boolAttrMap;
1877 };
1878
1879 self.getBlockElements = function() {
1880 return blockElementsMap;
1881 };
1882
1883 self.getShortEndedElements = function() {
1884 return shortEndedElementsMap;
1885 };
1886
1887 self.getSelfClosingElements = function() {
1888 return selfClosingElementsMap;
1889 };
1890
1891 self.getNonEmptyElements = function() {
1892 return nonEmptyElementsMap;
1893 };
1894
1895 self.getWhiteSpaceElements = function() {
1896 return whiteSpaceElementsMap;
1897 };
1898
1899 self.isValidChild = function(name, child) {
1900 var parent = children[name];
1901
1902 return !!(parent && parent[child]);
1903 };
1904
1905 self.getElementRule = function(name) {
1906 var element = elements[name], i;
1907
1908 // Exact match found
1909 if (element)
1910 return element;
1911
1912 // No exact match then try the patterns
1913 i = patternElements.length;
1914 while (i--) {
1915 element = patternElements[i];
1916
1917 if (element.pattern.test(name))
1918 return element;
1919 }
1920 };
1921
1922 self.addValidElements = addValidElements;
1923
1924 self.setValidElements = setValidElements;
1925
1926 self.addCustomElements = addCustomElements;
1927
1928 self.addValidChildren = addValidChildren;
1929 };
1930
1931 // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
1932 tinymce.html.Schema.boolAttrMap = boolAttrMap;
1933 tinymce.html.Schema.blockElementsMap = blockElementsMap;
1934})(tinymce);
1935(function(tinymce) {
1936 tinymce.html.SaxParser = function(settings, schema) {
1937 var self = this, noop = function() {};
1938
1939 settings = settings || {};
1940 self.schema = schema = schema || new tinymce.html.Schema();
1941
1942 if (settings.fix_self_closing !== false)
1943 settings.fix_self_closing = true;
1944
1945 // Add handler functions from settings and setup default handlers
1946 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
1947 if (name)
1948 self[name] = settings[name] || noop;
1949 });
1950
1951 self.parse = function(html) {
1952 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
1953 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
1954 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
1955 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
1956
1957 function processEndTag(name) {
1958 var pos, i;
1959
1960 // Find position of parent of the same type
1961 pos = stack.length;
1962 while (pos--) {
1963 if (stack[pos].name === name)
1964 break;
1965 }
1966
1967 // Found parent
1968 if (pos >= 0) {
1969 // Close all the open elements
1970 for (i = stack.length - 1; i >= pos; i--) {
1971 name = stack[i];
1972
1973 if (name.valid)
1974 self.end(name.name);
1975 }
1976
1977 // Remove the open elements from the stack
1978 stack.length = pos;
1979 }
1980 };
1981
1982 // Precompile RegExps and map objects
1983 tokenRegExp = new RegExp('<(?:' +
1984 '(?:!--([\\w\\W]*?)-->)|' + // Comment
1985 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
1986 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
1987 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
1988 '(?:\\/([^>]+)>)|' + // End element
1989 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
1990 ')', 'g');
1991
1992 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
1993 specialElements = {
1994 'script' : /<\/script[^>]*>/gi,
1995 'style' : /<\/style[^>]*>/gi,
1996 'noscript' : /<\/noscript[^>]*>/gi
1997 };
1998
1999 // Setup lookup tables for empty elements and boolean attributes
2000 shortEndedElements = schema.getShortEndedElements();
2001 selfClosing = schema.getSelfClosingElements();
2002 fillAttrsMap = schema.getBoolAttrs();
2003 validate = settings.validate;
2004 fixSelfClosing = settings.fix_self_closing;
2005
2006 while (matches = tokenRegExp.exec(html)) {
2007 // Text
2008 if (index < matches.index)
2009 self.text(decode(html.substr(index, matches.index - index)));
2010
2011 if (value = matches[6]) { // End element
2012 processEndTag(value.toLowerCase());
2013 } else if (value = matches[7]) { // Start element
2014 value = value.toLowerCase();
2015 isShortEnded = value in shortEndedElements;
2016
2017 // Is self closing tag for example an <li> after an open <li>
2018 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2019 processEndTag(value);
2020
2021 // Validate element
2022 if (!validate || (elementRule = schema.getElementRule(value))) {
2023 isValidElement = true;
2024
2025 // Grab attributes map and patters when validation is enabled
2026 if (validate) {
2027 validAttributesMap = elementRule.attributes;
2028 validAttributePatterns = elementRule.attributePatterns;
2029 }
2030
2031 // Parse attributes
2032 if (attribsValue = matches[8]) {
2033 attrList = [];
2034 attrList.map = {};
2035
2036 attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2037 var attrRule, i;
2038
2039 name = name.toLowerCase();
2040 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2041
2042 // Validate name and value
2043 if (validate && name.indexOf('data-') !== 0) {
2044 attrRule = validAttributesMap[name];
2045
2046 // Find rule by pattern matching
2047 if (!attrRule && validAttributePatterns) {
2048 i = validAttributePatterns.length;
2049 while (i--) {
2050 attrRule = validAttributePatterns[i];
2051 if (attrRule.pattern.test(name))
2052 break;
2053 }
2054
2055 // No rule matched
2056 if (i === -1)
2057 attrRule = null;
2058 }
2059
2060 // No attribute rule found
2061 if (!attrRule)
2062 return;
2063
2064 // Validate value
2065 if (attrRule.validValues && !(value in attrRule.validValues))
2066 return;
2067 }
2068
2069 // Add attribute to list and map
2070 attrList.map[name] = value;
2071 attrList.push({
2072 name: name,
2073 value: value
2074 });
2075 });
2076 } else {
2077 attrList = [];
2078 attrList.map = {};
2079 }
2080
2081 // Process attributes if validation is enabled
2082 if (validate) {
2083 attributesRequired = elementRule.attributesRequired;
2084 attributesDefault = elementRule.attributesDefault;
2085 attributesForced = elementRule.attributesForced;
2086
2087 // Handle forced attributes
2088 if (attributesForced) {
2089 i = attributesForced.length;
2090 while (i--) {
2091 attr = attributesForced[i];
2092 name = attr.name;
2093 attrValue = attr.value;
2094
2095 if (attrValue === '{$uid}')
2096 attrValue = 'mce_' + idCount++;
2097
2098 attrList.map[name] = attrValue;
2099 attrList.push({name: name, value: attrValue});
2100 }
2101 }
2102
2103 // Handle default attributes
2104 if (attributesDefault) {
2105 i = attributesDefault.length;
2106 while (i--) {
2107 attr = attributesDefault[i];
2108 name = attr.name;
2109
2110 if (!(name in attrList.map)) {
2111 attrValue = attr.value;
2112
2113 if (attrValue === '{$uid}')
2114 attrValue = 'mce_' + idCount++;
2115
2116 attrList.map[name] = attrValue;
2117 attrList.push({name: name, value: attrValue});
2118 }
2119 }
2120 }
2121
2122 // Handle required attributes
2123 if (attributesRequired) {
2124 i = attributesRequired.length;
2125 while (i--) {
2126 if (attributesRequired[i] in attrList.map)
2127 break;
2128 }
2129
2130 // None of the required attributes where found
2131 if (i === -1)
2132 isValidElement = false;
2133 }
2134
2135 // Invalidate element if it's marked as bogus
2136 if (attrList.map['data-mce-bogus'])
2137 isValidElement = false;
2138 }
2139
2140 if (isValidElement)
2141 self.start(value, attrList, isShortEnded);
2142 } else
2143 isValidElement = false;
2144
2145 // Treat script, noscript and style a bit different since they may include code that looks like elements
2146 if (endRegExp = specialElements[value]) {
2147 endRegExp.lastIndex = index = matches.index + matches[0].length;
2148
2149 if (matches = endRegExp.exec(html)) {
2150 if (isValidElement)
2151 text = html.substr(index, matches.index - index);
2152
2153 index = matches.index + matches[0].length;
2154 } else {
2155 text = html.substr(index);
2156 index = html.length;
2157 }
2158
2159 if (isValidElement && text.length > 0)
2160 self.text(text, true);
2161
2162 if (isValidElement)
2163 self.end(value);
2164
2165 tokenRegExp.lastIndex = index;
2166 continue;
2167 }
2168
2169 // Push value on to stack
2170 if (!isShortEnded) {
2171 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2172 stack.push({name: value, valid: isValidElement});
2173 else if (isValidElement)
2174 self.end(value);
2175 }
2176 } else if (value = matches[1]) { // Comment
2177 self.comment(value);
2178 } else if (value = matches[2]) { // CDATA
2179 self.cdata(value);
2180 } else if (value = matches[3]) { // DOCTYPE
2181 self.doctype(value);
2182 } else if (value = matches[4]) { // PI
2183 self.pi(value, matches[5]);
2184 }
2185
2186 index = matches.index + matches[0].length;
2187 }
2188
2189 // Text
2190 if (index < html.length)
2191 self.text(decode(html.substr(index)));
2192
2193 // Close any open elements
2194 for (i = stack.length - 1; i >= 0; i--) {
2195 value = stack[i];
2196
2197 if (value.valid)
2198 self.end(value.name);
2199 }
2200 };
2201 }
2202})(tinymce);
2203(function(tinymce) {
2204 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2205 '#text' : 3,
2206 '#comment' : 8,
2207 '#cdata' : 4,
2208 '#pi' : 7,
2209 '#doctype' : 10,
2210 '#document-fragment' : 11
2211 };
2212
2213 // Walks the tree left/right
2214 function walk(node, root_node, prev) {
2215 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2216
2217 // Walk into nodes if it has a start
2218 if (node[startName])
2219 return node[startName];
2220
2221 // Return the sibling if it has one
2222 if (node !== root_node) {
2223 sibling = node[siblingName];
2224
2225 if (sibling)
2226 return sibling;
2227
2228 // Walk up the parents to look for siblings
2229 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2230 sibling = parent[siblingName];
2231
2232 if (sibling)
2233 return sibling;
2234 }
2235 }
2236 };
2237
2238 function Node(name, type) {
2239 this.name = name;
2240 this.type = type;
2241
2242 if (type === 1) {
2243 this.attributes = [];
2244 this.attributes.map = {};
2245 }
2246 }
2247
2248 tinymce.extend(Node.prototype, {
2249 replace : function(node) {
2250 var self = this;
2251
2252 if (node.parent)
2253 node.remove();
2254
2255 self.insert(node, self);
2256 self.remove();
2257
2258 return self;
2259 },
2260
2261 attr : function(name, value) {
2262 var self = this, attrs, i, undef;
2263
2264 if (typeof name !== "string") {
2265 for (i in name)
2266 self.attr(i, name[i]);
2267
2268 return self;
2269 }
2270
2271 if (attrs = self.attributes) {
2272 if (value !== undef) {
2273 // Remove attribute
2274 if (value === null) {
2275 if (name in attrs.map) {
2276 delete attrs.map[name];
2277
2278 i = attrs.length;
2279 while (i--) {
2280 if (attrs[i].name === name) {
2281 attrs = attrs.splice(i, 1);
2282 return self;
2283 }
2284 }
2285 }
2286
2287 return self;
2288 }
2289
2290 // Set attribute
2291 if (name in attrs.map) {
2292 // Set attribute
2293 i = attrs.length;
2294 while (i--) {
2295 if (attrs[i].name === name) {
2296 attrs[i].value = value;
2297 break;
2298 }
2299 }
2300 } else
2301 attrs.push({name: name, value: value});
2302
2303 attrs.map[name] = value;
2304
2305 return self;
2306 } else {
2307 return attrs.map[name];
2308 }
2309 }
2310 },
2311
2312 clone : function() {
2313 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2314
2315 // Clone element attributes
2316 if (selfAttrs = self.attributes) {
2317 cloneAttrs = [];
2318 cloneAttrs.map = {};
2319
2320 for (i = 0, l = selfAttrs.length; i < l; i++) {
2321 selfAttr = selfAttrs[i];
2322
2323 // Clone everything except id
2324 if (selfAttr.name !== 'id') {
2325 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2326 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2327 }
2328 }
2329
2330 clone.attributes = cloneAttrs;
2331 }
2332
2333 clone.value = self.value;
2334 clone.shortEnded = self.shortEnded;
2335
2336 return clone;
2337 },
2338
2339 wrap : function(wrapper) {
2340 var self = this;
2341
2342 self.parent.insert(wrapper, self);
2343 wrapper.append(self);
2344
2345 return self;
2346 },
2347
2348 unwrap : function() {
2349 var self = this, node, next;
2350
2351 for (node = self.firstChild; node; ) {
2352 next = node.next;
2353 self.insert(node, self, true);
2354 node = next;
2355 }
2356
2357 self.remove();
2358 },
2359
2360 remove : function() {
2361 var self = this, parent = self.parent, next = self.next, prev = self.prev;
2362
2363 if (parent) {
2364 if (parent.firstChild === self) {
2365 parent.firstChild = next;
2366
2367 if (next)
2368 next.prev = null;
2369 } else {
2370 prev.next = next;
2371 }
2372
2373 if (parent.lastChild === self) {
2374 parent.lastChild = prev;
2375
2376 if (prev)
2377 prev.next = null;
2378 } else {
2379 next.prev = prev;
2380 }
2381
2382 self.parent = self.next = self.prev = null;
2383 }
2384
2385 return self;
2386 },
2387
2388 append : function(node) {
2389 var self = this, last;
2390
2391 if (node.parent)
2392 node.remove();
2393
2394 last = self.lastChild;
2395 if (last) {
2396 last.next = node;
2397 node.prev = last;
2398 self.lastChild = node;
2399 } else
2400 self.lastChild = self.firstChild = node;
2401
2402 node.parent = self;
2403
2404 return node;
2405 },
2406
2407 insert : function(node, ref_node, before) {
2408 var parent;
2409
2410 if (node.parent)
2411 node.remove();
2412
2413 parent = ref_node.parent || this;
2414
2415 if (before) {
2416 if (ref_node === parent.firstChild)
2417 parent.firstChild = node;
2418 else
2419 ref_node.prev.next = node;
2420
2421 node.prev = ref_node.prev;
2422 node.next = ref_node;
2423 ref_node.prev = node;
2424 } else {
2425 if (ref_node === parent.lastChild)
2426 parent.lastChild = node;
2427 else
2428 ref_node.next.prev = node;
2429
2430 node.next = ref_node.next;
2431 node.prev = ref_node;
2432 ref_node.next = node;
2433 }
2434
2435 node.parent = parent;
2436
2437 return node;
2438 },
2439
2440 getAll : function(name) {
2441 var self = this, node, collection = [];
2442
2443 for (node = self.firstChild; node; node = walk(node, self)) {
2444 if (node.name === name)
2445 collection.push(node);
2446 }
2447
2448 return collection;
2449 },
2450
2451 empty : function() {
2452 var self = this, nodes, i, node;
2453
2454 // Remove all children
2455 if (self.firstChild) {
2456 nodes = [];
2457
2458 // Collect the children
2459 for (node = self.firstChild; node; node = walk(node, self))
2460 nodes.push(node);
2461
2462 // Remove the children
2463 i = nodes.length;
2464 while (i--) {
2465 node = nodes[i];
2466 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2467 }
2468 }
2469
2470 self.firstChild = self.lastChild = null;
2471
2472 return self;
2473 },
2474
2475 isEmpty : function(elements) {
2476 var self = this, node = self.firstChild, i, name;
2477
2478 if (node) {
2479 do {
2480 if (node.type === 1) {
2481 // Ignore bogus elements
2482 if (node.attributes.map['data-mce-bogus'])
2483 continue;
2484
2485 // Keep empty elements like <img />
2486 if (elements[node.name])
2487 return false;
2488
2489 // Keep elements with data attributes or name attribute like <a name="1"></a>
2490 i = node.attributes.length;
2491 while (i--) {
2492 name = node.attributes[i].name;
2493 if (name === "name" || name.indexOf('data-') === 0)
2494 return false;
2495 }
2496 }
2497
2498 // Keep non whitespace text nodes
2499 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2500 return false;
2501 } while (node = walk(node, self));
2502 }
2503
2504 return true;
2505 }
2506 });
2507
2508 tinymce.extend(Node, {
2509 create : function(name, attrs) {
2510 var node, attrName;
2511
2512 // Create node
2513 node = new Node(name, typeLookup[name] || 1);
2514
2515 // Add attributes if needed
2516 if (attrs) {
2517 for (attrName in attrs)
2518 node.attr(attrName, attrs[attrName]);
2519 }
2520
2521 return node;
2522 }
2523 });
2524
2525 tinymce.html.Node = Node;
2526})(tinymce);
2527(function(tinymce) {
2528 var Node = tinymce.html.Node;
2529
2530 tinymce.html.DomParser = function(settings, schema) {
2531 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2532
2533 settings = settings || {};
2534 settings.validate = "validate" in settings ? settings.validate : true;
2535 settings.root_name = settings.root_name || 'body';
2536 self.schema = schema = schema || new tinymce.html.Schema();
2537
2538 function fixInvalidChildren(nodes) {
2539 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2540 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2541
2542 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2543 nonEmptyElements = schema.getNonEmptyElements();
2544
2545 for (ni = 0; ni < nodes.length; ni++) {
2546 node = nodes[ni];
2547
2548 // Already removed
2549 if (!node.parent)
2550 continue;
2551
2552 // Get list of all parent nodes until we find a valid parent to stick the child into
2553 parents = [node];
2554 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2555 parents.push(parent);
2556
2557 // Found a suitable parent
2558 if (parent && parents.length > 1) {
2559 // Reverse the array since it makes looping easier
2560 parents.reverse();
2561
2562 // Clone the related parent and insert that after the moved node
2563 newParent = currentNode = self.filterNode(parents[0].clone());
2564
2565 // Start cloning and moving children on the left side of the target node
2566 for (i = 0; i < parents.length - 1; i++) {
2567 if (schema.isValidChild(currentNode.name, parents[i].name)) {
2568 tempNode = self.filterNode(parents[i].clone());
2569 currentNode.append(tempNode);
2570 } else
2571 tempNode = currentNode;
2572
2573 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2574 nextNode = childNode.next;
2575 tempNode.append(childNode);
2576 childNode = nextNode;
2577 }
2578
2579 currentNode = tempNode;
2580 }
2581
2582 if (!newParent.isEmpty(nonEmptyElements)) {
2583 parent.insert(newParent, parents[0], true);
2584 parent.insert(node, newParent);
2585 } else {
2586 parent.insert(node, parents[0], true);
2587 }
2588
2589 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2590 parent = parents[0];
2591 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2592 parent.empty().remove();
2593 }
2594 } else if (node.parent) {
2595 // If it's an LI try to find a UL/OL for it or wrap it
2596 if (node.name === 'li') {
2597 sibling = node.prev;
2598 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2599 sibling.append(node);
2600 continue;
2601 }
2602
2603 sibling = node.next;
2604 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2605 sibling.insert(node, sibling.firstChild, true);
2606 continue;
2607 }
2608
2609 node.wrap(self.filterNode(new Node('ul', 1)));
2610 continue;
2611 }
2612
2613 // Try wrapping the element in a DIV
2614 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2615 node.wrap(self.filterNode(new Node('div', 1)));
2616 } else {
2617 // We failed wrapping it, then remove or unwrap it
2618 if (node.name === 'style' || node.name === 'script')
2619 node.empty().remove();
2620 else
2621 node.unwrap();
2622 }
2623 }
2624 }
2625 };
2626
2627 self.filterNode = function(node) {
2628 var i, name, list;
2629
2630 // Run element filters
2631 if (name in nodeFilters) {
2632 list = matchedNodes[name];
2633
2634 if (list)
2635 list.push(node);
2636 else
2637 matchedNodes[name] = [node];
2638 }
2639
2640 // Run attribute filters
2641 i = attributeFilters.length;
2642 while (i--) {
2643 name = attributeFilters[i].name;
2644
2645 if (name in node.attributes.map) {
2646 list = matchedAttributes[name];
2647
2648 if (list)
2649 list.push(node);
2650 else
2651 matchedAttributes[name] = [node];
2652 }
2653 }
2654
2655 return node;
2656 };
2657
2658 self.addNodeFilter = function(name, callback) {
2659 tinymce.each(tinymce.explode(name), function(name) {
2660 var list = nodeFilters[name];
2661
2662 if (!list)
2663 nodeFilters[name] = list = [];
2664
2665 list.push(callback);
2666 });
2667 };
2668
2669 self.addAttributeFilter = function(name, callback) {
2670 tinymce.each(tinymce.explode(name), function(name) {
2671 var i;
2672
2673 for (i = 0; i < attributeFilters.length; i++) {
2674 if (attributeFilters[i].name === name) {
2675 attributeFilters[i].callbacks.push(callback);
2676 return;
2677 }
2678 }
2679
2680 attributeFilters.push({name: name, callbacks: [callback]});
2681 });
2682 };
2683
2684 self.parse = function(html, args) {
2685 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
2686 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
2687 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
2688
2689 args = args || {};
2690 matchedNodes = {};
2691 matchedAttributes = {};
2692 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
2693 nonEmptyElements = schema.getNonEmptyElements();
2694 children = schema.children;
2695 validate = settings.validate;
2696
2697 whiteSpaceElements = schema.getWhiteSpaceElements();
2698 startWhiteSpaceRegExp = /^[ \t\r\n]+/;
2699 endWhiteSpaceRegExp = /[ \t\r\n]+$/;
2700 allWhiteSpaceRegExp = /[ \t\r\n]+/g;
2701
2702 function createNode(name, type) {
2703 var node = new Node(name, type), list;
2704
2705 if (name in nodeFilters) {
2706 list = matchedNodes[name];
2707
2708 if (list)
2709 list.push(node);
2710 else
2711 matchedNodes[name] = [node];
2712 }
2713
2714 return node;
2715 };
2716
2717 function removeWhitespaceBefore(node) {
2718 var textNode, textVal, sibling;
2719
2720 for (textNode = node.prev; textNode && textNode.type === 3; ) {
2721 textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
2722
2723 if (textVal.length > 0) {
2724 textNode.value = textVal;
2725 textNode = textNode.prev;
2726 } else {
2727 sibling = textNode.prev;
2728 textNode.remove();
2729 textNode = sibling;
2730 }
2731 }
2732 };
2733
2734 parser = new tinymce.html.SaxParser({
2735 validate : validate,
2736 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
2737
2738 cdata: function(text) {
2739 node.append(createNode('#cdata', 4)).value = text;
2740 },
2741
2742 text: function(text, raw) {
2743 var textNode;
2744
2745 // Trim all redundant whitespace on non white space elements
2746 if (!whiteSpaceElements[node.name]) {
2747 text = text.replace(allWhiteSpaceRegExp, ' ');
2748
2749 if (node.lastChild && blockElements[node.lastChild.name])
2750 text = text.replace(startWhiteSpaceRegExp, '');
2751 }
2752
2753 // Do we need to create the node
2754 if (text.length !== 0) {
2755 textNode = createNode('#text', 3);
2756 textNode.raw = !!raw;
2757 node.append(textNode).value = text;
2758 }
2759 },
2760
2761 comment: function(text) {
2762 node.append(createNode('#comment', 8)).value = text;
2763 },
2764
2765 pi: function(name, text) {
2766 node.append(createNode(name, 7)).value = text;
2767 removeWhitespaceBefore(node);
2768 },
2769
2770 doctype: function(text) {
2771 var newNode;
2772
2773 newNode = node.append(createNode('#doctype', 10));
2774 newNode.value = text;
2775 removeWhitespaceBefore(node);
2776 },
2777
2778 start: function(name, attrs, empty) {
2779 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
2780
2781 elementRule = validate ? schema.getElementRule(name) : {};
2782 if (elementRule) {
2783 newNode = createNode(elementRule.outputName || name, 1);
2784 newNode.attributes = attrs;
2785 newNode.shortEnded = empty;
2786
2787 node.append(newNode);
2788
2789 // Check if node is valid child of the parent node is the child is
2790 // unknown we don't collect it since it's probably a custom element
2791 parent = children[node.name];
2792 if (parent && children[newNode.name] && !parent[newNode.name])
2793 invalidChildren.push(newNode);
2794
2795 attrFiltersLen = attributeFilters.length;
2796 while (attrFiltersLen--) {
2797 attrName = attributeFilters[attrFiltersLen].name;
2798
2799 if (attrName in attrs.map) {
2800 list = matchedAttributes[attrName];
2801
2802 if (list)
2803 list.push(newNode);
2804 else
2805 matchedAttributes[attrName] = [newNode];
2806 }
2807 }
2808
2809 // Trim whitespace before block
2810 if (blockElements[name])
2811 removeWhitespaceBefore(newNode);
2812
2813 // Change current node if the element wasn't empty i.e not <br /> or <img />
2814 if (!empty)
2815 node = newNode;
2816 }
2817 },
2818
2819 end: function(name) {
2820 var textNode, elementRule, text, sibling, tempNode;
2821
2822 elementRule = validate ? schema.getElementRule(name) : {};
2823 if (elementRule) {
2824 if (blockElements[name]) {
2825 if (!whiteSpaceElements[node.name]) {
2826 // Trim whitespace at beginning of block
2827 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
2828 text = textNode.value.replace(startWhiteSpaceRegExp, '');
2829
2830 if (text.length > 0) {
2831 textNode.value = text;
2832 textNode = textNode.next;
2833 } else {
2834 sibling = textNode.next;
2835 textNode.remove();
2836 textNode = sibling;
2837 }
2838 }
2839
2840 // Trim whitespace at end of block
2841 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
2842 text = textNode.value.replace(endWhiteSpaceRegExp, '');
2843
2844 if (text.length > 0) {
2845 textNode.value = text;
2846 textNode = textNode.prev;
2847 } else {
2848 sibling = textNode.prev;
2849 textNode.remove();
2850 textNode = sibling;
2851 }
2852 }
2853 }
2854
2855 // Trim start white space
2856 textNode = node.prev;
2857 if (textNode && textNode.type === 3) {
2858 text = textNode.value.replace(startWhiteSpaceRegExp, '');
2859
2860 if (text.length > 0)
2861 textNode.value = text;
2862 else
2863 textNode.remove();
2864 }
2865 }
2866
2867 // Handle empty nodes
2868 if (elementRule.removeEmpty || elementRule.paddEmpty) {
2869 if (node.isEmpty(nonEmptyElements)) {
2870 if (elementRule.paddEmpty)
2871 node.empty().append(new Node('#text', '3')).value = '\u00a0';
2872 else {
2873 // Leave nodes that have a name like <a name="name">
2874 if (!node.attributes.map.name) {
2875 tempNode = node.parent;
2876 node.empty().remove();
2877 node = tempNode;
2878 return;
2879 }
2880 }
2881 }
2882 }
2883
2884 node = node.parent;
2885 }
2886 }
2887 }, schema);
2888
2889 rootNode = node = new Node(settings.root_name, 11);
2890
2891 parser.parse(html);
2892
2893 if (validate)
2894 fixInvalidChildren(invalidChildren);
2895
2896 // Run node filters
2897 for (name in matchedNodes) {
2898 list = nodeFilters[name];
2899 nodes = matchedNodes[name];
2900
2901 // Remove already removed children
2902 fi = nodes.length;
2903 while (fi--) {
2904 if (!nodes[fi].parent)
2905 nodes.splice(fi, 1);
2906 }
2907
2908 for (i = 0, l = list.length; i < l; i++)
2909 list[i](nodes, name, args);
2910 }
2911
2912 // Run attribute filters
2913 for (i = 0, l = attributeFilters.length; i < l; i++) {
2914 list = attributeFilters[i];
2915
2916 if (list.name in matchedAttributes) {
2917 nodes = matchedAttributes[list.name];
2918
2919 // Remove already removed children
2920 fi = nodes.length;
2921 while (fi--) {
2922 if (!nodes[fi].parent)
2923 nodes.splice(fi, 1);
2924 }
2925
2926 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
2927 list.callbacks[fi](nodes, list.name, args);
2928 }
2929 }
2930
2931 return rootNode;
2932 };
2933
2934 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
2935 // make it possible to place the caret inside empty blocks. This logic tries to remove
2936 // these elements and keep br elements that where intended to be there intact
2937 if (settings.remove_trailing_brs) {
2938 self.addNodeFilter('br', function(nodes, name) {
2939 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
2940 nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
2941
2942 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
2943 for (i = 0; i < l; i++) {
2944 node = nodes[i];
2945 parent = node.parent;
2946
2947 if (blockElements[node.parent.name] && node === parent.lastChild) {
2948 // Loop all nodes to the right of the current node and check for other BR elements
2949 // excluding bookmarks since they are invisible
2950 prev = node.prev;
2951 while (prev) {
2952 prevName = prev.name;
2953
2954 // Ignore bookmarks
2955 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
2956 // Found a non BR element
2957 if (prevName !== "br")
2958 break;
2959
2960 // Found another br it's a <br><br> structure then don't remove anything
2961 if (prevName === 'br') {
2962 node = null;
2963 break;
2964 }
2965 }
2966
2967 prev = prev.prev;
2968 }
2969
2970 if (node) {
2971 node.remove();
2972
2973 // Is the parent to be considered empty after we removed the BR
2974 if (parent.isEmpty(nonEmptyElements)) {
2975 elementRule = schema.getElementRule(parent.name);
2976
2977 // Remove or padd the element depending on schema rule
2978 if (elementRule.removeEmpty)
2979 parent.remove();
2980 else if (elementRule.paddEmpty)
2981 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
2982 }
2983 }
2984 }
2985 }
2986 });
2987 }
2988 }
2989})(tinymce);
2990tinymce.html.Writer = function(settings) {
2991 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
2992
2993 settings = settings || {};
2994 indent = settings.indent;
2995 indentBefore = tinymce.makeMap(settings.indent_before || '');
2996 indentAfter = tinymce.makeMap(settings.indent_after || '');
2997 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
2998 htmlOutput = settings.element_format == "html";
2999
3000 return {
3001 start: function(name, attrs, empty) {
3002 var i, l, attr, value;
3003
3004 if (indent && indentBefore[name] && html.length > 0) {
3005 value = html[html.length - 1];
3006
3007 if (value.length > 0 && value !== '\n')
3008 html.push('\n');
3009 }
3010
3011 html.push('<', name);
3012
3013 if (attrs) {
3014 for (i = 0, l = attrs.length; i < l; i++) {
3015 attr = attrs[i];
3016 html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3017 }
3018 }
3019
3020 if (!empty || htmlOutput)
3021 html[html.length] = '>';
3022 else
3023 html[html.length] = ' />';
3024
3025 if (empty && indent && indentAfter[name] && html.length > 0) {
3026 value = html[html.length - 1];
3027
3028 if (value.length > 0 && value !== '\n')
3029 html.push('\n');
3030 }
3031 },
3032
3033 end: function(name) {
3034 var value;
3035
3036 /*if (indent && indentBefore[name] && html.length > 0) {
3037 value = html[html.length - 1];
3038
3039 if (value.length > 0 && value !== '\n')
3040 html.push('\n');
3041 }*/
3042
3043 html.push('</', name, '>');
3044
3045 if (indent && indentAfter[name] && html.length > 0) {
3046 value = html[html.length - 1];
3047
3048 if (value.length > 0 && value !== '\n')
3049 html.push('\n');
3050 }
3051 },
3052
3053 text: function(text, raw) {
3054 if (text.length > 0)
3055 html[html.length] = raw ? text : encode(text);
3056 },
3057
3058 cdata: function(text) {
3059 html.push('<![CDATA[', text, ']]>');
3060 },
3061
3062 comment: function(text) {
3063 html.push('<!--', text, '-->');
3064 },
3065
3066 pi: function(name, text) {
3067 if (text)
3068 html.push('<?', name, ' ', text, '?>');
3069 else
3070 html.push('<?', name, '?>');
3071
3072 if (indent)
3073 html.push('\n');
3074 },
3075
3076 doctype: function(text) {
3077 html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3078 },
3079
3080 reset: function() {
3081 html.length = 0;
3082 },
3083
3084 getContent: function() {
3085 return html.join('').replace(/\n$/, '');
3086 }
3087 };
3088};
3089(function(tinymce) {
3090 tinymce.html.Serializer = function(settings, schema) {
3091 var self = this, writer = new tinymce.html.Writer(settings);
3092
3093 settings = settings || {};
3094 settings.validate = "validate" in settings ? settings.validate : true;
3095
3096 self.schema = schema = schema || new tinymce.html.Schema();
3097 self.writer = writer;
3098
3099 self.serialize = function(node) {
3100 var handlers, validate;
3101
3102 validate = settings.validate;
3103
3104 handlers = {
3105 // #text
3106 3: function(node, raw) {
3107 writer.text(node.value, node.raw);
3108 },
3109
3110 // #comment
3111 8: function(node) {
3112 writer.comment(node.value);
3113 },
3114
3115 // Processing instruction
3116 7: function(node) {
3117 writer.pi(node.name, node.value);
3118 },
3119
3120 // Doctype
3121 10: function(node) {
3122 writer.doctype(node.value);
3123 },
3124
3125 // CDATA
3126 4: function(node) {
3127 writer.cdata(node.value);
3128 },
3129
3130 // Document fragment
3131 11: function(node) {
3132 if ((node = node.firstChild)) {
3133 do {
3134 walk(node);
3135 } while (node = node.next);
3136 }
3137 }
3138 };
3139
3140 writer.reset();
3141
3142 function walk(node) {
3143 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3144
3145 if (!handler) {
3146 name = node.name;
3147 isEmpty = node.shortEnded;
3148 attrs = node.attributes;
3149
3150 // Sort attributes
3151 if (validate && attrs && attrs.length > 1) {
3152 sortedAttrs = [];
3153 sortedAttrs.map = {};
3154
3155 elementRule = schema.getElementRule(node.name);
3156 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3157 attrName = elementRule.attributesOrder[i];
3158
3159 if (attrName in attrs.map) {
3160 attrValue = attrs.map[attrName];
3161 sortedAttrs.map[attrName] = attrValue;
3162 sortedAttrs.push({name: attrName, value: attrValue});
3163 }
3164 }
3165
3166 for (i = 0, l = attrs.length; i < l; i++) {
3167 attrName = attrs[i].name;
3168
3169 if (!(attrName in sortedAttrs.map)) {
3170 attrValue = attrs.map[attrName];
3171 sortedAttrs.map[attrName] = attrValue;
3172 sortedAttrs.push({name: attrName, value: attrValue});
3173 }
3174 }
3175
3176 attrs = sortedAttrs;
3177 }
3178
3179 writer.start(node.name, attrs, isEmpty);
3180
3181 if (!isEmpty) {
3182 if ((node = node.firstChild)) {
3183 do {
3184 walk(node);
3185 } while (node = node.next);
3186 }
3187
3188 writer.end(name);
3189 }
3190 } else
3191 handler(node);
3192 }
3193
3194 // Serialize element and treat all non elements as fragments
3195 if (node.type == 1 && !settings.inner)
3196 walk(node);
3197 else
3198 handlers[11](node);
3199
3200 return writer.getContent();
3201 };
3202 }
3203})(tinymce);
3204(function(tinymce) {
3205 // Shorten names
3206 var each = tinymce.each,
3207 is = tinymce.is,
3208 isWebKit = tinymce.isWebKit,
3209 isIE = tinymce.isIE,
3210 Entities = tinymce.html.Entities,
3211 simpleSelectorRe = /^([a-z0-9],?)+$/i,
3212 blockElementsMap = tinymce.html.Schema.blockElementsMap,
3213 whiteSpaceRegExp = /^[ \t\r\n]*$/;
3214
3215 tinymce.create('tinymce.dom.DOMUtils', {
3216 doc : null,
3217 root : null,
3218 files : null,
3219 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3220 props : {
3221 "for" : "htmlFor",
3222 "class" : "className",
3223 className : "className",
3224 checked : "checked",
3225 disabled : "disabled",
3226 maxlength : "maxLength",
3227 readonly : "readOnly",
3228 selected : "selected",
3229 value : "value",
3230 id : "id",
3231 name : "name",
3232 type : "type"
3233 },
3234
3235 DOMUtils : function(d, s) {
3236 var t = this, globalStyle;
3237
3238 t.doc = d;
3239 t.win = window;
3240 t.files = {};
3241 t.cssFlicker = false;
3242 t.counter = 0;
3243 t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3244 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3245 t.hasOuterHTML = "outerHTML" in d.createElement("a");
3246
3247 t.settings = s = tinymce.extend({
3248 keep_values : false,
3249 hex_colors : 1
3250 }, s);
3251
3252 t.schema = s.schema;
3253 t.styles = new tinymce.html.Styles({
3254 url_converter : s.url_converter,
3255 url_converter_scope : s.url_converter_scope
3256 }, s.schema);
3257
3258 // Fix IE6SP2 flicker and check it failed for pre SP2
3259 if (tinymce.isIE6) {
3260 try {
3261 d.execCommand('BackgroundImageCache', false, true);
3262 } catch (e) {
3263 t.cssFlicker = true;
3264 }
3265 }
3266
3267 if (isIE) {
3268 // Add missing HTML 4/5 elements to IE
3269 ('abbr article aside audio canvas ' +
3270 'details figcaption figure footer ' +
3271 'header hgroup mark menu meter nav ' +
3272 'output progress section summary ' +
3273 'time video').replace(/\w+/g, function(name) {
3274 d.createElement(name);
3275 });
3276 }
3277
3278 tinymce.addUnload(t.destroy, t);
3279 },
3280
3281 getRoot : function() {
3282 var t = this, s = t.settings;
3283
3284 return (s && t.get(s.root_element)) || t.doc.body;
3285 },
3286
3287 getViewPort : function(w) {
3288 var d, b;
3289
3290 w = !w ? this.win : w;
3291 d = w.document;
3292 b = this.boxModel ? d.documentElement : d.body;
3293
3294 // Returns viewport size excluding scrollbars
3295 return {
3296 x : w.pageXOffset || b.scrollLeft,
3297 y : w.pageYOffset || b.scrollTop,
3298 w : w.innerWidth || b.clientWidth,
3299 h : w.innerHeight || b.clientHeight
3300 };
3301 },
3302
3303 getRect : function(e) {
3304 var p, t = this, sr;
3305
3306 e = t.get(e);
3307 p = t.getPos(e);
3308 sr = t.getSize(e);
3309
3310 return {
3311 x : p.x,
3312 y : p.y,
3313 w : sr.w,
3314 h : sr.h
3315 };
3316 },
3317
3318 getSize : function(e) {
3319 var t = this, w, h;
3320
3321 e = t.get(e);
3322 w = t.getStyle(e, 'width');
3323 h = t.getStyle(e, 'height');
3324
3325 // Non pixel value, then force offset/clientWidth
3326 if (w.indexOf('px') === -1)
3327 w = 0;
3328
3329 // Non pixel value, then force offset/clientWidth
3330 if (h.indexOf('px') === -1)
3331 h = 0;
3332
3333 return {
3334 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3335 h : parseInt(h) || e.offsetHeight || e.clientHeight
3336 };
3337 },
3338
3339 getParent : function(n, f, r) {
3340 return this.getParents(n, f, r, false);
3341 },
3342
3343 getParents : function(n, f, r, c) {
3344 var t = this, na, se = t.settings, o = [];
3345
3346 n = t.get(n);
3347 c = c === undefined;
3348
3349 if (se.strict_root)
3350 r = r || t.getRoot();
3351
3352 // Wrap node name as func
3353 if (is(f, 'string')) {
3354 na = f;
3355
3356 if (f === '*') {
3357 f = function(n) {return n.nodeType == 1;};
3358 } else {
3359 f = function(n) {
3360 return t.is(n, na);
3361 };
3362 }
3363 }
3364
3365 while (n) {
3366 if (n == r || !n.nodeType || n.nodeType === 9)
3367 break;
3368
3369 if (!f || f(n)) {
3370 if (c)
3371 o.push(n);
3372 else
3373 return n;
3374 }
3375
3376 n = n.parentNode;
3377 }
3378
3379 return c ? o : null;
3380 },
3381
3382 get : function(e) {
3383 var n;
3384
3385 if (e && this.doc && typeof(e) == 'string') {
3386 n = e;
3387 e = this.doc.getElementById(e);
3388
3389 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3390 if (e && e.id !== n)
3391 return this.doc.getElementsByName(n)[1];
3392 }
3393
3394 return e;
3395 },
3396
3397 getNext : function(node, selector) {
3398 return this._findSib(node, selector, 'nextSibling');
3399 },
3400
3401 getPrev : function(node, selector) {
3402 return this._findSib(node, selector, 'previousSibling');
3403 },
3404
3405
3406 select : function(pa, s) {
3407 var t = this;
3408
3409 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
3410 },
3411
3412 is : function(n, selector) {
3413 var i;
3414
3415 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
3416 if (n.length === undefined) {
3417 // Simple all selector
3418 if (selector === '*')
3419 return n.nodeType == 1;
3420
3421 // Simple selector just elements
3422 if (simpleSelectorRe.test(selector)) {
3423 selector = selector.toLowerCase().split(/,/);
3424 n = n.nodeName.toLowerCase();
3425
3426 for (i = selector.length - 1; i >= 0; i--) {
3427 if (selector[i] == n)
3428 return true;
3429 }
3430
3431 return false;
3432 }
3433 }
3434
3435 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
3436 },
3437
3438
3439 add : function(p, n, a, h, c) {
3440 var t = this;
3441
3442 return this.run(p, function(p) {
3443 var e, k;
3444
3445 e = is(n, 'string') ? t.doc.createElement(n) : n;
3446 t.setAttribs(e, a);
3447
3448 if (h) {
3449 if (h.nodeType)
3450 e.appendChild(h);
3451 else
3452 t.setHTML(e, h);
3453 }
3454
3455 return !c ? p.appendChild(e) : e;
3456 });
3457 },
3458
3459 create : function(n, a, h) {
3460 return this.add(this.doc.createElement(n), n, a, h, 1);
3461 },
3462
3463 createHTML : function(n, a, h) {
3464 var o = '', t = this, k;
3465
3466 o += '<' + n;
3467
3468 for (k in a) {
3469 if (a.hasOwnProperty(k))
3470 o += ' ' + k + '="' + t.encode(a[k]) + '"';
3471 }
3472
3473 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3474 if (typeof(h) != "undefined")
3475 return o + '>' + h + '</' + n + '>';
3476
3477 return o + ' />';
3478 },
3479
3480 remove : function(node, keep_children) {
3481 return this.run(node, function(node) {
3482 var child, parent = node.parentNode;
3483
3484 if (!parent)
3485 return null;
3486
3487 if (keep_children) {
3488 while (child = node.firstChild) {
3489 // IE 8 will crash if you don't remove completely empty text nodes
3490 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3491 parent.insertBefore(child, node);
3492 else
3493 node.removeChild(child);
3494 }
3495 }
3496
3497 return parent.removeChild(node);
3498 });
3499 },
3500
3501 setStyle : function(n, na, v) {
3502 var t = this;
3503
3504 return t.run(n, function(e) {
3505 var s, i;
3506
3507 s = e.style;
3508
3509 // Camelcase it, if needed
3510 na = na.replace(/-(\D)/g, function(a, b){
3511 return b.toUpperCase();
3512 });
3513
3514 // Default px suffix on these
3515 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3516 v += 'px';
3517
3518 switch (na) {
3519 case 'opacity':
3520 // IE specific opacity
3521 if (isIE) {
3522 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3523
3524 if (!n.currentStyle || !n.currentStyle.hasLayout)
3525 s.display = 'inline-block';
3526 }
3527
3528 // Fix for older browsers
3529 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3530 break;
3531
3532 case 'float':
3533 isIE ? s.styleFloat = v : s.cssFloat = v;
3534 break;
3535
3536 default:
3537 s[na] = v || '';
3538 }
3539
3540 // Force update of the style data
3541 if (t.settings.update_styles)
3542 t.setAttrib(e, 'data-mce-style');
3543 });
3544 },
3545
3546 getStyle : function(n, na, c) {
3547 n = this.get(n);
3548
3549 if (!n)
3550 return;
3551
3552 // Gecko
3553 if (this.doc.defaultView && c) {
3554 // Remove camelcase
3555 na = na.replace(/[A-Z]/g, function(a){
3556 return '-' + a;
3557 });
3558
3559 try {
3560 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3561 } catch (ex) {
3562 // Old safari might fail
3563 return null;
3564 }
3565 }
3566
3567 // Camelcase it, if needed
3568 na = na.replace(/-(\D)/g, function(a, b){
3569 return b.toUpperCase();
3570 });
3571
3572 if (na == 'float')
3573 na = isIE ? 'styleFloat' : 'cssFloat';
3574
3575 // IE & Opera
3576 if (n.currentStyle && c)
3577 return n.currentStyle[na];
3578
3579 return n.style ? n.style[na] : undefined;
3580 },
3581
3582 setStyles : function(e, o) {
3583 var t = this, s = t.settings, ol;
3584
3585 ol = s.update_styles;
3586 s.update_styles = 0;
3587
3588 each(o, function(v, n) {
3589 t.setStyle(e, n, v);
3590 });
3591
3592 // Update style info
3593 s.update_styles = ol;
3594 if (s.update_styles)
3595 t.setAttrib(e, s.cssText);
3596 },
3597
3598 removeAllAttribs: function(e) {
3599 return this.run(e, function(e) {
3600 var i, attrs = e.attributes;
3601 for (i = attrs.length - 1; i >= 0; i--) {
3602 e.removeAttributeNode(attrs.item(i));
3603 }
3604 });
3605 },
3606
3607 setAttrib : function(e, n, v) {
3608 var t = this;
3609
3610 // Whats the point
3611 if (!e || !n)
3612 return;
3613
3614 // Strict XML mode
3615 if (t.settings.strict)
3616 n = n.toLowerCase();
3617
3618 return this.run(e, function(e) {
3619 var s = t.settings;
3620
3621 switch (n) {
3622 case "style":
3623 if (!is(v, 'string')) {
3624 each(v, function(v, n) {
3625 t.setStyle(e, n, v);
3626 });
3627
3628 return;
3629 }
3630
3631 // No mce_style for elements with these since they might get resized by the user
3632 if (s.keep_values) {
3633 if (v && !t._isRes(v))
3634 e.setAttribute('data-mce-style', v, 2);
3635 else
3636 e.removeAttribute('data-mce-style', 2);
3637 }
3638
3639 e.style.cssText = v;
3640 break;
3641
3642 case "class":
3643 e.className = v || ''; // Fix IE null bug
3644 break;
3645
3646 case "src":
3647 case "href":
3648 if (s.keep_values) {
3649 if (s.url_converter)
3650 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3651
3652 t.setAttrib(e, 'data-mce-' + n, v, 2);
3653 }
3654
3655 break;
3656
3657 case "shape":
3658 e.setAttribute('data-mce-style', v);
3659 break;
3660 }
3661
3662 if (is(v) && v !== null && v.length !== 0)
3663 e.setAttribute(n, '' + v, 2);
3664 else
3665 e.removeAttribute(n, 2);
3666 });
3667 },
3668
3669 setAttribs : function(e, o) {
3670 var t = this;
3671
3672 return this.run(e, function(e) {
3673 each(o, function(v, n) {
3674 t.setAttrib(e, n, v);
3675 });
3676 });
3677 },
3678
3679 getAttrib : function(e, n, dv) {
3680 var v, t = this;
3681
3682 e = t.get(e);
3683
3684 if (!e || e.nodeType !== 1)
3685 return false;
3686
3687 if (!is(dv))
3688 dv = '';
3689
3690 // Try the mce variant for these
3691 if (/^(src|href|style|coords|shape)$/.test(n)) {
3692 v = e.getAttribute("data-mce-" + n);
3693
3694 if (v)
3695 return v;
3696 }
3697
3698 if (isIE && t.props[n]) {
3699 v = e[t.props[n]];
3700 v = v && v.nodeValue ? v.nodeValue : v;
3701 }
3702
3703 if (!v)
3704 v = e.getAttribute(n, 2);
3705
3706 // Check boolean attribs
3707 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
3708 if (e[t.props[n]] === true && v === '')
3709 return n;
3710
3711 return v ? n : '';
3712 }
3713
3714 // Inner input elements will override attributes on form elements
3715 if (e.nodeName === "FORM" && e.getAttributeNode(n))
3716 return e.getAttributeNode(n).nodeValue;
3717
3718 if (n === 'style') {
3719 v = v || e.style.cssText;
3720
3721 if (v) {
3722 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
3723
3724 if (t.settings.keep_values && !t._isRes(v))
3725 e.setAttribute('data-mce-style', v);
3726 }
3727 }
3728
3729 // Remove Apple and WebKit stuff
3730 if (isWebKit && n === "class" && v)
3731 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3732
3733 // Handle IE issues
3734 if (isIE) {
3735 switch (n) {
3736 case 'rowspan':
3737 case 'colspan':
3738 // IE returns 1 as default value
3739 if (v === 1)
3740 v = '';
3741
3742 break;
3743