NOTE: I am not looking for a way to query the HTML document itself. I want to create my own document from my javaScript object and pass it as root argument to the evaluate function.
Say I have the following script:
function Attribute(name, value) {
this.name;
this.value;
}
function Node(nodeName) {
this.nodeName = nodeName;
this.textContent = "";
this.childrenNodes = [];
this.attributes = [];
}
var root = new Node("root");
root.attributes.push(new Attribute("name", "treeRoot"));
var c1 = new Node("child");
c1.attributes.push(new Attribute("name", "child1"));
c1.textContent = "I'm the first child!";
var c2 = new Node("child");
c2.attributes.push(new Attribute("name", "child2"));
root.childrenNodes.push(c1);
root.childrenNodes.push(c2);
That represents the following simple XML:
<root name="treeRoot">
<child name="child1">
I'm the first child!
</child>
<child name="child2"/>
</root>
I would like to use the build in XPath engine to query this XML-like structure. Somthing like:
myDocument = createDocument(root);
myDocument.evaluate("/root/child[#name='child2']", myDocument, null, XPathResult.ANY_TYPE, null);
That would return an XPathResult of Node collection containing c1 Node.
How do I implement the createDocument function?
EDIT:
My goal is to be able to query javaScript objects. In Java I can create a Document object and use XPath to query it. I'm looking for something similar in javaScript.
You need a couple of functions here to convert your "DOM" implementation to a standard XML DOM - one to create the document and another to recursively create elements:
// create a document based on a Node instance
function toXmlDom(node) {
// create a document
var doc = document.implementation.createDocument('', '');
// convert the root node
var e = toXmlElement(doc, node);
// add root to document
doc.appendChild(e);
return doc;
}
// convert a Node and its children to an XML element
function toXmlElement(doc, node) {
// create an element
var e = doc.createElement(node.nodeName);
// set its attributes
for(var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
e.setAttribute(attr.name, attr.value);
}
// set its text content
e.textContent = node.textContent;
// convert and add its child nodes
for(var i = 0; i < node.childrenNodes.length; i++) {
var childrenNode = node.childrenNodes[i];
var childNode = toXmlElement(doc, childrenNode);
e.appendChild(childNode);
}
return e;
}
// do the conversion
var myDocument = toXmlDom(root);
Working Example
console.clear();
function Attribute(name, value) {
this.name = name;
this.value = value;
}
function Node(nodeName) {
this.nodeName = nodeName;
this.textContent = "";
this.childrenNodes = [];
this.attributes = [];
}
function toXmlDom(node) {
// create a document
var doc = document.implementation.createDocument('', '');
// convert the root node
var e = toXmlElement(doc, node);
// add root to document
doc.appendChild(e);
return doc;
}
function toXmlElement(doc, node) {
// create an element
var e = doc.createElement(node.nodeName);
// set its attributes
for(var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
e.setAttribute(attr.name, attr.value);
}
// set its text content
e.textContent = node.textContent;
// convert and add its child nodes
for(var i = 0; i < node.childrenNodes.length; i++) {
var childrenNode = node.childrenNodes[i];
var childNode = toXmlElement(doc, childrenNode);
e.appendChild(childNode);
}
return e;
}
var root = new Node("root");
root.attributes.push(new Attribute("name", "treeRoot"));
var c1 = new Node("child");
c1.attributes.push(new Attribute("name", "child1"));
c1.textContent = "I'm the first child!";
var c2 = new Node("child");
c2.attributes.push(new Attribute("name", "child2"));
root.childrenNodes.push(c1);
root.childrenNodes.push(c2);
var myDocument = toXmlDom(root);
// get the text of the first child - "I'm the first child!"
var result = myDocument.evaluate("/root/child[#name='child1']", myDocument, null, XPathResult.ANY_TYPE, null);
var thisNode = result.iterateNext();
while (thisNode) {
document.getElementById('result').innerHTML += thisNode.textContent + "<br/>";
thisNode = result.iterateNext();
}
document.getElementById('doctext').value = myDocument.documentElement.outerHTML;
<p><b>/root/child[#name='child1'].textContent:</b> <span id="result"></span></p>
<b>Document XML</b><br/>
<textarea id="doctext" cols="50" rows="10"></textarea>
Related
I have a loop in which I am calling rec_append() recursively, apparently the first pass alone works, then the loop stops.
I have an array of 4 elements going into that $.each loop but I see only the first element going into the function recursively. Help!
I switched it for a element.forEach but that gives me only the second element and I am stuck, is there a better solution to process a tree of elements? My array is a part of a tree.
var data = JSON.parse(JSON.stringify(result))
var graph = $(".entry-point");
function rec_append(requestData, parentDiv) {
var temp_parent_details;
$.each(requestData, function (index, jsonElement) {
if (typeof jsonElement === 'string') {
//Element construction
//Name and other details in the form of a : delimited string
var splitString = jsonElement.split(':');
var details = document.createElement("details");
var summary = document.createElement("summary");
summary.innerText = splitString[0];
details.append(summary);
temp_parent_details = details;
parentDiv.append(details);
var kbd = document.createElement("kbd");
kbd.innerText = splitString[1];
summary.append(' ');
summary.append(kbd);
var div = document.createElement("div");
div.className = "col";
details.append(div);
var dl = document.createElement("dl");
div.append(dl);
var dt = document.createElement("dt");
dt.className = "col-sm-1";
dt.innerText = "Path";
div.append(dt);
var dd = document.createElement("dd");
dd.className = "col-sm-11";
dd.innerText = splitString[2];
div.append(dd);
var dt2 = document.createElement("dt");
dt2.className = "col-sm-1";
dt2.innerText = "Type";
div.append(dt2);
var dd2 = document.createElement("dd");
dd2.className = "col-sm-11";
dd2.innerText = splitString[1];
div.append(dd2);
} else {
$.each(jsonElement, function (jsonElementArrIndx, jsonChildElement) {
rec_append(jsonChildElement, temp_parent_details); //Only 1 pass works, rest skip
});
}
});
}
rec_append(data, graph);
Sample data:enter image description here
I'm trying to push a element children text to my object reminder by passing it to to the new reminder method, but it doesn't push each of the element text it just replaces them.
$(document).on('click', '.save-reminder-button', function() {
var value = $(this).siblings('input').val(); //get the value of input
var title = $(this).siblings('h1').text(value); //set the h1
var saveSetName = $(this).parent().siblings().children('input').val();
var elem = $(this).parent().parent().children(".reminder-lists").children(); //get the reminder-lists children
$(elem).each(function(i, e) {
var txt = $(e).text(); //set txt to the elem text node
saveSetName = new ReminderSet(saveSetName)
.add(new Reminder(txt)) //create new reminder to push to reminders array
});
})
var ReminderSet = function(name) {
this.name = name;
this.reminders = [];
}
ReminderSet.prototype.add = function(reminder) {
this.reminders.push(reminder);
console.log(this.name, this.reminders);
return this;
}
ReminderSet.prototype.list = function() {
console.log(this.reminders);
}
var Reminder = function(description) {
this.description = description;
}
In your code, you create a ReminderSet object for each pushing element. Create the object once. Like this,
$(document).on('click', '.save-reminder-button', function() {
var value = $(this).siblings('input').val(); //get the value of input
var title = $(this).siblings('h1').text(value); //set the h1
var saveSetName = $(this).parent().siblings().children('input').val();
var elem = $(this).parent().parent().children(".reminder-lists").children(); //get the reminder-lists children
saveSetName = new ReminderSet(saveSetName);
$(elem).each(function(i, e) {
var txt = $(e).text(); //set txt to the elem text node
saveSetName.add(new Reminder(txt)) //create new reminder to push to reminders array
});
})
With this line:
saveSetName = new ReminderSet(saveSetName)
.add(new Reminder(txt))
You're just replacing the value of the old object with that new value, in order for it to work the way you want it you have to declare a new variable, outside the for loop:
var reminderSet = new ReminderSet(saveSetName);
And once you're inside your loop you can then add your reminders:
$(elem).each(function(i, e) {
var txt = $(e).text(); //set txt to the elem text node
reminderSet.add(new Reminder(txt))
});
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>
I need to create custom objects based on an XML input, the rule is that for every node if it has a direct child node is named EndNode and the text value of which is 1, then I create a leaf object. So for every node, I need to check the direct child with name EndNode and its value. It's not so easy with the DOM API and DOM selector (in this case I use Ext.DomQuery) doesn't have a way to select direct child of the root node... Below is my attempt for using DOM selector, I need to wrap the node around with another level of the node for the selector to work. But I can't just say new Node(), it silently fails.
I guess I have to walk through n.childNodes, but it is complicated to do it this way to check the rule I described above. Any solution?
Ext.each(node.childNodes, function(n){
if (n.nodeType == this.XML_NODE_ELEMENT) {
var tmp=new Node();
console.log('hi');
tmp.appendChild(n);
console.log(Ext.DomQuery.select(n.tagName+">EndNode", tmp));
}
}
I did an xml parser. It is quite easy with Dojo's library. Here ya are. When you're done with it though I recommend exporting the var to JSON and using it as cache.
dojo.require("dojox.xml.parser");
var parser = dojox.xml.parser;
function crules() {
this.rules = new Array();
this.xml = Object;
}
xml = '';
crules.prototype.load = function(file){
var xmlget = dojo.xhrGet({
url: file,
handleAs: "xml",
load: function(data){
xml = data;
},
error: function (error) {
console.error ('Error: ', error);
},
sync: true
}
);
this.xml = xml;
}
crules.prototype.buildout = function (){
var rules = this.xml.getElementsByTagName('ruleset');
//dojo.byId('jsloading').innerHTML = 'Loading Javascript';
for(var i=0; i<rules.length; i++){
//dojo.byId('jsloading').innerHTML += ' .';
r = new cruleset();
r.name = xtagvalue(rules[i],'name');
base = xtag(rules[i],'base');
textcustom = xtag(rules[i],'textcustom');
r.textcustomy = xtagvalue(textcustom[0],'y');
r.textcustomx = xtagvalue(textcustom[0],'x');
for(var j=0; j<base.length; j++){
r.bases[j] = new cbase();
r.bases[j].imgsrc = xtagvalue(base[j],'imgsrc');
r.bases[j].color = xtagvalue(base[j],'color');
r.bases[j].coloropts = new Array();
var copts = xtag(rules[i],'option');
for(var k=0; k<copts.length;k++){
var cc = new Object();
cc.color = xtagvalue(copts[k],'color');
cc.imgsrc = xtagvalue(copts[k],'imgsrc');
r.bases[j].coloropts.push(cc);
}
}
zones = xtag(rules[i],'zone');
for(var j=0; j<zones.length; j++){
z = new czone();
z.name =xtagvalue(zones[j],'name');
zoneconfigs = xtag(zones[j],'zoneconfig');
for(var n=0; n<zoneconfigs.length; n++){
zc = new czoneconfig();
zc.name = z.name;
zc.x1 =xtagvalue(zones[j],'x1');
zc.y1 =xtagvalue(zones[j],'y1');
zc.w =xtagvalue(zones[j],'w');
zc.h =xtagvalue(zones[j],'h');
hotspots = xtag(zoneconfigs[n],'hotspot');
for(var k=0; k<hotspots.length; k++){
h = new chotspot();
h.name = xtagvalue(hotspots[k],'name');
h.x =xtagvalue(hotspots[k],'x');
h.y =xtagvalue(hotspots[k],'y');
h.nameyoffset = xtagvalue(hotspots[k],'nameyoffset');
h.accessoryonly = xtagvalue(hotspots[k],'accessoryonly');
if(h.accessoryonly == null){
h.accessoryonly = 0;
}
var showname = xtag(hotspots[k],'showname');
if(!isEmpty(showname)){
h.showname = xtagvalue(hotspots[k],'showname');
}
/*h.itemset =xtagvalue(hotspots[k],'itemset');*/
items = xtag(hotspots[k],'item');
if(items){
for(var l=0;l<items.length;l++){
t = new citem();
t.id = xtagvalue(items[l],'id');
h.items[h.items.length] = t;
}
}
zc.hotspots[zc.hotspots.length] = h;
}
z.zoneconfigs[z.zoneconfigs.length] = zc;
}
r.zones[r.zones.length] = z;
}
this.rules[this.rules.length] = r;
}
/*xmltext = parser.innerXML(xml);
dojo.byId('cwindow').innerHTML = xmltext;*/
}
function xtag(e,tag){
var n=null;
n = e.getElementsByTagName(tag);
if(n.length>=1){
return e.getElementsByTagName(tag);
}
else return null;
}
function xtagvalue(e,tag){
var n=null;
n = e.getElementsByTagName(tag);
if(n.length>=1){
//console.log(tag,'here',n[0],parser.textContent(n[0]));
return parser.textContent(n[0]);
}
else return null;
}