1 //<script type="text/javascript">
4 * Based Ext JS Library 1.1.1
5 * Copyright(c) 2006-2007, Ext JS, LLC.
11 * @class Roo.HtmlEditorCore
12 * @extends Roo.Component
13 * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
15 * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
18 Roo.HtmlEditorCore = function(config){
21 Roo.HtmlEditorCore.superclass.constructor.call(this, config);
27 * Fires when the editor is fully initialized (including the iframe)
28 * @param {Roo.HtmlEditorCore} this
33 * Fires when the editor is first receives the focus. Any insertion must wait
34 * until after this event.
35 * @param {Roo.HtmlEditorCore} this
40 * Fires before the textarea is updated with content from the editor iframe. Return false
42 * @param {Roo.HtmlEditorCore} this
43 * @param {String} html
48 * Fires before the iframe editor is updated with content from the textarea. Return false
50 * @param {Roo.HtmlEditorCore} this
51 * @param {String} html
56 * Fires when the textarea is updated with content from the editor iframe.
57 * @param {Roo.HtmlEditorCore} this
58 * @param {String} html
63 * Fires when the iframe editor is updated with content from the textarea.
64 * @param {Roo.HtmlEditorCore} this
65 * @param {String} html
71 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
72 * @param {Roo.HtmlEditorCore} this
77 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
86 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
90 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
96 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
101 * @cfg {Number} height (in pixels)
105 * @cfg {Number} width (in pixels)
110 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
118 // private properties
119 validationEvent : false,
123 sourceEditMode : false,
124 onFocus : Roo.emptyFn,
136 * Protected method that will not generally be called directly. It
137 * is called when the editor initializes the iframe with HTML contents. Override this method if you
138 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
140 getDocMarkup : function(){
143 Roo.log(this.stylesheets);
145 // inherit styels from page...??
146 if (this.stylesheets === false) {
148 Roo.get(document.head).select('style').each(function(node) {
149 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
152 Roo.get(document.head).select('link').each(function(node) {
153 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
156 } else if (!this.stylesheets.length) {
158 st = '<style type="text/css">' +
159 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
162 Roo.each(this.stylesheets, function(s) {
163 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
168 st += '<style type="text/css">' +
169 'IMG { cursor: pointer } ' +
173 return '<html><head>' + st +
174 //<style type="text/css">' +
175 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
177 ' </head><body class="roo-htmleditor-body"></body></html>';
181 onRender : function(ct, position)
184 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
185 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
188 this.el.dom.style.border = '0 none';
189 this.el.dom.setAttribute('tabIndex', -1);
190 this.el.addClass('x-hidden hide');
194 if(Roo.isIE){ // fix IE 1px bogus margin
195 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
199 this.frameId = Roo.id();
203 var iframe = this.owner.wrap.createChild({
205 cls: 'form-control', // bootstrap..
209 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
214 this.iframe = iframe.dom;
218 this.doc.designMode = 'on';
221 this.doc.write(this.getDocMarkup());
225 var task = { // must defer to wait for browser to be ready
227 //console.log("run task?" + this.doc.readyState);
229 if(this.doc.body || this.doc.readyState == 'complete'){
231 this.doc.designMode="on";
235 Roo.TaskMgr.stop(task);
236 this.initEditor.defer(10, this);
243 Roo.TaskMgr.start(task);
250 onResize : function(w, h)
252 Roo.log('resize: ' +w + ',' + h );
253 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
257 if(typeof w == 'number'){
259 this.iframe.style.width = w + 'px';
261 if(typeof h == 'number'){
263 this.iframe.style.height = h + 'px';
265 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
272 * Toggles the editor between standard and source edit mode.
273 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
275 toggleSourceEdit : function(sourceEditMode){
277 this.sourceEditMode = sourceEditMode === true;
279 if(this.sourceEditMode){
281 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
284 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
285 //this.iframe.className = '';
288 //this.setSize(this.owner.wrap.getSize());
289 //this.fireEvent('editmodechange', this, this.sourceEditMode);
296 * Protected method that will not generally be called directly. If you need/want
297 * custom HTML cleanup, this is the method you should override.
298 * @param {String} html The HTML to be cleaned
299 * return {String} The cleaned HTML
301 cleanHtml : function(html){
304 if(Roo.isSafari){ // strip safari nonsense
305 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
308 if(html == ' '){
315 * HTML Editor -> Textarea
316 * Protected method that will not generally be called directly. Syncs the contents
317 * of the editor iframe with the textarea.
319 syncValue : function(){
320 if(this.initialized){
321 var bd = (this.doc.body || this.doc.documentElement);
322 //this.cleanUpPaste(); -- this is done else where and causes havoc..
323 var html = bd.innerHTML;
325 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
326 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
328 html = '<div style="'+m[0]+'">' + html + '</div>';
331 html = this.cleanHtml(html);
332 // fix up the special chars.. normaly like back quotes in word...
333 // however we do not want to do this with chinese..
334 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
335 var cc = b.charCodeAt();
337 (cc >= 0x4E00 && cc < 0xA000 ) ||
338 (cc >= 0x3400 && cc < 0x4E00 ) ||
339 (cc >= 0xf900 && cc < 0xfb00 )
345 if(this.owner.fireEvent('beforesync', this, html) !== false){
346 this.el.dom.value = html;
347 this.owner.fireEvent('sync', this, html);
353 * Protected method that will not generally be called directly. Pushes the value of the textarea
354 * into the iframe editor.
356 pushValue : function(){
357 if(this.initialized){
358 var v = this.el.dom.value.trim();
364 if(this.owner.fireEvent('beforepush', this, v) !== false){
365 var d = (this.doc.body || this.doc.documentElement);
368 this.el.dom.value = d.innerHTML;
369 this.owner.fireEvent('push', this, v);
375 deferFocus : function(){
376 this.focus.defer(10, this);
381 if(this.win && !this.sourceEditMode){
388 assignDocWin: function()
390 var iframe = this.iframe;
393 this.doc = iframe.contentWindow.document;
394 this.win = iframe.contentWindow;
396 // if (!Roo.get(this.frameId)) {
399 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
400 // this.win = Roo.get(this.frameId).dom.contentWindow;
402 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
406 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
407 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
412 initEditor : function(){
413 //console.log("INIT EDITOR");
418 this.doc.designMode="on";
420 this.doc.write(this.getDocMarkup());
423 var dbody = (this.doc.body || this.doc.documentElement);
424 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
425 // this copies styles from the containing element into thsi one..
426 // not sure why we need all of this..
427 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
429 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
430 //ss['background-attachment'] = 'fixed'; // w3c
431 dbody.bgProperties = 'fixed'; // ie
432 //Roo.DomHelper.applyStyles(dbody, ss);
433 Roo.EventManager.on(this.doc, {
434 //'mousedown': this.onEditorEvent,
435 'mouseup': this.onEditorEvent,
436 'dblclick': this.onEditorEvent,
437 'click': this.onEditorEvent,
438 'keyup': this.onEditorEvent,
443 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
445 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
446 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
448 this.initialized = true;
450 this.owner.fireEvent('initialize', this);
455 onDestroy : function(){
461 //for (var i =0; i < this.toolbars.length;i++) {
462 // // fixme - ask toolbars for heights?
463 // this.toolbars[i].onDestroy();
466 //this.wrap.dom.innerHTML = '';
467 //this.wrap.remove();
472 onFirstFocus : function(){
477 this.activated = true;
480 if(Roo.isGecko){ // prevent silly gecko errors
482 var s = this.win.getSelection();
483 if(!s.focusNode || s.focusNode.nodeType != 3){
484 var r = s.getRangeAt(0);
485 r.selectNodeContents((this.doc.body || this.doc.documentElement));
490 this.execCmd('useCSS', true);
491 this.execCmd('styleWithCSS', false);
494 this.owner.fireEvent('activate', this);
498 adjustFont: function(btn){
499 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
500 //if(Roo.isSafari){ // safari
503 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
504 if(Roo.isSafari){ // safari
505 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
506 v = (v < 10) ? 10 : v;
507 v = (v > 48) ? 48 : v;
508 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
513 v = Math.max(1, v+adjust);
515 this.execCmd('FontSize', v );
518 onEditorEvent : function(e){
519 this.owner.fireEvent('editorevent', this, e);
520 // this.updateToolbar();
521 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
524 insertTag : function(tg)
526 // could be a bit smarter... -> wrap the current selected tRoo..
527 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
529 range = this.createRange(this.getSelection());
530 var wrappingNode = this.doc.createElement(tg.toLowerCase());
531 wrappingNode.appendChild(range.extractContents());
532 range.insertNode(wrappingNode);
539 this.execCmd("formatblock", tg);
543 insertText : function(txt)
547 var range = this.createRange();
548 range.deleteContents();
549 //alert(Sender.getAttribute('label'));
551 range.insertNode(this.doc.createTextNode(txt));
557 * Executes a Midas editor command on the editor document and performs necessary focus and
558 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
559 * @param {String} cmd The Midas command
560 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
562 relayCmd : function(cmd, value){
564 this.execCmd(cmd, value);
565 this.owner.fireEvent('editorevent', this);
566 //this.updateToolbar();
567 this.owner.deferFocus();
571 * Executes a Midas editor command directly on the editor document.
572 * For visual commands, you should use {@link #relayCmd} instead.
573 * <b>This should only be called after the editor is initialized.</b>
574 * @param {String} cmd The Midas command
575 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
577 execCmd : function(cmd, value){
578 this.doc.execCommand(cmd, false, value === undefined ? null : value);
585 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
587 * @param {String} text | dom node..
589 insertAtCursor : function(text)
600 var r = this.doc.selection.createRange();
611 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
615 // from jquery ui (MIT licenced)
619 if (win.getSelection && win.getSelection().getRangeAt) {
620 range = win.getSelection().getRangeAt(0);
621 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
622 range.insertNode(node);
623 } else if (win.document.selection && win.document.selection.createRange) {
624 // no firefox support
625 var txt = typeof(text) == 'string' ? text : text.outerHTML;
626 win.document.selection.createRange().pasteHTML(txt);
628 // no firefox support
629 var txt = typeof(text) == 'string' ? text : text.outerHTML;
630 this.execCmd('InsertHTML', txt);
639 mozKeyPress : function(e){
641 var c = e.getCharCode(), cmd;
644 c = String.fromCharCode(c).toLowerCase();
658 this.cleanUpPaste.defer(100, this);
674 fixKeys : function(){ // load time branching for fastest keydown performance
677 var k = e.getKey(), r;
680 r = this.doc.selection.createRange();
683 r.pasteHTML('    ');
690 r = this.doc.selection.createRange();
692 var target = r.parentElement();
693 if(!target || target.tagName.toLowerCase() != 'li'){
695 r.pasteHTML('<br />');
701 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
702 this.cleanUpPaste.defer(100, this);
708 }else if(Roo.isOpera){
714 this.execCmd('InsertHTML','    ');
717 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
718 this.cleanUpPaste.defer(100, this);
723 }else if(Roo.isSafari){
729 this.execCmd('InsertText','\t');
733 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
734 this.cleanUpPaste.defer(100, this);
742 getAllAncestors: function()
744 var p = this.getSelectedNode();
747 a.push(p); // push blank onto stack..
748 p = this.getParentElement();
752 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
756 a.push(this.doc.body);
763 getSelection : function()
766 return Roo.isIE ? this.doc.selection : this.win.getSelection();
769 getSelectedNode: function()
771 // this may only work on Gecko!!!
773 // should we cache this!!!!
778 var range = this.createRange(this.getSelection()).cloneRange();
781 var parent = range.parentElement();
783 var testRange = range.duplicate();
784 testRange.moveToElementText(parent);
785 if (testRange.inRange(range)) {
788 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
791 parent = parent.parentElement;
796 // is ancestor a text element.
797 var ac = range.commonAncestorContainer;
798 if (ac.nodeType == 3) {
802 var ar = ac.childNodes;
805 var other_nodes = [];
806 var has_other_nodes = false;
807 for (var i=0;i<ar.length;i++) {
808 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
811 // fullly contained node.
813 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
818 // probably selected..
819 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
820 other_nodes.push(ar[i]);
824 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
829 has_other_nodes = true;
831 if (!nodes.length && other_nodes.length) {
834 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
840 createRange: function(sel)
842 // this has strange effects when using with
843 // top toolbar - not sure if it's a great idea.
844 //this.editor.contentWindow.focus();
845 if (typeof sel != "undefined") {
847 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
849 return this.doc.createRange();
852 return this.doc.createRange();
855 getParentElement: function()
859 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
861 var range = this.createRange(sel);
864 var p = range.commonAncestorContainer;
865 while (p.nodeType == 3) { // text node
876 * Range intersection.. the hard stuff...
880 * [ -- selected range --- ]
884 * if end is before start or hits it. fail.
885 * if start is after end or hits it fail.
887 * if either hits (but other is outside. - then it's not
893 // @see http://www.thismuchiknow.co.uk/?p=64.
894 rangeIntersectsNode : function(range, node)
896 var nodeRange = node.ownerDocument.createRange();
898 nodeRange.selectNode(node);
900 nodeRange.selectNodeContents(node);
903 var rangeStartRange = range.cloneRange();
904 rangeStartRange.collapse(true);
906 var rangeEndRange = range.cloneRange();
907 rangeEndRange.collapse(false);
909 var nodeStartRange = nodeRange.cloneRange();
910 nodeStartRange.collapse(true);
912 var nodeEndRange = nodeRange.cloneRange();
913 nodeEndRange.collapse(false);
915 return rangeStartRange.compareBoundaryPoints(
916 Range.START_TO_START, nodeEndRange) == -1 &&
917 rangeEndRange.compareBoundaryPoints(
918 Range.START_TO_START, nodeStartRange) == 1;
922 rangeCompareNode : function(range, node)
924 var nodeRange = node.ownerDocument.createRange();
926 nodeRange.selectNode(node);
928 nodeRange.selectNodeContents(node);
932 range.collapse(true);
934 nodeRange.collapse(true);
936 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
937 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
939 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
941 var nodeIsBefore = ss == 1;
942 var nodeIsAfter = ee == -1;
944 if (nodeIsBefore && nodeIsAfter)
946 if (!nodeIsBefore && nodeIsAfter)
947 return 1; //right trailed.
949 if (nodeIsBefore && !nodeIsAfter)
950 return 2; // left trailed.
955 // private? - in a new class?
956 cleanUpPaste : function()
958 // cleans up the whole document..
959 Roo.log('cleanuppaste');
961 this.cleanUpChildren(this.doc.body);
962 var clean = this.cleanWordChars(this.doc.body.innerHTML);
963 if (clean != this.doc.body.innerHTML) {
964 this.doc.body.innerHTML = clean;
969 cleanWordChars : function(input) {// change the chars to hex code
970 var he = Roo.HtmlEditorCore;
973 Roo.each(he.swapCodes, function(sw) {
974 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
976 output = output.replace(swapper, sw[1]);
983 cleanUpChildren : function (n)
985 if (!n.childNodes.length) {
988 for (var i = n.childNodes.length-1; i > -1 ; i--) {
989 this.cleanUpChild(n.childNodes[i]);
996 cleanUpChild : function (node)
1000 if (node.nodeName == "#text") {
1001 // clean up silly Windows -- stuff?
1004 if (node.nodeName == "#comment") {
1005 node.parentNode.removeChild(node);
1006 // clean up silly Windows -- stuff?
1010 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1012 node.parentNode.removeChild(node);
1017 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1019 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1020 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1022 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1023 // remove_keep_children = true;
1026 if (remove_keep_children) {
1027 this.cleanUpChildren(node);
1028 // inserts everything just before this node...
1029 while (node.childNodes.length) {
1030 var cn = node.childNodes[0];
1031 node.removeChild(cn);
1032 node.parentNode.insertBefore(cn, node);
1034 node.parentNode.removeChild(node);
1038 if (!node.attributes || !node.attributes.length) {
1039 this.cleanUpChildren(node);
1043 function cleanAttr(n,v)
1046 if (v.match(/^\./) || v.match(/^\//)) {
1049 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1052 if (v.match(/^#/)) {
1055 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1056 node.removeAttribute(n);
1060 function cleanStyle(n,v)
1062 if (v.match(/expression/)) { //XSS?? should we even bother..
1063 node.removeAttribute(n);
1066 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1067 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1070 var parts = v.split(/;/);
1073 Roo.each(parts, function(p) {
1074 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1078 var l = p.split(':').shift().replace(/\s+/g,'');
1079 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1081 if ( cblack.indexOf(l) > -1) {
1082 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1083 //node.removeAttribute(n);
1087 // only allow 'c whitelisted system attributes'
1088 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1089 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1090 //node.removeAttribute(n);
1101 node.setAttribute(n, clean.join(';'));
1103 node.removeAttribute(n);
1109 for (var i = node.attributes.length-1; i > -1 ; i--) {
1110 var a = node.attributes[i];
1113 if (a.name.toLowerCase().substr(0,2)=='on') {
1114 node.removeAttribute(a.name);
1117 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1118 node.removeAttribute(a.name);
1121 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1122 cleanAttr(a.name,a.value); // fixme..
1125 if (a.name == 'style') {
1126 cleanStyle(a.name,a.value);
1129 /// clean up MS crap..
1130 // tecnically this should be a list of valid class'es..
1133 if (a.name == 'class') {
1134 if (a.value.match(/^Mso/)) {
1135 node.className = '';
1138 if (a.value.match(/body/)) {
1139 node.className = '';
1150 this.cleanUpChildren(node);
1155 * Clean up MS wordisms...
1157 cleanWord : function(node)
1160 var cleanWordChildren = function()
1162 if (!node.childNodes.length) {
1165 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1166 _t.cleanWord(node.childNodes[i]);
1172 this.cleanWord(this.doc.body);
1175 if (node.nodeName == "#text") {
1176 // clean up silly Windows -- stuff?
1179 if (node.nodeName == "#comment") {
1180 node.parentNode.removeChild(node);
1181 // clean up silly Windows -- stuff?
1185 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1186 node.parentNode.removeChild(node);
1190 // remove - but keep children..
1191 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1192 while (node.childNodes.length) {
1193 var cn = node.childNodes[0];
1194 node.removeChild(cn);
1195 node.parentNode.insertBefore(cn, node);
1197 node.parentNode.removeChild(node);
1198 cleanWordChildren();
1202 if (node.className.length) {
1204 var cn = node.className.split(/\W+/);
1206 Roo.each(cn, function(cls) {
1207 if (cls.match(/Mso[a-zA-Z]+/)) {
1212 node.className = cna.length ? cna.join(' ') : '';
1214 node.removeAttribute("class");
1218 if (node.hasAttribute("lang")) {
1219 node.removeAttribute("lang");
1222 if (node.hasAttribute("style")) {
1224 var styles = node.getAttribute("style").split(";");
1226 Roo.each(styles, function(s) {
1227 if (!s.match(/:/)) {
1230 var kv = s.split(":");
1231 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1234 // what ever is left... we allow.
1237 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1238 if (!nstyle.length) {
1239 node.removeAttribute('style');
1243 cleanWordChildren();
1247 domToHTML : function(currentElement, depth, nopadtext) {
1250 nopadtext = nopadtext || false;
1252 if (!currentElement) {
1253 return this.domToHTML(this.doc.body);
1256 //Roo.log(currentElement);
1258 var allText = false;
1259 var nodeName = currentElement.nodeName;
1260 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1262 if (nodeName == '#text') {
1263 return currentElement.nodeValue;
1268 if (nodeName != 'BODY') {
1271 // Prints the node tagName, such as <A>, <IMG>, etc
1274 for(i = 0; i < currentElement.attributes.length;i++) {
1276 var aname = currentElement.attributes.item(i).name;
1277 if (!currentElement.attributes.item(i).value.length) {
1280 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1283 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1292 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1295 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1300 // Traverse the tree
1302 var currentElementChild = currentElement.childNodes.item(i);
1306 while (currentElementChild) {
1307 // Formatting code (indent the tree so it looks nice on the screen)
1308 var nopad = nopadtext;
1309 if (lastnode == 'SPAN') {
1313 if (currentElementChild.nodeName == '#text') {
1314 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1315 if (!nopad && toadd.length > 80) {
1316 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1321 currentElementChild = currentElement.childNodes.item(i);
1327 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1329 // Recursively traverse the tree structure of the child node
1330 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1331 lastnode = currentElementChild.nodeName;
1333 currentElementChild=currentElement.childNodes.item(i);
1339 // The remaining code is mostly for formatting the tree
1340 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1345 ret+= "</"+tagName+">";
1351 // hide stuff that is not compatible
1369 * @cfg {String} fieldClass @hide
1372 * @cfg {String} focusClass @hide
1375 * @cfg {String} autoCreate @hide
1378 * @cfg {String} inputType @hide
1381 * @cfg {String} invalidClass @hide
1384 * @cfg {String} invalidText @hide
1387 * @cfg {String} msgFx @hide
1390 * @cfg {String} validateOnBlur @hide
1394 Roo.HtmlEditorCore.white = [
1395 'area', 'br', 'img', 'input', 'hr', 'wbr',
1397 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1398 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1399 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1400 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1401 'table', 'ul', 'xmp',
1403 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1406 'dir', 'menu', 'ol', 'ul', 'dl',
1412 Roo.HtmlEditorCore.black = [
1413 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1415 'base', 'basefont', 'bgsound', 'blink', 'body',
1416 'frame', 'frameset', 'head', 'html', 'ilayer',
1417 'iframe', 'layer', 'link', 'meta', 'object',
1418 'script', 'style' ,'title', 'xml' // clean later..
1420 Roo.HtmlEditorCore.clean = [
1421 'script', 'style', 'title', 'xml'
1423 Roo.HtmlEditorCore.remove = [
1428 Roo.HtmlEditorCore.ablack = [
1432 Roo.HtmlEditorCore.aclean = [
1433 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1437 Roo.HtmlEditorCore.pwhite= [
1438 'http', 'https', 'mailto'
1441 // white listed style attributes.
1442 Roo.HtmlEditorCore.cwhite= [
1443 // 'text-align', /// default is to allow most things..
1449 // black listed style attributes.
1450 Roo.HtmlEditorCore.cblack= [
1451 // 'font-size' -- this can be set by the project
1455 Roo.HtmlEditorCore.swapCodes =[