标签:
最近一段时间在修改自己的个人在线简历. 这个在线简历用到了css3来制作3D的旋转效果, 因此会有兼容性问题, 针对于不支持css3的3D透视的浏览器, 比如 IE, 360等等, 我使用的是另一套css文件兼容. 针对于移动端浏览器, 尽管基本都是webkit内核, 但经测试发现3D效果并不流畅, 因此移动端是识别userAgent切换到另一套非3D页面. 因为没用任何数据库, 那么问题就来了, 移动端和pc端两套页面是共用的一套数据, 我想到的方法有两个: 一是页面加载之后用ajax请求同一个数据文件, 然后js在页面显示的时候填充数据, 但这个方法要增加http请求, 万一碰到需要请求超多数据文件的时候呢? 加载速度自然要受影响. 方法二在html中直接写
,这样不会增加多余的请求, 但是万一里面的数据要修改一下呢, 多个页面你去慢慢找吧...... 因此就想实现自己的一套模板替换工具, 像php那样插入, 然后就能直接打印出来. 正好最近在学nodejs, 它可以操作文件, 对于我们jser来说犹如神器! 于是我就想模拟php插入标签方式实现一个简单的模板替换工具rtt ---- replace templete tool, 通过nodejs来操作html文件, 然后替换输出到目标文件

