GithubHelp home page GithubHelp logo

shepherdwind / velocity.js Goto Github PK

View Code? Open in Web Editor NEW
603.0 58.0 145.0 3.27 MB

velocity for js

License: MIT License

Makefile 0.50% JavaScript 83.19% Lex 7.06% Yacc 8.97% TypeScript 0.29%
javascript velocity

velocity.js's Introduction

Velocity - Template Engine

NPM version build status Test coverage npm download

Velocityjs is velocity template engine for javascript.

中文版文档

Features

  • Supports both client and server side use.
  • Separation of parsing and rendering templates.
  • The basic syntax is fully supported all java version velocity.
  • Vim Syntax for vim.

Install

via npm:

$ npm install velocityjs

Browser

Compatible with all modern browsers. You can try test case in your browser to test it.

For other lower version browsers, you need have those polyfill function.

  1. Array.prototype map, forEach, some, filter, every, indexOf
  2. Date.now
  3. Object.keys

Examples

You can find a lot of examples from the tests directory. There is no different between the use of browser and NodeJs.

Public API

{
  // render method
  render(vm: string, context?: Object, macros?: Object): string;

  parse(vm: string, config?: Object, ignorespace?: boolean): Array<Ast>;

  Compile: {
    (asts: Array<Ast>, config?: Object): {
      render(context?: Object, macros?: Object);
    };
  };
}

render

params:

  • vm {string} velocity string input
  • context {object} render context, data or function for vm
  • macros {object} such as #include('path/xxx') , you can define you inlcude macro function
var Velocity = require('velocityjs');

Velocity.render('string of velocity', context, macros);

context

context is an object or undefined, for vm $foo.bar, data look up path will be context.foo.bar. context can have method, and call it just on velocity string.

The method of context, will have eval method on this of inner method body. You can eval to rerender velocity string, such as test code $control.setTemplate.

Compile and parse

parse method can parse vm, and return ast tree of velocity.

Compile will render asts to result string.

var Compile = Velocity.Compile;

var asts = Velocity.parse('string of velocity');
(new Compile(asts)).render(context, macros);

Compile

params:

  • asts {array} array of vm asts tree
  • config {object} you can define some option for Compile
config
  • escape {boolean} default false, default not escape variable to html encode, you can set true to open it.
  • unescape {object} define the object, which key do not need escape. For example, set unescape equal {control: true}, so $control.html will not escape.
  • env {string} when env equal development will throw error when null values are used
  • valueMapper {function} this config allow us to redefine the #set value, @see #105
  • customMethodHandlers {Array} Used to implement some custom function behavior, the specific use of reference #145

parse

params:

  • vm {string} string to parse
  • blocks {object} self define blocks, such as #cms(1) hello #end, you can set {cms: true}
  • ignorespace {boolean} if set true, then ignore the newline trim.

Syntax

Syntax you can find from velocity user guide

Directives

Directives supports have set, foreach, if|else|elseif, macro, break, stop.

Some othe directive evaluate, define, parse, do not supported default, but You can realize by context or macros, for example parse

Questions

You can find help from those ways:

  1. New issue
  2. Email to eward.song at gmail.com
  3. 阿里内部员工,可以通过 hanwen.sah 搜到我的旺旺

Other

Recommend an other velocity.

License

(The MIT License)

velocity.js's People

Contributors

2betop avatar andreirailean avatar brucek avatar cabumtz avatar davishong avatar dependabot[bot] avatar edwardzzz avatar jamescookie avatar lightsofapollo avatar popomore avatar reiz avatar rschick avatar shepherdwind avatar timse avatar toqoz avatar xch89820 avatar ximsfei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

velocity.js's Issues

#foreach中的#set全局变量

在foreach中的#set,如果是已经在context中有的,应该是操作context而非local

#set($total = 0)
#foreach($i in [1,2,3])
    #set($total = $total + $i)
#end
$total

应该输出6而非0

src\compile\set.js中的再加一个判断可以解决,不知道会不会有其他问题

if (this.condition && this.condition.indexOf('macro:') === 0) {
        context = this.context;
} else if (this.context[ref.id] != null ){
        context = this.context;
}

vm中直出的模板和JS模板如何在页面**存复用?

比如在页面中直出如下代码:

 #foreach($item in $list)
        xxxx
#end

同时,页面中还有JS模板,如下:

< script type="text/html">
#foreach($item in $list)
xxxx
#end
< /script>

如何避免JS标签里的内容在直出时不被解析?

