I'm using Ajax/jQuery to pull in some content from an RSS feed, but it seems to be failing to read the content of an XML node with the name 'link'.
Here's a simplified version of the XML:
<?xml version="1.0" encoding="UTF-8"?>
<channel>
<item>
<title>Title one</title>
<link>https://example.com/</link>
<pubDate>Mon, 12 Feb 2019</pubDate>
</item>
<item>...</item>
<item>...</item>
</channel>
</xml>
And the code I'm using:
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
$('item', this.responseText).each(function(){
var thisPostData = {};
thisPostData.title = $(this).find('title').text();
thisPostData.link = $(this).find('link').text();
thisPostData.date = $(this).find('pubDate').text();
posts.push(thisPostData);
});
console.log(posts);
}
};
var posts = [];
xhttp.open('GET', 'https://example.com/rssfeed/', true);
xhttp.send();
You'll see I'm trying to add each 'item' to an object, and storing them inside the 'posts' array. 'Title' and 'pubDate' are stored fine but 'link' isn't.
The actual RSS feed in question contains a huge amount of extra data, all of which I can read except the 'link' nodes. Any suggestions why nodes called 'link' would act differently from all the others?
The problem is because you're attempting to parse XML as HTML. The <link> object in HTML is an inline element, not a block level one, so it has no textContent property for jQuery to read, hence the output is empty.
To fix this first read the XML using $.parseXML(), then put it in a jQuery object which you can traverse.
There's also a couple of things to note. Firstly you will need to remove the </xml> node at the end of the XML output as it's invalid and will cause an error when run through $.parseXML. Secondly you can use map() to build an array instead of manually calling push() on an array, and you can just return the object definition directly from that. Try this:
var responseText = '<?xml version="1.0" encoding="UTF-8"?><channel><item><title>Title one</title><link>https://example.com/</link><pubDate>Mon, 12 Feb 2019</pubDate></item><item><title>Title two</title><link>https://foo.com/</link><pubDate>Tue, 13 Feb 2019</pubDate></item></channel>';
var xmlDoc = $.parseXML(responseText)
var posts = $('item', xmlDoc).map(function() {
var $item = $(this);
return {
title: $item.find('title').text(),
link: $item.find('link').text(),
date: $item.find('pubDate').text()
};
}).get();
console.log(posts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Lastly, you're using a rather odd mix of JS and jQuery. I'd suggest going with one or the other. As such, here's a full jQuery implementation with the AJAX request included too:
$.ajax({
url: 'https://example.com/rssfeed/',
success: function(responseText) {
var xmlDoc = $.parseXML(responseText)
var posts = $('item', xmlDoc).map(function() {
var $item = $(this);
return {
title: $item.find('title').text(),
link: $item.find('link').text(),
date: $item.find('pubDate').text()
};
}).get();
// work with posts here...
}
});
Related
I am having trouble loading values from an XML file and inserting them into a HTML table. It says that the html tag I am attempting to access is undefined "script.js:15 Uncaught TypeError: Cannot read property 'getElementsByTagName' of undefined". I am trying to insert the values of and into a html table from the loaded XML file.
// script.js
function loadXMLDoc() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
myFunction(this);
}
};
xmlhttp.open("GET", "table.xml", true);
xmlhttp.send();
}
function myFunction() {
var xmlDoc = XMLHttpRequest.responseXML;
var table = '<tr><th>Name</th><th>Age</th></tr>';
var x = xmlDoc.getElementsByTagName('STUDENT');
for (i = 0; i < x.length; i++) {
table += "<tr><td>" +
x[i].getElementsByTagName("NAME")[0].childNodes[0].nodeValue +
"</td><td>" +
x[i].getElementsByTagName("AGE")[0].childNodes[0].nodeValue +
"</td></tr>";
}
document.getElementById("students") = table;
}
//relevant html
<body>
<br><br>
<table id="students"></table>
<script src="script.js"></script>
</body>
//XML Doc Contents
<CLASS ID=”Advanced Web Development”>
<STUDENT>
<NAME>Tom</NAME>
<AGE>19</AGE>
<HEIGHT>1.3</HEIGHT>
<SEX>M</SEX>
<GRADE>B</GRADE>
</STUDENT>
<STUDENT>
<NAME>Dick</NAME>
<AGE>29</AGE>
<HEIGHT>1.1</HEIGHT>
<SEX>M</SEX>
<GRADE>A</GRADE>
</STUDENT>
<STUDENT>
<NAME>Harry</NAME>
<AGE>39</AGE>
<HEIGHT>1.5</HEIGHT>
<SEX>M</SEX>
<GRADE>C</GRADE>
</STUDENT>
<STUDENT>
<NAME>Mary</NAME>
<AGE>30</AGE>
<HEIGHT>1.1</HEIGHT>
<SEX>F</SEX>
<GRADE>B+</GRADE>
</STUDENT>
</CLASS>
There appears to be a few things you aren't quite doing right here:
Firstly, the XMLHttpRequest class doesn't have a responseXML property: that isn't where to get the result of an asynchronous request. Instead, the responseXML property belongs to the individual XMLHttpRequest objects (or 'instances'). You create a new XMLHttpRequest instance in the variable xmlhttp in the line var xmlhttp = new XMLHttpRequest();, and it will be xmlhttp that contains the responseXML property.
Additionally, you are passing the value of this in your onreadystatechange handler to myFunction, but myFunction doesn't take any arguments. JavaScript has very lax restrictions on calling functions with the correct number of arguments: any surplus arguments are simply ignored.
Try changing the first couple of lines of this function from
function myFunction() {
var xmlDoc = XMLHttpRequest.responseXML;
to
function myFunction(result) {
var xmlDoc = result.responseXML;
This allows myFunction to receive the xmlhttp instance and pull the response XML out of it.
Secondly, at the end of the function you write:
document.getElementById("students") = table;
Your variable table contains an HTML string representing the contents of a table. However, you can't set an element to an HTML string. What you instead want to do is to assign this HTML string to the inner HTML of this element, using the innerHTML property of the element:
document.getElementById("students").innerHTML = table;
Finally, the XML document you include in your question isn't well-formed, in that the ID attribute of the root element uses smart quotes (”) to surround the value when it should use neutral quotes (") instead. If the XML isn't well-formed, the responseXML property of result will be null.
I made these changes to your code and it worked, in that I could load up the XML and have the table filled out with the data in the XML file.
EDIT
With all the edits to my question it had grown quite lengthy. So let me try to shorten it up a bit and make it easier to follow.
I am building an XUL application using XULRunner. I have it load a dummy XUL page, and then I am looking to use XMLHttprequest to load everything from my server (local ampps server), using PHP to do all the real work. My PHP is setting the XML Content-Type header, and formatting all the output data as XML.
Here is what the JavaScript function, that handles the XMLHttprequest and the response, currently looks like.
function RequestData()
{
if (window.XMLHttpRequest)
{
var url = 'newmenu.xml';
var request = new XMLHttpRequest();
}
else
{
var url = 'http://localdomain.prog/';
var request = Components.classes['#mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Components.interfaces.nsIXMLHttpRequest);
}
request.onload = function(aEvent)
{
var xmlDoc = aEvent.target.responseXML;
var oldmenu = document.getElementById('menubarwrapper');
oldmenu.parentNode.replaceChild(xmlDoc.documentElement, oldmenu);
};
request.onerror = function(aEvent)
{
window.alert("Error Status: " + aEvent.target.status);
};
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
request.send('pageid=menu');
}
The RequestData() is called with the window onload event.
My original code looked to do nothing, but as I researched and tested I eventually got XULRunner to put out some errors in the error console. Ultimately it lead to what, I now assume, were working versions but I just didn't know it.
The Error Console was putting out this message (and still is)
Warning: XUL box for window element contained an inline toolbox child, forcing all its children to be wrapped in a block.
In order to find out if my code worked I had to get it into Firefox. Hence the reason for the if (window.XMLHttpRequest), as it allows me to test with both Firefox and XULRunner. I then took the XML that my PHP was generating and made a local file, as Firefox will not allow an XMLHttprequest to load a remote file (even if it is technically local).
The above code does import the XML and replaces the <menubar id="menubarwrapper">...</menubar>. But, in both Firefox and XULRunner the menu disappears. I can see all the elements if use Firebug, but why they are no longer visible is beyond me, and there are no errors in the Firebug console. This is where I am currently stumped.
In-case its of any use, below is a copy of the dummy XUL file I load.
<?xml version="1.0"?>
<?xml-stylesheet href="main.css" type="text/css"?>
<window id="main" title="My App" width="1000" height="800" sizemode="maximized" orient="vertical" persist="screenX screenY width height" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="main.js"/>
<toolbox id="toolboxwrapper">
<menubar id="menubarwrapper">
<menu id="file-menu" label="File" accesskey="F">
<menupopup id="file-popup">
<menuitem label="Refresh" funcname="RefreshWin"/>
<menuitem label="Open Window" funcname="OpenWin" acceltext="Ctrl+O" accesskey="O" />
<menuseparator/>
<menuitem label="Exit" funcname="ExitProg"/>
</menupopup>
</menu>
<menu id="edit-menu" label="Edit" accesskey="E">
<menupopup id="edit-popup">
<menuitem label="Undo"/>
<menuitem label="Redo"/>
</menupopup>
</menu>
</menubar>
</toolbox>
</window>
The XML that my PHP generates is quite lengthy, but basically it is the <menubar> element with all of its child elements, similar to above, but with a lot more <menu> and <menuitem> elements.
Without more information, this is just a guess at what the problem is.
What may be happening is that the XML data which is retrieved from your XMLHttpRequest() is being interpreted as XML/HTML and not XUL. This could result in the behavior which you describe. You can check this by using the DOM Inspector to look at the elements which were inserted with your line:
oldmenu.parentNode.replaceChild(xmlDoc.documentElement, oldmenu);
What you should look for is what Namespace URI shows for those elements. If it is not http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul then the elements are not being treated as XUL.
One way to solve this would be to do:
oldmenu.insertAdjacentHTML("afterend", aEvent.target.responseText);
oldmenu.setAttribute("hidden","true");
//Alternately (if you don't want to keep the placeholder <menubar>):
//oldmenu.parentNode.removeChild(oldmenu);
I got it working but the code is 80+ lines longer than it should ever need to be. I tried every variation of importNode, replaceChild, appendChild, adoptNode, removeChild, ect. I could think of. I am fairly certain I could write a book on how many ways you could handle any given XML element or node.
Here's an example of how absurd the issue was. This will work
function AddNewElem()
{
var menubar = document.getElementById('menubarwrapper');
var menuitem = '<menu id="help-menu" label="Help" accesskey="H" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">';
menuitem += '<menupopup id="help-popup">';
menuitem += '<menuitem id="test-menu" label="Test" acceltext="Ctrl+T" accesskey="T" />';
menuitem += '<menuseparator/>';
menuitem += '<menuitem id="about-menu" label="About" acceltext="Ctrl+A" accesskey="A" />';
menuitem += '</menupopup>';
menuitem += '</menu>';
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(menuitem, 'text/xml').documentElement;
menubar.appendChild(xmlDoc);
}
but this will not
function RequestData()
{
var url = 'newmenu.txt';
var request = new XMLHttpRequest();
request.onload = function(aEvent)
{
var menubar = document.getElementById('menubarwrapper');
var responsetxt = aEvent.target.responseText;
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(responsetxt, 'text/xml').documentElement;
menubar.appendChild(xmlDoc);
};
request.onerror = function(aEvent)
{
alert("Error Status: " + aEvent.target.status);
};
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
request.send('pageid=menu');
}
newmenu.txt is just the contents of menuitem in the preceding piece of code. I even went as far as to serialize the xmlDoc back to a string and then parse it again and still wouldn't work. I tried a ton of dumb ideas, and many I knew wouldn't work, on the off chance I might get a clue as to the problem. When you crash Firefox you know you have pushed too hard.
But I digress. So for the sake of others trying to use do a XHR with XULrunner below is what I had to do.
function RequestData()
{
var url = 'http://localdomain.prog/';
var request = Components.classes['#mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Components.interfaces.nsIXMLHttpRequest);
request.onload = function(aEvent)
{
var xmlDoc = aEvent.target.responseXML;
var toolbar = document.getElementById('toolboxwrapper');
var menubar = document.getElementById('menubarwrapper');
toolbar.removeChild(menubar);
var menubar = document.createElement('menubar');
menubar.setAttribute('id', 'menubarwrapper');
var docfrag = document.createDocumentFragment();
docfrag.appendChild(menubar);
var newmenus = xmlDoc.getElementsByTagName('menu');
var menuslen = newmenus.length;
for (var i = 0; i < menuslen; i++)
{
docfrag = CreateMenu(newmenus[i], docfrag);
}
toolbar.appendChild(docfrag);
};
request.onerror = function(aEvent)
{
window.alert("Error Status: " + aEvent.target.status);
};
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
request.send('pageid=menu');
}
function CreateMenu(obj, docfrag)
{
var fragbar = docfrag.getElementById('menubarwrapper');
var menu = document.createElement('menu');
menu = SetMenuAttr(obj, 'id', menu);
menu = SetMenuAttr(obj, 'label', menu);
menu = SetMenuAttr(obj, 'accesskey', menu);
var fragmenu = fragbar.appendChild(menu);
var popup = obj.getElementsByTagName('menupopup')[0];
var menupopup = document.createElement('menupopup');
menupopup = SetMenuAttr(popup, 'id', menupopup);
var fragpopup = fragmenu.appendChild(menupopup);
if (popup.hasChildNodes())
{
var childmenu = popup.childNodes;
var menulen = childmenu.length;
for (var i = 0; i < menulen; i++)
{
if (childmenu[i].nodeName == 'menuitem' || childmenu[i].nodeName == 'menu')
{
if (childmenu[i].nodeName == 'menuitem')
{
var menuitem = CreateMenuitem(childmenu[i]);
}
if (childmenu[i].nodeName == 'menu')
{
var menuitem = CreateMenu(childmenu[i]);
}
fragpopup.appendChild(menuitem);
}
}
}
return docfrag;
}
function CreateMenuitem(obj)
{
var menuitem = document.createElement('menuitem');
menuitem = SetMenuAttr(obj, 'id', menuitem);
menuitem = SetMenuAttr(obj, 'label', menuitem);
menuitem = SetMenuAttr(obj, 'accesskey', menuitem);
menuitem = SetMenuAttr(obj, 'acceltext', menuitem);
menuitem = SetMenuAttr(obj, 'disabled', menuitem);
return menuitem;
}
function SetMenuAttr(obj, attr, newobj)
{
if (obj.hasAttribute(attr))
{
newobj.setAttribute(attr, obj.getAttribute(attr));
}
return newobj;
}
Basically had to retrieve each element and its attributes and make new elements. By far its not the preferred solution. For myself, if I have to write one line of code more than is absolutely necessary, it is wrong. The solution should have been 2 lines.
This is related to another question, but is not a duplicate.
It deals with a proposed solution that I have reached an impasse.
I have the following code that reads an XML, makes changes, opens a window, and writes the XML into the document. The problem is that the content is not rendered as XML.
Any way to set a content type, etc, to have the browser handle the content as XML?
<script>
var wxml;
var xDoc;
var xDevices, xInputs;
var xDevice, xInput;
function fSetXmlAInput(iDevice, iNode, nodeNewVal) {
xInput = xInputs[iNode];
xValue = xInput.getElementsByTagName("value")[0];
// change node value:
// console.log("nodeVal: " + xValue.firstChild.nodeValue);
xValue.firstChild.nodeValue = nodeNewVal;
// console.log("newVal: " + xValue.firstChild.nodeValue);
}
function fSetXmlDevice(iDevice) {
xDevice = xDevices[iDevice];
xInputs = xDevice.getElementsByTagName("input");
fSetXmlAInput(iDevice, 0, "22");
fSetXmlAInput(iDevice, 1, "33");
}
function alternativeLoadXML3() {
// load xml file
if (window.XMLHttpRequest) {
xhttp = new XMLHttpRequest();
} else { // IE 5/6
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xhttp.open("GET", "my_template.xml", false);
xhttp.send();
xDoc = xhttp.responseXML;
xDevices = xDoc.getElementsByTagName("device");
fSetXmlDevice(1);
var xmlText = serializeXmlNode(xDoc);
var newWindow = window.open("my_template.xml", "Test", "width=300,height=300,scrollbars=1,resizable=1");
newWindow.document.open();
newWindow.document.write(xmlText);
newWindow.document.close()
};
</script>
Below the XML as well:
<?xml version="1.0" encoding="utf-8"?>
<myXmlRoot>
<device>
<input><name>"name 1"</name><value>{replaceMe!}</value></input>
<input><name>"name 2"</name><value>{replaceMe!}</value></input>
</device>
<device>
<input><name>"name 1"</name><value>{replaceMe!}</value></input>
<input><name>"name 2"</name><value>{replaceMe!}</value></input>
</device>
<device>
<input><name>"name 1"</name><value>{replaceMe!}</value></input>
<input><name>"name 2"</name><value>{replaceMe!}</value></input>
</device>
</myXmlRoot>
Any way to force the browser to render content in new window as XML...or does using document.open and document.write mean code is rendered as HTML?
Related: Change XML content using JavaScript, missing refresh
Use dataURI to write xml into new window is very easy.
window.open('data:text/xml,'+encodeURIComponent(xmlText),
"Test", "width=300,height=300,scrollbars=1,resizable=1");
Both document.open and document.write are used to write HTML or Javascript to a document. This goes back to the DOM Level 2 Specification of document.open, which assumes that the document is opened in order to write unparsed HTML.
Instead of using document.open and document.write, I would instead suggest dymanically adding elements using the XML DOM as in sampleElement.appendChild(XMLnode)
A similar question can also be found here: how to display xml in javascript?
I have a XML file and am trying to pull out data out of it. The XML file looks like this
<doc>
<str name="name">Rocky</str>
<str name="Last_name">balboa</str>
<str name="age">42</str>
<str name="sex">M</str>
<str name="dob">2012-09-09</str>
</doc>
<doc>... </doc>
<doc>... </doc>
<doc>... </doc>
My .ajax call goes like this...
$.ajax({
type : "GET",
url : "my.xml",
dataType : "xml",
success : function(data) {
$(data).find('doc').each(function() {
alert("i was here");
var u1 = $(this).find('name').text();alert(u1);
var u2 = $(this).find('last_name').text();
var finale1 = u1 + "/" + u2;
var dt = $(this).find('dob').text();
var dt1 = dt.substr(0,4);
var desc = $(this).find('age').text();
alert("i am here");
});
}
});
What am I doing wrong over here? Can anyone please point out.
When you are trying to select the following tag:
<str name="name">Rocky</str>
Instead of using $(this).find('name') you should use $(this).find('str[name="name"]')
This error appears many times, for each str tag.
You should parse your xml before using it (no need to do it if your ajax call returns xml).
Pay attention to:
Tag names: you look for a <document> element whereas you have <doc>
elements
Attributes and tag are different things. find('name') looks for a
tag, not for a name attribute:
See here for a working example (My xml is a local string, but you can easily adapt the script) and here for parseXML documentation and xml usage examples.
var xml = "<doc><str name=\"name\">Rocky</str><str name=\"sex\">M</str><str name=\"dob\">2012-09-09</str></doc>",
xmlDoc = $.parseXML( xml ),
xml = $( xmlDoc ),
name = xml.find( "str[name='name']" );
alert (name.text());
You can use Jquery parse xml to navigate the dom e.g. http://jsfiddle.net/qd2xY/
var xml = '<docs><doc><str name="name">Rocky</str><str name="Last_name">balboa</str><str name="age">42</str><str name="sex">M</str><str name="dob">2012-09-09</str></doc><doc><str name="name">Rocky1</str></doc></docs>';
$(document).ready(function(){
xmlDoc = $.parseXML(xml);
$xml = $( xmlDoc ),
$xml.find('doc').each(function(){
alert($(this).find('str[name="name"]').text())
})
})
your xml is not well formed, also never use the DOM traversal methods to parse the XML it becomes browser dependent, always use some sort of standard parser e.g. in jquery you can use .parseXML, in you success call back try
success : function(data) {
var xml=data;
xmlDoc = $.parseXML( xml ),
$xml = $( xmlDoc );
$.each($xml.find("str"),function(){
alert($(this).attr("name"));
});
}
DEMO
I need some code for parsing XML from a url in javascript. I tried following code:
Source:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">
<html>
<head><title>Employee Data</title>
<script>
// This function loads the XML document from the specified URL, and when
// it is fully loaded, passes that document and the url to the specified
// handler function. This function works with any XML document
function loadXML(url, handler) {
// Use the standard DOM Level 2 technique, if it is supported
if (document.implementation && document.implementation.createDocument) {
// Create a new Document object
var xmldoc = document.implementation.createDocument("", "", null);
// Specify what should happen when it finishes loading
xmldoc.onload = function() { handler(xmldoc, url); }
// And tell it what URL to load
xmldoc.load(url);
}
// Otherwise use Microsoft's proprietary API for Internet Explorer
else if (window.ActiveXObject) {
var xmldoc = new ActiveXObject("Microsoft.XMLDOM"); // Create doc.
xmldoc.onreadystatechange = function() { // Specify onload
if (xmldoc.readyState == 4) handler(xmldoc, url);
}
xmldoc.load(url); // Start loading!
}
}
// This function builds an HTML table of employees from data it reads from
// the XML document it is passed.
function makeTable(xmldoc, url) {
// Create a <table> object and insert it into the document.
var table = document.createElement("table");
table.setAttribute("border", "1");
document.body.appendChild(table);
// Use convenience methods of HTMLTableElement and related interfaces
// to define a table caption and a header that gives a name to each column.
var caption = "Employee Data from " + url;
table.createCaption().appendChild(document.createTextNode(caption));
var header = table.createTHead();
var headerrow = header.insertRow(0);
headerrow.insertCell(0).appendChild(document.createTextNode("Name"));
headerrow.insertCell(1).appendChild(document.createTextNode("Address"));
headerrow.insertCell(2).appendChild(document.createTextNode("State"));
// Now find all <employee> elements in our xmldoc document
var employees = xmldoc.getElementsByTagName("item");
// Loop through these employee elements
for(var i = 0; i < employees.length; i++) {
// For each employee, get name, job, and salary data using standard DOM
// methods. The name comes from an attribute. The other values are
// in Text nodes within <job> and <salary> tags.
var e = employees[i];
var name = e.getAttribute("name");
var job = e.getElementsByTagName("address")[0].firstChild.data;
var salary = e.getElementsByTagName("state")[0].firstChild.data;
alert(name);
// Now that we have the employee data, use methods of the table to
// create a new row and then use the methods of the row to create
// new cells containing the data as text nodes.
var row = table.insertRow(i+1);
row.insertCell(0).appendChild(document.createTextNode(name));
row.insertCell(1).appendChild(document.createTextNode(job));
row.insertCell(2).appendChild(document.createTextNode(salary));
}
}
</script>
</head>
<!--
The body of the document contains no static text; everything is dynamically
generated by the makeTable() function above.
The onload event handler starts things off by calling loadXML() to load the XML data file. Note the use of location.search to encode the name of the xml file in the query string. Load this HTML file with a URL like this: DisplayEmployeeData.html?data.xml
-->
<body onload="loadXML(url, makeTable)">
</body>
</html>