I have following method which highlight matched word in text:

# Hightlight matched term
# Ex(for term: some):
#   "<span class="bold">Some</span> CEO Event"
def highlight_matched(matched_word, text)
  regex = matched_word.gsub(/\*|\"|\'/, "")
                       .split(" ")
                       .map { |s| "\\b#{s}" }.join('|')

  text.gsub(/(#{regex})/i, '<span class="bold">\1</span>')

Is there a better solution for that?

I am using it here:

"%s %s" %[highlight_matched(title[0..70]), content_tag(:span, caption, class: 'search-type')]
  • \$\begingroup\$ Is text pure text or HTML? can you add to the question some asserts? \$\endgroup\$ – tokland Dec 18 '12 at 20:06
  • \$\begingroup\$ Text is HTML \$\endgroup\$ – tomekfranek Dec 18 '12 at 20:18

If text may contain HTML (in that case text is a somewhat misleading variable name) you definitely should use a HTML parser. What about this? (adding a <p> as wrapper, otherwise xpath won't find parent text nodes).

require 'nokogiri'

def highlight(html, word)
  Nokogiri::HTML::fragment("<p>" + html + "</p>").tap do |doc|
    doc.search('.//text()').each do |text_node|
      new_contents = text_node.text.gsub(/\b(#{word})\b/i, '<span class="bold">\1</span>')

puts highlight('<p><a href="http://runner.com">Run Rabbit Run</a></p>', "run")
# <p><a href="http://runner.com"><span class="bold">Run</span>     
# Rabbit <span class="bold">Run</span></a></p>
| improve this answer | |

You should use a better class name, such as highlighted or search-term or search-match.


Your solution will break anchors, or titles, or other hmtl properties:

# Yields: "Broken <a href='http://www.example.org/<span>cat</span>nip'>example</a>."
puts highlight_matched("cat", "Broken <a href='http://catnip.com'>example</a>.")

Another potential problem

And if you repeatedly run it on the same text with a list of words, you will get nested replacements if you don't have the words sorted by size-descending and replace a word ("cat") that is a substring of a previous replacement ("catnip").

This might be, or not, a problem.
For me it was, because I was replacing with anchors and HTML does not allow nested anchors.
For you it might be, too, depending on how you wish to style the matches.

intermediate = highlight_matched("catnip", "I love catnip!")
# Yields: "I love <span><span>cat</span>nip</span>!"
puts highlight_matched("cat", intermediate)
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.