直接判断vm变量的问题

if($!css_pureui) ... #end 为何还是进入内层,按理说$css_pureui未定义应该输出为undefined表达式返回false才对啊

helper 如何使用

我的想法是扫描所有 vm,每个 vm 生成一个对应的 json。

应该用哪个 helper

Print error stack when error throwed

Make error message more helpful. For example:

// vm.vm
111

#foo($name)

// vm1.vm

hello #parse("vm.vm")
// source.vm


#parse("vm1.vm")

context like this:

  var compile = new Compile(Parser.parse(getFileString('source.vm')))
  var macros = {
    foo: function(name){
      throw new Error('Run error')
    },
    parse: function(name){
      return this.eval(getFileString(name));
    }
  }
  compile.render({}, macros)
 Error: Run error
      at #foo($name) L/N 3:0
      at #parse("vm.vm") L/N 2:5
      at #parse("vm1.vm") L/N 3:0
      at Velocity.<anonymous> (/Users/eward/code/velocity/src/compile/compile.js:78:25)
      at Array.forEach (native)
      at Object.utils.(anonymous function) [as forEach] (/Users/eward/code/velocity/src/utils.js:9:25)

自定义block语法支持

比如

#cms(1)
  <div class="abs-right">
    #H(1,"第一个链接")
  </div>
#end

解析过程

 var ast = Velocity.parse(vm, { cms: true });

获得ast

{ type: 'cms',
  args: [ { type: 'integer', value: '1' } ],
  pos: { first_line: 1, last_line: 1, first_column: 0, last_column: 7 } }

set 命令bug

set变量层级大于2时,后面的set指令会覆盖前面set的值

即形如下面的VM指令

#set($a.b.c1 = 1 )
#set($a.b.c2 = 2)

执行后 a = {b:{c2:2}},c1会被覆盖掉

出错代码:

var Velocity = require('velocityjs');
var tpl = '#set($a = "{}")\n#set($a.b = "{}")\n#set($a.b.c1 = 1)\n#set($a.b.c2 = 2)\n<h1>$a.b.c1</h1>';
console.log(Velocity.render(tpl, {}));

原因:
src/compile/set.js 第61行,重置了前面的设置

#stop支持

stop计划支持吗?看了沉鱼的代码,好像支持。如果没有计划加进去,我先参考沉鱼的版本修改一下。

jsonify use

Jsonify是velocityjs中最重要的一个Helper,主要用于解决数据同步问题。

Jsonify的基本**是,根据给定vm编译成一个只包含数据的vm,编译后的vm通过解析可以获得一个json字符串

调用接口重要有两个,一个是命令行方式,和lessc命令类似,另外node模块的方式,和调用velocity解释器的api相似。Jsonify在0.3.3才开始稳定。执行命令之前,确保是安装到了最新版本的velocityjs,对比

  • velocity -v
  • npm info velocity version

命令行方式调用

$ velocity -j xx.vm
$ velocity -j xx.vm > data.vm
$ velocity -j xx.js

当第二个参数不是vm文件,而是js的时候,可以自定义context函数和macros函数

var fs = require('fs');
module.exports = {
  files: __dirname + '/a.vm',
  context: {
    control : {
      setTemplate: function(file){
        var fullfile = __dirname + '/' + file;
        if (fs.existsSync(fullfile)) {
          return this.eval((fs.readFileSync(fullfile)).toString());
        } 
      }
    }
  }
};

node模块调用

作为node模块,调用方式为

var Velocity = require('velocityjs');
var Jsonify = Velocity.Jsonify;
var asts = Parser.parse(fs.readFileSync(file).toString());

var makeup = new Jsonify(asts);
console.log(makeup.toVTL())

编译规则

Jsonify执行过程中,把vm变量分为两者基本类型:

  1. 数据 $foo.bar => {foo: {bar: “$foo.bar”}}
  2. 函数 $foo.bar() => {foo: {bar: “function(){}”}}

主要处理数据,对于函数的处理是返回一段站位字符串function(){},因为函数无法描述在json中。数据主要分为三种不同的结构:

  1. 字符串/数字/布尔等简单数据,这种情况处理起来最简单
  2. 数组数据,来源于#foreach #foreach($a in $foo)
  3. map集合,来源于#foreach #foeach($a in $foo.keySet())
  4. 数据对象,同样来自于#foreach,和2类似,只是数组的元素是对象,而不是基本数据类型

遇到macro调用时,会找到macro形参对应的真实变量,并且支持递归。

例子:example.vm

  #macro(a $a $b)
    $a $b
  #end

  #macro(b $aa $bb)
    #a($aa, $bb)
  #end

  #b($aaa $bbb())

通过命令执行

$ velocity -j example.vm
{
  "aaa": "$aaa",
  "bbb": "function(){}"
}

内部api使用/宏重写

Jsonify构造器接受三个参数

  1. asts velocity语法树对象,通过Parser.parse(vmText)获得
  2. context
  3. macros

第二个参数和第三个参数是可选的,用于定义vm中的一些宏或者方法,plum中webx规则使用了内部api,下面看一个例子:

文件a.vm

$ctrl.load("b.vm")
#parse("c.vm")

b.vm

$hello.b

c.vm

$hello.b

a.js

var velocity = require('velocityjs');
var fs = require('fs');
var str     = fs.readFileSync(__dirname + '/a.vm').toString();
var Jsonify = velocity.Jsonify;
var Parser  = velocity.Parser;

function loadVFile(txt){
  var file = __dirname + '/'+ txt;
  if (fs.existsSync(file)) {
    this.eval(fs.readFileSync(file).toString());
  }
}

var markup = new Jsonify(Parser.parse(str), {ctrl: {load: loadVFile}}, {parse: loadVFile});
console.log(markup.toVTL());

执行a.js

$ node a.js
{
  "hello": {
    "b": "$hello.b",
    "c": "$hello.c"
  }
}

上面a.vm中,第一个函数$ctrl.load调用的是Jsonify构造器第二个参数context中的方法,第二个宏#parse(“c.vm”),处理来自Jsonify构造器第三个参数。也就是说,Jsonify中提供一套api,可以通过这些api来在执行过程中执行自定义的函数,比如加载另外一个vm,进行解析。需要注意的是,在loadVFile函数定义中,必须使用this.eval这个api来解析获得的字符串。

macors支持

对于#macro (test) 这种的在vm中调用的时候,现在貌似只能采用#test()这种调用方式。
可否也支持#test这种方式的调用呢?

数值解析错误

#set($ratio=$height*1.0/$width*1.0)

错误

Error: Lexical error on line 2. Unrecognized text.
...set($ratio=$height*1.0/$width*1.0)<div 
-----------------------^
    at Object.parseError (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:267:11)
    at Object.parseError (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:378:28)
    at Object.lexer.next (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:514:25)
    at Object.lex (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:519:22)
    at lex (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:289:28)
    at Object.parse (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:302:26)
    at Object.exports.parse (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:736:53)
    at getVTL (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/bin/velocity-cli.js:139:21)
    at Object.jsonify (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/bin/velocity-cli.js:130:17)
    at Object.<anonymous> (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/bin/velocity:23:11)

context this 指向的问题

var velocity = require('velocityjs');
var data = 'a = $a.get()';

function b(c) {
  this.c = c;
}
b.prototype.get = function() {
  return this.c;
};

var property = velocity.render(data, {
  a: new b(1)
});
console.log(property); // 输出 a = $a.get()

get 方法调用实例上属性的时候输出错误,如果改成字符串是正确的。

macro 能否支持自定义

在 render 的时候可以传自定义的 macro,可以覆盖页面上的宏

var macro = {
  parse: function(){},
  shtml: function(){}
};
velocity.render(str, context, macro)

API修改

去掉Velocity.Parser,改为Velocity.parse。Velocity.parse等于以前的Velocity.Parser.parse。

Unicode escaping: why ? how to config ?

Hi, thanks for this awesome module, it works a charm!

in src/compile/index.js you have:

function Velocity(asts, config) {
  this.asts = asts;
  this.config = {
    // 自动输出为经过html encode输出
    escape: true,
    // 不需要转义的白名单
    unescape: {}
  };
  utils.mixin(this.config, config);
  this._state = { stop: false, break: false };
  this.init();
}

I would like to set escape to false. Escaping those: <, >, & with &lt;, &gt;, &amp; is not a behavior I want in my application.

For now I'm using a fork of your module with escape set to false, but I would love to use your code if this could be configured!

What do you think ? If you're busy, I can PR a configurable something.
Thanks!

PS : Why escaping in the first place ?
PPS : your MIT license is outdated (2012-2013) 😉

解析出错

$!tradeDetailModel.goodsInfoModel.goodsTitle[<a href="$personalModule.setTarget('/p.htm').addQueryData('id',$!stringUtil.substringAfter($!tradeDetailModel.goodsInfoModel.goodsId,'guarantee.'))" target="_blank">商品页面</a>]

