How can I count the number of leaf nodes coming off a particular XML node using jQuery?
The XML in question looks similar to this. I want all leaf nodes coming off the <Errors> node.
<Errors>
<ErrorParentCat>
<ErrorTag/>
<ErrorTag2/>
</ErrorParentCat>
</Errors>
In this example I want <ErrorTag/> and <ErrorTag2/> counted only. The result should therefore be 2. Also <ErrorParentCat> is an example tag, there could be many within <Errors> with different names.
Once I have the number I would like a list of these leaf nodes too, if possible.
Assuming you already have an XMLDocument named xml:
var $xml = $(xml),
count = $xml.find('*').filter(function ()
{
return $(this).children().length === 0;
}).length;
console.log(count);
You can also just pass the XML string directly to the jQuery function:
var $xml = $('<Errors><ErrorParentCat><ErrorTag/><ErrorTag2/></ErrorParentCat></Errors>');
// the rest is the same
jsfiddle demo →
Edit you said you wanted a list of those leaf nodes. In the code above, you've already got them:
var $xml = /* whatever */,
$leafNodes = $xml.find('*').filter(function ()
{
return $(this).children().length === 0;
}),
count = $leafNodes.length;
Edit #2 as Tim Down has pointed out (see comments below), you cannot just pass the XML string to $() in IE (!##$ing IE). You should use the jQuery 1.5 function $.parseXML() to parse an arbitrary well-formed XML string into an XMLDocument:
var xmlDoc = $.parseXML('<Errors><ErrorParentCat><ErrorTag/><ErrorTag2/></ErrorParentCat></Errors>'),
$xml = $(xmlDoc);
/* the rest is unchanged */
new jsfiddle demo →
Loads of ways to do this:
var countThem = 0;
jQuery.ajax({
type: "GET",
url: 'blahblah.xml',
dataType: ($.browser.msie) ? "text/xml" : "xml",
success: function(xml) {
var xml2 = load_xml(xml);
$(xml2).find('Errors').each(function(){
$(xml2).find('ErrorParentCat').each(function(){
alert($(this).text()); //alert the contents
countThem++;
});
});
alert(countThem); //alert the total number
}
});
and the XML load function:
function load_xml(msg) {
if ( typeof msg == 'string') {
if (window.DOMParser)//Firefox
{
parser=new DOMParser();
data=parser.parseFromString(text,"text/xml");
}else{ // Internet Explorer
data=new ActiveXObject("Microsoft.XMLDOM");
data.async="false";
data.loadXML(msg);
}
} else {
data = msg;
}
return data;
}
Related
I also need to put the array in a variable.
I'm using .DataTable for pagination but it doesn't accept tables that are created from xml using Javascript and according to this https://datatables.net/forums/discussion/2689, I need to convert my xml into 2d array.
Here's my xml file
<person>
<data>
<name>juan</name>
<city>tokyo</city>
<age>20</age>
<sex>m</sex>
</data>
<data>
<name>pedro</name>
<city>manila</city>
<age>22</age>
<sex>m</sex>
</data>
<data>
<name>maria</name>
<city>bangkok</city>
<age>23</age>
<sex>f</sex>
</data>
</person>
My 2D array should look like this:
var person =[
["juan","tokyo","20","m"],
["pedro","manila","22","m"],
["maria","bangkok","23","f"],
];
This is my javascript code. The output shows on my html page but I cannot use it for the DataTable that is why i need to store it in an javascript array. How can I modify this code so i can put it in a variable instead of displaying it in an html page?
function readperson(){
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(){
if (this.readyState == 4 && this.status ==200){
writeperson(this);
}
}
xmlhttp.open("GET", "person.xml", true);
xmlhttp.send();
}
function writeperson(xml){
var x,i,xmlDoc,txt,ths,trs,tre,the;
xmlDoc = xml.responseXML;
var person =xmlDoc.getElementsByTagName("data");
var l = person.length;
var nodes = person[0].childNodes[0];
//var l3 = nodes[0].length;
var l2 = person[0].childNodes[0].nodeValue;
var arr = [];
//orders.length = 3 since two <data> tag
for(i=0; i < person.length; i++){
//will add brackets inside the array arr
arr.push([]);//example: if arr.push("hello") output is hello,hello,hello
arr[i][0]=person[i].getElementsByTagName("name")[0].childNodes[0].nodeValue
arr[i][1]=person[i].getElementsByTagName("city")[0].childNodes[0].nodeValue
arr[i][2]=person[i].getElementsByTagName("age")[0].childNodes[0].nodeValue
arr[i][3]=person[i].getElementsByTagName("sex")[0].childNodes[0].nodeValue
}
document.getElementById("person").innerHTML = arr;
}
When I use a return statement instead of the innerHTML it does not work.
UPDATE
I figured it out. Here's my final code
$(document).ready(function () {
$.ajax({
type: "GET",
url: "person.xml",
dataType: "xml",
success: function (xml) {
const res = [];
$(xml).find("person > data").each(function (i, person) {
res.push([
$(this).find("name", person).text(),
$(this).find("city", person).text(),
$(this).find("age", person).text(),
$(this).find("sex", person).text(),
]);
});
$("#person_table").DataTable({
data: res,
columns: [
{ title: "Name" },
{ title: "Address" },
{ title: "Age" },
{ title: "Sex." },
],
});
},
});
});
Here is another Vanilla JS take on it with fetch() and DOMParser():
/* uncomment the next line for real application: */
// fetch("person.xml").then(r=>r.text()).then(txt=>{
const atts="name,city,age,sex".split(",");
/* XML data string for SO demo, remove line for real application: */
const txt = `<person><data><name>juan</name><city>tokyo</city><age>20</age><sex>m</sex></data><data><name>pedro</name><city>manila</city><age>22</age><sex>m</sex></data><data><name>maria</name><city>bangkok</city><age>23</age><sex>f</sex></data></person>`;
const xml=new DOMParser().parseFromString(txt,"text/html"),
result=[...xml.querySelectorAll("data")].reduce((res,da)=>
(res.push(atts.map(at=>da.querySelector(at).textContent)),res),
[]);
// Test
console.log(result);
/* end of fetch(), uncomment next line for real application: */
// });
You can use jQuery (you have this tag below your question) to parse html tags. First convert XML data string to DOM HTML, then do all searching and extraction how you regularly would with jQuery:
// XML data string
const xml = `
<person>
<data>
<name>juan</name>
<city>tokyo</city>
<age>20</age>
<sex>m</sex>
</data>
<data>
<name>pedro</name>
<city>manila</city>
<age>22</age>
<sex>m</sex>
</data>
<data>
<name>maria</name>
<city>bangkok</city>
<age>23</age>
<sex>f</sex>
</data>
</person>
`;
// Convert to DOM HTML
const html = $.parseHTML(xml);
// Set array for result
const res = [];
// Parse html, find data tags,
// loop through it's content
$(html).find("data").each(function() {
// For each data element push
// required data to array
res.push([
$(this).find("name").text(),
$(this).find("city").text(),
$(this).find("age").text(),
$(this).find("sex").text()
]);
});
// Test
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Consider the following.
Example: https://jsfiddle.net/Twisty/1vw3z6mf/
JavaScript
$(function() {
function xmlToArray(xml) {
var arr = [];
$(xml).find("person > data").each(function(i, person) {
arr.push([
$("name", person).text(),
$("city", person).text(),
$("age", person).text(),
$("sex", person).text()
]);
});
console.log("Converted", arr);
return arr;
}
function writeperson(xml) {
console.log("Write People");
var people = xmlToArray(xml);
$("#person").html(people);
}
function readperson() {
console.log("Read People");
$.get("person.xml", function(data) {
writeperson(data);
});
}
$("button").click(readperson);
});
jQuery can read XML just like it can reach HTML. So you can use jQuery selectors to traverse the XML. You can do this with .find() or with $("elem", object) shorthand which is the same.
The logic iterates over each data portion and creates Arrays at each index. This gives you an Array of Arrays or a 2D Array that Datatables can use.
I cleaned up other code elements to all use jQuery, yet there is nothing wrong with using JavaScript if you choose.
I have an XML which I am parsing using jquery, it works but I want to only get part of the XML data and then save all that part in localstorage.
My xml looks like this
<channel id="123"><display-name>123</display-name></name></channel>
<channel id="123"><display-name>123</display-name></name></channel>
<channel id="123"><display-name>123</display-name></name></channel>
<programme id="123"><display-name>123</display-name></name></programme>
<programme id="123"><display-name>123</display-name></name></programme>
<programme id="123"><display-name>123</display-name></name></programme>
But I only want to get all the <programme> data and then save that to localstorage. Im not sure how I can only grab the programme sections.
I have tried saving the whole xml but that didnt seem to output any data. This is what what I have tried.
<div id="text"></div>
$(function(){
$.ajax({
type: "GET",
url: "/myxml",
dataType: "xml",
success: function(xml){
window.localStorage.setItem('fullxml', xml)
$("#text").append(window.localStorage.getItem('fullxml'));
},
error: function() {
alert("An error occurred while processing XML file.");
}
});
});
To get a specific node from an XML document, you can select the XML node like this:
Javascript:
var programmeNodes = fullxml.getElementsByTagName('programme');
jQuery:
var programmeNodes = $(fullxml).find('programme');
My Solution:
This solution grabs all <programme> nodes and saves the data into an array and then stores that array in local storage for later use.
Given XML data like this:
var xml = `
<programmes>
<programme id="1">
<display-name>Test 1</display-name>
</programme>
<programme id="2">
<display-name>Test 2</display-name>
</programme>
</programmes>
`;
Will give an array of objects like this which can then be stored.
[
{
id: 1,
name: Test1
},
{
id: 2,
name: Test2
}
]
Full demo code:
var xml = `
<programmes>
<programme id="1">
<display-name>Test 1</display-name>
</programme>
<programme id="2">
<display-name>Test 2</display-name>
</programme>
</programmes>
`;
var fullxml = $.parseXML(xml);
// find <programme> XML elements
var programmeNodes = $(fullxml).find('programme');
// create array for programme data
var programmesArr = [];
// loop through each programme and store data in array
$.each(programmeNodes, function(i) {
var programmeID = $(programmeNodes[i]).attr('id');
var programmeDisplayName = $(programmeNodes[i]).find('display-name').text();
programmesArr.push({
id: programmeID,
name: programmeDisplayName
});
});
// store programmesArr in local storage
// localStorage only allows strings, so we need to convert array to JSON string
localStorage.setItem('programmes', JSON.stringify(programmesArr));
// get programmes from local storage
var storedProgrammes = JSON.parse(localStorage.getItem('programmes'));
DEMO: https://jsfiddle.net/1nLw7hjr/13/
Usage:
var programmeToFind = 2;
var programme = $.grep(storedProgrammes, function(e) {
return e.id == programmeToFind;
});
console.log(programme[0].id); // 2
console.log(programme[0].name); // Test2
Or as a little function:
function searchProgrammes(id) {
var programme = $.grep(storedProgrammes, function(e) {
return e.id == id;
});
return programme[0];
}
var programme = searchProgrammes(2);
console.log(programme.id); // 2
console.log(programme.name); // Test2
grep()
Finds the elements of an array which satisfy a filter function. The
original array is not affected.
I think you don't get any response because when you append the XML to the document, the browser try to parse it, and because this isn't valid HTML, it fails and show only the text, not the tags. So, the simplest solution will to not use jQuery's append() method, and instead, append the XML via text() (that doesn't parse the HTML tags, and instead escapes them), like:
$(function() {
$.ajax({
type: "GET",
url: "/myxml",
dataType: "xml",
success: function(xml) {
window.localStorage.setItem('fullxml', xml);
var el = $("#text");
el.text(el.text() + window.localStorage.getItem('fullxml'));
},
error: function() {
alert("An error occurred while processing XML file.");
}
});
});
Edit:
If you want to store only some elements, you should convert the XML to objects. Use DOMParser, as follows:
$(function() {
$.ajax({
type: "GET",
url: "/myxml",
dataType: "xml",
success: function(xml) {
var xmldoc;
if (window.DOMParser) {
parser = new DOMParser();
xmldoc = parser.parseFromString(xml, "text/xml");
} else { // Internet Explorer
xmldoc = new ActiveXObject("Microsoft.XMLDOM");
xmldoc.async = false;
xmldoc.loadXML(xml);
}
var programmes = xmldoc.getElementsByTagName('programme');
var str = '';
programmes.forEach(function(el) {
str += el.outerHTML;
});
// str now contains only the <programme> elements
window.localStorage.setItem('fullxml', str);
var el = $("#text");
el.text(el.text() + window.localStorage.getItem('fullxml'));
},
error: function() {
alert("An error occurred while processing XML file.");
}
});
});
i have a simple XML file which is loaded on page by a script posted below. It converts from a string to a XML file without any problems, but what complicates everything is the fact, that I can't get to a child's child.
I'd like to know why my code doesn't work and what should I do to get the tag name.
function load_xml() {
$.ajax({
type: "GET",
url: "file.xml",
dataType: "xml",
success: function (xmlData) {
var $first_child = $(xmlData).children()[0];
var first_name = $first_child.nodeName; // returns a proper name of a node
var $second_child = $first_child.children()[0]; // doesn't work
var $second_name = $second_child.nodeName; // returns nothing (don't know why)
},
error: function () {
alert("Could not retrieve XML file.");
}
});
}
In your case $first_child is not a jQuery collection. You need to wrap it with $(). Here is a corrected version.
var first_child = $(xmlData).children()[0]; // [0] actually returns the first "raw" node
var first_name = first_child.nodeName;
var $first_child = $(first_child);
var second_child = $first_child.children()[0];
var second_name = second_child.nodeName;
I have simple XML file
<?xml version="1.0" encoding="utf-8" ?>
<config>
<device>
<node>1</node>
<name>Deposit</name>
<description>Server</description>
<area>Saloon</area>
<type>Server</type>
</device>
</config>
and i have Ajax which is loading xml:
function changename(node2){
var nodenumber = node2
var newname = "newname"
$.ajax({
type: "GET",
url: "config2.xml",
dataType: "xml",
success: function(xml) {
$(xml).find('device').each(function () {
var node = $(this).find('node');
if (node.text() == nodenumber) {
var name = $(this).find('name').text();
alert(name);
}
});
}
});
}
What i need to do is update name in XML. I need to put newname variable value there.
This code only alert old name. Now i need to update xml file new name which is in newname variable.
Please help
use the same function which allows you to get the old name value:
var xml = $(xml);
xml.find('device').each(function () {
var node = $(this).find('node');
if (node.text() == nodenumber) {
$(this).find('name').text(newname);
}
});
you can also change whole your code in the success callback to this:
var xml = $(xml);
var node = xml.find('device>node')
.filter(function(index, node){ return ($(node).text()==nodenumber); })
.parent().find(">name").text(newname);
the point is it is now changed, but only in what you have in xml variable, and if you create another jQuery object, it has the old value.
Consider that it is a client side change (just in browser) in a xml document which is a JavaScript object, after you made your changes in it you should POST it to your server side, and override the new changed xml over the old version.
I have a list of quotes in an XML document. Each quote is wrapped like this:
<Item>
<Quote>This is a quote!</Quote>
<Source>-- this is the Source of the Quote!</Source>
</Item>
Here's the jQuery:
var html = '';
var tmpl = '<li class=""><p class="quote">__quote</p><p class="source">__source</p></li>';
$(quoteObj).find('Item').each(function(){
$that = $(this);
var _quote = $that.children('Quote').text();
var _source = $that.children('Source').text();
var qhtml = tmpl.replace('__quote', _quote).replace('__source', _source);
html += qhtml;
});
return html;
In the end product, the QUOTES are all there, but the SOURCES aren't. I can't for the life of me figure out why. What's right in front of me that I can't see?
ADDITIONAL INFORMATION TO ANSWER COMMENTS:
The XML is properly formed, and I changed it above.
I added the var tmpl line to show what I'm replacing in the loop. The __quote is being replaced, and the __source is at least being acted upon, since the second <p> is empty, instead of containing a string.
I have checked the actual XML coming back from the AJAX call, and it is all there, as it should be.
It seems to me this is some sort of issue with scoping and this, or with the action of the .children() method, but I still can't find it.
ONE LAST NOTE:
Changed the XML tag case to Initial Caps, which it is in the document in question.
jQuery doesn't parse XML. Passing an XML string to $() simply assigns the string as the innerHTML property of an element, which has variable and unpredictable results. You need to parse the XML yourself, using the browser's built-in XML parser, and then pass the resulting document into jQuery:
var parseXml;
if (window.DOMParser) {
parseXml = function(xmlStr) {
return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml");
};
} else if (typeof window.ActiveXObject != "undefined" && new window.ActiveXObject("Microsoft.XMLDOM")) {
parseXml = function(xmlStr) {
var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = "false";
xmlDoc.loadXML(xmlStr);
return xmlDoc;
};
} else {
parseXml = function() { return null; }
}
var xmlStr = "<Item><Quote>This is a quote!</Quote><Source>-- this is the Source of the Quote!</Source></Item>";
var xmlDoc = parseXml(xmlStr);
$xml = $(xmlDoc);
$xml.find('Item').each(function() {
// Do stuff with each item here
alert("Item");
});
Just tried this and the only thing I had to change was the find line to match the case of the XML node, eg
$(quoteObj).find('ITEM').each( function() {
I did also change the $that assignment line to include the var keyword, but it was working before I did that
var $that = $(this);