Merge branch 'master' of http://git.roojs.com/roojs1
[roojs1] / Roo / htmleditor / FilterWord.js
1 /**
2  * @class Roo.htmleditor.FilterWord
3  * try and clean up all the mess that Word generates.
4  * 
5  * This is the 'nice version' - see 'Heavy' that white lists a very short list of elements, and multi-filters 
6  
7  * @constructor
8  * Run a new Span Filter
9  * @param {Object} config Configuration options
10  */
11
12 Roo.htmleditor.FilterWord = function(cfg)
13 {
14     // no need to apply config.
15     this.replaceDocBullets(cfg.node);
16     
17     this.replaceAname(cfg.node);
18     // this is disabled as the removal is done by other filters;
19    // this.walk(cfg.node);
20     this.replaceImageTable(cfg.node);
21     
22 }
23
24 Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter,
25 {
26     tag: true,
27      
28     
29     /**
30      * Clean up MS wordisms...
31      */
32     replaceTag : function(node)
33     {
34          
35         // no idea what this does - span with text, replaceds with just text.
36         if(
37                 node.nodeName == 'SPAN' &&
38                 !node.hasAttributes() &&
39                 node.childNodes.length == 1 &&
40                 node.firstChild.nodeName == "#text"  
41         ) {
42             var textNode = node.firstChild;
43             node.removeChild(textNode);
44             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
45                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
46             }
47             node.parentNode.insertBefore(textNode, node);
48             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
49                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
50             }
51             
52             node.parentNode.removeChild(node);
53             return false; // dont do chidren - we have remove our node - so no need to do chdhilren?
54         }
55         
56    
57         
58         if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
59             node.parentNode.removeChild(node);
60             return false; // dont do chidlren
61         }
62         //Roo.log(node.tagName);
63         // remove - but keep children..
64         if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
65             //Roo.log('-- removed');
66             while (node.childNodes.length) {
67                 var cn = node.childNodes[0];
68                 node.removeChild(cn);
69                 node.parentNode.insertBefore(cn, node);
70                 // move node to parent - and clean it..
71                 if (cn.nodeType == 1) {
72                     this.replaceTag(cn);
73                 }
74                 
75             }
76             node.parentNode.removeChild(node);
77             /// no need to iterate chidlren = it's got none..
78             //this.iterateChildren(node, this.cleanWord);
79             return false; // no need to iterate children.
80         }
81         // clean styles
82         if (node.className.length) {
83             
84             var cn = node.className.split(/\W+/);
85             var cna = [];
86             Roo.each(cn, function(cls) {
87                 if (cls.match(/Mso[a-zA-Z]+/)) {
88                     return;
89                 }
90                 cna.push(cls);
91             });
92             node.className = cna.length ? cna.join(' ') : '';
93             if (!cna.length) {
94                 node.removeAttribute("class");
95             }
96         }
97         
98         if (node.hasAttribute("lang")) {
99             node.removeAttribute("lang");
100         }
101         
102         if (node.hasAttribute("style")) {
103             
104             var styles = node.getAttribute("style").split(";");
105             var nstyle = [];
106             Roo.each(styles, function(s) {
107                 if (!s.match(/:/)) {
108                     return;
109                 }
110                 var kv = s.split(":");
111                 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
112                     return;
113                 }
114                 // what ever is left... we allow.
115                 nstyle.push(s);
116             });
117             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
118             if (!nstyle.length) {
119                 node.removeAttribute('style');
120             }
121         }
122         return true; // do children
123         
124         
125         
126     },
127     
128     styleToObject: function(node)
129     {
130         var styles = (node.getAttribute("style") || '').split(";");
131         var ret = {};
132         Roo.each(styles, function(s) {
133             if (!s.match(/:/)) {
134                 return;
135             }
136             var kv = s.split(":");
137              
138             // what ever is left... we allow.
139             ret[kv[0].trim()] = kv[1];
140         });
141         return ret;
142     },
143     
144     
145     replaceAname : function (doc)
146     {
147         // replace all the a/name without..
148         var aa = Array.from(doc.getElementsByTagName('a'));
149         for (var i = 0; i  < aa.length; i++) {
150             var a = aa[i];
151             if (a.hasAttribute("name")) {
152                 a.removeAttribute("name");
153             }
154             if (a.hasAttribute("href")) {
155                 continue;
156             }
157             // reparent children.
158             this.removeNodeKeepChildren(a);
159             
160         }
161         
162         
163         
164     },
165
166     
167     
168     replaceDocBullets : function(doc)
169     {
170         // this is a bit odd - but it appears some indents use ql-indent-1
171          //Roo.log(doc.innerHTML);
172         
173         var listpara = Array.from(doc.getElementsByClassName('MsoListParagraphCxSpFirst'));
174         for( var i = 0; i < listpara.length; i ++) {
175             listpara[i].className = "MsoListParagraph";
176         }
177         
178         listpara =  Array.from(doc.getElementsByClassName('MsoListParagraphCxSpMiddle'));
179         for( var i = 0; i < listpara.length; i ++) {
180             listpara[i].className = "MsoListParagraph";
181         }
182         listpara =  Array.from(doc.getElementsByClassName('MsoListParagraphCxSpLast'));
183         for( var i = 0; i < listpara.length; i ++) {
184             listpara[i].className = "MsoListParagraph";
185         }
186         listpara =  Array.from(doc.getElementsByClassName('ql-indent-1'));
187         for( var i = 0; i < listpara.length; i ++) {
188             listpara[i].className = "MsoListParagraph";
189         }
190         
191         // this is a bit hacky - we had one word document where h2 had a miso-list attribute.
192         var htwo =  Array.from(doc.getElementsByTagName('h2'));
193         for( var i = 0; i < htwo.length; i ++) {
194             if (htwo[i].hasAttribute('style') && htwo[i].getAttribute('style').match(/mso-list:/)) {
195                 htwo[i].className = "MsoListParagraph";
196             }
197         }
198         listpara =  Array.from(doc.getElementsByClassName('MsoNormal'));
199         for( var i = 0; i < listpara.length; i ++) {
200             if (listpara[i].hasAttribute('style') && listpara[i].getAttribute('style').match(/mso-list:/)) {
201                 listpara[i].className = "MsoListParagraph";
202             } else {
203                 listpara[i].className = "MsoNormalx";
204             }
205         }
206        
207         listpara = doc.getElementsByClassName('MsoListParagraph');
208         // Roo.log(doc.innerHTML);
209         
210         
211         
212         while(listpara.length) {
213             
214             this.replaceDocBullet(listpara.item(0));
215         }
216       
217     },
218     
219      
220     
221     replaceDocBullet : function(p)
222     {
223         // gather all the siblings.
224         var ns = p,
225             parent = p.parentNode,
226             doc = parent.ownerDocument,
227             items = [];
228          
229         //Roo.log("Parsing: " + p.innerText)    ;
230         var listtype = 'ul';   
231         while (ns) {
232             if (ns.nodeType != 1) {
233                 ns = ns.nextSibling;
234                 continue;
235             }
236             if (!ns.className.match(/(MsoListParagraph|ql-indent-1)/i)) {
237                 //Roo.log("Missing para r q1indent - got:" + ns.className);
238                 break;
239             }
240             var spans = ns.getElementsByTagName('span');
241             
242             if (ns.hasAttribute('style') && ns.getAttribute('style').match(/mso-list/)) {
243                 items.push(ns);
244                 ns = ns.nextSibling;
245                 has_list = true;
246                 if (!spans.length) {
247                     continue;
248                 }
249                 var ff = '';
250                 var se = spans[0];
251                 for (var i = 0; i < spans.length;i++) {
252                     se = spans[i];
253                     if (se.hasAttribute('style')  && se.hasAttribute('style') && se.style.fontFamily != '') {
254                         ff = se.style.fontFamily;
255                         break;
256                     }
257                 }
258                  
259                     
260                 //Roo.log("got font family: " + ff);
261                 if (typeof(ff) != 'undefined' && !ff.match(/(Symbol|Wingdings)/) && "·o".indexOf(se.innerText.trim()) < 0) {
262                     listtype = 'ol';
263                 }
264                 
265                 continue;
266             }
267             //Roo.log("no mso-list?");
268             
269             var spans = ns.getElementsByTagName('span');
270             if (!spans.length) {
271                 break;
272             }
273             var has_list  = false;
274             for(var i = 0; i < spans.length; i++) {
275                 if (spans[i].hasAttribute('style') && spans[i].getAttribute('style').match(/mso-list/)) {
276                     has_list = true;
277                     break;
278                 }
279             }
280             if (!has_list) {
281                 break;
282             }
283             items.push(ns);
284             ns = ns.nextSibling;
285             
286             
287         }
288         if (!items.length) {
289             ns.className = "";
290             return;
291         }
292         
293         var ul = parent.ownerDocument.createElement(listtype); // what about number lists...
294         parent.insertBefore(ul, p);
295         var lvl = 0;
296         var stack = [ ul ];
297         var last_li = false;
298         
299         var margin_to_depth = {};
300         max_margins = -1;
301         
302         items.forEach(function(n, ipos) {
303             //Roo.log("got innertHMLT=" + n.innerHTML);
304             
305             var spans = n.getElementsByTagName('span');
306             if (!spans.length) {
307                 //Roo.log("No spans found");
308                  
309                 parent.removeChild(n);
310                 
311                 
312                 return; // skip it...
313             }
314            
315                 
316             var num = 1;
317             var style = {};
318             for(var i = 0; i < spans.length; i++) {
319             
320                 style = this.styleToObject(spans[i]);
321                 if (typeof(style['mso-list']) == 'undefined') {
322                     continue;
323                 }
324                 if (listtype == 'ol') {
325                    num = spans[i].innerText.replace(/[^0-9]+]/g,'')  * 1;
326                 }
327                 spans[i].parentNode.removeChild(spans[i]); // remove the fake bullet.
328                 break;
329             }
330             //Roo.log("NOW GOT innertHMLT=" + n.innerHTML);
331             style = this.styleToObject(n); // mo-list is from the parent node.
332             if (typeof(style['mso-list']) == 'undefined') {
333                 //Roo.log("parent is missing level");
334                   
335                 parent.removeChild(n);
336                  
337                 return;
338             }
339             
340             var margin = style['margin-left'];
341             if (typeof(margin_to_depth[margin]) == 'undefined') {
342                 max_margins++;
343                 margin_to_depth[margin] = max_margins;
344             }
345             nlvl = margin_to_depth[margin] ;
346              
347             if (nlvl > lvl) {
348                 //new indent
349                 var nul = doc.createElement(listtype); // what about number lists...
350                 if (!last_li) {
351                     last_li = doc.createElement('li');
352                     stack[lvl].appendChild(last_li);
353                 }
354                 last_li.appendChild(nul);
355                 stack[nlvl] = nul;
356                 
357             }
358             lvl = nlvl;
359             
360             // not starting at 1..
361             if (!stack[nlvl].hasAttribute("start") && listtype == "ol") {
362                 stack[nlvl].setAttribute("start", num);
363             }
364             
365             var nli = stack[nlvl].appendChild(doc.createElement('li'));
366             last_li = nli;
367             nli.innerHTML = n.innerHTML;
368             //Roo.log("innerHTML = " + n.innerHTML);
369             parent.removeChild(n);
370             
371              
372              
373             
374         },this);
375         
376         
377         
378         
379     },
380     
381     replaceImageTable : function(doc)
382     {
383          /*
384           <table cellpadding=0 cellspacing=0 align=left>
385   <tr>
386    <td width=423 height=0></td>
387   </tr>
388   <tr>
389    <td></td>
390    <td><img width=601 height=401
391    src="file:///C:/Users/Alan/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg"
392    v:shapes="Picture_x0020_2"></td>
393   </tr>
394  </table>
395  */
396         var imgs = Array.from(doc.getElementsByTagName('img'));
397         Roo.each(imgs, function(img) {
398             var td = img.parentNode;
399             if (td.nodeName !=  'TD') {
400                 return;
401             }
402             var tr = td.parentNode;
403             if (tr.nodeName !=  'TR') {
404                 return;
405             }
406             var tbody = tr.parentNode;
407             if (tbody.nodeName !=  'TBODY') {
408                 return;
409             }
410             var table = tbody.parentNode;
411             if (table.nodeName !=  'TABLE') {
412                 return;
413             }
414             // first row..
415             
416             if (table.getElementsByTagName('tr').length != 2) {
417                 return;
418             }
419             if (table.getElementsByTagName('td').length != 3) {
420                 return;
421             }
422             if (table.innerText.trim() != '') {
423                 return;
424             }
425             var p = table.parentNode;
426             img.parentNode.removeChild(img);
427             p.insertBefore(img, table);
428             p.removeChild(table);
429             
430             
431             
432         });
433         
434       
435     }
436     
437 });