var string = "Please click on dashboard and then open the dashboard details to verify your details on the data";
var stringArray = ["dashboard" , "dashboard" , "data"]
var replaceArray = ["https://abcd.com/login" , "https://abcd.com/home" , "https://abcd.com/data"]
for(i=0;i<stringArray.length; i++){
string = string.replace(stringArray[i].trim(), "<a href='"+replaceArray[i].trim()+"'>"+stringArray[i].trim()+"</a>");
}
I have a string and 2 arrays like above. I need to replace my string with respective anchor link tags as mentioned in two arrays. stringArray defines the word to be linked and replaceArray defines the URL should be added. Like first occurrence of dashboard should be anchor linked with "https://abcd.com/login" and second occurance of "dashboard" should be replaced with "https://abcd.com/home" and "data" should be replaced with "https://abcd.com/data".
I tried to find out the word in string and replace it using replace/replaceAll, working fine for single occurrence word, but for multiple occurrences it is not working.
Anyone help me to resolve this.
Resulting :
"Please click on <a href='https://abcd.com/login'><a href='https://abcd.com/home'>dashboard</a></a> and then open the dashboard details to verify your details on the <a href='https://abcd.com/data'>data</a>"
Expected Output:
"Please click on <a href='https://abcd.com/login'>dashboard</a> and then open the <a href='https://abcd.com/home'>dashboard</a> details to verify your details on the <a href='https://abcd.com/data'>data</a>"
When using a string as the first parameter (substring) to the Javascript replace function, replace will find and replace only the first occurrence of the substring. That's why both your "login" and "home" links are nested around the first occurrence of "dashboard", and the remaining occurrences of "dashboard" remain unchanged. Using a regular expression as the first parameter is one solution, however not the only solution...
Using indexOf() to keep track of the last index where word from array strArray was matched, then slice-ing the string after the last insertion to continue the replacement search from there:
var string = "Please click on dashboard and then open the dashboard details to verify your details on the data";
var stringArray = ["dashboard", "dashboard", "data"]
var replaceArray = ["https://abcd.com/login", "https://abcd.com/home", "https://abcd.com/data"]
// keep track of last position of matched string
let ii = 0;
for (i = 0; i < stringArray.length; i++) {
let str = stringArray[i].trim();
let repstr = '' + str + '';
// move position to index of matched string
ii += string.slice(ii).indexOf(str);
string =
// this is the portion of string before and including last replacement
string.slice(0, ii)
// this is the portion after last replacement
+ string.slice(ii).replace(str, repstr);
// move position to past current replacement
ii += repstr.length;
}
console.log(string);
// Please click on dashboard and then open the dashboard details to verify your details on the data
And this solution benchmarks about 120 times faster than both the regular expression solution, and the reduce solutions I posted below.
Here's a solution combining the words and links into a single array, then using reduce to iterate the array replace_arr, update the string string, and maintain the match index ii:
let string = "Please click on dashboard and then open the dashboard details to verify your details on the data";
const replace_arr = [["dashboard", "https://abcd.com/login"], ["dashboard", "https://abcd.com/home"], ["data", "https://abcd.com/data"]];
replace_arr.reduce(
(ii, [str, link]) => {
let repstr = '' + str + '';
ii += string.slice(ii).indexOf(str);
string = string.slice(0, ii)
+ string.slice(ii).replace(str, repstr)
return ii + repstr.length;
}
, 0
);
console.log(string);
// Please click on dashboard and then open the dashboard details to verify your details on the data
Refactored reduction method for better performance—initially including string in the reduce() function, and processing internally, cuts execution time almost in half, compared to accessing the string externally to the reduction process with each iteration:
let string = "Please click on dashboard and then open the dashboard details to verify your details on the data";
const replace_arr = [["dashboard", "https://abcd.com/login"], ["dashboard", "https://abcd.com/home"], ["data", "https://abcd.com/data"]];
[string] = replace_arr.reduce(([ss, ii], [str, link]) => {
let repstr = '' + str + '';
ii += ss.slice(ii).indexOf(str);
return [ss.slice(0, ii) +
ss.slice(ii).replace(str, repstr), ii + repstr.length
];
}, [string, 0]);
console.log(string);
// Please click on dashboard and then open the dashboard details to verify your details on the data
...and this final solution benchmarks nearly twice as fast as the regex solution. :)
Here a solution using a regex with lookaround:
const text = "Please click on dashboard and then open the dashboard details to verify your details on the data or the other data";
const tokens = ["dashboard", "dashboard", "data", "data"]
const links = ["https://abcd.com/login", "https://abcd.com/home", "https://abcd.com/data", "https://abcd.com/dashboard/data"]
var result = text;
for (i = 0; i < tokens.length; i++) {
const re = new RegExp('(?<=.*)((?<= )' + tokens[i] + '(?= |$))(?=.*)');
result = result.replace(re, '$&'); //TODO array length validation
}
console.log(result)
This regex will only work for tokens surrounded by whitespaces to avoid replacing the texts inside URLs.
You can see more about lookahead and lookbehind here and about browser compatibility here.
How about this one guy,
var string = "Please click on dashboard and then open the dashboard details to verify your details on the data";
const stringArray = string.split(' ');
var targetTexts = ["dashboard" , "dashboard" , "data"]
var replaceTexts = ["https://abcd.com/login" , "https://abcd.com/home" , "https://abcd.com/data"]
const resultArray = []
for (let i = 0; i < stringArray.length; i++) {
const word = stringArray[i];
const targetTextIndex = targetTexts.indexOf(word);
if (targetTextIndex > -1) {
resultArray.push("<a href='"+replaceTexts[targetTextIndex]+"'>"+word+"</a>")
targetTexts = targetTexts.filter((_el, idx) => idx !== targetTextIndex)
replaceTexts = replaceTexts.filter((_el, idx) => idx !== targetTextIndex)
} else {
resultArray.push(word);
}
}
console.log(resultArray.join(' '))
I hope you get a hint on this one.
It works like a charm, there will be exception handling for you to handle.
The presented approach consists of
a mapping task which firstly merges two related arrays, the list of search terms and the list of hypertext references, into another array of replacement items.
a reduce tasks which processes a list of replacement items and safely (without running into the same but already replaced search again) replaces each search by its related complete hypertext expression.
The 2nd part gets achieved by making use of the offset parameter of the replace method's replacerFunction. Upon the offset value, the current matches index, one can programmatically (while reducing) split the originally provided string value into processed and unprocessed substrings/partials. The reduce task's array result gets joined back into the final entirely replaced string value.
function createReplacementItemFromBoundHrefs(search, idx) {
const hrefList = this;
return {
search,
href: hrefList[idx],
}
}
function aggregateSafelyReplacedHrefPartials(partials, { search, href }) {
// intentionally mutates `partials`, an array of zero to many
// processed substrings and always an unprocessed (last item)
// `target` substring of the originally provided string value.
const target = partials.pop();
let sliceAt;
const result = target
.replace(
// a case insensitive search regex.
RegExp(`${ search }`, 'i'),
// a replacer function which helps preventing multiple
// manipulations of always the same search/replace term.
(match, offset) => {
const replacement = `${ match }`;
sliceAt = offset + replacement.length;
return replacement;
},
);
return [
...partials, // - processed lately.
result.slice(0, sliceAt), // - processed latest.
result.slice(sliceAt), // - to be processed.
];
}
function safelyReplaceSearchWithRelatedHref(str, searchList, hrefList) {
return searchList
.map(createReplacementItemFromBoundHrefs, hrefList)
.reduce(aggregateSafelyReplacedHrefPartials, [str])
.join('');
}
const string = "Please click on dashboard and then open the dashboard details to verify your details on the data";
const stringArray = [
"dashboard",
"dashboard",
"data",
];
const replaceArray = [
"https://abcd.com/login",
"https://abcd.com/home",
"https://abcd.com/data",
];
console.log(
'before ... ',
string,
'\n\nafter ... ',
safelyReplaceSearchWithRelatedHref(string, stringArray, replaceArray),
);
console.log(
stringArray
.map(createReplacementItemFromBoundHrefs, replaceArray)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
"I have another use case, with an occurrences array , i only want to replace that specific occurrence word from the string. From below, 1 and 3 occurrences of dashboard and 2nd occurrence of data should be replaced. Can you help me with this.. ?? [...] var string = 'On dashboard, then open dashboard details, verify your dashboard details on data and other data'; var wordsArray = ['dashboard', 'dashboard', 'data']; var occurrences = [1 , 3 , 2]; var linksArray = ['abcd.com/login', 'abcd.com/home', 'abcd.com/data'];" – Siva_K22
For this the above presented first solution could be easily refactored into a new one which keeps the two folded approach.
The first task would be the creation of a replacement tracker which enables a replacer function to keep track of each search's current occurrence count and replacement.
The second task straightforwardly does replace the provided original string via
a case insensitive regex which features any possible word/search ... and ...
a replacer function which keeps track of each search's occurrence and upon a search's current occurrence count decides whether to really replace a search by its related full hypertext reference or just with itself (its very own match).
"#PeterSeliger -- Yea, but i resolved that with below RegEx.. string= string.replace(new RegExp("(?:(?:.|\n)*?"+currentstring+"){"+occurence+"}"), function(x){return x.replace(RegExp(currentstring+"$"), replaceString)});" – Siva_K22
Again a regex only approach will never be as reliable (also not as readable) as one that truly tracks search occurrences and was implemented with both goals being generic on one hand but also specialized enough (a regex only approach is limited much earlier in the latter terms).
function aggregateReplacementTrackerFromBoundData(collector, search, idx) {
const { occurrences = [], hrefList = [], tracker = {} } = collector;
// create and/or access and aggregate
// a search specific occurrence tracker.
(tracker[search.toLowerCase()] ??= {
occurrence: {},
occurrenceCount: 0,
})
.occurrence[ occurrences[idx] ] = hrefList[idx];
return { occurrences, hrefList, tracker };
}
function safelyReplaceSearchOccurrencesWithRelatedHref(
str, searchList, hrefList, occurrences,
) {
// create an overall replacement tracker for any search.
const { tracker } = searchList
.reduce(aggregateReplacementTrackerFromBoundData, {
occurrences,
hrefList,
tracker: {},
});
return str
.replace(
// a case insensitive regex which features any possible word/search.
RegExp(`\\b(?:${ searchList.join('|') })\\b`, 'gi'),
// a replacer function which keeps track of each search's occurrence
// and upon a search's current occurrence count decides whether to
// really replace a search by its related full hypertext reference
// or just with itself (its very own match).
(match/*, offset, target*/) => {
const searchTracker = tracker[match.toLowerCase()];
const count = ++searchTracker.occurrenceCount;
const href = searchTracker.occurrence[count] ?? null;
return (href !== null)
&& `${ match }`
|| match;
},
);
}
const string = "On dashboard, then open dashboard details, verify your dashboard details on data and other data";
const wordsArray = [
"dashboard",
"dashboard",
"data",
];
const occurrences = [1 , 3 , 2];
const linksArray = [
"https://abcd.com/login",
"https://abcd.com/home",
"https://abcd.com/data",
];
console.log(
'before ... ',
string,
'\n\nafter ... ',
safelyReplaceSearchOccurrencesWithRelatedHref(
string, wordsArray, linksArray, occurrences,
),
);
console.log({
replacementTracker: wordsArray
.reduce(aggregateReplacementTrackerFromBoundData, {
occurrences,
hrefList: linksArray,
tracker: {},
})
.tracker
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
This is the opposite problem to Efficient JavaScript String Replacement. That solution covers the insertion of data into placeholders whilst this question covers the matching of strings and the extraction of data from placeholders.
I have a problem in understanding how to do the following in ES6 JavaScript.
I'm trying to figure out a way to match strings with placeholders and extract the contents of the placeholders as properties of an object. Perhaps an example will help.
Given the pattern:
my name is {name} and I live in {country}
It would match the string:
my name is Mark and I live in England
And provide an object:
{
name: "Mark",
country: "England"
}
The aim is to take a string and check against a number of patterns until I get a match and then have access to the placeholder values.
Can anyone point me in the right direction...
You can use named capture groups for that problem e.g.
const string = "my name is Mark and I live in England";
const regEx = /name is\s(?<name>\w+?)\b.*?live in (?<country>\w+?)\b/i;
const match = regEx.exec(string);
console.log(match?.groups);
I would be surprised if it can be done with a regex.
The way I would think about it is as follows:
Split the template by { or }
iterate over the latter template parts (every other one starting with index 1)
In each iteration, get the key, its prefix, and postfix (or next prefix)
We can then compute the start and end indices to extract the value from the string with the help of the above.
const extract = (template, str) => {
const templateParts = template.split(/{|}/);
const extracted = {};
for (let index = 1; index < templateParts.length; index += 2) {
const
possibleKey = templateParts[index],
keyPrefix = templateParts[index - 1],
nextPrefix = templateParts[index + 1];
const substringStartIndex = str.indexOf(keyPrefix) + keyPrefix.length;
const substringEndIndex = nextPrefix ? str.indexOf(nextPrefix) : str.length;
extracted[possibleKey] = str.substring(substringStartIndex, substringEndIndex);
}
return extracted;
}
console.log( extract('my name is {name} and I live in {country}', 'my name is Mark and I live in England') );
I have a long string, which I have to manipulate in a specific way. The string can include other substrings which causes problems with my code. For that reason, before doing anything to the string, I replace all the substrings (anything introduced by " and ended with a non escaped ") with placeholders in the format: $0, $1, $2, ..., $n. I know for sure that the main string itself doesn't contain the character $ but one of the substrings (or more) could be for example "$0".
Now the problem: after manipulation/formatting the main string, I need to replace all the placeholders with their actual values again.
Conveniently I have them saved in this format:
// TypeScript
let substrings: { placeholderName: string; value: string }[];
But doing:
// JavaScript
let mainString1 = "main string $0 $1";
let mainString2 = "main string $0 $1";
let substrings = [
{ placeholderName: "$0", value: "test1 $1" },
{ placeholderName: "$1", value: "test2" }
];
for (const substr of substrings) {
mainString1 = mainString1.replace(substr.placeholderName, substr.value);
mainString2 = mainString2.replaceAll(substr.placeholderName, substr.value);
}
console.log(mainString1); // expected result: "main string test1 test2 $1"
console.log(mainString2); // expected result: "main string test1 test2 test2"
// wanted result: "main string test1 $1 test2"
is not an option since the substrings could include $x which would replace the wrong thing (by .replace() and by .replaceAll()).
Getting the substrings is archived with an regex, maybe a regex could help here too? Though I have no control about what is saved inside the substrings...
If you're sure that all placeholders will follow the $x format, I'd go with the .replace() method with a callback:
const result = mainString1.replace(
/\$\d+/g,
placeholder => substrings.find(
substring => substring.placeholderName === placeholder
)?.value ?? placeholder
);
// result is "main string test1 $1 test2"
This may not be the most efficient code. But here is the function I made with comments.
Note: be careful because if you put the same placeholder inside itself it will create an infinite loop. Ex:
{ placeholderName: "$1", value: "test2 $1" }
let mainString1 = "main string $0 $1";
let mainString2 = "main string $0 $1";
let substrings = [{
placeholderName: "$0",
value: "test1 $1"
},
{
placeholderName: "$1",
value: "test2"
},
];
function replacePlaceHolders(mainString, substrings) {
let replacedString = mainString
//We will find every placeHolder, the followin line wil return and array with all of them. Ex: ['$1', $n']
let placeholders = replacedString.match(/\$[0-9]*/gm)
//while there is some place holder to replace
while (placeholders !== null && placeholders.length > 0) {
//We will iterate for each placeholder
placeholders.forEach(placeholder => {
//extrac the value to replace
let value = substrings.filter(x => x.placeholderName === placeholder)[0].value
//replace it
replacedString = replacedString.replace(placeholder, value)
})
//and finally see if there is any new placeHolder inserted in the replace. If there is something the loop will start again.
placeholders = replacedString.match(/\$[0-9]*/gm)
}
return replacedString
}
console.log(replacePlaceHolders(mainString1, substrings))
console.log(replacePlaceHolders(mainString2, substrings))
EDIT:
Ok... I think I understood your problem now... You did't want the placeHoldersLike strings inside your values to be replaced.
This version of code should work as expected and you won't have to worry aboy infine loops here. However, be carefull with your placeHolders, the "$" is a reserved caracter in regex and they are more that you should scape. I asume all your placeHolders will be like "$1", "$2", etc. If they are not, you should edit the regexPlaceholder function that wraps and scapes that caracter.
let mainString1 = "main string $0 $1";
let mainString2 = "main string $0 $1 $2";
let substrings = [
{ placeholderName: "$0", value: "$1 test1 $2 $1" },
{ placeholderName: "$1", value: "test2 $2" },
{ placeholderName: "$2", value: "test3" },
];
function replacePlaceHolders(mainString, substrings) {
//You will need to escape the $ characters or maybe even others depending of how you made your placeholders
function regexPlaceholder(p) {
return new RegExp('\\' + p, "gm")
}
let replacedString = mainString
//We will find every placeHolder, the followin line wil return and array with all of them. Ex: ['$1', $n']
let placeholders = replacedString.match(/\$[0-9]*/gm)
//if there is any placeHolder to replace
if (placeholders !== null && placeholders.length > 0) {
//we will declare some variable to check if the values had something inside that can be
//mistaken for a placeHolder.
//We will store how many of them have we changed and replace them back at the end
let replacedplaceholdersInValues = []
let indexofReplacedValue = 0
placeholders.forEach(placeholder => {
//extrac the value to replace
let value = substrings.filter(x => x.placeholderName === placeholder)[0].value
//find if the value had a posible placeholder inside
let placeholdersInValues = value.match(/\$[0-9]*/gm)
if (placeholdersInValues !== null && placeholdersInValues.length > 0) {
placeholdersInValues.forEach(placeholdersInValue => {
//if there are, we will replace them with another mark, so our primary function wont change them
value = value.replace(regexPlaceholder(placeholdersInValue), "<markToReplace" + indexofReplacedValue + ">")
//and store every change to make a rollback later
replacedplaceholdersInValues.push({
placeholderName: placeholdersInValue,
value: "<markToReplace" + indexofReplacedValue + ">"
})
})
indexofReplacedValue++
}
//replace the actual placeholders
replacedString = replacedString.replace(regexPlaceholder(placeholder), value)
})
//if there was some placeholderlike inside the values, we change them back to normal
if (replacedplaceholdersInValues.length > 0) {
replacedplaceholdersInValues.forEach(replaced => {
replacedString = replacedString.replace(replaced.value, replaced.placeholderName)
})
}
}
return replacedString
}
console.log(replacePlaceHolders(mainString1, substrings))
console.log(replacePlaceHolders(mainString2, substrings))
The key is to choose a placeholder that is impossible in both the main string and the substring. My trick is to use non-printable characters as the placeholder. And my favorite is the NUL character (0x00) because most other people would not use it because C/C++ consider it to be end of string. Javascript however is robust enough to handle strings that contain NUL (encoded as unicode \0000):
let mainString1 = "main string \0-0 \0-1";
let mainString2 = "main string \0-0 \0-1";
let substrings = [
{ placeholderName: "\0-0", value: "test1 $1" },
{ placeholderName: "\0-1", value: "test2" }
];
The rest of your code does not need to change.
Note that I'm using the - character to prevent javascript from interpreting your numbers 0 and 1 as part of the octal \0.
If you have an aversion to \0 like most programmers then you can use any other non-printing characters like \1 (start of heading), 007 (the character that makes your terminal make a bell sound - also, James Bond) etc.
I have the following string
133. Alarm (Peep peep)
My goal is to split the string using regex into 3 parts and store it as a json object, like
{
"id": "133",
"Title": "Alarm",
"Subtitle": "Peep peep"
}
I can get the number using
function getID(text){
let numberPattern = /\d+/g;
let id = title.match(numberPattern);
if(id){
return id[0];
}
}
and the text between braces using
function getSubtitle(text){
let braces = /\((.*)\)/i;
let subtitle = title.match(braces);
if(subtitle){
return subtitle[1];
}
}
I'm wondering if I can get the three values from the string using a single regex expression (assuming that I will apply it on a long list of that string shape)
You can do this:
const data = '133. Alarm (Peep peep)'
const getInfo = data => {
let [,id, title, subtitle] = data.match(/(\d+)\.\s*(.*?)\s*\((.*?)\)/)
return { id, title, subtitle }
}
console.log(getInfo(data))
Something like
let partsPattern = /(\d+)\.\s*(.*[^[:space:]])\s*\((.*)\)/
Not sure if JS can into POSIX charsets, you might want to use \s instead of [:space:] (or even the space itself if you know that there aren't any other whitespaces expected).
This should capture all the three parts inside the respective submatches (numbers 1, 2 and 3).
You could use one function. exec() will return null if no matches are found, else it will return the matched string, followed by the matched groups. With id && id[1] a check is performed to not access the second element of id for when a match is not found and id === null.
The second element is used id[1] instead of id[0] because the first element will be the matched string, which will contain the dots and whitespace that helped find the match.
var str = "133. Alarm (Peep peep)";
function getData(str) {
var id = (/(\d+)\./).exec(str);
var title = (/\s+(.+)\s+\(/).exec(str);
var subtitle = (/\((.+)\)/).exec(str);
return {
"id": id && id[1],
"Title": title && title[1],
"Subtitle": subtitle && subtitle[1]
};
}
console.log(getData(str));
I am loading data about NBA games from an API using Javascript, and I want to manipulate it but am having trouble. Each game is its own separate object, and is the data is returned like this:
Date: "Nov 7, 2014"
Opponent: "# Charlotte"
Result: "L"
Score: "122-119"
Spread: "+1.5"
Depending on whether the team is home or away, there is either a "#" or a "vs" in front of the name of the opponent for that particular game. I want to get rid of this, so that the "Opponent" key only has "Charlotte" as its value in the above example.
I've tried usinggameLog[i].Opponent = (gameLog[i].Opponent.split(" ").pop
to get rid of any characters before the space, but this ruins the data when there is a team name with a space in it like "New York" or "Los Angeles"
This takes the string, and creates a new substring starting at the index of the first white space. e.g.:
# New York = a new string starting after the #. -> New York
gameLog[i].Opponent = gameLog[i].Opponent.substr(gameLog[i].Opponent.indexOf(' ')+1);
I guess, something along these lines might help.
var home = "# Charlotte";
var opponent = "vs New York";
function parse(team){
// Case when it is a home team
if ( team.indexOf("#") === 0 ){
return team.replace("#","").trim();
// Away team
} else {
return team.replace("vs","").trim();
}
}
console.log( parse(home) );
console.log( parse(opponent) );
gameLog[i].Opponent = (gameLog[i].Opponent.split(" ").slice(1).join(" "));
Split based off space character
Slice off the first item in the array
Join the contents of the array back together with space.
You can use regular expressions to replace unwanted characters while looping over an array of objects.
for (var i = 0; i < arr.length; i++) {
arr[i].Opponent = arr[i].Opponent.replace(/#\s|vs\s/g, '');
}
Here's a jsbin
You need the substr() method:
var str = "# Charlotte";
var res = str.substr(2);
Result: Charlotte
Unless there is also a space after "vs", which is not clear.
Then you could use:
var str = "# Charlotte";
var res = str.substr(str.indexOf(' ')+1);