SitePoint Sponsor

User Tag List

Results 1 to 9 of 9
  1. #1
    SitePoint Guru
    Join Date
    Nov 2005
    Location
    Midwest
    Posts
    777
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Parse XML in JavaScript

    I am trying to setup an Ajax code base and I am getting pretty close but I am having an issue parsing my XML in the response back.

    Here is my XML

    Code:
    <?xml version="1.0" ?>
    <content>
       <header>Header 1</header>
       <description>Description 1</description>
    </content>
    <content>
       <header>Header 2</header>
       <description>Description 2</description>
    </content>
    I know the XML is working because this code displays the 1st value.

    Code:
    response.getElementsByTagName('header')[0].firstChild.data;
    But I am trying to set this up in a loop and here is where my problem is. This code is not working… I am hoping someone can help me determine why.

    Code:
    var content = response.getElementsByTagName('content');
    
    alert("content.length = " + content.length); - this is always 0 so I know something is wrong here
    
    for (var i = 0; i < content.length ; i++)
    {
          //-- For each <content> node, get child nodes
          var nodeList = content.item(i).childNodes;
    
          // Loop through child nodes
          for (var j = 0; j < nodeList.length ; j++)
               { 
                 var node = nodeList.item(j);
    
             if (node.nodeName == "header")
             {
                var header = node.firstChild.nodeValue;
             }
    
                    if (node.nodeName == "description")
            {
               var description = node.firstChild.nodeValue;
            }
          }
    }
    Thanks in advance for your time.

  2. #2
    SitePoint Guru
    Join Date
    Nov 2005
    Location
    Midwest
    Posts
    777
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Working Example... Suggestions Appreciated

    OK… I was wrong, my XML was not correct and I think I have a process setup to parse through the XML but I would love feedback to see if this is the best way to do it.

    Here is my new XML.

    Code:
    <?xml version=”1.0” ?>
    <content>
       <data>
          <header>Header 1</header>
          <description>Description 1</description>
       </data>
       <data>
          <header>Header 2</header>
          <description>Description 2</description>
       </data>
    </content>
    Here is my JavaScript that is parsing the XML. This does work but it just seems like there might be a better way. The reason I say this is because I am getting all Data nodes and separating each data node down by their specific node name. Any feedback is appreciated.

    Code:
       var xmlData = response.getElementsByTagName('data');
    
       //-- Parse the response XML
       for ( i=0; i < xmlData.length; i++ )
       {
          var xmlHeader = xmlData [i].getElementsByTagName('header');
          alert("nodeValue = " + xmlHeader[0].childNodes[0].nodeValue );
       
          var xmlDesc = xmlData [i].getElementsByTagName('description'); 
          alert("nodeValue = " + xmlDesc[0].childNodes[0].nodeValue );
       }

  3. #3
    SitePoint Enthusiast PainBehindMyEye's Avatar
    Join Date
    Apr 2005
    Posts
    29
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Perhaps back up a bit and let's look at your approach. I'm assuming you've done the obligatory check to ensure the XML request has been completed before parsing out.

    How are you initiating the call and the write out to the page? Is this event based within an existing page or are you doing this on page load?

    Your loop still requires you to check against the node names and unless you have a large XML file with many node names, you don't necessarily need to get wrapped up on loops right off the hop.

    Here's an example of something I've been working with (ignore the x_loc and y_loc parameters, I just copied and pasted this from an R&D page I'm working on). I've included a similar bit of code I'm using that does something similar to what your doing. The difference is that I've cheated a bit by using formatted blocks in the nodes rather than do the DOM write out with JavaScript (yeah, I used innerHTML too so I'm sure someone will come for my head after reading this ):



    PHP Code:
     function ajaxRead(filewhichnodemyIDx_locy_loc) {
        var 
    xmlObj null;
        var 
    thisnode whichnode;
        var 
    thisDOMobj myID;
        if (
    window.XMLHttpRequest) {
            
    xmlObj = new XMLHttpRequest;
        } else if (
    window.ActiveXObject) {
            
    xmlObj = new ActiveXObject("Microsoft.XMLHTTP");
        } else {
            return;
        }
        
    xmlObj.onreadystatechange = function () {if (xmlObj.readyState == 4) {
            var 
    parentNode;
            
    parentNode xmlObj.responseXML.getElementsByTagName("data")[thisnode];
            
    updateObj(thisDOMobjparentNode.firstChild.data);
            }
        }
        
    xmlObj.open("GET"filetrue);
        
    xmlObj.send("");
    }
    function 
    updateObj(thisDOMobjnodeContents) {
        
    document.getElementById(thisDOMobj).innerHTML nodeContents;

    The XML file (called 'date.xml') looks like this:

    PHP Code:
     <?xml version="1.0" encoding="UTF-8"?>
    <root>
      <data>
        This is some sample data. It is stored in an XML file and retrieved by JavaScript.
      </data>
      <data>
      This is some other sample data. It's also stored in and XML file but is the second node.
      </data>
      <data><![CDATA[<p><img src="images/_IAN2105.jpg">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas enim. Nulla facilisi. Vestibulum accumsan augue vulputate justo. Fusce faucibus. Sed blandit, neque sed lacinia nonummy, diam quam imperdiet justo, at dictum augue nunc a neque. Sed urna lacus, tincidunt at, aliquam id, fringilla id, felis. Vivamus feugiat molestie quam. Sed id dolor. Sed ac purus id sapien</p>]]>
      </data>
      <data><![CDATA[<iframe src="http://www.sitepoint.com" id="SPiframe" name="SPiframe" frameborder="0" />]]>
      </data>
    </root>
    In the case above, I'm using calls from an existing document that sends the parameters ('file', 'whichnode', 'myID') then overwrites 'myID areas with the 'whichnode' portions from the file (or fills in new content on other otherwise-empty 'myID' portions). Have a look at what I've done here (some of which I got from very useful Sitepoint books bought here) and see if it looks at all useful to you.

    - PBME
    Last edited by PainBehindMyEye; Dec 12, 2006 at 09:41. Reason: forgot a function in the code

  4. #4
    SitePoint Guru
    Join Date
    Nov 2005
    Location
    Midwest
    Posts
    777
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    To be honest, I am not sure if I have done the obligatory check to ensure the XML request has been completed before parsing out. I can explain my process and then maybe you can let me know if I have or not.

    What I am working on is a Ajax request. The purpose is to change the values displayed in a select listbox based on the user changing a value in a dropdown box.

    Here is my logic…

    1) The listbox has an onChange event which calls a javascript function.

    2) The javascript function gets the value from the list box, and then makes the Ajax GET request to an ASP (Classic ASP) page passing the listbox value.

    3) The Classic ASP page selects all the data from the database based on the listbox value. Then in a loop, it builds the XML and places the xml in a variable I defined. Then then pass the variable back to the Ajax using a Response.Write.

    Specific code for this section includes setting the ContentType like:

    response.ContentType = "text/xml"

    Here is the Response.Write…

    Response.Write( returnXML )

    4) Then may Ajax code parses the response (which is an XML file). Here is some code for that…

    Code:
    //------------------------------------------------------------------------
    //-- The onResponse() method is called when the request has a state change
    //------------------------------------------------------------------------
    function onResponse() 
    {
    	if( checkReadyState(request) )
    	{
    	    alert("test = " + request.responseText );	
    	    
    	    if( request.responseXML.documentElement == null )
    	    {
    	       alert("null");
    	    }   
    	    else
    	    {
    		   var response = request.responseXML.documentElement;
    		
    		   //-- All pages that use Ajax, have to define this JavaScript function
    		   ProcessResponse( response );
    		}
    	}
    }
    Here is my checkReadyState code…

    Code:
    //------------------------------------------------------------------------------
    //-- Here is where the code focuses on loading and error handling
    //--
    //-- Value:
    //--   0 - Uninitialized. The object is not initialized with data.
    //--   1 - Loading. The object is loading its data.
    //--   2 - Loaded. The object has finished loading its data.
    //--   3 - Interactive. The user can interact with the object even though it's 
    //-        not fully loaded.
    //--   4 - Complete. The object is completely initialized.
    //--
    //-- Response: A value of 200 means that data received in the request is OK.
    //------------------------------------------------------------------------------
    function checkReadyState(obj)
    {
    	if(obj.readyState == 0) 
    	{  }
    	if(obj.readyState == 1) 
    	{  }
    	if(obj.readyState == 2) 
    	{  }
    	if(obj.readyState == 3) 
    	{  }
    	if(obj.readyState == 4)
    	{
    		if(obj.status == 200)
    		{
    			return true;
    		}
    		else if(obj.status == 404)
    		{
    			//-- Add a custom message or redirect the user to another page
    		}
    		else
    		{
    		    //-- There was a problem retrieving the XML     
    		}
    	}
    }
    The above JavaScript functions I have coded in a re-usable library. Then in each page I want to use Ajax, I have a method called ProcessResponse() which handles the returned XML.

    My goal is to get a Ajax library that I can reuse so I do not have to have all the Ajax code in each page. Instead I would like to use the INCLUDE ASP code to include a Ajax library.

    Do you think I have any issues here that I need to be concerned about? Also, I always plan on returning XML when I use this so I wanted to make sure I am using good XML parsing code since I will be basing all my other XML code on this.

    Thanks in advance for your time and knowledge.

  5. #5
    SitePoint Enthusiast PainBehindMyEye's Avatar
    Join Date
    Apr 2005
    Posts
    29
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    You want it to be reusable and that's a great approach. In reading through your parsing routine, you will have to always use the same node names (not a big deal, I do that frequently and did so in the code I posted previously).

    Regarding your checkReadyState, you are explicitly testing against each possible state value rather than listening for the '4' value as the trigger to continue. I think you might end up getting your other values fired off prematurely to early "positive" results for other states like 2 or 3. If you review my previous code posting, you'll see that I'm making a function test for 'true' on return state of '4' (which is the return state for a completed xml request).

    Here's the segment I'm talking about:
    Code:
        xmlObj.onreadystatechange = function () {if (xmlObj.readyState == 4) {
            var parentNode;
            parentNode = xmlObj.responseXML.getElementsByTagName("data")[thisnode];
            updateObj(thisDOMobj, parentNode.firstChild.data);
            }
        }
        xmlObj.open("GET", file, true);
        xmlObj.send("");
    I create an object called xmlObj and then attach properties to it. "onreadystatechange is a function looking for a return value of '4'. Then I tell it to attach the responseXML for a "getElementsByTagName" and it's now set up to do its thing for going through the XML file. After that I send the object off on its' way to go get the XML file with the caveate that it needs to evaluate to 'true' before proceeding with the parse. That's the second last line: xmlObj.open("GET", file, true);

    It's the 'true' parameter that seems to do the trick. I had already defined xmlObj as an xml request object as such:
    Code:
            xmlObj = new XMLHttpRequest;
        } else if (window.ActiveXObject) {
            xmlObj = new ActiveXObject("Microsoft.XMLHTTP");
    testing for support to determine which flavour of the XML request to attach. Since xmlObj is already an XML request, then I attach further properties to it as mentioned above in order to gauge whether or not a state of '4' has been reached on the request. This saves you from the testing of each individual state AND will keep you from inadvertently falling into your tests for the other states. You really don't want to care about any of the other ones because '4' is the only one that matters. So, make the object wait for it ;-)

    If you're concerned of an outage or some other bottleneck, you can add an 'else' to the onreadystatechange function approach but I'd suggest adding a timer or time-out type of set up so that you don't prematurely "hang up" on the XML request mid-stream.

    The way you're parsing is fine. There's many ways to do this and mine is no better than yours. If it's working properly, stick with what makes the most sense to you.

    - PBME

  6. #6
    SitePoint Guru
    Join Date
    Nov 2005
    Location
    Midwest
    Posts
    777
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thank you very much for the feedback. I think we are doing things pretty much the same way (as soon as I remove the non-4 checks).

  7. #7
    I'll take mine raw silver trophy MikeFoster's Avatar
    Join Date
    Dec 2002
    Location
    Alabama, USA
    Posts
    2,560
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Great thread!

    Regarding parsing the received XML, there is a very simple way to do so - and it doesn't matter how deeply nested it is. You can see this in action on a page I'm experimenting around with.

    First set up some data:
    Code:
    var d2ParseData = {
      xe:['gc1', 'gc2'], // xml element whose content is to be enclosed in the following
      ot:['<h5>', '<p>'], // opening tag
      ct:['<\/h5>', '<\/p>'] // closing tag
    };
    When we receive an XML response, call xParseXml:
    Code:
        s = xParseXml(req.xmlDoc, d2ParseData);
    Code:
    function xParseXml(r, d)
    {
      var s = '';
      xWalkTree(r, iter);
      return s;
      function iter(n)
      {
        for (var i = 0; i < d.xe.length; ++i) {
          if (n.nodeName == d.xe[i]) s += d.ot[i] + xNodeText(n) + d.ct[i];
        }
      }
    }

  8. #8
    SitePoint Guru
    Join Date
    Nov 2005
    Location
    Midwest
    Posts
    777
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I will have to give this a try. I really liked your Cross-Browser page... it ws informative and easy to read from a users stand point.

  9. #9
    I'll take mine raw silver trophy MikeFoster's Avatar
    Join Date
    Dec 2002
    Location
    Alabama, USA
    Posts
    2,560
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks!


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •