Home > Mobile >  Search And Replace Text But Exclude Certain Tags
Search And Replace Text But Exclude Certain Tags

Time:09-09

This seems simple but finding it really hard to suss out tried a tonne of stuff.

Basically I want to search the dom with js and find every instance of a word and replace it with another one. This is easy to do.

function replaceAllText(text) {
    var walker = document.createTreeWalker(
        document.body,
        NodeFilter.SHOW_ELEMENT,
        null,
        false
    );
    while (walker.nextNode()) {
        walker.currentNode.innerHTML = walker.currentNode.innerHTML.replaceAll(/hello/ig, text);
    }
}

replaceAllText('Holla');

The hard bit is I want to do it only on certain tags for instance I dont want it to replace text inside A tags.

Now I thought skipping A tags would be easy I could do it like this.

function replaceAllText(text) {
    var walker = document.createTreeWalker(
        document.body,
        NodeFilter.SHOW_ELEMENT,
        null,
        false
    );
    while (walker.nextNode()) {
        if (walker.currentNode.tagName !== 'A') {
            walker.currentNode.innerHTML = walker.currentNode.innerHTML.replaceAll(/hello/ig, text);
        }
    }
}

replaceAllText('Holla');

This still places the text inside A tags.

I have an example here I am testing with enter image description here

To this see its replacing all instances of hello with holla except inside the A tag.

enter image description here

CodePudding user response:

We need to look at the text nodes then

Note that the body in a stacksnippet includes the whole document so I exclude the script tag too

Also note I create a regex and test and replace case INsensitive

Lastly I also ignore tags INSIDE a link

const replaceAllText = (fromText, toText) =>  {
  document.body.querySelectorAll("*")
    .forEach(tag => {
      if (tag.closest("A")) return; // A and nested tags inside A tags
      if (["SCRIPT"].includes(tag.tagName)) return; // we can add more here
      [...tag.childNodes].forEach(node => {
        if (node.nodeType === Node.TEXT_NODE) {
          const nodeText = node.textContent;
          if (!nodeText.toUpperCase().includes(fromText.toUpperCase())) return; 
          console.log(nodeText);
          node.textContent = nodeText.replace(new RegExp(fromText,"ig"), toText)
        }
      });
    });
}

replaceAllText('Hello','Holla');
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>

<body>
  <p>this is some test hello world</p>
  <div >Hello world! <a href="">Hello</a></div>
  <div >Hello world! <a href=""><span>Hello</span></a></div>
</body>

</html>

CodePudding user response:

Your current approach looks good. You only need to take into account that innerHTML returns all html content, including nested tags. That's why your function replaced also text inside a tags.

One way to make it work is to take only text nodes, and filter out text nodes that are inside a tags, i.e. whose parent node's tag name is A.

var rejectScriptTextFilter = {
  acceptNode: function(node) {
    if (node.parentNode.nodeName !== 'A') {
      return NodeFilter.FILTER_ACCEPT;
    }
  }
};

function replaceAllText(text) {
    var walker = document.createTreeWalker(
        document.body,  
        NodeFilter.SHOW_TEXT,  
        rejectScriptTextFilter,
        false
    );
    
    while (walker.nextNode()) {
        var node = walker.currentNode;
        node.nodeValue = node.nodeValue.replaceAll(/hello/ig, text);
    }
}

replaceAllText('Holla');
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <p>this is some test hello world</p>
  <div >Hello world! <a href="">Hello</a></div>
</body>
</html>

  • Related