2

I'm trying to create a simple extension that replaces certain words with others. I'm using regexp to find global matches, then using conditionals to replace the words. However, it only replaces the first occurrence of each word despite my specifying "gi". Could anyone explain why it exhibits this behavior?

I got some of the code from here: Javascript Regex to replace text NOT in html attributes

The code is below:

Fiddle

// Reusable generic function
function surroundInElement(el, regex, surrounderCreateFunc) {
    // script and style elements are left alone
    if (!/^(script|style)$/.test(el.tagName)) {
        var child = el.lastChild;
        while (child) {
            if (child.nodeType == 1) {
                surroundInElement(child, regex, surrounderCreateFunc);
            } else if (child.nodeType == 3) {
                surroundMatchingText(child, regex, surrounderCreateFunc);
            }
            child = child.previousSibling;
        }
    }
}

// Reusable generic function
function surroundMatchingText(textNode, regex, surrounderCreateFunc) {
    var parent = textNode.parentNode;
    var result, surroundingNode, matchedTextNode, matchLength, matchedText;
    while ( textNode && (result = regex.exec(textNode.data)) ) {
        matchedTextNode = textNode.splitText(result.index);
        matchedText = result[0];
        matchLength = matchedText.length;
        textNode = (matchedTextNode.length > matchLength) ?
            matchedTextNode.splitText(matchLength) : null;
        surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true));
        parent.insertBefore(surroundingNode, matchedTextNode);
        parent.removeChild(matchedTextNode);
    }
}

// This function does the surrounding for every matched piece of text
// and can be customized  to do what you like
function createSpan(matchedTextNode) {
    var val = matchedTextNode.nodeValue;
    var valuelower = val.toLowerCase();
    if(valuelower === "nice" || valuelower === "good" || valuelower === "great" || valuelower === "awesome" || valuelower === "amazing"){
        var t = document.createTextNode("gj");
        var el = document.createElement("span");
        el.style.color = "red";
        el.appendChild(t);
        return el;
    }

    if(valuelower === "bad" || valuelower === "terrible" || valuelower === "horrendous" || valuelower === "awful" || valuelower === "abominable"){
        var t = document.createTextNode("bj");
        var el = document.createElement("span");
        el.style.color = "red";
        el.appendChild(t);
        return el;
    }

    if(valuelower === "does"){
        var t = document.createTextNode("dose");
        var el = document.createElement("span");
        el.style.color = "red";
        el.appendChild(t);
        return el;
    }
}

// The main function
function wrapWords(container, words) {
    // Replace the words one at a time.
    for (var i = 0, len = words.length; i < len; ++i) {
        surroundInElement(container, new RegExp(words[i], "gi"), createSpan);
    }
}

wrapWords(document.body, ["nice", "good", "great", "awesome", "amazing", "bad", "terrible", "horrendous", "awful", "abominable", "does"]);
Community
  • 1
  • 1
  • Ignoring the detailed question. could you explain the overall idea behind the code. I understand you want to replace words on a page. But do you mean in a specific element, the whole page, etc? – SReject Oct 26 '14 at 07:55
  • 1
    remove "g" it will fix your issue. RegExp is kind of broken(behaves weirdly) - Also regexp does not get reset - So on each successive execution it continues on from the last match index - try http://xregexp.com/ instead – Dinesh Oct 26 '14 at 07:57
  • @SReject, the purpose of the code was to parse through the DOM tree and only change the words that were visible, and not words in hidden HTML divs and such. Dinesh, that works perfectly. Thanks! –  Oct 26 '14 at 17:33

1 Answers1

0

It is because the regex.exec method returns the 'next' match, starting from the end of the last match. What you are doing is getting the first match for each RegExp object you've created, making the substitution then moving on to the next RegExp object.

What you need is to run the regex again but start it matching where the last node ended, for each of the RegExp Objects. With your current code, its easy, just remvoe the 'g' switch.

SReject
  • 3,774
  • 1
  • 25
  • 41