I'm trying to replace every occurrence of a word with a new HTML content. I have tried this:
function walk(node) {
var child, next;
switch ( node.nodeType ) {
case 1: // Element
case 9: // Document
case 11: // Document fragment
child = node.firstChild;
while ( child ) {
next = child.nextSibling;
walk(child);
child = next;
}
break;
case 3: // Text node
handleText(node);
break;
}
}
function handleText(textNode) {
var v = textNode.nodeValue;
var replacement = "<h1>foo</h1>";
v = v.replace(/toReplace/g, replacement);
textNode.nodeValue = v;
}
walk(document.body);
What I want is to replace all toReplace text with new actual HTML content (a foo in h1) but it just literally prints put <h1>foo</h1>
Fiddle: http://jsfiddle.net/7zkY8/
How can I fix it?
Just simply replace the document.body.innerHTML, the browser will parse the text and reconstruct the DOM
var replacement = "<h1>foo</h1>";
document.body.innerHTML = document.body.innerHTML.replace(/toReplace/g,replacement);
DEMO
This is how I did it
function handleText(textNode) {
if (textNode.nodeValue.match(/toReplace/)) {
var parent = textNode.parentNode;
var replacement = "<h1>foo</h1>";
textNode.nodeValue = textNode.nodeValue.replace(/toReplace/g, replacement);
var newSpan = document.createElement('span');
newSpan.innerHTML = textNode.nodeValue;
parent.replaceChild(newSpan, textNode);
}
}
Related
Javascript string to html without dom. This is for a content management system that stores content data like this.
Given an array:
var arr = [['underline', 'italics'], 'some paragraph'];
show output:
<u><i>some paragraph</i></u>
var getTag = function(tag, str){
var text = "";
switch (tag) {
case "":
text = `<p>${str}</p>`;
break;
case "italics":
text = `<i>${str}<i>`;
break;
case "underline":
text = `<u>${str}</u>`;
break;
case "strikethrough":
text = `<s>${str}</s>`;
break;
case "bold":
text = `<b>${str}</b>`;
break;
default:
text = ``;
}
return text;
};
arr[0].map((e) => {
console.log( getTag(e, arr[1]) );
});
expected:
<u><i>some paragraph</i></u>
actual:
<u>some paragraph</u>
<i>some paragraph</i>
One approach would be to use Array#reduce() to obtain the desired result where, for each reduce iteration, the current element of that iteration is wrapped around the accumulated result as shown below.
To obtain the required wrapping order, the elements array would first be reversed via a call to Array#reverse():
var arr = [['underline', 'italics'], 'some paragraph'];
/* Extract element and content data from arr */
const [elements, text] = arr;
/* Reduce elements to the result string to wrap result with each
element iterated */
const result = elements.reverse().reduce((acc, element) => {
switch(element) {
case 'underline':
return `<u>${acc}</u>`;
case 'italics':
return `<i>${acc}</i>`;
case 'strikethrough':
return `<s>${acc}</s>`;
case 'bold':
return `<b>${acc}</b>`;
}
return acc;
}, text);
console.log(result);
You can create a tagMap object which holds the start and end tags, first loop through the tag array to get starting tags, than add text and than loop on reversed tag to get ending tags.
let tagsMap = {
"": { start: "<p>", end: "</p>" },
"italics": { start: "<i>", end: "</i>" },
"underline": { start: "<u>", end: "</u>" },
"strikethrough": { start: "<s>", end: "</s>"},
"b": { start: "<b>", end: "</b>"}
}
let arr = [['underline', 'italics'], 'some paragraph'];
let getTag = ([tags,text]) => {
let final = ""
// adding starting tags
tags.forEach(tag => {
final += tagsMap[tag] && tagsMap[tag].start || ''
})
// adding text
final += text;
// adding ending tags
[...tags].reverse().forEach(tag => {
final += tagsMap[tag] && tagsMap[tag].end || ''
})
return final
}
console.log(getTag(arr))
One way to do this is to make getTag recursive, only returning the text when there are no more modifiers to apply:
var arr = [['underline', 'italics'], 'some paragraph'];
var getTag = function(arr){
if (!arr[0].length) return arr[1];
switch (arr[0].shift()) {
case "":
text = '<p>' + getTag(arr) + '</p>';
break;
case "italics":
text = '<i>' + getTag(arr) + '</i>';
break;
case "underline":
text = '<u>' + getTag(arr) + '</u>';
break;
case "strikethrough":
text = '<s>' + getTag(arr) + '</s>';
break;
case "bold":
text = '<b>' + getTag(arr) + '</b>';
break;
default:
text = '';
break;
}
return text;
};
console.log(getTag(arr));
By using reduce with an initial value you can do what you are looking for. Note: because of the order of arguments for reduce you have to switch the order of the arguments for the function you provided (I did not change the actual contents of the function).
Edit:
To preserve the order, you need to use reduceRight.
var arr = [['underline', 'italics'], 'some paragraph'];
var getTag = function(str, tag){
var text = "";
switch (tag) {
case "":
text = `<p>${str}</p>`;
break;
case "italics":
text = `<i>${str}<i>`;
break;
case "underline":
text = `<u>${str}</u>`;
break;
case "strikethrough":
text = `<s>${str}</s>`;
break;
case "bold":
text = `<b>${str}</b>`;
break;
default:
text = ``;
}
return text;
};
var result = arr[0].reduceRight(getTag,arr[1])
console.log(result);
Another approach is going from the last to the first element on the list of elements you are replacing.
Like:
var arr = [
['underline', 'italics'], 'some paragraph'
];
var tags = {
'underline': 'u',
'italics': 'i',
};
var formatted = arr[0].reverse().reduce((description, tag) => {
return `<${tags[tag]}>${description}</${tags[tag]}`;
}, arr[1]);
console.log(formatted);
Creates a hash with the supported tags
Reverse to process from last to first element of tags
Apply a reduce to process over the paragraph
PS: A check inside of the callback of reduce is required in case a tag is not defined on the tags hash.
I have a problem with the javascript replace function and I don't succeed to resolve it.
This is my code : https://jsfiddle.net/r36k20sa/1/
var tags = ['zazie', 'johnny'];
tags.forEach(function(element) {
content = content.replace(
new RegExp("(?!<a.*?>.*?)(\\b" + element + "\\b)(?!.*?<\\/a>)", "igm"),
'$1'
);
});
In the tags array, if I reverse the array "johnny" then "zazie" all tags are well selected otherwise, some tags are missing. (The last in this example). What can be the trick?
What can be explained that ? It seems like the javascript replace function runs asynchronous?
Thanks for your help.
Are you seriously using regex to process HTML when you have a DOM parser at your fingertips?
var content = document.getElementById('content');
function findTextNodes(root,ret) {
// recursively descend into child nodes and return an array of text nodes
var children = root.childNodes, l = children.length, i;
ret = ret || [];
for( i=0; i<l; i++) {
if( children[i].nodeType == 1) { // ElementNode
// excluding A tags here, you might also want to exclude BUTTON tags
if( children[i].nodeName != "A") {
findTextNodes(children[i],ret);
}
}
if( children[i].nodeType == 3) { // TextNode
ret.push(children[i]);
}
}
return ret;
}
var textNodes = findTextNodes(content);
// now search those text node contents for matching tags.
var tags = ['zazie','johnny'], tagcount = tags.length, regexes, tag;
for( tag=0; tag<tagcount; tag++) {
regexes[tag] = new RegExp("\b"+tags[tag]+"\b","i");
}
var node, match, index, tagtext, newnode;
while(node = textNodes.shift()) {
for( tag=0; tag<tagcount; tag++) {
if( match = node.nodeValue.match(regexes[tag])) {
index = match.index;
textNodes.unshift(node.splitText(index + tags[tag].length));
tagtext = node.splitText(index);
newnode = document.createElement('a');
newnode.href = "";
newnode.className = "esk-seo-plu-link";
newnode.style.cssText = "background:red;color:white";
tagtext.parentNode.replaceChild(newnode,tagtext);
newnode.appendChild(tagtext);
}
}
}
// and done - no more action needed since it was in-place.
See it in action
Please replace . with \\.
var tags = ['zazie', 'johnny'];
tags.forEach(function(element) {
content = content.replace(
new RegExp("(?!<a.*?>\\.*?)(\\b" + element + "\\b)(?!\\.*?<\\/a>)", "igm"),
'$1'
);
});
I have this JavaScript function that takes a string and highlight it in the html page. I'm basically trying to simulate Ctrl-F with initial value string:
Function
<script type="text/javascript">
function highlight(word) {
var node = document.body;
for (node = node.firstChild; node; node = node.nextSibling) {
var n = node;
var match_pos = 0;
match_pos = n.nodeValue.indexOf(word);
var before = n.nodeValue.substr(0, match_pos);// split into a part before the match
var middle = n.nodeValue.substr(match_pos, word.length); // the matched word to preserve case
var after = document.createTextNode(n.nodeValue.substr(match_pos + word.length));// and the part after the match
var highlight_span = document.createElement("span");// create a span in the middle
highlight_span.style.backgroundColor = "yellow";
highlight_span.appendChild(document.createTextNode(middle));// insert word as textNode in new span
n.nodeValue = before; // Turn node data into before
n.parentNode.insertBefore(after, n.nextSibling); // insert after
n.parentNode.insertBefore(highlight_span, n.nextSibling); // insert new span
highlights.push(highlight_span);
highlight_span.id = "highlight_span" + highlights.length;
node = node.nextSibling; // Advance to next node or we get stuck in a loop because we created a span (child)
}
}
</script>
Basically, The sentence I give to the function as an argument is not highlighted. Knowing that I'm positive it exists.
This Loads the HTML page
#Html.Action("GetHtmlPage", "Upload", new { path = Model.documentPath })
Then, This Calls the funtion
#{
var str = Model.sentence["sentence"].AsString;
<script>highlight(#str)</script>
}
There was a problem with your loop. Something like this will work much better.
var highlights = []
function searchElement(elem, word){
var children = Array.prototype.slice.call(elem.childNodes);
for(var i=0; i<children.length; i++){
if(children[i].nodeType == Node.TEXT_NODE){
var n = children[i];
var match_pos = n.nodeValue.indexOf(word);
if(match_pos == -1){
continue;
}
var before = n.nodeValue.substr(0, match_pos);// split into a part before the match
var middle = n.nodeValue.substr(match_pos, word.length); // the matched word to preserve case
var after = document.createTextNode(n.nodeValue.substr(match_pos + word.length));// and the part after the match
var highlight_span = document.createElement("span");// create a span in the middle
highlight_span.style.backgroundColor = "yellow";
highlight_span.appendChild(document.createTextNode(middle));// insert word as textNode in new span
n.nodeValue = before; // Turn node data into before
n.parentNode.insertBefore(after, n.nextSibling); // insert after
n.parentNode.insertBefore(highlight_span, n.nextSibling); // insert new span
highlights.push(highlight_span);
highlight_span.id = "highlight_span" + highlights.length;
}else if(children[i].childNodes.length){
searchElement(children[i], word);
}
}
}
function highlight(word) {
searchElement(document.body, word)
}
highlight("Even more test");
test
<div>
More test
<span>Even more test</span>
</div>
***EDIT -- the issue appears to be in the way I'm trying to add a class (I tried changing some CSS for the class to test and it doesn't do anything)
I want to find an element in the DOM based on text, and add a class to it, so that I can manipulate it/its parent elements
This is the function I wrote to do this (before this I'm using a function from stackoverflow to walk through the DOM , and call my function to replace the matches- (the actual string values are not important right now) -- the HTML I'm modifying is the DOM.
var MATCH = ['word'];
var REPLACE = ['other'];
function replaceText(textNode) {
var badWord = textNode.nodeValue;
var replaceWord = "";
badWord.className = "filter";
//Go through and match/replace strings equal to MATCH
for (var i=0; i< MATCH.length; i++) {
replaceWord = document.getElementsByClassName("filter").innerHTML;
replaceWord = replaceWord.replace(new RegExp('\\b' + MATCH[i] + '\\b', 'g'), REPLACE[i]);
}
textNode.nodeValue = replaceWord;
}
It works when I just directly replace the text in the word like this below - but I want to access and modify from the class, so that I can change the parent elements/css
//working version without adding class
function hide(textNode) {
var badWord = textNode.nodeValue;
//Go through and match/replace strings equal to MATCH
for (var i=0; i< MATCH.length; i++) {
badWord = badWord.replace(new RegExp('\\b' + MATCH[i] + '\\b', 'g'), REPLACE[i]);
}
textNode.nodeValue = badWord;
}
function from stackoverflow post -
walk(document.body);
function walk(node) {
var child, next;
switch (node.nodeType) {
case ELEMENT: // Element
case DOCUMENT: // Document
case DOCUMENT_FRAGMENT: // Document fragment
child = node.firstChild;
while (child) {
next = child.nextSibling;
walk(child);
child = next;
}
break;
case TEXT: // Text node
replaceText(node);
break;
}
}
I changed your replaceText() function and tested it on this page. It replaces text and adds a filter class on the nodes with replaced text. This solution uses classList.add('filter') which is not supported in IE9 and earlier, but that's no issue since this code is for a Chrome extension.
function replaceText(textNode) {
var nodeValue = textNode.nodeValue;
for (var i=0; i < MATCH.length; i++) {
if(-1 != nodeValue.indexOf(MATCH[i])) {
nodeValue = nodeValue.replace(new RegExp(MATCH[i], 'g'), REPLACE[i]);
textNode.parentNode.classList.add('filter');
}
}
textNode.nodeValue = nodeValue;
}
is there a straightforward method for searching within a div for a specific string and replacing it with another? I cannot use .replaceWith alone because there are other elements within the div I need to preserve. I've tried various javascript methods found here to no avail.
So something like:
$('#foo').find('this string').replaceWith('this other string');
for:
<div id="foo"><div id="child">Other Element</div>this string</div>
Thanks.
Try this:
var foo = $('#foo').html();
foo = foo.replace('this string', 'this other string');
$('#foo').html(foo);
Fiddle: http://jsfiddle.net/maniator/w9GzF/
This replaces all occurrences:
var $foo = $('#foo'),
fooHtml = $foo.html();
$foo.html(fooHtml.replace(/this string/g, 'this other string'));
Just using html().replace() with match all results element attribute or tag name.
I face this issue also, my solution is similar to findAndReplace() function from http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/ but using regular expression to get all textNode and search in each of them.
function epubSearch(query) {
var d = document.getElementsByTagName("body")[0];
var re = new RegExp(query, "gi");//pattern for keyword
var re0 = new RegExp("[>][^><]*[><]", "gi");//pattern to get textnode
d.innerHTML = d.innerHTML.replace(re0, function (text) {
// with each textNode, looking for keyword
return text.replace(re, "<span class=\"search-result\" style=\"background-color:red;\">$&</span>");
});
}
Here's a jQuery plugin I just wrote that provides safeReplace for collections.
(function($){
$.fn.safeReplace = function ( find, replacement ) {
return this.each(function(index, elem) {
var
queue = [elem],
node,
i;
while (queue.length) {
node = queue.shift();
if (node.nodeType === 1) {
i = node.childNodes.length;
while (i--) {
queue[queue.length] = node.childNodes[i];
}
} else if (node.nodeType === 3) {
node.nodeValue = node.nodeValue.replace( find, replacement );
}
}
});
};
})(jQuery);
And here's how you use it:
$('#foo').safeReplace( /this string/g, 'something else' );
I've only tested in FF 4, and only on the sample HTML input - more testing is recommended.
Hope this helps!
What's wrong with String.replace();?
e.g.
$("#div").html($("#div").html().replace("search string", "replace string"));
Or Exploded:
var $divElement = $("#div"); //Find the div to perform replace on
var divContent = $divElement.html(); //Get the div's content
divContent = divContent.replace("search string", "replace string"); //Perform replace
$divElement.html(divContent); //Replace contents of div element.
This one works as many times as your term appears and will not kill any of the important things that shouldn't be changed (stored in the excludes array).
usage: findAndReplace('dog','cat', document.getElementById('content'));
/* js find andreplace Based on http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/ */
function findAndReplace(searchText, replacement, searchNode) {
if (!searchText || typeof replacement === 'undefined') {
return;
}
var regex = typeof searchText === 'string' ?
new RegExp(searchText, 'g') : searchText,
childNodes = (searchNode || document.body).childNodes,
cnLength = childNodes.length,
excludes = ['html','head','style','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);
}
}