smarty4js
Version:
A JavaScript Template Engine Most Like Smarty
450 lines (422 loc) • 16.9 kB
JavaScript
/**
* @file render literal
* @author johnson [zoumiaojiang@gmaile.com]
*/
var utils = require('../utils');
module.exports = function (Renderer) {
utils.mixin(Renderer.prototype, {
/**
* render literal
* (top level than variable, include all of type in switch-case)
* @param {Object} node ast node
* @param {boolean} f is in echo env?
* @return {string} render result
*/
_getLiteral: function (node, f) {
var type = node.type;
var me = this;
switch (type) {
case 'VAR':
return me._getVar(node);
case 'NUM':
return me._getNum(node);
case 'BOOL':
return me._getBool(node);
case 'STR':
return me._getStr(node);
case 'PIPE':
return me._getPipe(node);
case 'OBJ':
return me._getObj();
case 'ARRAY':
return me._getArray(node, f);
case 'FUNC':
return me._getPhpFunc(node);
case 'AUTO': // ++/-- opration
return me._getVar(node.items, node.r + node.ops);
case 'GLOBAL':
return me._getGlobal(node);
default:
break;
}
},
/**
* render variables first time
* (because assign variable is diffrent with echo variable...)
* @param {Object} node ast node
* @return {string} render result
*/
_getOriginVar: function (node) {
var items = node.value;
var me = this;
var ret = '';
if (utils.isArray(items)) {
items.forEach(function (idNode, index) {
if (idNode.type === 'VAR' && idNode.opt === '.') {
ret += '.[' + me._getExpr(idNode) + ']';
}
else if (idNode.type === 'ECHO' && idNode.opt === '.') {
ret += '.[' + me._getExpr(idNode.value) + ']';
}
else if (idNode.opt) {
var opt = idNode.opt;
switch (opt) {
case '.':
ret += '.' + idNode.value;
break;
case '[':
// if (idNode.opt1 && idNode.opt1 === ']') {
if (idNode.type !== 'SEC') {
ret += '.[' + (idNode.value.type ? me._getExpr(idNode.value) : '0') + ']';
}
else { // if section name property...
var secVal = idNode.value;
if (secVal.length === 1) {
ret += '.[__sec__D'
+ me._getExpr(secVal[0]).replace(/"/g, '')
+ '__Dindex]';
}
else {
ret += '.[__sec__D' + me._getExpr(secVal[0]).replace(/"/g, '')
+ '__D' + me._getExpr(secVal[1]).replace(/"/g, '') + ']';
}
}
// }
break;
case '->':
ret += '.[';
if (idNode.type === 'ID') {
ret += '"' + idNode.value + '"';
}
else if (idNode.type === 'E' && idNode.value.type !== 'ID') {
ret += me._getExpr(idNode.value).replace(/\./g, '__D');
}
ret += ']';
break;
case '@':
ret = me.ctxScope[me.ctxScope.length - 1] + '____D' + idNode.value + '@';
break;
}
}
else {
if (index === 0) {
if (idNode.type === 'ECHO') {
ret += '__SCOPE.[' + me._getExpr(idNode.value) + ']';
}
else {
ret += '__SCOPE.["' + idNode.value + '"]';
}
}
else {
if (idNode.type === 'ECHO') {
ret = ret.replace(/\]$/, '');
ret += '+' + me._getExpr(idNode.value) + ']';
}
else if (idNode.type === 'ID') {
ret = ret.replace(/\]$/, '');
ret += '+' + me._getStr(idNode) + ']';
}
}
}
});
}
else if (utils.isObject(items)) {
var val = items.value;
if (me.includeAssign[val]) {
ret = '__fn__' + val + '()';
}
else if (me.capAssign[val] === true) {
ret = '__h+=__cap.' + val + '()';
}
else {
ret += '__SCOPE.' + val;
}
}
return ret;
},
/**
* render variables
* @param {Object} node ast node
* @param {string} opTag inner operator of variable
* @return {string} variable result with scope
*/
_getVar: function (node, opTag) {
var me = this;
var ret = me._getOriginVar(node);
if (opTag) {
var r = opTag.charAt(0);
opTag = opTag.slice(1);
}
/**
* split variables's items, and make sure variable link is right
* @param {array<string>} arr pices of varible
* @return {string} result after add scope
*/
function aw(arr) {
var awret = '';
if (arr.length === 2) {
awret = arr[1].trim() + ((arr[0].indexOf('[') > -1) ? '' : '__D') + arr[0].trim();
}
else {
var s = arr.shift();
awret = '__v(' + aw(arr) + ',{})' + ((s.indexOf('[') > -1) ? '' : '__D') + s.trim();
}
return awret;
}
/**
* add all scope for current variable by scope stack
* @param {string} str origin code
* @return {string} result code
*/
function addScope(str) {
var tmps = '__v(' + ((opTag && r === 'l') ? opTag : '');
// from most close scope to root scope
me.ctxScope.reverse().forEach(function (scope) {
var dots = str.replace(/__SCOPE/g, scope).split(/\./);
tmps += aw(dots.reverse()) + ((opTag && r === 'r') ? opTag : '') + ',';
});
me.ctxScope.reverse();
return tmps.slice(0, tmps.length - 1) + ')';
}
if (ret.indexOf('__fn__') === -1 && ret.indexOf('__cap__') === -1) {
ret = ret.indexOf('@') > -1 ? ret.slice(0, ret.length - 1) : addScope(ret);
}
return ret;
},
/**
* render php function (translate php function to Js function)
* @param {Object} node ast node
* @return {string} render result
*/
_getPhpFunc: function (node) {
var pstr = '';
var me = this;
var ps = node.params;
ps && ps.forEach(function (p) {
pstr += me._getExpr(p) + (p !== ps[ps.length - 1] ? ',' : '');
});
return '__f["' + node.name + '"](' + pstr + ')';
},
/**
* render text content not in smarty tags
* @param {Object} node ast node
* @return {string} render result
*/
_getText: function (node) {
return utils.escapeString(node.value);
},
/**
* render numble
* @param {Object} node ast node
* @return {string} render result
*/
_getNum: function (node) {
return parseFloat(node.value, 10);
},
/**
* render bool
* @param {Object} node ast node
* @return {string} render result
*/
_getBool: function (node) {
return node.value;
},
/**
* render string
* @param {Object} node ast node
* @return {string} render result
*/
_getStr: function (node) {
var me = this;
var str = node.value;
var ld = me.engine.conf.left_delimiter;
var rd = me.engine.conf.right_delimiter;
var envRe = new RegExp(utils.regEscape(ld) + '.*?' + utils.regEscape(rd), 'g');
var ret = '';
// adapta tag series if there is some smarty tag in string
str = str.replace(/(\`.*?\`)/g, function (item) {
return ld + item.slice(1, item.length - 1) + rd;
}).replace(new RegExp('\\b\\$[_\\d\\w]+\\b(?!' + utils.regEscape(rd) + ')'), function (item) {
return ld + item + rd;
});
// smarty tag in string, open a new compile environment
if (envRe.test(str)) {
var Class = me.eClass;
var s = new Class(me.engine.conf);
var strPipe = s.compile(str).getJsTpl(2);
ret = strPipe;
}
else {
ret = utils.escapeString(str);
}
return ret;
},
/**
* render variables filter function
* @param {Object} node ast node
* @return {string} render result
*/
_getPipe: function (node) {
return '__f["' + node.func + '"]';
},
/**
* render smarty params of variables filter function
* @param {Array} arr params ast data
* @return {string} render result [like `xx,xx`]
*/
_getPipeParams: function (arr) {
var me = this;
var ret = '';
if (arr.length > 0) {
ret = ',';
arr.forEach(function (node) {
ret += me._getExpr(node) + ',';
});
ret = ret.substr(0, ret.length - 1);
}
return ret;
},
/**
* render JavaScript Object
* (but is array in smarty -- PHP)
* @param {Object} obj obj ast data
* @return {string} render result
*/
_getObj: function (obj) {
var me = this;
var ret = '{';
if (utils.isArray(obj)) {
obj.forEach(function (node) {
if (node.type === 'STR') {
ret += '"0":"' + node.value + '",';
}
else if (node.key && node.key.type === 'STR') {
ret += me._getLiteral(node.key) + ':' + me._getExpr(node.value) + ',';
}
});
}
else if (utils.isObject(obj)) {
var key = me._getLiteral(obj.key);
var value = me._getExpr(obj.value);
ret += 'key:' + key + ',';
ret += key + ':' + value + ',';
}
return ret.slice(0, ret.length - 1) + '}';
},
/**
* render real JavaScript array
* @param {array<Object>} arr array ast data
* @return {string} render result
*/
_getArrayReal: function (arr) {
var me = this;
var ret = '[';
arr.forEach(function (item, index) {
ret += me._getExpr(item) + ',';
});
return ret.slice(0, ret.length - 1) + ']';
},
/**
* render array
* (php array not like JavaScript, include object && array)
* @param {Object} node ast node
* @param {boolean} f is in echo environment?
* @return {string} render result
*/
_getArray: function (node, f) {
var ret;
var me = this;
var items = node.items;
if (utils.isObject(items)) {
ret = me._getObj([items]);
}
if (utils.isArray(items)) {
if (items.length === 0) {
return '{}';
}
var objf = items.every(function (nod) {
return nod.type === 'OBJ';
});
var arrf = items.every(function (nod) {
return nod.type !== 'OBJ';
});
if (objf && !f) {
ret = me._getObj(items);
}
else if (arrf && !f) {
ret = me._getArrayReal(items);
}
else {
ret = '{';
var ind = 0;
items.forEach(function (nod, index) {
if (nod.type === 'OBJ') {
ret += me._getStr(nod.key) + ': ' + me._getExpr(nod.value, true) + ',';
ind--;
}
else {
ret += '"__a' + ind + '":' + me._getExpr(nod, true) + ',';
}
ind++;
});
ret = ret.slice(0, ret.length - 1) + '}';
}
}
else {
ret = '{}';
}
return ret;
},
/**
* render global variables
* @param {Object} node ast node
* @return {string} render result
*/
_getGlobal: function (node) {
// exclude '$smarty.const, $smarty.template, $smarty.current_dir, $smarty.block, $smarty.version'
var ret = '';
switch (node.value) {
case '$smarty.now':
return 'Date.now()';
case '$smarty.ldelim':
return 'smarty__Dldelim';
case '$smarty.rdelim':
return 'smarty__Drdelim';
default:
var val = node.value;
if (val.indexOf('$smarty.foreach.') === 0) {
var ga = val.split('.');
var sepMap = {
'first': 1,
'last': 1,
'index': 1,
'key': 1,
'index_prev': 1,
'index_next': 1,
'total': 1,
'show': 1
};
if (ga.length === 3 && sepMap[ga[2]] === 1) {
ret = 'smarty__Dforeach__D' + val.slice(16).replace(/\./, '__D');
}
else if (ga.length === 4 && sepMap[ga[3]] === 1) {
ret = '__for__D' + val.slice(16).replace(/\./, '__D');
}
}
else if (val.indexOf('$smarty.capture.') === 0) {
ret = 'smarty__Dcapture__D' + val.slice(16).replace(/\./, '__D');
}
else if (val.indexOf('$smarty.section.') === 0) {
ret = '__sec__D' + val.slice(16).replace(/\./, '__D');
}
else {
// about `$smarty.block`, With `append or prepend` instead of it
// other global variables don't support here, because they are no use in javascript
// so... helpless... no result if you use others global variables
ret = '""';
}
return ret;
}
}
});
};