Using XML in scripting

September 16th, 2005 by Andy Nicholas - Viewed 17359 times -




This article is intended to be a fast primer to understanding XML for anyone who hasn’t had the time to investigate its capabilities. XML is a text based file format that allows you to define your own structure within the conventions laid out by the XML format. What follows will hopefully show you the benefits of using XML and how you can use it to manipulate data with minimum effort. How you then apply this to your technical solutions inside and outside of XSI is up to you.

The first part of this article looks at the ideas and concepts behind XML data. The second half will show how easy it is to put it into practice in scripting and C++.

Making sure you have the latest vesion

Before we go on, you should make sure that you have the latest version of Microsoft’s XML support installed. Note that MSXML 4.0 sits on top of the previous versions of MSXML, and so it shouldn’t cause any conflicts with any other software.

To obtain the latest version, I’ve created a web page which should perform the installation quickly and easily through your web browser. Alternatively if you prefer, you can download it directly from Microsoft by visiting this web page here.

XML Basics

Without further delay, let’s look at a very simple example of XML before we examine the benefits of using it:

1
2
3
4
5
< ?xml version="1.0" encoding="utf-8" ?>
<object>
   <name>robot</name>
   <file>c:\project\models\robot.emdl</file>
</object>

The first line is the declaration of the XML file type. At this stage, it’s not worth examining any further, except to say that this is how the file identifies itself as XML.

The second line declares an opening tag with “object” as the identifier. The last line then specifies the corresponding closing tag. All tags must have an open and close tag, unless it is a special XML tag (indicated by a <?, see the first line) or if it contains no child data. If it contains no child data, then as a shortcut it can be written as ““.

The nice indented layout of the example is optional. It wouldn’t matter at all if there were no new lines or white space between the tags. You should note however, that this doesn’t mean that XML ignores the white space. Many XML parsers have by default the option to store the white space as extra child nodes of the parent node, so be warned!

The big advantage of using XML as a file format is scalability. This piece of jargon basically just means that it’s easy to expand on, without needing to have to plan for every eventuality at the outset. For example, if we later decide that we need to store information about the animation of the object, we can just add in an extra set of nodes like this:

1
2
3
4
5
6
7
8
< ?xml version="1.0" encoding="utf-8" ?>
<object>
   <name>robot</name>
   <file>c:\project\models\robot.emdl</file>
   <animation>
      <!-- ...other data... -->
   </animation>
</object>

Imagine that we had written a script with a function to parse the original node tree structure. If that old function had been written properly, it will now just ignore the new nodes. If we update the function, we can tell it to read the new nodes if they exist, or to use some predefined default values if they don’t.

We can also embed the node tree into an entirely different XML tree. For example, we might have an XML file that describes a scene:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
< ?xml version="1.0" encoding="utf-8" ?>
<scene>
   <numobjects>1</numobjects>
   <file>c:\project\scenes\robotfight.scn</file>
   <objectlist>
      <object>
         <name>robot</name>
         <file>c:\project\models\robot.emdl</file>
         <animation>
            <!-- ...other data... -->
         </animation>
      </object>
   </objectlist>
</scene>

The point to note here is that we can still use our original function to parse the node sub-tree, even though it’s located in an entirely different file format. This is why XML is so nice to use; it saves so much unnecessary work maintaining input/output (IO) routines.

Transforming XML with XSL Transforms

Since the format of XML is standardised, it is possible for any application to read the data. Of course, it doesn’t necessarily mean that the application will know what to do with it or what the information means.

For example, if you double click on any valid XML document in Windows, Internet Explorer will start up and display the document, allowing you to expand and collapse the tree nodes by clicking on them. Internet Explorer doesn’t know what the XML data means, but that doesn’t stop it from presenting it to you in a convenient manner.

The way that Internet Explorer does this is by using what is known as a XML style sheet. These are themselves XML documents, but they also use a convention known as XSL. An XSL document has the ability to transform an XML document into another text based format. This new format can be anything of your choosing, but you’ll generally want it to be another XML document.

When Internet Explorer loads your XML file, it is using a default XSL document to transform it into XHTML (an HTML format that is compliant with XML standards). By writing your own XSL documents, you can easily edit and present your XML data with minimum effort.

