bfcb7cfb2a28ffc144b204fa3c73d04e540eaca7
[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     
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 = this.in_inline || this.in_pre ? '' : this.indentstr;
71         
72         if (!in_inline && !this.in_pre) {
73             this.addLine();
74         }
75         
76         this.html.push(indentstr + '<', name.toLowerCase());
77         
78         if (attrs) {
79             for (i = 0, l = attrs.length; i < l; i++) {
80                 attr = attrs[i];
81                 this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"');
82             }
83         }
84      
85         if (empty) {
86             if (is_short) {
87                 this.html[this.html.length] = '/>';
88             } else {
89                 this.html[this.html.length] = '></' + name.toLowerCase() + '>';
90             }
91             
92             if (!this.in_inline && !this.in_pre) {
93                 this.addLine();
94             }
95             return;
96         
97         }
98         // not empty..
99         this.html[this.html.length] = '>';
100         
101         // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these.
102         /*
103         if (!in_inline && !in_pre) {
104             var cn = node.firstChild;
105             while(cn) {
106                 if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) {
107                     in_inline = true
108                     break;
109                 }
110                 cn = cn.nextSibling;
111             }
112              
113         }
114         */
115         
116         
117         this.pushState({
118             indentstr : in_pre || in_inline ? '' : (this.indentstr + this.indent),
119             in_pre : in_pre,
120             in_inline :  in_inline
121         });
122         // add a line after if we are not in a
123         
124         if (!in_inline && !in_pre) {
125             this.addLine();
126         }
127         
128             
129          
130         
131     },
132     /**
133      * Writes the a end element such as </p>.
134      *
135      * @method end
136      * @param {String} name Name of the element.
137      */
138     end: function(name) {
139         var value;
140         this.popState();
141         var indentstr = ''; 
142         if (!this.in_pre && !this.in_inline) {
143             this.addLine();
144             indentstr  = this.indentstr;
145         }
146         this.html.push(indentstr + '</', name.toLowerCase(), '>');
147        
148         
149         // pop the indent state..
150     },
151     /**
152      * Writes a text node.
153      *
154      * In pre - we should not mess with the contents.
155      * 
156      *
157      * @method text
158      * @param {String} text String to write out.
159      * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
160      */
161     text: function(text )
162     {
163         // if not in whitespace critical
164         if (text.length < 1) {
165             return;
166         }
167         if (this.in_pre || this.is_inline) {
168             this.html[this.html.length] =  text;
169             return;
170             
171         }
172         // see if last line is a line break
173         
174         this.addLine();
175             
176         
177         
178         text = text.replace(/\s/g," ") // all line breaks to ' '
179                 .replace(/^\s+/,'')  // leding white space
180                 .replace(/\s+$/,''); // clean trailing white space
181         
182         if (text.length < 1) {
183             return;
184         }
185         if (!text.match(/\n/)) {
186             this.html.push(this.indentstr + text);
187             return;
188         }
189         
190         text = this.indentstr + text.replace(
191             /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
192         );
193         // remoeve the last whitespace / line break.
194         text = text.replace(/\s+$/,''); 
195         
196         this.html.push(text);
197         
198         // split and indent..
199         
200         
201     },
202     /**
203      * Writes a cdata node such as <![CDATA[data]]>.
204      *
205      * @method cdata
206      * @param {String} text String to write out inside the cdata.
207      */
208     cdata: function(text) {
209         this.html.push('<![CDATA[', text, ']]>');
210     },
211     /**
212     * Writes a comment node such as <!-- Comment -->.
213     *
214     * @method cdata
215     * @param {String} text String to write out inside the comment.
216     */
217    comment: function(text) {
218        this.html.push('<!--', text, '-->');
219    },
220     /**
221      * Writes a PI node such as <?xml attr="value" ?>.
222      *
223      * @method pi
224      * @param {String} name Name of the pi.
225      * @param {String} text String to write out inside the pi.
226      */
227     pi: function(name, text) {
228         text ? this.html.push('<?', name, ' ', this.encode(text), '?>') : this.html.push('<?', name, '?>');
229         this.indent != '' && this.html.push('\n');
230     },
231     /**
232      * Writes a doctype node such as <!DOCTYPE data>.
233      *
234      * @method doctype
235      * @param {String} text String to write out inside the doctype.
236      */
237     doctype: function(text) {
238         this.html.push('<!DOCTYPE', text, '>', this.indent != '' ? '\n' : '');
239     },
240     /**
241      * Resets the internal buffer if one wants to reuse the writer.
242      *
243      * @method reset
244      */
245     reset: function() {
246         this.html.length = 0;
247         this.state = [];
248         this.pushState({
249             indentstr : '',
250             in_pre : false, 
251             in_inline : false
252         })
253     },
254     /**
255      * Returns the contents that got serialized.
256      *
257      * @method getContent
258      * @return {String} HTML contents that got written down.
259      */
260     getContent: function() {
261         return this.html.join('').replace(/\n$/, '');
262     },
263     
264     pushState : function(cfg)
265     {
266         this.state.push(cfg);
267         Roo.apply(this, cfg);
268     },
269     
270     popState : function()
271     {
272         if (this.state.length < 1) {
273             return; // nothing to push
274         }
275         var cfg = {
276             in_pre: false,
277             indentstr : ''
278         };
279         this.state.pop();
280         if (this.state.length > 0) {
281             cfg = this.state[this.state.length-1]; 
282         }
283         Roo.apply(this, cfg);
284     },
285     
286     addLine: function()
287     {
288         if (this.html.length < 1) {
289             return;
290         }
291         
292         
293         var value = this.html[this.html.length - 1];
294         if (value.length > 0 && '\n' !== value) {
295             this.html.push('\n');
296         }
297     }
298     
299     
300 //'pre script noscript style textarea video audio iframe object code'
301 // shortended... 'area base basefont br col frame hr img input isindex link  meta param embed source wbr track');
302 // inline 
303 };
304
305 Roo.htmleditor.TidyWriter.inline_elements = [
306         'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR',
307         'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP'
308 ];
309 Roo.htmleditor.TidyWriter.shortend_elements = [
310     'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT',
311     'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK'
312 ];
313
314 Roo.htmleditor.TidyWriter.whitespace_elements = [
315     'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE'
316 ];