Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looping through XML with jQuery

I've got some basic code that can loop through some XML that's generated from Adobe RoboHelp (for our help documentation). This works fine, but since a topic could be nested as many time as the writer wants, i need a better way to loop through this XML rather than just nesting .each() loops.

Here's what the XML looks like

<?xml version="1.0" encoding="utf-8"?>
<!--RoboML: Table of Content-->
<roboml_toc>
  <page title="Welcome" url="Welcome.htm"/>
 <book title="Getting Started" url="Getting_Started/Initial_Setup.htm">
   <page title="Initial Setup" url="Getting_Started/Initial_Setup.htm"/>
   <page title="Customize Settings" url="Getting_Started/Settings.htm"/>
 </book>
 <book title="Administrator Services" url="Administrator_Services/General_Administrator.htm">
  <book title="Portal Workspace" url="Administrator_Services/Portal_Workspace/AdminHome.htm">
    <page title="Home" url="Administrator_Services/Portal_Workspace/AdminHome.htm"/>
    <page title="Portal Accounts" url="Administrator_Services/Portal_Workspace/Portal_Accounts.htm"/>

  </book>
  <book title="SpamLab" url="Administrator_Services/SpamLab/SpamLab_Admin_General.htm">
    <page title="Alerts" url="Administrator_Services/SpamLab/Alerts.htm"/>
    <page title="Spam Quarantine" url="Administrator_Services/SpamLab/Admin_Spam_Quarantine_.htm"/>

  </book>

 </book>
 <book title="User Services" url="User_Services/General_User.htm">
  <book title="Portal Workspace" url="User_Services/Portal_Workspace/Home.htm">
    <page title="Home" url="User_Services/Portal_Workspace/Home.htm"/>
    <page title="Self Help" url="User_Services/Portal_Workspace/Self_Help.htm"/>
  </book>
  <book title="SpamLab" url="User_Services/SpamLab/SpamLab_General.htm">
    <page title="Spam Quarantine" url="User_Services/SpamLab/Spam_Quarantine.htm"/>
    <page title="Virus Quarantine" url="User_Services/SpamLab/Virus_Quarantine.htm"/>
  </book>

  <book title="Encryption" url="User_Services/Encryption/Encryption_General.htm">
    <page title="Outlook Plug-in" url="User_Services/Encryption/Encryption_Outlook_Plug_in.htm"/>
  </book>
 </book>
</roboml_toc>

A <page> is an article, and a <book> is a folder.

Her's my jQuery code, which only can look one level deep of tags

   //Get the TOC
$tocOutput="";
$.get(tocURL,function(toc){
    $(toc).children().each(function(){
        $tocOutput+="<li><a href='"+$(this).attr("url")+"'>"+$(this).attr("title")+"</a>";
        if(this.tagName=="BOOK"){
            $tocOutput+="<ul>";
            $(this).find("page").each(function(){
                $tocOutput+="<li><a href='"+$(this).attr("url")+"'>"+$(this).attr("title")+"</a></li>";
            });
            $tocOutput+="</ul>";
        }
        $tocOutput+="</li>";
    });
    $("#list").html($tocOutput);

I know there's a better way to just loop through all elements and then determine if the element has children, etc. but I just can't think of how to do it.

Any help is greatly appreciated!

like image 228
FiniteLooper Avatar asked Oct 15 '09 18:10

FiniteLooper


4 Answers

Recursive functions work well for this. When you create a function that creates and uses an internal recursive closure you can wrap it all up in a neat little package:

    $.get(tocURL, function(toc) {
    function makeToc($xml) {
        // variable to accumulate markup
        var markup = "";
        // worker function local to makeToc
        function processXml() {
            markup += "<li><a href='" + $(this).attr("url") + "'>" + $(this).attr("title") + "</a>";
            if (this.nodeName == "BOOK") {
                markup += "<ul>";
                // recurse on book children
                $(this).find("page").each(processXml);
                markup += "</ul>";
            }
            markup += "</li>";
        }
        // call worker function on all children
        $xml.children().each(processXml);
        return markup;
    }
    var tocOutput = makeToc($(toc));
    $("#list").html(tocOutput);
});
like image 124
keithm Avatar answered Oct 12 '22 02:10

keithm


You can use

$(el).children().length which would return '0' or a positive number, then loop through if it's a positive number which evaluates to true. You could also use a while loop to do this recursively, and re-set the reference handler however I'm not quite sure that would work out because your nodeNames for each subsequent child differ ( or do they? ) .. What's the most nested example you can provide?

like image 24
meder omuraliev Avatar answered Oct 12 '22 00:10

meder omuraliev


THanks so much Keith, that was the ticket - well almost, I had to make one MINOR change and then it worked perfectly!

My code is below.

$tocOutput="";
$.get(tocURL,function(toc){
 function makeToc($xml) {
  // worker function local to makeToc
  function processXml() {
   console.log($(this));
   $tocOutput += "<li><a href='" + $(this).attr("url") + "'>" + $(this).attr("title") + "</a>";
   if (this.nodeName == "BOOK") {
    $tocOutput += "<ul>";
    // recurse on book children
    $(this).children().each(processXml);
    $tocOutput += "</ul>";
   }
   $tocOutput += "</li>";
  }
  // call worker function on all children
  $xml.children().each(processXml);
 }
 var tocOutput = makeToc($(toc));
 $("#toc").html($tocOutput);
 completed($("#toc"));
});

You'll notice all I'm doing is declaring the variable outside the $.get() and then I use $xml.children().each(processXml); instead of $(this).find("page").each(processXml); that you had.

The reason for this is that the children could be pages OR books, but what you had was limiting it to only pages.

Thanks again!

like image 34
FiniteLooper Avatar answered Oct 12 '22 00:10

FiniteLooper


This link provides a good example for use of iterating through xml http://anasthecoder.blogspot.in/2012/02/looping-through-xml-with-jquery.html

xml.find('result').find('permissionDetails').each(function(){
    $(this).children().each(function(){
        var tagName=this.tagName;
        var val=$(this).text();
        if(val==1){
            $('input:checkbox[name='+tagName+']').attr('checked',true);
        }
        else if(val==0){
            $('input:checkbox[name='+tagName+']').removeAttr('checked');
        }
    })

});
like image 33
Ima Avatar answered Oct 12 '22 00:10

Ima