Here’s an example based on the previous section. The XML file below has an extra XML tag at line 2 which specifies a default XSL document to transform it. This is only a recommendation to the application loading it, and does not mean that it cannot be transformed by a different XSL document.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
< ?xml version="1.0" encoding="utf-8" ?>
< ?xml-stylesheet type="text/xsl" href="TabulateRobots.xslt"?>
<scene>
   <objectlist>
      <object>
         <name>robot</name>
         <file>c:\project\models\robot.emdl</file>
      </object>
      <object>
         <name>droid</name>
         <file>c:\project\models\droid.emdl</file>
      </object>
      <object>
         <name>mech</name>
         <file>c:\project\models\mech.emdl</file>
      </object>
   </objectlist>
</scene>

Below is an XSL transform document that will process this XML, and turn it into an XHTML document that displays the data as a table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
< ?xml version="1.0" encoding="utf-8" ?>
<xsl :stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
</xsl><xsl :template match="/scene/objectlist">
   <html>
      <body>   
         <table border="1" width="500" cellpadding="5">      
            <tr bgcolor="#cccccc">
               <td width="200" align="center"><b>Model Name</b></td>
               <td align="center"><b>File Path</b></td>
            </tr>      
            <xsl :for-each select="object">
            <tr>
               <td><xsl :value-of select="name"/></td>
               <td><xsl :value-of select="file"/></td>
            </tr>
            </xsl>
         </table>
      </body>
   </html>
</xsl>

If you copy and paste the examples into Robot.xml and TabulateRobots.xslt, then you can double click on Robot.xml to see the result, which should look something like this:

Model Name File Path
robot c:\project\models\robot.emdl
droid c:\project\models\droid.emdl
mech c:\project\models\mech.emdl

This hopefully illustrates how easy it is to manipulate XML using XSL. In the above example, we chose to transform the data into XHTML. In fact, it’s possible to transform it into any text based format we choose by using “CDATA” tags. We could even create a script from an XML document like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
< ?xml version="1.0" encoding="utf-8" ?>
<xsl :stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl :output omit-xml-declaration = "yes"/>
   </xsl><xsl :template match="/scene/objectlist">
      < ![CDATA[Class Robot   
      Public name
      Public file
      End Class
      Sub PrintRobots
      ]]>
      < ![CDATA[Dim robotArray(]]><xsl :value-of select="count(object)"/>< ![CDATA[)
      ]]>
      </xsl><xsl :for-each select="object">
         < ![CDATA[Set curRobot =  new Robot
         curRobot.name = "]]><xsl :value-of select="name"/>< ![CDATA["
         curRobot.file = "]]><xsl :value-of select="file"/>< ![CDATA["
         Set robotArray(]]><xsl :value-of select="position()-1"/>< ![CDATA[) = curRobot
         ]]>   
      </xsl>
         < ![CDATA[For i = 0 To ]]><xsl :value-of select="count(object)-1"/>
         < ![CDATA[
         Msgbox robotArray(i).name +" : "+ robotArray(i).file
         Next
         End Sub]]>

The above XSL transform will generate the following VBScript code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Class Robot
   Public name
   Public file
End Class 
 
Sub PrintRobots
   Dim robotArray(3)
   Set curRobot = new Robot
   curRobot.name = "robot"
   curRobot.file = "c:\project\models\robot.emdl"
   Set robotArray(0) = curRobot
   Set curRobot = new Robot
   curRobot.name = "droid"
   curRobot.file = "c:\project\models\droid.emdl"
   Set robotArray(1) = curRobot
   Set curRobot = new Robot
   curRobot.name = "mech"
   curRobot.file = "c:\project\models\mech.emdl"
   Set robotArray(2) = curRobot
   For i = 0 To 2
      Msgbox robotArray(i).name +" : "+ robotArray(i).file
   Next 
End Sub

A few things to note: Firstly, I’ve manually tidied up the formatting of the code (good luck trying to get it to look that neat straight from XSL). Generally it shouldn’t be a problem, unless of course you’re trying to generate Python code.

Secondly, it’s not hard to see that it doesn’t look like a particularly efficient way of doing things. For example, what happens if I have 12000 robots? That’d be one big VBScript!

Thirdly; great, so now we’ve got this script. How can we actually use it? Well, one easy way is to embed the script into HTML and let Internet Explorer run it. A better way though is to call it from your own scripts. That’s where the second half of this article begins.

