xml with dotnet on maxscript
by eks on Apr.25, 2011, under 3ds Max, pipeline, xml
Paul Neale has a very good tutorial about dotnet and maxscript, specially reading and writing xml, but I decided to add some more information from a slightly different approach. First, let’s translate an XML to dotnetesque:
<rootElement> <element1 /> <element2 attributeName="attribute content"> <element2.1>innerXML of this element</element2.1> </element2> </element>
And the entire document is a XmlDocument (instance of dotNetObject “System.Xml.XmlDocument“)
There are many ways to read and write XML with dotnet. If you need to read the whole document first, probably the most optimized is by using XmlParser, but it can be a bit of a pain. If you are using Maxscript you are not doing it in realtime, so a couple miliseconds won’t make that much of a difference.
Anyway, if you are looking for specific information inside tags you can easily do it using .GetElementsByTagName:
XmlDoc = dotNetObject "System.Xml.XmlDocument" XmlDoc.load "D:\\Characters\\Rider\\exportEMFX.xml" -- I know <baseCharacter> has only one entry, so I get the first one tagBaseChar = XmlDoc.GetElementsByTagName "baseCharacter" baseCharacter = (tagBaseChar.item 0).InnerXML -- I don't know how many <file>s there is, so I get them into an array tagFiles = XmlDoc.GetElementsByTagName "file" fileList = (for i=0 to (tagFiles.count-1) collect (tagFiles.item i).InnerXML)
To read something like:
<mlExportEMFX> <baseCharacter>D:\\Characters\\Rider\\rider.max</baseCharacter> <filesToLoad> <file>D:\\Characters\\Rider\\FBX\\stopNeutral_L.fbx</file> <file>D:\\Characters\\Rider\\FBX\\stopNeutral_R.fbx</file> </filesToLoad> </mlExportEMFX>
Note that GetElementsByTagName doesn’t care about hierarchy, it just finds the element you are looking for, no matter where in the tree it is or how many times it’s repeated. If you have a “file” under “filesToLoad” and also under “filesToSave” it won’t differentiate between them.
If you have a tree/graph structure and you don’t know each tag name one solution is to recourse through it all loading them on a dictionary or array.
Say you have an hierarchy structure you want to save, together with their layers, into an XML. You can store the data in two arrays with:
/* this two are recursive functions to return the hierarchy in a single array */ fn GetHierarchyTree lParent = ( toRet = #(lParent.name) for o in lParent.children do append toRet (GetHierarchyTree o) toRet ) fn GetLayerTree lParent = ( toRet = #(lParent.layer.name) for o in lParent.children do append toRet (GetLayerTree o) toRet ) /* this is just an example, you shouldn't use globals like this */ global parentTree = #() global layerTree = #() /* gets the data into parentTree and layerTree */ fn getData = ( for o in objects where o.parent == undefined do ( append parentTree (GetHierarchyTree o) append layerTree (GetLayerTree o) ) )
To save this into an XML you also go with recursion:
fn recurseTreeToXML baseNode xmlDocNew pTree lTree = ( for i=1 to pTree.count do ( -- if it's a leaf, there's data, so we append if (isKindOf pTree[i] String) then ( curNode = xmlDocNew.CreateElement pTree[i] curNode.SetAttribute "layer" lTree[i] baseNode.appendChild curNode baseNode = curNode ) else -- it's a branch with more data underneath ( recurseTreeToXML baseNode xmlDocNew pTree[i] lTree[i] ) ) ) fn saveDataOnXML = ( -- you cannot append elements to an xmldoc -- you have to append elements to a single root element, child of xmldoc xmlDocNew = dotNetObject "System.Xml.XmlDocument" xmlRoot = xmlDocNew.CreateElement (getFilenameFile maxfilename) xmlDocNew.appendChild xmlRoot recurseTreeToXML xmlRoot xmlDocNew parentTree layerTree xmlDocNew.save ("C:\\temp\\test.xml") )
And also to read an XML:
fn recurseXML docElement = ( tempPTree = #(docElement.name) tempLayerTree = #(docElement.getAttribute "layer") for i = 0 to (docElement.childNodes.count - 1) do ( tmp = (recurseXML docElement.childNodes.itemOf[i]) append tempPTree tmp[1] append tempLayerTree tmp[2] ) return #(tempPTree, tempLayerTree) ) global parentTreeXML = #() global layerTreeXML = #() fn readXML = ( xmlDoc = dotNetObject "System.Xml.XmlDocument" xmlDoc.load ("C:\\temp\\test.xml") docElement = XmlDoc.documentElement -- if we don't do this here we end up with the root node on parentTreeXML -- which can be worked out in anoter way also, of course. for i = 0 to (docElement.childNodes.count - 1) do ( tmp = (recurseXML docElement.childNodes.itemOf[i]) append parentTreeXML tmp[1] append layerTreeXML tmp[2] ) )
Remember, besides dotnet help website, showproperties and showmethods are your best friends. You can also copy this code and paste on maxscript editor or jedit or somewhere with a syntax highlighter to better read it.
Warning: count(): Parameter must be an array or an object that implements Countable in /home/public/wp-includes/class-wp-comment-query.php on line 388
July 8th, 2013 on 03:15
Hi, at first thank you very much for the samples. Helped me a lot!
I’ve noticed one error with ‘recurseTreeToXML’
in ‘baseNode = curNode’, it makes every node becoming a children node for the previous(although maybe this is what was your intention).
Here is may workaround if same may need it.
adding an optional arg to the function lastNode:
and replace the line ‘baseNode = curNode’ with ‘if pTree[i+1]!=undefined and classof pTree[i+1]== array do lastNode=curNode’ and the recurse ‘recurseTreeToXML lastNode xmlDocNew pTree[i] lTree[i]’