2

I want to replace specific texts on a page with javascript. For simplicty lets say I want to replace all letters A with the letter X. Important is that it's not going to break inline HTML.

Is there a simple way to iterate over all DOM elements and only change actual texts?

<span>hello world <a href="/">abcd</a>..</span>

should become

<span>hello world <a href="/">xbcd</a>..</span>

and not

<spxn>hello world <x href="/">xbcd</x>..</spxn>
boop
  • 6,834
  • 11
  • 47
  • 84
  • Assign all the spans the same class id then use JQuery to change the text of all elements with that class name. – SPlatten Sep 06 '19 at 10:57
  • Possible duplicate of [Javascript .replace command replace page text?](https://stackoverflow.com/questions/7275650/javascript-replace-command-replace-page-text) – wOxxOm Sep 06 '19 at 11:01

2 Answers2

3

Iterate over all text nodes, and change their nodeValue if they contain an a:

function getAllTextNodes() {
    var walker = document.createTreeWalker(
        document.body, 
        NodeFilter.SHOW_TEXT, 
        null, 
        false
    );

    var node;
    var textNodes = [];

    while(node = walker.nextNode()) {
        textNodes.push(node);
    }
    return textNodes;
}

getAllTextNodes().forEach((node) => {
  const { nodeValue } = node;
  const newValue = nodeValue.replace(/a/g, 'x');
  if (newValue !== nodeValue) {
    node.nodeValue = newValue;
  }
});
<a href="/">abcd</a>

You can also create a whitelist or blacklist of parents whose text nodes are changeable, if you want:

function getAllTextNodes() {
    var walker = document.createTreeWalker(
        document.body, 
        NodeFilter.SHOW_TEXT, 
        null, 
        false
    );

    var node;
    var textNodes = [];

    while(node = walker.nextNode()) {
        textNodes.push(node);
    }
    return textNodes;
}

const tagNamesToKeepUnchanged = ['SCRIPT'];

getAllTextNodes().forEach((node) => {
  if (tagNamesToKeepUnchanged.includes(node.parentNode.tagName)) {
    return;
  }
  const { nodeValue } = node;
  const newValue = nodeValue.replace(/a/g, 'x');
  if (newValue !== nodeValue) {
    node.nodeValue = newValue;
  }
});

const obj = JSON.parse(
  document.querySelector('script[type="application/json"]').textContent
);
console.log(obj.key);
<a href="/">abcd</a>
<p>foo bar</p>
<script type="application/json">{"key":"value"}</script>

This will preserve tag names, event listeners, and pretty much everything except the content of certain text nodes.

CertainPerformance
  • 313,535
  • 40
  • 245
  • 254
1

I usually use this:

/**
 * Executes operation over all text nodes in a document
 * @param {HTMLElement} element
 * @param {function(Text):void} callback
 */
function processTextNodes(element, callback) {
    // For text node, execute callback
    if (element.nodeType == Node.TEXT_NODE)
        callback(element);
    // Otherwise, loop over child nodes
    else if (element.childNodes.length > 0) {
        for (const childNode of element.childNodes) {
            if (childNode.nodeType == Node.TEXT_NODE)
                callback(childNode);
            // Recursion to child nodes
            else {
                processTextNodes(childNode, callback);
            }
        }
    }
}

For example try this:

processTextNodes(document.body, (el)=>{el.data = el.data.toUpperCase()})

I used this in several userscripts that replace words in news articles to make them more fun.