Выделять только полные слова

Есть функция которая находит на странице слова которые есть в массиве и заключает их в тег.

Она работает хорошо, но проблема в том что она заключает в тег даже часть слова, например, если в массиве есть:

"кот", "собака"

а текст такой:

... тот который ...

то получится так:

... тот <span class="highlight">кот</span>орый ...

Нужно чтобы функция выделяла только полные слова.

Буду очень благодарен за помощь!

var words = ["кот", "текст", "пример"];

highlight(document.body, words);


function highlight(elem, keywords, caseSensitive = false, cls = 'highlight') {
        const flags = caseSensitive ? 'gi' : 'g';
        keywords.sort((a, b) => b.length - a.length);
        Array.from(elem.childNodes).forEach(child => {
            const keywordRegex = RegExp(keywords.join('|'), flags);
            if (child.nodeType !== 3) { // not a text node
                highlight(child, keywords, caseSensitive, cls);
            } else if (keywordRegex.test(child.textContent)) {
                const frag = document.createDocumentFragment();
                let lastIdx = 0;
                child.textContent.replace(keywordRegex, (match, idx) => {
                    const part = document.createTextNode(child.textContent.slice(lastIdx, idx));
                    const highlighted = document.createElement('span');
                    highlighted.textContent = match;
                    highlighted.classList.add(cls);
                    frag.appendChild(part);
                    frag.appendChild(highlighted);
                    lastIdx = idx + match.length;
                });
                const end = document.createTextNode(child.textContent.slice(lastIdx));
                frag.appendChild(end);
                child.parentNode.replaceChild(frag, child);
            }
        });
    }
.highlight {
  background: red;
  color: #fff;
 }
<body>
пример текста который стоит тут
</body>


Ответы (2 шт):

Автор решения: Pavel Nazarian

  const words = ["кот", "текст", "пример"];

highlight(document.body, words);

function highlight(elem, keywords, cls = 'highlight') {        
   Array.from(elem.childNodes).forEach(child => {
    if (child.nodeType == 3) {                      
     let html = [];
     let frag = document.createDocumentFragment();
     child.textContent.trim().split(" ").forEach(word => {              
       if (keywords.some(el => { return el == word })) {            
          const highlighted = document.createElement('span');
          highlighted.innerHTML = ` <span class='${cls}'>${word}</span> `
          frag.appendChild(highlighted)                            
       } else {
        const part = document.createTextNode(` ${word} `);            
        frag.appendChild(part)               
       }
     });              
     child.parentNode.replaceChild(frag, child);    
    }
  })
}
.highlight {
  background: red;
  color: #fff;
 }
<body>
пример текста который стоит тут, а кот гуляет
</body>

→ Ссылка
Автор решения: yar85

С исправленной регуляркой, и упрощенным созданием фрагмента документа (неявным - через присваивание outerHTML):

var words = ["кот", "текст", "пример"];

highlight(document.body, words);


function highlight(elem, keywords, caseSensitive = false, cls = 'highlight') {
  const flags = caseSensitive ? 'giu' : 'gu';
  const keywordRegex = RegExp(`(\\P{L})(${keywords.join('|')})(\\P{L})`, flags);
  keywords.sort((a, b) => b.length - a.length);
  Array.from(elem.childNodes).forEach(child => {
    if (child.nodeType !== 3) { // not a text node
      highlight(child, keywords, caseSensitive, cls);
    } else {
      const replacement = document.createElement('span');
      child.parentNode.replaceChild(replacement, child);
      replacement.outerHTML = child.textContent.replace(keywordRegex,
        `$1<span class="${cls}">$2</span>$3`
      );
    }
  });
}
.highlight {
  background: red;
  color: #fff;
}
<body>
  пример текста который стоит тут как кот
</body>

Создание экземпляра RegExp поднял, т.к. нет практического смысла на каждой итерации его пересоздавать.

→ Ссылка