3

I am highlighting words in search results by using the query that user enters. Some of the results contain symbols like apostrophes and I would like to make the highlighting work if the apostrophe is entered or not. So, if I have this search result

Patrick O'Hagan

And user enters

O'Hagan

Or

Ohagan

It should match the highlighted part: Patrick O'Hagan

One way to achieve this that I thought of was to build a regex by insert a not required apostrophe after each character that user entered, so query ohagan would be translated to this regex:

/(o[']?h[']?a[']?g[']?a[']?n[']?)/gi

This works but there must be a better way?

EDIT: Example I provided previously was not clear, so I will just provide an example code that should show what I want to achieve:

    var resultText = 'Patrick O\'Hagan';
    var query1 = 'o\'hagan';
    var query2 = 'ohagan';

    var regex1 = this.buildRegex(query1);
    var regex2 = this.buildRegex(query2);

    var highlightedText1 = resultText.replace(regex1, x => `<b>${x}</b>`);
    var highlightedText2 = resultText.replace(regex2, x => `<b>${x}</b>`);

    console.log(highlightedText1); //prints: Patrick <b>O'Hagan</b>;
    console.log(highlightedText2); //prints: Patrick <b>O'Hagan</b>;

What I am looking for is the buildRegex function which would construct a regular expression that would match the query in resultText but would ignore the apostrophes.

user1242967
  • 1,220
  • 3
  • 18
  • 30

1 Answers1

0

Alternation | for each character

Either the character OR the character followed by an apostrophe

  1. split() the keyword (ex. obrien) into an array of characters:

    var searchLetters = keyword.split('')
    
    // ['o','b','r','i','e','n']
    
  2. map() each character into a regex string that will accept either the ${literal match} OR| the ${literal match} followed by a single smart quote: [’ or a single straight quote: ']:

    var regexStrings = searchLetters.map(function(character) {
      return `(${character}|${character}['’])`;
    });
    
    // [`(${o}|${o}['’])`,`(${b}|${b}['’])`,`(${r}|${r}['’])`...]
    
  3. Next, join() the new array of regex strings into a single regex string and use it in a RegExp Object:

    var singleRegex = regexStrings.join('');
    var regexObject = new RegExp(`(${singleRegex})`, `gi`);
    
  4. That RegExp Object will be used to wrap whatever matches with a <mark> tag:

    var hits = targetContent.innerHTML.replace(regexObject, `<mark>$1</mark>`);
    

Demo

document.getElementById('search').addEventListener('change', function(e) {
  highlight(this.value, '#content');
});

function highlight(keyword, selector) {
  var node = document.querySelector(selector);
  var html = node.innerHTML;
  var clean = html.replace(/(<mark>|<\/mark>)/, '');
  var escaped = keyword.replace(/[.*+?^${}()|[\]\\]/gi, '\\$&');
  var letters = escaped.split('').map(function(letter) {
    return `(${letter}|${letter}['’])`;
  });
  var string = letters.join('');
  var regex = new RegExp(`(${string})`, `gi`);
  var hits = clean.replace(regex, `<mark>$1</mark>`);
  node.innerHTML = hits;
}
<input id='search' type='search'><input type='button' value='search'>

<article id='content'>
  <p>Murphy, Kelly, O’Sullivan, Walsh, Smith, O’Brien, Byrne, Ryan, O’Connor, O’Neill, O’Reilly, Doyle, McCarthy, Gallagher, O’Doherty, Kennedy, Lynch, Murray, Quinn, Moore, McLoughlin, O’Carroll, Connolly, Daly, O’Connell, Wilson, Dunne, Brennan, Burke, Collins, Campbell, Clarke, Johnston, Hughes, O’Farrell, Fitzgerald, Brown, Martin, Maguire, Nolan, Flynn, Thompson, O’Callaghan, O’Donnell, Duffy, O’Mahony, Boyle, Healy, O’Shea, White, Sweeney, Hayes, Kavanagh, Power, McGrath, Moran, Brady, Stewart, Casey, Foley, Fitzpatrick, O’Leary, McDonnell, MacMahon, Donnelly, Regan, Donovan, Burns, Flanagan, Mullan, Barry, Kane, Robinson, Cunningham, Griffin, Kenny, Sheehan, Ward, Whelan, Lyons, Reid, Graham, Higgins, Cullen, Keane, King, Maher, MacKenna, Bell, Scott, Hogan, O’Keeffe, Magee, MacNamara, MacDonald, MacDermott, Molony, O’Rourke, Buckley, O’Dwyer</p>
</article>
zer00ne
  • 41,936
  • 6
  • 41
  • 68