/**
* 编辑器模拟的节点类
* @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) {
//这里就针对
//
'![](http://nsclick.baidu.com/u.gif?&asdf=\"sdf&asdfasdfs;asdf)
//这里边的\"做转换,要不用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('<\/' + node.tagName + '>');
}
}
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;
}
}
})();