Open al-the-x opened 10 years ago
Assigned to myself so I can fill out the examples.
Dead simple PHP templates:
<?php
function render($template, array $context){
extract($context);
eval($template);
}
function renderFile($filename, array $context){
render(
'?>' . file_get_contents($filename), $context);
}
render('?>This is David\'s <?=$foo?> as a string!<?', [
'foo' => 'bar',
]);
renderFile('template.phtml', [
'foo' => 'bat',
]);
Dead simple JavaScript templates:
var assert = require('assert');
function render(template, context){
assert(typeof template == 'string');
return template.replace(/{{(\S+)}}/g, function (match, prop) {
return context[prop];
});
}
var tpl = '{{var1}} {{var2}}';
console.log(render(tpl, {var1: 'something', var2: 'something else'}));
module.exports = render;
And then someone wants to to {{var1.propA}}
in a template... What else might break our assumptions?
Not sure how to run arbitrary code in a specified context in JavaScript (looks like you cannot pass a context object to eval
), but this will work in a broader range of cases:
instead of return context[prop];
we should call return eval('context.' + prop);
.
And this is what happens if I try analyzing the template first and returning a function which accepts a context as an argument:
function render(template){
assert(typeof template == 'string');
var interpolatedTokens = [],
stringTokens = [],
match,
re = /{{([^}}]*)}}/g,
lastIndex = 0;
while (match = re.exec(template)) {
stringTokens.push(template.slice(lastIndex, match.index));
interpolatedTokens.push(match[1]);
lastIndex = re.lastIndex;
}
stringTokens.push(template.slice(lastIndex));
return function (context) {
return stringTokens.reduce(function (previous, current, index) {
return previous + current +
(index === stringTokens.length - 1 ? '' : eval('context.' + interpolatedTokens[index]));
}, '');
};
}
var tpl = "{{date}} | {{date.getYear()}}-{{date.getMonth()}}-{{date.getDay()}}"
assert.equals("Jan 01, 2000, 00:00:00 | 2000-01-01", render(tpl)(new Date('2000-01-01'));
// I don't know what the actual format of `Date.toString()` will be...
var tpl = "{{foo}} | {{foo.bar}} | {{foo.baz()}}";
assert.equals("{ bar: 'bat' }", render(tpl)({ foo: { bar: 'bat', baz: function(){ } } }));
The concept of rendering and displaying templates is a tricky one, if you haven't encountered it before. I spent a lot of my early career in PHP, so I tend to take that for granted. Let's explore those concepts through some examples.