Lastly, I should point out that generating a script from an XML document is not a particularly common thing to do. Usually, you will be generating a different XML structure, such as XHTML. This example is only to illustrate the flexability of using XML with XSL tranforms.

If you didn’t before, you should now have some sort of understanding of what XML is all about, and how it can be used to store and manipulate data. In the next section, I’ll be showing you how you can easily use it from VBScript and C++ without having to write your own XML parser.

XML and VBScript

You may be wondering why I’m using VBScript in this article. The main reason is that I experienced a few problems at work with JScript returning me the wrong interface from the COM object. I’ve since tested JScript successfully at home, and have come to the conclusion that it must be a problem with the machine at work. I still don’t know what the cause of this is, but until I do, I’m sticking with VBScript for any work with XML.

With the amount of people using XML in web applications, it shouldn’t come as any surprise that Microsoft support the use of XML in the form of COM objects (also known as ActiveX objects). This means that we can use these objects in scripts and let them do all the hard work.

Let’s go straight to an example. Save the VBScript XSL document example above as “VBSScriptRobots.xslt”, and then copy and paste the code below into a new text document and rename it “GenerateScript.vbs”.

1
2
3
4
5
6
7
8
9
10
11
12
13
set xmlDoc = CreateObject("Msxml2.DOMDocument.4.0")
xmlDoc.async=false    
xmlDoc.load "Robots.xml"
 
set xslDoc= CreateObject("Msxml2.DOMDocument.4.0")
xslDoc.async = false
xslDoc.load "VBSScriptRobots.xslt"
 
funcStr = xmlDoc.transformNode(xslDoc)
 
msgbox funcStr
execute funcStr
PrintRobots

If you now run the script, it will display the generated code in a message box, and then it will run the function and present you with three message boxes displaying the XML data records.

I’m sure you’ll still be concerned about the fact that we generated the script with the robot names and filenames hard-coded into the generated function (if not, you should be!). A much better way of going about this task, would be to use the XML document object model to iterate through the XML tree structure, like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Set xmlDoc = CreateObject("Msxml2.DOMDocument.4.0")
xmlDoc.async = False
xmlDoc.load "Robots.xml"
 
Set sceneNode = xmlDoc.documentElement
Set objListNode = sceneNode.firstChild
Set objectListNode = objListNode.childNodes
 
For i=0 To objectListNode.length-1
   Set objectNode = objectListNode.item(i)
   set nameNode = objectNode.firstChild
   set fileNode = nameNode.nextSibling
   msgbox nameNode.text &" : "& fileNode.text
Next

This script doesn’t even need an XSL document. It simply traverses the XML node tree and iterates through each node, displaying the name and file as it goes.

When writing a function to parse an XML node, we should generally make sure it does the following:

1) Ignore any extra nodes that it doesn’t expect to encounter.
2) Use default data when non-critical data is missing.
3) If critical data is missing, attempt to continue to read the rest of the node wherever possible, and at the very least, log the error and make sure it is reported to the user. (There will of course be circumstances where it cannot continue reading data, and the function will have to return an error immediately.)
4) Unless there is good reason, only parse the current level in the tree for each function. This will mean that you can chop and change your XML structure around easily and just change the order that the functions are being called. It also allows for easier code reuse.

The following code does exactly the same job as the code you’ve just seen, but it also implements the above points. While the code is much larger (and slightly slower), it is definitely more robust and is capable of withstanding most syntactical errors in the format. It also provides helpful error messages should anything go wrong by encapsulating the output data inside a resusable class that stores the error information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
Class SceneData
   Private Sub Class_Initialize
      Set objectsDict = CreateObject("Scripting.Dictionary")
   End Sub
   Public objectsDict
End Class
 
Class XMLDataWrapper
   Private Sub Class_Initialize
      Set dataObject = Nothing
      errNum=0
      errMsg=""
   End Sub
 
   public dataObject
 
   Public errNum
   Public errMsg
End Class
 
LoadSceneXMLDoc "Robots.xml"
 
