Convert string to pattern string

Is it possible to create a template string as a regular string

let a="b:${b}"; 

a then convert it to a pattern string

 let b=10; console.log(a.template());//b:10 

without eval , new Function and other ways to generate dynamic code?

+71
javascript eval ecmascript-6
Mar 21 '15 at 11:34
source share
15 answers

Since your template string should dynamically refer to the variable b (at run time), so the answer is NO, it is impossible to do without generating dynamic code.

But with eval it's pretty simple:

 let tpl = eval('`'+a+'`'); 
+34
Mar 21 '15 at 12:36
source share

What do you ask for here:

 //non working code quoted from the question let b=10; console.log(a.template());//b:10 

exactly equivalent (in terms of power and, er, security) to eval : the ability to take a line containing code and execute this code; and the ability of the executable to see local variables in the caller’s environment.

In JS, there is no way for a function to see local variables in its calling object, unless that function is eval() . Even Function() cannot do this.




When you hear something called “template strings” that comes with JavaScript, it’s natural to assume that it is a built-in template library, such as Mustache. This is not true. This is basically just string interpolation and multi-line strings for JS. I think this will be a common misconception for a while. :(

+24
Mar 23 '15 at 1:22
source share

No, there is no way to do this without generating dynamic code.

However, I created a function that turns a regular string into a function that can be provided with a map of values ​​using internal template strings.

Generate String Gist Template

 /** * Produces a function which uses template strings to do simple interpolation from objects. * * Usage: * var makeMeKing = generateTemplateString('${name} is now the king of ${country}!'); * * console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'})); * // Logs 'Bryan is now the king of Scotland!' */ var generateTemplateString = (function(){ var cache = {}; function generateTemplate(template){ var fn = cache[template]; if (!fn){ // Replace ${expressions} (etc) with ${map.expressions}. var sanitized = template .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){ return `\$\{map.${match.trim()}\}`; }) // Afterwards, replace anything that not ${map.expressions}' (etc) with a blank string. .replace(/(\$\{(?!map\.)[^}]+\})/g, ''); fn = Function('map', `return \`${sanitized}\``); } return fn; } return generateTemplate; })(); 

Using:

 var kingMaker = generateTemplateString('${name} is king!'); console.log(kingMaker({name: 'Bryan'})); // Logs 'Bryan is king!' to the console. 

Hope this helps someone. If you find a problem with the code, please be kind enough to update the Gist.

+16
Aug 13 '15 at 23:15
source share

In my project, I created something like this with ES6:

 String.prototype.interpolate = function(params) { const names = _.keys(params); const vals = _.values(params); return new Function(...names, `return \`${this}\`;`)(...vals); } const template = 'Example text: ${text}'; const result = template.interpolate({ text: 'Foo Boo' }); console.log(result); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script> 
+6
Dec 07 '16 at 11:07
source share

TL; DR: https://jsfiddle.net/w3jx07vt/

Everyone seems to be worried about accessing variables, why not just pass them? I am sure it will not be too difficult to get the variable context in the caller and pass it on. Use https://stackoverflow.com/a/3126902/javascript/questions/4053/... to get props from obj. I can't check you right now, but it should work.

 function renderString(str,obj){ return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)}) } 

Tested. Here is the complete code.

 function index(obj,is,value) { if (typeof is == 'string') is=is.split('.'); if (is.length==1 && value!==undefined) return obj[is[0]] = value; else if (is.length==0) return obj; else return index(obj[is[0]],is.slice(1), value); } function renderString(str,obj){ return str.replace(/\$\{.+?\}/g,(match)=>{return index(obj,match)}) } renderString('abc${a}asdas',{a:23,b:44}) //abc23asdas renderString('abc${ac}asdas',{a:{c:22,d:55},b:44}) //abc22asdas 
+6
Jan 09 '17 at 2:58
source share

The problem is to have a function that has access to the variables of its caller. This is why we see that the direct eval used to process templates. A possible solution would be to create a function with formal parameters called dictionary properties, and call it with the corresponding values ​​in the same order. An alternative way would be to have something simple:

 var name = "John Smith"; var message = "Hello, my name is ${name}"; console.log(new Function('return `' + message + '`;')()); 

And for those who use the Babel compiler, we need to create a closure that remembers the environment in which it was created:

 console.log(new Function('name', 'return `' + message + '`;')(name)); 
+4
Nov 28 '15 at 11:10
source share

You can use a string prototype like

 String.prototype.toTemplate=function(){ return eval('`'+this+'`'); } //... var a="b:${b}"; var b=10; console.log(a.toTemplate());//b:10 

But the answer to the original question is in no way.

+3
Jul 10 '16 at 7:53 on
source share

I cannot comment on existing answers at this time, so I cannot directly comment on Bryan Raynor's excellent answer. So this answer will update its answer with a slight correction.

In short, its function cannot actually cache the created function, so it will always recreate, regardless of whether it has seen the template before. Here is the corrected code:

  /** * Produces a function which uses template strings to do simple interpolation from objects. * * Usage: * var makeMeKing = generateTemplateString('${name} is now the king of ${country}!'); * * console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'})); * // Logs 'Bryan is now the king of Scotland!' */ var generateTemplateString = (function(){ var cache = {}; function generateTemplate(template){ var fn = cache[template]; if (!fn){ // Replace ${expressions} (etc) with ${map.expressions}. var sanitized = template .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){ return `\$\{map.${match.trim()}\}`; }) // Afterwards, replace anything that not ${map.expressions}' (etc) with a blank string. .replace(/(\$\{(?!map\.)[^}]+\})/g, ''); fn = cache[template] = Function('map', `return \`${sanitized}\``); } return fn; }; return generateTemplate; })(); 
+3
Dec 04 '16 at 16:33
source share

I need this method with Internet Explorer support. It turned out that reverse ticks are not supported even by IE11. Also; using eval or the equivalent Function doesn't feel good.

For those who notify; I also use backticks, but they are removed by compilers like babel. The methods suggested by others depend on them at runtime. As said earlier; this is a problem in IE11 and below.

So here is what I came up with:

 function get(path, obj, fb = `$\{${path}}`) { return path.split('.').reduce((res, key) => res[key] || fb, obj); } function parseTpl(template, map, fallback) { return template.replace(/\$\{.+?}/g, (match) => { const path = match.substr(2, match.length - 3).trim(); return get(path, map, fallback); }); } 

Output Example:

 const data = { person: { name: 'John', age: 18 } }; parseTpl('Hi ${person.name} (${person.age})', data); // output: Hi John (18) parseTpl('Hello ${person.name} from ${person.city}', data); // output: Hello John from ${person.city} parseTpl('Hello ${person.name} from ${person.city}', data, '-'); // output: Hello John from - 
+2
Jan 17 '17 at 15:01
source share

@Mateusz Moska, the solution works fine, but when I used it in React Native (build mode), it throws an error: Invalid character `` '' , although it works when I run it in debug mode.

So, I wrote down my own solution using regex.

 String.prototype.interpolate = function(params) { let template = this for (let key in params) { template = template.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), params[key]) } return template } const template = 'Example text: ${text}', result = template.interpolate({ text: 'Foo Boo' }) console.log(result) 

Demo: https://es6console.com/j31pqx1p/

NOTE. . Since I don’t know the root cause of the problem, I raised my ticket in reaction-native repo mode, https://github.com/facebook/react-native/issues/14107 , so that as soon as they can fix / direct me the same way :)

+2
May 23 '17 at 15:31
source share

Still dynamic, but seems more controllable than just using bare eval:

 const vm = require('vm') const moment = require('moment') let template = '### ${context.hours_worked[0].value} \n Hours worked \n #### ${Math.abs(context.hours_worked_avg_diff[0].value)}% ${fns.gt0(context.hours_worked_avg_diff[0].value, "more", "less")} than usual on ${fns.getDOW(new Date())}' let context = { hours_worked:[{value:10}], hours_worked_avg_diff:[{value:10}], } function getDOW(now) { return moment(now).locale('es').format('dddd') } function gt0(_in, tVal, fVal) { return _in >0 ? tVal: fVal } function templateIt(context, template) { const script = new vm.Script('`'+template+'`') return script.runInNewContext({context, fns:{getDOW, gt0 }}) } console.log(templateIt(context, template)) 

https://repl.it/IdVt/3

+2
Jun 09 '17 at 16:01
source share

Like Daniel K's answer (and s.meijer gist ), but more readable:

 const regex = /\${[^{]+}/g; export default function interpolate(template, variables, fallback) { return template.replace(regex, (match) => { const path = match.slice(2, -1).trim(); return getObjPath(path, variables, fallback); }); } //get the specified property or nested property of an object const getObjPath = (path, obj, fallback = '') => { return path.split('.').reduce((res, key) => res[key] || fallback, obj); } 

Note. This slightly improves the original s.meijer, as it will not match things like ${foo{bar} (the regular expression only allows curly braces inside ${ and } ).




UPDATE: I was asked to use an example, so here you are:

 const replacements = { name: 'Bob', age: 37 } interpolate('My name is ${name}, and I am ${age}.`, replacements) 
+1
Nov 17 '17 at 19:38 on
source share

This solution works without ES6:

 function render(template, opts) { return new Function( 'return new Function (' + Object.keys(opts).reduce((args, arg) => args += '\'' + arg + '\',', '') + '\'return `' + template.replace(/(^|[^\\])'/g, '$1\\\'') + '`;\'' + ').apply(null, ' + JSON.stringify(Object.keys(opts).reduce((vals, key) => vals.push(opts[key]) && vals, [])) + ');' )(); } render("hello ${ name }", {name:'mo'}); // "hello mo" 

Note: the Function constructor is always created in the global scope, which could potentially lead to global variables being overwritten by the template, for example. render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});

0
Jan 06 '17 at 22:34 on
source share

I liked the answer of s.meijer and wrote my own version based on it:

 function parseTemplate(template, map, fallback) { return template.replace(/\$\{[^}]+\}/g, (match) => match .slice(2, -1) .trim() .split(".") .reduce( (searchObject, key) => searchObject[key] || fallback || match, map ) ); } 
0
Feb 01 '17 at 5:42 on
source share

Since we are inventing the wheel on something that would be a great feature in javascript.

I use eval() , which is not safe, but javascript is not secure. I readily admit that I'm not excellent with javascript, but I had a need, and I needed an answer, so I did one.

I decided to style my variables with @ , not $ , especially because I want to use the multi-line literal function without evaluating it until ready. So the variable syntax is @{OptionalObject.OptionalObjectN.VARIABLE_NAME}

I'm not a javascript expert, so I gladly accept improvement tips, but ...

 var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g for(i = 0; i < myResultSet.length; i++) { prsLiteral = rt.replace(prsRegex,function (match,varname) { return eval(varname + "[" + i + "]"); // you could instead use return eval(varname) if you're not looping. }) console.log(prsLiteral); } 

A very simple implementation follows

 myResultSet = {totalrecords: 2, Name: ["Bob", "Stephanie"], Age: [37,22]}; rt = `My name is @{myResultSet.Name}, and I am @{myResultSet.Age}.` var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g for(i = 0; i < myResultSet.totalrecords; i++) { prsLiteral = rt.replace(prsRegex,function (match,varname) { return eval(varname + "[" + i + "]"); // you could instead use return eval(varname) if you're not looping. }) console.log(prsLiteral); } 

In my actual implementation, I prefer to use @{{variable}} . Another set of braces. It is absurdly unlikely to happen unexpectedly. The regular expression for this will look like /\@\{\{(.*?)(?!\@\{\{)\}\}/g

To make reading easier

 \@\{\{ # opening sequence, @{{ literally. (.*?) # capturing the variable name # ^ captures only until it reaches the closing sequence (?! # negative lookahead, making sure the following # ^ pattern is not found ahead of the current character \@\{\{ # same as opening sequence, if you change that, change this ) \}\} # closing sequence. 

If you have no experience with regular expressions, a pretty safe rule is to get away from every non-alphanumeric character and never avoid letters, as many escaped letters have special meaning for almost all flavors of regular expressions.

0
Jan 17 '18 at 5:33
source share



All Articles