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

前端技术 everyinch 5317℃ 0评论

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

效果演示

DOM生成工具的HTML文件

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

1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
4<head>
5<title>DOM Generation</title>
6    <title>AdvancED DOM Scripting Sample Document</title>
7    <link rel="stylesheet" type="text/css" href="styles/source.css" />
8    <link rel="stylesheet" type="text/css" href="styles/style.css" />
9 
10    <!-- 自定义的ADS库-->
11    <script type="text/javascript" src="libs/ADS-final-verbose.js"></script>
12    <!-- 日志对象 -->
13    <script type="text/javascript" src="libs/myLogger.js"></script>
14    <!-- 生成DOM的函数 -->
15    <script type="text/javascript" src="generateDOM.js"></script>
16    <!-- 事件处理 -->
17    <script type="text/javascript" src="load.js"></script>
18</head>
19<body>
20<h1>DOM Generation</h1>
21<div id="content">
22<form id="generator" action="">
23    <fieldset>
24        <h2>Source</h2>
25        <label for="source">Enter an HTML document fragment</label>
26        <textarea id="source" cols="30" rows="15">
27<li id="$commentId" class="$commentClass" style="background-color:white">
28    <a href="$countHref" class="counter" title="$countTitle" onclick="window.open(this.href); return false;">$countContent</a>
29    <span class="commentauthor"><a href="$authorHref" rel="external nofollow">$authorContent</a></span>
30    <small class="comment-meta">
31        <a href="$metaHref" title="$metaTitle">$metaContent</a>
32    </small>
33    <div class="comment-content">
34        $commentContent
35    </div>
36</li>
37        </textarea>
38        <input id="generate" type="button" value="&#8595; generate &#8595;" />
39        <h2>DOM Code</h2>
40        <label for="result">and voila! DOM goodness:</label>
41        <textarea id="result" cols="30" rows="15"></textarea>
42    </fieldset>
43</form>
44</div>
45</body>
46</html>

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

1// 注册事件侦听器
2ADS.addEvent(window, 'load', function() {
3 
4    // 注册click事件侦听器
5    ADS.addEvent('generate','click', function(W3CEvent) {
6         
7        // 取得HTML源代码
8        var source = ADS.$('source').value;    
9         
10        // 输出结果
11        ADS.$('result').value = generateDOM(source);
12                 
13    });
14 
15});

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

1<li id="comment-1" class="comment c1 c-y2007 c-m01 c-d01 c-h05 alt">
2    <a href="#comment-1" class="counter" title="Permanent Link to this Comment">1</a>
3    <span class="commentauthor">
4        <a href="http://wordpress.org/" rel="external nofollow">Mr WordPress</a>
5    </span>
6    <small class="comment-meta">
7        <a href="#comment-1" title="Permanent Link to this ?Comment">Aug 22nd, 2006 at 5:09 pm</a>
8    </small>
9    <div class="comment-content">
10        <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>
11    </div>
12</li>

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

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

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

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

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

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

扩充ADS库

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

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

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

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

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

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

generateDOM的框架

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

1/* generateDOM对象的新命名空间 */
2(function(){
3 
4function encode(str) { }
5 
6function checkForVariable(v) { }
7 
8function processAttribute(tabCount,refParent) { }
9 
10function processNode(tabCount,refParent) { }
11 
12var domCode = '';
13var nodeNameCounters = [];
14var requiredVariables = '';
15var newVariables = '';
16 
17function generate(strHTML,strRoot) { }
18 
19window['generateDOM'] = generate;
20 
21})();

encode()方法

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

