ADS3.13 DOM2核心和DOM2 HTML——将HTML代码转换为DOM代码

前端技术 everyinch 4987℃ 0评论

创建一个简单的工具,通过它把一段HTML代码转换为DOM代码。

效果演示

DOM生成工具的HTML文件

在HTML文件中最主要的是具有两个<textarea>和一个<button>元素:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>DOM Generation</title>
    <title>AdvancED DOM Scripting Sample Document</title>
    <link rel="stylesheet" type="text/css" href="styles/source.css" />
    <link rel="stylesheet" type="text/css" href="styles/style.css" />

    <!-- 自定义的ADS库-->
    <script type="text/javascript" src="libs/ADS-final-verbose.js"></script>
    <!-- 日志对象 -->
    <script type="text/javascript" src="libs/myLogger.js"></script>
    <!-- 生成DOM的函数 -->
    <script type="text/javascript" src="generateDOM.js"></script>
    <!-- 事件处理 -->
    <script type="text/javascript" src="load.js"></script>
</head>
<body>
<h1>DOM Generation</h1>
<div id="content">
<form id="generator" action="">
    <fieldset>
        <h2>Source</h2>
        <label for="source">Enter an HTML document fragment</label>
        <textarea id="source" cols="30" rows="15">
<li id="$commentId" class="$commentClass" style="background-color:white">
    <a href="$countHref" class="counter" title="$countTitle" onclick="window.open(this.href); return false;">$countContent</a>
    <span class="commentauthor"><a href="$authorHref" rel="external nofollow">$authorContent</a></span>
    <small class="comment-meta">
        <a href="$metaHref" title="$metaTitle">$metaContent</a>
    </small>
    <div class="comment-content">
        $commentContent
    </div>
</li>
        </textarea>
        <input id="generate" type="button" value="&#8595; generate &#8595;" />
        <h2>DOM Code</h2>
        <label for="result">and voila! DOM goodness:</label>
        <textarea id="result" cols="30" rows="15"></textarea>
    </fieldset>
</form>
</div>
</body>
</html>

这个页面完全依赖于JavaScript,其中包含一个generateDOM.js文件和注册事件侦听器的load.js脚本:

// 注册事件侦听器
ADS.addEvent(window, 'load', function() {

    // 注册click事件侦听器
    ADS.addEvent('generate','click', function(W3CEvent) {
        
        // 取得HTML源代码
        var source = ADS.$('source').value;     
        
        // 输出结果
        ADS.$('result').value = generateDOM(source);
                
    });

});

要转换成DOM代码的HTML代码片段

<li id="comment-1" class="comment c1 c-y2007 c-m01 c-d01 c-h05 alt">
    <a href="#comment-1" class="counter" title="Permanent Link to this Comment">1</a>
    <span class="commentauthor">
        <a href="http://wordpress.org/" rel="external nofollow">Mr WordPress</a>
    </span>
    <small class="comment-meta">
        <a href="#comment-1" title="Permanent Link to this ?Comment">Aug 22nd, 2006 at 5:09 pm</a>
    </small>
    <div class="comment-content">
        <p>Hi, this is a comment.<br>To delete a comment, just log ?in, and view the posts' comments, there you will have the option ?to edit or delete them.</p>
    </div>
</li>

这个工具的目标就是取得类似下面的HTML代码片段:

<a href="http://wordpress.org/" rel="external nofollow">Mr WordPress</a>

并将它转换为等价的DOM代码:

var a = document.createElement('A');
a.setAttribute('href','http://wordpress.org');
a.setAttribute('rel','external nofollow');

上面代码的链接只是指向了http://wordpress.org,为了增加通用性,将HTML代码片段中需要变化的地方修改成变量,并使用美元符合($)作为前缀:

<li id="$commentId" class="$commentClass" style="background-color:white">
     <a href="$countHref" class="counter" title="$countTitle"?onclick="window.open(this.href); return false;">$countContent</a>
    <span class="commentauthor"><a href="$authorHref" rel="external?nofollow">$authorContent</a></span>
    <small class="comment-meta">
        <a href="$metaHref" title="$metaTitle">$metaContent</a>
    </small>
    <div class="comment-content">
        $commentContent
    </div>
</li>

扩充ADS库

在构建generateDOM.js之前,需要向ADS.js库添加几个方法。首先利用字符串的prototype属性添加两个新方法:
生成重复的字符串:

/**
* 利用核心对象的 prototype 属性
* 重复1个字符串
*/
if (!String.repeat) {
    String.prototype.repeat = function(l){
        return new Array(l+1).join(this);
    }
}

上面的函数生成一个参数长度加1的空白字符串,然后使用字符串作为分隔符,例如:

var example = 'a'.repeat(5);
//example is now: aaaaa
[/code]
清除字符串两端的空白字符:
/** 
* 移除字符串头部和结尾的空白字符
*/
if (!String.trim) {
    String.prototype.trim = function() {
        return this.replace(/^\s+|\s+$/g,'');
    }
}

下面是添加到ADS命名空间的camelize()方法:

/**
 *  将word-word类型的字符串转换成wordWord类型的字符串
 */
function camelize(s) {
    return s.replace(/-(\w)/g, function (strMatch, p1){
        return p1.toUpperCase();
    });
}
window['ADS']['camelize'] = camelize;

generateDOM的框架

框架创建了一个新的命名空间,然后包含一些辅助方法和属性,最后是为window对象赋值:

/* generateDOM对象的新命名空间 */
(function(){

function encode(str) { }

function checkForVariable(v) { }

function processAttribute(tabCount,refParent) { }

function processNode(tabCount,refParent) { }

var domCode = '';
var nodeNameCounters = [];
var requiredVariables = '';
var newVariables = '';

function generate(strHTML,strRoot) { }

window['generateDOM'] = generate;

})();

encode()方法

generateDOM中的第一个方法是encode()方法。对于生成DOM的方法而言,encode()方法用于保证字符串是一个安全的JavaScript字符串。只需要转义反斜杠、单引号和换行符即可:

function encode(str) {
    if (!str) return '';
	// 转义反斜杠
    str = str.replace(/\\/g,'\\\\');
	// 转义单引号
    str = str.replace(/';/g, "\\'");
	// 转义换行符
    str = str.replace(/\s+^/mg, "\\n");
    return str;
}

checkForVariable()方法

generateDOM中的第二个方法是checkForVariable()方法。该方法检查字符串中是否包含一个美元符号,如果是,则返回一个带引号的字符串或者变量名。同时,将变量添加到requiredVariable中,以便在输出结果:

function checkForVariable(v) {
    if(v.indexOf('$') == -1) {
        v = '\'' + v + '\'';
    } else {
        // 取得该字符串从$到结尾处的子字符串
        v = v.substring(v.indexOf('$')+1)
        requiredVariables += 'var ' + v + ';\n';
    }
    return v;
}

generate()方法

generate()方法是核心方法。它遍历DOM树并检测其中所有的节点,然后按照节点的类型生成DOM代码:

function generate(strHTML,strRoot) {

    //将HTML代码添加到页面主体中
    var domRoot = document.createElement('DIV');
    domRoot.innerHTML = strHTML;
    
    // 重置变量
    domCode = '';
    nodeNameCounters = [];
    requiredVariables = '';
    newVariables = '';
    
    // 使用processNode()方法处理domRoot中的所有子节点
    var node = domRoot.firstChild;
    while(node) {
        ADS.walkTheDOMRecursive(processNode,node,0,strRoot);
        node = node.nextSibling;
    }

    // 输出结果
    domCode =
        '/* requiredVariables in this code\n' + requiredVariables + '*/\n\n' 
        + domCode + '\n\n'
        + '/* new objects in this code\n' + newVariables + '*/\n\n';
    
    return domCode;
}

processNode()方法

当遍历domRoot中的子节点时,使用processNode()方法分析树中的每个节点,确定节点的类型、值和属性,以便重新创建适当的DOM代码:

function processNode(tabCount,refParent) {
    // 根据树的深度重复制表符
	// 以便对每一行进行适当的缩进
    var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');
    
    // 确定节点类型
	// 处理元素节点和文本节点 
    switch(this.nodeType) {
        case ADS.node.ELEMENT_NODE:
            // 递增计数器
            // 结合标签和计数器来表示变量,例如: a1,a2,a3
            if(nodeNameCounters[this.nodeName]) {
                ++nodeNameCounters[this.nodeName];
            } else {
                nodeNameCounters[this.nodeName] = 1;
            }
            
            var ref = this.nodeName.toLowerCase() 
                + nodeNameCounters[this.nodeName];
            
            // 创建元素节点
            domCode += tabs 
                + 'var ' 
                + ref 
                + ' = document.createElement(\'' + this.nodeName +'\');\n';
            
            // 将心变量添加到列表中
            newVariables += '' + ref + ';\n';
            
            // 遍历属性
			// 使用processAttribute()方法遍历属性
            if (this.attributes) {
                for(var i=0; i < this.attributes.length; i++) {
                    ADS.walkTheDOMRecursive(
                        processAttribute,
                        this.attributes&#91;i&#93;,
                        tabCount,
                        ref
                    );
                }
            }
            
            break;
            
        case ADS.node.TEXT_NODE:
        
            // 编码文本节点,并去掉空白符
            var value = (this.nodeValue ? encode(this.nodeValue.trim()) : '' );
            if(value) {

                // 递增计数器
                // 使用txt和计数器来表示变量,例如: txt1,txt2,txt3
                if(nodeNameCounters&#91;'txt'&#93;) {
                    ++nodeNameCounters&#91;'txt'&#93;;
                } else {
                    nodeNameCounters&#91;'txt'&#93; = 1;
                }
                var ref = 'txt' + nodeNameCounters&#91;'txt'&#93;;

                // 检查格式。格式要求类似于$var
                value = checkForVariable(value);

                // 创建文本节点
                domCode += tabs 
                    + 'var ' 
                    + ref 
                    + ' = document.createTextNode('+ value +');\n';
                // 将新变量添加到列表中
                newVariables += '' + ref + ';\n';
                
            } else {
                // 如果只有空白符则返回
		// 即这个节点将不会添加到父节点中
                return;
            }
            break;
            
        default:
            // 忽略其它情况
            break;
    }
    
    // 添加到父节点
    if(refParent) {
        domCode += tabs + refParent + '.appendChild('+ ref + ');\n';
    }
    return ref;
}
&#91;/code&#93;
该方法主要做了以下几件事:
1. 基于递归的深度来确定DOM代码缩进的级别,并按照需要重复制表符。这种缩进并不是必要的,但它使生成的代码更清晰,也更容易理解:
&#91;code lang="js"&#93;
var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');
&#91;/code&#93;
2. 按照节点类型来做相应的处理。本方法中需要处理两种类型的节点:ADS.node.ELEMENT_NODE(类型值为1)ADS.node.TEXT_NODE(类型值为3)。主要逻辑如下:
&#91;code lang="js"&#93;
switch(node.nodeType) {
    case ADS.node.ELEMENT_NODE:
        //处理元素节点
        break;
    case ADS.node.TEXT_NODE:
        //处理文本节点
        break;
    default:
        //忽略其它情况
        break;
}
&#91;/code&#93;
所有ELEMENT_NODE节点都可能具有属性。属性也是节点,它无法通过同辈定位的方法进行迭代。属性节点包含在node.attributes数组中,因此必须要单独对它们jinx那个遍历:
&#91;code lang="js"&#93;
if (node.attributes) {
    for(var i=0; i < node.attributes.length; i++) {
        myWalkTheDOM(processAttribute,node.attributes&#91;i&#93;, tabCount, ref);
    }
}
&#91;/code&#93;
3. 在处理完所有的节点之后,唯一要做的就是将其添加到父节点中:
&#91;code lang="js"&#93;
// 添加到父节点
if(refParent) {
    domCode += tabs + refParent + '.appendChild('+ ref + ');\n';
}
&#91;/code&#93;
在创建processAttribute()方法之前,使用示例的HTML代码片段试验这个工具,将会得到如下的DOM代码:
&#91;code lang="js"&#93;
/* requiredVariables in this code
var countContent;
var authorContent;
var metaContent;
var commentContent;
*/
var li1 = document.createElement('li');
document.body.appendChild(li1);
    var a1 = document.createElement('a');
    li1.appendChild(a1);
        var txt1 = document.createTextNode(countContent);
        a1.appendChild(txt1);
    var span1 = document.createElement('span');
    li1.appendChild(span1);
        var a2 = document.createElement('a');
        span1.appendChild(a2);
            var txt2 = document.createTextNode(authorContent);
            a2.appendChild(txt2);
    var small1 = document.createElement('small');
    li1.appendChild(small1);
        var a3 = document.createElement('a');
        small1.appendChild(a3);
            var txt3 = document.createTextNode(metaContent);
            a3.appendChild(txt3);
    var div1 = document.createElement('div');
    li1.appendChild(div1);
        var txt4 = document.createTextNode(commentContent);
        div1.appendChild(txt4);
/* new objects in this code
li1;
a1;
txt1;
span1;
a2;
txt2;
small1;
a3;
txt3;
&#91;/code&#93;

<strong><span style="font-size: large;">processAttribute()方法</span></strong>

上面的输出中包含所有的ELEMENT_NODE和TEXT_NODE节点,但还缺少节点中的属性,而这正式需要processAttribute()方法来解决的问题:

function processAttribute(tabCount,refParent) {
    
    // 忽略文本节点
    if(this.nodeType != ADS.node.ATTRIBUTE_NODE) return; 

    // 取得属性的值
    var attrValue = (this.nodeValue ? encode(this.nodeValue.trim()) : '');
    if(this.nodeName == 'cssText') alert('true');
    // 如果没有值,则返回
    if(!attrValue) return;  

    // 确定缩进的级别
    var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');
    
    // 根据nodeName进行判断
    switch(this.nodeName){
        default:
            if (this.nodeName.substring(0,2) == 'on') {
                // 如果属性名称以'on'开始,说明是一个事件属性
				// 需要创建一个给属性赋值的函数
                domCode += tabs 
                    + refParent 
                    + '.' 
                    + this.nodeName 
                    + '= function(){' + attrValue +'}\n';
            } else{
            
                // 对于其它情况,则使用setAttribute()方法
                domCode += tabs 
                    + refParent 
                    + '.setAttribute(\'' 
                    + this.nodeName 
                    + '\', ' 
                    + checkForVariable(attrValue) 
                    +');\n';
            }
        break;
        case 'class':
            // 将class属性替换为clasName
            domCode += tabs 
                + refParent 
                + '.className = ' 
                + checkForVariable(attrValue) 
                + ';\n';                
            break;
        case 'style':
            // 使用分好(;)和空白符来分割样式
            var style = attrValue.split(/\s*;\s*/);
            
            if(style){
                for(pair in style){
                    
                    if(!style[pair]) continue;
                    
                    // 使用冒号(:)和空白符来分隔样式的属性和属性值
                    var prop = style[pair].split(/\s*:\s*/);
                    if(!prop[1]) continue;
                    
                    // 将css-property格式的CSS属性转换为cssProperty格式
                    prop[0] = ADS.camelize(prop[0]);
                   
                    var propValue = checkForVariable(prop[1]);
                    if (prop[0] == 'float') {
                        // 由于float是保留字
						// 将float值替换成cssFloat
                        domCode += tabs 
                            + refParent 
                            + '.style.cssFloat = ' 
                            + propValue 
                            + ';\n';
                        domCode += tabs 
                            + refParent 
                            + '.style.styleFloat = ' 
                            + propValue 
                            + ';\n';
                    } else {
                        domCode += tabs 
                            + refParent 
                            + '.style.' 
                            + prop[0] 
                            + ' = ' 
                            + propValue + ';\n';
                    }
                }
            }
    break;
    }
}

processAttribute()方法大致遵循了与processNode()方法相同的处理方式。处理过程大致如下:
1. 属性节点可以通过nodeValue属性取得节点的值,但在processNode()方法已经迭代了TEXT_NODE。所以如果要使用nodeValue属性,就必须跳过非ATTRIBUTE_NODE节点。
2. 如果不存在class或style属性,则使用setAttribute()方法创建相应的属性。但要检查嵌入的事件属性的情形。
3. 如果是class属性,就需要将class节点的值赋给节点的className属性。
4. 如果是style属性,则需要对其进行必要的分割。

至此将HTML代码转换为DOM代码的工具制作完毕,从而免去了许多无谓的DOM脚本编程的工作量。

转载请注明:陈童的博客 » ADS3.13 DOM2核心和DOM2 HTML——将HTML代码转换为DOM代码

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(1)个小伙伴在吐槽
  1. advice. I appreciate you taking time to share such valuable information. I had no clue on some of the things you mentioned earlier, thanks!
    Nike Patriots2014-10-08 23:19 回复
'; } if( dopt('d_footcode_b') ) echo dopt('d_footcode'); ?>