views:

346

answers:

4

Hi,

I was wondering if it is possible to use JavaScript to add a <div> tag around a word in an HTML page.

I have a JS search that searches a set of HTML files and returns a list of files that contain the keyword. I'd like to be able to dynamically add a <div class="highlight"> around the keyword so it stands out.

If an alternate search is performed, the original <div>'s will need to be removed and new ones added. Does anyone know if this is even possible?

Any tips or suggestions would be really appreciated.

Cheers,

Laurie.

A: 

Wrapping is pretty easy with jQuery:

$('span').wrap('<div class="highlight"></div>'); // wraps spans in a b tag

Then, to remove, something like this:

$('div.highlight').each(function(){ $(this).after( $(this).text() ); }).remove();

Sounds like you will have to do some string splitting, though, so wrap may not work unless you want to pre-wrap all your words with some tag (ie. span).

Adam Backstrom
The problem here is that you do not have a selector that will give you the keywords that should be highlighted so this may not be that easy. You need to select a single word in the middle of a paragraph and surround it with the tags.
Vincent Ramdhanie
A: 

The DOM API does not provide a super easy way to do this. As far as I know the best solution is to read text into JavaScript, use replace to make the changes that you want, and write the entire content back. You can do this either one HTML node at a time, or modify the whole <body> at once.

Here is how that might work in jQuery:

$('body').html($('body').html().replace(/(pretzel)/gi, '<b>$1</b>'));
Jesse Hallett
be careful with that approach. your replace expression operates on the html-level, so if someone wants to highlight 'table', for example, it will mess up the html.
Mr. Shiny and New
A: 

couldn't you just write a selector as such to wrap it all?

$("* :contains('foo')").wrap("<div class='bar'></div>");

adam wrote the code above to do the removal:

$('div.bar').each(function(){ $(this).after( $(this).text() ); }).remove();

edit: on second thought, the first statement returns an element which would wrap the element with the div tag and not the sole word. maybe a regex replace would be a better solution here.

marduk
+2  A: 

In general you will need to parse the html code in order to ensure that you are only highlighting keywords and not invisible text or code (such as alt text attributes for images or actual markup). If you do as Jesse Hallett suggested:

$('body').html($('body').html().replace(/(pretzel)/gi, '<b>$1</b>'));

You will run into problems with certain keywords and documents. For example:

<html>
<head><title>A history of tables and tableware</title></head>
<body>
<p>The table has a fantastic history.  Consider the following:</p>
<table><tr><td>Year</td><td>Number of tables made</td></tr>
<tr><td>1999</td><td>12</td></tr>
<tr><td>2009</td><td>14</td></tr>
</table>
<img src="/images/a_grand_table.jpg" alt="A grand table from designer John Tableius">
</body>
</html>

This relatively simple document might be found by searching for the word "table", but if you just replace text with wrapped text you could end up with this:

<<span class="highlight">table</span>><tr><td>Year</td><td>Number of <span class="highlight">table</span>s made</td></tr>

and this:

<img src="/images/a_grand_<span class="highlight">table</span>.jpg" alt="A grand <span class="highlight">table</span> from designer John <span class="highlight">Table</span>ius">

This means you need parsed HTML. And parsing HTML is tricky. But if you can assume a certain quality control over the html documents (i.e. no open-angle-brackets without closing angle brackets, etc) then you should be able to scan the text looking for non-tag, non-attribute data that can be further-marked-up.

Here is some Javascript which can do that:

function highlight(word, text) {
  var result = '';

  //char currentChar;
  var csc; // current search char
  var wordPos = 0;
  var textPos = 0;
  var partialMatch = ''; // container for partial match

  var inTag = false;

  // iterate over the characters in the array
  // if we find an HTML element, ignore the element and its attributes.
  // otherwise try to match the characters to the characters in the word
  // if we find a match append the highlight text, then the word, then the close-highlight
  // otherwise, just append whatever we find.

  for (textPos = 0; textPos < text.length; textPos++) {
    csc = text.charAt(textPos);
    if (csc == '<') {
      inTag = true;
    }
    if (inTag) {
      result += csc;
    } else {
      var currentChar = word.charAt(wordPos);
      if (csc == currentChar) {
        // we are matching the current word
        partialMatch += csc;
        wordPos++;
        if (wordPos == word.length) {
          // we've matched the whole word
          result += '<span class="highlight">';
          result += partialMatch;
          result += '</span>';
          wordPos = 0;
          partialMatch = '';
        }
      } else if (wordPos > 0) {
        // we thought we had a match, but we don't, so append the partial match and move on
        result += partialMatch;
        result += csc;
        partialMatch = '';
        wordPos = 0;
      } else {
        result += csc;
      }
    }


    if (inTag && csc == '>') {
      inTag = false;
    }
  }
  return result;
}
Mr. Shiny and New