How to change all texts in DOM without breaking existing HTML? - javascript

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 abcd..</span>
should become
<span>hello world xbcd..</span>
and not
<spxn>hello world <x href="/">xbcd</x>..</spxn>

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;
}
});
abcd
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);
abcd
<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.

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.

The crawler by #CertainPerformance made JSfiddle.net crash for me.
I also need to replace the text node with an html element, so I had to move away from text nodes and I settled for more modest solution with an extensive regex lookaround to ensure html tags, properties and values are (mostly) not edited.
var list = ["crabe", "eau", "voir", "nom", "de", "des", "le", "les"];
var colorThoseWords = function(arr) {
words = arr.join('|');
// Regex lookareound: https://regular-expressions.info/lookaround.html
// Regex `negative lookbehind` and `negative lookahead`
// Run it: https://regex101.com/r/NZ5LQZ/1
var s = `(?<![<=#"'\`:;,./({[-])\\b(${words})\\b(?![>=#"'\`:)\]}-])`,
r = new RegExp(s, 'gi');
console.log({r});
$("p,li,h2,h3,a").each(function() {
var text = $(this).html();
$(this).html(text.replace(r, "<i class='signit-colored'>$1</i>"));
});
};
var uncolorWords = function() {
$(".signit-colored").each(function() {
var $text = $(this).text();
$(this).replaceWith($text);
});
};
colorThoseWords(list);
// uncolorWords();
See https://jsfiddle.net/x7f24qnv/13/
I suspect #Tomáš_Zato_-_Reinstate_Monica's transverse solution may be best if we edit the TEXT_NODE's parent.

Related

JavaScript add spaces between sentences while parsing XML string

I have this XML string which I am displaying as a text in a document:
<p>The new strain of <s alias="coronavirus">COVID</s>seems to be having a greater spread rate.</p>
The following function returns the text form of the XML:
function stripHtml(html) {
// Create a new div element
var temporalDivElement = document.createElement("div");
// Set the HTML content with the providen
temporalDivElement.innerHTML = html;
// Retrieve the text property of the element (cross-browser support)
return temporalDivElement.textContent || temporalDivElement.innerText || "";
}
The problem is, this function returns the following string:
The new strain of COVIDseems to be having a greater spread rate.
which is nearly what I want, but there is no space between the word COVID and seems. Is it possible that I can add a space between contents of two tags if it doesn't exist?
One option is to iterate over text nodes and insert spaces at the beginning if they don't exist, something like:
const getTextNodes = (parent) => {
var walker = document.createTreeWalker(
parent,
NodeFilter.SHOW_TEXT,
null,
false
);
var node;
var textNodes = [];
while(node = walker.nextNode()) {
textNodes.push(node);
}
return textNodes;
}
function stripHtml(html) {
// Create a new div element
var temporalDivElement = document.createElement("div");
// Set the HTML content with the providen
temporalDivElement.innerHTML = html;
// Retrieve the text property of the element (cross-browser support)
for (const node of getTextNodes(temporalDivElement)) {
node.nodeValue = node.nodeValue.replace(/^(?!\s)/, ' ');
}
return temporalDivElement.textContent.replace(/ +/g, ' ').trim();
}
console.log(stripHtml(`<p>The new strain of <s alias="coronavirus">COVID</s>seems to be having a greater spread rate.</p>`));

Add arrays into multi-dimensional array or object

I'm parsing content generated by a wysiwyg into a table of contents widget in React.
So far I'm looping through the headers and adding them into an array.
How can I get them all into one multi-dimensional array or object (what's the best way) so that it looks more like:
h1-1
h2-1
h3-1
h1-2
h2-2
h3-2
h1-3
h2-3
h3-3
and then I can render it with an ordered list in the UI.
const str = "<h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3>";
const patternh1 = /<h1>(.*?)<\/h1>/g;
const patternh2 = /<h2>(.*?)<\/h2>/g;
const patternh3 = /<h3>(.*?)<\/h3>/g;
let h1s = [];
let h2s = [];
let h3s = [];
let matchh1, matchh2, matchh3;
while (matchh1 = patternh1.exec(str))
h1s.push(matchh1[1])
while (matchh2 = patternh2.exec(str))
h2s.push(matchh2[1])
while (matchh3 = patternh3.exec(str))
h3s.push(matchh3[1])
console.log(h1s)
console.log(h2s)
console.log(h3s)
I don't know about you, but I hate parsing HTML using regexes. Instead, I think it's a better idea to let the DOM handle this:
const str = `<h1>h1-1</h1>
<h3>h3-1</h3>
<h3>h3-2</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();
let tree = [];
let leaf = null;
for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
const nodeLevel = parseInt(node.tagName[1]);
const newLeaf = {
level: nodeLevel,
text: node.textContent,
children: [],
parent: leaf
};
while (leaf && newLeaf.level <= leaf.level)
leaf = leaf.parent;
if (!leaf)
tree.push(newLeaf);
else
leaf.children.push(newLeaf);
leaf = newLeaf;
}
console.log(tree);
This answer does not require h3 to follow h2; h3 can follow h1 if you so please. If you want to turn this into an ordered list, that can also be done:
const str = `<h1>h1-1</h1>
<h3>h3-1</h3>
<h3>h3-2</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();
let tree = [];
let leaf = null;
for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
const nodeLevel = parseInt(node.tagName[1]);
const newLeaf = {
level: nodeLevel,
text: node.textContent,
children: [],
parent: leaf
};
while (leaf && newLeaf.level <= leaf.level)
leaf = leaf.parent;
if (!leaf)
tree.push(newLeaf);
else
leaf.children.push(newLeaf);
leaf = newLeaf;
}
const ol = document.createElement("ol");
(function makeOl(ol, leaves) {
for (const leaf of leaves) {
const li = document.createElement("li");
li.appendChild(new Text(leaf.text));
if (leaf.children.length > 0) {
const subOl = document.createElement("ol");
makeOl(subOl, leaf.children);
li.appendChild(subOl);
}
ol.appendChild(li);
}
})(ol, tree);
// add it to the DOM
document.body.appendChild(ol);
// or get it as text
const result = ol.outerHTML;
Since the HTML is parsed by the DOM and not by a regex, this solution will not encounter any errors if the h1 tags have attributes, for example.
You can simply gather all h* and then iterate over them to construct a tree as such:
Using ES6 (I inferred this is ok from your usage of const and let)
const str = `
<h1>h1-1</h1>
<h2>h2-1</h2>
<h3>h3-1</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>
`
const patternh = /<h(\d)>(.*?)<\/h(\d)>/g;
let hs = [];
let matchh;
while (matchh = patternh.exec(str))
hs.push({ lev: matchh[1], text: matchh[2] })
console.log(hs)
// constructs a tree with the format [{ value: ..., children: [{ value: ..., children: [...] }, ...] }, ...]
const add = (res, lev, what) => {
if (lev === 0) {
res.push({ value: what, children: [] });
} else {
add(res[res.length - 1].children, lev - 1, what);
}
}
// reduces all hs found into a tree using above method starting with an empty list
const tree = hs.reduce((res, { lev, text }) => {
add(res, lev-1, text);
return res;
}, []);
console.log(tree);
But because your html headers are not in a tree structure themselves (which I guess is your use case) this only works under certain assumptions, e.g. you cannot have a <h3> unless there's a <h2> above it and a <h1> above that. It will also assume a lower-level header will always belong to the latest header of an immediately higher level.
If you want to further use the tree structure for e.g. rendering a representative ordered-list for a TOC, you can do something like:
// function to render a bunch of <li>s
const renderLIs = children => children.map(child => `<li>${renderOL(child)}</li>`).join('');
// function to render an <ol> from a tree node
const renderOL = tree => tree.children.length > 0 ? `<ol>${tree.value}${renderLIs(tree.children)}</ol>` : tree.value;
// use a root node for the TOC
const toc = renderOL({ value: 'TOC', children: tree });
console.log(toc);
Hope it helps.
What you want to do is known as (a variant of a) document outline, eg. creating a nested list from the headings of a document, honoring their hierarchy.
A simple implementation for the browser using the DOM and DOMParser APIs goes as follows (put into a HTML page and coded in ES5 for easy testing):
<!DOCTYPE html>
<html>
<head>
<title>Document outline</title>
</head>
<body>
<div id="outline"></div>
<script>
// test string wrapped in a document (and body) element
var str = "<html><body><h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3></body></html>";
// util for traversing a DOM and emit SAX startElement events
function emitSAXLikeEvents(node, handler) {
handler.startElement(node)
for (var i = 0; i < node.children.length; i++)
emitSAXLikeEvents(node.children.item(i), handler)
handler.endElement(node)
}
var outline = document.getElementById('outline')
var rank = 0
var context = outline
emitSAXLikeEvents(
(new DOMParser()).parseFromString(str, "text/html").body,
{
startElement: function(node) {
if (/h[1-6]/.test(node.localName)) {
var newRank = +node.localName.substr(1, 1)
// set context li node to append
while (newRank <= rank--)
context = context.parentNode.parentNode
rank = newRank
// create (if 1st li) or
// get (if 2nd or subsequent li) ol element
var ol
if (context.children.length > 0)
ol = context.children[0]
else {
ol = document.createElement('ol')
context.appendChild(ol)
}
// create and append li with text from
// heading element
var li = document.createElement('li')
li.appendChild(
document.createTextNode(node.innerText))
ol.appendChild(li)
context = li
}
},
endElement: function(node) {}
})
</script>
</body>
</html>
I'm first parsing your fragment into a Document, then traverse it to create SAX-like startElement() calls. In the startElement() function, the rank of a heading element is checked against the rank of the most recently created list item (if any). Then a new list item is appended at the correct hierarchy level, and possibly an ol element is created as container for it. Note the algorithm as it is won't work with "jumping" from h1 to h3 in the hierarchy, but can be easily adapted.
If you want to create an outline/table of content on node.js, the code could be made to run server-side, but requires a decent HTML parsing lib (a DOMParser polyfill for node.js, so to speak). There are also the https://github.com/h5o/h5o-js and the https://github.com/hoyois/html5outliner packages for creating outlines, though I haven't tested those. These packages supposedly can also deal with corner cases such as heading elements in iframe and quote elements which you generally don't want in the the outline of your document.
The topic of creating an HTML5 outline has a long history; see eg. http://html5doctor.com/computer-says-no-to-html5-document-outline/. HTML4's practice of using no sectioning roots (in HTML5 parlance) wrapper elements for sectioning and placing headings and content at the same hierarchy level is known as "flat-earth markup". SGML has the RANK feature for dealing with H1, H2, etc. ranked elements, and can be made to infer omitted section elements, thus automatically create an outline, from HTML4-like "flat earth markup" in simple cases (eg. where only section or another single element is allowed as sectioning root).
I'll use a single regex to get the <hx></hx> contents and then sort them by x using methods Array.reduce.
Here is the base but it's not over yet :
// The string you need to parse
const str = "\
<h1>h1-1</h1>\
<h2>h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2</h1>\
<h2>h2-2</h2>\
<h3>h3-2</h3>";
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = str.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
tmp[hNumber].push(x.substr(4, x.length - 9));
return tmp;
}, {}));
console.log(matchesSorted);
As you are parsing html content I want to aware you about special cases like presency of \n or space. For example look at the following non-working snippet :
// The string you need to parse
const str = "\
<h1>h1-1\n\
</h1>\
<h2> h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2 </h1>\
<h2>h2-2 \n\
</h2>\
<h3>h3-2</h3>";
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = str.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
tmp[hNumber].push(x.substr(4, x.length - 9));
return tmp;
}, {}));
console.log(matchesSorted);
We gotta add .replace() and .trim() in order to remove unwanted \n and spaces.
Use this snippet
// The string you need to parse
const str = "\
<h1>h1-1\n\
</h1>\
<h2> h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2 </h1>\
<h2>h2-2 \n\
</h2>\
<h3>h3-2</h3>";
// Remove all unwanted \n
const preparedStr = str.replace(/(\r\n\t|\n|\r\t)/gm, "");
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = preparedStr.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
// call trim() to remove unwanted spaces
tmp[hNumber].push(x.substr(4, x.length - 9).trim());
return tmp;
}, {}));
console.log(matchesSorted);
I write this code works with JQuery. (Please don't DV. Maybe someone needs a jquery answer later)
This recursive function creates lis of string and if one item has some childern, it will convert them to an ol.
const str =
"<div><h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3></div><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3>";
function strToList(stri) {
const tags = $(stri);
function partToList(el) {
let output = "<li>";
if ($(el).children().length) {
output += "<ol>";
$(el)
.children()
.each(function() {
output += partToList($(this));
});
output += "</ol>";
} else {
output += $(el).text();
}
return output + "</li>";
}
let output = "<ol>";
tags.each(function(itm) {
output += partToList($(this));
});
return output + "</ol>";
}
$("#output").append(strToList(str));
li {
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
(This code can be converted to pure JS easily)

Get checked and undetermined nodes

I have been selected a node in jstree. I can get a json value of the selected nodes. But the problem is, I want the parent nodes of the child node , upto root
You can do this with the code as below. Check demo - Fiddle.
function getParentNode(node) {
return $('#tree').jstree().get_parent(node);
}
var nodes = $('#tree').jstree().get_selected('full'), parentNodes = [], parentTexts = [];
nodes.forEach( function(node) {
var parentNode = getParentNode(node);
while (parentNode && parentNode !=='#') {
if (parentNodes.indexOf(parentNode) === -1 ) {
parentNodes.push( parentNode );
parentTexts.push( $('#tree').jstree().get_node(parentNode).text );
}
parentNode = getParentNode(parentNode);
}
})
UPDATE
The code you use could look like:
var checked_ids = [], checked_ids1 = [];
$("#temporary1").find(".jstree-undetermined").each(
function(i, element) {
var nodeId = $(element).closest('.jstree-node').attr("id");
// alert( nodeId );
checked_ids.push( nodeId );
checked_ids1.push( $('#temporary1').jstree().get_node( nodeId ).text );
}
);
Try using jstree's get_path. The way I have it below will return to the console an array of the parents of the selected node.
* get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
$('#jstree').jstree().on('changed.jstree', function(e, data) {
console.log(data.instance.get_path(data.node, undefined, true));
});

How to select this text using pure javascript, no jquery?

I need to select some text using javascript (no jquery), and Im not sure how to do it. I need to select the text ('Hello World') in myClass element before the first child element. Here are two examples, sometimes it will be an <img> tag as the first element and sometimes it will be a <br> tag. I also need to select the text between the <br> tags.
<p class="myClass" style="padding: 5px;">
Hello World
<img src="http://xyz.com/image.gif">
<br>
30-12-2011 19:45
<br>
Testing
<br>
</p>
<p class="myClass" style="padding: 5px;">
Hello World
<br>
30-12-2011 19:45
<br>
Testing
<br>
</p>
Edit note: I also need to select the other text nodes in myClass. And note that sometimes there will be an <img> as one of the child elements and sometimes there won't.
So I would like to end up with
var a = 'Hello World'
var b = '30-12-2011 19:45'
var c = 'Testing'
Anyone able to do this?
Not really sure what you want. If you want to get all the text, then use:
var text = element.innerText || element.textContent;
If you want to get the text in several pieces, then you have to iterate over all child nodes and extract the text nodes:
var texts = [],
children = element.childNodes;
for(var i = 0, len = children.length; i < len; i++) {
var node = children[i];
if(node.nodeType === 3) {
var text = node.nodeValue.replace(/^\s+|\s+$/g, '');
if(text.length > 0) {
texts.push(text);
}
}
}
This would not concatenate consecutive text nodes though, which can occur if you inserted new text nodes or split text nodes with JavaScript. In this case, you could do this:
var texts = [''],
children = element.childNodes,
new_bucket = false,
bucket = 0;
for(var i = 0, len = children.length; i < len; i++) {
var node = children[i];
if(node.nodeType === 1 && new_bucket) {
new_bucket = false;
texts[++bucket] = '';
}
else if(node.nodeType === 3) {
var text = node.nodeValue.replace(/^\s+|\s+$/g, '');
if(text.length > 0) {
new_bucket = true;
texts[bucket] += text
}
}
}
var elem = document.getElementsByClassname("myClass")[0],
txtNode = elem.childNodes[0],
txtNodeValue = txtNode.nodeValue;
console.log(txtNodeValue);
Example.
We select the element by its class ([0] just tells it to select the first element in the matched set) and then look for its first child node. In your case this would be the text node, so all we have to do from there is get the nodeValue.
This will get the first element with the class myClass and the first text node of that element:
document.getElementsByClassName("myClass")[0].childNodes[0].nodeValue
Here is a great article explaining.
You will most likely want to trim the string as well to remove white-space. You can add the following to give you trim functionality:
if(typeof String.prototype.trim !== 'function') {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
}
}
...and use it as such:
var trimmedString = document.getElementsByClassName("myClass")[0].childNodes[0].nodeValue.trim();
http://jsfiddle.net/kgdnR/1/
var abc = [].filter.call((document.getElementsByClassName("myClass")[0].childNodes), function(node) {
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length) {
return true;
}
return false;
}).map(function(node) {
return node.nodeValue.trim();
});
var a = abc[0],
b = abc[1],
c = abc[2];
console.log(a, b, c);
Can be made work in IE but if you wanted that you might as well use jQuery.
I dont know how regular your example is but you can use the document object to interact with the DOM.
select the myClass elements
var elements = document.getElementsByClassName('myClass');
then take the first index , its childNodes and the first index of that which is a text node
var nodes = elements[0].childNodes;
for (var i = 0; i < nodes.length; i++)
{
var node = nodes[i],
text;
// loop over the nodes and see if its as textnode. Then trim and skip empty strings
if(node.nodeType === 3) {
text = node.textContent.trim();
if(text) {
alert(text);
}
}
}
first of all, you can use
document.getElementsByClassName('test')
to select the elements using the class name.
select the element you want (it returns an array), and then use
nodeValue
which will return the text.
With using XPath expression:
var contextNode = document.body;
var node, textNodes = [];
var search = document.evaluate('.//text()', contextNode, null, XPathResult.ANY_TYPE);
while(node = search.iterateNext()) textNodes.push(node);
Selecting text as asked by the OP:
var my_class = document.getElementsByClassName('myClass')[0];
window.getSelection().selectAllChildren(my_class);

JavaScript: Add elements in textNode

I want to add an element to a textNode. For example: I have a function that search for a string within element's textNode. When I find it, I want to replace with a HTML element. Is there some standard for that?
Thank you.
You can't just replace the string, you'll have to replace the entire TextNode element, since TextNode elements can't contain child elements in the DOM.
So, when you find your text node, generate your replacement element, then replace the text node with a function similar to:
function ReplaceNode(textNode, eNode) {
var pNode = textNode.parentNode;
pNode.replaceChild(textNode, eNode);
}
For what it appears you want to do, you will have to break apart the current Text Node into two new Text Nodes and a new HTML element. Here's some sample code to point you hopefully in the right direction:
function DecorateText(str) {
var e = document.createElement("span");
e.style.color = "#ff0000";
e.appendChild(document.createTextNode(str));
return e;
}
function SearchAndReplaceElement(elem) {
for(var i = elem.childNodes.length; i--;) {
var childNode = elem.childNodes[i];
if(childNode.nodeType == 3) { // 3 => a Text Node
var strSrc = childNode.nodeValue; // for Text Nodes, the nodeValue property contains the text
var strSearch = "Special String";
var pos = strSrc.indexOf(strSearch);
if(pos >= 0) {
var fragment = document.createDocumentFragment();
if(pos > 0)
fragment.appendChild(document.createTextNode(strSrc.substr(0, pos)));
fragment.appendChild(DecorateText(strSearch));
if((pos + strSearch.length + 1) < strSrc.length)
fragment.appendChild(document.createTextNode(strSrc.substr(pos + strSearch.length + 1)));
elem.replaceChild(fragment, childNode);
}
}
}
}
Maybe jQuery would have made this easier, but it's good to understand why all of this stuff works the way it does.

Categories

Resources