JavaScript - Extract specific nodes along with the node start position - javascript

Following is a sample node,
<div>Hell<span class="locate">Q1</span>o <b>w<span class="locate">Q2</span>or</b>ld</div>
My goal is to extract all the 'locate' class nodes along with their start character/text position (So later, i can reuse that text/character position to inject the node)
Ex:
<div>Hell<span class="locate">Q1</span>o <b>w<span class="locate">Q2</span>or</b>ld</div>
Extract something like
Output:
1. Extraction
[
{
"start": 5,
"node": "<span class='locate'>Q1</span>"
},
{
"start": 9,
"node": "<span class='locate'>Q2</span>"
}
]
Removal of locate nodes to Hello world`
So far what I've tried:
treeWalker = document.createTreeWalker(input, NodeFilter.SHOW_ALL);
while(treeWalker.nextNode()) {
temp = {};
currentNode = treeWalker.currentNode;
if (currentNode.parentNode.tagName.toLowerCase() === 'div') {
totalText += currentNode.textContent.length;
if (
currentNode.className &&
currentNode.className.toLowerCase() === 'locate'
) {
temp.startPosition = totalText;
temp.node = currentNode.cloneNode(true);
collectorArray.push(temp);
console.log(currentNode, totalText);
} else {
updatedNode.appendChild(currentNode.cloneNode(true));
console.log(currentNode, totalText);
}
}
}
I tried to use TreeWalker to collect nodes and start position, but, I'm not getting no where.
I also feel the way i calculate the text length is wrong. May be a better way?
The idea of the whole process is, reapply the collected locate nodes after some text change occurs, using the start position and node collected.

Try this (sorry for bad indentation in the snippet):
var nodeIterator = document.createNodeIterator(
document.getElementById('someId'),
NodeFilter.SHOW_ELEMENT,
{ acceptNode: function(node) {
if ( node.className == 'locate' ) {
return NodeFilter.FILTER_ACCEPT;
}
}
},
false
);
var node;
var doc = document.getElementById('someId').textContent;
var result = [];
var currentStart = 0;
while ((node = nodeIterator.nextNode())) {
var tempDoc = doc.slice(currentStart, doc.length);
var idx = tempDoc.indexOf(node.textContent);
var temp = {};
temp.start = currentStart + idx;
temp.node = node.outerHTML;
currentStart += (idx + node.textContent.length);
result.push(temp);
}
console.log(result)
<div id="someId">Hell<span class="locate">Q1</span>o <b>w<span class="locate">Q1</span>or</b>ld<span class="locate">Q1</span></div>
Note, that I use SHOW_ELEMENT because you distinguish what is needed by elements class name. So later you can just set a rule in iterator that accepts only those nodes.
And then I get textContent of the root element and get index value of accepted nodes from iterator object.

Related

Javascript replace function error

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'
);
});

Javascript RegEx - Split Html-string

I'm working on a script and need to split strings which contain both html tags and text. I'm trying to isolate the text and elimanate the tags
For example, I want this:
string = '<p><span style="color:#ff3366;">A</span></p><p><span style="color:#ff3366;text-decoration:underline;">B</span></p><p><span style="color:#ff3366;text-decoration:underline;"><em>C</em></span></p>';
to be split like this:
separation = string.split(/some RegExp/);
and become:
separation[0] = "<span style="color:#ff3366;">A</span>";
separation[1] = "<span style="color:#ff3366;text-decoration:underline;">B</span>";
separation[2] = "<span style="color:#ff3366;text-decoration:underline;"><em>C</em></span>";
After that I would like to split the sepeartion string like this:
stringNew = '<span style="color:#ff3366;">A</span>';
extendedSeperation = stringNew.split(/some RegExp/);
extendedSeperation[0] = "A";
extendedSeperation[1] = "style="color:#ff3366;";
Don't use RegEx for reasons explained in comments.
Instead, do this:
Create an invisible node:
node = $("<div>").css("display", "none");
Attach it to the body:
$("body").append(node);
Now inject your HTML into the node:
node.html(myHTMLString);
Now you can traverse the DOM tree and extract/render it as you like, much like this:
ptags = node.find("p") // will return all <p> tags
To get the content of a tag use:
ptags[0].html()
Finally, to clear the node do:
node.html("");
This should be enough to get you going.
This way you leverage the internal parser of the browser, as suggested in the comments.
Your exact expectations are a little unclear, but based only on the information given here is an example that may give you ideas.
Does not use RegExp
Does not use jQuery or any other library
Does not append and remove elements from the DOM
Is well supported across browsers
function walkTheDOM(node, func) {
func(node);
node = node.firstChild;
while (node) {
walkTheDOM(node, func);
node = node.nextSibling;
}
}
function textContent(node) {
if (typeof node.textContent !== "undefined" && node.textContent !== null) {
return node.textContent;
}
var text = ""
walkTheDOM(node, function (current) {
if (current.nodeType === 3) {
text += current.nodeValue;
}
});
return text;
}
function dominate(text) {
var container = document.createElement('div');
container.innerHTML = text;
return container;
}
function toSeparation(htmlText) {
var spans = dominate(htmlText).getElementsByTagName('span'),
length = spans.length,
result = [],
index;
for (index = 0; index < length; index += 1) {
result.push(spans[index].outerHTML);
}
return result;
}
function toExtendedSeperation(node) {
var child = dominate(node).firstChild,
attributes = child.attributes,
length = attributes.length,
text = textContent(child),
result = [],
style,
index,
attr;
if (text) {
result.push(text);
}
for (index = 0; index < length; index += 1) {
attr = attributes[index]
if (attr.name === 'style') {
result.push(attr.name + '=' + attr.value);
break;
}
}
return result;
}
var strHTML = '<p><span style="color:#ff3366;">A</span></p><p><span style="color:#ff3366;text-decoration:underline;">B</span></p><p><span style="color:#ff3366;text-decoration:underline;"><em>C</em></span></p>',
separation = toSeparation(strHTML),
extendedSeperation = toExtendedSeperation(separation[0]),
pre = document.getElementById('out');
pre.appendChild(document.createTextNode(JSON.stringify(separation, null, 2)));
pre.appendChild(document.createTextNode('\n\n'));
pre.appendChild(document.createTextNode(JSON.stringify(extendedSeperation, null, 2)));
<pre id="out"></pre>
Of course you will need to make modifications to suit your exact needs.

Finding path using algorithm A*

hello i'd like to ask some javascript code in finding path using algorithm A*. the path will have source node and target node, and here's the example code
do{
var cursor = nodes.node1 //this is source node
for (var i in GexfJS.graph.edgeList) { // search all edgeList on graph
var _e = GexfJS.graph.edgeList[i] //each list has variable _e
var position = []; // where i put the pointer
if ( _e.source == cursor ) { //when some edge source is match from the cursor(source node/initial node)
var _n = GexfJS.graph.nodeList[_e.target]; // edge target from the node which match with the cursor
_str += 'Found '; // just give string if it's works
position.push(_e.target); // for pointer?
cursor = _e.target // go to the next node, but how can i move to previos node?
}
}
} while(cursor!=nodes.node2); //until find the target node
the problem is im using position.push and it is array but i can't implement the pointer, if its has move next or move previous when finding path to the target node. thanks
maybe you don't need push position.push(_e.target); , just let the cursor in to the end , after the cursor hasn't more target , you can initial the cursor to back to the first node and don't forget each node you visit has to be set to be true
i have the answer for this, initial the cursor from the first node until the target node and don't forget each node you visit has to be set to be true if the node has visited, then go back again try to finding the first edges from the nodes, if the node hasn't true go to the next node, if the node has true find other node. here's my example code
pathed={};
ketemu=false;
cursor1="";
if(path==true){
while(ketemu!=true){
var cursor = nodes.node1;
var lala = new Array();
var visit = new Array();
var j = 0;
for (var i in GexfJS.graph.edgeList) { // relasi yang dituju
var _e = GexfJS.graph.edgeList[i];
if ( _e.source == cursor ) {
var _n = GexfJS.graph.nodeList[_e.target];
if(pathed[_n.label] != true){
if(_e.target==nodes.node2){
cursor = _e.target;
cursor1 = _e.target;
lala[j] = cursor;
visit[j] = cursor;
j++;
ketemu=true;
break;
}
if(_e.target !=nodes.node2){
cursor = _e.target;
lala[j] = cursor;
visit[j] = cursor;
j++;
}
//alert(cursor);
//alert(lala[j]);
}
}
//else if(cursor1==_e.target){
//cursor = nodes.node1;
//alert(cursor);
//}
}
if(ketemu!=true){
i=0;
for(var j=lala.length-1;j>=0;j--){
var test = lala.length-1;
var ujung = lala[test];
var koyong = nodes.node1;
i++;
}
ujung = GexfJS.graph.nodeList[ujung];
pathed[ujung.label] = true;
cursor = koyong;
}
}
for(var k=0;k<visit.length;k++){
var visitednya = visit[k];
var _n = GexfJS.graph.nodeList[visitednya];
_str += '<li><div class="smallpill" style="background: ' + _n.color.base +'"></div>' + _n.label + '' + ( GexfJS.params.showEdgeWeight && _e.weight ? ' [' + _e.weight + ']' : '') + '</li>';
}
visit=[];
//end
}
thanks

Find DOM-element that occurs the most

A container div.example can have different 1st-level child elements (section, div, ul, nav, ...). Quantity and type of those elements can vary.
I have to find the type (e.g. div) of the direct child that occurs the most.
What is a simple jQuery or JavaScript solution?
jQuery 1.7.1 is available, although it should work in IE < 9 (array.filter) as well.
Edit: Thank you #Jasper, #Vega and #Robin Maben :)
Iterate through the children using .children() and log the number of element.tagNames you find:
//create object to store data
var tags = {};
//iterate through the children
$.each($('#parent').children(), function () {
//get the type of tag we are looking-at
var name = this.tagName.toLowerCase();
//if we haven't logged this type of tag yet, initialize it in the `tags` object
if (typeof tags[name] == 'undefined') {
tags[name] = 0;
}
//and increment the count for this tag
tags[name]++;
});
Now the tags object holds the number of each type of tag that occurred as a child of the #parent element.
Here is a demo: http://jsfiddle.net/ZRjtp/ (watch your console for the object)
Then to find the tag that occurred the most you could do this:
var most_used = {
count : 0,
tag : ''
};
$.each(tags, function (key, val) {
if (val > most_used.count) {
most_used.count = val;
most_used.tag = key;
}
});
The most_used object now holds the tag used the most and how many times it was used.
Here is a demo: http://jsfiddle.net/ZRjtp/1/
Edit: I think a jQuery function like below should be more useful..
DEMO
$.fn.theMostChild = function() {
var childs = {};
$(this).children().each(function() {
if (childs.hasOwnProperty(this.nodeName)) {
childs[this.nodeName] += 1;
} else {
childs[this.nodeName] = 1;
}
});
var maxNode = '', maxNodeCount = 0;
for (nodeName in childs) {
if (childs[nodeName] > maxNodeCount) {
maxNode = nodeName;
maxNodeCount = childs[nodeName];
}
}
return $(maxNode);
}
And then you can,
$('div.example').theMostChild().css('color', 'red');
A function like below should give you the count of child elements, from which you can get the max count. See below,
DEMO
$(function () {
var childs = {};
$('div.example').children().each(function () {
if (childs.hasOwnProperty(this.nodeName)) {
childs[this.nodeName] += 1;
} else {
childs[this.nodeName] = 1;
}
});
for (i in childs) {
console.log(i + ': ' + childs[i]);
}
});
That is not possible without some information about the expected types of child nodes.
EDIT : It is possible as Jasper pointed out that we need not know the tag names before hand. The following works in case you're looking only within a specific set of selectors.
var selectorArray = ['div', 'span', 'p',........]
var matches = $(div).children(selectorArray.join());
var max = 0, result = [];
$.each(selectorArray, function(i, selector){
var l = matches.filter(selector).length;
if(l > max){
max = l;
result[max] = selector;
}
});
result[max] gives you the tag name and max gives you the occurrence count

Simple javascript find and replace

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

Categories