How would you inject HTML tags into a string, given the start and end index of the injections?

Given a string and a style array render HTML pretty much like a rich text editor.

For example: ‘Hello, world’, [[0, 2, ‘i’], [4, 9, ‘b’], [7, 10, ‘u’]] Output: <i>Hel</i>l<b>o, w<u>orl</u></b><u>d</u>

Keep in mind that tag gets placed before the tag and after it as the insertion index overlaps it.

So far this is my answer:

function render(str, styles) {
  let res = ""
  let lastIndex = 0
  styles.forEach(([start, end, tag], index) => {
    if(lastIndex > start) {
      res  = res.substring(0,start) +  `<${tag}>` + res.substring(start,lastIndex) + `</${tag}>` + res.substring(lastIndex) 
      start = lastIndex + 1
    }
    let newTaggedString = str.substring(lastIndex,start)  + `<${tag}>` + str.substring(start,end) + `</${tag}>` 
    res+=newTaggedString
    lastIndex = end
  })
  
  if(lastIndex < str.length) {
      res += str.substring(lastIndex) 
  }

  return res
}

and it outputs

"<i>He</<u>i></u>ll<b>o, wo</b>r<u></u>ld"

Answer

You could use a stack to keep track of the tags that were previously opened and not closed. And on each letter, check whether a tag needs to be closed (popped off the stack), or a new one opened (pushed to the stack):

function render(str, styles) {
  return str.split('').reduce((res, letter, i, arr) => {
    const tags = styles.reduce((acc, [start, end, tag]) => // Current letter tags
      i >= start && i <= end ? acc.concat(tag) : acc
    , []);
    
    while (res.stack.some(tag => !tags.includes(tag))) {
      res.str += `</${res.stack.pop()}>`; // Pop unwanted tags off the stack
    }
    
    tags.forEach(tag => {
        if (!res.stack.includes(tag)) { // Push wanted tags to the stack
          res.stack.push(tag);
          res.str += `<${tag}>`;
        }
    });

    res.str += letter;

    // If there are tags to close at the end of the string
    if (i === arr.length - 1)
      while (res.stack.length)
        res.str += `</${res.stack.pop()}>`;

    return res;
  }, { stack: [], str: '' }).str;
}

const res = render('Hello, world', [[0, 2, 'i'], [4, 9, 'b'], [7, 10, 'u']]);

document.body.innerHTML = res;
console.log(res);

In case you didn’t know about Array.prototype.reduce, here are some docs