Function LoadSceneXMLDoc(filename)
   Set xmlDoc = CreateObject("Msxml2.DOMDocument.4.0")
   xmlDoc.async = False
   xmlDoc.load filename
 
   Set errObj = xmlDoc.parseError
   If (errObj.errorCode <> 0) Then     
        Msgbox "XML Load Error:"& vbCrLf & errObj.reason
        LoadSceneXMLDoc=False
        Exit Function
   End If
 
   Set XMLDataObj = new XMLDataWrapper
   Set XMLDataObj.dataObject = new SceneData
 
   If LoadSceneNode(xmlDoc.documentElement, XMLDataObj)=True Then
      keysArr = XMLDataObj.dataObject.objectsDict.keys
      dataArr = XMLDataObj.dataObject.objectsDict.items
      num = XMLDataObj.dataObject.objectsDict.count
      For i = 0 to num-1
         Msgbox keysArr(i) &" : "& dataArr(i)
      Next
      LoadSceneXMLDoc=True
   Else
      Msgbox "XML Load Error ("&XMLDataObj.errNum&"):"& vbCrLf & XMLDataObj.errMsg
      LoadSceneXMLDoc=False
   End If
End Function
 
Function LoadSceneNode(sceneNode, XMLDataObj)
   Set objListNode = sceneNode.selectSingleNode("objectlist")   
   If Not objListNode Is Nothing Then
      LoadSceneNode = LoadObjectListNode(objListNode, XMLDataObj)
   Else
      XMLDataObj.errNum=1
      XMLDataObj.errMsg="Can't find <objectlist> node"
      LoadSceneNode = False
   End If
End Function
 
Function LoadObjectListNode(objListNode, XMLDataObj)
   Set objList = objListNode.selectNodes("object")   
   If Not objList Is Nothing Then   
      For i=0 To objList.length-1
         If LoadObjectNode(objList.item(i), XMLDataObj) Then
            LoadObjectListNode = True
         End If
      Next
      If LoadObjectListNode=False Then
         XMLDataObj.errNum=2
         XMLDataObj.errMsg="Could not load any <object> nodes correctly"   
      End If
   Else
      XMLDataObj.errNum=1
      XMLDataObj.errMsg="Can't find any </object><object> nodes"
      LoadObjectListNode = False
   End If
End Function
 
Function LoadObjectNode(objNode, XMLDataObj)
   Set nameNode = objNode.selectSingleNode("name")   
   If Not nameNode Is Nothing Then
      Set fileNode = objNode.selectSingleNode("file")   
      If Not fileNode Is Nothing Then
         XMLDataObj.dataObject.objectsDict.Add nameNode.text, fileNode.text
         LoadObjectNode=True
      Else
         XMLDataObj.errNum=1
         XMLDataObj.errMsg="Can't find <file> node for "&nameNode.text
         LoadObjectNode = False
      End If
   Else
      XMLDataObj.errNum=1
      XMLDataObj.errMsg="Can't find <name> node"
      LoadObjectNode = False
   End If
End Function</name></file></object></objectlist>

If you look at the node loading functions, you should see that they are all quite similar. This means that writing them isn’t that much extra work, since once you’ve written one, you can duplicate alot of the functionality in the others. It also gives rise to an interesting thought; why not try using XSL to automatically generate the code for your XML loader? (That’s left as an exercise to the reader!)

Following these guidelines will generally mean that XML IO routines are well behaved, expandable, and should mean minimal effort when updating their functionality.

To this end, a good idea is to use XML attributes to specify the version of a node. For example, we could rewrite the object node as follows:

1
<object version="1.0"></object>

This would mean that we could easily tell which version of the scene structure we are loading, and know in advance which child nodes are to be expected.

Finding the documentation

You may have been wondering where you can find the documentation on the various objects that we’re using. For example, how do we find out what properties and methods our XML document object has? The first step is to enter the following code:

1
2
Set xmlDoc = CreateObject("Msxml2.DOMDocument.4.0")
MsgBox(typename(xmlDoc))

You should get a message box with the word “DOMDocument” in it. If you now go to msdn.microsoft.com and do a search in the MSDN library for DOMDocument, click on the first result and you should see a list of properties, methods, and events.

The encompassing area that you need to be looking under is MSXML which can be found here. Of course, you can also find this information in the MSDN documentation that comes with any version of Visual Studio .NET.

Moving on to C++

Although it helps, you don’t really have to know that much about COM to be able to use MSXML objects in your C++ code.

The first thing to do is to use the #import statement. This pre-processor directive automatically creates and includes two header files which contain the necessary information for using the MSXML COM interfaces. We use this statement to include the msxml4.dll file which contains the necessary type information to generate the headers. This statement should be placed in a suitable header file to be included by any source files that need to use any of the MSXML interfaces.

1
#import "msxml4.dll"