在html插入<%%>类型的标签, 然后里面可以使用js代码:
<div class="face info">
<%
for(var i=0, m=data.info.length; i<m; i++){
%>
<div class="info-kind">
<h2><%=data.info[i].title%></h2>
<p><%=data.info[i].content%></p>
</div>
<%
}
%>
</div>
替换之后, 生成内容如下:
<div class="face info">
<div class="info-kind">
<h2>工作经历</h2>
<p>###########</p>
</div>
<div class="info-kind">
<h2>兴趣爱好</h2>
<p>###########</p>
</div>
<div class="info-kind">
<h2>英语水平</h2>
<p>###########</p>
</div>
......
</div>
实现模板替换工具
如何实现一个这种工具? 其实原理非常简单, 就是用运行在node中的js读取文件, 会用到 fs.readFileSync 方法, 然后将得到的字符串用正则进行匹配, 通过<%%>作为定界符, js就可以区分开哪些是直接输出, 哪些是需要经过运算, 最后用fs.writeFileSync方法输出. 因为之前自己实现过一个复杂dom选择器, 所以一下就想到使用正则断开特殊字符, 然后放到一个数组中, 进行遍历.
新建一个rtt.js的文件:
var fs = require(‘fs‘); //获取fs模块
var srcHtml = fs.readFileSync(‘a.src.html‘); //读取a.src.html文件数据
srcHtml = srcHtml.toString(‘utf-8‘); //将读取到的二进制数据转成字符串
srcHtml = srcHtml.replace(/[\r\n\t]+/g, ‘‘); //去掉换行和tab
//本可以和上面合并, 方便观看分开写, 去掉html和js注释
srcHtml = srcHtml.replace(/<!--.*?-->|\/\*.*?\*\//g, ‘‘);
//将srcHtml用<%%>断开
//如果是 abc<% alert(1); %>def 会输出[‘abc‘, ‘<% alert(1); %>‘, ‘def‘]
var arr = srcHtml.match(/<%={0,2}.*?%>|.*?(?=<%)|.*?(?=$)/g)
因此得到的arr是类似于 [‘<div>‘, ‘<% data.name %>‘, ‘</div>‘, ‘<% data.age %>‘, ‘<ul>‘ ........ ]
最后需要通过eval才能读取<%%>中的js语句, 所以需要设置一个用于eval的s变量, s变量中有res变量最终会被eval出来,在遍历数组的过程中不断迭代, 用于输出
var s = "res=‘‘;";
for(var i=0, m=arr.length; i<m; i++){
/*遍历数组的时候, 进行判断, 如果是<%开头的, 则是js语句, 如果不是则直接输出*/
if(arr[i].indexOf(‘<%‘) === 0 ){
if(arr[i].charAt(2) === ‘=‘ && arr[i].charAt(3) !== ‘=‘ ){
//如果是js变量的时候
s += ‘res+=‘ + arr[i] + ‘;‘ ;
}
else {
//js语句的时候
s += arr[i];
}
}
else {
s += ‘res+="‘ + arr[i] + ‘";‘ ;
}
}
eval(s); //生成res变量, 并执行
fs.writeFileSync(a.html, res); //将res变量输出到目标文件
现在, 一个简单的模板替换工具就完成了, 每次的arr[i]还需要检验, 转义这里就不赘述了, 声明一个函数调用即可.
添加include函数用来引入其他文件内容
上面的模板替换只允许替换变量, 通过数组生成多个指定标签, 但我们经常需要引入一些外部的模块文件, php中的require和include非常好用, 但现在是nodejs.
比如该说a.src.html中引入了m.tpl.html, m.tpl.html又引入了x.tpl.js和y.tpl.html, y.tpl.html中又引入了z.tpl.json.
原理也没那么难,首先将上面的代码封装成一个函数.
/*
src --- 源文件路径
dest --- 生成文件路径
dataPath --- json数据路径
dataName --- src文件中的对象名字
delimiter --- 定界符
*/
function rtt(src, dest, dataPath, dataName, delimiter){
//设置对象名字默认为dataPath的文件名
dataName = dataName || dataPath.split(‘/‘)[dataPath.split(‘/‘).length-1].replace(/\.[a-z0-9]+$/gi, ‘‘);
//设置定界符默认是<%%>, 然后在中间分隔开保存成[‘<%‘, ‘%>‘]
delimiter = delimiter || ‘<%%>‘;
delimiter = (function(){
var n = Math.floor(delimiter.length/2);
return [delimiter.substr(0, n), delimiter.substr(-n)];
})();
//调用replace函数, 最终输出得到替换之后的字符串, 生成到dest文件
//这里大部分替换逻辑已经封装到了replace中, replace只读取源文件生成目标最终字符串
//因为replace中会使用的rtt中的各种变量, 因此使用闭包形式书写
fs.writeFileSync(dest, replace(src));
console.log(‘compile finished!‘); //打印‘compile finished‘
function replace(src){
//传入一个src路径, 返回替换之后的字符串
......
}
}
replace大部分和上上面试一样的, 只是封装成了一个函数, 然后进行了include扩展:
function replace(src){
eval("var "+dataName+" =" + data);
var s = "res=‘‘;",
fs = require("fs"),
location = getLocation(src);
//读取源文件html, 并得到字符串, 然后将字符串中的\r\n\t
var srcHtml = fs.readFileSync(src).toString(‘utf-8‘).replace(/[\r\n\t]+/g, ‘‘);
//去掉html注释, js注释/**/类型
srcHtml = srcHtml.replace(/<!--.*?-->|\/\*.*?\*\//g, ‘‘);
//分割html文件中的字符串
var reg = new RegExp(delimiter[0] + "={0,2}.*?"+delimiter[1]+"|.*?(?="+delimiter[0]+")|.*?(?=$)", "g");
var arr = srcHtml.match(reg);
for(var i=0, m=arr.length; i<m; i++){
if(arr[i].indexOf(‘<%‘) === 0){
if(arr[i].charAt(2)===‘=‘ && arr[i].charAt(3) !== ‘=‘ ){
//js变量的时候
s += ‘res+=‘ + trim(arr[i]) + ‘;‘;
}
else if(arr[i].charAt(2)===‘=‘ && arr[i].charAt(3) === ‘=‘){
//js变量的时候, 如果是两个等号, 则将其中<替换成< 将> 替换成>
s += ‘res+=‘ + esHtml(trim(arr[i])) + ‘;‘;
}
else if(/^<%\s*include\(.*\)\s*;?\s*%>$/.test(arr[i])){
//如果识别到是include(‘abc.html‘)类型格式, 则递归调用replace函数, 传入abc.html
var newPath = genPath(src, arr[i].replace(/.+[‘"](.+)[‘"].+/g, function(a, b){ return b; }));
s += ‘res+="‘ + es(replace( newPath )) + ‘";‘;
}
else{
//js语句的时候
s += trim(arr[i]);
}
}
else{
s += ‘res+="‘ + es(arr[i]) + ‘";‘;
}
}
eval(s);
//返回eval得到的变量res
return res;
}
上面的replace函数中用到了递归来实现多层引用, 但是有个问题, 如果 a.src.html 中引用了 tpl/b.html, tpl/b.html 又引用了 ./css/c.css, 那么路径就会出现问题, 因为被引用的文件中, 引用其他文件的路径是相对于他自己, 而node读取文件的位置是src文件所在的位置, 如图所示:

当node执行到include(‘css/c.html‘)的时候, 会根据src文件的位置, 读取/css/c.css文件, 因此会出错, 所以需要使用函数来生成新的相对于src的路径, 才能得到/tpl/css/c.css文件
//传入一个文件路径,返回其所在目录
function getLocation(src){
if(src.charAt(src.length-1) === ‘/‘) return src;
else if(src.indexOf(‘/‘) === 0) return ‘./‘;
else if(src.indexOf(‘/‘) < 0) return ‘./‘;
else{
var temp = src.split(‘/‘);
temp.pop();
return temp.join(‘/‘)+‘/‘;
}
}
然后还需要生成新路径的函数:
//传入当前文件路径, 和include的文件路径, 生成相对于index的相对路径
function genPath(cur, dest){
if(dest.charAt(0) !== ‘.‘ && dest.charAt(0) !== ‘/‘){
return getLocation(cur) + dest.replace(/^\//, ‘‘);
}
else if(dest.indexOf(‘./‘) === 0){
return getLocation(cur) + dest.replace(/^\.\//, ‘‘);
}
else{
//如果目标路径是 ../../../a/b/c.html类型
var cur = getLocation(cur).match(/[^\/]+\//g);
var m = cur.length;
var n = dest.match(/\.\.\//g).length;
if(m>n){
return cur.slice(0, m-n).join(‘‘) + dest.replace(/\.\.\//g, ‘‘);
}
else if(m === n) return ‘./‘ + dest.replace(/\.\.\//g, ‘‘);
else if(m < n){
for(var res=‘‘, i=0; i<n-m; i++) res+=‘../‘;
return res + dest.replace(/\.\.\//g, ‘‘);
}
}
}
至此, 一个可以引用其他文件的模板已经完成了, 可以通过npm install rtt --save-dev 进行安装, 然后新建一个replace.js文件, 输入:
var rtt = require(‘rtt‘); rtt(‘a.src.html‘ , ‘a.html‘, ‘data.json‘);
就可以讲a.src.html源文件中<%%>包含的使用的data命名空间输出到a.html, 允许多层引入文件, 注意: <%include(‘‘);%>
由于本人学习nodejs时间不长, 有任何问题请各位指出, 多谢啦
编写一个简单的js模板替换工具 rtt----replace templete tool
标签:
原文地址:http://www.cnblogs.com/flfwzgl/p/4573887.html