Reputation: 596703
I am coding a little bookmarket to convert all the devises in the current page to another. I heavily relies on regexp, and I use Jquery to easy the work.
For now, I do that like a big fat pig, replacing all the body :
$("body").children().each(function(){
var $this = $(this);
var h = $.html().replace(/eyes_hurting_regexp/g, "my_super_result");
$this.html(h);
});
It works fine on static page, but if js events are involves, it's an Apocalypse.
The only way I can think of is to go trough all the node, check if it contains text only, then replace the text. On heavy HTML markup, I'm worried about the perfs.
Any idea out here ?
Upvotes: 1
Views: 2574
Reputation: 596703
I modified the script for my own needs and put the new version here in case somebody would need the new features :
Some comments so the code won't hurt someone else eyes :-)
function findAndReplace(searchText, replacement, callback, searchNode, blacklist) {
var regex = typeof searchText === 'string' ? new RegExp(searchText, 'g') : searchText,
childNodes = (searchNode || document.body).childNodes,
cnLength = childNodes.length,
excludes = blacklist || {'html' : '',
'head' : '',
'style' : '',
'title' : '',
'link' : '',
'meta' : '',
'script' : '',
'object' : '',
'iframe' : ''};
while (cnLength--)
{
var currentNode = childNodes[cnLength];
// see http://www.sutekidane.net/memo/objet-node-nodetype.html for constant ref
// recursive call if the node is of type "ELEMENT" and not blacklisted
if (currentNode.nodeType === Node.ELEMENT_NODE &&
!(currentNode.nodeName.toLowerCase() in excludes)) {
arguments.callee(searchText, replacement, callback, currentNode, excludes);
}
// skip to next iteration if the data is not a text node or a text that matches nothing
if (currentNode.nodeType !== Node.TEXT_NODE || !regex.test(currentNode.data) ) {
continue;
}
// generate the new value
var parent = currentNode.parentNode;
var new_node = (callback
|| (function(text_node, pattern, repl) {
text_node.data = text_node.data.replace(pattern, repl);
return text_node;
}))
(currentNode, regex, replacement);
parent.insertBefore(new_node, currentNode);
parent.removeChild(currentNode);
}
}
Example of callback function :
findAndReplace(/foo/gi, "bar", function(text_node, pattern, repl){
var wrap = document.createElement('span');
var txt = document.createTextNode(text_node.data.replace(pattern, repl));
wrap.appendChild(txt);
return wrap;
});
Thanks again, J-P, for this very helpful piece of code.
Upvotes: 0
Reputation: 111900
Unfortunately, going through each text node, step-by-step, is the only reliable way to do this. This has worked for me in the past: (demo)
function findAndReplace(searchText, replacement, searchNode) {
if (!searchText || typeof replacement === 'undefined') {
// Throw error here if you want...
return;
}
var regex = typeof searchText === 'string' ?
new RegExp(searchText, 'g') : searchText,
childNodes = (searchNode || document.body).childNodes,
cnLength = childNodes.length,
excludes = 'html,head,style,title,link,meta,script,object,iframe';
while (cnLength--) {
var currentNode = childNodes[cnLength];
if (currentNode.nodeType === 1 &&
(excludes + ',').indexOf(currentNode.nodeName.toLowerCase() + ',') === -1) {
arguments.callee(searchText, replacement, currentNode);
}
if (currentNode.nodeType !== 3 || !regex.test(currentNode.data) ) {
continue;
}
var parent = currentNode.parentNode,
frag = (function(){
var html = currentNode.data.replace(regex, replacement),
wrap = document.createElement('div'),
frag = document.createDocumentFragment();
wrap.innerHTML = html;
while (wrap.firstChild) {
frag.appendChild(wrap.firstChild);
}
return frag;
})();
parent.insertBefore(frag, currentNode);
parent.removeChild(currentNode);
}
}
Upvotes: 7