41052673d3fa5bc904fa5e4b21fffa29840d693f
[roojs1] / Roo / htmleditor / TidyWriter.js
1 /***
2  * This is based loosely on tinymce 
3  * @class Roo.htmleditor.TidyWriter
4  * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
5  */
6
7 Roo.htmleditor.TidyWriter = function(settings)
8 {
9     
10     // indent, indentBefore, indentAfter, encode, htmlOutput, html = [];
11     Roo.apply(this, settings);
12     this.html = [];
13     this.state = [];
14      
15     this.encode = Roo.htmleditor.TidyEntities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
16   
17 }
18 Roo.htmleditor.TidyWriter.prototype = {
19
20
21
22     makeMap : function (items, delim, map) {
23                 var i;
24                 items = items || [];
25                 delim = delim || ',';
26                 if (typeof items == "string") {
27                         items = items.split(delim);
28                 }
29                 map = map || {};
30                 i = items.length;
31                 while (i--) {
32                         map[items[i]] = {};
33                 }
34                 return map;
35         },
36
37     state : false,
38     
39     indent :  '  ',
40     
41     // part of state...
42     indentstr : '',
43     in_pre: false,
44     in_inline : false,
45     last_inline : false,
46     encode : false,
47      
48     
49             /**
50     * Writes the a start element such as <p id="a">.
51     *
52     * @method start
53     * @param {String} name Name of the element.
54     * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
55     * @param {Boolean} empty Optional empty state if the tag should end like <br />.
56     */
57     start: function(name, attrs, empty, node)
58     {
59         var i, l, attr, value;
60         
61         // there are some situations where adding line break && indentation will not work. will not work.
62         // <span / b / i ... formating?
63         
64         var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
65         var in_pre    = this.in_pre    || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1;
66         
67         var is_short   = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false;
68         
69
70         var indentstr = in_inline || this.in_pre ? '' : this.indentstr;
71         
72         // if this element is inline - then don't add stuff beforehand..
73         if (!in_inline && !this.in_pre) {
74             this.addLine();
75         }
76         
77         this.html.push(indentstr + '<', name.toLowerCase());
78         
79         if (attrs) {
80             for (i = 0, l = attrs.length; i < l; i++) {
81                 attr = attrs[i];
82                 this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"');
83             }
84         }
85      
86         if (empty) {
87             if (is_short) {
88                 this.html[this.html.length] = '/>';
89             } else {
90                 this.html[this.html.length] = '></' + name.toLowerCase() + '>';
91             }
92             
93             if (!this.in_inline && !this.in_pre) {
94                 this.addLine();
95             }
96             return;
97         
98         }
99         // not empty..
100         this.html[this.html.length] = '>';
101         
102         // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these.
103         /*
104         if (!in_inline && !in_pre) {
105             var cn = node.firstChild;
106             while(cn) {
107                 if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) {
108                     in_inline = true
109                     break;
110                 }
111                 cn = cn.nextSibling;
112             }
113              
114         }
115         */
116         
117         
118         this.pushState({
119             indentstr : in_pre || in_inline ? '' : (this.indentstr + this.indent),
120             in_pre : in_pre,
121             in_inline :  in_inline
122         });
123         // add a line after if we are not in a
124         
125         if (!in_inline && !in_pre) {
126             this.addLine();
127         }
128         
129             
130          
131         
132     },
133     /**
134      * Writes the a end element such as </p>.
135      *
136      * @method end
137      * @param {String} name Name of the element.
138      */
139     end: function(name) {
140         var value;
141         this.popState();
142         var indentstr = '';
143         var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
144         
145         if (!this.in_pre && !in_inline) {
146             this.addLine();
147             indentstr  = this.indentstr;
148         }
149         this.html.push(indentstr + '</', name.toLowerCase(), '>');
150         this.last_inline = in_inline;
151         
152         // pop the indent state..
153     },
154     /**
155      * Writes a text node.
156      *
157      * In pre - we should not mess with the contents.
158      * 
159      *
160      * @method text
161      * @param {String} text String to write out.
162      * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
163      */
164     text: function(text, node)
165     {
166         // if not in whitespace critical
167         if (text.length < 1) {
168             return;
169         }
170         if (this.in_pre || this.in_inline) {
171             this.html[this.html.length] =  text;
172             return;   
173         }
174         // see if last line is a line break
175         var indentstr = this.indentstr;
176         if (node.previousSibling && node.previousSibling.nodeType == 1 && Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.previousSibling.nodeName) > -1) {
177             indentstr = '';
178         } else {
179             this.addLine();
180         }
181             
182         
183         
184         text = text.replace(/\s/g," ") // all line breaks to ' '
185                 .replace(/^\s+/,'')  // leding white space
186                 .replace(/\s+$/,''); // clean trailing white space
187         
188         if (text.length < 1) {
189             return;
190         }
191         if (!text.match(/\n/)) {
192             this.html.push(indentstr + text);
193             return;
194         }
195         
196         text = this.indentstr + text.replace(
197             /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
198         );
199         // remoeve the last whitespace / line break.
200         text = text.replace(/\s+$/,''); 
201         
202         this.html.push(text);
203         
204         // split and indent..
205         
206         
207     },
208     /**
209      * Writes a cdata node such as <![CDATA[data]]>.
210      *
211      * @method cdata
212      * @param {String} text String to write out inside the cdata.
213      */
214     cdata: function(text) {
215         this.html.push('<![CDATA[', text, ']]>');
216     },
217     /**
218     * Writes a comment node such as <!-- Comment -->.
219     *
220     * @method cdata
221     * @param {String} text String to write out inside the comment.
222     */
223    comment: function(text) {
224        this.html.push('<!--', text, '-->');
225    },
226     /**
227      * Writes a PI node such as <?xml attr="value" ?>.
228      *
229      * @method pi
230      * @param {String} name Name of the pi.
231      * @param {String} text String to write out inside the pi.
232      */
233     pi: function(name, text) {
234         text ? this.html.push('<?', name, ' ', this.encode(text), '?>') : this.html.push('<?', name, '?>');
235         this.indent != '' && this.html.push('\n');
236     },
237     /**
238      * Writes a doctype node such as <!DOCTYPE data>.
239      *
240      * @method doctype
241      * @param {String} text String to write out inside the doctype.
242      */
243     doctype: function(text) {
244         this.html.push('<!DOCTYPE', text, '>', this.indent != '' ? '\n' : '');
245     },
246     /**
247      * Resets the internal buffer if one wants to reuse the writer.
248      *
249      * @method reset
250      */
251     reset: function() {
252         this.html.length = 0;
253         this.state = [];
254         this.pushState({
255             indentstr : '',
256             in_pre : false, 
257             in_inline : false
258         })
259     },
260     /**
261      * Returns the contents that got serialized.
262      *
263      * @method getContent
264      * @return {String} HTML contents that got written down.
265      */
266     getContent: function() {
267         return this.html.join('').replace(/\n$/, '');
268     },
269     
270     pushState : function(cfg)
271     {
272         this.state.push(cfg);
273         Roo.apply(this, cfg);
274     },
275     
276     popState : function()
277     {
278         if (this.state.length < 1) {
279             return; // nothing to push
280         }
281         var cfg = {
282             in_pre: false,
283             indentstr : ''
284         };
285         this.state.pop();
286         if (this.state.length > 0) {
287             cfg = this.state[this.state.length-1]; 
288         }
289         Roo.apply(this, cfg);
290     },
291     
292     addLine: function()
293     {
294         if (this.html.length < 1) {
295             return;
296         }
297         
298         
299         var value = this.html[this.html.length - 1];
300         if (value.length > 0 && '\n' !== value) {
301             this.html.push('\n');
302         }
303     }
304     
305     
306 //'pre script noscript style textarea video audio iframe object code'
307 // shortended... 'area base basefont br col frame hr img input isindex link  meta param embed source wbr track');
308 // inline 
309 };
310
311 Roo.htmleditor.TidyWriter.inline_elements = [
312         'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR',
313         'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP'
314 ];
315 Roo.htmleditor.TidyWriter.shortend_elements = [
316     'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT',
317     'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK'
318 ];
319
320 Roo.htmleditor.TidyWriter.whitespace_elements = [
321     'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE'
322 ];