Jump to content

problem reading subchilds in javascript


junlee

Recommended Posts

Hello, I am learning to use XML and javascript, however I have run into a problem.I am using javascript to read an XML file containing information about a game and put the info into a table for my website, however I am having trouble reading subchildren of subchildren...here is my XML code:

<?xml version="1.0" encoding="ISO-8859-1"?><!-- Edited by XMLSpy® --><EQUIP>	<SWORD>		<PIC>images/swords/sword1.jpg</PIC>		<GRADE>Item Shop</GRADE>		<NAME>Bad Sword</NAME>		<LEVEL>1</LEVEL>		<ATTACK>10</ATTACK>		<M_ATTACK>7</M_ATTACK>		<STATS>			<STAT1>2% CRIT</STAT1>			<STAT2>8% MOVESPEED</STAT2>		</STATS>	</SWORD></EQUIP>

In this case, I would like to read all of the subchildren of the <STATS> tag (so I don't have to explicitly state how many to look for, I would like to be able to just put all of the subchilds into a table cell regardless of how many).I am using this javascript code:


<script type="text/javascript">if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari xmlhttp=new XMLHttpRequest(); }else {// code for IE6, IE5 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); }xmlhttp.open("GET","swords.xml",false);xmlhttp.send();xmlDoc=xmlhttp.responseXML; document.write("<table border='1'>");var x=xmlDoc.getElementsByTagName("SWORD");document.write("<tr><td>Icon</td><td>Name</td><td>Level</td><td>Attack</td><td>Magic Attack</td><td>Stats</td></tr>");for (i=0;i<x.length;i++) { document.write("<tr><td>"); document.write("<img src='"); document.write(x.getElementsByTagName("PIC")[0].childNodes[0].nodeValue); document.write("'>"); document.write("</td><td>"); document.write(x.getElementsByTagName("NAME")[0].childNodes[0].nodeValue); document.write("</td><td>"); document.write(x.getElementsByTagName("LEVEL")[0].childNodes[0].nodeValue); document.write("</td><td>"); document.write(x.getElementsByTagName("ATTACK")[0].childNodes[0].nodeValue); document.write("</td><td>"); document.write(x.getElementsByTagName("M_ATTACK")[0].childNodes[0].nodeValue); document.write("</td><td>"); document.write(x.getElementsByTagName("STAT1")[0].childNodes[0].nodeValue); document.write("<br>"); document.write(x.getElementsByTagName("STAT2")[0].childNodes[0].nodeValue); document.write("<br>"); document.write(x.getElementsByTagName("STAT2")[0].childNodes[0].nodeValue); document.write("</td></tr>"); }document.write("</table>");document.write(s.childNodes.length);</script>[/code]

Here is what I am using now, it states exactly how many "STATS" to look for because I was unable to get it to loop through and get subchildren of <STATS>I tried things like:

var s = x.getElementsByTagName("STATS");for (j=0;j<s.length;j++)  {	document.write(s[i][0].childNodes[0].childNodes[0].nodeValue);  }

Which turns out aren't valid expressions, but I haven't been able to find any documentation on performing the task that I am trying to accomplish.Can anyone help me out? Thanks!

Link to comment
Share on other sites

Each item of s is an element. Each element has a childNodes collection which has it's own length.Looping over inner elements is similar to looping on inner array elements - you need a separate inner loop for each inner element, like:

var s = x.getElementsByTagName("STATS");for (var i=0;i<s.length;i++) {/* Loops over all elements of the "s" collection, i.e. all STATS elements */	var ss = s[i].childNodes;	for(var j=0;j<ss.length;j++) {/* Loops over all elements of the "ss" collection, i.e. all child nodes of the STATS element in position "i" */		document.write(ss[j].childNodes[0].nodeValue);/* Writes out the value of the first child node of the current node. Assuming the node contains only text, this means you're outputting the text value of the child node at position "j" of the STATS element at position "i" */	}}

It's also worth mentioning that whitespace is treated differently in IE8- and other browsers (IE9 included), so as a workaround, you might want to use getElementsByTagName("*") to only tager any descendant elements. Keep in mind that using this shortcut includes descendants i.e. elements on all deeper levels, not just direct children. In your case, it doesn't make a difference though, so you can just have:

var s = x.getElementsByTagName("STATS");for (var i=0;i<s.length;i++) {/* Loops over all elements of the "s" collection, i.e. all STATS elements */	var ss = s[i].getElementsByTagName("*");	for(var j=0;j<ss.length;j++) {/* Loops over all elements of the "ss" collection, i.e. all child nodes of the STATS element in position "i" */		document.write(ss[j].childNodes[0].nodeValue);/* Writes out the value of the first descendant element of the current node. Assuming the element contains only text, this means you're outputting the text value of the descendant element at position "j" of the STATS element at position "i" */	}}

Link to comment
Share on other sites

If it's possible to change the structure of your XML, life might be easier if1. stat1, stat2 and so on had more specific names that you could search for individually, like <crit> <movespeed> etcor2. if all the stat items used the same tag name (stat) and you could collect them all at once without having to know how many there are. You might even be able to do something like <stat kind="crit"> and the attribute values could simplify the search.

Link to comment
Share on other sites

Thanks for the reply, however it still isn't working :) I modified the loop in my javascript to:

document.write("<tr><td>Icon</td><td>Name</td><td>Level</td><td>Attack</td><td>Magic Attack</td><td>Stats</td></tr>");for (i=0;i<x.length;i++)  {    document.write("<tr><td>");   document.write("<img src='");   document.write(x[i].getElementsByTagName("PIC")[0].childNodes[0].nodeValue);   document.write("'>");   document.write("</td><td>");   document.write(x[i].getElementsByTagName("NAME")[0].childNodes[0].nodeValue);   document.write("</td><td>");   document.write(x[i].getElementsByTagName("LEVEL")[0].childNodes[0].nodeValue);   document.write("</td><td>");   document.write(x[i].getElementsByTagName("ATTACK")[0].childNodes[0].nodeValue);   document.write("</td><td>");   document.write(x[i].getElementsByTagName("M_ATTACK")[0].childNodes[0].nodeValue);   document.write("</td><td>");   var s = x.getElementsByTagName("STATS");   for (var p=0;p<s.length;p++) {/* Loops over all elements of the "s" collection, i.e. all STATS elements */   				var ss = s[p].childNodes;				for(var j=0;j<ss.length;j++) {				document.write(ss[j].childNodes[0].nodeValue); 				document.write("<br>"); 				 }	}	document.write("</td></tr>");   }document.write("</table>");

However it basically just gets to that part and stops. On my web page, it writes the table just fine until that cell, then it leaves it blank, and doesn't continue on to write the next row either.You can see the result here: http://locohq.com/equipment/equipment.htmland the xml file is:

<?xml version="1.0" encoding="ISO-8859-1"?><EQUIP>	<SWORD>		<PIC>images/swords/sword1.jpg</PIC>		<NAME>Bad Sword</NAME>		<LEVEL>1</LEVEL>		<ATTACK>10</ATTACK>		<M_ATTACK>7</M_ATTACK>		<STATS>			<STAT1>2% CRIT</STAT1>			<STAT2>8% MOVESPEED</STAT2>		</STATS>	</SWORD>	<SWORD>		<PIC>images/swords/sword2.jpg</PIC>		<NAME>Big Sword</NAME>		<LEVEL>2</LEVEL>		<ATTACK>13</ATTACK>		<M_ATTACK>5</M_ATTACK>		<STATS>			<STAT1>3% CRIT</STAT1>			<STAT2>22% MOVESPEED</STAT2>		</STATS>	</SWORD></EQUIP>

It seems like it should work, and I think I tried something like this last night and had the same results. I'm not sure where I am going wrong here.

If it's possible to change the structure of your XML, life might be easier if1. stat1, stat2 and so on had more specific names that you could search for individually, like <crit> <movespeed> etcor2. if all the stat items used the same tag name (stat) and you could collect them all at once without having to know how many there are. You might even be able to do something like <stat kind="crit"> and the attribute values could simplify the search.
Well I originally had them all named <stat> when I was first trying to get this to work, but later renamed them for my workaround. They are going to be displayed on top of each other in a single cell so at this point it doesn't matter what category (crit, movespeed) they are. I just would like to be able to have a variation in how many stats there are per item, and have them all be displayed in each row's respective STATS cell without having to parse a specific number like I am now.
Link to comment
Share on other sites

Notice carefully that the inner getElementsByTagName() is applied on an element in my example:

var ss = s[i].getElementsByTagName("*");

"s" is a collection, and "s" is a member (element) of that collection.In your example, you have

var s = x.getElementsByTagName("STATS");

"x" is a collection. You can't apply getElementsByTagName() on a collection. Only over a document or an element.Change that line to:

var s = x[i].getElementsByTagName("STATS");

to apply it over a specific element in the "x" collection, and it should work.

Link to comment
Share on other sites

Ah, so the way I had it before was on a collection...so when I ran

var ss = s[0].childNodes;

it was trying to get the first childNode for two different <STATS> elements, causing an error and causing the script to stop. That makes sense now, it should become easier for me to recognize things like that right away the more I use it :)Thanks for the help!

