1

This is a part of bigger problem I'm trying to solve. I'll mention both of them.

Say, with a needle like bi and haystack like Bird, I could use the following code to search for the needle and apply some formatting to the haystack:

const regExp = new RegExp(/bi/, 'gi');
inputStr = inputStr.replace(regExp, '*' + '$&' + '@');

The above would mutate the inputStr like: *Bi@rd (I'll explain later why I'm formatting.)

Now, is it possible to search this modified haystack *Bi@rd for a needle ir?

Bigger problem I'm trying to solve: Finding multiple needles in a haystack and highlighting them. Needles will come in the form of a space separated string. The haystack is user names, so not very long.

This is one of the solutions I came up with:

function highlighter(inputStr, searchQuery) {
  const needles = searchQuery.split(' ');
  let regExp;

  needles.forEach((pattern) => {
    regExp = new RegExp(pattern, 'gi');
    inputStr = inputStr.replace(regExp, '*' + '$&' + '@');
  });

 const highlightedStr = inputStr.replace(/[*]/g, '<span class="h">').replace(/[@]/g, '</span>');
 return highlightedStr;
}

My solution will fail if I try to highlight bi ir in Bird.

I've another solution but it's complicated. So wondering, short of using a library, what's the best way to solve my problem.

Devin
  • 1,755
  • 1
  • 19
  • 27
  • Sorry, I still don't understand why you need to format the string? – XTOTHEL Oct 26 '18 at 18:02
  • @XTOTHEL I need to format the string so that I can apply some HTML for highlighting. Eg.: If I've a formatted string like *Bir@d, I could replace all * with and @ with – Devin Oct 26 '18 at 18:20

1 Answers1

2

One option is, between each character in the needle, use [(insertedChars)]* to optionally match the characters that might be inserted. For your particular example, the only character inserted between the characters is @, so to find ir, you would use:

i[@]*r

But there's only one character in that character set, so it reduces to i@*r. Example:

const haystack = '*Bi@rd';
const re = /i@*r/i;
console.log(haystack.replace(re, '<<foobar>>'));

Another example, for if the inserted characters can be @, #, or %, in a haystack of qw##e@%rty with a needle of wer:

const haystack = 'qw##e@%rty';
const re = /w[@#%]*e[@#%]*r/i;
console.log(haystack.replace(re, '<<foobar>>'));

Also note that if the searchQuery items can contain RE special characters, they'll have to be escaped first.

For a dynamic needle, you'll have to insert the character set in between each character programmatically. Using the above example again:

const escape = s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');

const haystack = 'qw##e@%rty';
const needle = 'wer';
const inserted = '@#%';
const re = new RegExp(
  escape(needle).replace(/\\?.(?!^)/g, '$&[' + inserted + ']*'),
  'gi'
);

console.log(haystack.replace(re, '<<foobar>>'));

Another thing to keep in mind is that rather than concatenating three strings together, you can simply use one large string:

inputStr = inputStr.replace(regExp, '*$&@');
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Thank you so much for a well thought out and thorough answer. Just tested with some input and, this is exactly what I want. I think I can take it from here and refine as I see fit. – Devin Oct 26 '18 at 18:18