x-http-client
Version:
An http client to make it easier to send requests (including JSONP requests) to the server.
236 lines (208 loc) • 6.07 kB
JavaScript
var T_STR = 1;
var T_EXP = 2;
/**
* A simple template function
*
* @example
* // Rreturns '/post/1'
* template('/post/{ post.id }', { post: { id: 1 } })
*
* @param {string} template The template text.
* @param {Object.<string, *>} data The data object.
* @param {TemplateOptions} options The template options.
* @returns {string} Returns the compiled text.
*/
function template(template, data, options) {
var templ = template + '';
var model = data || {};
var opts = options || {};
var start = opts.start || '{';
var end = opts.end || '}';
var encode = opts.encode || encodeURIComponent;
var ast = compile(templ, start, end, function (expr) {
var first = expr.charAt(0);
var second = expr.charAt(1);
var raw = false;
if (first === '-' && second === ' ') {
raw = true;
expr = expr.substr(2);
}
expr = expr.replace(/^\s+|\s+$/g, '');
return {
type: T_EXP,
text: expr,
raw: raw
};
});
var render = buildRenderFunction(ast, encode);
try {
return render(model);
} catch (e) {
throw new Error('Compile Error:\n\n' + template + '\n\n' + e.message);
}
}
/**
* Build render function.
*
* @param {Object.<string, *>[]} ast The abstract syntax tree.
* @param {(str: string) => string} encode The function to encode the string.
* @returns {(model: Object.<string, *>) => string} Returns a function that compile data to string.
*/
function buildRenderFunction(ast, encode) {
var fn;
var line;
var lines = [];
var i = 0;
var l = ast.length;
lines.push('var __o=[]');
lines.push('with(__s){');
for ( ; i < l; ++i) {
line = ast[i];
if (line.type === T_STR) {
lines.push('__o.push(' + JSON.stringify(line.text) + ')');
} else if (line.type === T_EXP && line.text) {
if (line.raw) {
lines.push('__o.push(' + line.text + ')');
} else {
lines.push('__o.push(__e(' + line.text + '))');
}
}
}
lines.push('}');
lines.push('return __o.join("")');
fn = new Function('__s', '__e', lines.join('\n'));
return function (model) {
return fn(model, function (val) {
return (val === null || val === undefined) ? '' : encode(val + '');
});
};
}
/**
* Compile the template.
*
* @param {string} template The template to compile.
* @param {string} startTag The start tag.
* @param {string} endTag The end tag.
* @param {(expr: string) => string} parseExpr The function to parse the expression.
* @returns {string} Return the compiled string.
*/
function compile(template, startTag, endTag, parseExpr) {
var i = 0;
var l = template.length;
var sl = startTag.length;
var el = endTag.length;
var ast = [];
var strbuffer = [];
var exprbuffer = [];
var type = T_STR;
/**
* Get the char in `template` at the given position.
*
* @param {numner} index The index to read.
* @returns {string} Returns the char.
*/
var charAt = function (index) {
return template.charAt(index);
};
/**
* Escape the tag.
*
* @param {string} tag The tag to escape.
* @param {string[]} buffer The buffer to put the char.
*/
var esc = function (tag, buffer) {
var c;
var m = tag.length;
var s = '\\';
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (1) {
c = charAt(i);
if (c === s) {
c = charAt(++i);
if (c === s) {
buffer.push(s);
++i;
} else if (isWord(tag)) {
buffer.push(tag);
i += m;
} else {
buffer.push(s);
break;
}
} else {
break;
}
}
};
/**
* Check whether the next input is the word.
*
* @param {string} word The word to check.
* @returns {number} Returns `1` on yes, otherwise `0` is returned.
*/
var isWord = function (word) {
var k = 0;
var j = i;
var m = word.length;
while (k < m && j < l) {
if (word.charAt(k) !== charAt(j)) return 0;
++k;
++j;
}
return 1;
};
/**
* Flush the str to the ast and reset the str buffer.
*/
var flushStr = function () {
if (strbuffer.length) {
ast.push({
type: T_STR,
text: strbuffer.join('')
});
strbuffer = [];
}
};
/**
* Flush the expr to the ast and reset the expr buffer.
*/
var flushExpr = function () {
flushStr();
ast.push(parseExpr(exprbuffer.join('')));
exprbuffer = [];
};
while (i < l) {
if (type === T_STR) {
esc(startTag, strbuffer);
if (isWord(startTag)) {
type = T_EXP;
i += sl;
} else {
strbuffer.push(charAt(i));
++i;
}
} else if (type === T_EXP) {
esc(endTag, exprbuffer);
if (isWord(endTag)) {
type = T_STR;
i += el;
flushExpr();
} else {
exprbuffer.push(charAt(i));
++i;
}
}
}
if (type === T_EXP) {
throw new Error('Unexpected end');
}
flushStr();
return ast;
}
/**
* @typedef {Object.<string, *>} TemplateOptions
* @property {string} [start] The start tag of the template, default is `{`.
* @property {string} [end] The end tag of the template, default is `}`.
* @property {(value: string) => string} [encode] The function to encode the string, default is `encodeURIComponent`.
*/
module.exports = template;