源码如上,虽然这样写很坑爹,$!{tradeDetailModel.goodsInfoModel.goodsTitle} 这样写应该更好,但是否可以避免。

Error: Parse error on line 135:
...nfoModel.goodsTitle[<a href="$personalMo
-----------------------^
Expecting 'DOLLAR', '-', 'CONTENT', 'CLOSE_BRACKET', 'BOOL', 'INTEGER', 'STRING', 'EVAL_STRING', got '<'
    at Object.parseError (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/parse/index.js:267:11)
    at Object.parse (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/parse/index.js:319:22)
    at Object.exports.parse (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/parse/index.js:736:53)
    at Object.Velocity.render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/velocity.js:22:21)
    at Jsonify.macro.parse (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/lib/model/macro.js:25:23)
    at Jsonify.utils.mixin.getMacro (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/block.js:126:17)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:66:14)
    at Array.forEach (native)
    at Object.forEach.utils.(anonymous function) [as forEach] (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/utils.js:14:25)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:41:11)

vm 解析失败

var velocity = require('velocity.js');

var str = '#set($js_file = [ ' +
  '"js/app/apww.js"'+
  '])' +
  '#foreach($item in $js_file)' +
  's($item)' +
  '#end';

var property = velocity.render(str, {
  s: function() {
    return s.replace('.js', '');
  }
});
console.log(property)

set 对象数组出错

#set($a = [
  {'name': 1},
  {'name': 2}
])

错误

Error: Lexical error on line 2. Unrecognized text.
...set($a = [  {'name': 1},  {'name': 2}
----------------------^

jsonify 的问题

报了错,但不太好描述,不太清楚内部实现,在内部打了些 log

TypeError: Cannot set property 'amount' of undefined
    at Jsonify.module.exports.utils.mixin.setRef (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/jsonify.js:78:21)
    at Jsonify.module.exports.utils.mixin.toBasicType (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/jsonify.js:54:12)
    at Jsonify.utils.mixin.getReferences (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/references.js:159:14)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:59:14)
    at Array.forEach (native)
    at Object.forEach.utils.(anonymous function) [as forEach] (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/utils.js:14:25)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:41:11)
    at Jsonify.utils.mixin.getBlockIf (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/block.js:67:12)
    at Jsonify.utils.mixin.getBlock (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/block.js:43:20)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:76:38)

https://github.com/shepherdwind/velocity.js/blob/master/src/helper/jsonify/jsonify.js#L58

应该是 setRef 的错,发现 75 行 context 为空,查了下 vm 应该是这个变量 $!consumeRecordListQueryResult.totalAmount.amount

但在之前的 context 中未解析出来

{
  ...
  consumeRecordListQueryResult: '{@31@}',
  ...
}

但不知道为什么没解析出来,查了下还有其他变量

consumeRecordListQueryResult.totalCount
consumeRecordListQueryResult.records.paginator

属性值为null时异常

$one.two
context = {
  one:{two:null} 
}

在context对象中的属性为null时,会报异常,之前用的版本没问题,应该是新版本引入的。

错误栈

TypeError: Cannot read property '$stop' of null
  at Velocity.<anonymous> (D:\workspace\static-server\node_modules\velocityjs\src\compile\references.js:117:39)
  at Array.some (native)
  at Object.utils.(anonymous function) [as some] (D:\workspace\static-server\node_modules\velocityjs\src\utils.js:14:25)
  at Velocity.utils.mixin.getReferences (D:\workspace\static-server\node_modules\velocityjs\src\compile\references.js:107:15)
  at Velocity.<anonymous> (D:\workspace\static-server\node_modules\velocityjs\src\compile\compile.js:86:25)
  at Array.forEach (native)
  at Object.utils.(anonymous function) [as forEach] (D:\workspace\static-server\node_modules\velocityjs\src\utils.js:14:25)
  at Velocity.utils.mixin._render (D:\workspace\static-server\node_modules\velocityjs\src\compile\compile.js:82:13)
  at Velocity.utils.mixin.render (D:\workspace\static-server\node_modules\velocityjs\src\compile\compile.js:48:22)
  at Object.Velocity.render (D:\workspace\static-server\node_modules\velocityjs\src\velocity.js:33:18)
  at Object.<anonymous> (D:\workspace\static-server\test\test_velocity.coffee:9:18, <js>:14:21)
  at Object.<anonymous> (D:\workspace\static-server\test\test_velocity.coffee:1:1, <js>:18:4)
  at Module._compile (module.js:456:26)

