How to select a <div> block inside a text? - javascript

I have a text composed of two <div> inside one <body> saved as raw_text as following:
var raw_text = "<body><div>This is the 'div' text that I don't want.</div> <div>This is the 'div' text that I want to print.</div></body>";
I need a script for print on the screen only the <div> present in raw-text that include a certain string.
if the string wanted is:
var x = "that I want";
the script should take:
<div>This is the 'div' text that I want to print.</div>
and the output should be:
This is the 'div' text that I want to print.

This is the proper way to do it:
Use a DOM parser
Iterate the text nodes
Check if they contain the desired string
var html = "<body><div>This is the 'div' text that I don't want.</div> <div>This is the 'div' text that I want to print.</div></body>";
var x = "that I want";
var doc = new DOMParser().parseFromString(html, 'text/html');
var it = doc.createNodeIterator(doc.body, NodeFilter.SHOW_TEXT);
var node;
while (node = it.nextNode()) if(node.nodeValue.includes(x)) {
console.log(node.nodeValue);
break;
}

var raw_text = "<body><div>This is the 'div' text that I don't want.</div> <div>This is the 'div' text that I want to print.</div></body>";
var x = "that I want";
var homework_solution = raw_text.match(new RegExp("<div>([^<>]*?"+x+"[^<>]*?)</div>"))[1];
This should do the job. The regex could possibly be made a bit more robust.
The "proper" way to do this would be to use DOMParser to search for the node you want.

You can use jQuery to convert your string to proper DOM elements, and then parse them easily, as #Retr0spectrum says on their comment. You have the HTML in a plain string:
var htmlString = "<body><div>This is the 'div' text that I don't want.</div> <div>This is the 'div' text that I want to print.</div></body>";
Now you have to:
parse it to DOM,
filter the elements, and
get the text
Like this:
// Process the string through jQuery so it parses the DOM elements
var dom = $(htmlString);
// and then we convert to array...
var array = dom.toArray();
// ... so we can filter it, using RegEx to find the
// <div>(s) we are interested in:
var matchingDivs = array.filter(function (div, i) {
return $(div).text().match(/that I want/g) !== null;
});
// we pop the last matched div from the filtered array (the first
// one would also work, since normally you will find just one)
var theDiv = matchingDivs.pop();
// Then get the <div>'s text:
var theText = selectedDiv.textContent;
The beautiful thing is you can chain all the methods so you can write the above like this:
var theText = $(htmlString).toArray().filter(function (div, i) {
return $(div).text().match(/that I want/g) !== null;
})[0].textContent;
Note: In the chained methods example I took the first element instead of the last one, using the bracket operator [0] instead of pop().
Hope this helps understanding how it works.

Related

How can I create a syntax like vue js in vanilla JavaScript?

<div id="">
<span>{{msg}}</span>
</div>
Let's think msg is variable of JavaScript and now I want to get the parent tag of {{msg}} and push a new value by innerHTML, here {{msg}} working as an identity.
demo JavaScript example:
<script>
var msg = "This is update data";
{{msg}}.parentElement.innerHTML=msg;
</scritp>
This is not actual JavaScript code, only for better understanding.
You can use jquery easily to find that element and then replace the text
var msg = "This is update data";
$(`span:contains(${msg})`).html("Its New");
In javascript:
var spanTags = document.getElementsByTagName("span");
var msg = "This is update data";
var found;
for (var i = 0; i < spanTags.length; i++) {
if (spanTags[i].textContent == msg) {
found = spanTags[i];
break;
}
}
Now, you have found that element in found and you can now change its text
if (found) {
found.innerHTML = "New text";
}
The simplest approach is to treat the entire document as a string and then re-parse it when you're done.
The .innerHTML property is both an HTML decompiler and compiler depending on weather you're reading or writing to it. So for example if you have a list of variables that you want to replace in your document you can do:
let vars = {
msg: msg, // pass value as variable
test_number: 10, // pass value as number
test_str: 'hello' // pass value as string
};
let htmlText = document.body.innerHTML;
// find each var (assuming the syntax is {{var_name}})
// and replace with its value:
for (let var in vars) {
let pattern = '\\{\\{\\s*' + var + '\\s*\\}\\}';
let regexp = new RegExp(pattern, 'g'); // 'g' to replace all
htmlText = htmlText.replace(regexp, vars[var]);
}
// Now re-parse the html text and redraw the entire page
document.body.innerHTML = htmlText;
This is a quick, simple but brutal way to implement the {{var}} syntax. As long as you've correctly specified/designed the syntax to make it impossible to appear in the middle of html tags (for example <span {{ msg > hello </ }} span>) then this should be OK.
There may be performance penalties redrawing the entire page but if you're not doing this all the time (animation) then you would generally not notice it. In any case, if you are worried about performance always benchmark your code.
A more subtle way to do this is to only operate on text nodes so we don't accidentally mess up real html tags. The key to doing this is to write your own recursive descent parser. All nodes have a .childNodes attribute and the DOM is strictly a tree (non-cyclic) so we can scan the entire DOM and search for the syntax.
I'm not going to write complete code for this because it can get quite involved but the basic idea is as follows:
const TEXT_NODE = 3;
let vars = {
msg: msg, // pass value as variable
test_number: 10, // pass value as number
test_str: 'hello' // pass value as string
};
function walkAndReplace (node) {
if (node.nodeType === TEXT_NODE) {
let text = node.nodeValue;
// Do what you need to do with text here.
// You can copy the RegExp logic from the example above
// for simple text replacement. If you need to generate
// new DOM elements such as a <span> or <a> then remove
// this node from its .parentNode, generate the necessary
// objects then add them back to the .parentNode
}
else {
if (node.childNodes.length) {
for (let i=0; i<node.childNodes.length; i++) {
walkAndReplace(node.childNodes[i]); // recurse
}
}
}
}
walkAndReplace(document.body);

Text Highlighting using native javascript no jquery

I am currently working in native JS and I am trying to build the highlight text feature in a contenteditable div. I have successfully built the highlight feature but I am encountering a problem when I want to toggle between the highlight and unhighlight text using a single button. So I am getting the selected text and the range of the selected text via
var selectedText = window.getSelection();
var range = selectedText.getRangeAt(0);
and I am wrapping the selected text using surroundContents that is a function of range object.
var wrapper = document.createElement("span");
wrapper.setAttribute("class","highlight");
But now when I am trying to unhighlight some part of the highlighted text and some part of plain text the natural behavior should unhighlight the highlighted text and highlight the plain text. To achieve this I am cloning the range via
var clone = range.cloneContents()
var nodeInBetween = clone.childNodes //array of nodes between the start and end nodes.
Now there are two problems I am facing. First I need to remove the span.highlight nodes and replace it with a TextNode again in order to make it unhighlight-ed and I need some method to wrap a textnode with a span. Unfortunately there is no way to wrap a textnode as one can for range variable.
I have experimented with a (recursive) highlighter method in this jsFiddle. It may be of use to you. The actual method:
function highLight(term,root,forElements,styleclass){
root = root || document.querySelector('body');
term = term instanceof Array ? term.join('|') : term;
if (!term) {throw TypeError('Highlighter needs a term to highlight anything');}
forElements = forElements && forElements instanceof Array
? forElements.join(',')
: /string/i.test(typeof forElements) ? forElements : '*';
styleclass = styleclass || 'highlight';
var allDiv = root.querySelectorAll(forElements),
re = RegExp(term,'gi'),
highlighter = function(a){return '<span class="'+styleclass+'">'+a+'</span>'};
for (var i=0; i<allDiv.length; i+=1){
// recurse children
if (allDiv[i].querySelectorAll(forElements).length){
highLight.call(null,term, allDiv[i],forElements,styleclass);
}
// replace term(s) in text nodes
var node = allDiv[i];
for (node=node.firstChild; node; node=node.nextSibling) {
if (node.nodeType===3){
var re = RegExp(term,'gi');
node.data = node.data.replace(re,highlighter);
}
}
}
//finally, replace all text data html encoded brackets
root.innerHTML = root.innerHTML
.replace(/</gi,'<')
.replace(/>/gi,'>');
}

How to the text within a pair of tags, like "my head" in "<h1>my head</h1>", by Javascript?

E.g.
<h1>my head</h1>
As I know, the below codes can print the h1,
var el = document.getElementsByTagName('h1')[0];
alert("tag : "+el.tagName);
But, how could I get the text between a pair of tags, i.e. my head ?
Use element.innerHTML
var el = document.getElementsByTagName('h1')[0];
alert("tag : "+el.innerHTML);
alert(el.firstChild.data);
This is the normal way with Dom
try by regx
var str = "<h1>my head</h1> ";
var result = str.match(/<h1>(.*?)<\/h1>/g).map(function(val){
return val.replace(/<\/?h1>/g,'');
});
by element.innerHTML
function getValue(){
var x=document.getElementsByTagName("h1");
alert(x.innerHTML);
}
Use el.firstChild.nodeValue
var el = document.getElementsByTagName('h1')[0];
alert("tag : "+el.firstChild.nodeValue);
Using innerHtml will also return the text value in this case since there are no child elements however if it had any they would also be returned as a string (e.g., my head) which doesn't sound like what you want.

Create string from HTMLDivElement

What I would like to be able to do is create a string from a Javascript HTMLElement Object. For example:
var day = document.createElement("div");
day.className = "day";
day.textContent = "Random Text";
Now we have create the day HTMLDivElement Object is it possible to make it print as a string? e.g.
<div class="day">Random Text</div>
Variant on Gump's wrapper, since his implementation lifts the target node out of the document.
function nodeToString ( node ) {
var tmpNode = document.createElement( "div" );
tmpNode.appendChild( node.cloneNode( true ) );
var str = tmpNode.innerHTML;
tmpNode = node = null; // prevent memory leaks in IE
return str;
}
To print the resulting string on screen (re: escaped)
var escapedStr = nodeToString( node ).replace( "<" , "<" ).replace( ">" , ">");
outputNode.innerHTML += escapedStr;
Note, attributes like "class" , "id" , etc being stringified properly is questionable.
You can use this function (taken from pure.js)
function outerHTML(node){
return node.outerHTML || new XMLSerializer().serializeToString(node);
}
A few years have passed since the last answers. So here is an easier approach:
I found out that .outerHTML is supported by all major browsers now (see caniuse).
You can use it to get the HTML of an JS element with ease:
// Create a sample HTMLDivElement
var Day = document.createElement("div");
Day.className = "day";
Day.textContent = "Random Text";
// Log the element's HTML to the console
console.log(Day.outerHTML)
This will log: <div class="day">Random Text</div>
You can wrap that element into another element and use innerHTML on it:
var wrapper = document.createElement("div");
wrapper.appendChild(day);
var str = wrapper.innerHTML;
You need to create text node to add text for your created element like this:
var day = document.createElement("div");
day.className = "day";
// create text node
var txt = document.createTextNode('Random Text');
// add text to div now
day.appendChild(txt);
// append to body
document.body.appendChild(day);
Why would you use createElement if you can also directly parse a string?
Like: var string = '<div class="' + class + '">' + text + '</div>';
My element was a object with element : HTMLDivElement, so this worked for me.
console.log(row.element.outerHTML);
If you have just HTMLDivElement, then this should work:
console.log(row.outerHTML);
Simple use the function outerHTML
var anc = document.createElement("a");
anc.href = "https://developer.mozilla.org?a=b&c=d";
console.log(anc.outerHTML); // output: "<a href='https://developer.mozilla.org?a=b&c=d'></a>"
know more

How do to wrap a span around a section of text without using jQuery

<p>Lorem Ipsum Link <div ... </div> </p>
I want to put a span around 'Lorem Ipsum' without using jQuery, so the result looks like:
<p><span>Lorem Ipsum </span>Link <div ... </div> </p>
Any ideas? Thanks
First, you need some way of accessing the paragraph. You might want to give it an id attribute, such as "foo":
<p id="foo">Lorem Ipsum Link <div ... </div> </p>
Then, you can use document.getElementById to access that element and replace its children as required:
var p = document.getElementById('foo'),
firstTextNode = p.firstChild,
newSpan = document.createElement('span');
// Append "Lorem Ipsum" text to new span:
newSpan.appendChild( document.createTextNode(firstTextNode.nodeValue) );
// Replace old text node with new span:
p.replaceChild( newSpan, firstTextNode );
To make it more reliable, you might want to call p.normalize() before accessing the first child, to ensure that all text nodes before the anchor are merged as one.
Oook, So you want to replace a part of a text node with an element. Here's how I'd do it:
function giveMeDOM(html) {
var div = document.createElement('div'),
frag = document.createDocumentFragment();
div.innerHTML = html;
while (div.firstChild) {
frag.appendChild( div.firstChild );
}
return frag;
}
var p = document.getElementById('foo'),
firstChild = p.firstChild;
// Merge adjacent text nodes:
p.normalize();
// Get new DOM structure:
var newStructure = giveMeDOM( firstChild.nodeValue.replace(/Lorem Ipsum/i, '<span>$&</span>') );
// Replace first child with new DOM structure:
p.replaceChild( newStructure, firstChild );
Working with nodes at the low level is a bit of a nasty situation to be in; especially without any abstraction to help you out. I've tried to retain a sense of normality by creating a DOM node out of an HTML string produced from the replaced "Lorem Ipsum" phrase. Purists probably don't like this solution, but I find it perfectly suitable.
EDIT: Now using a document fragment! Thanks Crescent Fresh!
UPDATE:
The method below will search through the subtree headed by container and wrap all instances of textin a span element. The words can occur anywhere within a text node, and the text node can occur at any position in the subtree.
(OK, so it took more than a few minor tweaks. :P)
function wrapText(container, text) {
// Construct a regular expression that matches text at the start or end of a string or surrounded by non-word characters.
// Escape any special regex characters in text.
var textRE = new RegExp('(^|\\W)' + text.replace(/[\\^$*+.?[\]{}()|]/, '\\$&') + '($|\\W)', 'm');
var nodeText;
var nodeStack = [];
// Remove empty text nodes and combine adjacent text nodes.
container.normalize();
// Iterate through the container's child elements, looking for text nodes.
var curNode = container.firstChild;
while (curNode != null) {
if (curNode.nodeType == Node.TEXT_NODE) {
// Get node text in a cross-browser compatible fashion.
if (typeof curNode.textContent == 'string')
nodeText = curNode.textContent;
else
nodeText = curNode.innerText;
// Use a regular expression to check if this text node contains the target text.
var match = textRE.exec(nodeText);
if (match != null) {
// Create a document fragment to hold the new nodes.
var fragment = document.createDocumentFragment();
// Create a new text node for any preceding text.
if (match.index > 0)
fragment.appendChild(document.createTextNode(match.input.substr(0, match.index)));
// Create the wrapper span and add the matched text to it.
var spanNode = document.createElement('span');
spanNode.appendChild(document.createTextNode(match[0]));
fragment.appendChild(spanNode);
// Create a new text node for any following text.
if (match.index + match[0].length < match.input.length)
fragment.appendChild(document.createTextNode(match.input.substr(match.index + match[0].length)));
// Replace the existing text node with the fragment.
curNode.parentNode.replaceChild(fragment, curNode);
curNode = spanNode;
}
} else if (curNode.nodeType == Node.ELEMENT_NODE && curNode.firstChild != null) {
nodeStack.push(curNode);
curNode = curNode.firstChild;
// Skip the normal node advancement code.
continue;
}
// If there's no more siblings at this level, pop back up the stack until we find one.
while (curNode != null && curNode.nextSibling == null)
curNode = nodeStack.pop();
// If curNode is null, that means we've completed our scan of the DOM tree.
// If not, we need to advance to the next sibling.
if (curNode != null)
curNode = curNode.nextSibling;
}
}
Combine these 2 tutorials:
PPK on JavaScript: The DOM - Part 3
Adding elements to the DOM
Basically you need to access the node value, remove it, and create a new child element who's node value is the value of the parent item's node, then append that element (span in this case) to the parent (paragraph in this case)
How about using a regular expression in javascript and replacing "Lorem Ipsum" with "<span>Lorem Ipsum</span>" (just remember that you will have to get the "innerHTML" of the element and then replace the whole lot again which may be a bit slow)

Categories