1function encode(str) {
2    if (!str) return '';
3    // 转义反斜杠
4    str = str.replace(/\\/g,'\\\\');
5    // 转义单引号
6    str = str.replace(/';/g, "\\'");
7    // 转义换行符
8    str = str.replace(/\s+^/mg, "\\n");
9    return str;
10}

checkForVariable()方法

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

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

generate()方法

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

1function generate(strHTML,strRoot) {
2 
3    //将HTML代码添加到页面主体中
4    var domRoot = document.createElement('DIV');
5    domRoot.innerHTML = strHTML;
6     
7    // 重置变量
8    domCode = '';
9    nodeNameCounters = [];
10    requiredVariables = '';
11    newVariables = '';
12     
13    // 使用processNode()方法处理domRoot中的所有子节点
14    var node = domRoot.firstChild;
15    while(node) {
16        ADS.walkTheDOMRecursive(processNode,node,0,strRoot);
17        node = node.nextSibling;
18    }
19 
20    // 输出结果
21    domCode =
22        '/* requiredVariables in this code\n' + requiredVariables + '*/\n\n'
23        + domCode + '\n\n'
24        + '/* new objects in this code\n' + newVariables + '*/\n\n';
25     
26    return domCode;
27}

processNode()方法

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

1function processNode(tabCount,refParent) {
2    // 根据树的深度重复制表符
3    // 以便对每一行进行适当的缩进
4    var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');
5     
6    // 确定节点类型
7    // 处理元素节点和文本节点
8    switch(this.nodeType) {
9        case ADS.node.ELEMENT_NODE:
10            // 递增计数器
11            // 结合标签和计数器来表示变量,例如: a1,a2,a3
12            if(nodeNameCounters[this.nodeName]) {
13                ++nodeNameCounters[this.nodeName];
14            } else {
15                nodeNameCounters[this.nodeName] = 1;
16            }
17             
18            var ref = this.nodeName.toLowerCase()
19                + nodeNameCounters[this.nodeName];
20             
21            // 创建元素节点
22            domCode += tabs
23                + 'var '
24                + ref
25                + ' = document.createElement(\'' + this.nodeName +'\');\n';
26             
27            // 将心变量添加到列表中
28            newVariables += '' + ref + ';\n';
29             
30            // 遍历属性
31            // 使用processAttribute()方法遍历属性
32            if (this.attributes) {
33                for(var i=0; i < this.attributes.length; i++) {
34                    ADS.walkTheDOMRecursive(
35                        processAttribute,
36                        this.attributes&#91;i&#93;,
37                        tabCount,
38                        ref
39                    );
40                }
41            }
42             
43            break;
44             
45        case ADS.node.TEXT_NODE:
46         
47            // 编码文本节点,并去掉空白符
48            var value = (this.nodeValue ? encode(this.nodeValue.trim()) : '' );
49            if(value) {
50 
51                // 递增计数器
52                // 使用txt和计数器来表示变量,例如: txt1,txt2,txt3
53                if(nodeNameCounters&#91;'txt'&#93;) {
54                    ++nodeNameCounters&#91;'txt'&#93;;
55                } else {
56                    nodeNameCounters&#91;'txt'&#93; = 1;
57                }
58                var ref = 'txt' + nodeNameCounters&#91;'txt'&#93;;
59 
60                // 检查格式。格式要求类似于$var
61                value = checkForVariable(value);
62 
63                // 创建文本节点
64                domCode += tabs
65                    + 'var '
66                    + ref
67                    + ' = document.createTextNode('+ value +');\n';
68                // 将新变量添加到列表中
69                newVariables += '' + ref + ';\n';
70                 
71            } else {
72                // 如果只有空白符则返回
73        // 即这个节点将不会添加到父节点中
74                return;
75            }
76            break;
77             
78        default:
79            // 忽略其它情况
80            break;
81    }
82     
83    // 添加到父节点
84    if(refParent) {
85        domCode += tabs + refParent + '.appendChild('+ ref + ');\n';
86    }
87    return ref;
88}
89&#91;/code&#93;
90该方法主要做了以下几件事:
911. 基于递归的深度来确定DOM代码缩进的级别,并按照需要重复制表符。这种缩进并不是必要的,但它使生成的代码更清晰,也更容易理解:
92&#91;code lang="js"&#93;
93var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');
94&#91;/code&#93;
952. 按照节点类型来做相应的处理。本方法中需要处理两种类型的节点:ADS.node.ELEMENT_NODE(类型值为1)ADS.node.TEXT_NODE(类型值为3)。主要逻辑如下:
96&#91;code lang="js"&#93;
97switch(node.nodeType) {
98    case ADS.node.ELEMENT_NODE:
99        //处理元素节点
100        break;
101    case ADS.node.TEXT_NODE:
102        //处理文本节点
103        break;
104    default:
105        //忽略其它情况
106        break;
107}
108&#91;/code&#93;
109所有ELEMENT_NODE节点都可能具有属性。属性也是节点,它无法通过同辈定位的方法进行迭代。属性节点包含在node.attributes数组中,因此必须要单独对它们jinx那个遍历:
110&#91;code lang="js"&#93;
111if (node.attributes) {
112    for(var i=0; i < node.attributes.length; i++) {
113        myWalkTheDOM(processAttribute,node.attributes&#91;i&#93;, tabCount, ref);
114    }
115}
116&#91;/code&#93;
1173. 在处理完所有的节点之后,唯一要做的就是将其添加到父节点中:
118&#91;code lang="js"&#93;
119// 添加到父节点
120if(refParent) {
121    domCode += tabs + refParent + '.appendChild('+ ref + ');\n';
122}
123&#91;/code&#93;
124在创建processAttribute()方法之前,使用示例的HTML代码片段试验这个工具,将会得到如下的DOM代码:
125&#91;code lang="js"&#93;
126/* requiredVariables in this code
127var countContent;
128var authorContent;
129var metaContent;
130var commentContent;
131*/
132var li1 = document.createElement('li');
133document.body.appendChild(li1);
134    var a1 = document.createElement('a');
135    li1.appendChild(a1);
136        var txt1 = document.createTextNode(countContent);
137        a1.appendChild(txt1);
138    var span1 = document.createElement('span');
139    li1.appendChild(span1);
140        var a2 = document.createElement('a');
141        span1.appendChild(a2);
142            var txt2 = document.createTextNode(authorContent);
143            a2.appendChild(txt2);
144    var small1 = document.createElement('small');
145    li1.appendChild(small1);
146        var a3 = document.createElement('a');
147        small1.appendChild(a3);
148            var txt3 = document.createTextNode(metaContent);
149            a3.appendChild(txt3);
150    var div1 = document.createElement('div');
151    li1.appendChild(div1);
152        var txt4 = document.createTextNode(commentContent);
153        div1.appendChild(txt4);
154/* new objects in this code
155li1;
156a1;
157txt1;
158span1;
159a2;
160txt2;
161small1;
162a3;
163txt3;
164&#91;/code&#93;
165 
166<strong><span style="font-size: large;">processAttribute()方法</span></strong>
167 
168上面的输出中包含所有的ELEMENT_NODE和TEXT_NODE节点,但还缺少节点中的属性,而这正式需要processAttribute()方法来解决的问题:
169 
170function processAttribute(tabCount,refParent) {
171     
172    // 忽略文本节点
173    if(this.nodeType != ADS.node.ATTRIBUTE_NODE) return;
174 
175    // 取得属性的值
176    var attrValue = (this.nodeValue ? encode(this.nodeValue.trim()) : '');
177    if(this.nodeName == 'cssText') alert('true');
178    // 如果没有值,则返回
179    if(!attrValue) return; 
180 
181    // 确定缩进的级别
182    var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');
183     
184    // 根据nodeName进行判断
185    switch(this.nodeName){
186        default:
187            if (this.nodeName.substring(0,2) == 'on') {
188                // 如果属性名称以'on'开始,说明是一个事件属性
189                // 需要创建一个给属性赋值的函数
190                domCode += tabs
191                    + refParent
192                    + '.'
193                    + this.nodeName
194                    + '= function(){' + attrValue +'}\n';
195            } else{
196             
197                // 对于其它情况,则使用setAttribute()方法
198                domCode += tabs
199                    + refParent
200                    + '.setAttribute(\''
201                    + this.nodeName
202                    + '\', '
203                    + checkForVariable(attrValue)
204                    +');\n';
205            }
206        break;
207        case 'class':
208            // 将class属性替换为clasName
209            domCode += tabs
210                + refParent
211                + '.className = '
212                + checkForVariable(attrValue)
213                + ';\n';               
214            break;
215        case 'style':
216            // 使用分好(;)和空白符来分割样式
217            var style = attrValue.split(/\s*;\s*/);
218             
219            if(style){
220                for(pair in style){
221                     
222                    if(!style[pair]) continue;
223                     
224                    // 使用冒号(:)和空白符来分隔样式的属性和属性值
225                    var prop = style[pair].split(/\s*:\s*/);
226                    if(!prop[1]) continue;
227                     
228                    // 将css-property格式的CSS属性转换为cssProperty格式
229                    prop[0] = ADS.camelize(prop[0]);
230                    
231                    var propValue = checkForVariable(prop[1]);
232                    if (prop[0] == 'float') {
233                        // 由于float是保留字
234                        // 将float值替换成cssFloat
235                        domCode += tabs
236                            + refParent
237                            + '.style.cssFloat = '
238                            + propValue
239                            + ';\n';
240                        domCode += tabs
241                            + refParent
242                            + '.style.styleFloat = '
243                            + propValue
244                            + ';\n';
245                    } else {
246                        domCode += tabs
247                            + refParent
248                            + '.style.'
249                            + prop[0]
250                            + ' = '
251                            + propValue + ';\n';
252                    }
253                }
254            }
255    break;
256    }
257}

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'); ?>