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