Magnum::OpenDdl::Document class

OpenDDL document.

Parser for the OpenDDL file format.

The parser loads the file into an in-memory structure, which is just a set of flat arrays. When traversing the parsed document, all Structure and Property objects are just tiny wrappers around references to the internal data of the originating document, thus you must ensure that the document is available for whole lifetime of these instances. On the other hand this allows you to copy and store these instances without worrying about performance.

Usage

To avoid needless allocations and string comparisons when using the parsed document, all structure and property names are represented as integer IDs. To parse a file, you first need to build a list of string names with their corresponding IDs for both structure and property names. The following example is a subset of the OpenGEX file format:

namespace OpenGex {

enum: Int {
    GeometryObject,
    IndexArray,
    Mesh,
    VertexArray
};

const std::initializer_list<OpenDdl::CharacterLiteral> structures{
    "GeometryObject",
    "IndexArray",
    "Mesh",
    "VertexArray"
}

enum: Int {
    attrib,
    key,
    motion_blur,
    primitive,
    shadow,
    two_sided,
    visible
};

const std::initializer_list<OpenDdl::CharacterLiteral> properties{
    "attrib",
    "key",
    "motion_blur",
    "primitive",
    "shadow",
    "two_sided",
    "visible"
};

}

Each enum value has corresponding string representation and the string identifiers are then passed to parse():

OpenDdl::Document d;
bool parsed = d.parse(data, OpenGex::structures, OpenGex::properties);

If the file contains structures or properties which are not included in the identifer lists, these are parsed with UnknownIdentifier ID. If the document has syntax errors, the function returns false and prints detailed diagnostics on Corrade::Utility::Error output. After parsing you can traverse the document using IDs from the enums:

for(OpenDdl::Structure geometryObject: d.childrenOf(OpenGex::GeometryObject)) {
    // Decide about primitive
    if(Containers::Optional<OpenDdl::Property> primitive = geometryObject.findPropertyOf(OpenGex::primitive)) {
        if(!primitive->isTypeCompatibleWith(OpenDdl::Type::String)) {
            // error ...
        }

        std::string str = primitive->as<std::string>();
        if(str == "triangles") {
            // ...
        } else if(str == "lines") {
            // ...
        } // ...
    } else {
        // ...
    }

    // Parse vertex array
    if(Containers::Optional<OpenDdl::Structure> vertexArray = geometryObject.findFirstChildOf(OpenGex::VertexArray)) {
        if(!vertexArray->hasChildren() || vertexArray->firstChild().type() != OpenDdl::Type::Float) {
            // error ...
        }

        Containers::ArrayView<const Float> vertexArray = vertexArray->firstChild().asArray<Float>();
        // ...

    } else {
        // error ...
    }
}

As you can see, the error checking can get pretty tiresome after a while. That's when document validation proves to be useful. The validation is just rough and checks only proper document hierarchy, allowed structure and property types, structure count and presence of required properties, but that's often enough to avoid most of the redundant checks. You define which structures can appear at document level and then for each structure what properties and which substructures it can have. Again a (very stripped down) subset of OpenGEX specification:

namespace OpenGex {

using namespace OpenDdl::Validation;

// GeometryObject and Metric can be root structures
const Structures rootStructures{
    {GeometryObject, {}},
    {Metric, {}}
};

// Info about particular structures
const std::initializer_list<Structure> structureInfo{
    // Metric structure has required key string property and contains exactly
    // one float or string primitive substructure with exactly one value
    {Metric,            Properties{{key, PropertyType::String, RequiredProperty}},
                        Primitives{Type::Float,
                                   Type::String}, 1, 1},

    // GeometryObject structure has optional visible and shadow boolean
    // properties and one or more Mesh substructures
    {GeometryObject,    Properties{{visible, OpenDdl::PropertyType::Bool, OptionalProperty},
                                   {shadow, OpenDdl::PropertyType::Bool, OptionalProperty}},
                        Structures{{Mesh, {1, 0}}}},

    // Mesh structure has optional lod and primitive properties, at least one
    // VertexArray substructure and zero or more IndexArray substructures
    {Mesh,              Properties{{lod, OpenDdl::PropertyType::UnsignedInt, OptionalProperty},
                                   {primitive, OpenDdl::PropertyType::String, OptionalProperty}},
                        Structures{{VertexArray, {1, 0}},
                                   {IndexArray, {}}}},

    // IndexArray structure has exactly one unsigned primitive substructure
    // with any number of values
    {IndexArray,        Primitives{OpenDdl::Type::UnsignedByte,
                                   OpenDdl::Type::UnsignedShort,
                                   OpenDdl::Type::UnsignedInt}, 1, 0},

    // VertexArray structure has required attrib property and exactly one float
    // substructure with any number of values
    {VertexArray,       Properties{{attrib, OpenDdl::PropertyType::String, RequiredProperty}},
                        Primitives{OpenDdl::Type::Float}, 1, 0}
};

}

You then pass it to validate() and check the return value. As with parse(), structures with UnknownIdentifier ID are ignored and if the validation fails, detailed diagnostics is printed on Corrade::Utility::Error output:

bool valid = d.validate(OpenGex::rootStructures, OpenGex::structureInfo);

If the document is valid, you can access child structures and properties directly with e.g. Structure::firstChildOf(), Structure::propertyOf() etc. instead of using Structure::findFirstChildOf(), Structure::findPropertyOf() etc. and checking return value all the time:

// Decide about primitive
if(Containers::Optional<OpenDdl::Property> primitive = geometryObject.findPropertyOf(OpenGex::primitive)) {
    auto&& str = primitive->as<std::string>();
    if(str == "triangles") {
        // ...
    } else if(str == "lines") {
        // ...
    } // ...
} else {
    // ...
}

// Parse vertex array
OpenDdl::Structure vertexArray = geometryObject.firstChildOf(OpenGex::VertexArray);
auto&& attrib = vertexArray.propertyOf(OpenGex::attrib).as<std::string>();
if(attrib == "position") {
    // ...
} else if(attrib == "normal") {
    // ...
}

// Parse vertex array data
Containers::ArrayView<const Float> data = vertexArray.firstChild().asArray<Float>();
// ...

Constructors, destructors, conversion operators

Document() explicit
Constructor.
Document(const Document&) deleted
Copying is disabled.
Document(Document&&) deleted
Moving is disabled.

Public functions

auto operator=(const Document&) -> Document& deleted
Copying is disabled.
auto operator=(Document&&) -> Document& deleted
Moving is disabled.
auto parse(Containers::ArrayView<const char> data, std::initializer_list<CharacterLiteral> structureIdentifiers, std::initializer_list<CharacterLiteral> propertyIdentifiers) -> bool
Parse data.
auto isEmpty() -> bool
Whether the document is empty.
auto findFirstChild() const -> Containers::Optional<Structure>
Find first top-level structure in the document.
auto firstChild() const -> Structure
First top-level structure in the document.
auto children() const -> Implementation::StructureList
Top-level structures.
auto findFirstChildOf(Type type) const -> Containers::Optional<Structure>
Find first custom top-level structure of given type.
auto findFirstChildOf(Int identifier) const -> Containers::Optional<Structure>
Find first custom top-level structure of given identifier.
auto findFirstChildOf(std::initializer_list<Int> identifiers) const -> Containers::Optional<Structure>
auto findFirstChildOf(Containers::ArrayView<const Int> identifiers) const -> Containers::Optional<Structure>
auto firstChildOf(Type type) const -> Structure
First custom top-level structure of given type.
auto firstChildOf(Int identifier) const -> Structure
First custom top-level structure of given identifier.
template<class ... T>
auto childrenOf(Int identifier, T... identifiers) const -> Implementation::StructureOfList<sizeof...(T)+1>
Top-level structures of given identifier.
auto validate(Validation::Structures allowedRootStructures, std::initializer_list<Validation::Structure> structures) const -> bool
Validate document.

Function documentation

Magnum::OpenDdl::Document::Document() explicit

Constructor.

Creates empty document.

bool Magnum::OpenDdl::Document::parse(Containers::ArrayView<const char> data, std::initializer_list<CharacterLiteral> structureIdentifiers, std::initializer_list<CharacterLiteral> propertyIdentifiers)

Parse data.

Parameters
data Document data
structureIdentifiers Structure identifiers
propertyIdentifiers Property identifiers
Returns Whether the parsing succeeded

The data are appended to already parsed data. Each identifier from the lists is converted to ID corresponding to its position in the list. If the parsing results in error, detailed info is printed on error output and the document has undefined contents.

After parsing, all references to structure data are valid until next parse call.

Containers::Optional<Structure> Magnum::OpenDdl::Document::findFirstChild() const

Find first top-level structure in the document.

Returns Corrade::Containers::NullOpt if the document is empty.

Structure Magnum::OpenDdl::Document::firstChild() const

First top-level structure in the document.

The document must not be empty.

Implementation::StructureList Magnum::OpenDdl::Document::children() const

Top-level structures.

The returned list can be traversed using common range-based for:

for(Structure s: document.children()) {
    // ...
}

Containers::Optional<Structure> Magnum::OpenDdl::Document::findFirstChildOf(Type type) const

Find first custom top-level structure of given type.

Returns Corrade::Containers::NullOpt if there is no such structure.

Containers::Optional<Structure> Magnum::OpenDdl::Document::findFirstChildOf(Int identifier) const

Find first custom top-level structure of given identifier.

Returns Corrade::Containers::NullOpt if there is no such structure.

Containers::Optional<Structure> Magnum::OpenDdl::Document::findFirstChildOf(std::initializer_list<Int> identifiers) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

Containers::Optional<Structure> Magnum::OpenDdl::Document::findFirstChildOf(Containers::ArrayView<const Int> identifiers) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

Structure Magnum::OpenDdl::Document::firstChildOf(Type type) const

First custom top-level structure of given type.

Expects that there is such structure.

Structure Magnum::OpenDdl::Document::firstChildOf(Int identifier) const

First custom top-level structure of given identifier.

Expects that there is such structure.

template<class ... T>
Implementation::StructureOfList<sizeof...(T)+1> Magnum::OpenDdl::Document::childrenOf(Int identifier, T... identifiers) const

Top-level structures of given identifier.

The returned list can be traversed using common range-based for:

for(Structure s: document.childrenOf(...)) {
    // ...
}

bool Magnum::OpenDdl::Document::validate(Validation::Structures allowedRootStructures, std::initializer_list<Validation::Structure> structures) const

Validate document.

Validates the document according to passed specification. Structures and properties that have unknown names are ignored.

Note that sub-array sizes, reference validity and some other things are not checked, this is just to ensure that the document has expected structure so you can use firstChildOf(), Structure::firstChildOf(), Structure::propertyOf() etc. without additional validation.