2

imagine this html on a page

<div id="hpl_content_wrap">
<p class="foobar">this is one word and then another word comes in foobar and then more words and then foobar again.</p>
<p>this is a <a href="http://foobar.com" data-bitly-type="bitly_hover_card">link with foobar in an attribute</a> but only the foobar inside of the link should be replaced.</p>
</div>

using javascript, how to change all 'foobar' words to 'herpderp' without changing any inside of html tags?

ie. only plain text should be changed.

so the successful html changed will be

<div id="hpl_content_wrap">
<p class="foobar">this is one word and then another word comes in herpderp and then more words and then herpderp again.</p>
<p>this is a <a href="http://foobar.com" data-bitly-type="bitly_hover_card">link with herpderp in an attribute</a> but only the herpderp inside of the link should be replaced.    </p>
</div>
CommentLuv
  • 1,079
  • 1
  • 9
  • 14
  • What on earth is `data-bitly-type="bitly_hover_card"`? It came across one of our newsletters last week when a user built it with our tool and screwed up all the links. I've been googling it but haven't found a solid answer on what it is. – Valien Aug 27 '12 at 20:23
  • @Valien I think it is added to the source if you're using Chrome and have the bit.ly extension active. (maybe) – CommentLuv Aug 28 '12 at 07:34
  • +CommentLuv - Gotcha. That makes sense. – Valien Aug 28 '12 at 16:54

2 Answers2

1

Here is what you need to do...

  1. Get a reference to a bunch of elements.
  2. Recursively walk the children, replacing text in text nodes only.

Sorry for the delay, I was sidetracked before I could add the code.

var replaceText = function me(parentNode, find, replace) {
    var children = parentNode.childNodes;

    for (var i = 0, length = children.length; i < length; i++) {
        if (children[i].nodeType == 1) {
            me(children[i], find, replace);            
        } else if (children[i].nodeType == 3) {
            children[i].data = children[i].data.replace(find, replace);
        }

    }

    return parentNode;

}

replaceText(document.body, /foobar/g, "herpderp");​​​

jsFiddle.

alex
  • 479,566
  • 201
  • 878
  • 984
  • That's true, but I assume (hope?) the OP had gotten this idea, and was more confused about the "how". – haylem Jun 08 '12 at 05:00
  • @haylem My edit wasn't sent until just now. The "how" is implemented above. – alex Jun 08 '12 at 08:37
0

It's a simple matter of:

  • identifying all text nodes in the DOM tree,
  • then replacing all foobar strings in them.

Here's the full code:

// from: https://stackoverflow.com/questions/298750/how-do-i-select-text-nodes-with-jquery
var getTextNodesIn = function (el) {
    return $(el).find(":not(iframe)").andSelf().contents().filter(function() {
        return this.nodeType == 3;
    });
};

var replaceAllText = function (pattern, replacement, root) {
    var nodes = getTextNodesIn(root || $('body'))
    var re    = new RegExp(pattern, 'g')

    nodes.each(function (i, e) {
        if (e.textContent && e.textContent.indexOf(pattern) != -1) {
           e.textContent = e.textContent.replace(re, replacement);
        }
    });
};


// replace all text nodes in document's body
replaceAllText('foobar', 'herpderp');

// replace all text nodes under element with ID 'someRootElement'
replaceAllText('foobar', 'herpderp', $('#someRootElement'));

Note that I do a precheck on foobar to avoid processing crazy long strings with a regexp. May or may not be a good idea.

If you do not want to use jQuery, but only pure JavaScript, follow the link in the code snippet ( How do I select text nodes with jQuery? ) where you'll also find a JS only version to fetch nodes. You'd then simply iterate over the returned elements in a similar fashion.

Community
  • 1
  • 1
haylem
  • 22,460
  • 3
  • 67
  • 96
  • thanks I got it figured out in the end by doing what you said and looping through all text nodes and replacing text. I am different code but the concept is the same. thanks! – CommentLuv Jun 08 '12 at 06:29
  • I modded my function to use the getTextNodesIn function, that was more efficient that the one I had so your answer was useful I wanted to add a wrap around only text nodes that matched. not sure if my version was efficient. I ended up doing it in two parts, first add unique text to wrap the matches using the above functions and then after that is done, do another replace on the html with tags – CommentLuv Jun 08 '12 at 07:39
  • If you're going to `not:iframe()` you may was well cover other exceptions such as `script`, `style`, etc. – alex Jun 08 '12 at 08:38