I am writing a JavaScript script that will walk the DOM
and wrap a specific keyword in a <span>
tag. I want my script
to wrap any occurrences of the word pan
in a <span>
so I can style it using <span style='color: red'
. I don't actually want to use the word, pan
, I am just using it as an example.
I have already reviewed many similar posts here, but none of them solve my problem. Most are either nuclear, over-complicated and confusing, or over-simplified and do not work as I've intended.
Here is what I have written so far:
<html>
<body>
<p>My <span style='font-weight: bold'>favorite</span> kitchen item is the pan.</p>
<p>A pan can also be used as a weapon.</p>
<script>
// walk the document body
function walk (node) {
// if text node
if (node.nodeType == 3) {
// will always be an element
const parent = node.parentNode;
// ignore script and style tags
const tagName = parent.tagName;
if (tagName !== 'SCRIPT' && tagName !== 'STYLE') {
// wrap occurrences of 'pan' in a red `<span>` tag
const span = '<span style="color: red">pan</span>';
parent.innerHTML = parent.innerHTML.replace (/pan/g, span)
}
}
node = node.firstChild;
while (node) {
walk (node);
node = node.nextSibling;
}
}
walk (document.body)
</script>
</body>
</html>
This code runs as intended most of the time. However, in this example, it doesn't. If you were to run this code, this would be the result.
I know what is causing this. However, I have no idea how to resolve it.
Two of the text nodes, My
and kitchen item is the pan.
have a parent element with the following innerHTML
: My <span style="font-weight: bold">favorite</span> kitchen item is the pan.
The "pan" in <span>
is being replaced, and is causing the problem.
If I use parentNode.textContent
instead of parentNode.innerHTML
, it does not wrap it in a <span>
tag, it inserts it as visible text.
I understand this could be fixed by changing /pan/g
to /\bpan\b/g
, but that only fixes this example I created. I need the <span>
tag to be only inserted into text content, and not tag names or other HTML.
What should I do?
Search a given htmlString
with an escaped search string. Doing so (with appropriate escaping) will help avoid problems like matching HTML tags (ex. <s
pan>
) or substrings (ex. Pandora).
/*
highlight(selector, string)
@ Params:
selector [String]: Same syntax as CSS/jQuery selector
string [String]: Seach string
*/
// A: Get the htmlString of the target's content
// B: Escape the search string
// C: Create a RegExp Object of the escaped search string
// D: Find and replace all matches with match wrapped in a <mark>
// E: Remove original HTML
// F: Insert new HTML
function highlight(selector, string) {
let dom = document.querySelector(selector);
let str = dom.innerHTML; //A
let esc = `(?!(?:[^<]+>|[^>]+<\\/a>))\\b(${string})\\b`; //B
let rgx = new RegExp(esc, "gi"); //C
let txt = str.replace(rgx, `<mark>$1</mark>`); //D
dom.replaceChildren(); //E
dom.insertAdjacentHTML('beforeend', txt); //F
}
highlight('body', "pan");
<html>
<body>
<p>My <span style='font-weight: bold'>favorite</span> kitchen item is the pan.</p>
<p>A pan can also be used as a weapon.</p>
<p>Pan was the Greek god of the wild.</p>
<p>Eboli was breifly a pandemic threat.</p>
</body>
</html>