Link to comment
Share on other sites

hmm actually the problem still isn't solved...I thought it was but it only runs in internet explorer. I noticed this when trying to set up a new file....Here I have a script just testing variables right now to make sure that I am correctly reading the XML:

 var level = 1;   var i = 0;   var bonus_2 = x[i].getElementsByTagName("SET_BONUS2")[0].childNodes[0].nodeValue;   var bonus_4 = x[i].getElementsByTagName("SET_BONUS4")[0].childNodes[0].nodeValue;   var current_set = x[i].getElementsByTagName("LVL" + level);   var name = current_set[0].getElementsByTagName("NAME")[0].childNodes[0].nodeValue;   var head = current_set[0].getElementsByTagName("HEAD");   var head_pic = head[0].getElementsByTagName("PIC")[0].childNodes[0].nodeValue;   var head_pdef = head[0].getElementsByTagName("PHYS_DEF")[0].childNodes[0].nodeValue;   var head_mdef = head[0].getElementsByTagName("MAG_DEF")[0].childNodes[0].nodeValue;   var head_stats = head[0].getElementsByTagName("STATS");   var grade = x[i].getElementsByTagName("GRADE")[0].childNodes[0].nodeValue;  	 document.write("grade = " + grade + "<br>");	 document.write("bonus_2 = " + bonus_2 + "<br>");	 document.write("bonus_4 = " + bonus_4 + "<br>");	 document.write("head_pic = " + head_pic + "<br>");	 document.write("head_pdef = " + head_pdef + "<br>");	 document.write("head_mdef = " + head_mdef + "<br>");  	 var h_stats = head_stats[0].childNodes;	  document.write(h_stats[0].childNodes[0].nodeValue);

