2 * This is based loosely on tinymce
3 * @class Roo.htmleditor.TidyWriter
4 * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
7 * - not tested much with 'PRE' formated elements.
13 Roo.htmleditor.TidyWriter = function(settings)
16 // indent, indentBefore, indentAfter, encode, htmlOutput, html = [];
17 Roo.apply(this, settings);
21 this.encode = Roo.htmleditor.TidyEntities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
24 Roo.htmleditor.TidyWriter.prototype = {
40 * Writes the a start element such as <p id="a">.
43 * @param {String} name Name of the element.
44 * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
45 * @param {Boolean} empty Optional empty state if the tag should end like <br />.
47 start: function(name, attrs, empty, node)
49 var i, l, attr, value;
51 // there are some situations where adding line break && indentation will not work. will not work.
52 // <span / b / i ... formating?
54 var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
55 var in_pre = this.in_pre || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1;
57 var is_short = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false;
59 var add_lb = name == 'BR' ? false : in_inline;
61 if (!add_lb && !this.in_pre && this.lastElementEndsWS()) {
65 var indentstr = this.indentstr;
67 // e_inline = elements that can be inline, but still allow \n before and after?
68 // only 'BR' ??? any others?
70 // ADD LINE BEFORE tage
76 } else if (this.lastElementEndsWS()) {
79 // otherwise - no new line. (and dont indent.)
90 this.html.push(indentstr + '<', name.toLowerCase());
93 for (i = 0, l = attrs.length; i < l; i++) {
95 this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"');
101 this.html[this.html.length] = '/>';
103 this.html[this.html.length] = '></' + name.toLowerCase() + '>';
105 var e_inline = name == 'BR' ? false : this.in_inline;
107 if (!e_inline && !this.in_pre) {
114 this.html[this.html.length] = '>';
116 // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these.
118 if (!in_inline && !in_pre) {
119 var cn = node.firstChild;
121 if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) {
133 indentstr : in_pre ? '' : (this.indentstr + this.indent),
135 in_inline : in_inline
137 // add a line after if we are not in a
139 if (!in_inline && !in_pre) {
148 lastElementEndsWS : function()
150 var value = this.html.length > 0 ? this.html[this.html.length-1] : false;
151 if (value === false) {
154 return value.match(/\s+$/);
159 * Writes the a end element such as </p>.
162 * @param {String} name Name of the element.
164 end: function(name) {
168 var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
170 if (!this.in_pre && !in_inline) {
172 indentstr = this.indentstr;
174 this.html.push(indentstr + '</', name.toLowerCase(), '>');
175 this.last_inline = in_inline;
177 // pop the indent state..
180 * Writes a text node.
182 * In pre - we should not mess with the contents.
186 * @param {String} text String to write out.
187 * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
189 text: function(in_text, node)
191 // if not in whitespace critical
192 if (in_text.length < 1) {
195 var text = new XMLSerializer().serializeToString(document.createTextNode(in_text)); // escape it properly?
198 this.html[this.html.length] = text;
202 if (this.in_inline) {
203 text = text.replace(/\s+/g,' '); // all white space inc line breaks to a slingle' '
205 text = text.replace(/\s+/,' '); // all white space to single white space
208 // if next tag is '<BR>', then we can trim right..
209 if (node.nextSibling &&
210 node.nextSibling.nodeType == 1 &&
211 node.nextSibling.nodeName == 'BR' )
213 text = text.replace(/\s+$/g,'');
215 // if previous tag was a BR, we can also trim..
216 if (node.previousSibling &&
217 node.previousSibling.nodeType == 1 &&
218 node.previousSibling.nodeName == 'BR' )
220 text = this.indentstr + text.replace(/^\s+/g,'');
222 if (text.match(/\n/)) {
224 /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
226 // remoeve the last whitespace / line break.
227 text = text.replace(/\n\s+$/,'');
233 this.html[this.html.length] = text;
236 // see if previous element was a inline element.
237 var indentstr = this.indentstr;
239 text = text.replace(/\s+/g," "); // all whitespace into single white space.
242 if (node.previousSibling &&
243 node.previousSibling.nodeType == 1 &&
244 Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.previousSibling.nodeName) > -1)
250 text = text.replace(/^\s+/,''); // trim left
253 // should trim right?
254 if (node.nextSibling &&
255 node.nextSibling.nodeType == 1 &&
256 Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.nextSibling.nodeName) > -1)
261 text = text.replace(/\s+$/,''); // trim right
268 if (text.length < 1) {
271 if (!text.match(/\n/)) {
272 this.html.push(indentstr + text);
276 text = this.indentstr + text.replace(
277 /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
279 // remoeve the last whitespace / line break.
280 text = text.replace(/\s+$/,'');
282 this.html.push(text);
284 // split and indent..
289 * Writes a cdata node such as <![CDATA[data]]>.
292 * @param {String} text String to write out inside the cdata.
294 cdata: function(text) {
295 this.html.push('<![CDATA[', text, ']]>');
298 * Writes a comment node such as <!-- Comment -->.
301 * @param {String} text String to write out inside the comment.
303 comment: function(text) {
304 this.html.push('<!--', text, '-->');
307 * Writes a PI node such as <?xml attr="value" ?>.
310 * @param {String} name Name of the pi.
311 * @param {String} text String to write out inside the pi.
313 pi: function(name, text) {
314 text ? this.html.push('<?', name, ' ', this.encode(text), '?>') : this.html.push('<?', name, '?>');
315 this.indent != '' && this.html.push('\n');
318 * Writes a doctype node such as <!DOCTYPE data>.
321 * @param {String} text String to write out inside the doctype.
323 doctype: function(text) {
324 this.html.push('<!DOCTYPE', text, '>', this.indent != '' ? '\n' : '');
327 * Resets the internal buffer if one wants to reuse the writer.
332 this.html.length = 0;
341 * Returns the contents that got serialized.
344 * @return {String} HTML contents that got written down.
346 getContent: function() {
347 return this.html.join('').replace(/\n$/, '');
350 pushState : function(cfg)
352 this.state.push(cfg);
353 Roo.apply(this, cfg);
356 popState : function()
358 if (this.state.length < 1) {
359 return; // nothing to push
366 if (this.state.length > 0) {
367 cfg = this.state[this.state.length-1];
369 Roo.apply(this, cfg);
374 if (this.html.length < 1) {
379 var value = this.html[this.html.length - 1];
380 if (value.length > 0 && '\n' !== value) {
381 this.html.push('\n');
386 //'pre script noscript style textarea video audio iframe object code'
387 // shortended... 'area base basefont br col frame hr img input isindex link meta param embed source wbr track');
391 Roo.htmleditor.TidyWriter.inline_elements = [
392 'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR',
393 'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP', 'A'
395 Roo.htmleditor.TidyWriter.shortend_elements = [
396 'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT',
397 'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK'
400 Roo.htmleditor.TidyWriter.whitespace_elements = [
401 'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE'