Warnings
  • XML handles and interfaces returned by any XML functions (the XML Interface, XMLNode, or DataMLNode) are only valid until control is returned to the framework (i.e. within the context of a single event fired on a Component). You should discard all XML handles and interfaces when you return control at the end of an event.
  • Strings returned by the XML Interface are only valid until control is returned to the framework OR until another XML Interface call is made, whichever is sooner. You should dereference and copy any such string immediately, and then not dereference it again.

Overview

C++ API (1199)
class DataMLNode;

The DataMLNode is an interface to an XMLNode that implements DataML read and write on that node. Since XMLNode is an interface to an underlying physical node, DataMLNode can be seen as just an alternative interface that is more constrained but much easier to use for tasks that are typically performed by a BRAHMS process.

DataML is the XML format used to communicate (State and Log) with all processes supplied with BRAHMS, and with all processes authored by users, at time of writing. Whilst BRAHMS offers full support for the use of any XML format you might choose (to specify your process State and any Log you might return), if you do use a non-DataML format you will need to perform all your own accesses within process (using XMLNode) and you will need to write codecs for the 995 bindings if you wish to use them with your process. Therefore, you should seriously consider using DataML to communicate with your process before deciding otherwise.

The DataMLNode interface will only read and write DataML nodes (XML nodes with particular attributes and content). Attempting to associate it with any other XML node will raise E_DATAML. The interface, also, can only modify the underlying XML node in ways that leave it as a valid DataML node. Therefore, the XML node underlying DataMLNode is always a DataML node.

Like XMLNode, DataMLNode is (through XMLNode) an interface to physical XML nodes that are maintained in trees by the framework. The interface implements accessors of that node in a convenient object-oriented form. If you delete a DataMLNode, or it goes out of scope, the underlying tree is unchanged - you just lose your access to the underlying physical node. If you use the interface to create nodes and do not return these to the framework, they will be cleaned up (harmlessly) at termination (but obviously this is not desirable, since it uses memory).

Interface

Create interface and associate with existing physical node

DataMLNode()
Create a new DataMLNode that does not interface an XML node. Any call on the interface other than to assign it to an XML node will raise an error. Assignment can be to an existing DataMLNode, or by assigning to an XML node using operator= (below).
DataMLNode(XMLNode& xmlNode)
Create a new interface to the specified XML node (which must be a DataML node, otherwise E_DATAML is raised). Note that a brand new (empty) XML node is a valid DataML node (it describes an empty string), so it is possible to get an interface to an empty XML node for later modification of that node.
DataMLNode& operator=(DataMLNode& dataMLNode)
Let the DataMLNode interface the same XML node as the passed DataMLNode. The two interfaces are identical (and symmetrical), following this call.
DataMLNode& operator=(XMLNode& xmlNode)
Let the DataMLNode interface the passed XML node. Restrictions are as for the constructor, above.

Miscellaneous

void setRootTags()
Set the "Root Tags" on the DataMLNode. The Root Tags are the tags "Format" and "Version", which allow a general XML parser to identify the node as the root node of a DataML document. Call this function just before returning a new DataML document to the framework (see example, below).

Structure Accessors

DataML nodes represent array objects; therefore, they have a structure much like a Matlab array.

TYPE getType() const
Return the node's Numeric Type Constant (including non-numeric extensions).
TYPE getElementType() const
Return the node's element type (see Numeric Type Constant).
bool getComplexity() const
Return true if the node is complex.
Dims getDims() const
Return the node's dimensions.
UINT32 getBytesPerElement() const
Return the number of bytes per real element.
UINT32 getNumberOfElementsReal() const
Return the number of real elements.
UINT32 getNumberOfElementsTotal() const
Return the number of real and imaginary elements (just the real count, if the array is not complex).
UINT32 getNumberOfBytesReal() const
Return the number of bytes used to store the real data.
UINT32 getNumberOfBytesTotal() const
Return the number of bytes used to store the real and imaginary data (just the real count, if the array is not complex).

Structure Validation

const DataMLNode& validate(const Dims& dims) const
Validate that the dimensions of the node are as specified (else raise E_DATAML).
const DataMLNode& validate(TYPE type) const
Validate that the type of the node is as specified (else raise E_DATAML).
const DataMLNode& validate(TYPE type, const Dims& dims) const
Validate both at once.

Accessing "structure" type nodes