Everything works correctly until it gets to the last part with 'var h_stats'. I tried looping using the same code you provided in your examples but that didn't work, so I changed it to try and read just the first node but that didn't work either.Here is the new XML file:

<?xml version="1.0" encoding="ISO-8859-1"?><EQUIP><SET>		<GRADE>Grade D</GRADE>		<LVL1>			<NAME>Black Spider</NAME>			<SET_BONUS2>set bonus 1</SET_BONUS2>			<SET_BONUS4>set bonus 2</SET_BONUS4> 			<HEAD>				<PIC>images/hibachi/d_head.jpg</PIC>				<PHYS_DEF>10</PHYS_DEF>				<MAG_DEF>7</MAG_DEF>				<STATS>						<STAT1>99% CRIT</STAT1>					<STAT2>99% MOVESPEED</STAT2>				</STATS>			</HEAD>		</LVL1></SET></EQUIP>

Any ideas? Chrome is reporting that h_stats[0].childNodes[0] is undefined, IE is printing the text but underlining it, and firefox just isn't showing it at all.

Link to comment
Share on other sites

How is "head" created?You seem to write the code with assuming that it's

var head = xmlDoc.getElementsByTagName("head");

i.e. a collection of head elements... is it? Or is it

var head = xmlDoc.getElementsByTagName("head")[0];

