/** * 编辑器模拟的节点类 * @file * @module UE * @class uNode * @since 1.2.6.1 */ /** * UEditor公用空间,UEditor所有的功能都挂载在该空间下 * @unfile * @module UE */ (function() { /** * 编辑器模拟的节点类 * @unfile * @module UE * @class uNode */ /** * 通过一个键值对,创建一个uNode对象 * @constructor * @param { Object } attr 传入要创建的uNode的初始属性 * @example * ```javascript * var node = new uNode({ * type:'element', * tagName:'span', * attrs:{style:'font-size:14px;'} * }) * ``` */ var uNode = (UE.uNode = function(obj) { this.type = obj.type; this.data = obj.data; this.tagName = obj.tagName; this.parentNode = obj.parentNode; this.attrs = obj.attrs || {}; this.children = obj.children; }); var notTransAttrs = { href: 1, src: 1, _src: 1, _href: 1, cdata_data: 1 }; var notTransTagName = { style: 1, script: 1 }; var indentChar = " ", breakChar = "\n"; function insertLine(arr, current, begin) { arr.push(breakChar); return current + (begin ? 1 : -1); } function insertIndent(arr, current) { //插入缩进 for (var i = 0; i < current; i++) { arr.push(indentChar); } } //创建uNode的静态方法 //支持标签和html uNode.createElement = function(html) { if (/[<>]/.test(html)) { return UE.htmlparser(html).children[0]; } else { return new uNode({ type: "element", children: [], tagName: html }); } }; uNode.createText = function(data, noTrans) { return new UE.uNode({ type: "text", data: noTrans ? data : utils.unhtml(data || "") }); }; function nodeToHtml(node, arr, formatter, current) { switch (node.type) { case "root": for (var i = 0, ci; (ci = node.children[i++]); ) { //插入新行 if ( formatter && ci.type == "element" && !dtd.$inlineWithA[ci.tagName] && i > 1 ) { insertLine(arr, current, true); insertIndent(arr, current); } nodeToHtml(ci, arr, formatter, current); } break; case "text": isText(node, arr); break; case "element": isElement(node, arr, formatter, current); break; case "comment": isComment(node, arr, formatter); } return arr; } function isText(node, arr) { if (node.parentNode.tagName == "pre") { //源码模式下输入html标签,不能做转换处理,直接输出 arr.push(node.data); } else { arr.push( notTransTagName[node.parentNode.tagName] ? utils.html(node.data) : node.data.replace(/[ ]{2}/g, "  ") ); } } function isElement(node, arr, formatter, current) { var attrhtml = ""; if (node.attrs) { attrhtml = []; var attrs = node.attrs; for (var a in attrs) { //这里就针对 //

'

//这里边的\"做转换,要不用innerHTML直接被截断了,属性src //有可能做的不够 attrhtml.push( a + (attrs[a] !== undefined ? '="' + (notTransAttrs[a] ? utils.html(attrs[a]).replace(/["]/g, function(a) { return """; }) : utils.unhtml(attrs[a])) + '"' : "") ); } attrhtml = attrhtml.join(" "); } arr.push( "<" + node.tagName + (attrhtml ? " " + attrhtml : "") + (dtd.$empty[node.tagName] ? "/" : "") + ">" ); //插入新行 if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != "pre") { if (node.children && node.children.length) { current = insertLine(arr, current, true); insertIndent(arr, current); } } if (node.children && node.children.length) { for (var i = 0, ci; (ci = node.children[i++]); ) { if ( formatter && ci.type == "element" && !dtd.$inlineWithA[ci.tagName] && i > 1 ) { insertLine(arr, current); insertIndent(arr, current); } nodeToHtml(ci, arr, formatter, current); } } if (!dtd.$empty[node.tagName]) { if ( formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != "pre" ) { if (node.children && node.children.length) { current = insertLine(arr, current); insertIndent(arr, current); } } arr.push(""); } } function isComment(node, arr) { arr.push(""); } function getNodeById(root, id) { var node; if (root.type == "element" && root.getAttr("id") == id) { return root; } if (root.children && root.children.length) { for (var i = 0, ci; (ci = root.children[i++]); ) { if ((node = getNodeById(ci, id))) { return node; } } } } function getNodesByTagName(node, tagName, arr) { if (node.type == "element" && node.tagName == tagName) { arr.push(node); } if (node.children && node.children.length) { for (var i = 0, ci; (ci = node.children[i++]); ) { getNodesByTagName(ci, tagName, arr); } } } function nodeTraversal(root, fn) { if (root.children && root.children.length) { for (var i = 0, ci; (ci = root.children[i]); ) { nodeTraversal(ci, fn); //ci被替换的情况,这里就不再走 fn了 if (ci.parentNode) { if (ci.children && ci.children.length) { fn(ci); } if (ci.parentNode) i++; } } } else { fn(root); } } uNode.prototype = { /** * 当前节点对象,转换成html文本 * @method toHtml * @return { String } 返回转换后的html字符串 * @example * ```javascript * node.toHtml(); * ``` */ /** * 当前节点对象,转换成html文本 * @method toHtml * @param { Boolean } formatter 是否格式化返回值 * @return { String } 返回转换后的html字符串 * @example * ```javascript * node.toHtml( true ); * ``` */ toHtml: function(formatter) { var arr = []; nodeToHtml(this, arr, formatter, 0); return arr.join(""); }, /** * 获取节点的html内容 * @method innerHTML * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @return { String } 返回节点的html内容 * @example * ```javascript * var htmlstr = node.innerHTML(); * ``` */ /** * 设置节点的html内容 * @method innerHTML * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @param { String } htmlstr 传入要设置的html内容 * @return { UE.uNode } 返回节点本身 * @example * ```javascript * node.innerHTML('text'); * ``` */ innerHTML: function(htmlstr) { if (this.type != "element" || dtd.$empty[this.tagName]) { return this; } if (utils.isString(htmlstr)) { if (this.children) { for (var i = 0, ci; (ci = this.children[i++]); ) { ci.parentNode = null; } } this.children = []; var tmpRoot = UE.htmlparser(htmlstr); for (var i = 0, ci; (ci = tmpRoot.children[i++]); ) { this.children.push(ci); ci.parentNode = this; } return this; } else { var tmpRoot = new UE.uNode({ type: "root", children: this.children }); return tmpRoot.toHtml(); } }, /** * 获取节点的纯文本内容 * @method innerText * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @return { String } 返回节点的存文本内容 * @example * ```javascript * var textStr = node.innerText(); * ``` */ /** * 设置节点的纯文本内容 * @method innerText * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @param { String } textStr 传入要设置的文本内容 * @return { UE.uNode } 返回节点本身 * @example * ```javascript * node.innerText('text'); * ``` */ innerText: function(textStr, noTrans) { if (this.type != "element" || dtd.$empty[this.tagName]) { return this; } if (textStr) { if (this.children) { for (var i = 0, ci; (ci = this.children[i++]); ) { ci.parentNode = null; } } this.children = []; this.appendChild(uNode.createText(textStr, noTrans)); return this; } else { return this.toHtml().replace(/<[^>]+>/g, ""); } }, /** * 获取当前对象的data属性 * @method getData * @return { Object } 若节点的type值是elemenet,返回空字符串,否则返回节点的data属性 * @example * ```javascript * node.getData(); * ``` */ getData: function() { if (this.type == "element") return ""; return this.data; }, /** * 获取当前节点下的第一个子节点 * @method firstChild * @return { UE.uNode } 返回第一个子节点 * @example * ```javascript * node.firstChild(); //返回第一个子节点 * ``` */ firstChild: function() { // if (this.type != 'element' || dtd.$empty[this.tagName]) { // return this; // } return this.children ? this.children[0] : null; }, /** * 获取当前节点下的最后一个子节点 * @method lastChild * @return { UE.uNode } 返回最后一个子节点 * @example * ```javascript * node.lastChild(); //返回最后一个子节点 * ``` */ lastChild: function() { // if (this.type != 'element' || dtd.$empty[this.tagName] ) { // return this; // } return this.children ? this.children[this.children.length - 1] : null; }, /** * 获取和当前节点有相同父亲节点的前一个节点 * @method previousSibling * @return { UE.uNode } 返回前一个节点 * @example * ```javascript * node.children[2].previousSibling(); //返回子节点node.children[1] * ``` */ previousSibling: function() { var parent = this.parentNode; for (var i = 0, ci; (ci = parent.children[i]); i++) { if (ci === this) { return i == 0 ? null : parent.children[i - 1]; } } }, /** * 获取和当前节点有相同父亲节点的后一个节点 * @method nextSibling * @return { UE.uNode } 返回后一个节点,找不到返回null * @example * ```javascript * node.children[2].nextSibling(); //如果有,返回子节点node.children[3] * ``` */ nextSibling: function() { var parent = this.parentNode; for (var i = 0, ci; (ci = parent.children[i++]); ) { if (ci === this) { return parent.children[i]; } } }, /** * 用新的节点替换当前节点 * @method replaceChild * @param { UE.uNode } target 要替换成该节点参数 * @param { UE.uNode } source 要被替换掉的节点 * @return { UE.uNode } 返回替换之后的节点对象 * @example * ```javascript * node.replaceChild(newNode, childNode); //用newNode替换childNode,childNode是node的子节点 * ``` */ replaceChild: function(target, source) { if (this.children) { if (target.parentNode) { target.parentNode.removeChild(target); } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === source) { this.children.splice(i, 1, target); source.parentNode = null; target.parentNode = this; return target; } } } }, /** * 在节点的子节点列表最后位置插入一个节点 * @method appendChild * @param { UE.uNode } node 要插入的节点 * @return { UE.uNode } 返回刚插入的子节点 * @example * ```javascript * node.appendChild( newNode ); //在node内插入子节点newNode * ``` */ appendChild: function(node) { if ( this.type == "root" || (this.type == "element" && !dtd.$empty[this.tagName]) ) { if (!this.children) { this.children = []; } if (node.parentNode) { node.parentNode.removeChild(node); } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === node) { this.children.splice(i, 1); break; } } this.children.push(node); node.parentNode = this; return node; } }, /** * 在传入节点的前面插入一个节点 * @method insertBefore * @param { UE.uNode } target 要插入的节点 * @param { UE.uNode } source 在该参数节点前面插入 * @return { UE.uNode } 返回刚插入的子节点 * @example * ```javascript * node.parentNode.insertBefore(newNode, node); //在node节点后面插入newNode * ``` */ insertBefore: function(target, source) { if (this.children) { if (target.parentNode) { target.parentNode.removeChild(target); } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === source) { this.children.splice(i, 0, target); target.parentNode = this; return target; } } } }, /** * 在传入节点的后面插入一个节点 * @method insertAfter * @param { UE.uNode } target 要插入的节点 * @param { UE.uNode } source 在该参数节点后面插入 * @return { UE.uNode } 返回刚插入的子节点 * @example * ```javascript * node.parentNode.insertAfter(newNode, node); //在node节点后面插入newNode * ``` */ insertAfter: function(target, source) { if (this.children) { if (target.parentNode) { target.parentNode.removeChild(target); } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === source) { this.children.splice(i + 1, 0, target); target.parentNode = this; return target; } } } }, /** * 从当前节点的子节点列表中,移除节点 * @method removeChild * @param { UE.uNode } node 要移除的节点引用 * @param { Boolean } keepChildren 是否保留移除节点的子节点,若传入true,自动把移除节点的子节点插入到移除的位置 * @return { * } 返回刚移除的子节点 * @example * ```javascript * node.removeChild(childNode,true); //在node的子节点列表中移除child节点,并且吧child的子节点插入到移除的位置 * ``` */ removeChild: function(node, keepChildren) { if (this.children) { for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === node) { this.children.splice(i, 1); ci.parentNode = null; if (keepChildren && ci.children && ci.children.length) { for (var j = 0, cj; (cj = ci.children[j]); j++) { this.children.splice(i + j, 0, cj); cj.parentNode = this; } } return ci; } } } }, /** * 获取当前节点所代表的元素属性,即获取attrs对象下的属性值 * @method getAttr * @param { String } attrName 要获取的属性名称 * @return { * } 返回attrs对象下的属性值 * @example * ```javascript * node.getAttr('title'); * ``` */ getAttr: function(attrName) { return this.attrs && this.attrs[attrName.toLowerCase()]; }, /** * 设置当前节点所代表的元素属性,即设置attrs对象下的属性值 * @method setAttr * @param { String } attrName 要设置的属性名称 * @param { * } attrVal 要设置的属性值,类型视设置的属性而定 * @return { * } 返回attrs对象下的属性值 * @example * ```javascript * node.setAttr('title','标题'); * ``` */ setAttr: function(attrName, attrVal) { if (!attrName) { delete this.attrs; return; } if (!this.attrs) { this.attrs = {}; } if (utils.isObject(attrName)) { for (var a in attrName) { if (!attrName[a]) { delete this.attrs[a]; } else { this.attrs[a.toLowerCase()] = attrName[a]; } } } else { if (!attrVal) { delete this.attrs[attrName]; } else { this.attrs[attrName.toLowerCase()] = attrVal; } } }, /** * 获取当前节点在父节点下的位置索引 * @method getIndex * @return { Number } 返回索引数值,如果没有父节点,返回-1 * @example * ```javascript * node.getIndex(); * ``` */ getIndex: function() { var parent = this.parentNode; for (var i = 0, ci; (ci = parent.children[i]); i++) { if (ci === this) { return i; } } return -1; }, /** * 在当前节点下,根据id查找节点 * @method getNodeById * @param { String } id 要查找的id * @return { UE.uNode } 返回找到的节点 * @example * ```javascript * node.getNodeById('textId'); * ``` */ getNodeById: function(id) { var node; if (this.children && this.children.length) { for (var i = 0, ci; (ci = this.children[i++]); ) { if ((node = getNodeById(ci, id))) { return node; } } } }, /** * 在当前节点下,根据元素名称查找节点列表 * @method getNodesByTagName * @param { String } tagNames 要查找的元素名称 * @return { Array } 返回找到的节点列表 * @example * ```javascript * node.getNodesByTagName('span'); * ``` */ getNodesByTagName: function(tagNames) { tagNames = utils.trim(tagNames).replace(/[ ]{2,}/g, " ").split(" "); var arr = [], me = this; utils.each(tagNames, function(tagName) { if (me.children && me.children.length) { for (var i = 0, ci; (ci = me.children[i++]); ) { getNodesByTagName(ci, tagName, arr); } } }); return arr; }, /** * 根据样式名称,获取节点的样式值 * @method getStyle * @param { String } name 要获取的样式名称 * @return { String } 返回样式值 * @example * ```javascript * node.getStyle('font-size'); * ``` */ getStyle: function(name) { var cssStyle = this.getAttr("style"); if (!cssStyle) { return ""; } var reg = new RegExp("(^|;)\\s*" + name + ":([^;]+)", "i"); var match = cssStyle.match(reg); if (match && match[0]) { return match[2]; } return ""; }, /** * 给节点设置样式 * @method setStyle * @param { String } name 要设置的的样式名称 * @param { String } val 要设置的的样值 * @example * ```javascript * node.setStyle('font-size', '12px'); * ``` */ setStyle: function(name, val) { function exec(name, val) { var reg = new RegExp("(^|;)\\s*" + name + ":([^;]+;?)", "gi"); cssStyle = cssStyle.replace(reg, "$1"); if (val) { cssStyle = name + ":" + utils.unhtml(val) + ";" + cssStyle; } } var cssStyle = this.getAttr("style"); if (!cssStyle) { cssStyle = ""; } if (utils.isObject(name)) { for (var a in name) { exec(a, name[a]); } } else { exec(name, val); } this.setAttr("style", utils.trim(cssStyle)); }, /** * 传入一个函数,递归遍历当前节点下的所有节点 * @method traversal * @param { Function } fn 遍历到节点的时,传入节点作为参数,运行此函数 * @example * ```javascript * traversal(node, function(){ * console.log(node.type); * }); * ``` */ traversal: function(fn) { if (this.children && this.children.length) { nodeTraversal(this, fn); } return this; } }; })();