Objective Grid : PART II Programmer’s Guide : Chapter 23 XML Read/Write Library : Implementation
Implementation
Please refer to the samples xml_grid and xml_samp, available from the Rogue Wave Web site, as explained in “Location of Sample Code” in the Getting Started part.
The XML map which is the parsed version of the XML schema/DTD is represented using the following data structure:
 
class XML_ENTRY_MAP
{
...
/* The name of the node.*/
LPCTSTR pszName; // 1
/* Unique id assigned to this node. When an element appears in more
than one part of a document, each occurence should usually have
a different relative id.*/
ENTRYID dwID; // 2
/* The type of the node. Please refer to DOMNode type in the MSDN
documentation.*/
DOMNodeType entryType; // 3
/*Qualifying namespace if any.*/
LPCTSTR pszNameSpace; // 4
 
/*List that holds nested nodes.*/
CXMLList listXMLMapEntries_p; // 5
};
This structure has all the essential elements that are needed to represent a node in the XML document. These are:
//1 The node name (the pszName member): This is the name of the XML node.
//2 The unique node ID (the dwID member): This is an ID that is not known to the XML file as such. It is only used internally by us when writing and reading the file. This node id has to be unique even when an element appears in more than one part of a document. This id enables us to tie together that node with a piece of data when processing.
//3 The DOMNodeType (the entryType member): This member defines the type of node. Please refer to the MSDN documentation on DOMNodeType for further information.
//4 Qualifying namespace, if any (the pszNameSpace member): This member specifies the qualifying namespace. This is optional.
//5 XML_ENTRY_MAP list for nested nodes (the listXMLMapEntries_p member): This member holds all child nodes for this node. Each of the child nodes is in turn described by an XML_ENTRY_MAP class, thus allowing us to represent an XML tree of any depth and complexity.
When the processing system reads this data structure, it can loop through the nodes and, depending on whether an XML read or write operation is taking place, call feedback interfaces. The purpose of these feedback interfaces varies between the read and write operation. Let us look at IXMLRResolver, the read feedback interface, first.
The IXMLRResolver interface is the primary interface that is used by the XML read code that processes XML_ENTRY_MAP structures when reading XML data streams.
Given below is the interface specification for this interface.
 
class IXMLRResolver
{
...
 
//@cmember
/* Will be called when the system starts to read data from the
XML data stream.*/
virtual void OnStartRead() = 0;
 
//@cmember
/* Will be called when the system completes reading from the
stream*/
virtual void OnCompleteRead() = 0;
 
//@cmember
/* Will be called when read is started for a particular
node type. */
virtual bool OnStartReadLoop(const XML_ENTRY_MAP* pEntry) = 0;
 
//@cmember
/* Will be called to give the value of a node (as the nodes are
read out) */
virtual bool OnSetValue(const XML_ENTRY_MAP* pEntry,
MSXML::IXMLDOMNode* pThisNode, XMLVALUE& xmlValue) = 0;
};
When read() starts, the system calls OnStartRead(), and when read ends, the system calls OnCompleteRead(). This is pretty straightforward. You can perform any one-time initialization (and cleanup) when these calls are made. You usually do not have to implement these and the other IXMLRResolver methods, as they are implemented by macros which we shall see later.
OnStartReadLoop() requires a little explanation. This is called the first time a node with a particular ID is read. For example, consider the following XML snippet:
 
<address>
<street>Pine drive</street>
<apt>432</apt>
</address>
<address>
<street>Oak drive</street>
<apt>32</apt>
</address>
When this is read, if there is an handler for street (identified to the system by the unique id, say, idStreet), then OnStartReadLoop() will be called when this node is hit for the first time. This is useful if you have to perform any initialization to be able to read this data. When the actual element is read, OnSetValue() is called. This has parameters (the XML_ENTRY_MAP value and the node pointer) that identify the node in question. The value itself is stored in an XML_VALUE parameter. This can be used to initialize your data structures.
The write feedback interface is very similar.
 
class IXMLWResolver
{
...
// write feedback interface
// @access public
/* Will be called when write is started */
virtual void OnStartWrite() = 0;
/* Will be called when write is completed */
virtual void OnCompleteWrite() = 0;
/* Will be called when a value needs to be supplied (just before
the write operation for a node takes place) */
virtual bool OnGetValue(const XML_ENTRY_MAP* pEntry,
MSXML::IXMLDOMNode*
pParentNode, XMLVALUE& xmlValue) = 0;
/* Will be called when a node is to be written out for
the first time. */
virtual bool OnStartWriteLoop(const XML_ENTRY_MAP* pEntry) = 0;
 
// will be called when to check whether write needs to
// be continued on a node type
virtual bool OnCanContinueWriteLoop(const XML_ENTRY_MAP* pEntry,
MSXML::IXMLDOMNode* pNodeAdded) = 0;
// will be called when writing is stopped for a
// particular node type
virtual bool OnEndWriteLoop(const XML_ENTRY_MAP* pEntry) = 0;
};
There are some differences from IXMLWResolver. OnGetValue() will be called to retrieve the value for the node that is being written. OnStartWriteLoop() will be called when the node is written for the first time (same as when the node is read for the first time). However, if this function returns true, it will be repeatedly called.
In addition, OnEndWriteLoop() will be called when the write process is complete for a loop. Also OnCanContinueLoop() will be called to check whether there is additional data to be written out. You can use this to control how many node instances get written out for a given ID. For example if you have a linked list of addresses, you could return false from this function when you have reached the end of the linked list in order to write the whole list.
Populating these structures can be automated in two ways:
Using macros
Using tools that can generate code based on input parameters
We have followed both these approaches in order to make development with the Stingray XML library easier.