Replace regular expression matches with array of values - javascript

I have a regular expression to find text with ??? inside a string.
const paragraph = 'This ??? is ??? and ???. Have you seen the ????';
const regex = /(\?\?\?)/g;
const found = paragraph.match(regex);
console.log(found);
Is there a way to replace every match with a value from an array in order?
E.g. with an array of ['cat', 'cool', 'happy', 'dog'], I want the result to be 'This cat is cool and happy. Have you seen the dog?'.
I saw String.prototype.replace() but that will replace every value.

Use a replacer function that shifts from the array of replacement strings (shift removes and returns the item at the 0th index):
const paragraph = 'This ??? is ??? and ???. Have you seen the ????';
const regex = /(\?\?\?)/g;
const replacements = ['cat', 'cool', 'happy', 'dog'];
const found = paragraph.replace(regex, () => replacements.shift());
console.log(found);
(if there are not enough items in the array to replace all, the rest of the ???s will be replaced by undefined)

Related

How to replace matches via regex with an array of strings?

I have the following sentence with quiz items to solve.
With regex I can easily identify these items and replace them with a text, e.g. to get a quiz sentence.
However, can replace the items with an array of strings, e.g. so that I can build the correct sentence by replacing each item with its appropriate answer?
const regex = /(\[.*?\])/gm;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
const replaceAllRegex = (text, regex, replace) => text.replace(new RegExp(regex, 'g'), replace);
const phrases = {};
phrases.cloze = replaceAllRegex(text, regex, '[_________]');
for (const answer of answers) {
phrases.right = replaceAllRegex(text, regex, answer);
}
console.log(phrases.cloze);
// OUTPUTS: This [_________] a sentence, and those [_________] some apples.
console.log(phrases.right)
// OUTPUTS: This are a sentence, and those are some apples.
// SHOULD OUTPUT: This is a sentence, and those are some apples.
I think that you are considering the correct answers by the order they appear in the answers array. So an option would be, for each element of answers, replace the next regex's match with it.
Something like:
const regex = /(\[.*?\])/;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
let final = text;
for (const answer of answers) {
final = final.replace(regex, answer);
}
console.log(final); // This is a sentence, and those are some apples.
Or you can use the replacer function which String.prototype.replaceAll accepts, with an incrementing counter:
const regex = /(\[.*?\])/g;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
let curr = 0;
final = text.replaceAll(regex, () => {
return answers[curr++];
});
console.log(final);
Note: In both of the previous excerpts of code, you may also want to check if the number of matches is equal to the number of elements in the answers array. I've omitted this kind of verification to keep the code small and just give the idea.
This answer allows you to keep your replaceAllRegex function for both question setup and answer.
The global flag was preventing your script from identifying individual replacements on the second for loop. I made that an argument of your function, and also, passed the modified phrases.right through your iterator rather than text, so it would replace each item in sequence one at a time.
const regex = /(\[.*?\])/gm;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
const replaceAllRegex = (text, regex, replace, global) => text.replace(new RegExp(regex, global || ''), replace);
const phrases = {};
phrases.cloze = replaceAllRegex(text, regex, '[_________]', 'g');
phrases.right = text;
for (const answer of answers) {
phrases.right = replaceAllRegex(phrases.right, regex, answer);
}
console.log(phrases.cloze);
// OUTPUTS: This [_________] a sentence, and those [_________] some apples.
console.log(phrases.right)
// OUTPUTS: This are a sentence, and those are some apples.
// SHOULD OUTPUT: This is a sentence, and those are some apples.

Replace words without replacing them if they appear in another open compound word

