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.
Notes
  • More discussion on this matter can be found in "xml.h" in the source repository.

Overview

C++ API (1199)
class XMLNode { // Constructors (create new underlying XML element) XMLNode(Symbol hComponent, const char* name) XMLNode(Symbol hComponent, const char* name, const char* text) // Constructors (associate with existing XML element) XMLNode(Symbol hComponent, Symbol xmlElement) // W3C "Node" interface const char* nodeName(); void nodeName(const char* tagName); XMLNode* appendChild(XMLNode* newChild); vector<XMLNode> childNodes(); // W3C "Element" interface const char* getAttribute(const char* name); const char* getAttributeOrNull(const char* name); void setAttribute(const char* name, const char* value); // extensions to the W3C interfaces XMLNode* getChild(const char* name, UINT32 index = 0); XMLNode* getChildOrNull(const char* name, UINT32 index = 0); void clear(); // implementation-specific interface const char* nodeText(); void nodeText(const char* text); // handle to underlying node Symbol element; };
Notes
  • The XMLNode interface gives you unconstrained access to read and write XML. However, all existing BRAHMS processes use the more constrained, but less laborious, DataMLNode interface as an overlay on XMLNode. You should seriously consider if the DataMLNode interface meets your needs before attempting to use XMLNode.

XMLNode is 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 an XMLNode, 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

Notes
  • Much of the interface is not thoroughly documented here, since it is simply an implementation of the W3C standard. You can find documentation for that here.

Create interface, and physical node

Use these constructors to create new XML nodes for return to the framework, whilst servicing EVENT_STATE_GET or EVENT_LOG_TERM. Create one root node, then create new nodes and add them as children to the root node, then return the root node to the framework when you are finished. If you create nodes and do not return them to the framework (either directly, or as children of a node you do return), they will remain resident and unused throughout the execution and will be destroyed when BRAHMS exits. This is harmless, but uses memory unnecessarily.

Notes
  • All XMLNode constructors must be passed hComponent, which is a member variable of the Component Class. This allows DataMLNode access to the framework if necessary (the framework can only, in general, be called with a handle to a component).
XMLNode(Symbol hComponent, const char* name)
Create a node with specified tag name and empty node text. name can be NULL, in which case the node is not named.
XMLNode(Symbol hComponent, const char* name, const char* text)
Create a node with specified tag name and node text.

Create interface and associate with existing physical node

Use this constructor to access an existing XML node (or tree) passed to you by the framework, whilst servicing EVENT_STATE_SET, for instance.

XMLNode(Symbol hComponent, Symbol xmlElement)
Create a node from a BRAHMS XML element.

Read or write existing nodes

const char* nodeName()
Return the tag name of the node.
void nodeName(const char* tagName)
Set the tag name of the node.
XMLNode* appendChild(XMLNode* newChild)
Append the passed child node to the node (returns newChild).
vector<XMLNode> childNodes()
Return all child nodes of the node.
const char* getAttribute(const char* name)
Return the value of the named attribute. Throw E_NOT_FOUND if this attribute is not present.
const char* getAttributeOrNull(const char* name)
As above, but returns NULL if attribute is not present, rather than raising an exception.
XMLNode* getChild(const char* nodeName, UINT32 index = 0)
Return the specified child node, being the indexth child node with the specified name. Throw E_NOT_FOUND if this node is not present.
XMLNode* getChildOrNull(const char* nodeName, UINT32 index = 0)
As above, but returns NULL if child is not present, rather than raising an exception.
void clear()
Clear all attributes and child nodes of the node.
const char* nodeText()
Return the text of the node.
void nodeText(const char* text)
Set the text of the node.

Example

Read

Here, we use the interface to read parameters from our state node during EVENT_STATE_SET.

C++ API (1199)
case EVENT_STATE_SET: { // create interface to the StateML node we were passed EventStateSet* data = (EventStateSet*) event->data; XMLNode nodeState(hComponent, data->state); // use accessors to read the state myparameter = nodeState->getChild("myparameter")->nodeText(); // ok to return: nothing new has been created and // the interface "nodeState" will do nothing when // it goes out of scope return C_OK; }

Write

Here, we use the interface to write our log during EVENT_LOG_TERM.

C++ API (1199)
case EVENT_LOG_TERM: { // create a new node for return to the framework XMLNode nodeLog(hComponent, "Log"); // add some stuff to it XMLNode nodeResult(hComponent, "E", "MC^2"); nodeLog.appendChild(&nodeResult); // return root node to the framework EventLog* data = (EventLog*) event->data; data->result = nodeLog.element; // ok to return: both new nodes we created are // going back to the framework, one as the child // of the other return C_OK; }

Abuse

This example below shows how to misuse the interface, not how to use it. Since XMLNode 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 node 1 XMLNode node1(hComponent, "Node1"); // create node 2 XMLNode node2(hComponent, "Node2"); // make node 2 a child of node 1 node1.appendChild(&node2); // and then clear node 1 (deleting physical node 2) node1.clear(); // now access node 2 through the interface we still have bout << node2.nodeText() << D_INFO;

The result is as follows.

Matlab Console
E_INVALID_HANDLE: invalid object handle/index (0x2000005c, range was 92) whilst firing EVENT_STATE_SET on process ________________________________________________________________ ??? Error using ==> brahms at 270 One or more errors occurred during execution

Limitations

This interface implementation is closer to Minimal XML than full-blown XML (XML is overspecified for most tasks).

  • Only one node type is supported, "Element". The other elements are not used, as follows.
    • Text and Attr are represented as properties of the Element node rather than child nodes.
    • Document and DocumentFragment are not really needed in this context (since the document is represented as an object in C++, the runtime environment can perform Document tasks like creating nodes).
    • Comments are handled correctly during parsing but are discarded, and cannot be added to the document for output.
    • CDATASections are not currently handled, though I think they could be parsed correctly in future by a simple improvement to the parser.
    • The other complex node types (Notation, ProcessingInstruction) I am just assuming are too much for our purposes.
  • Mixed content (text and children on the same node) is not supported. This is partly because it's impossible to handle general cases of mixed content correctly once you've made the decision to represent text as a property of Element, and partly because I'm not alone in believing that you can manage just fine without it and that documents are more intuitive and software less complex without it.

Proposed Additions (not yet implemented)

C++ API (1199)
class XMLNode { // W3C "Node" interface XMLNode* parentNode(); XMLNode* firstChild(); XMLNode* lastChild(); XMLNode* previousSibling(); XMLNode* nextSibling(); XMLNode* insertBefore(XMLNode* newChild, XMLNode* refChild = NULL); XMLNode* replaceChild(XMLNode* newChild, XMLNode* oldChild); void removeChild(XMLNode* oldChild); bool hasChildNodes(); XMLNode* cloneNode(bool deep); bool hasAttributes(); bool isSameNode(const XMLNode* node); UINT32 getUserData(); void setUserData(UINT32 data); // W3C "Element" interface void removeAttribute(const char* name); XMLNodeList getElementsByTagName(const char* tagName); bool hasAttribute(const char* name); // extensions to the W3C interfaces bool hasChild(const char* tagName); void removeAttributes(); void removeChildren(); };

Implementation Notes

The following notes were originally taken from brahms-1065.h.

Text File
We implement some parts of the W3C DOM specification exactly and some parts inexactly. Those that are represented exactly are marked "W3C". http://www.w3.org/TR/DOM-Level-3-Core/core.html The W3C DOM is complex, having 12 different node types to represent the full gamut of XML. Here, we support only Minimal XML, and we demote attributes and textual content from node status. Thus, our XMLNode's are all of the same type: "element". Our interface, thus, is a merging of the W3C "Node" and "Element" interfaces, with some inappropriate functions left out. In contrast to the W3C DOM, we do not represent text content of a tag as a separate sub-node or set of nodes. Rather, we use the simpler model that each tag has a text string and a set of child nodes that are elements. Those functions that return a pointer to an object return a pointer to an object that still belongs to the called node, not a copy. The returned object, thus, should not be deleted. To take charge of a node, copy it and then call removeChild() on its parent node.