查看代码发现

 //第三个参数,返回后面的参数ast
          ret = this.getAttributes(property, ret, ast.path.slice(i + 1), ast);

//提前结束计算,某些情况下,连缀运行,后面的运算影响前面的结果,这种
          //情况需要特殊处理,比如$control.setTempalte('a.vm').setParameter('a', 'b')
          //第一个函数就返回结果,这时候,函数返回对象为
          //{$stop: true, $return: 'string'}
          //$stop表示停滞,$return:返回结果
          if (ret === undefined || ret.$stop === true) {
            ret = ret && ret.$stop ? ret.$return : ret;
            return true;
          }

如果getAttributes返回null的话,ret === undefined为false,ret.$stop就报错了,改为==或者加一个null判断即可

又一个语法错误

Error: Parse error on line 416:
...ankName.length()>15)...#end #end</li>
-----------------------^
Expecting 'EOF', 'COMMENT', 'HASH', 'DOLLAR', 'ID', 'CONTENT', got 'DOT'

具体代码

<li class="name">
#if($stringUtil.isNotBlank($!switchBankName)) $stringUtil.left($!switchBankName,15)#if($!switchBankName.length()>15)...#end 
#end
</li>

#parse用法问题

parse找不到文件不报错么?为什么定义了getParseString #parse还一直解析为空?为什么不定义renderFile接口,这样就可以找到include或parse的文件了?

#parse使用求教

关于模板中的#parse 宏,看文档和issue还是不知道在node中究竟怎样实际使用!
例如在a.vm文件中有一行:#parse("./b.vm");
b.vm是个vm模板文件,里面有#set宏!
求教如何写才能正确渲染出来a.vm的内容?

getParseString 不能返回为空

Error: Parse error on line 1:

^
Expecting 'COMMENT', 'HASH', 'DOLLAR', 'ID', 'CONTENT', got 'EOF'
at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:283:11)
at Object.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:335:22)
at Object.exports.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:758:53)
at Velocity.utils.mixin.getParse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/parse.js:19:27)
at Velocity.utils.mixin._render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/compile.js:101:23)
at Array.forEach (native)
at Object.forEach.utils.(anonymous function) [as forEach] (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/utils.js:14:25)
at Velocity.utils.mixin._render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/compile.js:67:13)
at Velocity.utils.mixin.getMacro (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/blocks.js:95:20)
at Velocity.utils.mixin._render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/compile.js:97:23)

无法调用 object 的方法

#set($static_root = $staticServer.getURI(""))
#set($js_file = {
    "js_arale":"build/js/arale.js?t=20110608",
    "js_ma_template":"build/js/ma/template.js?t=20110608",
    "js_pa_pa":"build/js/pa/pa.js?t=20110608",
    "js_swiff":"build/js/app/swiff.js?t=20110608",
    "js_alieditControl":"build/js/pa/alieditcontrol-update.js?t=20110608"
})

#foreach($item in $js_file.entrySet())
    $item.key = <script charset="utf-8" src="$staticServer.getURI("/$item.value")"></script>
#end 

js_file 应该是个 Map,有 entrySet 方法,这种应该如何解析比较好?有没有什么接口可以把 set 的值封装下。

jsonify 处理macro时局部变量和全局变量同名时死循环

文件a.vm代码如下:

#macro(showMsg $msg)
  #if($msg)
    <div class="valid-under" style="height: 26px; ">
      <p class="estate error">
      <span class="label">$msg</span>
      </p>
    </div>
  #end
#end
#showMsg($msg)

执行命令

$ velocity -j a.vm

出现死循环,报错

/Users/eward/code/velocity.js/src/helper/jsonify/references.js:156
        var index = local.variable.indexOf(ref.id);
                                   ^
RangeError: Maximum call stack size exceeded

渲染 css 解析错误

    throw new Error(str);
          ^
Error: Parse error on line 15:
...ght: bold;   color: #666;    height:39px;    
----------------------^
Expecting 'NOESCAPE', 'SET', 'IF', 'ELSEIF', 'ELSE', 'END', 'FOREACH', 'ID', 'BREAK', 'INCLUDE', 'PARSE', 'EAVL', 'DEFINE', 'MACRO', 'CONTENT', got 'INTEGER'
    at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:261:11)
    at Object.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:313:22)
    at Object.exports.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:710:53)
    at Object.Velocity.render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/velocity.js:21:21)
    at Object.<anonymous> (/Users/popomore/code/popomore/node-sofa/test.js:5:22)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.runMain (module.js:492:10)

