2

I found a short snippet for string-interpolation on GitHub and shortened it to this:

var interpolate = function (tmpl, data) {
    return new Function('data', 'with(data){return \'' + 
        tmpl.replace(/{{\s*(.+?)\s*}}/g, '\'+($1)+\'') + '\';}')(data);
};

It works fine for this:

interpolate('Hello {{user.name}}!', {user: {name: 'Admin'}});
// Hello Admin!

But there are two results I don´t like:

interpolate('Hello {{user.firstname}}!', {user: {name: 'Admin'}});
// Hello undefined!

and this one:

interpolate('Hello {{user.name}}!', {userinfo: {name: 'Admin'}});
// Uncaught ReferenceError: user is not defined(…)

Is there a way to modify the snipped to use a default (empty string) if the key does not exist?

UPDATE Now I found a solution for all my requirements:

function interpolate(tmpl, data, defaultValue, reg) {       
    return tmpl.replace(reg || /{{([^{}]*)}}/g, function(a, b) {
        try {
            return new Function('data', ['with(data){return ',b,' || \'',(defaultValue || ''),'\';}'].join(''))(data);
        }
        catch(e) {
            return defaultValue || '';                  
        }
    });
}

interpolate('Existing value: {{user.name}} - global value: {{Date.now()}} - undefined: {{user.age}} - exception: {{dat.haha}}', {user: {name: 'Admin'}}, '?');
// "Existing value: Admin - global value: 1441555682168 - undefined: ? - exception: ?"

Suggestions are wellcome!

Martin
  • 1,283
  • 2
  • 14
  • 28

1 Answers1

1

The following works, assuming you have only one placeholder in the string:

var interpolate = function(s, data) {
  var RE= /{{\s*(.+?)\s*}}/,
      sp= s.match(RE)[1].split('.');  //example: ['user','name']

  sp.forEach(function(key) {
    data= data[key] || '';   //walk the object, defaulting to null string if undefined
  });
  
  return s.replace(RE, data);
};

console.log(interpolate('Hello {{user.name}}!', {user: {name: 'Admin'}}));

console.log(interpolate('Hello {{user.firstname}}!', {user: {name: 'Admin'}}));

console.log(interpolate('Hello {{user.name}}!', {userinfo: {name: 'Admin'}}));

Unfortunately expressions like {{Date.now()}} dont´t work with this solution

In that case, you do need to use new Function() or eval().

You can add short-circuit evaluation (|| '') to the function declaration string to avoid undefined object properties, along with try...catch to avoid uncaught reference errors.

In the following Snippet, the function string for the second example ends up looking something like this:

with(data) try {
  return 'Hello '+(user.firstname || "")+'!';
} catch(e) {
  return 'Hello !'
}

Snippet:

var interpolate = function (tmpl, data) {
  var f= 'with(data) try{return \'' + 
           tmpl.replace(/{{\s*(.+?)\s*}}/g, ('\'+($1 || "")+\'') ) + '\';}'+
         'catch(e) {return \'' +
           tmpl.replace(/{{\s*(.+?)\s*}}/g, '') + "'"+
         '}';
  return new Function('data', f)(data);
};

console.log(interpolate('Hello {{user.name}}, it is currently {{Date()}}!', {user: {name: 'Admin'}}));

console.log(interpolate('Hello {{user.firstname}}!', {user: {name: 'Admin'}}));

console.log(interpolate('Hello {{user.name}}!', {userinfo: {name: 'Admin'}}));
Rick Hitchcock
  • 35,202
  • 5
  • 48
  • 79
  • 1
    Thank you, this is a correct anwer to my question. Unfortunately expressions like {{Date.now()}} dont´t work with this solution, but this wasn´t part of my question, so I accepted your answer. – Martin Sep 05 '15 at 21:39
  • Ha, as soon as I updated my answer, I see that you found your own answer to the problem. Good job. – Rick Hitchcock Sep 06 '15 at 19:47