3

I want to make a google chrome extension that replaces many different strings of text on a webpage to exhibit different words on client side. I came accros with this example below.

But I failed after trying a lot to change it to handle different words to replace. I can only handle one at a time.

i.e: I want to change all 'pink' words to 'blue', all 'cat' words to 'dog' and all 'boy' words to 'girl'. All at once.

How could I accomplish that? When I tinkered with this sample code all the times I would end only changing one of the words. In some cases, only on its first occurence.

Thanks in advance. I could not an answer to this question anywhere. Sorry if it looks noobish.

var elements = document.getElementsByTagName('*');

for (var i = 0; i < elements.length; i++) {
    var element = elements[i];

    for (var j = 0; j < element.childNodes.length; j++) {
        var node = element.childNodes[j];

        if (node.nodeType === 3) {
            var text = node.nodeValue;
            var replacedText = text.replace(/pink/gi, 'blue');

            if (replacedText !== text) {
                element.replaceChild(document.createTextNode(replacedText), node);
            }
        }
    }
}

4 Answers 4

2

In order to simplify mapping of multiple strings to their replacements, you could create a js object and corresponding function for your replacements.

Edited for improved usability per comment from @MihaiIorga (now you only have to edit matches to add new replacement words). For example (only selecting <div> tags in the snippet below for simplicity but you can modify to select all elements, certain tags, etc):

const matches = { pink: 'blue', cat: 'dog', boy: 'girl' };
const replaces = Object.keys(matches).join('|');
const replacer = (s) => matches[s];
const elems = document.getElementsByTagName('div');
for (const elem of elems) {
  const s = elem.textContent;
  elem.textContent = s.replace(new RegExp(replaces, 'gi'), replacer);
}
<div>pink horse</div>
<div>green cat</div>
<div>purple boy moon</div>
<div>boy pink cat</div>

Sign up to request clarification or add additional context in comments.

6 Comments

As much as I like simplicity :) you can add another const const replaces = Object.keys(matches).join('|'); and then elem.textContent = s.replace(new RegExp(replaces, "gi"), replacer); would make matches a perfect DAL :) example
@MihaiIorga - very nice improvement. Edited answer accordingly - thanks.
Unfortunately this code is breaking the pages when I upload it as a chrome extension. The resulting displaying page is a tex-only page. With everything garbled.
@moidsch - hard to troubleshoot without knowing more about the pages you are testing and how you have implemented the code in your chrome extension. If you can provide some specifics either as an edit to your question or post as a separate question, then we may be able to help.
I'm testing it on news websites, like NY Times or UOL.com.br, a major Brazilian hub. The only purpose of the chrome extension is to replace certain words and change it to on browser viewing. Without compromising website previous layout or CSS, like the extension that would make Trump's name Drumpf. Since it's a direct implementation of the suggestions, I'll post what I tried and the results.
|
1

There are a few key things to note here:

  1. You're already grabbing every element on the page with your wildcard getElementsByTagName search. Therefore, you don't have to traverse the tree down to the child nodes, as you've already captured them in the initial search and they'll come up later in the iterations.

  2. You don't have to replace a string, compare it to the original contents, then replace the node. You can just directly replace the inner text of any node.

  3. Square brackets in regular expressions indicate "classes" of characters to match. That means, without special characters, it will match any character inside the brackets, not all the characters, and order doesn't matter. In other words, /[one]/ will match o, n, or e, it won't match the string "one". So no square brackets needed.

So (without adding methods with callbacks -- though you should look into Array.forEach at least, as it's useful!), your code simplifies to just:

elements = document.getElementsByTagName("*");
for (var i = 0; i < elements.length; i++) {
    elements[i].innerText = elements[i].innerText.replace(/pink/gi, "blue");
}

1 Comment

Sorry. I forgot the Square brackets. Gonna try using the different methods suggested here.
1

The wildcard is "too wild" I think you will need to use '*:not(script):not(link) ... '

Or maybe something like: var elements = document.getElementsByTagName('body')[0].querySelectorAll('*');

var elements = document.getElementsByTagName('body')[0].querySelectorAll('*:not(script)');
[].forEach.call(elements,function(elem){
    elem.textContent = elem.textContent ?  elem.textContent.replace(/pink/igm,'blue') : elem.innerText;
});

Comments

0

So there is a very similar question posted here, though it completely replaces the text content of each child node, rather than replacing selectively. I've modified it, here's a possible way to streamline it. First, use a function, so you're hiding away some of the complexity. ;)

let doc = document.querySelector("article"),
    pinkBtn = document.querySelector("#pink-btn"),
    pinkBoyBtn=document.querySelector("#pink-boy-btn"),
    pinkBoyDogBtn=document.querySelector("#pink-boy-dog-btn");


pinkBtn.addEventListener("click", function(){
  replaceTextNodes(doc, /pink/gi, "blue");
});
pinkBoyBtn.addEventListener("click", function(){
  replaceTextNodes(doc, /pink/gi, "blue");
  replaceTextNodes(doc, /boy/gi, "girl");
})
pinkBoyDogBtn.addEventListener("click", function(){
  replaceTextNodes(doc, /pink/gi, "blue");
  replaceTextNodes(doc, /boy/gi, "girl");
  replaceTextNodes(doc, /dog/gi, "cat");
})

function replaceTextNodes(node, oldText, newText) {
  node.childNodes.forEach(function(el) {
    if (el.nodeType === 3) {  // If this is a text node, replace the text
      if (el.nodeValue.trim() !== "") { // Ignore this node it it an empty text node
        el.nodeValue = el.nodeValue.replace(oldText, newText);
      }
    } else { // Else recurse on this node
      replaceTextNodes(el, oldText, newText);
    }
  });
}
article {
 border: 1px solid blue;
 padding: 2px;
}
section{
 border: 1px dotted red;
 padding: 2px;
}
<article>
      <h1>This is a pink page</h1>

      <section id="home">
        Pink pink dog cat cow chicken boy
      </section>
      <section id="about">
        this is a story about a pink dog named Clyde.
      </section>
      <section id="projects">
        And more on the Clyde story. He has a boy.
      </section>
    </article>
    
    <button id="pink-btn">'pink'-&gt;'blue'</button> | <button id="pink-boy-btn">'pink'-&gt;'blue', 'boy'-&gt;'girl'</button> | <button id="pink-boy-dog-btn">'pink'-&gt;'blue', 'boy'-&gt;'girl', 'dog'-&gt;'cat'</button>

Thing to note, I tell it what to use as the root node, and to change all the text of the elements below that. I pass as parameters a DOM node, the old text, and the new text value. For old text, I use a regex.

This is tricky, as I'm using recursion (if the current child node isn't a text node, recall my function on that child node, using it as the parent). Confusing, but very efficient.

Hope this helps!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.