Once you’ve done that, you can go ahead and create your DOMDocument object. To create the object, you do something like this:

1
2
3
4
5
6
7
8
9
10
11
MSXML2::IXMLDOMDocument2 * xmlDoc=0;
 
HRESULT hr = CoCreateInstance(__uuidof(MSXML2::DOMDocument40),
                      0,
                      CLSCTX_INPROC_SERVER,
                      __uuidof(MSXML2::IXMLDOMDocument2),
                      (void**)&xmlDoc);
if(SUCCEEDED(hr))
{
   //etc.
}

Now that we have a pointer to the IXMLDOMDocument2 interface of the DOMDocument40 object, we can then proceed to use its methods and properties.

To save yourself a lot of grief when using COM interfaces, make sure you check out the Compiler COM Support classes:

Class Purpose
_bstr_t Wraps the BSTR type to provide useful operators and
methods
_com_ptr_t Encapsulates COM interface pointers, and automates the
required calls to AddRef, Release, and
QueryInterface.
_variant_t Wraps the VARIANT type to provide useful operators
and methods.

That should hopefully be enough to get you started with using XML in C++. I’ll save a more detailed XML C++tutorial for a future article.

4 Responses to “Using XML in scripting”

  1. Hi,

    Very interesting article, especially considering that this is exactly what I”m doing right now. I”m doing this with Python, wich also happens to have an ElementTree package, that provides simple and nice XML parsing capabilities.

    I have a few questions…

    1- You have presented two ways of exporting XSI data to XML, one being like tree-based scene file, the other being writing programming objects with attributes that can be used to set or reconstruct XSI objects. What are the pros and cons of both approach?

    2- You didn”t talk much about the actual process of outputting the XML. I”d be very interested in knowing how other do that, and why.

    For the tools I”m working on, the user can select any object in the scene (including objects “above” or “next to” the scene root in the Application scope, like a material library, or the list of action sources, etc), and it will export it as well as everything that is below. To accomplish this, I have wrote a myriad of functions that deal specifically with most of these objects in their own way, and there is this central dispatcher that can determine wich object is being tested and call the appropriate function to handle it. Obviously this resulted in a huge amount of code (around 1700 lines as of this writing), and an important part of this amount is dedicated to handling “cases” (for examples, the local kinematics shorts under an X3DObject).
    Also it runs fast on a small amount of objects, but if you export a single action source that is the result of a plot of an entire character over 100 frames, then it takes 10 minutes and results in a 50 Mb file.

    So I”m interested in where is exporting XML relevant and not, and how do people takle the task of writing the XML.

    3- Now about the import/interpretation/analysis. Once you have parsed the XML data, how do you translate its content to XSI? For instance, let say you wrote out a camera with all its parameters and animation to an XML file. Getting a parser to parse the file is easy, but then how would you deal with the XML camera and get it to XSI?

    The approach I have been taking so far is, as I did with the export of XML, have functions for each specific XML object or so. So for example, I have a camera importer that looks for a camera object in the XML tree. If finds one, now let”s check if this camera exists in the XSI scene. If not, create it, if so, go directly to transfer parameter. Oh, I see the XSI camera no longer has its direction constraint, but the XML one does. So add a constraint, and set it like in the XML camera….. and so on and so on. This will also lead to huge amounts of code and the need for many many functions.

    Once again I”m interested in how you people tackle the task of applying the XML information to XSI.

    Thanks and great article!
    Bernard

  2. Luc-Eric says:

    FYI, the Collada ( as you know, a 3D file format based on XML syntax) converter is written with the FTK, which provides high level objects for dealing with things. The source is available here : http://collada.org/public_forum/viewtopic.php?t=153

  3. Hi Bernhard,

    Thanks for the feedback, I”m glad you found the article useful. To address your questions:

    1) You said that I talked of two methods for exporting XML. I only intended one; that of outputting some form of XML that represented the basic data contained within the scene. I”d favour a scene format that would represent the data in its purest form to preserve scalability. I”d worry that if you try to add any structure or data that is specific to a particular application or usage, then it”ll just get in the way later on when you need to use the same XML data in a different way to that originally intended.

    The application specific part comes in the form of the XSL transform. Different XSL transforms can do completely different things with the same XML data. I”d recommend letting the XSL document do the work for you, it”s much more flexible and is much easier to alter later. Plus, you can store the XSL document as it”s own asset, which neatly separates its functionality from any other pipeline scripts.

    2) I use a script class to generate my XML, it handles things like indents nicely (to make it a little more easy to read) and also provides functionality for me to come back to insert nodes later on. The script class basically wraps the MSXML DOMDocument object. I”ve pasted it in below in case you”re interested:

    Class XMLDocument

    Public Sub Create(rootNodeName)
    Set xmlDoc = CreateObject("Msxml2.DOMDocument.4.0")
    xmlDoc.LoadXML("<?xml version="&Chr(34)&"1.0"&Chr(34)&" encoding="&Chr(34)&"utf-8"&Chr(34)&" ?>"&vbCrLf&"<"&rootNodeName&"/>")
    xmlDoc.preserveWhiteSpace=True
    Set xmlRoot = xmlDoc.documentElement
    indentLevel=1
    End Sub

    Public Function AddElement(parentNode, elementName, elementContent)
    If(typename(parentNode)<>"IXMLDOMElement") Then Set parentNode=xmlRoot

    ”Formatting
    parentNode.appendChild xmlDoc.CreateTextNode(vbCrLf & String(indentLevel,Chr(9)))

    Dim newNode
    Set newNode = xmlDoc.CreateElement(elementName)
    if(elementContent<>"") Then newNode.Text = elementContent
    parentNode.appendChild newNode

    Set lastElementParent=parentNode
    Set AddElement=newNode
    End Function

    Public Sub AddAttribute(node, attributeName, attributeValue)
    Dim att, namedNodeMap
    Set att = xmlDoc.CreateAttribute(attributeName)
    Set namedNodeMap = node.Attributes
    namedNodeMap.setNamedItem att
    node.setAttribute attributeName, attributeValue
    End Sub

    Public Sub StartInsertAt(parentNode, levelToindent)
    indentLevel=levelToindent
    Set lastElementParent=parentNode
    End Sub

    Public Property Get IndentSpacing()
    IndentSpacing = indentLevel
    End Property

    Public Function Indent()
    indentLevel=indentLevel+1
    Indent=indentLevel
    End Function

    Public Function Outdent()
    indentLevel=indentLevel-1
    If not (lastElementParent Is nothing) Then
    lastElementParent.appendChild xmlDoc.CreateTextNode(vbCrLf & String(indentLevel,Chr(9)))
    Set lastElementParent=lastElementParent.parentNode
    End If
    Outdent=indentLevel
    End Function

    Public Function Save(filename)
    ”Add carriage return for closing node
    If(xmlRoot.lastChild.nodeTypeString<>"text") Then
    xmlRoot.appendChild xmlDoc.CreateTextNode(vbCrLf)
    End If
    Save = xmlDoc.Save(filename)
    End Function

    Private xmlDoc
    Private xmlRoot
    Private indentLevel
    Private lastElementParent

    End Class

    I use it like this:

    Function Export_Animation(xmlDoc, parentNode, exportData)
    Dim animNode
    Set animNode = xmlDoc.AddElement(parentNode,"animation","")
    xmlDoc.AddAttribute animNode,"version",exportData.version

    xmlDoc.Indent

    xmlDoc.AddElement animNode,"name",exportData.animName
    xmlDoc.AddElement animNode,"numframes",1+exportData.endFrame-exportData.startFrame
    xmlDoc.AddElement animNode,"fps",exportData.fps

    exportData.success=Export_Skeleton(xmlDoc, animNode, exportData)

    xmlDoc.Outdent

    Export_Animation=exportData.success
    End Function

    Your example of a 50Mb file is quite large for XML, but I think that the flexibility that it offers, far outweighs the disadvantage of processing such a large file. XML usually compresses pretty well, so that can help, although that does add processing time.

    Another strategy would be to split the XML up into numerous smaller XML files, and simply link between them. For example, you could use a node like this:

    <include filename="c:\scenedata\robotmodel.xml" />.

    When your script loads the XML document, it can search for any <include> nodes and where desired, it can replace the <include> node with the tree fragment from the relevant file. This means that you can then run the XSL transform on smaller trees that only contain the appropriate information. Managing the extra files and all the different versions of them could be a headache though.

    Incidentally, most popular databases are adding good support for XML now. For example, SQL can return query data as pure XML, formatted in a variety of different ways. So maybe another alternative would be to push all your data into a database and then generate your XML on the fly with queries.

    3) I”ve not tackled that problem. I”ve been mainly using XML to export animation data. I”ve read it back in to a Custom Display Host, but I”ve not needed to apply it directly to a rig in the scene. What you”re doing sounds like fun though ;-)

    Regards

    Andy

  4. Hi Andy,

    Thanks a lot for your answers.

    About point #3, yeah, it”s lot of fun. In fact it”s rather easy because I write the XML to my taste and thus writing a XML “interpreter” for XSI is easy for most things. The only culpritt though is handling the case of trying to “map” the XML text to existing XSI objects.

    Here are such a functions (Python):
    (Note: I mentioned that I was using ElementTree, but that was because of a bug with BeautifulSoup, another Python XML parser. However that bug was corrected yesterday and thus I have reimplemented it in my code.)

    def processSceneObject( oXSIObject, oXMLObject ):

    “”"
    ARGUMENTS:
    - oXSIObject (object): an XSI 3D object
    - oXMLObject (BeautifulSoup object): an XML ElementTree object that is a tag

    “”"

    # Parameters
    transferParameters( oXSIObject, oXMLObject )

    # ActivePrimitive
    processActivePrimitive( oXSIObject, oXMLObject )

    # LocalProperties
    processProperties( oXSIObject, oXMLObject, ”localproperties” )

    # Kinematics
    processKinematics( oXSIObject, oXMLObject )

    def processKinematics( oXSIObject, oXMLObject ):

    “”"
    ARGUMENTS:
    - oXSIObject (object): the XSI object (must have a Kinematics object
    - oXMLObject (BeautifulSoup object): the XML scene object that has a tag under

    Note: We don”t transfer global kinematics, it”s useless.
    “”"

    oXMLConstraints = oXMLObject.localproperties.constraints.fetch( ”constraint” )
    oXMLLocalKine = oXMLObject.first( ”kinematicstate”, attrs = { ”name” : ”Local Transform” } )

    oXSIConstraints = oXSIObject.Kinematics.Constraints
    oXSILocalKine = oXSIObject.Kinematics.Local

    # Constraints
    for oXMLConstraint in oXMLConstraints:

    oXSIConstraint = None

    if oXSIConstraints.count :: No matching constraint could be established with %s.” % ( oXMLConstaint[ ''fullname'' ] ), c.siError )
    else:
    transferParameters( oXSIConstraint, oXMLConstraint )

    # Local KinematicState
    transferParameters( oXSILocalKine, oXMLLocalKine )

    def transferParameters( oXSIInput, oXMLInput ):

    “”"
    ARGUMENTS:
    - oXSIInput (XSI object): the XSI object that has parameters
    - oXMLInput (BeautifulSoup object): the BeautifulSoup tag object under wich there is a tag

    “”"

    oXSIParameters = oXSIInput.parameters
    oXMLParameters = oXMLInput.parameters.fetch( ”parameter” )

    for oXMLParameter in oXMLParameters:

    if oXMLParameter.contents[0] != ”None”:

    # Get the XSI parameter object
    oXSIParameter = oXSIParameters( oXMLParameter[ ''scriptname'' ] )

    # Check if parameter has a source
    if oXMLParameter[ ''sourceclassname'' ] != ”nosource”:
    # Has a source

    # Dispatch appropriate action to handle source
    if oXMLParameter[ ''sourceclassname'' ] == ”FCurve”: transferFcurve( oXSIParameter, oXMLParameter )
    elif oXMLParameter[ ''sourceclassname'' ] == ”Constraint”: pass
    elif oXMLParameter[ ''sourceclassname'' ] == ”Expression”: transferExpression( oXSIParameter, oXMLParameter )
    elif oXMLParameter[ ''sourceclassname'' ] == ”CustomOperator”: pass
    elif oXMLParameter[ ''sourceclassname'' ] == ”Shader”: pass
    elif oXMLParameter[ ''sourceclassname'' ] == ”ImageClip”: pass
    else: xsi.logmessage( ” :: Unsupported type of source “%s”, parameter “%s” skipped.” % ( oXMLParameter[ ''sourceclassname'' ], oXSIParameter.scriptname ), c.siWarning )

    else:
    # No source
    oXSIParameter.value = oXMLParameter.contents[0]

    I will definitely check the XSL transformation stuff. All this is very new to me.

    Thanks again
    Bernard