8

How do I search the DOM for a certain string in the document's text (say, "cheese") then insert some HTML immediately after that string (say, "< b >is fantastic< /b >").

I have tried the following:

for (var tag in document.innerHTML) {
    if (tag.matches(/cheese/) != undefined) {
        document.innerHTML.append(<b>is fantastic</b>
    }
}

(The above is more of an illustration of what I have tried, not the actual code. I expect the syntax is horribly wrong so please excuse any errors, they are not the problem).

Cheers,

Pete

6 Answers6

11

There are native methods for finding text inside a document:

MSIE:textRange.findText()
Others: window.find()

Manipulate the given textRange if something was found.
Those methods should provide much more performance than the traversing of the whole document.

Example:

<html>
<head>

  <script>
  function fx(a,b)
  {
    if(window.find)
    {
      while(window.find(a))
      {
        var node=document.createElement('b');
            node.appendChild(document.createTextNode(b));
        var rng=window.getSelection().getRangeAt(0);
            rng.collapse(false);
            rng.insertNode(node);
      }
    }
    else if(document.body.createTextRange)
    {
      var rng=document.body.createTextRange();
      while(rng.findText(a))
      {
        rng.collapse(false);
        rng.pasteHTML('<b>'+b+'</b>');
      }
    }
  }
  </script>
</head>
<body onload="fx('cheese','is wonderful')">
<p>I've made a wonderful cheesecake with some <i>cheese</i> from my <u>chees</u>e-factory!</p>
</body>
</html>
Dr.Molle
  • 116,463
  • 16
  • 195
  • 201
  • Wrapping the text once you've found it is non-trivial in non-IE browsers. – Tim Down Dec 20 '10 at 12:54
  • The question is about appending not surrounding, that's trivial in non-IE-browsers too. – Dr.Molle Dec 20 '10 at 13:08
  • Fair enough. I'm not convinced this will be as precise as the DOM traversal solution in terms of exactly where it puts these nodes that are being created, there's a case sensitivity issue to address and it messes with the user's selection, but it does work and is a valid alternative. +1. – Tim Down Dec 20 '10 at 14:23
  • Both methods have parameters to set case-sensitivity, it's just a simple example not a finished solution. – Dr.Molle Dec 20 '10 at 14:50
  • I stumbled on a similar problem and found your solution very helpful. Do you know how I could wrap the found text in a `` tag, for example? I have done this in Firefox, but I'm stuck with IE8. – Dragos Dec 06 '12 at 12:05
  • The code above wraps the text in a `` and should work in IE8 – Dr.Molle Dec 06 '12 at 20:52
6

This is crude and not the way to do it, but;

document.body.innerHTML = document.body.innerHTML.replace(/cheese/, 'cheese <b>is fantastic</b>');
Eivind
  • 841
  • 4
  • 4
2

You can use this with JQuery:

$('*:contains("cheese")').each(function (idx, elem) {
    var changed = $(elem).html().replace('cheese', 'cheese <b>is fantastic</b>');
    $(elem).html(changed);
});

I haven't tested this, but something along these lines should work.

Note that * will match all elements, even html, so you may want to use body *:contains(...) instead to make sure only elements that are descendants of the document body are looked at.

Deniz Dogan
  • 25,711
  • 35
  • 110
  • 162
0

Sample Solution:

<ul>
  <li>cheese</li>
  <li>cheese</li>
  <li>cheese</li>
</ul>

Jquery codes:

$('ul li').each(function(index) {
  if($(this).text()=="cheese")
  {
    $(this).text('cheese is fantastic');
  }
});
Mohan Ram
  • 8,345
  • 25
  • 81
  • 130
0

The way to do this is to traverse the document and search each text node for the desired text. Any way involving innerHTML is hopelessly flawed.

Here's a function that works in all browsers and recursively traverses the DOM within the specified node and replaces occurrences of a piece of text with nodes copied from the supplied template node replacementNodeTemplate:

function replaceText(node, text, replacementNodeTemplate) {
    if (node.nodeType == 3) {
        while (node) {
            var textIndex = node.data.indexOf(text), currentNode = node;
            if (textIndex == -1) {
                node = null;
            } else {
                // Split the text node after the text
                var splitIndex = textIndex + text.length;
                var replacementNode = replacementNodeTemplate.cloneNode(true);
                if (splitIndex < node.length) {
                    node = node.splitText(textIndex + text.length);
                    node.parentNode.insertBefore(replacementNode, node);
                } else {
                    node.parentNode.appendChild(replacementNode);
                    node = null;
                }
                currentNode.deleteData(textIndex, text.length);
            }
        }
    } else {
        var child = node.firstChild, nextChild;
        while (child) {
            nextChild = child.nextSibling;
            replaceText(child, text, replacementNodeTemplate);
            child = nextChild;
        }
    }
}

Here's an example use:

replaceText(document.body, "cheese", document.createTextNode("CHEESE IS GREAT"));

If you prefer, you can create a wrapper function to allow you to specify the replacement content as a string of HTML instead:

function replaceTextWithHtml(node, text, html) {
    var div = document.createElement("div");
    div.innerHTML = html;
    var templateNode = document.createDocumentFragment();
    while (div.firstChild) {
        templateNode.appendChild(div.firstChild);
    }
    replaceText(node, text, templateNode);
}

Example:

replaceTextWithHtml(document.body, "cheese", "cheese <b>is fantastic</b>");

I've incorporated this into a jsfiddle example: http://jsfiddle.net/timdown/azZsa/

Tim Down
  • 318,141
  • 75
  • 454
  • 536
0

Works in all browsers except IE I think, need confirmation though.

This supports content in iframes as well.

Note, other examples I have seen, like the one above, are RECURSIVE which is potentially bad in javascript which can end in stack overflows, especially in a browser client which has limited memory for such things. Too much recursion can cause javascript to stop executing.

If you don't believe me, try the examples here yourself...

If anyone would like to contribute, the code is here.

function grepNodes(searchText, frameId) {
  var matchedNodes = [];
  var regXSearch;
  if (typeof searchText === "string") {
    regXSearch = new RegExp(searchText, "g");
  }
  else {
    regXSearch = searchText;
  } 
  var currentNode = null, matches = null;
  if (frameId && !window.frames[frameId]) {
    return null;
  }
  var theDoc = (frameId) ? window.frames[frameId].contentDocument : document;
  var allNodes = (theDoc.all) ? theDoc.all : theDoc.getElementsByTagName('*');
  for (var nodeIdx in allNodes) {
    currentNode = allNodes[nodeIdx];
    if (!currentNode.nodeName || currentNode.nodeName === undefined) {
      break;
    }
    if (!(currentNode.nodeName.toLowerCase().match(/html|script|head|meta|link|object/))) {
      matches = currentNode.innerText.match(regXSearch);
      var totalMatches = 0;
      if (matches) {
        var totalChildElements = 0;
        for (var i=0;i<currentNode.children.length;i++) {
          if (!(currentNode.children[i].nodeName.toLowerCase().match(/html|script|head|meta|link|object/))) {
            totalChildElements++;
          }
        }
        matchedNodes.push({node: currentNode, numMatches: matches.length, childElementsWithMatch: 0, nodesYetTraversed: totalChildElements});
      }
      for (var i = matchedNodes.length - 1; i >= 0; i--) {
        previousElement = matchedNodes[i - 1];
        if (!previousElement) {
          continue;
        }
        if (previousElement.nodesYetTraversed !== 0 && previousElement.numMatches !== previousElement.childElementsWithMatch) {
          previousElement.childElementsWithMatch++;
          previousElement.nodesYetTraversed--;
        }      
        else if (previousElement.nodesYetTraversed !== 0) {
          previousElement.nodesYetTraversed--;
        }               
      }
    }
  }
  var processedMatches = [];
  for (var i =0; i <  matchedNodes.length; i++) {
    if (matchedNodes[i].numMatches > matchedNodes[i].childElementsWithMatch) {
      processedMatches.push(matchedNodes[i].node);
    }
  }
  return processedMatches; 
};
brandon
  • 91
  • 1
  • 2