Dynamic Structure Documents

A CMarkup object can be used as a "dynamic structure document" to mimic the functionality of a simple C/C++ struct, but without compile-time rigidity. Compile-time checking of struct member names and types is often helpful, but it requires you to declare all of the members you might need even if you are unlikely to need them, and usually modifies a header file which affects other modules. For rapid development it can be handy just to attach and access values when needed, based on a string name.

Comparing struct and XML creation

In the following example, two pieces of information are stored in a structure.

struct CConfig
{
  CString strLab;
  CString strPriority;
}
config;

config.strLab = "Miami";
config.strPriority = "high";

Alternatively you could have a CMarkup object to store the same information.

CMarkup xmlConfig;

xmlConfig.AddElem( "config" );
xmlConfig.IntoElem();
xmlConfig.AddElem( "lab", "Miami" );
xmlConfig.AddElem( "priority", "high" );

Here is the document (indent added for illustration).

<config>
  <lab>Miami</lab>
  <priority>high</priority>
</config>

Now say that there are certain times when it would be convenient to attach certain diagnostic values to that original structure.

struct CDiagnostics
{
  CString strPath;
  CString strAutomation;
  CString strLog;
};

struct CConfig
{
  CString strLab;
  CString strPriority;
  CDiagnostics diagnostics;
}
config;

config.diagnostocs.strPath = "C:\\temp";
config.diagnostocs.strAutomation = "on";
config.diagnostocs.strLog = "a.log";

In the XML case, there would be no need to change the declaration of the CMarkup object; just attach the additional information.

xmlConfig.AddElem( "diagnostics" );
xmlConfig.IntoElem();
xmlConfig.AddElem( "path", "C:\\temp" );
xmlConfig.AddElem( "automation", "on" );
xmlConfig.AddElem( "log", "a.log" );
<config>
  <lab>Miami</lab>
  <priority>high</priority>
  <diagnostics>
    <path>C:\temp</path>
    <automation>on</automation>
    <log>a.log</log>
  </diagnostics>
</config>

You can treat it as a separate subdocument too.

CMarkup xmlDiagnostics;
xmlDiagnostics.AddElem( "diagnostics" );
xmlDiagnostics.IntoElem();
xmlDiagnostics.AddElem( "path", "C:\\temp" );
xmlDiagnostics.AddElem( "automation", "on" );
xmlDiagnostics.AddElem( "log", "a.log" );

// Now add it to the config document
xmlConfig.AddSubDoc( xmlDiagnostics.GetDoc() );

Retrieving from struct and XML

In the developer version of CMarkup there are convenient methods for getting and setting values in a dynamic structure document.

CMarkup Developer License

The FindSetData and FindGetData methods which provide 1-stop set/get methods for the dynamic structure functionality of CMarkup are only in CMarkup Developer and the free XML editor  FOAL C++ scripting.

The FindGetData method makes retrieving values very simple.

CString strLab = xmlConfig.FindGetData( "/config/lab" );
CString strPath = xmlConfig.FindGetData( "/*/diagnostics/path" );

Retrieving from the struct is comparable.

CString strLab = config.strLab;
CString strPath = config.diagnostics.strPath;

Using the XML without FindGetData it takes a little more code, but it is still not difficult.

xmlConfig.ResetPos();
xmlConfig.FindChildElem( "lab" );
xmlConfig.IntoElem();
CString strLab = xmlConfig.GetData();
xmlConfig.FindElem( "diagnostics" );
xmlConfig.IntoElem();
xmlConfig.FindElem( "path" );
CString strPath = xmlConfig.GetData();

FindSetData creates elements if necessary

As of CMarkup release 9.0, the FindSetData method has been enhanced to complete the convenient dynamic structure functionality of CMarkup. This allows you to set values in the document structure without first creating the element. For example, if you call FindSetData("/config/lab","Miami") on an empty CMarkup object it will create the config and lab elements, and set the value to "Miami".

<config>
  <lab>Miami</lab>
</config>
xmlConfig.FindSetData( "/config/diagnostics/path", "C:\\temp" );
<config>
  <lab>Miami</lab>
  <diagnostics>
    <path>C:\temp</path>
  </diagnostics>
</config>

Get and set attributes too

Update June 7, 2009: In Release 11.1 FindGetData and FindSetData provide access to attributes too.

xmlConfig.FindSetData( "/config/diagnostics/@modified", "20080603" );
xmlConfig.FindGetData( "/config/lab/@modified" ); // returns "20080603
<config>
  <lab modified="20090602">Miami</lab>
  <diagnostics modified="20080603">
    <path>C:\temp</path>
  </diagnostics>
</config>

Dispensing with the root element

The XML standard calls for a single root element, but when using a CMarkup object within a program you have the option of using multiple elements at the top level (see Generic Markup In CMarkup). First look at what the document looks like without the root element.

<lab>Miami</lab>
<priority>high</priority>
<diagnostics>
  <path>C:\temp</path>
  <automation>on</automation>
  <log>a.log</log>
</diagnostics>

This makes access simpler and once you're accustomed to it, you will wonder why you ever used a root element.

CMarkup xmlConfig;

xmlConfig.FindSetData( "/lab", "Miami" );
xmlConfig.FindSetData( "/priority", "high" );
<lab>Miami</lab>
<priority>high</priority>
CMarkup xmlDiagnostics;
xmlDiagnostics.FindSetData( "/path", "C:\\temp" );
xmlDiagnostics.FindSetData( "/automation", "on" );
xmlDiagnostics.FindSetData( "/log", "a.log" );
<path>C:\temp</path>
<automation>on</automation>
<log>a.log</log>

The following code demonstrates how to put the rootless diagnostics information into the other document as the content of the diagnostics element.

xmlConfig.AddElem( "diagnostics" );
xmlConfig.SetElemContent( xmlDiagnostics.GetDoc() );