I have a paragraph of text that may contain a single word ("dog") that is also contained in another open compound word ("dog walker"). I have a glossary of words that need to match the word found in the paragraph exactly.
I map over the glossary terms, and create a key value object to be used in .replace()
const wordTermArray = this.props.glossaryTerms.map(i => [
`${i.glossaryItem}`,
`${i.glossaryItem}`,
]);
const wordTermObject = Object.fromEntries(wordTermArray);
And here is the object being used in .replace()
const replaceText = text.replace(
/(\w+)/g,
(match, key) => wordTermObject[key] || match,
);
But the problem is using .replace() could possibly match "dog" with the "dog" in "dog walker". So, how would I match the word or open compound word exactly as it appears in the glossary without having them cross replace each other?
I'm not limited to Regex, and also not against using a library.
I suggest creating a regex out of your glossary items after sorting them by length in descending order, and then wrapping the matches with a tag:
const glossaryTerms = ['dog', 'dog walker'];
glossaryTerms.sort(function(a, b){
return b.length - a.length;
});
const wordTermRegex = new RegExp( glossaryTerms.map(i => i.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')).join("|"));
// => /dog walker|dog/
const replaceText = "dog walker and a dog".replace(wordTermRegex, '$&');
// => dog walker and a dog
See the JS demo.
The $& pattern in the replacement refers to the whole match.
See the regex demo

Could a regular expression be used to find text between pairs of delimiters

I need to parse an email template for custom variables that occur between pairs of dollar signs, e.g:
$foo$bar$baz$foo$bar$baz$wtf
So I would want to start by extracting 'foo' above, since it comes between the first pair (1st and 2nd) of dollar signs. And then skip 'bar' but extract 'baz' as it comes between the next pair (3rd and 4th) of dollar signs.
I was able to accomplish this with split and filter as below, but am wondering, if there's a way to accomplish the same with a regular expression instead? I presume some sort of formal parser, recursive or otherwise, could be used, but that would seem like overkill in my opinion
const body = "$foo$bar$baz$foo$bar$baz$wtf";
let delimitedSegments = body.split('$');
if (delimitedSegments.length % 2 === 0) {
// discard last segment when length is even since it won't be followed by the delimiter
delimitedSegments.pop();
}
const alternatingDelimitedValues = delimitedSegments.filter((segment, index) => {
return index % 2;
});
console.log(alternatingDelimitedValues);
OUTPUT: [ 'foo', 'baz', 'bar' ]
Code also at: https://repl.it/#dexygen/findTextBetweenDollarSignDelimiterPairs
Just match the delimiter twice in the regexp
const body = "$foo$bar$baz$foo$bar$baz$wtf";
const result = body.match(/\$[^$]*\$/g).map(s => s.replace(/\$/g, ''));
console.log(result);
You could use this regex /\$\w+\$/g to get the expected output'
let regex = /\$\w+\$/g;
let str = '$foo$bar$baz$foo$bar$baz$wtf';
let result = str.match(regex).map( item => item.replace(/\$/g, ''));
console.log(result);
You can use capturing group in the regex.
const str1 = '$foo$bar$baz$foo$bar$baz$wtf';
const regex1 = /\$(\w+)\$/g;
const str2 = '*foo*bar*baz*foo*bar*baz*wtf';
const regex2 = /\*(\w+)\*/g;
const find = (str, regex) =>
new Array(str.match(regex).length)
.fill(null)
.map(m => regex.exec(str)[1]);
console.log('delimiters($)', JSON.stringify(find(str1, regex1)));
console.log('delimiters(*)', JSON.stringify(find(str2, regex2)));

How to remove a string from an array of strings?

Basically, I have an array of strings
var array_strings = ['string1', 'string2', 'string3']
And I would like to know using that array, how could I find every piece of a string that contains something from the array array_strings and remove it.
For example, If I have the string var hello = 'string1 Hello string2'
I would like it to output only Hello and remove string1 and string2.
One possibility would be to join the array of strings you want to remove by |, then construct a regular expression from that, and .replace with '':
const array_strings = ['string1', 'string2', 'string3'];
const pattern = new RegExp(array_strings.join('|'), 'g');
const hello = 'string1 Hello string2';
console.log(hello.replace(pattern, ''));
If you also want to remove the leading/trailing spaces, then use .trim() as well.
Iterate over the array and use the string replace method to remove the strings from the array. We turn the string into a regular expression through the RegExp constructor. This will allow for multiple replaces and the use of a variable within our expression.
var array_strings = ['string1', 'string2', 'string3'],
str = "string1 hello string2",
printStr = (str, removables) => {
for (let removable of removables) {
let re_removable = new RegExp(removable,"g");
str = str.replace(re_removable, "").trim();
}
return str;
};
console.log(printStr(str, array_strings));
If you are going to have only words as per your example with no commas/punctuation etc then you could also simply split the string and then Array.filter it via Array.includes:
const str = 'string1 Hello string2 there string3',
arr = ['string1', 'string2', 'string3'];
console.log(...str.split(' ').filter(x => !arr.includes(x)))
It is a simpler approach in scenario where you do not have complex sentences/string data for which you would need String.replace via RegEx etc.

How to get parameterise a string separated by commas with regex?

I have a string that looks like this:
{{tagName(21, 'hello, jane','smith')}}
I'm trying to use regex to match() this string to result in:
[0] = tagName
[1] = 21
[2] = 'hello, jane'
[3] = 'smith'
The parameter part of the string can grow. That is to say, it may have more or less parameters and the regex needs to be "greedy" yet knows how to group them up.
I've been trying something like that: ^\{\{([^\(]+)\({1}(.*)\){1}\}\}
But this results in:
[0] = tagName
[1] = 21, 'hello, jane','smith'
What should I do to my regex to get the results I want?
Replace {, }, (, ) with empty string; match [a-z]+, \d+, '.+' followed by , or end of input
var str = "{{tagName(21, 'hello, jane','smith')}}";
var res = str.replace(/\{|\}|\(|\)/g, "")
.match(/([a-z]+)|\d+|('.+')(?=,)|('.+')(?=$)/ig);
console.log(res);
If you're ok with using two regexes, you can return a new array with function name and concat all the parameters onto that array.
With ES6 you can use the spread operator:
const str = "{{tagName(21, 'hello, jane','smith')}}";
const result = str.match(/^\{\{([^\(]+)\({1}(.*)\){1}\}\}/);
console.log([
result[1],
...result[2].match(/^\d+|'.*?'/g)
])
In ES5 you'll have to concat the parameters onto the array containing the function name as its first item:
var str = "{{tagName(21, 'hello, jane','smith')}}";
var result = str.match(/^\{\{([^\(]+)\({1}(.*)\){1}\}\}/);
console.log([result[1]].concat(result[2].match(/^\d+|'.*?'/g)))
In reality, you could concat in ES6, but
So far I've manage to come up with the following:
([A-Za-z]+)|('.*?'|[^',\s\(]+)(?=\s*,|\s*\))
Tested on https://regex101.com

Categories

Resources