代码改变世界

Limited Razor,在JavaScript中使用Razor式的模板

2010-08-01 20:46  Nana's Lich  阅读(3154)  评论(7编辑  收藏  举报

最近,微软公布了新的WebMatrix便捷Web开发环境——有些人可能还记得许多年以前也有一个WebMatrix,但其实这次的新WebMatrix除了因出于同样的目的被创造出来而沿用了WebMatrix以外,和多年前的那个WebMatrix完全是两回事。

新的WebMatrix同时还为我们带来了IIS Express、SQL Server CE 4和Razor视图引擎。

Razor视图引擎和WebForms不同,是ASP.NET的另一种形式,Razor的特点完全可以用一个词来概括:简洁!

关于Razor视图引擎,以及IIS Express、SQL Server CE 4、WebMatrix的信息,可以在Scott Guthrie's Blog找到,这里就不多讲了。

 

在最近的一个项目中,我试图找到一个可以让网页很容易在浏览器端模板化、又可以渐进展现、不像XML+XSLT那样需要等待全文件加载之后才能显示的办法。

在这个过程中,我受到了Razor视图引擎的启发——虽然浏览器端的类Razor实现可能不是解决我的问题的最好的办法,但我还是先行动起来了。

依照着Razor引擎的思想,经过4个小时的编写和调整,我实现出了一个类Razor的模板处理器。由于其功能相比于正牌的Razor还比较有限,所以我暂时叫它Limited Razor。

代码如下:

    function lyRazor(s) {
      var m, rx = new RegExp('@@|@', 'g');
      var dw = 'document.write("';
      var r = dw, li = 0, i, l;
      while (m = rx.exec(s)) {
        r += jsEncode(s.substring(li, i = m.index));
        switch (l = m[0].length) {
          case 1:
            var exp, sf, find = s.charAt(i + 1);
            if (find == '(') {
              r += '"+';
              find = '\\)';
              sf = '+"';
            } else if (find == '{') { // 代码块
              r += '");';
              i++; // 从下一个字符开始寻找
              find = '\\}';
              sf = ';' + dw;
            } else {
              find = '[\\s<;]'; // 寻找空白字符或者尖括号
              sf = null;
            }
            li = i + 1; // 跳过@

            var rxSpc = new RegExp(find, 'g');
            rxSpc.lastIndex = li;

            while (m = rxSpc.exec(s)) {
              try {
                exp = s.substring(li, m.index);
                m = m[0];
                if (')' == m)
                  exp += ')'; // 附加右括号
                else if (';' == m)
                  l++;

                new Function(exp + ';'); // 测试语法

                l += exp.length;

                switch (m) {
                  case '<': // 不包括
                    break;
                  case ';':
                  case ')': // 已经附加到exp,但不确定是否有多余的空白字符
                    var m2 = /^\r\n|^\s/.exec(s.substr(l + i, 2)); // 连续的\r\n被当作“一个”空白字符来消除
                    if (m2)
                      l += m2[0].length;
                    break;
                  case '}': // 代码块结束
                    if ('}\r\n' == s.substr(l + i, 3))  // 连续的\r\n被当作“一个”空白字符来消除
                      l += 2;
                    l++;
                    break;
                  default: // 从空字符之后开始处理
                    if ('\r\n' == s.substr(l + i, 2))  // 连续的\r\n被当作“一个”空白字符来消除
                      l++;
                    l++;
                    break;
                }

                break;
              } catch (ex) { }
            }
            if (!m) // 无法匹配为js语法
              throw 'bad syntax!';

            if (sf) // 块或括号
              r += exp + sf;
            else if (';' == m) { // 语句
              r += '");' + exp + ';' + dw;
            } else // 表达式
              r += '"+(' + exp + ')+"';
            
            break;
          case 2:
            r += '@'; // @自转义
            break;
          default:
            break;
        }
        li = i + l;
      }

      return r + jsEncode(s.substr(li)) + '");';
    }

实现这个东西的核心思想就是寻找@,然后根据@之后的内容来寻找特定的边界字符,再进行语法测试。

进行语法测试的时候我用了一个比较懒但很有效的办法:把代码作为body参数直接传给Function构造器。这个办法也可以用在其它的需要对用户输入、或者程序产生的JavaScript代码进行语法检测,而不用担心框架在不正确的时间执行这些代码。

在这个实现中,我用了一个名为jsEncode的自定义函数,用来对那些不能原样写在字符串表达式中的特殊字符进行转义,它的实现原理也挺简单,所以有兴趣的同学请自己想想应该怎么实现。

 

这个Limited Razor处理器现在支持这样的用法:

变量/属性:@location.href
字面量:@1
计算:@1/7
括号:@(1 + 2)
函数:@parseInt("7f", 16)
语句:@document.write('test');
代码块:@{ document.write("Hello, Razor!"); }

目前这个Limited Razor还不支持@if、@while、@for等在@之后直接使用流控制语句的用法,也没有Helper之类的支持。也许以后我会把这些都加上。

 

具体使用的示范:

<script type="text/l-razor" id="tmpl">
  
  • 语句调用:@document.write("Hello, Razor!");
  • 属性调用:@location.href
</script>
<script type="text/javascript">
  var code = lyRazor(document.getElementById("tmpl").text);
  eval(code);
</script>

 

好了,现在有一个简单的思考题:

很多AJAX(RIA)的页面是在页面文档已经加载完毕之后才对页面内容进行改变的,这个时候是不能使用document.write方法来输出HTML的。如果遇到了这样的情况,这个Limited Razor应该怎么改呢?