datalib
Version:
JavaScript utilites for loading, summarizing and working with data.
241 lines (215 loc) • 6.48 kB
JavaScript
var util = require('./util'),
format = require('./format');
var context = {
formats: [],
format_map: {},
truncate: util.truncate,
pad: util.pad,
day: format.day,
month: format.month,
quarter: format.quarter,
utcQuarter: format.utcQuarter
};
function template(text) {
var src = source(text, 'd');
src = 'var __t; return ' + src + ';';
/* jshint evil: true */
return (new Function('d', src)).bind(context);
}
template.source = source;
template.context = context;
template.format = get_format;
module.exports = template;
// Clear cache of format objects.
// This can *break* prior template functions, so invoke with care!
template.clearFormatCache = function() {
context.formats = [];
context.format_map = {};
};
// Generate property access code for use within template source.
// object: the name of the object (variable) containing template data
// property: the property access string, verbatim from template tag
template.property = function(object, property) {
var src = util.field(property).map(util.str).join('][');
return object + '[' + src + ']';
};
// Generate source code for a template function.
// text: the template text
// variable: the name of the data object variable ('obj' by default)
// properties: optional hash for collecting all accessed properties
function source(text, variable, properties) {
variable = variable || 'obj';
var index = 0;
var src = '\'';
var regex = template_re;
// Compile the template source, escaping string literals appropriately.
text.replace(regex, function(match, interpolate, offset) {
src += text
.slice(index, offset)
.replace(template_escaper, template_escapeChar);
index = offset + match.length;
if (interpolate) {
src += '\'\n+((__t=(' +
template_var(interpolate, variable, properties) +
'))==null?\'\':__t)+\n\'';
}
// Adobe VMs need the match returned to produce the correct offest.
return match;
});
return src + '\'';
}
function template_var(text, variable, properties) {
var filters = text.match(filter_re);
var prop = filters.shift().trim();
var stringCast = true;
function strcall(fn) {
fn = fn || '';
if (stringCast) {
stringCast = false;
src = 'String(' + src + ')' + fn;
} else {
src += fn;
}
return src;
}
function date() {
return '(typeof ' + src + '==="number"?new Date('+src+'):'+src+')';
}
function formatter(type) {
var pattern = args[0];
if ((pattern[0] === '\'' && pattern[pattern.length-1] === '\'') ||
(pattern[0] === '"' && pattern[pattern.length-1] === '"')) {
pattern = pattern.slice(1, -1);
} else {
throw Error('Format pattern must be quoted: ' + pattern);
}
a = template_format(pattern, type);
stringCast = false;
var arg = type === 'number' ? src : date();
src = 'this.formats['+a+']('+arg+')';
}
if (properties) properties[prop] = 1;
var src = template.property(variable, prop);
for (var i=0; i<filters.length; ++i) {
var f = filters[i], args = null, pidx, a, b;
if ((pidx=f.indexOf(':')) > 0) {
f = f.slice(0, pidx);
args = filters[i].slice(pidx+1)
.match(args_re)
.map(function(s) { return s.trim(); });
}
f = f.trim();
switch (f) {
case 'length':
strcall('.length');
break;
case 'lower':
strcall('.toLowerCase()');
break;
case 'upper':
strcall('.toUpperCase()');
break;
case 'lower-locale':
strcall('.toLocaleLowerCase()');
break;
case 'upper-locale':
strcall('.toLocaleUpperCase()');
break;
case 'trim':
strcall('.trim()');
break;
case 'left':
a = util.number(args[0]);
strcall('.slice(0,' + a + ')');
break;
case 'right':
a = util.number(args[0]);
strcall('.slice(-' + a +')');
break;
case 'mid':
a = util.number(args[0]);
b = a + util.number(args[1]);
strcall('.slice(+'+a+','+b+')');
break;
case 'slice':
a = util.number(args[0]);
strcall('.slice('+ a +
(args.length > 1 ? ',' + util.number(args[1]) : '') +
')');
break;
case 'truncate':
a = util.number(args[0]);
b = args[1];
b = (b!=='left' && b!=='middle' && b!=='center') ? 'right' : b;
src = 'this.truncate(' + strcall() + ',' + a + ',\'' + b + '\')';
break;
case 'pad':
a = util.number(args[0]);
b = args[1];
b = (b!=='left' && b!=='middle' && b!=='center') ? 'right' : b;
src = 'this.pad(' + strcall() + ',' + a + ',\'' + b + '\')';
break;
case 'number':
formatter('number');
break;
case 'time':
formatter('time');
break;
case 'time-utc':
formatter('utc');
break;
case 'month':
src = 'this.month(' + src + ')';
break;
case 'month-abbrev':
src = 'this.month(' + src + ',true)';
break;
case 'day':
src = 'this.day(' + src + ')';
break;
case 'day-abbrev':
src = 'this.day(' + src + ',true)';
break;
case 'quarter':
src = 'this.quarter(' + src + ')';
break;
case 'quarter-utc':
src = 'this.utcQuarter(' + src + ')';
break;
default:
throw Error('Unrecognized template filter: ' + f);
}
}
return src;
}
var template_re = /\{\{(.+?)\}\}|$/g,
filter_re = /(?:"[^"]*"|\'[^\']*\'|[^\|"]+|[^\|\']+)+/g,
args_re = /(?:"[^"]*"|\'[^\']*\'|[^,"]+|[^,\']+)+/g;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var template_escapes = {
'\'': '\'',
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var template_escaper = /\\|'|\r|\n|\u2028|\u2029/g;
function template_escapeChar(match) {
return '\\' + template_escapes[match];
}
function template_format(pattern, type) {
var key = type + ':' + pattern;
if (context.format_map[key] == null) {
var f = format[type](pattern);
var i = context.formats.length;
context.formats.push(f);
context.format_map[key] = i;
return i;
}
return context.format_map[key];
}
function get_format(pattern, type) {
return context.formats[template_format(pattern, type)];
}