Home > front end >  How to safely replace searches each with a replacement term where the former is part of the latter w
How to safely replace searches each with a replacement term where the former is part of the latter w

Time:07-06

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>"

CodePudding user response:

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, '<a href="'   links[i]   '">$&</a>'); //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.

CodePudding user response:

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 = '<a href="'   replaceArray[i].trim()   '">'   str   '</a>';

  // 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 <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>


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 = '<a href="'   link   '">'   str   '</a>';
    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 <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>


Refactored reduction method for better performance—initially including string in the reduce() function, and processing internally, speeds execution time nearly 100%, 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 = '<a href="'   link   '">'   str   '</a>';
  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 <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>

CodePudding user response:

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.

CodePudding user response:

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 = `<a href="${ href }">${ match }</a>`;

        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; }

  • Related