Phone: 905 409-1589
Email: info@penproductions.ca
RSS LinkedIn Twitter Twitter
XML Reading
Navigate:
Reading XML Document:
We will start the same way that we did in writing XML files. Load the assembly and then create a xmlDocument object.

 

Code:
--Load the xml assembly
dotNet.loadAssembly "system.xml"
--Create an xml document object.
xmlDoc=dotNetObject "system.xml.xmlDocument"
								

First we will set the path to the XML file that we wrote out in the previous tutorial and store it in xmlPath. Next I'm just doing some error checking to confirm that the file does exist.

If you do a "showMethods xmlDoc" one of the methods is .load. This method will load an XML doc in several different ways. The one that we need is loading from a file.

Every XML file has to have one root element, you can access that element using the .documentElement property. We will set a variable for that called docEle. I usually confirm that the documentElement exists and then I test the name property of docEle. The name property of the documentElement will return the name that we gave it. In this case it should return "Root", I also test this to make sure that I have the right xml file loaded. Usually I will use a more descriptive documentElement name. In the case of penHelper I use the name penObjects since all of my penObjects can use the same format.

Once the documentElement is confirmed I am using "showProperties xmlEle" and then I'm doing the same to to of it's properties. '.attributes' and '.childNodes'. '.attributes' will return a dotNet object that contains all the attributes on a given element, the object if printed will look like this..

Listener:
dotNetObject:System.Xml.XmlAttributeCollection
and has all of it's own properties and methods. We will use that later on. '.childNodes' returns an array of child elements of documentElement, we will use this property to recurse down the hierarchy to collect all the elements and their contents.

 

Code:
--Open the file in Max to see the file. 
xmlPath=((getDir #scripts)+"\\test.xml")

--If the file exists then load it.
if doesFileExist xmlPath then
(
	--Load the XML file.
	xmlDoc.load xmlPath
	--Check to make sure the xmlDoc has a root element. 
	docEle=xmlDoc.documentElement

	--If the root element exists continue. 
	if docEle!=undefined and docEle.name=="Root" then
	(
		clearListener()
		format "Element Name: %\n\n" docEle.name
		
		format "docEle Props:\n"
		showProperties docEle
		
		format "\nAttribute Props:\n"
		showProperties docEle.Attributes
		
		format "\nChildNodes Props:\n"
		showProperties docEle.ChildNodes
	)
)
								

Reading Child Elements:
To read in all the elements in the XML document we need to have a recursive function. In this case the function is printing the name of each element as it goes. Not so use full yet as the name of each element is "node" so we don't really know what we are getting.

 

Code:
fn recurseXml ele=
(
	for i = 0 to ele.childNodes.count-1 do
	(
		print ele.ChildNodes.itemOf[i].name
		recurseXml ele.ChildNodes.itemOf[i]
	)
)
								

I have changed up the if statement below from showing the properties and methods to calling the recurseXml function. First is create the xmlDoc and then checks to see if it has a root element called "Root: then calls the recursion.

 

Code:
--If the file exists then load it.
if doesFileExist xmlPath then
(
	--Load the XML file.
	xmlDoc.load xmlPath
	--Check to make sure the xmlDoc has a root element. 
	docEle=xmlDoc.documentElement
	
	--If the root element exists continue. 
	if docEle!=undefined and docEle.name=="Root" then
	(
		--Recurse the XML tree.
		recurseXml docEle
	)
)
								

Collecting Attributes:
There are a couple of ways to get the attributes that you want from an element. In the recurse function I have used a method that will return all the attributes in an element and their value. This might be of use if you are not sure what attributes might be available for a given element. I also show a method for getting an attribute by name using getNamedItem "theName". Using this you can get an attribute that you already know exists and you know the name.

 

Code:
fn recurseXml ele=
(
	for i = 0 to ele.childNodes.count-1 do
	(
		--Loop through all the attributes in an element. 
		for ii = 0 to ele.ChildNodes.itemOf[i].attributes.count-1 do
		(
			--First method
			--Get the name of the attribute
			attribName=ele.ChildNodes.itemOf[i].attributes.itemOf[ii].name
			--Get the value of the attribute as a string. 
			attribVal=ele.ChildNodes.itemOf[i].attributes.itemOf[ii].value
			
			--Second method.
			--You can also get the attribute by name and then get the value from it. 
 			attrib=ele.ChildNodes.itemOf[i].attributes.getNamedItem attribName
			--Format the result to the listener
 			format "AttribName: % Value: %\n" attrib.name attrib.value
		)
		recurseXml ele.ChildNodes.itemOf[i]
	)
)
								

You can also get to the attributes without the .attributes property if you know the name of it using the getAttributeNode method and passing it the name of the attribute and then accessing the value property.

 

Code:
(ele.ChildNodes.itemOf[i].GetAttributeNode "class").value

"class" being the name of the attribute that we want the value of. The only values that are returned are string so we will have to use a function like execute if we want to get values or other classes of max attributes.

Practical Application:
This is a semi practical example as it doesn't take into account many attributes that would be needed to recreate the objects that were saved.

We have added another parameter to the function called parent. This will be the last node created and will set as the parent of the next.

Once we have the values of the two attributes that we stored with each node we will execute the class attribute string and then check if it is a creatable class. If it is creatable then we create it, name it and then set the parent property. When the recurseXML function is called again we need to pass the node that was created to the parent property so that it can be set on the next object.

 

Code:
fn recurseXml ele parent:undefined=
(
	--Loop through all the children
	for i = 0 to ele.childNodes.count-1 do
	(
		--Get the attribute value called class
		objClassStr=(ele.ChildNodes.itemOf[i].GetAttributeNode "class").value
		--Get the attribute value called name
		objNameStr=(ele.ChildNodes.itemOf[i].GetAttributeNode "name").value
		
		--Check if the class value can be turned into a max class object and is creatable.
		objClass=(execute objClassStr)
		if objClass!=undefined and objClass.creatable then 
		(
			--Create the object
			n=objClass()
			--Name the object
			n.name=objNameStr
			--Set the parent of the object if there is one. 
			n.parent=parent
		)
		
		--recurse the xml tree
		recurseXml ele.ChildNodes.itemOf[i] parent:n
	)
)
								

Some of the data that is missing form this example are position, properties of the objects like radius of a sphere or the height of a box. You could also take into consideration UV coordinates, materials and controllers on the object.