DOM guide: Creating documents

From COLLADA Public Wiki
Jump to: navigation, search

Creating a simple document

All interaction with the DOM begins with the DAE object. The DAE object provides a container within which documents can cross-reference each other, and provides state settings needed by several other objects.

DAE dae;

To add a document, call the DAE::add method with a file name.

dae.add("simple.dae");

Now write the document using one of the DAE::write* methods.

dae.writeAll();

DAE::writeAll writes all open documents to the paths originally given. In our case we only have one document, so this will create a Collada file "simple.dae" in the current directory.

The entire program is quite simple:

#include <dae.h>

int main() {
	DAE dae;
	dae.add("simple.dae");
	dae.writeAll();
	return 0;
}

as is the output Collada file:

<?xml version="1.0" encoding="UTF-8"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1"/>

Creating a valid document

The previous code outputs an xml document, but it isn't a valid Collada document according to the Collada schema. See schema validation for more info.

Let's create a full Collada document. Collada requires an <asset> element be present as a child of <COLLADA>.

daeElement* root = dae.add("valid.dae");
daeElement* asset = root->add("asset");

We create a document just as we did before, but this time we receive the output into a daeElement object. In the DOM, daeElement is the root class of all Collada element objects. The DAE::add method returns a pointer to the <COLLADA> element, which is the root element of all Collada documents, or null if creating the document fails for some reason.

Then we use the daeElement::add method to add a new child node to the <COLLADA> element. Just as the DAE::add method adds new documents to the DOM, the daeElement::add method adds new child elements to an already existing element. daeElement::add returns a daeElement pointer, which is the newly created element.

According to the schema, the <asset> element must have child elements <contributor>, <created>, and <modified>. Let's create those elements.

daeElement* contributor = asset->add("contributor");
daeElement* created = asset->add("created");
daeElement* modified = asset->add("modified");

The <contributor> element can be empty according to the schema, so we'll leave it empty for now. The <created> and <modified> elements must contain valid xs:dateTime strings. We'll set them appropriately.

const char* date = "2008-04-08T13:07:52-08:00";
created->setCharData(date);
modified->setCharData(date);

The daeElement::setCharData is used to set the xml character content of an element.

Now just write the document with DAE::writeAll and we're done. We now have a complete Collada document, although it doesn't have any interesting content yet.

// The whole program
#include <dae.h>
#include <dom/domCOLLADA.h>

int main() {
	DAE dae;
	daeElement* root = dae.add("valid.dae");
	daeElement* asset = root->add("asset");
	daeElement* contributor = asset->add("contributor");
	daeElement* created = asset->add("created");
	daeElement* modified = asset->add("modified");
	const char* date = "2008-04-08T13:07:52-08:00";
	created->setCharData(date);
	modified->setCharData(date);
	dae.writeAll();
	return 0;
}
<!-- The resulting (schema conforming!) xml document -->
<?xml version="1.0" encoding="UTF-8"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
	<asset>
		<contributor/>
		<created>2008-04-08T13:07:52-08:00</created>
		<modified>2008-04-08T13:07:52-08:00</modified>
	</asset>
</COLLADA>

Creating xml data

At its core, the DOM is just a library for creating xml data, with some added support for implementing concepts specific to Collada. When creating Collada xml data, there are three basic operations to consider: creating elements, setting attributes, and setting character data.

Creating elements

As we saw in the example code, creating new elements is done with the daeElement::add method.

daeElement* add(daeString name, int index = -1);

This method returns a pointer to the element if it was successfully created, and null otherwise. daeElement::add also takes an optional integer index which specifies the location in the child list to put the new element. In practice this is rarely used because when the schema requires elements to appear in a certain order, the DOM handles this automatically for you.

One additional nicety of daeElement::add is that you can pass it a whitespace separated list of element names. The DOM will create each element in the list as a child of the previous element, and return the last element created. So, for example, if you have an empty <COLLADA> element and you want to create a new <geometry> containing a <mesh>, you could do that as follows (recall that a <geometry> must appear in a <library_geometries>)

daeElement* mesh = root->add("library_geometries geometry mesh");

instead of

daeElement* geomLib = root->add("library_geometries");
daeElement* geom = geomLib->add("geometry");
daeElement* mesh = geom->add("mesh");

Setting attributes

Setting attributes is done via the daeElement::setAttribute method.

virtual daeBool setAttribute(daeString name, daeString value);

This method returns true if the attribute with the given name was found and the value applied successfully, and false otherwise. You might use it like this.

// Create a <geometry> element and set the 'id' attribute.
daeElement* geom = root->create("library_geometries geometry");
geom->setAttribute("id", "myGeom");

// Create an <asset><unit> element and set the 'meter' and 'name' attributes.
daeElement* unit = root->create("asset unit");
unit->setAttribute("meter", "0.01");
unit->setAttribute("name", "centimeter");

Setting character data

Character data refers to the text contained within an element. For example, in the xml <translate>1 2 3</translate>, the text "1 2 3" is the character data.

