/*** * This is based loosely on tinymce * @class Roo.htmleditor.TidyWriter * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js * * Known issues? * - not tested much with 'PRE' formated elements. * * * */ Roo.htmleditor.TidyWriter = function(settings) { // indent, indentBefore, indentAfter, encode, htmlOutput, html = []; Roo.apply(this, settings); this.html = []; this.state = []; this.encode = Roo.htmleditor.TidyEntities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); } Roo.htmleditor.TidyWriter.prototype = { state : false, indent : ' ', // part of state... indentstr : '', in_pre: false, in_inline : false, last_inline : false, encode : false, /** * Writes the a start element such as <p id="a">. * * @method start * @param {String} name Name of the element. * @param {Array} attrs Optional attribute array or undefined if it hasn't any. * @param {Boolean} empty Optional empty state if the tag should end like <br />. */ start: function(name, attrs, empty, node) { var i, l, attr, value; // there are some situations where adding line break && indentation will not work. will not work. // <span / b / i ... formating? var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1; var in_pre = this.in_pre || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1; var is_short = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false; var add_lb = name == 'BR' ? false : in_inline; if (!add_lb && !this.in_pre && this.lastElementEndsWS()) { i_inline = false; } var indentstr = this.indentstr; // e_inline = elements that can be inline, but still allow \n before and after? // only 'BR' ??? any others? // ADD LINE BEFORE tage if (!this.in_pre) { if (in_inline) { //code if (name == 'BR') { this.addLine(); } else if (this.lastElementEndsWS()) { this.addLine(); } else{ // otherwise - no new line. (and dont indent.) indentstr = ''; } } else { this.addLine(); } } else { indentstr = ''; } this.html.push(indentstr + '<', name.toLowerCase()); if (attrs) { for (i = 0, l = attrs.length; i < l; i++) { attr = attrs[i]; this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"'); } } if (empty) { if (is_short) { this.html[this.html.length] = '/>'; } else { this.html[this.html.length] = '></' + name.toLowerCase() + '>'; } var e_inline = name == 'BR' ? false : this.in_inline; if (!e_inline && !this.in_pre) { this.addLine(); } return; } // not empty.. this.html[this.html.length] = '>'; // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these. /* if (!in_inline && !in_pre) { var cn = node.firstChild; while(cn) { if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) { in_inline = true break; } cn = cn.nextSibling; } } */ this.pushState({ indentstr : in_pre ? '' : (this.indentstr + this.indent), in_pre : in_pre, in_inline : in_inline }); // add a line after if we are not in a if (!in_inline && !in_pre) { this.addLine(); } }, lastElementEndsWS : function() { var value = this.html.length > 0 ? this.html[this.html.length-1] : false; if (value === false) { return true; } return value.match(/\s+$/); }, /** * Writes the a end element such as </p>. * * @method end * @param {String} name Name of the element. */ end: function(name) { var value; this.popState(); var indentstr = ''; var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1; if (!this.in_pre && !in_inline) { this.addLine(); indentstr = this.indentstr; } this.html.push(indentstr + '</', name.toLowerCase(), '>'); this.last_inline = in_inline; // pop the indent state.. }, /** * Writes a text node. * * In pre - we should not mess with the contents. * * * @method text * @param {String} text String to write out. * @param {Boolean} raw Optional raw state if true the contents wont get encoded. */ text: function(in_text, node) { // if not in whitespace critical if (in_text.length < 1) { return; } var text = new XMLSerializer().serializeToString(document.createTextNode(in_text)); // escape it properly? if (this.in_pre) { this.html[this.html.length] = text; return; } if (this.in_inline) { text = text.replace(/\s+/g,' '); // all white space inc line breaks to a slingle' ' if (text != ' ') { text = text.replace(/\s+/,' '); // all white space to single white space // if next tag is '<BR>', then we can trim right.. if (node.nextSibling && node.nextSibling.nodeType == 1 && node.nextSibling.nodeName == 'BR' ) { text = text.replace(/\s+$/g,''); } // if previous tag was a BR, we can also trim.. if (node.previousSibling && node.previousSibling.nodeType == 1 && node.previousSibling.nodeName == 'BR' ) { text = this.indentstr + text.replace(/^\s+/g,''); } if (text.match(/\n/)) { text = text.replace( /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr ); // remoeve the last whitespace / line break. text = text.replace(/\n\s+$/,''); } // repace long lines } this.html[this.html.length] = text; return; } // see if previous element was a inline element. var indentstr = this.indentstr; text = text.replace(/\s+/g," "); // all whitespace into single white space. // should trim left? if (node.previousSibling && node.previousSibling.nodeType == 1 && Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.previousSibling.nodeName) > -1) { indentstr = ''; } else { this.addLine(); text = text.replace(/^\s+/,''); // trim left } // should trim right? if (node.nextSibling && node.nextSibling.nodeType == 1 && Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.nextSibling.nodeName) > -1) { // noop } else { text = text.replace(/\s+$/,''); // trim right } if (text.length < 1) { return; } if (!text.match(/\n/)) { this.html.push(indentstr + text); return; } text = this.indentstr + text.replace( /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr ); // remoeve the last whitespace / line break. text = text.replace(/\s+$/,''); this.html.push(text); // split and indent.. }, /** * Writes a cdata node such as <![CDATA[data]]>. * * @method cdata * @param {String} text String to write out inside the cdata. */ cdata: function(text) { this.html.push('<![CDATA[', text, ']]>'); }, /** * Writes a comment node such as <!-- Comment -->. * * @method cdata * @param {String} text String to write out inside the comment. */ comment: function(text) { this.html.push('<!--', text, '-->'); }, /** * Writes a PI node such as <?xml attr="value" ?>. * * @method pi * @param {String} name Name of the pi. * @param {String} text String to write out inside the pi. */ pi: function(name, text) { text ? this.html.push('<?', name, ' ', this.encode(text), '?>') : this.html.push('<?', name, '?>'); this.indent != '' && this.html.push('\n'); }, /** * Writes a doctype node such as <!DOCTYPE data>. * * @method doctype * @param {String} text String to write out inside the doctype. */ doctype: function(text) { this.html.push('<!DOCTYPE', text, '>', this.indent != '' ? '\n' : ''); }, /** * Resets the internal buffer if one wants to reuse the writer. * * @method reset */ reset: function() { this.html.length = 0; this.state = []; this.pushState({ indentstr : '', in_pre : false, in_inline : false }) }, /** * Returns the contents that got serialized. * * @method getContent * @return {String} HTML contents that got written down. */ getContent: function() { return this.html.join('').replace(/\n$/, ''); }, pushState : function(cfg) { this.state.push(cfg); Roo.apply(this, cfg); }, popState : function() { if (this.state.length < 1) { return; // nothing to push } var cfg = { in_pre: false, indentstr : '' }; this.state.pop(); if (this.state.length > 0) { cfg = this.state[this.state.length-1]; } Roo.apply(this, cfg); }, addLine: function() { if (this.html.length < 1) { return; } var value = this.html[this.html.length - 1]; if (value.length > 0 && '\n' !== value) { this.html.push('\n'); } } //'pre script noscript style textarea video audio iframe object code' // shortended... 'area base basefont br col frame hr img input isindex link meta param embed source wbr track'); // inline }; Roo.htmleditor.TidyWriter.inline_elements = [ 'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR', 'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP', 'A' ]; Roo.htmleditor.TidyWriter.shortend_elements = [ 'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT', 'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK' ]; Roo.htmleditor.TidyWriter.whitespace_elements = [ 'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE' ];