Jonas Praem
Jonas Praem

Reputation: 2444

Typescript: Replace every occurrence loop. String manipulation loop

I have a piece of code, which goal is to replace every occurence of @something with an anchor tag. The replacement itself works fine, the looping in general does not. Just consider this a string manipulation method, which has to find every occurence of something, then replace it with something else - but can't do replace all, since it depends on what comes after the '@' character.

Here is the code.

private generateAnchors(content: string) {
    let cutIndex = 0;
    let contentToReturn = content;
    let leftToAnalyze = content;
    if (leftToAnalyze.indexOf('@') !== -1) {
        while (true) {
            leftToAnalyze = content.substring(cutIndex);
            leftToAnalyze = leftToAnalyze.substring(content.indexOf('@'));
            let tag = leftToAnalyze.substring(leftToAnalyze.indexOf('@'), leftToAnalyze.indexOf(' ', leftToAnalyze.indexOf('@')));
            cutIndex += leftToAnalyze.indexOf(tag)+tag.length;
            let address = tag.substring(1, tag.length);
            let anchor = '<a href="/home/user/'+address+'">'+tag+'</a>';
            let newContentSubString = leftToAnalyze.replace(tag, anchor);
            contentToReturn = contentToReturn.replace(leftToAnalyze, newContentSubString);
            if (leftToAnalyze.indexOf('@') === -1) break;
        }
    }
    return contentToReturn;
}

in a small string like ' hello @JEDS & @Mill also @theHulk deserves a mention' it works fine. However I found an occurrence with was a larger string, where the @ tag was in the end of the string, and it seemed like it was looping forever, and replacing stuff it weren't supposed too.

What am I overseeing in this piece of code?

Upvotes: 0

Views: 302

Answers (1)

tony19
tony19

Reputation: 138326

There's a bug in your cutIndex calculation. It looks up the index of tag in leftToAnalyze and uses that index into content at the beginning of the loop (i.e., leftToAnalyze = content.substring(cutIndex)), causing it to analyze the same text from the previous run.

cutIndex should actually be looking in content:

// cutIndex += leftToAnalyze.indexOf(tag)+tag.length;  // DON'T DO THIS
cutIndex += content.indexOf(tag)+tag.length;

In addition, the loop should bail early if leftToAnalyze is empty:

while (true) {
  leftToAnalyze = content.substring(cutIndex);
  const indexOfFirstAt = content.indexOf('@');
  leftToAnalyze = leftToAnalyze.substring(indexOfFirstAt);

  // nothing left? bail
  if (!leftToAnalyze) break;
  ...
}

function generateAnchors(content) {
  let cutIndex = 0;
  let contentToReturn = content;
  let leftToAnalyze = content;
  if (leftToAnalyze.indexOf('@') !== -1) {
      let limit = 100;
      let i = 0;
      while (i++ < limit) {
          leftToAnalyze = content.substring(cutIndex);
          const indexOfFirstAt = content.indexOf('@');
          leftToAnalyze = leftToAnalyze.substring(indexOfFirstAt);
          if (!leftToAnalyze) break;
          const indexOfNextAt = leftToAnalyze.indexOf('@');
          const indexOfSpace = leftToAnalyze.indexOf(' ', indexOfNextAt);
          let tag = leftToAnalyze.substring(leftToAnalyze.indexOf('@'), indexOfSpace);
          cutIndex += content.indexOf(tag)+tag.length;
          let address = tag.substring(1, tag.length);
          let anchor = '<a href="/home/user/'+address+'">'+tag+'</a>';
          let newContentSubString = leftToAnalyze.replace(tag, anchor);
          contentToReturn = contentToReturn.replace(leftToAnalyze, newContentSubString);
          if (leftToAnalyze.indexOf('@') === -1) break;
      }
  }
  return contentToReturn;
}

const input = 'foo @bar baz @qux @ @@@@@';

const output = generateAnchors(input);
console.log(output);

But I recommend simplifying your code with regular expressions. This loop could actually be simplified into one line with String#replace:

return content.replace(/(@([^ @]+))/ig, '<a href="/home/user/$2">$1</a>');

function generateAnchors(content) {
  return content.replace(/(@([^ @]+))/ig, '<a href="/home/user/$2">$1</a>');
}

const input = 'foo @bar baz @qux @ @@@@@';

const output = generateAnchors(input);
console.log(output);

Explanation of /(@([^ @]+))/ig

Upvotes: 1

Related Questions