Could you give a link to the whole page (as you did earlier; I see you're not writing in the same file anymore).

Link to comment
Share on other sites

here ya go:

<script type="text/javascript">if (window.XMLHttpRequest)  {// code for IE7+, Firefox, Chrome, Opera, Safari  xmlhttp=new XMLHttpRequest();  }else  {// code for IE6, IE5  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");  }xmlhttp.open("GET","hibachi.xml",false);xmlhttp.send();xmlDoc=xmlhttp.responseXML; var x=xmlDoc.getElementsByTagName("SET");   var level = 1;   var i = 0;   var bonus_2 = x[i].getElementsByTagName("SET_BONUS2")[0].childNodes[0].nodeValue;   var bonus_4 = x[i].getElementsByTagName("SET_BONUS4")[0].childNodes[0].nodeValue;   var current_set = x[i].getElementsByTagName("LVL" + level);   var name = current_set[0].getElementsByTagName("NAME")[0].childNodes[0].nodeValue;   var head = current_set[0].getElementsByTagName("HEAD");   var head_pic = head[0].getElementsByTagName("PIC")[0].childNodes[0].nodeValue;   var head_pdef = head[0].getElementsByTagName("PHYS_DEF")[0].childNodes[0].nodeValue;   var head_mdef = head[0].getElementsByTagName("MAG_DEF")[0].childNodes[0].nodeValue;   var head_stats = head[0].getElementsByTagName("STATS");    var grade = x[i].getElementsByTagName("GRADE")[0].childNodes[0].nodeValue;  	 document.write("grade = " + grade + "<br>");	 document.write("bonus_2 = " + bonus_2 + "<br>");	 document.write("bonus_4 = " + bonus_4 + "<br>");	 document.write("head_pic = " + head_pic + "<br>");	 document.write("head_pdef = " + head_pdef + "<br>");	 document.write("head_mdef = " + head_mdef + "<br>");  	 var h_stats = head_stats[0].childNodes;	   document.write(h_stats[0].childNodes[0].nodeValue);</script>

Link to comment
Share on other sites

I mentioned this earlier, but perhaps I didn't emphasized it enough...

It's also worth mentioning that whitespace is treated differently in IE8- and other browsers (IE9 included), so as a workaround, you might want to use getElementsByTagName("*") to only taget any descendant elements.
childNodes includes whitespaces. In IE8-, whitespace nodes are omitted. Your example only works in IE (I assume IE8?) because you're thinking that the first node in the childNodes collection is the first element, but that's only the case in IE8-.
Link to comment
Share on other sites

Yeah the example worked in IE8. Hmm I tried using

getElementsByTagName("*")

this morning with a for loop but it didn't work, probably was just a syntax error in my code.I just added a quick test using

var h_stats = head_stats[0].getElementsByTagName("*");	   	 document.write(h_stats[0].childNodes[0].nodeValue);

and

var h_stats = head_stats[0].getElementsByTagName("*");	   	 document.write(h_stats[1].childNodes[0].nodeValue);

And it seems to be displaying correctly now. Hm I guess most of my confusion with this comes from trying to distinguish 'elements' and 'nodes' when working on the inner tree. That should be fine for now though, I will work on updating the full script when I get home tonight.Thanks again

Link to comment
Share on other sites

In case you're missing on the terminology:Node - any distinct part of an XML document - element is one kind of a node, attribute is another kind of a node, text (whether it's just whitespace or actual text) is a third kind of a node. Contains information that all (or at least most) kind of nodes have such as a "childNodes" collection and the "nodeName" property.Element - one kind of a node (therefore has all information that's part of nodes). Has information specific to it such as an "attributes" collection and the mentioned getElementsByTagName() method, among others.document - another kind of a node (surprisingly; therefore has all information that's part of nodes). Has information specific to it such as the "documentElement" property and the getElementsByTagName() method, among others.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...