Recursive function to loop through XML/DOM

i’m trying to write a function that will loop through XML/DOM and store all the node names, values (if any) and attributes (if any) into a javascript object.

here’s my very basic XML file…

<?xml version="1.0" encoding="iso-8859-1"?>
<data>
    <people>
        <person firstname="Bob" lastname="Jones">
            <email>bobjones@aol.com</email>
            <mobile>865-555-5555</mobile>
        </person>
        <person firstname="Larry" lastname="Smith">
            <email>larrysmith@yahoo.com</email>
            <mobile>865-444-4444</mobile>
        </person>
        <person firstname="Wanda" lastname="Williams">
            <email>wandawilliams@hotmail.com</email>
            <mobile>865-333-333</mobile>
        </person>
    </people>
    <computers>
        <computer brand="Apple">
            <model>MacBook</model>
            <year>2007</year>
        </computer>
        <computer brand="Sony">
            <model>VAIO</model>
            <year>2007</year>
        </computer>
    </computers>
</data>

i’m passing along the XML document via AJAX (var doc = req.responseXML.documentElement).

i can step through the nodes, but not dynamically, which is what i want to do since i’m going to be manipulating multiple XML documents.

for example, this will output the first couple of levels. real basic, but not dynamic. if i had an XML file with 20 levels i’d have to make one long for() loop.

    var result = doc.nodeName;
    for (var i = 0; i < doc.childNodes.length; i++) {
        if (doc.childNodes[i].nodeType == 1) {
            result += "\
\	" + doc.childNodes[i].nodeName;
            for (var j = 0; j < doc.childNodes[i].childNodes.length; j++) {
                if (doc.childNodes[i].childNodes[j].nodeType == 1) {
                    result += "\
\	\	" + doc.childNodes[i].childNodes[j].nodeName + " (" + doc.childNodes[i].childNodes[j].childNodes.length + ")";
                }
            }
        }
    }
    alert(result);

how can i dynamically step through the document object?

if the above stuff was at all confusing, basically i want to convert an XML document into a javascript object so this…

xml

<foo>
     <bar>oh noes!</bar>
</foo>

becomes this…

javascript object

var contents_of_bar = foo.bar;

Hope this helps…


//
//  This function converts an XML to JS objects and arrays.
//
function convertXmlToStructure(x) {

	//
	//  Output function.
	//
	var o = {
		_text: '',
	};

	//
	//  c = each child
	//
	var c = x.firstChild;

	//
	//  Loop through child nodes.
	//
	while (c) {

		//
		//  Add to _text property if it is a text.
		//
		if (c.nodeName == '#text') {
			o._text += c.nodeValue;

		//
		//  Else if it is a normal node.
		//
		} else if (c.nodeType == 1) {

			//
			//  Create property for each node name.
			//
			if (typeof o[c.nodeName] == 'undefined')
				o[c.nodeName] = [];
			o[c.nodeName][o[c.nodeName].length] = convertXmlToStructure(c);

		}

		//
		//  ...Next!
		//
		c = c.nextSibling;

	}

	//
	//  Now, attributes!
	//
	var a = x.attributes;
	var i = 0;
	if (a) {
		for (var i = 0; i < a.length; i ++) {
			o[a[i].name] = a[i].value;
		}
	}

	//
	//  Clean the text
	//
	if (o._text.match(/^\\s*$/))
		delete o._text;

	//
	//  Finished!
	//
	return o;

}

Example Code:


	var xml  = xh.responseXML;
	var xmls = convertXmlToStructure(xml);
	var out  = '';
	var i;

	var people = xmls.data[0].people[0].person;
	for (i = 0; i < people.length; i ++) {
		out += people[i].firstname + ' ' + people[i].lastname + '\
 -> Email: ' + people[i].email[0]._text + '\
 -> Mobile: ' + people[i].mobile[0]._text + '\
';
	}

	alert (out);

So for your example, that oh noes file, when you use my convertXmlToStructure function, you can refer to its value like this:

xmls.foo[0].bar[0]._text

Where xmls is a parsed JavaScript object.

that works well! thank you very much.