如果不存在就忽略,可以不抛错。

$!a.b, 若a不存在,会报错

$!a.b, 若a不存在,线上解析没问题,mock会js报错

推荐修改src/compile/references.js

utils.some(ast.path, function(property){
  //第三个参数,返回后面的参数ast
  ret = ret && this.getAttributes(property, ret, ast);
}, this);

不支持取模

好像测试用例里也没有

Error: Lexical error on line 361. Unrecognized text.
...if(0==$velocityCount%2) class="split" #e
-----------------------^
    at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:263:11)
    at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:374:28)
    at Object.lexer.next (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:510:25)
    at Object.lex (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:515:22)
    at lex (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:285:28)
    at Object.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:298:26)
    at Object.exports.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:712:53)
    at Object.Velocity.render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/velocity.js:21:21)
    at Object.module.exports.parse (/Users/popomore/code/popomore/node-sofa/lib/render.js:93:38)
    at /Users/popomore/code/popomore/node-sofa/lib/server.js:30:21

语法解析式中局部变量的实现

晚上和汤进伟讨论vim和emac的区别,各自的优势,这两个神器,一说总是有无限的话题。每个软件都有各自的特色,每个人有各自的用法,所谓神器就是每个人都能以各自舒适的方式使用。从语法提示的角度来说,文本编辑器的确实比不上IDE可以内置一个webkit,一个nodejs。小汤说,不就是一个语法解释器嘛,我也想实现一个js的语法解释器。那已经有了很多了啊。我想用lisp写,就是对抽象语法树获取到了以后,应该怎么做还是不明白,比如局部变量如何实现,比如,for循环如何实现。

这个问题,我毕竟做过velocityjs,还是可以谈谈这样的问题的。上次那篇文章,如何实现velocity语法解释器,写得有点大,感觉没说清楚,现在,主要说明一下语法解释器中局部变量的实现过程。

准备数据

这里首先说明一下,语法解释器基本上包括两个过程,抽象语法树生成和解释执行过程。前面那个过程有成熟的解决方案,比如c语言的Bison,js的Jison,还有Racc for ruby的,这些都是Parser Generator,姑且称之为语法分析生成器。它们得到的是输入字符串对应的语法结构,这些语法结构是自定义语法使用者自定义的。整个过程原理本文不详细说,总结一下就是,一个字符串输入,得到一个结构化数据。用js语法可以这样描述:

var text = "hello $name";
var Parse = require("./path/to/parse.js");
var result = Parse.parse(text);
// result = ["hello", {type: "var", id: "name"}];

上面只是一个简单的例子,当输入text变得复杂的时候,分析器得到的结果会复杂得多,但这个结构是使用者自己随意定义的。还是以velocity模板为例,模板语言有一个特点,模板有一部分是不含任何语法的字符串,中间参杂着语法结构。我定义vm语法结构规则是这样的:

  1. 不含结构的语法字符串,输出是一个原样字符串
  2. 语法结构用一个对象描述
  3. 整体上,语法结构由一个数组构成,数组的元素是上面两个结构

举几个实例,方便大家明白:

  1. foreach循环结构
var text = '#foreach($foo in $bars) get $foo #end';
var result = [
  {type: "foreach", from: {type: "var", id: "bars"}},
   " get ", 
   {type: "var", id: "foo"}, 
   " ", 
   {type: "end"}
 ];
  1. macro函数结构
var text = '#macro(sayHello $bar)  hello $bar #end';
var result = [
    {type: "macro", id: "sayHello", args: {type: "var", id: "bar"}},
    " hello ",
    {type: "var", id: "bar"},
    {type: "end"}
];

上面的两个例子是我们定义好的数据结构,这些数据结构,就是velocity语法对应的语法解析树,虽然名字里面有树,但实际上,这是一个数组,而且是一位数组,其实是不是树无所谓,关键是,这个结构是通过前面的字符串分析得到的,这个结构是有意义的,通过这个结构我们后面可以对之进行运算。下面主要讲一下,语法解析树得到之后需要执行的过程。

实现原理

前面说明了很多,主要是数据准备。这个数据结构非常重要,什么样的数据结构决定了后面运算过程。现在有了这样的数据了,就好像一台机器的零件完成,下面只要对其进行组装就好了。

