UNPKG

datalib

Version:

JavaScript utilites for loading, summarizing and working with data.

241 lines (215 loc) 6.48 kB
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)]; }