I am trying to use JavaScript to dynamically replace content inside of curly braces. Here is an example of my code:
var myString = "This is {name}'s {adjective} {type} in JavaScript! Yes, a {type}!";
var replaceArray = ['name', 'adjective', 'type'];
var replaceWith = ['John', 'simple', 'string'];
for(var i = 0; i <= replaceArray.length - 1; i ++) {
myString.replace(/\{replaceArray[i]\}/gi, replaceWith[i]);
}
alert(myString);
The above code, should, output "This is John's simple string in JavaScript! Yes, a string!".
Here is what happens:
we are given a string with values in braces that need replaced
a loop uses "replaceArray" to find all of the values in curly braces that will need replaced
these values, along with the curly braces, will be replaced with the corresponding values in the "replaceWith" array
However, I am not having any luck, especially since one value may be replaced in multiple locations, and that I am dealing a dynamic value inside of the regular expression.
Can anyone help me fix this, using a similar setup as above?
First, String.replace is not destructive - it doesn't change the string itself, so you'll have to set myString = myString.replace(...). Second, you can create RegExp objects dynamically with new RegExp, so the result of all that would be:
var myString = "This is {name}'s {adjective} {type} in JavaScript! Yes, a {type}!",
replaceArray = ['name', 'adjective', 'type'],
replaceWith = ['John', 'simple', 'string'];
for(var i = 0; i < replaceArray.length; i++) {
myString = myString.replace(new RegExp('{' + replaceArray[i] + '}', 'gi'), replaceWith[i]);
}
The best way I have found to do this, is to use an in-line replace function like others have mentioned, and from whom I borrowed. Special shout out to #yannic-hamann for the regex and clear example. I am not worried about performance, as I am only doing this to construct paths.
I found my solution in MDN's docs.
const interpolateUrl = (string, values) => string.replace(/{(.*?)}/g, (match, offset) => values[offset]);
const path = 'theresalways/{what}/inthe/{fruit}-stand/{who}';
const paths = {
what: 'money',
fruit: 'banana',
who: 'michael',
};
const expected = 'theresalways/money/inthe/banana-stand/michael';
const url = interpolateUrl(path, paths);
console.log(`Is Equal: ${expected === url}`);
console.log(`URL: ${url}`)
Strings are immutable
Strings in JavaScript are immutable. It means that this will never work as you expect:
myString.replace(x, y);
alert(myString);
This is not just a problem with .replace() - nothing can mutate a string in JavaScript. What you can do instead is:
myString = myString.replace(x, y);
alert(myString);
Regex literals don't interpolate values
Regular expression literals in JavaScript don't interpolate values so this will still not work:
myString = myString.replace(/\{replaceArray[i]\}/gi, replaceWith[i]);
You have to do something like this instead:
myString = myString.replace(new RegExp('\{'+replaceArray[i]+'\}', 'gi'), replaceWith[i]);
But this is a little bit messy, so you may create a list of regexes first:
var regexes = replaceArray.map(function (string) {
return new RegExp('\{' + string + '\}', 'gi');
});
for(var i = 0; i < replaceArray.length; i ++) {
myString = myString.replace(regexes[i], replaceWith[i]);
}
As you can see, you can also use i < replaceArray.length instead of i <= replaceArray.length - 1 to simplify your loop condition.
Update 2017
Now you can make it even simpler:
var regexes = replaceArray.map(string => new RegExp(`\{${string}\}`, 'gi'));
for(var i = 0; i < replaceArray.length; i ++) {
myString = myString.replace(regexes[i], replaceWith[i]);
}
Without a loop
Instead of looping and applying .replace() function over and over again, you can do it only once like this:
var mapping = {};
replaceArray.forEach((e,i) => mapping[`{${e}}`] = replaceWith[i]);
myString = myString.replace(/\{\w+\}/ig, n => mapping[n]);
See DEMO.
Templating engines
You are basically creating your own templating engine. If you want to use a ready solution instead, then consider using:
John Resig's Micro-Templating
Mustache
jQuery Templates
Handlebars
doT.js
or something like that.
An example of what you are trying to do using Mustache would be:
var myString = "This is {{name}}'s {{adjective}} {{type}} in JavaScript! Yes, a {{type}}!";
var myData = {name: 'John', adjective: 'simple', type: 'string'};
myString = Mustache.to_html(myString, myData);
alert(myString);
See DEMO.
Here's a function that takes the string and an array of replacements. It's flexible enough to be re-used. The only catch is, you need to use numbers in your string instead of strings. e.g.,
var str = "{0} membership will start on {1} and expire on {2}.";
var arr = ["Jamie's", '11/27/14', '11/27/15'];
function personalizeString(string, replacementArray) {
return string.replace(/{(\d+)}/g, function(match, g1) {
return replacementArray[g1];
});
}
console.log(
personalizeString(str, arr)
)
Demo: https://jsfiddle.net/4cfy7qvn/
I really like rsp's answer. Especially the 'Without a loop' section. Nonetheless, I find the code not that intuitive. I understand that this question comes from the two arrays scenario and that is more than 7 years old, but since this question appears as #1 on google when searching to replace a string with curly braces and the author asked for a similar setup I am tempted to provide another solution.
That being said, a copy and paste solution to play around with:
var myString = "This is {name}'s {adjective} {TYPE} in JavaScript! Yes, a { type }!";
var regex = /{(.*?)}/g;
myString.replace(regex, (m, c) => ({
"name": "John",
"adjective": "simple",
"type": "string"
})[c.trim().toLowerCase()]);
This resource really helped me to build and understand the code above and to learn more about regex with JavaScript in general.
Related
I have a string that contains template literal variables but is not a template literal due to it being built dynamically. I'd like to convert it into a template "literal" without doing an eval():
// this is dynamically created by an algo:
const vars = '${var1} ${var2}';
const str = 'the result is ' + vars;
// this works, but it's nasty:
eval('result = `' + str + '`');
I'm looking for access to the internal template builder in the spirit of the RegExp class, which can be used in place of standard regexp literal slashes //:
const myRegex = '^(\d+)$';
new RegExp(myRegex);
So that way I could do:
const myStr = 'I want to be ${foo}';
const foo = 99;
const lit = new TemplateLiteral(myStr);
console.log( lit.run() ); // I want to be 99
You can't "build a literal". It is a contradiction in terms. A literal is a syntax in source code that compiles to a value.
new RegExp(...) builds a new regexp object, but not a regexp literal. RegExp literal is when you literally write /.../.
In the same way, string literal is literally only when you write '...', "..." or `...` in code. You can build a string - typically using concatenation - but there is no way to build a string literal.
EDIT: Quick and dirty:
function makeTemplate(template) {
return new Function("return `" + template + "`");
}
const myStr = 'I want to be ${foo}';
const foo = 99;
const template = makeTemplate(myStr);
console.log(template());
// I want to be 99
However, while new Function is always better than eval, the usual disclaimers about executing untrusted strings apply.
For a safer alternative, see Mustache.js or Pug.js (not Pod as I said in comment, sorry), as the leading JavaScript template libraries.
The key issue here is what you have in your comment in the first code block:
this is dynamically created by an algo
If the template is created by an algorithm, your choices are:
Don't use the built-in template feature, use any of several similar templating libraries (or write your own)
Use eval or one of its cousins (new Function is probably best)
You can't create template literals dynamically. They're literals. :-)
If we knew more about your algorithm, we might be able to help you better. For instance, you might be able to have the algorithm create a function, and then call that function with the information it may need:
function getVarsFormatter(var1First) {
if (var1First) {
return (var1, var2) => `${var1} ${var2}`;
} else {
return (var1, var2) => `${var2} ${var1}`;
}
}
then
const formatter = getVarsFormatter(flag);
const result = formatter("this is var1", "this is var2");
Live Example:
function getVarsFormatter(var1First) {
if (var1First) {
return (var1, var2) => `${var1} ${var2}`;
} else {
return (var1, var2) => `${var2} ${var1}`;
}
}
for (let n = 0; n < 6; ++n) {
const formatter = getVarsFormatter(Math.random() < 0.5);
const result = formatter("this is var1", "this is var2");
console.log(result);
}
Suppose I have a sting like this: ABC5DEF/G or it might be ABC5DEF-15 or even just ABC5DEF, it could be shorter AB7F, or AB7FG/H.
I need to create a javascript variable that contains the substring only up to the '/' or the '-'. I would really like to use an array of values to break at. I thought maybe to try something like this.
...
var srcMark = array( '/', '-' );
var whereAt = new RegExp(srcMark.join('|')).test.str;
alert("whereAt= "+whereAt);
...
But this returns an error: ReferenceError: Can't find variable: array
I suspect I'm defining my array incorrectly but trying a number of other things I've been no more successful.
What am I doing wrong?
Arrays aren't defined like that in JavaScript, the easiest way to define it would be with:
var srcMark = ['/','-'];
Additionally, test is a function so it must be called as such:
whereAt = new RegExp(srcMark.join('|')).test(str);
Note that test won't actually tell you where, as your variable suggests, it will return true or false. If you want to find where the character is, use String.prototype.search:
str.search(new RegExp(srcMark.join('|'));
Hope that helps.
You need to use the split method:
var srcMark = Array.join(['-','/'],'|'); // "-|/" or
var regEx = new RegExp(srcMark,'g'); // /-|\//g
var substring = "222-22".split(regEx)[0] // "222"
"ABC5DEF/G".split(regEx)[0] // "ABC5DEF"
From whatever i could understand from your question, using this RegExp /[/-]/ in split() function will work.
EDIT:
For splitting the string at all special characters you can use new RegExp(/[^a-zA-Z0-9]/) in split() function.
var arr = "ABC5DEF/G";
var ans = arr.split(/[/-]/);
console.log(ans[0]);
arr = "ABC5DEF-15";
ans = arr.split(/[/-]/);
console.log(ans[0]);
// For all special characters
arr = "AB7FG/H";
ans = arr.split(new RegExp(/[^a-zA-Z0-9]/));
console.log(ans[0]);
You can use regex with String.split.
It will look something like that:
var result = ['ABC5DEF/G',
'ABC5DEF-15',
'ABC5DEF',
'AB7F',
'AB7FG/H'
].map((item) => item.split(/\W+/));
console.log(result);
That will create an Array with all the parts of the string, so each item[0] will contain the text till the / or - or nothing.
If you want the position of the special character (non-alpha-numeric) you can use a Regular Expression that matches any character that is not a word character from the basic Latin alphabet. Equivalent to [^A-Za-z0-9_], that is: \W
var pattern = /\W/;
var text = 'ABC5DEF/G';
var match = pattern.exec(text);
var position = match.index;
console.log('character: ', match[0]);
console.log('position: ', position);
I have a string with keywords, separated by comma's.
Now I also have a nice RegEx, to filter out all the keywords in that string, that matches a queried-string.
Check out this initial question - RegEx - Extract words that contains a substring, from a comma seperated string
The example below works fine; it has a masterString, and a resultString. That last one only contains the keywords that has at least the word "car" in it.
masterString = "typography,caret,car,align,shopping-cart,adjust,card";
resultString = masterString.match(/[^,]*car[^,]*/g);
console.log(resultString);
Result from the code above;
"caret", "car", "shopping-cart", "card"
But how can I use the RegEx, with a variable matching-word (the word "car" in this example static and not variable).
I think it has to do something with a RegExp - but I can't figure out...
Here's a general solution for use with regexes:
var query = "anything";
// Escape the metacharacters that may be found in the query
// sadly, JS lacks a built-in regex escape function
query = query.replace(/[-\\()\[\]{}^$*+.?|]/g, '\\$&');
var regex = new RegExp("someRegexA" + query + "someRegexB", "g");
As long as someRegexA and someRegexB form a valid regex with a literal in-between, the regex variable will always hold a valid regex.
But, in your particular case, I'd simply do this:
var query = "car";
var items = masterString.split(",");
query = query.toLowerCase();
for (var i = 0; i < items.length; ++i) {
if (items[i].toLowerCase().indexOf(query) >= 0) {
console.log(items[i]);
}
}
How about this one?, you only need to replace \ \ with String , and it works for me. it can find whether your string has "car", not other similar word
var query = 'car';
var string = "car,bike,carrot,plane,card";
var strRegEx = '[^,]*'+query+'[,$]*';
string.match(strRegEx);
Answer provided by OP and removed from inside the question
I figured out this quick-and-maybe-very-dirty solution...
var query = 'car';
var string = "car,bike,carrot,plane,card";
var regex = new RegExp("[^,]*|QUERY|[^,]*".replace('|QUERY|',query),'ig');
string.match(regex);
This code outputs the following, not sure if it is good crafted, 'though..
"car", "carrot", "card"
But ended figuring out another, much simpler solution;
var query = "car";
var string = "car,bike,carrot,plane,card";
string.match(new RegExp("[^,]*"+query+"[^,]*",'ig'));
This code outputs the string below;
["car", "carrot", "card"]
My app-search-engine now works perfect :)
So I have the following:
var token = '[token]';
var tokenValue = 'elephant';
var string = 'i have a beautiful [token] and i sold my [token]';
string = string.replace(token, tokenValue);
The above will only replace the first [token] and leave the second on alone.
If I were to use regex I could use it like
string = string.replace(/[token]/g, tokenValue);
And this would replace all my [tokens]
However I don't know how to do this without the use of //
I have found split/join satisfactory enough for most of my cases.
A real-life example:
myText.split("\n").join('<br>');
Why not replace the token every time it appears with a do while loop?
var index = 0;
do {
string = string.replace(token, tokenValue);
} while((index = string.indexOf(token, index + 1)) > -1);
string = string.replace(new RegExp("\\[token\\]","g"), tokenValue);
Caution with the accepted answer, the replaceWith string can contain the inToReplace string, in which case there will be an infinite loop...
Here a better version:
function replaceSubstring(inSource, inToReplace, inReplaceWith)
{
var outString = [];
var repLen = inToReplace.length;
while (true)
{
var idx = inSource.indexOf(inToReplace);
if (idx == -1)
{
outString.push(inSource);
break;
}
outString.push(inSource.substring(0, idx))
outString.push(inReplaceWith);
inSource = inSource.substring(idx + repLen);
}
return outString.join("");
}
"[.token.*] nonsense and [.token.*] more nonsense".replace("[.token.*]", "some", "g");
Will produce:
"some nonsense and some more nonsense"
I realized that the answer from #TheBestGuest won't work for the following example as you will end up in an endless loop:
var stringSample= 'CIC';
var index = 0;
do { stringSample = stringSample.replace('C', 'CC'); }
while((index = stringSample.indexOf('C', index + 1)) > -1);
So here is my proposition for replaceAll method written in TypeScript:
let matchString = 'CIC';
let searchValueString= 'C';
let replacementString ='CC';
matchString = matchString.split(searchValueString).join(replacementString);
console.log(matchString);
Unfortunately since Javascript's string replace() function doesn't let you start from a particular index, and there is no way to do in-place modifications to strings it is really hard to do this as efficiently as you could in saner languages.
.split().join() isn't a good solution because it involves the creation of a load of strings (although I suspect V8 does some dark magic to optimise this).
Calling replace() in a loop is a terrible solution because replace starts its search from the beginning of the string every time. This is going to lead to O(N^2) behaviour! It also has issues with infinite loops as noted in the answers here.
A regex is probably the best solution if your replacement string is a compile time constant, but if it isn't then you can't really use it. You should absolutely not try and convert an arbitrary string into a regex by escaping things.
One reasonable approach is to build up a new string with the appropriate replacements:
function replaceAll(input: string, from: string, to: string): string {
const fromLen = from.length;
let output = "";
let pos = 0;
for (;;) {
let matchPos = input.indexOf(from, pos);
if (matchPos === -1) {
output += input.slice(pos);
break;
}
output += input.slice(pos, matchPos);
output += to;
pos = matchPos + fromLen;
}
return output;
}
I benchmarked this against all the other solutions (except calling replace() in a loop which is going to be terrible) and it came out slightly faster than a regex, and about twice as fast as split/join.
Edit: This is almost the same method as Stefan Steiger's answer which I totally missed for some reason. However his answer still uses .join() for some reason which makes it 4 times slower than mine.
First a quick definition :)
Template - A string which may contain placeholders (example:"hello [name]")
Placeholder - A substring whitin square brackets (example: "name" in "hello [name]:).
Properties map - A valid object with strings as values
I need to write a code that replace placeholders (along with brackets) with the matching values in the properties map.
example:
for the following properties map:
{
"name":"world",
"my":"beautiful",
"a":"[b]",
"b":"c",
"c":"my"
}
Expected results:
"hello name" -> "hello name"
"hello [name]" -> "hello world"
"[b]" -> "c"
"[a]" -> "c" (because [a]->[b]->[c])
"[[b]]" -> "my" (because [[b]]->[c]->my)
"hello [my] [name]" -> "hello beautiful world"
var map = {
"name":"world",
"my":"beautiful",
"a":"[b]",
"b":"c",
"c":"my"
};
var str = "hello [my] [name] [[b]]";
do {
var strBeforeReplace = str;
for (var k in map) {
if (!map.hasOwnProperty(k)) continue;
var needle = "[" + k + "]";
str = str.replace(needle, map[k]);
}
var strChanged = str !== strBeforeReplace;
} while (strChanged);
document.write(str); //hello beautiful world my
The answer by #chris is excellent, I just want to provide an alternative solution using regular expressions that works "the other way round", i.e., not by looking for occurrences of the "placeholder versions" of all items in the properties map, but by repeatedly looking for occurrences of the placeholder itself, and substituting it with the corresponding value from the property map. This has two advantages:
If the property map grows very large, this solution should have
better performance (still to be benchmarked though).
The placeholder and the way substitutions work can easily be modified by adjusting the regular expression and the substitution function (might not be an issue here).
The downside is, of course, that the code is a little more complex (partly due to the fact that JavaScript lacks a nice way of substituting regular expression matches using custom functions, so that's what substituteRegExp is for):
function substituteRegExp(string, regexp, f) {
// substitute all matches of regexp in string with the value
// returned by f given a match and the corresponding group values
var found;
var lastIndex = 0;
var result = "";
while (found = regexp.exec(string)) {
var subst = f.apply(this, found);
result += string.slice(lastIndex, found.index) + subst;
lastIndex = found.index + found[0].length;
}
result += string.slice(lastIndex);
return result;
}
function templateReplace(string, values) {
// repeatedly substitute [key] placeholders in string by values[key]
var placeholder = /\[([a-zA-Z0-9]+)\]/g;
while (true) {
var newString = substituteRegExp(string, placeholder, function(match, key) {
return values[key];
});
if (newString == string)
break;
string = newString;
}
return string;
}
alert(templateReplace("hello [[b]] [my] [name]", {
"name":"world",
"my":"beautiful",
"a":"[b]",
"b":"c",
"c":"my"
})); // -> "hello my beautiful world"
Update: I did some little profiling to compare the two solutions (jsFiddle at http://jsfiddle.net/n8Fyv/1/, I also used Firebug). While #chris' solution is faster for small strings (no need for parsing the regular expression etc), this solution performs a lot better for large strings (in the order of thousands of characters). I did not compare for different sizes of the property map, but expect even bigger differences there.
In theory, this solution has runtime O(k n) where k is the depth of nesting of placeholders and n is the length of the string (assuming dictionary/hash lookups need constant time), while #chris' solution is O(k n m) where m is the number of items in the property map. All of this is only relevant for large inputs, of course.
If you're familiar with .NET's String.Format, then you should take a look at this JavaScript implementation. It supports number formatting too, just like String.Format.
Here's an example of how to use it:
var result = String.Format("Hello {my} {name}", map);
However, it would require some modification to do recursive templates.