In the DOM you set character data with the daeElement::setCharData method.

daeBool setCharData(const std::string& data);

Note that not all elements support character data. This method returns true if the element supports character data and the data was successfully changed, and returns false otherwise.

We've already seen daeElement::setCharData used when writing a valid document. Here's another example.

// Create a <node> with a <translate> element and set the translation's value
daeElement* translate = root->add("library_nodes node translate");
translate->setCharData("1 2 3");

The dom* classes

Earlier I mentioned that daeElement is the base class of all Collada element objects. In general, the DOM offers two ways of working with xml data: the daeElement interface (consisting of the daeElement methods add, setAttribute, and setCharData), and the dom* classes. In the DOM, every Collada schema type gets mapped to its own class. For example, the <node> element has a corresponding class domNode, <asset> has a class domAsset, <geometry> has a class domGeometry, and so on.

Because every dom* class also implements the daeElement interface, you have a choice of how to create data using the DOM. You can use the string based interface provided by daeElement, or you can use the more strongly typed interface provided by the dom* class. In a previous example I showed how to set the <asset><unit> information. Here's another example, this time using the domAsset::domUnit class.

// Create an <asset><unit> element and set the 'meter' and 'name' attributes.
// Don't forget to #include <dom/domAsset.h>
domAsset::domUnit* unit = (domAsset::domUnit*)root->add("asset unit");
unit->setMeter(0.01);
unit->setName("centimeter");

This time we need to #include <dom/domAsset.h> to make use of the domAsset::domUnit class. This illustrates another concept of the DOM's code generator: when the schema uses nested types, the code generator generates nested C++ classes.

Since the daeElement::add method returns a daeElement object, we need to cast it to the appropriate type (domAsset::domUnit in this case). It's possible that you might sometimes accidentally cast to the wrong type, which would cause a crash when you tried to use the object. To help prevent this the DOM provides a cast operator daeSafeCast which does type checking to make sure the type you're casting to and the type of the given object match. You would use it like this.

domAsset::domUnit* unit = daeSafeCast<domAsset::domUnit>(root->add("asset unit"));

If you try to cast to the wrong type, daeSafeCast will return null.

Let's see another example. This time let's create a <geometry> with a <float_array> and set the array to contain the values 1 2 3 and 4.

domFloat_array* floatArray = (domFloat_array*)root->add("library_geometries geometry mesh source float_array");
floatArray->getValue().append4(1.0, 2.0, 3.0, 4.0);

Many of the dom* classes have a getValue method which returns an object that contains a binary representation of their character data. In this case, the domFloat_array::getValue method returns a daeTArray of Collada floats (which confusingly are actually C++ doubles). daeTArray is a simple array wrapper object, similar to std::vector. We call the daeTArray::append4 method to set four float values. See daeArray.h for more info.

Internally, the DOM stores all data in a binary form appropriate for the type of data being stored. For example, a <float_array> element gets mapped to a domFloat_array object which stores data as a C++ array of float values, and not as a string.

daeElement vs dom*

We could also have used the daeElement interface to set the <float_array> data.

daeElement* floatArray = root->add("library_geometries geometry mesh source float_array");
floatArray->setCharData("1.0 2.0 3.0 4.0");

Internally the string will get broken down by whitespace and the individual values will be converted to floats and added to the daeTArray that the DOM uses internally to store the values. This is all taken care of behind the scenes by the DOM.

Which method to use, the daeElement interface or the dom* classes, depends entirely on the context of what you're trying to do. Sometimes one method is clearly easier to use (i.e. less code) than the other. Working with the dom* classes will be more efficient because no string conversions need to be done. In general I tend to use whatever method is easier to code.

Working with <extra> data

To work with <extra> data we use the daeElement interface just like we do for normal data. Let's say we want to add some <extra> data to a <node>. You would do that as follows.

daeElement* extra = root->add("library_nodes node extra");
daeElement* technique = extra->add("technique");
technique->setAttribute("profile", "steveT");
daeElement* elt = technique->add("myElement");
elt->setAttribute("myAttr", "myValue");
elt->setCharData("this is some text");

The output looks like this.

<node>
    <extra>
        <technique profile="steveT">
            <myElement myAttr="myValue">this is some text</myElement>
        </technique>
    </extra>
</node>

Full exporter example

There's also an example of exporting a full model with geometry, materials, etc, as part of the DOM's test suite. The file is test/export.cpp and comes with the DOM.


COLLADA DOM - Version 2.4 Historical Reference
List of main articles under the DOM portal.
User Guide chapters:  • Intro  • Architecture  • Setting up  • Working with documents  • Creating docs  • Importing docs  • Representing elements  • Working with elements  • Resolving URIs  • Resolving SIDs  • Using custom COLLADA data  • Integration templates  • Error handling

Systems:  • URI resolver  • Meta  • Load/save flow  • Runtime database  • Memory • StringRef  • Code generator
Additional information:  • What's new  • Backward compatibility  • Future work
Terminology categories:  • COLLADA  • DOM  • XML