来点画面感的东东吧,在一个流水线上,放着一排水果:苹果,香蕉,橘子等等,中间有一个位置放着一个篮子,里面写着:爸爸的惊喜——意思是,这个篮子里面将要放入爸爸给的惊喜,但现在还不知道是什么。然后,如果你想知道这一排水果分别是什么,那么你可以,一个一个把水果拿到另外一个流水线上,遇到篮子的时候,安装篮子的提示,去找爸爸要礼物,然后把礼物放到篮子占的位置。到最后一个水果,所有的水果你就能够知道了。这也就是模板解释器运算的过程。

上面的流水线运行过程,你知道,从某某人那里去拿礼物,这意味着,你需要去找到这个人,找人的过程,就是变量查找的过程。再看下,篮子上写着爸爸的礼物,爸爸这个可以改为一个名字,比如小明,这个时候我们需要去找到小明。找小明需要从一个地方去找,而且这个地方只有一个叫小明的人。这时候,我们引入一个花名册吧,在流水线的旁边放一个花名册,这样可以通过花名册找到小明,或者其他人。

在流水线中,我们遇到一个篮子,上面写着一个谜语,如果答对了,后面三个水果都可以拿去,如果没答对,就跳过后面三个。这个时候,我们可以把这个篮子和后面三个水果看做一个子流水线,在这条流水线的运行过程,和主线没有什么区别。这时候,我们引入了子流水线过程。

再加入一个循环的篮子,上面写着,找到花名册中名字是两个字的人,把这个人暂时起个名字小华,后面的5个项目组成一个子流水线。这时候,把主线暂停,只有子流水线在运行。可以想象成有两条平行的流水线了,这时候,子流水线隶属于主流水线,在子线上面的篮子需要找花名册的时候,还是能够查询住流水线的。同时,每条流水线都有属于自己的花名册,在这个子名册中,小华是通过子流水线运行之初通过前面那个循环的篮子赋予的,循环的篮子每找到一个名字,把这个名字赋予小华,然后子流水线运行一遍。

上面的例子,大致说明了foreach循环的时候,局部变量如何运行的。velocity的解析过程类似于上面的流水线,变量查找就是从流水线的花名册中找到名字。遇到foreach循环的时候,一条子流水线分离出来,每次自流水线运行的时候,主流水线赋予自流水线一个私有的花名册,在子流水线中,名字查找说先找自己的花名册,然后再到主花名册中去找。两个流水线本质上是没有区别的,所以,自流水线又可以分离出子流水线,构成foreach的嵌套。另外,macro函数执行的时候,情况是一样的,macro的内容相当于一条子流水线,marco运行时,上层流水线把形参赋予marco私有的花名册,这就是velocity解释器中局部变量实现的基本原理。

实现代码

上面是我自己想到的描述,可能不是很清晰。下面简单实现一下整个过程:

var compile = function(asts, context){
  this.asts = asts;
  this.context = context;
  this.local = {};
  this.conditions = [];
};

compile.prototype = {

  constructor: compile,

  render: function(asts){

    var str = '';
    var block = [];
    var index = 0;
    asts = asts || this.asts;

    // 流水线依次运行
    utils.forEach(asts, function(ast){

      var type = ast.type;

      //foreach if macro时,index加1
      if (utils.indexOf(type, BLOCK_TYPES) > -1) index ++;

      if (index) {
        type === 'end' && index--;
        // 如果index不为1,说明block还没有闭合,比如#foreach #if #else #end #end
        // 遇到if的时候,index是2,遇到第一个end的时候,index为1,此时流水线没有
        // 完整,需要等到下一个end,此时,block收集了中间所有的元素,构成一个流
        // 水线的元素,在下面调用getBlock启动子流水线
        if (index) return block.push(ast);
      }

      if(type == 'references') {
        str += this.getReferences(ast, true);
      } else if(type === 'end') {
        //使用slide获取block的拷贝
        str += this.getBlock(block.slice());
        // 清空数组
        block = [];
      } else {
        str += ast;
      }

    }, this);

    return str;
  },

  getReferences: function(ast){

    var name = ast.name;
    var local = this.local;
    // 默认赋值全局变量
    var ret = this.context[name];

    // 判断是否是局部变量,this.conditions依次记录当前状态堆栈,从第一个开始,一
    // 直向上查找,如果变量在当前局部环境中找到,那么返回局部变量的值,局部变量
    // 贮存在this.local[contextId]中
    utils.some(this.conditions, function(contextId){
      //局部环境中找到
      if (id in local[contextId]) {
        ret = local[contextId][name];
        return true;
      }
    });

    return ret;
  },

  getBlock: function(block){

    var ast = block[0];
    //创建唯一的contextId
    var contextId = 'foreach:' + utils.guid();

    if (ast.type === 'foreach') {

      utils.forEach(ast.from, function(val, i){

        //构造临时变量
        var local[ast.to] = val;
        local['foreach']['count'] = i + 1;
        local['foreach']['index'] = i;
        local['foreach']['hasNext'] = i + 1 < len;
        local['velocityCount'] = i + 1;
        //贮存local变量
        this.local[contextId] = local;
        // push contextId到堆栈,流水线开始
        this.conditions.unshift(contextId);
        ret += this.render(_block);
        // 流水线结束
        this.conditions.pop();

      }, this);

    }
  }

};

