skip to Main Content

I am facing an issue related to wrapping the flag words with HTML. The flag words response is coming from a third-party service as an array of objects. The paragraph text is something like this:

Hi, my nme is John, and I am from uas.\nthis sentce dones mke sense.\nHi, my nme is John, and I am from uas.

and the flag words response is this:

[
  { offset: 7, token: 'nme', type: 'UnknownToken' },
  { offset: 52, token: 'dones', type: 'UnknownToken' },
  { offset: 58, token: 'mke', type: 'UnknownToken' }
]

I want to wrap the flag tokens with the following HTML tag.

<span class="underline">nme</span>

The response already has the offset and token keys, but my logic is not correctly working with these keys, I am replacing the string on the offset index, but the output is not correct.

My logic:

function replaceAt(str, index, replacement) {
  return (
    str.substring(0, index) +
    replacement +
    str.substring(index + replacement.length)
  );
}

let input = `Hi, my nme is John, and I am from uas.\nthis sentce dones mke sense\nHi, my nme is John, and I am from uas.`;
const flagTokens = [
  { offset: 7, token: "nme", type: "UnknownToken" },
  { offset: 52, token: "dones", type: "UnknownToken" },
  { offset: 58, token: "mke", type: "UnknownToken" },
];

flagTokens.forEach((item) => {
  input = replaceAt(
    input,
    item.offset,
    `<span class="underline">${item.token}</span>`
  );
});

console.log("Output:", input);

Output:

Hi, my <span class="underline">nme</span>his sentce <span <span class="underline">mke</span>

How can I solve this issue?

6

