+
+ listpara = doc.getElementsByClassName('MsoListParagraph');
+ // Roo.log(doc.innerHTML);
+
+
+
+ while(listpara.length) {
+
+ this.replaceDocBullet(listpara.item(0));
+ }
+
+ },
+
+
+
+ replaceDocBullet : function(p)
+ {
+ // gather all the siblings.
+ var ns = p,
+ parent = p.parentNode,
+ doc = parent.ownerDocument,
+ items = [];
+
+ //Roo.log("Parsing: " + p.innerText) ;
+ var listtype = 'ul';
+ while (ns) {
+ if (ns.nodeType != 1) {
+ ns = ns.nextSibling;
+ continue;
+ }
+ if (!ns.className.match(/(MsoListParagraph|ql-indent-1)/i)) {
+ //Roo.log("Missing para r q1indent - got:" + ns.className);
+ break;
+ }
+ var spans = ns.getElementsByTagName('span');
+
+ if (ns.hasAttribute('style') && ns.getAttribute('style').match(/mso-list/)) {
+ items.push(ns);
+ ns = ns.nextSibling;
+ has_list = true;
+ if (!spans.length) {
+ continue;
+ }
+ var ff = '';
+ var se = spans[0];
+ for (var i = 0; i < spans.length;i++) {
+ se = spans[i];
+ if (se.hasAttribute('style') && se.hasAttribute('style') && se.style.fontFamily != '') {
+ ff = se.style.fontFamily;
+ break;
+ }
+ }
+
+
+ //Roo.log("got font family: " + ff);
+ if (typeof(ff) != 'undefined' && !ff.match(/(Symbol|Wingdings)/) && "·o".indexOf(se.innerText.trim()) < 0) {
+ listtype = 'ol';
+ }
+
+ continue;
+ }
+ //Roo.log("no mso-list?");
+
+ var spans = ns.getElementsByTagName('span');
+ if (!spans.length) {
+ break;
+ }
+ var has_list = false;
+ for(var i = 0; i < spans.length; i++) {
+ if (spans[i].hasAttribute('style') && spans[i].getAttribute('style').match(/mso-list/)) {
+ has_list = true;
+ break;
+ }
+ }
+ if (!has_list) {
+ break;
+ }
+ items.push(ns);
+ ns = ns.nextSibling;
+
+
+ }
+ if (!items.length) {
+ ns.className = "";
+ return;
+ }
+
+ var ul = parent.ownerDocument.createElement(listtype); // what about number lists...
+ parent.insertBefore(ul, p);
+ var lvl = 0;
+ var stack = [ ul ];
+ var last_li = false;
+
+ var margin_to_depth = {};
+ max_margins = -1;
+
+ items.forEach(function(n, ipos) {
+ //Roo.log("got innertHMLT=" + n.innerHTML);
+
+ var spans = n.getElementsByTagName('span');
+ if (!spans.length) {
+ //Roo.log("No spans found");
+
+ parent.removeChild(n);
+
+
+ return; // skip it...
+ }
+
+
+ var num = 1;
+ var style = {};
+ for(var i = 0; i < spans.length; i++) {
+
+ style = this.styleToObject(spans[i]);
+ if (typeof(style['mso-list']) == 'undefined') {
+ continue;
+ }
+ if (listtype == 'ol') {
+ num = spans[i].innerText.replace(/[^0-9]+]/g,'') * 1;
+ }
+ spans[i].parentNode.removeChild(spans[i]); // remove the fake bullet.
+ break;
+ }
+ //Roo.log("NOW GOT innertHMLT=" + n.innerHTML);
+ style = this.styleToObject(n); // mo-list is from the parent node.
+ if (typeof(style['mso-list']) == 'undefined') {
+ //Roo.log("parent is missing level");
+
+ parent.removeChild(n);
+
+ return;
+ }
+
+ var margin = style['margin-left'];
+ if (typeof(margin_to_depth[margin]) == 'undefined') {
+ max_margins++;
+ margin_to_depth[margin] = max_margins;
+ }
+ nlvl = margin_to_depth[margin] ;
+
+ if (nlvl > lvl) {
+ //new indent
+ var nul = doc.createElement(listtype); // what about number lists...
+ if (!last_li) {
+ last_li = doc.createElement('li');
+ stack[lvl].appendChild(last_li);
+ }
+ last_li.appendChild(nul);
+ stack[nlvl] = nul;
+
+ }
+ lvl = nlvl;
+
+ // not starting at 1..
+ if (!stack[nlvl].hasAttribute("start") && listtype == "ol") {
+ stack[nlvl].setAttribute("start", num);
+ }
+
+ var nli = stack[nlvl].appendChild(doc.createElement('li'));
+ last_li = nli;
+ nli.innerHTML = n.innerHTML;
+ //Roo.log("innerHTML = " + n.innerHTML);
+ parent.removeChild(n);
+
+
+
+
+ },this);
+
+
+
+
+ },
+
+ replaceImageTable : function(doc)
+ {
+ /*
+ <table cellpadding=0 cellspacing=0 align=left>
+ <tr>
+ <td width=423 height=0></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td><img width=601 height=401
+ src="file:///C:/Users/Alan/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg"
+ v:shapes="Picture_x0020_2"></td>
+ </tr>
+ </table>
+ */
+ var imgs = Array.from(doc.getElementsByTagName('img'));
+ Roo.each(imgs, function(img) {
+ var td = img.parentNode;
+ if (td.nodeName != 'TD') {
+ return;
+ }
+ var tr = td.parentNode;
+ if (tr.nodeName != 'TR') {
+ return;
+ }
+ var tbody = tr.parentNode;
+ if (tbody.nodeName != 'TBODY') {
+ return;
+ }
+ var table = tbody.parentNode;
+ if (table.nodeName != 'TABLE') {
+ return;
+ }
+ // first row..
+
+ if (table.getElementsByTagName('tr').length != 2) {
+ return;
+ }
+ if (table.getElementsByTagName('td').length != 3) {
+ return;
+ }
+ if (table.innerText.trim() != '') {
+ return;
+ }
+ var p = table.parentNode;
+ img.parentNode.removeChild(img);
+ p.insertBefore(img, table);
+ p.removeChild(table);
+
+
+
+ });
+
+
+ }
+
+});
+/**
+ * @class Roo.htmleditor.FilterStyleToTag
+ * part of the word stuff... - certain 'styles' should be converted to tags.
+ * eg.
+ * font-weight: bold -> bold
+ * ?? super / subscrit etc..
+ *
+ * @constructor
+* Run a new style to tag filter.
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterStyleToTag = function(cfg)
+{
+
+ this.tags = {
+ B : [ 'fontWeight' , 'bold'],
+ I : [ 'fontStyle' , 'italic'],
+ //pre : [ 'font-style' , 'italic'],
+ // h1.. h6 ?? font-size?
+ SUP : [ 'verticalAlign' , 'super' ],
+ SUB : [ 'verticalAlign' , 'sub' ]
+
+
+ };
+
+ Roo.apply(this, cfg);
+
+
+ this.walk(cfg.node);
+
+
+
+}
+
+
+Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter,
+{
+ tag: true, // all tags
+
+ tags : false,
+
+
+ replaceTag : function(node)
+ {
+
+
+ if (node.getAttribute("style") === null) {
+ return true;
+ }
+ var inject = [];
+ for (var k in this.tags) {
+ if (node.style[this.tags[k][0]] == this.tags[k][1]) {
+ inject.push(k);
+ node.style.removeProperty(this.tags[k][0]);
+ }
+ }
+ if (!inject.length) {
+ return true;
+ }
+ var cn = Array.from(node.childNodes);
+ var nn = node;
+ Roo.each(inject, function(t) {
+ var nc = node.ownerDocument.createElement(t);
+ nn.appendChild(nc);
+ nn = nc;
+ });
+ for(var i = 0;i < cn.length;cn++) {
+ node.removeChild(cn[i]);
+ nn.appendChild(cn[i]);
+ }
+ return true /// iterate thru
+ }
+
+})/**
+ * @class Roo.htmleditor.FilterLongBr
+ * BR/BR/BR - keep a maximum of 2...
+ * @constructor
+ * Run a new Long BR Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterLongBr = function(cfg)
+{
+ // no need to apply config.
+ this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter,
+{
+
+
+ tag : 'BR',
+
+
+ replaceTag : function(node)
+ {
+
+ var ps = node.nextSibling;
+ while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
+ ps = ps.nextSibling;
+ }
+
+ if (!ps && [ 'TD', 'TH', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(node.parentNode.tagName) > -1) {
+ node.parentNode.removeChild(node); // remove last BR inside one fo these tags
+ return false;
+ }
+
+ if (!ps || ps.nodeType != 1) {
+ return false;
+ }
+
+ if (!ps || ps.tagName != 'BR') {
+
+ return false;
+ }
+
+
+
+
+
+ if (!node.previousSibling) {
+ return false;
+ }
+ var ps = node.previousSibling;
+
+ while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
+ ps = ps.previousSibling;
+ }
+ if (!ps || ps.nodeType != 1) {
+ return false;
+ }
+ // if header or BR before.. then it's a candidate for removal.. - as we only want '2' of these..
+ if (!ps || [ 'BR', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(ps.tagName) < 0) {
+ return false;
+ }
+
+ node.parentNode.removeChild(node); // remove me...
+
+ return false; // no need to do children
+
+ }
+
+});
+
+/**
+ * @class Roo.htmleditor.FilterBlock
+ * removes id / data-block and contenteditable that are associated with blocks
+ * usage should be done on a cloned copy of the dom
+ * @constructor
+* Run a new Attribute Filter { node : xxxx }}
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterBlock = function(cfg)
+{
+ Roo.apply(this, cfg);
+ var qa = cfg.node.querySelectorAll;
+ this.removeAttributes('data-block');
+ this.removeAttributes('contenteditable');
+ this.removeAttributes('id');
+
+}
+
+Roo.apply(Roo.htmleditor.FilterBlock.prototype,
+{
+ node: true, // all tags
+
+
+ removeAttributes : function(attr)
+ {
+ var ar = this.node.querySelectorAll('*[' + attr + ']');
+ for (var i =0;i<ar.length;i++) {
+ ar[i].removeAttribute(attr);
+ }
+ }
+
+
+
+
+});
+/***
+ * This is based loosely on tinymce
+ * @class Roo.htmleditor.TidySerializer
+ * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
+ * @constructor
+ * @method Serializer
+ * @param {Object} settings Name/value settings object.
+ */
+
+
+Roo.htmleditor.TidySerializer = function(settings)
+{
+ Roo.apply(this, settings);
+
+ this.writer = new Roo.htmleditor.TidyWriter(settings);
+
+
+
+};
+Roo.htmleditor.TidySerializer.prototype = {
+
+ /**
+ * @param {boolean} inner do the inner of the node.
+ */
+ inner : false,
+
+ writer : false,
+
+ /**
+ * Serializes the specified node into a string.
+ *
+ * @example
+ * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
+ * @method serialize
+ * @param {DomElement} node Node instance to serialize.
+ * @return {String} String with HTML based on DOM tree.
+ */
+ serialize : function(node) {
+
+ // = settings.validate;
+ var writer = this.writer;
+ var self = this;
+ this.handlers = {
+ // #text
+ 3: function(node) {
+
+ writer.text(node.nodeValue, node);
+ },
+ // #comment
+ 8: function(node) {
+ writer.comment(node.nodeValue);
+ },
+ // Processing instruction
+ 7: function(node) {
+ writer.pi(node.name, node.nodeValue);
+ },
+ // Doctype
+ 10: function(node) {
+ writer.doctype(node.nodeValue);
+ },
+ // CDATA
+ 4: function(node) {
+ writer.cdata(node.nodeValue);
+ },
+ // Document fragment
+ 11: function(node) {
+ node = node.firstChild;
+ if (!node) {
+ return;
+ }
+ while(node) {
+ self.walk(node);
+ node = node.nextSibling
+ }
+ }
+ };
+ writer.reset();
+ 1 != node.nodeType || this.inner ? this.handlers[11](node) : this.walk(node);
+ return writer.getContent();
+ },
+
+ walk: function(node)
+ {
+ var attrName, attrValue, sortedAttrs, i, l, elementRule,
+ handler = this.handlers[node.nodeType];
+
+ if (handler) {
+ handler(node);
+ return;
+ }
+
+ var name = node.nodeName;
+ var isEmpty = node.childNodes.length < 1;
+
+ var writer = this.writer;
+ var attrs = node.attributes;
+ // Sort attributes
+
+ writer.start(node.nodeName, attrs, isEmpty, node);
+ if (isEmpty) {
+ return;
+ }
+ node = node.firstChild;
+ if (!node) {
+ writer.end(name);
+ return;
+ }
+ while (node) {
+ this.walk(node);
+ node = node.nextSibling;
+ }
+ writer.end(name);
+
+
+ }
+ // Serialize element and treat all non elements as fragments
+
+};
+
+/***
+ * 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