shepherdwind / velocity.js Goto Github PK
View Code? Open in Web Editor NEWvelocity for js
License: MIT License
velocity for js
License: MIT License
我的想法是扫描所有 vm,每个 vm 生成一个对应的 json。
应该用哪个 helper
关于模板中的#parse 宏,看文档和issue还是不知道在node中究竟怎样实际使用!
例如在a.vm文件中有一行:#parse("./b.vm");
b.vm是个vm模板文件,里面有#set宏!
求教如何写才能正确渲染出来a.vm的内容?
valocity中宏不需要参数时可以不带括号,但是
var Velocity = require('velocityjs');
var strHtml = Velocity.render('#cssHere', {}, {cssHere: function(){return 'done';}});
出错代码如下:
var Velocity = require('velocityjs');
console.log(Velocity.render('##'));
输出结果为: ##
实际应该为空
建议修正: https://github.com/shepherdwind/velocity.js/blob/master/src/parse/velocity.l 第38行:
<h,mu>"##"[^\n]+ { this.popState(); return 'COMMENT'; }
为
<h,mu>"##"[^\n]* { this.popState(); return 'COMMENT'; }
比如在页面中直出如下代码:
#foreach($item in $list)
xxxx
#end
同时,页面中还有JS模板,如下:
< script type="text/html">
#foreach($item in $list)
xxxx
#end
< /script>
如何避免JS标签里的内容在直出时不被解析?
Error: Lexical error on line 12. Unrecognized text.
...{css_pa_pa})#if($!__cssRef) #SLITERAL
---------------------^
去掉Velocity.Parser,改为Velocity.parse。Velocity.parse等于以前的Velocity.Parser.parse。
#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 的值封装下。
code
#macro(a $b $list)
#foreach($a in $list)
$a
#end
$b
#end
#a("hello", [1, 2])
expect
1
2
hello
actual get
1
2
$b
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)
@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 (test) 这种的在vm中调用的时候,现在貌似只能采用#test()这种调用方式。
可否也支持#test这种方式的调用呢?
#set($a = [
{'name': 1},
{'name': 2}
])
错误
Error: Lexical error on line 2. Unrecognized text.
...set($a = [ {'name': 1}, {'name': 2}
----------------------^
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>
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 <, >, &
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) 😉
foo = {
isTrue: function(str) {
return !!str;
}
}
console.log(velocity.render('#if($foo.isTrue("true"))true#end',{foo: foo}))
https://github.com/shepherdwind/velocity.js/blob/master/src/helper/jsonify/references.js#L109
这里是不是应该判断是否为函数,context 里应该可以放变量的吧
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)
如果不存在就忽略,可以不抛错。
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模块,调用方式为
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变量分为两者基本类型:
$foo.bar
=> {foo: {bar: “$foo.bar”}}
$foo.bar()
=> {foo: {bar: “function(){}”}}
主要处理数据,对于函数的处理是返回一段站位字符串function(){}
,因为函数无法描述在json中。数据主要分为三种不同的结构:
#foreach($a in $foo)
#foeach($a in $foo.keySet())
遇到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(){}"
}
Jsonify构造器接受三个参数
asts
velocity语法树对象,通过Parser.parse(vmText)
获得context
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来解析获得的字符串。
请问你的velocity.js还在维护吗, 有个工具要依赖vm, 正在犹豫依赖你这个还是foolfish的版本。
$!a.b, 若a不存在,线上解析没问题,mock会js报错
推荐修改src/compile/references.js
utils.some(ast.path, function(property){
//第三个参数,返回后面的参数ast
ret = ret && this.getAttributes(property, ret, ast);
}, this);
报了错,但不太好描述,不太清楚内部实现,在内部打了些 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
Currently this engine only renders the template writing null without throwing error in case of null values. Is there a configuration which can be used so the engine throws errors/reports failures to the caller on null values.
$!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)
赞,太好了
#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)
比如
#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 } }
code
#foreach($i in [1..3])
$velocityCount
#foreach($j in [1..3])
$velocityCount
#end
#end
expect
1
1 2 3
2
1 2 3
3
1 2 3
actual get
1
1 1 1
2
2 2 2
3
3 3 3
在 render 的时候可以传自定义的 macro,可以覆盖页面上的宏
var macro = {
parse: function(){},
shtml: function(){}
};
velocity.render(str, context, macro)
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 方法调用实例上属性的时候输出错误,如果改成字符串是正确的。
比如 context 中有 stringUtil,但是输出的 json 最好不包括这个。
在上下文中有set开头的变量,在取值的时候会被错误判断为函数处理造成取不到值
compile / references.js中hasSetMethod需要加个对 ast.type 的判断
if (lastId.indexOf('set') !== 0 || ast.type != 'method')
https://github.com/shepherdwind/velocity.js/blob/master/src/compile/references.js#L7 代码中 getSize
方法
if (utils.isArray(obj)) {
return obj.length;
} else if (typeof obj === 'string') {
return utils.keys(obj).length;
}
obj 是 string 的话应该不能这么用吧
文件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
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)
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)
在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;
}
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 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
$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判断即可
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
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行,重置了前面的设置
晚上和汤进伟讨论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语法结构规则是这样的:
举几个实例,方便大家明白:
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"}
];
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);
}
}
};
当在页面里使用jQuery的$符时, 有时候会因为$符被解析而导致出错parseError,希望能支持#[[don't parse me!]]#
语法
语法参见:http://velocity.apache.org/engine/devel/user-guide.html
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.