Answers


  1. Perhaps this solution is what you are looking for

    function replaceAt(str, index, replacement) {
      return (
        str.substring(0, index) +
        replacement +
        str.substring(index + replacement.length)
      );
    }
    
    let input = `Hi, my nme is John, and I am from uas.\nthis sentce dones mke sense`;
    const flagTokens = [
      { offset: 7, token: "nme", type: "UnknownToken" },
      { offset: 52, token: "dones", type: "UnknownToken" },
      { offset: 58, token: "mke", type: "UnknownToken" },
    ];
    let myTokenIndex = 1;
    
    flagTokens.forEach((item) => {
        
      input = replaceAt(
        input,
        item.offset + (31*myTokenIndex),
        `<span class="underline">${item.token}</span>`
      );
        myTokenIndex++;
    });
    
    console.log("Output:", input);
    

    Output

    Output: Hi, my nme is John, and I am from uas.<span class="underline">nme</span><span class="underline">dones</span><span class="underline">mke</span>
    
    Login or Signup to reply.
  2. The error in your logic

    function replaceAt(str, index, replacement, token) {
      return (
        str.substring(0, index) +
        replacement +
        str.substring(index /*+ replacement.length*/ + token.length)
      );
    }
    

    A better solution would be

    let str = 'your input string'
    tokens.forEach(item => str.replace(item.token, `<span class="underline">${item.token}</span>`)
    
    Login or Signup to reply.
  3. Use replace() instead

    const flags = [
      { offset: 7, token: 'nme', type: 'UnknownToken' },
      { offset: 52, token: 'dones', type: 'UnknownToken' },
      { offset: 58, token: 'mke', type: 'UnknownToken' }
    ];
    
    // Reference the element that has the text
    const p = document.querySelector("p");
    // Get the element's HTML content as a htmlString;
    let string = p.innerHTML;
    
    /**
     * For each object (flag) of array flags...
     *
     * Create a regex that is a capture group (...) of the
     * current object's token value and set the (g)lobal flag
     *
     * Replace any occurrence that matches the token with itself
     * ($1) wrapped in <u>...</u>.
     */
    flags.forEach(flag => {
      let rgx = new RegExp(`(${flag.token})`, "g");
      string = string.replace(rgx, "<u>$1</u>");
    });
    
    // Parse the HTML of <p> with the modified string
    p.innerHTML = string;
    <p>Hi, my nme is John, and I am from uas.<br> 
    this sentce dones mke sense</p>
    Login or Signup to reply.
  4. Well, there are two issues with your code.

    The first one is that after you replace the first token, your string has gotten longer, and now the offsets of the rest of the tokens are wrong since they have moved forward. A solution to this is to apply the replacements from the last to the first (since they are ordered in offset order)

    The second issue is that your replacement method replaceAt after inserting the replacement text it continues to add str.substring(index + replacement.length). But that is wrong. You only need to add the token length and not the replacement length. So you should pass that as well in the function.

    function replaceAt(str, index, replacement, length) {
      return (
        str.substring(0, index) +
        replacement +
        str.substring(index + length)
      );
    }
    
    let input = `Hi, my nme is John, and I am from uas.\nthis sentce dones mke sense.`;
    const flagTokens = [
      { offset: 7, token: "nme", type: "UnknownToken" },
      { offset: 52, token: "dones", type: "UnknownToken" },
      { offset: 58, token: "mke", type: "UnknownToken" },
    ];
    
    // using .reverse() here to apply the replacements
    // in reverse order (from last to first)
    flagTokens.reverse().forEach((item) => {
      input = replaceAt(
        input,
        item.offset,
        `<span class="underline">${item.token}</span>`, 
        item.token.length
      );
    });
    
    console.log("Output:", input);
    Login or Signup to reply.
  5. I think .replace() is a good and elegant solution for the general case where you want to "underline" all or just the first occurrences for a given token, but it struggles if you want to underline those at particular indexes like you have in your flagToken array, as you may not want to underline all or just the first occurance of a given token, but rather a particular one if they appear multiple times.

    One option could be to loop through your string. If the index your on does not appear in your flagTokens array then you can add the current character to a resulting string (res). If the index does appear in your array as an offset, then you can add your token wrapped in your <span> to the resulting string res, and then add the length of the token to the current index (skipping the rest of the token) to then process the remaining indexes of the string. By keeping the original input string untouched, you don’t need to worry about adjusting your offset based on the characters added by the <span>...</span>, as those are added to res, eg:

    let input = `Hi, my nme is John, and I am from uas.\nthis sentce dones mke sense`;
    const flagTokens = [
      { offset: 7, token: "nme", type: "UnknownToken" },
      { offset: 52, token: "dones", type: "UnknownToken" },
      { offset: 58, token: "mke", type: "UnknownToken" },
    ];
    
    const flagMap = new Map(flagTokens.map(o => [o.offset, o]));
    
    let res = "";
    let i = 0;
    while(i < input.length) {
      let item = flagMap.get(i);
      if(item) {
        res += `<span class="underline">${item.token}</span>`;
        i += item.token.length;
      } else {
        res += input[i];
        i++;
      }
    }
    
    console.log("Output:", res);
    document.body.innerHTML = res;
    .underline {
      color: red;
      text-decoration: underline;
    }
    Login or Signup to reply.
  6. Easiest solution is to start at the end and work your way forward so you do not have to worry about the characters you are adding into the string.

    const input = 'Hi, my nme is John, and I am from uas.\nthis sentce dones mke sense.\nHi, my nme is John, and I am from uas.'
    
    const flagTokens = [
      { offset: 7, token: 'nme', type: 'UnknownToken' },
      { offset: 52, token: 'dones', type: 'UnknownToken' },
      { offset: 58, token: 'mke', type: 'UnknownToken' }
     ];
     
     const replaceTokens = (input, tokens) => {
       const sorted = [...tokens].sort((a,b) => b.offset - a.offset);
       sorted.forEach(({offset, token, type}) => {
         const replacement = `<span class="underline">${token}</span>`;
         input = input.substring(0,offset) + replacement + input.substring(offset + token.length)
       });
       return input;
     }
    
    const result = replaceTokens(input, flagTokens);
    console.log(result);

    Other option is keeping track what you add and adding that to the offset

    const input = 'Hi, my nme is John, and I am from uas.\nthis sentce dones mke sense.\nHi, my nme is John, and I am from uas.'
    
    const flagTokens = [
      { offset: 7, token: 'nme', type: 'UnknownToken' },
      { offset: 52, token: 'dones', type: 'UnknownToken' },
      { offset: 58, token: 'mke', type: 'UnknownToken' }
     ];
     
     const replaceTokens = (input, tokens) => {
       let insertedOffset = 0;
       tokens.forEach(({offset, token, type}) => {
         const replacement = `<span class="underline">${token}</span>`;
         input = input.substring(0,offset + insertedOffset) + replacement + input.substring(offset + insertedOffset + token.length)
         insertedOffset += replacement.length - token.length; 
    
       });
       return input;
     }
    
    const result = replaceTokens(input, flagTokens);
    console.log(result);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search