Barak Gzlee
Barak Gzlee

Reputation: 98

censoring words on an html page

this is a continuation to this question the idea is still the same and the solution offered there would usually get the job done but after some testing i browsed into a site which bugged this solution see snippet

function censorWord(el, word) { 
  if (el.children.length > 0) { 
    Array.from(el.children).forEach(function(child){ 
      censorWord(child, word) 
    }) 
  } else { 
    if (el.innerText) { 
      el.innerText = el.innerText.replace(new RegExp(`\\b${word}\\b`, "g"), "***") 
    } 
  }  
}

censorWord(document.getElementById("body"),'censor')
<body id="body">
  <div>
    example of where this solution fails to work censor<br/>
    <strong>here it will work censor</strong>
  </div>
  <div>
    example of where this solution works censor
  </div>
</body>

the problem is that this solution assumes a HTML tag containing text necessarily has no child elements which isn't always the case i tried solving this by check text contained between triangle brackets but that was just a sloppy solution which would fail a lot due to elements without closing tags like <img> <br/> etc this has stumped me for a good while now

Upvotes: 0

Views: 846

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370829

The problem with your current code is if the element has both children and text that may need to be replaced, the recursive el.children.length > 0 will be entered into, and the el.innerText test section won't be.

Rather than else, instead iterate over the text nodes of the el parent regardless:

function censorWord(el, word) {
  if (el.children.length > 0) {
    Array.from(el.children).forEach(function(child) {
      censorWord(child, word)
    })
  }
  for (const child of el.childNodes) {
    if (child.nodeType === 3) {
      child.textContent = child.textContent.replace(new RegExp(`\\b${word}\\b`, "g"), "***");
    }
  }
}

censorWord(document.getElementById("body"), 'censor')
<body id="body">
  <div>
    example of where this solution fails to work censor<br/>
    <strong>here it will work censor</strong>
  </div>
  <div>
    example of where this solution works censor
  </div>
</body>

Another approach using a TreeWalker instead:

const getTextNodes = (parent) => {
    const walker = document.createTreeWalker(
        parent,
        NodeFilter.SHOW_TEXT,
        null,
        false
    );
    let node;
    const textNodes = [];
    while(node = walker.nextNode()) {
        textNodes.push(node);
    }
    return textNodes;
}

function censorWord(el, word) {
  const regex = new RegExp(`\\b${word}\\b`, "g");
  for (const textNode of getTextNodes(el)) {
    textNode.textContent = textNode.textContent.replace(regex, "***");
  }
}

censorWord(document.getElementById("body"), 'censor')
<body id="body">
  <div>
    example of where this solution fails to work censor<br/>
    <strong>here it will work censor</strong>
  </div>
  <div>
    example of where this solution works censor
  </div>
</body>

Upvotes: 1

Related Questions