Project info

What is CML?

CML is meant to be a replacement for XML and thus allows you to do pretty much the same as you would have done with XML, only more. CML allows you to turn your key-value pairs into typed data and thus you can use the CML file directly in your project as opposed to having to read and parse the file into custom prepared data structures.

What are the features of CML?

  • Has a normal and simplified syntax you can swap between at any point
  • Easier to read / write/ understand
  • All data stored under categories
  • Very forgiving syntax
  • Nodes don’t have to contain the same fields. Only add what you need when you need it
  • Load and save your data with a single line of code, respectively
  • Runtime typed data creation
  • Non-existing values return the default values for each type instead of throwing exceptions
  • Each node can be a parent for child nodes thus you can have nested nodes as deep as you want
  • Find nodes by:
    • Direct access via index
    • First node of type (optionally with offset)
    • First node with specific field
    • First node with specific field with specific value
    • Children of a specific node
  • Each node can have unlimited key-value pairs
  • Since each node can be a parent/header node it means you can have different sections inside a single file, thus consolidating multiple files into one
  • Available natively for Unity, Unreal Engine, .NET and php

Example CML file

<libraries>
  <library>name=Uptown;Contact number=555 2552
    <section>name=History
        <book>name=American
        <book>name=English
        <book>name=African
    <section>name=Fiction
        <book>name=Space Adventures 2099
    </section>
    <section>name=Manga
        <book>name=Akira
        <book>name=Tetsuo

  //this library entry has extra fields
  <library>name=Downtown;demolition date=2029/10/07;Contact=Bill Murray; Contact number = 555 1525
    <section>name=Science
        <book>name=Apollo 13
    <section>name=newspapers; type=subsection
        <newspaper>name=Times

            //This node uses the normal syntax in stead of simplified syntax
            <paper>date=2021/10/10
               [headline]Cubs win the world series
               [number of pages]86
               [copies sold]25625
            </paper>
            <paper>date=2021/10/11
               [headline]Oops, our mistake
               [number of pages]45
               [copies sold]89940
               We would like to apologize for that misinformation
               printed in yesterday's paper.
        <newspaper>name=Daily
            <paper>date=2021/10/10
               [headline]How to never lose at gambling
               [copies sold]199991
               [barcode]1251125425
               The process of never losing at gambling is really quite simple
               Don't gamble !

Example explained

Notice how there is no consistency in the above example and yet this is perfectly valid.

  • The first library uses exclusively simplified syntax while the second library mixes normal and simplified syntax
  • The second library lists more information about itself. Not all matching nodes need have the same fields
  • Only the second library has a subsection of newspapers
  • Multiple data types in one file with different fields per type and per entry
  • Trailing and preceding whitespace is automatically trimmed in regular and simplified syntax
  • Closing tags are entirely optional
  • Some paper nodes have additional data entries no other node has
  • Comments are supported in CML

How to use this example

Reading

All nodes are indexed sequentially starting from 0. By calling the Children() function and passing index 0 you will receive all children of node 0, which is “libraries“. The first child is library and thus it will return all library nodes.

Now that you have a library node (in this case the first node has an ID of 1) you can call the Children() function again, this time with the library‘s ID to return all of the library’s immediate children. The first node it will find is section and thus it will return all section nodes until it finds the next library node. In this case it will return 2 section nodes.

You could follow the same logic to find the newspapers if you knew in advance which nodes would follow which but since not all library sections have a section called newspaper here you have 2 options.

  • call AllNodesOfType("paper") to fetch all paper nodes in the entire file
  • call NodesWithField("type","subsection") to find all nodes that has a field called “type” that has a value of “subsection”. Alternatively, since the newspapers node is the only one to contain the field “type” you could omit the value while still receiving the same result
Writing

Each node consists of 2 separate sets of data. The first and most common is the key-value pairs where the values can be turned into typed data at runtime. The second is a list of strings that are added in sequence, fetched sequentially and used as strings only.

The former can be set by calling the Set or SetX function based on the type of data you want to set (where X is the data type).
In Unity, for example, this would be Seti() for integers and Set() for almost any other type.
In the Unreal Engine, this would be Set() for FStrings and SetBoolean, SetRotator, SetVector etc respectively for each respective type

Strings are added to the string list named Data using the function AddData().

Advanced reading

What is important to note is that all data is saved as strings and thus anything you can turn into a string can be saved to a CML file. This includes base64 encoded binary data like graphics or mp3 files. When the data is fetched you can specify the type you want the value to be returned as.
If the value cannot be returned as that type it will return the default value for that type. 

While it is not important for a field to actually exist when you try and use it, it is important for you to know the type of data a field should contain or else you may end up with unexpected results !
<mydata>
[age] 18
[can_vote] true
[name] Stormchaser
[url]aHR0cHM6Ly9teWJhZHN0dWRpb3MuY29t
[novalue]
Field
Type
Returns…

age float 18.0
age int 18
age boolean true *1
can_vote boolean true *1
can_vote int 0
can_vote string true
can_vote Vector3(Unity) Vector3.zero
name string Stormchaser
name boolean false *1
name Rect(Unity) Rect(0,0,0,0)
url string aHR0cHM6Ly9teWJhZHN0dWRpb3MuY29t
url Rotator(UE) (0,0,0)
url Vector(UE) (0,0,0)
url boolean false
novalue float 0.0f
novalue boolean false
novalue Quaternion(Unity) Quaternion.identity
novalue string(Unity) string.Empty
*1 Values larger than 0 and “true” return true. Anything else returns false *2 “true” is not numeric