module Twitter # Module for doing "hit highlighting" on tweets that have been auto-linked already. # Useful with the results returned from the Search API. module HitHighlighter # Default Tag used for hit highlighting DEFAULT_HIGHLIGHT_TAG = "em" # Add tags around the hits provided in the text. The # hits should be an array of (start, end) index pairs, relative to the original # text, before auto-linking (but the text may already be auto-linked if desired) # # The tags can be overridden using the :tag option. For example: # # irb> hit_highlight("test hit here", [[5, 8]], :tag => 'strong') # => "test hit here" def hit_highlight(text, hits = [], options = {}) if hits.empty? return text end tag_name = options[:tag] || DEFAULT_HIGHLIGHT_TAG tags = ["<" + tag_name + ">", ""] chunks = text.split("<").map do |item| item.blank? ? item : item.split(">") end.flatten result = "" chunk_index, chunk = 0, chunks[0] chunk_chars = chunk.respond_to?("mb_chars") ? chunk.mb_chars : chunk.respond_to?("chars") && chunk.chars.respond_to?("[]") ? chunk.chars : chunk prev_chunks_len = 0 chunk_cursor = 0 start_in_chunk = false for hit, index in hits.flatten.each_with_index do tag = tags[index % 2] placed = false until chunk.nil? || hit < prev_chunks_len + chunk.length do result << chunk_chars[chunk_cursor..-1] if start_in_chunk && hit == prev_chunks_len + chunk_chars.length result << tag placed = true end # correctly handle highlights that end on the final character. if tag_text = chunks[chunk_index+1] result << "<#{tag_text}>" end prev_chunks_len += chunk_chars.length chunk_cursor = 0 chunk_index += 2 chunk = chunks[chunk_index] chunk_chars = chunk.respond_to?("mb_chars") ? chunk.mb_chars : chunk.respond_to?("chars") && chunk.chars.respond_to?("[]") ? chunk.chars : chunk start_in_chunk = false end if !placed && !chunk.nil? hit_spot = hit - prev_chunks_len result << chunk_chars[chunk_cursor...hit_spot].to_s + tag chunk_cursor = hit_spot if index % 2 == 0 start_in_chunk = true end placed = true end # ultimate fallback, hits that run off the end get a closing tag if !placed result << tag end end if chunk if chunk_cursor < chunk_chars.length result << chunk_chars[chunk_cursor..-1] end (chunk_index+1).upto(chunks.length-1).each do |index| result << (index.even? ? chunks[index] : "<#{chunks[index]}>") end end result rescue text end end end