bool isStruct() const
Return true if the node is TYPE_STRUCT.
VSTRING getFieldNames() const
Return a list of field names (raises E_DATAML if the node is not TYPE_STRUCT).
bool hasField(const char* nodeName) const
Returns true if the node has the specified field (raises E_DATAML if the node is not TYPE_STRUCT).
DataMLNode getField(const char* nodeName, UINT32 index = 0) const
Returns the specified field (raises E_DATAML if the node is not TYPE_STRUCT, or if the field is not found).
DataMLNode addField(const char* tagName)
Adds and returns the specified field (raises E_DATAML if the node is not TYPE_STRUCT, or if the field already exists). Currently, also raises E_DATAML if the node is not scalar, since I've not been bothered to code this more general case yet.
void becomeStruct(const Dims& dims, VSTRING& fieldNames)
Change the underlying node into a structure node - this will clear any existing data on the underlying node, including deleting any descendants it might have.

Accessing "cell" type nodes

bool isCellArray() const
Return true if the node is TYPE_CELL.
DataMLNode getCell(UINT32 index) const
Return the contents of the indexth cell(raises E_DATAML if the node is not TYPE_CELL, or the index is out of range).
void setCell(UINT32 index, DataMLNode node)
Set the contents of the indexth cell (raises E_DATAML if the node is not TYPE_CELL, or the index is out of range).
void becomeCell(const Dims& dims)
Change the underlying node into a cell node - this will clear any existing data on the underlying node, including deleting any descendants it might have.

Accessing "string" type nodes

std::string getSTRING() const
Return the node's value as a string (raises E_DATAML if the node is not a 1xN or empty string).
void setSTRING(const char* text)
Set the node's value to be a string (i.e. make the node become a string node).

Accessing "numeric" type nodes (real scalar interface)

These functions raise E_DATAML if the node is not real, scalar, and of the expected element type.

DOUBLE getDOUBLE() const
UINT32 getUINT32() const
INT32 getINT32() const
bool getBOOL() const
Return the value of the single element of the node.

Accessing "numeric" type nodes (small array interface)

This interface is convenient for accessing small numeric arrays, but involves a copy operation. Use the large array interface (or fill()) for larger arrays, to avoid the copy operation.

In strict mode (when F_STRICT is set), this interface will raise E_DATAML if the data type is not as expected. In non-strict mode, this interface will attempt to convert between data types and raise E_DATAML only if the data is non-compliant (cannot be converted without loss of information). Thus, a user can specify a parameter as a DOUBLE (the default data type in Matlab), and have it accepted as a UINT32, provided it is an integer between 0 and 2^32-1.

In real mode (when F_REAL is set), this interface will raise E_DATAML if the data is not real. Thus, if you intend to accept both real and complex data, you should unset F_REAL before use.

The number of elements in the returned vector is equal to the value returned by getNumberOfElementsTotal().

VX getArrayX() const
Return the node's contents as a vector, where X is any of DOUBLE, SINGLE, UINT64, UINT32, UINT16, UINT8, INT64, INT32, INT16, INT8.
Vbool getArrayBOOL() const
Return the node's contents as a vector of "bool" type.
Dims getArrayDims() const
Return the node's contents as a Dims object (this is equivalent, apart from return type, to a call to getArrayINT64(), but provides the data in a form that is appropriate if they represent a set of dimensions).

Accessing "numeric" type nodes (large array interface)

This interface is identical in operation to the small array interface, but avoids the copy operation. The return value is equal to the argument.

VX& getArray(VX& dst) const
Return the node's contents as a vector.
Vbool& getArray(Vbool& dst) const
Return the node's contents as a vector.
Dims& getArray(Dims& dst) const
Return the node's contents as a Dims object.

Accessing "numeric" type nodes (set interface)

void setArray(const Dims& dims, const VX& src)
Set the node's real and imaginary data from the passed vector, where X is any of DOUBLE, SINGLE, UINT64, UINT32, UINT16, UINT8, INT64, INT32, INT16, INT8, bool. src must contain either dims.getNumberOfElements() elements, or twice that number, from which the complexity is inferred (else E_INVALID_ARG is raised).
void setArray(const Dims& dims, const Dims& src)
As above, but the data are provided in a Dims object (dims must indicate that the data is one-dimensional, and the single dimension, N, must be equal to the number of elements in src - complex data cannot be represented).

Accessing "numeric" type nodes (raw interface)

void getRaw(BYTE* p_real, BYTE* p_imag = NULL) const
Fill the passed memory blocks with the node's real and imaginary data. The number of bytes that will be written to each memory block is equal to the return value of getNumberOfElementsReal(). p_imag must be NULL if the data is real, and non-NULL if the data is complex (else E_DATAML is raised).
void setRaw(const Dims& dims, TYPE type, const BYTE* p_real, const BYTE* p_imag = NULL)
Set the node's real and, if complex, imaginary data from the passed memory blocks. If type indicates TYPE_REAL, N bytes are read from p_real, and p_imag must be NULL. If type indicates TYPE_COMPLEX, N bytes are read from each pointer, unless p_imag is NULL, in which case Nx2 bytes are read from p_real. If type indicates TYPE_COMPLEX_INTERLEAVED, Nx2 bytes are read from p_real, and p_imag must be NULL. N is equal to dims.getNumberOfElements() * TYPE_BYTES(type).

Accessing "numeric" type nodes (binary set interface)

If the caller has already written the node data into a binary file (during execution, for instance), it can set the node to be an unencapsulated node by passing the name of the binary file.

void setBinaryFile(const Dims& dims, TYPE type, const char* filename)
Set the unencapsulated binary file of the node. The size of the binary file should match the passed dims and type, if the node is to be readable, but is not checked by the function.

Mode Changes

DataMLNode& set(UINT16 flag), DataMLNode& unset(UINT16 flag)
Set or unset (clear) the specified node flag. Returns a reference to the node. Flags are listed below.
DataMLNode& precision(INT16 precision)
Set the precision at which the array should write data during serialization, if requested. precision must be between 0 and 31 (inclusive), or PRECISION_NOT_SET.

Flags

F_STRICT
(initially UNSET) If set, the node is in "strict" mode, and compliant but incorrect numeric data will not be passed during calls that perform auto-validation.
F_REAL
(initially SET) If set, complex data will be rejected on a call to the real scalar interface or the small or large array interfaces. Thus, to accept real and complex data, you must unset this flag (to accept only complex data, you should use node.unset(F_REAL).validate(TYPE_COMPLEX).get(...)).

Example

Read

The typical way to use DataMLNode to read a tree is by laying it over an XMLNode, and using this newly created interface to read from the XML, as follows.

C++ Source Code (against 1199)
case EVENT_STATE_SET: { // access passed XML node EventStateSet* data = (EventStateSet*) event->data; XMLNode xmlNode(hComponent, data->state); // DataMLNode interface DataMLNode nodeState(xmlNode); // access node through interface string fieldValue = nodeState.getField("fieldName").getSTRING(); DataMLNode nodeSubTree = nodeState.getField("subTree"); VDOUBLE v = nodeSubTree.getField("fieldName").getArrayDOUBLE(); // ok return C_OK; }

Write

The typical way to write a tree is by starting an XMLNode tree (which implicitly creates a BRAHMS XML node), wrapping that in a DataMLNode interface, and writing through the interface, as follows.

C++ Source Code (against 1199)
case EVENT_LOG_TERM: { // create new XML node and DataML interface XMLNode xmlNode(hComponent, "Log"); DataMLNode nodeLog(xmlNode); // create some fields VSTRING fieldNames; fieldNames.push_back("abc"); fieldNames.push_back("def"); fieldNames.push_back("ghi"); nodeLog.becomeStruct(Dims(1), fieldNames); // set two of them to strings nodeLog.getField("abc").setSTRING("ABC"); nodeLog.getField("def").setSTRING("DEF"); // make the other one a cell DataMLNode nodeCells = nodeLog.getField("ghi"); nodeCells.becomeCell(Dims(2, 3)); DataMLNode nodeCell = nodeCells.getCell(0); nodeCell.setSTRING("the first cell"); // add some numeric data nodeCell = nodeCells.getCell(5); VDOUBLE v; v.push_back(1.23456789); v.push_back(13); v.push_back(42.592); nodeCell.precision(3); nodeCell.setArray(Dims(3), v); // set DataML tags into Log nodeLog.setRootTags(); // return log to framework EventLog* data = (EventLog*) event->data; data->result = xmlNode.element; // ok return C_OK; }

Abuse

This example below shows how to misuse the interface, not how to use it. Since DataMLNode is only an interface to physical XML nodes, it is possible to use an interface to a node that no longer exists. This won't typically happen because you are either creating nodes to return to the framework (i.e. not deleting them), or you are reading nodes that you were passed (i.e. not deleting them). However, just to make the point, here's how you make things go wrong.

C++ API (1199)
// create root node (and XMLNode interface) XMLNode rootNode(hComponent, (const char*) NULL); // create node 1 (DataMLNode interface to root node) DataMLNode node1(rootNode); // make node 1 a cell array node node1.becomeCell(Dims(3)); // access node 2, one of the cells DataMLNode node2 = node1.getCell(1); // now change node 1 to something else, so // it and its cell children get cleared node1.setArray(Dims(2), VDOUBLE(2, 42)); // now try to modify node 2 through the // interface we still have - this will // try to clear() node 2, first of all node2.becomeCell(Dims(3));

The result is as follows.

Matlab Console
E_INVALID_HANDLE: invalid object handle/index (0x20000076, range was 117) at register.cpp:161 in brahms::XMLNode::clear() at brahms-1199.h:784