Created
December 13, 2010 14:31
-
-
Save hotpotato/739036 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2005, The haXe Project Contributors | |
* All rights reserved. | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* - Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* - Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY | |
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR | |
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | |
* DAMAGE. | |
*/ | |
private enum TemplateExpr { | |
OpVar( v : String ); | |
OpExpr( expr : Void -> Dynamic ); | |
OpIf( expr : Void -> Dynamic, eif : TemplateExpr, eelse : TemplateExpr ); | |
OpStr( str : String ); | |
OpBlock( l : List<TemplateExpr> ); | |
OpForeach( name:String, expr : Void -> Dynamic, loop : TemplateExpr ); | |
OpMacro( name : String, params : List<TemplateExpr> ); | |
OpFill(name:String, efill : TemplateExpr); | |
OpSet(name:String, eset : Void -> Dynamic); | |
OpUnset(varName:String); | |
} | |
private typedef Token = { | |
var s : Bool; | |
var p : String; | |
var l : Array<String>; | |
} | |
private typedef ExprToken = { | |
var s : Bool; | |
var p : String; | |
} | |
class HxTemplate { | |
var splitter:EReg; | |
var expr_splitter:EReg; | |
var expr_trim:EReg; | |
var expr_int:EReg; | |
var expr_float:EReg; | |
var expr : TemplateExpr; | |
var context : Dynamic; | |
var macros : Dynamic; | |
var fills : Hash<TemplateExpr>; | |
var stack : List<Dynamic>; | |
var buf : StringBuf; | |
var parentResolve:String->Dynamic; | |
public function new( str : String ) { | |
splitter = ~/(::(?:[A-Za-z0-9_ ()&|!+=\/><*."?-]|(?:[:][A-Za-z0-9_ ()&|!+=\/><*."-]))+::|\$\$([A-Za-z0-9_-]+)\()/; | |
expr_splitter = ~/(\(|\)|[ \r\n\t]*"[^"]*"[ \r\n\t]*|[?!+=\/><*.&|:-]+)/; | |
expr_trim = ~/^[ ]*([^ ]+)[ ]*$/; | |
expr_int = ~/^[0-9]+$/; | |
expr_float = ~/^([+-]?)(?=\d|,\d)\d*(,\d*)?([Ee]([+-]?\d+))?$/; | |
var tokens = parseTokens(str); | |
expr = parseBlock(tokens); | |
if( !tokens.isEmpty() ) | |
throw "Unexpected '"+tokens.first().s+"'"; | |
} | |
public function execute( context : Dynamic, ?macros : Dynamic, ?parentResolve : String->Dynamic) { | |
this.macros = if( macros == null ) {} else macros; | |
this.context = context; | |
this.parentResolve = parentResolve; | |
stack = new List(); | |
buf = new StringBuf(); | |
fills = new Hash(); | |
run(expr); | |
return buf.toString(); | |
} | |
function resolve( v : String ) : Dynamic { | |
var e = fills.get(v); | |
if (e != null) | |
{ | |
var old = buf; | |
buf = new StringBuf(); | |
run(e); | |
var res = buf.toString(); | |
buf = old; | |
return res; | |
} | |
if( Reflect.hasField(context,v) ) | |
return Reflect.field(context,v); | |
for( ctx in stack ) | |
if( Reflect.hasField(ctx,v) ) | |
return Reflect.field(ctx,v); | |
if( v == "__current__" ) | |
return context; | |
if (parentResolve != null) { | |
return parentResolve(v); | |
} | |
if (v.indexOf(".") != -1) { | |
var chunks = v.split("."); | |
var cur = resolve(chunks.shift()); | |
while (chunks.length > 0) { | |
cur = Reflect.field(cur, chunks.shift()); | |
} | |
return cur; | |
} | |
return null; | |
} | |
function parseTokens( data : String ) { | |
var tokens = new List<Token>(); | |
while ( splitter.match(data) ) { | |
var p = splitter.matchedPos(); | |
if( p.pos > 0 ) | |
tokens.add({ p : data.substr(0,p.pos), s : true, l : null }); | |
// : ? | |
if( data.charCodeAt(p.pos) == 58 ) { | |
tokens.add({ p : data.substr(p.pos + 2,p.len - 4), s : false, l : null }); | |
data = splitter.matchedRight(); | |
continue; | |
} | |
// macro parse | |
var parp = p.pos + p.len; | |
var npar = 1; | |
while( npar > 0 ) { | |
var c = data.charCodeAt(parp); | |
if( c == 40 ) | |
npar++; | |
else if( c == 41 ) | |
npar--; | |
else if( c == null ) | |
throw "Unclosed macro parenthesis"; | |
parp++; | |
} | |
var params = data.substr(p.pos+p.len,parp - (p.pos+p.len) - 1).split(","); | |
tokens.add({ p : splitter.matched(2), s : false, l : params }); | |
data = data.substr(parp,data.length - parp); | |
} | |
if( data.length > 0 ) | |
tokens.add({ p : data, s : true, l : null }); | |
return tokens; | |
} | |
function parseBlock( tokens : List<Token> ) { | |
var l = new List(); | |
while( true ) { | |
var t = tokens.first(); | |
if( t == null ) | |
break; | |
if( !t.s && (t.p == "end" || t.p == "else" || t.p.substr(0,7) == "elseif ") ) | |
break; | |
l.add(parse(tokens)); | |
} | |
if( l.length == 1 ) | |
return l.first(); | |
return OpBlock(l); | |
} | |
function parse( tokens : List<Token> ) { | |
var t = tokens.pop(); | |
var p = t.p; | |
if( t.s && p.substr(0, 4) != "set ") | |
return OpStr(p); | |
// macro | |
if( t.l != null ) { | |
var pe = new List(); | |
for( p in t.l ) | |
pe.add(parseBlock(parseTokens(p))); | |
return OpMacro(p,pe); | |
} | |
// 'end' , 'else', 'elseif' can't be found here | |
if ( p.substr(0, 5) == "fill " ) { | |
p = p.substr(5,p.length - 5); | |
var name = StringTools.trim(p); | |
var efill = parseBlock(tokens); | |
var t = tokens.first(); | |
if (t == null || tokens.pop().p != "end") { | |
throw "Unclosed 'set'"; | |
} | |
return OpFill(name, efill); | |
} | |
if ( p.substr(0, 6) == "unset " ) { | |
p = p.substr(6, p.length - 6); | |
var name = StringTools.trim(p.substr(0, p.length)); | |
if (!~/^[a-zA-Z_][a-zA-Z_0-9]+$/.match(name)) throw "Invalid variableName " + name; | |
return OpUnset(name); | |
} | |
if ( p.substr(0, 4) == "set " ) { | |
p = p.substr(4, p.length - 4); | |
var equalSign = p.indexOf("="); | |
if (equalSign == -1) throw "Invalid set, format ::set varname=value::"; | |
var name = StringTools.trim(p.substr(0, equalSign)); | |
if (!~/^[a-zA-Z_][a-zA-Z_0-9]+$/.match(name)) throw "Invalid variableName " + name; | |
var eset = parseExpr(StringTools.trim(p.substr(equalSign+1))); | |
return OpSet(name, eset); | |
} | |
if( p.substr(0,3) == "if " ) { | |
p = p.substr(3,p.length - 3); | |
var e = parseExpr(p); | |
var eif = parseBlock(tokens); | |
var t = tokens.first(); | |
var eelse; | |
if( t == null ) | |
throw "Unclosed 'if'"; | |
if( t.p == "end" ) { | |
tokens.pop(); | |
eelse = null; | |
} else if( t.p == "else" ) { | |
tokens.pop(); | |
eelse = parseBlock(tokens); | |
t = tokens.pop(); | |
if( t == null || t.p != "end" ) | |
throw "Unclosed 'else'"; | |
} else { // elseif | |
t.p = t.p.substr(4,t.p.length - 4); | |
eelse = parse(tokens); | |
} | |
return OpIf(e,eif,eelse); | |
} | |
if( p.substr(0,8) == "foreach " ) { | |
p = p.substr(8, p.length - 8); | |
var indexSplit = p.indexOf(" "); | |
if (indexSplit == -1) throw "Invalid 'foreach', format ::foreach user users::"; | |
var name = p.substr(0, indexSplit); | |
p = p.substr(name.length + 1, p.length-name.length-1); | |
var e = parseExpr(p); | |
var efor = parseBlock(tokens); | |
var t = tokens.pop(); | |
if( t == null || t.p != "end" ) | |
throw "Unclosed 'foreach'"; | |
return OpForeach(name, e,efor); | |
} | |
if( expr_splitter.match(p) ) | |
return OpExpr(parseExpr(p)); | |
return OpVar(p); | |
} | |
function parseExpr( data : String ) { | |
var l = new List<ExprToken>(); | |
var expr = data; | |
while( expr_splitter.match(data) ) { | |
var p = expr_splitter.matchedPos(); | |
var k = p.pos + p.len; | |
if ( p.pos != 0) { | |
var d = StringTools.trim(data.substr(0, p.pos)); | |
if (d.length > 0) { | |
l.add( { p : data.substr(0, p.pos), s : true } ); | |
} | |
} | |
var p = expr_splitter.matched(0); | |
l.add({ p : p, s : p.indexOf('"') >= 0 }); | |
data = expr_splitter.matchedRight(); | |
} | |
if( data.length != 0 ) | |
l.add({ p : data, s : true }); | |
var e; | |
try { | |
e = makeExpr(l); | |
if( !l.isEmpty() ) | |
throw l.first().p; | |
} catch( s : String ) { | |
throw "Unexpected '"+s+"' in "+expr; | |
} | |
return function() { | |
try { | |
return e(); | |
} catch( exc : Dynamic ) { | |
throw "Error : "+Std.string(exc)+" in "+expr; | |
} | |
} | |
} | |
function makeConst( v : String ) : Void -> Dynamic { | |
expr_trim.match(v); | |
v = expr_trim.matched(1); | |
if( v.charCodeAt(0) == 34 ) { | |
var str = v.substr(1,v.length-2); | |
return function() return str; | |
} | |
if( expr_int.match(v) ) { | |
var i = Std.parseInt(v); | |
return function() { return i; }; | |
} | |
if( expr_float.match(v) ) { | |
var f = Std.parseFloat(v); | |
return function() { return f; }; | |
} | |
if (v == "true") { | |
return function () { return true; }; | |
} | |
if (v == "false") { | |
return function () { return false; }; | |
} | |
var me = this; | |
return function() { return me.resolve(v); }; | |
} | |
function makePath( e : Void -> Dynamic, l : List<ExprToken> ) { | |
var p = l.first(); | |
if( p == null || p.p != "." ) | |
return e; | |
l.pop(); | |
var field = l.pop(); | |
if( field == null || !field.s ) | |
throw field.p; | |
var f = field.p; | |
expr_trim.match(f); | |
f = expr_trim.matched(1); | |
return makePath(function() { return Reflect.field(e(),f); },l); | |
} | |
function makeExpr( l ) { | |
return makePath(makeExpr2(l),l); | |
} | |
function makeExpr2( l : List<ExprToken> ) : Void -> Dynamic { | |
var p = l.pop(); | |
if( p == null ) | |
throw "<eof>"; | |
if( p.s) | |
return makeConst(p.p); | |
switch( p.p ) { | |
case "(": | |
var e1 = makeExpr(l); | |
var p = l.pop(); | |
if ( p == null) { | |
throw p.p; | |
} | |
if( p.p == ")" ) | |
return e1; | |
var e2 = makeExpr(l); | |
var p2 = l.pop(); | |
if ( p2 == null) { | |
throw p2.p; | |
} | |
var e3 = null; | |
if (p.p == "?" && p2.p == ":") { | |
e3 = makeExpr(l); | |
var p3 = l.pop(); | |
if (p3 == null || p3.p != ")") { | |
throw p3.p; | |
} | |
} else if (p2.p != ")") { | |
throw p2.p; | |
} | |
return switch( p.p ) { | |
case "?": function() { return cast e1() ? e2() : e3(); }; | |
case "+": function() { return cast e1() + e2(); }; | |
case "-": function() { return cast e1() - e2(); }; | |
case "*": function() { return cast e1() * e2(); }; | |
case "/": function() { return cast e1() / e2(); }; | |
case ">": function() { return cast e1() > e2(); }; | |
case "<": function() { return cast e1() < e2(); }; | |
case ">=": function() { return cast e1() >= e2(); }; | |
case "<=": function() { return cast e1() <= e2(); }; | |
case "==": function() { return cast e1() == e2(); }; | |
case "!=": function() { return cast e1() != e2(); }; | |
case "&&": function() { return cast e1() && e2(); }; | |
case "||": function() { return cast e1() || e2(); }; | |
default: throw "Unknown operation "+p.p; | |
} | |
case "!": | |
var e = makeExpr(l); | |
return function() { | |
var v : Dynamic = e(); | |
return (v == null || v == false); | |
}; | |
case "-": | |
var e = makeExpr(l); | |
return function() { return -e(); }; | |
} | |
throw p.p; | |
} | |
function copyContext(context:Dynamic) { | |
var c = { }; | |
for (f in Reflect.fields(context)) { | |
Reflect.setField(c, f, Reflect.field(context, f)); | |
} | |
return c; | |
} | |
function run( e : TemplateExpr ) { | |
switch( e ) { | |
case OpFill(name, efill): | |
fills.set(name, efill); | |
case OpSet(name, eset): | |
var old2 = buf; | |
buf = new StringBuf(); | |
Reflect.setField(context, name, eset()); | |
buf = old2; | |
case OpUnset(name): | |
Reflect.deleteField(context, name); | |
case OpVar(v): | |
buf.add(Std.string(resolve(v))); | |
case OpExpr(e): | |
buf.add(Std.string(e())); | |
case OpIf(e,eif,eelse): | |
var v : Dynamic = e(); | |
if( v == null || v == false ) { | |
if( eelse != null ) run(eelse); | |
} else | |
run(eif); | |
case OpStr(str): | |
buf.add(str); | |
case OpBlock(l): | |
for( e in l ) | |
run(e); | |
case OpForeach(name, e,loop): | |
var v : Dynamic = e(); | |
var length:Int = Reflect.hasField(v, "length") ? v.length : null; | |
try { | |
if( v.hasNext == null ) { | |
var x : Dynamic = v.iterator(); | |
if( x.hasNext == null ) throw null; | |
v = x; | |
} | |
} catch( e : Dynamic ) { | |
throw "Cannot iter on " + v; | |
} | |
stack.push(context); | |
context = copyContext(context); | |
var repeat:Dynamic; | |
if (Reflect.hasField(context, "repeat")) { | |
repeat = Reflect.field(context, "repeat"); | |
} | |
else { | |
repeat = { }; | |
Reflect.setField(context, "repeat", repeat); | |
} | |
if (Reflect.hasField(repeat,name)) throw "foreach Context for variable " + name + " is already defined "; | |
var curCtx = { }; | |
var foreachCtx = { }; | |
Reflect.setField(repeat, name, foreachCtx); | |
var v : Iterator<Dynamic> = v; | |
var even = true; | |
var index = 0; | |
var first = true; | |
for ( ctx in v ) { | |
var last = (length != null) ? (index == length - 1) : null; | |
Reflect.setField(context, name, ctx); | |
var foreachCtx = { | |
first : first, | |
even : even, | |
odd : !even, | |
index : index, | |
last : last, | |
number : index + 1, | |
size : length | |
}; | |
Reflect.setField(repeat, name, foreachCtx); | |
run(loop); | |
even = !even; | |
index++; | |
first = false; | |
} | |
Reflect.deleteField(repeat, name); | |
context = stack.pop(); | |
case OpMacro(m,params): | |
var v : Dynamic = Reflect.field(macros,m); | |
var pl = new Array<Dynamic>(); | |
var old = buf; | |
pl.push(resolve); | |
for( p in params ) { | |
switch( p ) { | |
case OpVar(v): pl.push(resolve(v)); | |
default: | |
buf = new StringBuf(); | |
run(p); | |
pl.push(buf.toString()); | |
} | |
} | |
buf = old; | |
try { | |
buf.add(Std.string(Reflect.callMethod(macros,v,pl))); | |
} catch( e : Dynamic ) { | |
var plstr = try pl.join(",") catch( e : Dynamic ) "???"; | |
var msg = "Macro call "+m+"("+plstr+") failed ("+Std.string(e)+")"; | |
#if neko | |
neko.Lib.rethrow(msg); | |
#else | |
throw msg; | |
#end | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class HxTemplateTest extends TestCase | |
{ | |
public function new() | |
{ | |
super(); | |
} | |
public function testSetAndUnsetVariable() | |
{ | |
var txt = "::set test=1::" | |
+ "::unset test::" | |
+ "::(test==1)::"; | |
var tmp = new HxTemplate(txt); | |
var res = tmp.execute( { } ); | |
assertEquals("false", res); | |
} | |
public function testAccessArrayLength() | |
{ | |
var txt = "::if (categories.length > 0)::" | |
+ "length>0" | |
+ "::else::" | |
+ "length=0" | |
+ "::end::"; | |
var tmp = new HxTemplate(txt); | |
var categories = []; | |
var res = tmp.execute( { categories : categories } ); | |
assertEquals("length=0", res); | |
} | |
public function testAccessBlockVariableFromOuterContext_VariableShouldNotBeDefined() | |
{ | |
var txt = "::foreach cat categories::" | |
+ '::set style="myStyle"::' | |
+ "::style:: " | |
+ "::end::" | |
+ "::style::"; | |
var tmp = new HxTemplate(txt); | |
var categories = ["one", "two"]; | |
var res = tmp.execute( { categories : categories } ); | |
assertEquals("myStyle myStyle null", res); | |
} | |
public function testComplexForeachWithSetAndTernaryOperator () | |
{ | |
var txt = "::foreach cat categories::" | |
+ '::set style=(repeat.cat.odd?"odd":"even")::' | |
+ "::style::,::cat:: " | |
+ "::end::"; | |
var tmp = new HxTemplate(txt); | |
var categories = ["one", "two", "three"]; | |
var res = tmp.execute( { categories : categories } ); | |
assertEquals("even,one odd,two even,three ", res); | |
} | |
public function testSimpleIfAnd () | |
{ | |
var txt = "::if (a && b)::" | |
+ "true" | |
+ "::end::"; | |
var tmp = new HxTemplate(txt); | |
var res = tmp.execute( { a:true, b:true } ); | |
assertEquals("true", res); | |
} | |
public function testComplexIfAnd () | |
{ | |
var txt = "::if (((a == true) && (b == true)))::" | |
+ "true" | |
+ "::end::"; | |
var tmp = new HxTemplate(txt); | |
var res = tmp.execute( { a:true, b:true } ); | |
assertEquals("true", res); | |
} | |
public function testComplexIfAndWithTemplate () | |
{ | |
var txt = "::if (((a != null) && (b != null)))::" | |
+ "true" | |
+ "::end::"; | |
var tmp = new HxTemplate(txt); | |
var res = tmp.execute( { a:true, b:true } ); | |
assertEquals("true", res); | |
} | |
public function testComplexForeachWithSetAndTernaryOperator2 () | |
{ | |
var txt = "::foreach cat categories::" | |
+ '::set style=(repeat.cat.odd?"odd":"even")::' | |
+ "::cat:: " | |
+ "::end::"; | |
var tmp = new HxTemplate(txt); | |
var categories = ["one", "two", "three"]; | |
var res = tmp.execute( { categories : categories } ); | |
assertEquals("one two three ", res); | |
} | |
public function testIfAndSetVariable_TypeComplexExpr () | |
{ | |
var tmp = new HxTemplate("::if repeat.cat.odd::::set style=0::::else::::set style=1::::end::::style::"); | |
var repeat = { | |
cat : { | |
odd : true | |
} | |
} | |
var res = tmp.execute( { repeat: repeat } ); | |
assertEquals("0", res); | |
} | |
public function testFullQualifiedVariableInMacro () | |
{ | |
var tmp = new HxTemplate("$$printvar(repeat.cat.odd)"); | |
var repeat = { | |
cat : { | |
odd : true | |
} | |
} | |
var printvar = function (resolve:String->Void, what:Dynamic) | |
{ | |
return resolve(what); | |
}; | |
var res = tmp.execute( { repeat: repeat }, { printvar:printvar } ); | |
assertEquals("true", res); | |
} | |
public function testTernaryIfElseOperatorInSetVariableWithString () | |
{ | |
var tmp = new HxTemplate('::set style=(even?"even":"odd")::::style::'); | |
var res = tmp.execute( { even : true} ); | |
assertEquals("even", res); | |
} | |
public function testTernaryIfElseOperatorInSetVariable_WithBool () | |
{ | |
var tmp = new HxTemplate('::set style=(true?"even":"odd")::::style::'); | |
var res = tmp.execute({} ); | |
assertEquals("even", res); | |
} | |
public function testTernaryIfElseOperatorInSetVariable_WithBoolAndVariablesToResolve_UseLeftSideExpression () | |
{ | |
var tmp = new HxTemplate('::set style=(true?one:two)::::style::'); | |
var res = tmp.execute({one:"One", two:"Two"} ); | |
assertEquals("One", res); | |
} | |
public function testTernaryIfElseOperatorInSetVariable_WithBoolAndVariablesToResolve_UseRightSideExpression () | |
{ | |
var tmp = new HxTemplate('::set style=(false?one:two)::::style::'); | |
var res = tmp.execute({one:"One", two:"Two"} ); | |
assertEquals("Two", res); | |
} | |
public function testSetVariable_TypeInt () | |
{ | |
var tmp = new HxTemplate("::set foo=1::::foo::"); | |
var res = tmp.execute({}); | |
assertEquals("1", res); | |
} | |
public function testSetVariable_TypeString () | |
{ | |
var tmp = new HxTemplate("::set foo=\"bar\"::::foo::"); | |
var res = tmp.execute({}); | |
assertEquals("bar", res); | |
} | |
public function testSetVariable_FalseSyntax_ThrowsException () | |
{ | |
var f = function () { | |
var tmp = new HxTemplate("::set foo 1::::foo::"); | |
} | |
assertRaises(f, String); | |
} | |
public function testFillVariable () | |
{ | |
var tmp = new HxTemplate("::fill content::foo::end::::content::"); | |
var res = tmp.execute({}); | |
assertEquals("foo", res); | |
} | |
public function testFillVariable_Complex () | |
{ | |
var tmp = new HxTemplate("::fill content::::foo::::end::::content::"); | |
var res = tmp.execute({foo:"bar"}); | |
assertEquals("bar", res); | |
} | |
public function testForeach () | |
{ | |
var tmp = new HxTemplate("::foreach u users::::u.name::::end::"); | |
var res = tmp.execute({users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("foobar", res); | |
} | |
public function testForeachContext_even () | |
{ | |
var tmp = new HxTemplate("::foreach u users::::repeat.u.even::::end::"); | |
var res = tmp.execute({users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("truefalse", res); | |
} | |
public function testForeachContext_odd () | |
{ | |
var tmp = new HxTemplate("::foreach u users::::repeat.u.odd::::end::"); | |
var res = tmp.execute({users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("falsetrue", res); | |
} | |
public function testForeachContext_size () | |
{ | |
var tmp = new HxTemplate("::foreach u users::::repeat.u.size::::end::"); | |
var res = tmp.execute({users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("22", res); | |
} | |
public function testForeachContext_last () | |
{ | |
var tmp = new HxTemplate("::foreach u users::::repeat.u.last::::end::"); | |
var res = tmp.execute({users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("falsetrue", res); | |
} | |
public function testForeachContext_index () | |
{ | |
var tmp = new HxTemplate("::foreach u users::::repeat.u.index::::end::"); | |
var res = tmp.execute({users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("01", res); | |
} | |
public function testForeachContext_number () | |
{ | |
var tmp = new HxTemplate("::foreach u users::::repeat.u.number::::end::"); | |
var res = tmp.execute({users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("12", res); | |
} | |
public function testForeachOuterContext () | |
{ | |
var t = "::foreach u users::"; | |
t += "::bar::"; | |
t += "::end::"; | |
var tmp = new HxTemplate(t); | |
var res = tmp.execute({bar:"7", users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("77", res); | |
} | |
public function testIfForeachContextIsRemovedAfterForeach () | |
{ | |
var t = "::foreach u users::"; | |
t += "::end::"; | |
t += "::u.name::"; | |
var tmp = new HxTemplate(t); | |
var res = tmp.execute({bar:"7", users : [{name:"foo"}, {name:"bar"}]}); | |
assertEquals("null", res); | |
} | |
public function testNestedForeachContexts () | |
{ | |
var t = "::foreach u users::"; | |
t += "::foreach i names::"; | |
t += "::repeat.u.even::"; | |
t += ","; | |
t += "::repeat.i.even::"; | |
t += " "; | |
t += "::end::"; | |
t += "::end::"; | |
var tmp = new HxTemplate(t); | |
var res = tmp.execute( { bar:"7", users : [ { name:"foo" }, { name:"bar" } ], | |
names : ["jim", "tim"]}); | |
assertEquals("true,true true,false false,true false,false ", res); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment