DEV Community

Piotr Sobuś
Piotr Sobuś

Posted on

4 2

Negative lookbehind alternative in JavaScript

Task description

One of my recent assignment was to create a function that traverses through a parsed HTML text, finds placeholders that meet a certain condition and wraps them with specific HTML tags. The condition was if a placeholder is wrapped around mark + span tag, ignore it, otherwise wrap it with those tags.

Example:

My name is {{ first_name }}. I am <b>{{ age }}</b> years old and I love <mark><span>{{ interest }}</span></mark>.
Enter fullscreen mode Exit fullscreen mode

The function should find 2 occurrences and replace them. The last placeholder should be ignored.

My name is <mark><span>{{ first_name }}</span></mark>. I am <b><mark><span>{{ age }}</span></mark></b> years old and I love <mark><span>{{ interest }}</span></mark>.
Enter fullscreen mode Exit fullscreen mode

First solution

In my first solution I used a negative lookbehind assertion (?<!) that basically tries to find expression A where expression B does not precede. In simpler words - match every placeholder that does not start with a span tag.

markPlaceholders(html: string): string {
  return html.replace(
    /w*(?<!<span>){{([a-z0-9_]*)}}/g,
    '<mark><span>{{$1}}</span></mark>'
  );
}
Enter fullscreen mode Exit fullscreen mode

Easy.

The problem

Unfortunately, when I tried to open the application on Safari, it crashed with a following message:

SyntaxError: Invalid regular expression: invalid group specifier name

Turns out that Safari does not support negative lookbehind assertions. What a shame.

Final solution

The workaround for this problem was to pass a function as the second parameter in the replace method. This so called "replacer" will check if the placeholder starts with the mark and span tag. If it does not, we create a new element and replace it with the matched placeholder. Otherwise, we return what we have, because it already contains those tags. The function will be invoked after the match has been performed.

markPlaceholders(html: string): string {
  return html.replace(/{{[a-z0-9_]*}}/g, (match, _, idx) => {
    const hasTagsBefore =
      html.substring(idx - '<mark><span>'.length, idx) === '<mark><span>';

    if (!hasTagsBefore) {
      return `<mark><span>${match}</span></mark>`;
    }

    return match;
  });
}
Enter fullscreen mode Exit fullscreen mode

I hope this example will help you if you are struggling with the same problem. Feel free to ask questions.

Billboard image

Synthetic monitoring. Built for developers.

Join Vercel, Render, and thousands of other teams that trust Checkly to streamline monitor creation and configuration with Monitoring as Code.

Start Monitoring

Top comments (2)

Collapse
 
shakilmansuridream profile image

I have a long html string & I want to highlight some words in it but not html tag itself. So this regex is to ignore html tags. e.g. I have html string <mark>mark this as Read</mark>. In this HTML, I want to highlight "mark" word into inner html but not "mark" between < & >. using: /\b(?<!<[^>]*)test(?<![^>]*<)\b/ regex

Collapse
 
jamesthomson profile image
James Thomson

Turns out that Safari does not support negative lookbehind assertions.

Ah, of course. SafarIE strikes again.

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay