Replace all text matched except in alt or href attribute - javascript

I want to replace all matched text with another text, but I don't want replace if that text is in the alt or href attribute.
Example:
<p>Hello world!</p>
<p><img src="hello.jpg" alt="Hello"/></p>
Hello
My code:
var replacepattern = new RegExp('Hello', 'gi');
newcontent = newcontent.replace(replacepattern, function(match, contents, offset, s) {
var link = 'demo.com'
index++;
if (link != '') {
return '' + match + '';
} else {
return match;
}
});
It works perfect with text only. How can I match text except img src, alt etc?

You can use jQuery itself to help you with the replacement:
$(html)
.contents()
.filter(function() {
return this.nodeType == 1 || this.nodeType == 3;
}).each(function() {
this.textContent = this.textContent.replace(replacepattern, 'whatever');
});
Note that the last occurrence of Hello is not replaced, because it's technically invalid to have a text node as a child of <body>.
Also, you would have to modify it to work in IE < 9 or 10; basically the browser is expected to support node.textContent :)
Update
The problem was slightly more complicated; or maybe my mind is making it more difficult than it is. Replacing text nodes with jQuery ain't the easiest to do, so some pure JS is required for that:
$('<div><p>Hello world!</p><p><img src="hello.jpg" alt="Hello"/></p>Hello</div>')
.find('*')
.andSelf()
.each(function() {
for (var i = 0, nodes = this.childNodes, n = nodes.length; i < n; ++i) {
if (nodes[i].nodeType == 3) {
var txt = nodes[i].textContent || nodes[i].innerText,
newtxt = txt.replace(/Hello/g, 'Bye');
if (txt != newtxt) {
var txtnode = document.createTextNode(newtxt);
this.replaceChild(txtnode, nodes[i]);
}
}
}
})
.end()
.end()
.appendTo('body');

Related

Get XPATH of the words of an HTML which match a regular expression

Is there any way of getting some sort of XPATH for each word which matches a given regular expression? I mean the words displayed by the navigator to the user.
That way I could manipulate the HTML corresponding to each word individually.
There's not a unique XPath to a node, but here's a solution that uses the function cssPath from this answer to return the CSS path of an element that contains text matching a regex.
var cssPath = function(el) {
if (!(el instanceof Element))
return;
var path = [];
while (el.nodeType === Node.ELEMENT_NODE) {
var selector = el.nodeName.toLowerCase();
if (el.id) {
selector += '#' + el.id;
path.unshift(selector);
break;
} else {
var sib = el,
nth = 1;
while (sib = sib.previousElementSibling) {
if (sib.nodeName.toLowerCase() == selector)
nth++;
}
if (nth != 1)
selector += ":nth-of-type(" + nth + ")";
}
path.unshift(selector);
el = el.parentNode;
}
return path.join(" > ");
}
var startElem = document.body;
var items = startElem.getElementsByTagName("*");
for (var i = items.length; i--;) {
var match = items[i].innerHTML.match(/fox/);
if (match) {
var matched = document.createElement('div');
matched.innerHTML = cssPath(items[i]);
document.getElementById("matches").appendChild(matched);
}
}
<div>The quick brown fox</div>
<div>jumped over the lazy dog.</div>
<div>The slow orange ocelot</div>
<div>crawled under the quick fox.</div>
<hr>
<h4>Matches /fox/</h4>
<div id="matches"></div>
My solution:
In the DOM, I put each word in a newly created span element.
<span id="foundword1">
word1
</span>
<span id="foundword2">
word2
</span>

jQuery get numbers inside paragraph, but not those in classes

I have the following HTML structure:
<div class="content">
<p>somecontent</p>
<p>another content <span id="name-1">content</span> 1234214</p>
</div>
I want to wrap only numbers in additional span (1234214). So far I've made this:
jQuery(window).load(function() {
jQuery('.content p').html(function(index, value) {
return value.replace(/(\d+)/g, '<span class="mathjaxfont">$1</span>');
});
});
However this replaces the 1 in span id. How can I exclude checking element attributes?
You might want not only to exclude attributes (think about the h1-element for example) but constrain your replacing on the text nodes. See this questions for some ideas on how to get only and work with text nodes: How do I select text nodes with jQuery?
This answer in above question How do I select text nodes with jQuery? gives you a collection of text-nodes on which you can do your string-replacing.
You should use .contents() and .replaceWith() for this:
jQuery('.content p').contents().each(function() {
var method = this.nodeType == 1 ? 'html' : 'replaceWith';
$(this)[method](this.textContent.replace(
/(\d+)/g,
'<span class="mathjaxfont">$1</span>'
));
});
Here's a JSFiddle.
Long and hard solution, but should work in nested elements.
The idea is to handle element's .html() string character by character, wrapping numbers when they are found, but omitting numbers inside tags' definition.
Fiddle.
$(document).ready(function()
{
$('.content p').each(function()
{
$(this).html(handleHtml($(this).html()));
});
});
function handleHtml(html)
{
var resultHtml = "";
var numberStr = "";
var re = /[0-9]/;
var isTag = false, quote = "";
for (var i = 0; i < html.length; i++)
{
var char = html.substr(i, 1);
if (!isTag && re.test(char))
{
numberStr += char;
}
else
{
if (numberStr)
{
resultHtml += wrapNumber(numberStr);
numberStr = "";
}
resultHtml += char;
if (isTag && !quote && (char == '"' || char == "'"))
{
quote = char;
}
else if (quote && quote == char)
{
quote = "";
}
if (char == '<')
{
isTag = true;
}
else if (!quote && char == '>')
{
isTag = false;
}
}
}
if (numberStr)
{
resultHtml += wrapNumber(numberStr);
}
return resultHtml;
}
function wrapNumber(number)
{
return '<span class="mathjaxfont">' + number+ "</span>";
}

Highlight text of a DIV as user types characters in an input field

I have seen many posts pertaining to highlighting text in a DIV using javascript, but none do quite what I'm looking for.
What I need to do is highlight the text within a specific DIV, character by character as the user enters the search term. Conversely, as the user backspaces or deletes characters, I need to "de-highlight" the text of the same DIV.
I imagine this has already been done somewhere by someone, but I have not yet found a post here or from Google that behaves exactly as I need.
Any feedback is appreciated.
this code executes as user types characters into an input field. The problem with it is that in some instances, it inserts the string " " into the table as I type and I don't know why, so I'm searching for a different solution.
Thanks for your feedback!
function filterTable(Stxt, table) {
dehighlight(document.getElementById(table));
if (Stxt.value.length > 0)
highlight(Stxt.value.toLowerCase(), document.getElementById(table));
}
function dehighlight(container) {
for (var i = 0; i < container.childNodes.length; i++) {
var node = container.childNodes[i];
if (node.attributes && node.attributes['class'] && node.attributes['class'].value == 'highlighted') {
node.parentNode.parentNode.replaceChild(
document.createTextNode(node.parentNode.innerHTML.replace(/<[^>]+>/g, "")),node.parentNode);
return;
} else if (node.nodeType != 3) {
dehighlight(node);
}
}
}
function highlight(Stxt, container) {
for (var i = 0; i < container.childNodes.length; i++) {
var node = container.childNodes[i];
if (node.nodeType == 3) {
var data = node.data;
var data_low = data.toLowerCase();
if (data_low.indexOf(Stxt) >= 0) {
var new_node = document.createElement('span');
node.parentNode.replaceChild(new_node, node);
var result;
while ((result = data_low.indexOf(Stxt)) != -1) {
new_node.appendChild(document.createTextNode(data.substr(0, result)));
new_node.appendChild(create_node(
document.createTextNode(data.substr(result, Stxt.length))));
data = data.substr(result + Stxt.length);
data_low = data_low.substr(result + Stxt.length);
}
new_node.appendChild(document.createTextNode(data));
}
} else {
highlight(Stxt, node);
}
}
}
function create_node(child) {
var node = document.createElement('span');
node.setAttribute('class', 'highlighted');
node.attributes['class'].value = 'highlighted';
node.appendChild(child);
return node;
}
This can be easily done with a regular expression to change the div's content. Here's a simple implementation :
var s = document.getElementById('s'); // your input
var div = document.getElementById('a'); // the div to change
var t = a.textContent || a.innerText;
s.onkeyup = function(){
div.innerHTML = this.value
? t.replace(new RegExp('('+this.value+')','ig'), '<span class=highlight>$1</span>')
: t;
};
Demonstration (click "Run with JS")
EDIT :
This more sophisticated version works even if you have tables and stuff :
var s = document.getElementById('s');
var div = document.getElementById('a');
function changeNode(n, r, f) {
f=n.childNodes; for(c in f) changeNode(f[c], r);
if (n.data) {
f = document.createElement('span');
f.innerHTML = n.data.replace(r, '<span class=found>$1</span>');
n.parentNode.insertBefore(f, n);
n.parentNode.removeChild(n);
}
}
s.onkeyup = function(){
var spans = document.getElementsByClassName('found');
while (spans.length) {
var p = spans[0].parentNode;
p.innerHTML = p.textContent || p.innerText;
}
if (this.value) changeNode(
div, new RegExp('('+this.value+')','gi')
);
};
Demonstration (click "Run with JS")
My Rangy library has support for this, although I admit it's quite a large script for just this one use.
Demo: http://rangy.googlecode.com/svn/trunk/demos/textrange.html
I made a demo that uses regex.
// Input element
var input = document.getElementById("highlighter"),
// Text container element
divText = document.getElementById("text"),
// using textContent property if it exists
textProp = ("textContent" in divText) ? "textContent" : "innerText",
// Getting text to discard html tags (delete line 6 and use divText.innerHTML if you want to keep the HTML tags)
originalText = divText[textProp];
function handler(){
// if Input.value is empty clear the highlights
if(!this.value){
divText.innerHTML = originalText;
return true;
}
// Regex to group the matches, with tags 'global' and 'case insensitive'
var regex = new RegExp("("+this.value+")", "ig");
// replace text with the new one ($1 refers to first group matched by regex)
divText.innerHTML = originalText.replace(regex, "<span class='highlight'>$1</span>");
};
// adding listener to input.. IE uses attachEvent method
input.addEventListener("keyup", handler, false);
JSFiddle DEMO
let keywords = $("#highlight").html(); //get replace text
let textBody = $("#textBody"); //replace body
//create regular expression //text without case change
let custfilter = new RegExp("(" + keywords + ")", "ig");
//replace with highlight
textBody.html(textBody.html().replace(custfilter, "<b>$1</b>"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
<p id="textBody">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
<span id="highlight">IPSUM</span>

Wrapping a split string's substrings in an element

How do i use javascript/jquery to select all $('td.created') and split the html on <br>, then wrap each section in span tags (so that i can add a class to span:first in order to style it).
The format of the string which is returned from $('td.created').html() is something like
posted by User123 <br> Posted on 1/2/12 at 4:15PM
Possible universal solution (works not only for 2 lines):
$("td.created").each(function() {
var text = this.innerHTML.split("<br>");
for (var i = 0; i < text.length; i++) {
var span = $("<span />").html(text[i]);
if (i == 0) span.addClass("first");
span.appendTo("#element");
}
});​
DEMO: http://jsfiddle.net/7xQAL/
$('td.created').each(function(i, html) {
var newHtml = $(this).contents(),
spans = [];
newHtml.each(function() {
var html = (this.nodeName.toLowerCase() == 'br') ? '<br>' : '<span>' + this.textContent + '</span>';
spans.push(html);
});
$(this).html(spans.join(''));
});
DEMO ( using DIV Container)
http://jsfiddle.net/PVLek/

Javascript: how to get text nodes following/preceding break tags and wrap them with ddb tag?

I have accomplished this in Jquery but would like an implementation in Javascript without dependence on any libraries.
$("br",document).parent().contents().each(function() {
var text = this.textContent ? this.textContent : this.innerText;
text = this.textContent.replace(/\s+/g, '')
if ( this.nodeType == 3 && text.length != 0) {
$(this).wrap('<ddb></ddb>')
}
});
The following code should do the exact same thing as your function does.
<html>
<body>
Hello
<p>
Hello
<br/>
Hello 2
<br/>
<br/>
<br/>
</p>
<button onclick="wrapText()">Wrap</button>
<script type="text/javascript">
function wrapText() {
var nodeList = document.getElementsByTagName('br');
for (var i=0; i < nodeList.length; i++) {
var node = nodeList[i];
var parentNode = node.parentNode;
if (!parentNode) continue;
for (var c=0; c < parentNode.childNodes.length; c++) {
var child = parentNode.childNodes[c];
if (child.nodeType != 3) continue;
if (child.nodeValue.match(/[^\s]/) != null) {
var newElement = document.createElement("b");
newElement.innerHTML = child.nodeValue;
parentNode.insertBefore(newElement, child);
parentNode.removeChild(child);
}
}
}
}
</script>
</body>
</html>
However, I should point out that if the <br/> is wrapped in any element, you are only getting the childNodes of that element, so if it was a simple <b> tag you would only wrap text nodes inside the <b> with <ddb></ddb> (what is that, by the way?).
You also had a bug that you were assigning text to node.textContent ? node.textContent : node.innerText but then the next line simply used node.textContent, so I fixed that. I also changed the regex to simply match the first non-whitespace character, and if it did find one, it wrapped it.

Categories