I am trying implement a replacing mechanism for a string like prepared statements that are evaluated dynamicaly in javascript. I have replacements like
[{username:"Max",age:10}]
Eg assume we have the string as input (username) is (age)
so a find replace is easy by the attribute and its value.
However I want something more advanced where parentheses are 'identified' and evaluted from the inner to outer eg for input:
[{username:"Max",age:10,myDynamicAttribute:"1",label1:'awesome', label2:'ugly'}]
and string
(username) is (age) and (label(myDynamicAttribute))
. In the first iteration of replacements the string should become
(username) is (age) and (label1)
and in second Peter is 10 and awesome
. Is there any tool or pattern that I can use to 'understand' the inner parentheses first and the evaluate the other?. I tried regexes but I wasn't able to create a regex that matches the inner parentheses first and then the outer.
CodePudding user response:
We can write a regular expression that finds a parenthesized expression which contains no internal parentheses, use the expression's internals as a key for our data object, replace the whole expression with that value, and then recur. We would stop when the string contains no such parenthesized expressions, and return the string intact.
Here's one way:
const interpolate = (data, str, parens = str .match (/\(([^\(\)] )\)/)) =>
parens ? interpolate (data, str. replace (parens [0], data [parens [1]])) : str
const data = {username: 'Max', age: 10, myDynamicAttribute: '1', label1: 'awesome', label2: 'ugly'}
const str = `(username) is (age) and (label(myDynamicAttribute))`
console .log (interpolate (data, str))
This would lead to a sequence of recursive calls with these strings:
"(username) is (age) and (label(myDynamicAttribute))",
"Max is (age) and (label(myDynamicAttribute))",
"Max is 10 and (label(myDynamicAttribute))",
"Max is 10 and (label1)",
"Max is 10 and awesome"
CodePudding user response:
You could tokenise the string and use a recursive replacer that traverses the tokens in one pass. If text within parentheses does not match with an object property, they are left as they are. When parentheses occur in the string that is retrieved from the object, they are taken as literals, and no attempt is made to perform a lookup on those again.
function interpolate(encoded, lookup) {
const tokens = encoded.matchAll(/[^()] |./g);
function* dfs(end="") {
while (true) {
const {value, done} = tokens.next();
if (value == end || done) return;
if (value != "(") yield value;
else {
const key = [...dfs(")")].join("");
yield lookup[key] ?? `(${key})`;
}
}
}
return [...dfs()].join("");
}
// Example run
const lookup = {
username: "Max",
age: 10,
myDynamicAttribute: "1",
label1019: 'awesome(really)', // These parentheses are treated as literals
really: "not", // ...so that this will not be retrieved
label2: 'ugly',
};
const str = "1) (username) is (age) (uh!) and (label(myDynamicAttribute)0(myDynamicAttribute)9)"
const res = interpolate(str, lookup);
console.log(res);