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