#if and to many newlines

Hi,
at first thanks for this create library!!!

If i use a #if comes after a line with a newline and the statement is also followed by a new line the rendered file contains two newlines. But there should be only one. The same issue i had with the #end statement.
If i use a #set statement outside of an #if, the output is like expected, there is only one newline.
But an #set statement inside an #if will also add a newline.

Unit-Tests is worth a thousand words :)

    it('multiline: #set multiline', function(){
      var vm = "$bar.foo()\n#set($foo=$bar)\n..."
      assert.equal("$bar.foo()\n...", render(vm)) 
      // all fine here, test pass
    })

    it('multiline: #if multiline', function(){
      var vm = "$bar.foo()\n#if(1>0)\n...#end"
      assert.equal("$bar.foo()\n...", render(vm)) 
      // assert failed, output is: "$bar.foo()\n\n..."
    })

    it('multiline: #set #set', function(){
      var vm = "$bar.foo()\n...\n#set($foo=$bar)\n#set($foo=$bar)"
      assert.equal("$bar.foo()\n...\n", render(vm))
      // all fine here, test pass
    })

    it('multiline: #if multiline #set', function(){
      var vm = "$bar.foo()\n#if(1>0)\n#set($foo=$bar)\n...#end"
      assert.equal("$bar.foo()\n...", render(vm)) 
      // assert failed, output is: "$bar.foo()\n\n\n..."
    })

    it('multiline: #if multiline #set #end', function(){
      var vm = "$bar.foo()\n#if(1>0)...\n#set($foo=$bar)\n#end"
      assert.equal("$bar.foo()\n...\n", render(vm)) 
      // assert failed, output is: "$bar.foo()\n...\n\n\n"
    })

regards,
David

不支持下划线变量?

Error: Lexical error on line 12. Unrecognized text.
...{css_pa_pa})#if($!__cssRef)  #SLITERAL
---------------------^

有没有block、extends?

@yanni4night 这个问题,单独建立一个issue吧。

已有讨论

by shepherdwind

@yanni4night http://velocity.apache.org/engine/devel/user-guide.html 这份文档,没有看到block和extends的介绍啊,block是define么?

by yanni4night

@shepherdwind 我确实也没有,不知道大家怎么用,习惯了用twig的extends继承功能,在父模板中留坑,从子模板上溯到父模板渲染感觉比include方便。参考 http://badqiu.iteye.com/blog/558194

by shepherdwind

@yanni4night 继承的使用确实要好很多,twig的那种还蛮好的。在velocity模板中实现,还得增加新的block语法才行,这个现在velocityjs没有支持。
不过,我不是很明白,velocity是基于java的模板引擎,就算velocityjs支持继承的语法,又有什么用呢,velocityjs现在还是主要用于模拟java的velocity模板解析过程。

macro中的变量作用域

velocity中macro中使用#set定义的变量默认情况下是外部可见的
http://velocity.apache.org/engine/releases/velocity-1.5/user-guide.html#velocimacros

velocimacro.context.localscope - This property has the possible values true or false, and the default is false. When true, any modifications to the context via #set() within a Velocimacro are considered 'local' to the Velocimacro, and will not permanently affect the context.

所以

#macro (local)
#set($val =1)
$val
#end
#local()
$val

会被解析为

1
1

而不是

1
$val

对context中set开头变量的取值错误

在上下文中有set开头的变量,在取值的时候会被错误判断为函数处理造成取不到值

compile / references.js中hasSetMethod需要加个对 ast.type 的判断

   if (lastId.indexOf('set') !== 0 || ast.type != 'method') 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.