XMLKey
    Classes for persisting application state in XML files, modeled on ATL::CRegKey
     I was happy persisting my program state to the registry. I've found CRegKey to be
        very useful for saving application settings
        such as window position and MRU lists.
         One thing that was especially nice about it is that I could access my settings
        randomly. But now I want to save larger amounts of data,
        so I think a file will be better. I'm thinking an XML file will be nice, because I'll be able to read it
        to verify that my stuff is saved correctly, and I can modify it manually if I want.
        I don't want to write a parser though, so I'll take advantage
        of MSXML.
     
     I investigated some alternatives, but none did what I wanted:
     
    
        - MS IsolatedStorage is only .NET, not COM
 
            (If anyone knows how to get at IsolatedStorage in unmanaged C++ I'd love to use
            it because I like how it creates a hidden place to store stuff) 
        - ATL has persistence mix-ins for COM objects, but they're not easily adapted for
            application settings. There's no implementation from the
            app side. 
 
        - John Torjo's Straightforward Settings
            Library (from Dr Dobbs) is nice, but it's global, not hierarchical and
            not XML. 
 
        - VB Settings are managed again.
 
        - ATLPersistXML
            persists into properties from streams or files, but uses SAX and is sequential.
 
        - 
            XMLLite is sequential too.
 
     
     I guess I want the XML exercise, so I'm making CXMLKey to look like ATL's CRegKey.
     
     Hopefully you're looking for XML persistence and these classes will save you a couple of days work!
     
    Implementation
     XMLKey is modelled on ATL's CRegKey. XMLKey only does the basic store and query
        stuff, not enumerating, multistring, binary, security or Flush.
        Only the newer CRegKey signatures are implemented (ATLVER >= 0x0700); QueryValue and SetValue are not. 
     XMLKey consists of three classes: CXMLNode, CXMLDoc and CXMLKey. CXMLNode is a base
        class for the other two classes. It contains a parent property for linking CXMLDoc
        and CXMLKey objects in a tree. CXMLKey is the CRegKey replacement. It represents
        a node in the XML element tree that can contain child nodes or values. CXMLDoc doesn't
        really have an analog in the registry paradigm - it represents the file that the
        XML lives in. It replaces the root keys used by CRegKey, like HKCU and HKLM. 
     A CXMLKey object cannot save or read values until it's linked into the document
        tree. CXMLKeys are linked into a tree by the Create or Open methods. The Create
        method connects itself up to the XMLKey specified in the parent property and instantiates
        a new node in the XML document. It is to be used when creating an XML file. 
            Open
        is to be used when reading from existing files. It links the key to a parent and
        to a node in the document, but does not create a node. It fails if there is no matching
        node in the file. The nodeName parameter of these methods is the tag text used in
        the XML file. Just like the linkage of CRegKey objects reflects the tree structure
        of the registry keys they represent, the linkage of the XMLKey objects is the same
        as the tree structure of the XML file. I'd have preferred that the CRegKey object
        methods created or opened children instead of linking to parents, but that's life.
     
    <Settings>
  <Main>
    <Left>300</Left>
    <Top>18446744073709551615</Top>
  </Main>
  <Configurations>
    <Count>3</Count>
    <Configuration>
      <Name>Easy</Name>
    </Configuration>
    <Configuration>
      <Name>Normal</Name>
    </Configuration>
    <Configuration>
      <Name>Difficult</Name>
    </Configuration>
  </Configurations>
</Settings>
     
     Here's a sample of the XML generated by the demo. The XML is very, very basic, but 
        very flexible. It has no schema and no pre-defined structure.
        There are XML elements for every CXMLKey.
        Any CXMLKey can save and read values,
        whether it was created or opened. Values are stored in the file as child nodes of
        the CXMLKey's node. All values are saved as text.
        DWORD & QWORD typed methods are
        provided, but CXMLKey stores the values as text.
        Values are stored as XML elements, not as
        attributes. This is
        only because I find it easier to read files that are more vertically than horizontally
        aligned. This generates a bigger file though, because all values have start and
        end tags, whereas attributes have only start tags. I might still be convinced to
        use attributes for values. Maybe if I adjust the formatting to newline between attributes...
     
     XML has more capabilities than the registry, especially the ability to add multiple
        items with same name. Where with CRegKey you'd have to add numbers to item names,
        e.g. Item1, Item2, Item3, to make them unique, in XML you can add 3 <Item> nodes, 
        each with a different value. This might be a feature, but it can be seen as a complication: 
    
        - When your reading values, how do you differentiate between three with the same name?
            All of CXMLKey's query methods and Open accept an XPath selection string as the valueName
            parameter. XPath supports indexing using [i] syntax. Indexing is 1-based. Please
            note that if you index past the actual number of items, the first item is returned.
 
     
    
        - When you're writing, how do you signal that you want to add another value of the
            same name to a list, as opposed to overwriting an existing value? CXMLKey's Create
            and SetStringValue methods add a boolean append parameter to the CRegKey parameters. When
            this parameter is set, CRegKey will append a duplicate node if one with the same
            name already exists, otherwise it'll use the existing node.
 
     
    
        - When there are many values with the same name, how do you know how many there are,
            so you can access them all? I didn't add a method to do this, what I do is have
            the application save the list length in a Count value. You can see this in the example.
        
 
     
     The CXMLDoc constructor accepts a parameter to enable pretty formatting. When this
        option is enabled, Create and SetStringValue insert
        line feeds and indents into the XML so it becomes formatted like the example above.
        Otherwise the XML is one single line of text. 
     CXMLDoc processes XML using DOM (Domain Object Model), not SAX (Simple API for XML).
        DOM keeps all of the data in memory while you have it open. With DOM you can randomly
        access the data by name; you're not subject to the SAX restriction of saving and getting
        everything in order. DOM uses more memory though. 
     CXMLDoc does not check for MSXML installed.
        If it's not installed, the constructor will abort with an HRESULT from xmlDoc.CoCreateInstance
        in ConstructorResult.
        CXMLDoc requests MSXML version 4. It uses the simplest functionality
        though, so you could modify it to request a lesser version.
     
     CXMLDoc::Save overwrites any existing file.
     
    Usage
     CXMLKeys can be used the same way the CRegKeys are used.
        Normally you will open
        and read all settings on application startup and then open and update or create and
        store settings on shutdown. 
     To load an existing file you need to instantiate
        a CXMLDoc and call its Load method. The CXMLDoc constructor creates
        and initializes an empty MSXML document instance. If anything fails in the constructor
        CXMLDoc saves the error code in its ConstructorResult public member. 
    CXMLDoc XMLSettings;
if (FAILED(XMLSettings.ConstructorResult))
    _tprintf (_T("MSXML instantiation error = %04X\n"), XMLSettings.ConstructorResult);
else 
{
    HRESULT Result = XMLSettings.Load (path);
    if (Result != S_OK)
    {
        USES_CONVERSION;
        CComBSTR Error;
        if (SUCCEEDED(XMLSettings.GetErrorDescription ((BSTR &) Error)))
            _tprintf (_T("SettingsLoad error = %s\n"), OLE2CT(Error));
        return Result;
    }
} 
     The Load method reads in and parses an existing XML file. 
        The demo application contains a nice SettingsFilePath method to
        get a file location in the user's Application Data folder. Don't test Load's
        return value with the FAILED macro. If the file doesn't exist the value is S_FALSE,
        which is not reported as a failure by FAILED.
        You can find out what failed in Load by calling GetErrorDescription. 
     
        After Load succeeds, the file's entire contents are in RAM and can
        be accessed using CXMLKey objects. 
    CXMLKey Settings;
Result = Settings.Open (XMLSettings, _T("Settings"));
if (Result != S_OK)
    return Result;
CXMLKey Main;
Result = Main.Open (Settings, _T("Main"));
if (Result != S_OK)
    return Result;
// Then get the values
DWORD Left;
Result = Main.QueryDWORDValue (_T("Left"), Left);
 
     XML data is structured hierarchically, just
        like registry data. A CXMLKey represents
        one node in the XML data tree. An XML data node may contain values and branches.
        A CXMLKey is a branch and contains the values. Like CRegKey, every CXMLKey must be linked to a parent node.
        At the root of the tree, a CXMLKey must link to a CXMLDoc. In the code above, the CXMLKey named Settings
        uses the Open method to link itself to the CXMLDoc named XMLSettings and the
        Main key links itself as a child of 
        Settings. Open's nodeName
        parameter is the name of the XML branch that this key
        will represent. 
            Main can access all of the data contained
        in Settings/Main. When applied to the XML above, the QueryDWORDValue
        for "Left" returns the value 300. Like Load, the Open and Query
        methods return S_FALSE if the requested node or value can't be found in the XML. 
     A CXMLKey provides access to all of the data of all
        of its child nodes. This is because the nodeName parameter is actually an XPath
        selection string. With XPath, you can specify subnodes using '/' delimiters. Thus
        you can skip opening Main and shortcut to the 'Left' value directly from Settings like this:
     
    CXMLKey Settings;
Result = Settings.Open (XMLSettings, _T("Settings"));
if (FAILED(Result))
    return Result;
DWORD Left;
Result = Settings.QueryDWORDValue (_T("Main/Left"), Left);
 
     XPath provides indexing syntax to access nodes or values from lists where the items
        all have the same XML tag: 
    CXMLKey Settings;
Result = Settings.Open (XMLSettings, _T("Settings"));
if (FAILED(Result))
    return Result;
TCHAR Name [24];
DWORD Length = 23;
Result = Settings.QueryStringValue (_T("Configurations/Configuration[3]"), Name, Length);
     
    
        Use the Create method to add a new key to the data hierarchy. This example adds
        a 'Configurations' node to the root 'Settings' node: 
    CXMLKey Settings;
Result = Settings.Create (XMLSettings, _T("Settings"));
if (FAILED(Result))
    return Result;
CXMLKey Configurations;
Result = Configurations.Create (Settings, _T("Configurations"));
 
     Create and SetValue do not use XPath parsing on the nodeName parameters like Open
        and QueryValue. When creating something new you are creating a parent/child relationship
        and so you must instantiate a CXMLKey for every new level in the hierarchy. You don't
        have to use Create all the way up the tree though. If you want to add to an existing
        hierarchy you can use Open to get the parent, and then use Create to add to it: 
    CXMLKey Configurations;
Result = Configurations.Open (XMLSettings, _T("Settings/Configurations"));
if (FAILED(Result))
    return Result;
Result = Configurations.SetDWORDValue (_T("Count"), 1);
CXMLKey Configuration;
Result = Configuration.Create (Configurations, _T("Configuration"), true);
 
     If XMLSettings already had a 'Settings/Configurations/Configuration' node, because it was
        loaded from a file or you had previously added that node, Configuration
        would refer
        to the existing node as if it were merely Opened. You can force creation of a new
        node, even one with the same name as one the parent already contains, by setting
        Create's append parameter true. Doing so puts another node with the same name right
        beside the existing one. You can add as many duplicates as you wish. You can access
        the duplicates using indexing syntax as described above. 
     After you've stored all of your settings in XMLKeys, use XMLDoc's Save method to
        save the XML file. If the XMLDoc was loaded from a file you can omit the path parameter
        and Save will update the file you loaded from. 
     XMLKeys are entirely random access. You can add nodes to settings loaded from a
        file and resave the file with the new nodes in it. You can delete nodes, but be
        careful not to delete any parents before you're done with their
        children. I didn't try to see what happens if you delete XMLDoc before finishing with
        key nodes.
     
    Observations
     The registry is only somewhat like XML files. XML has so many capabilities that
        are different from the registry or are additions to it, that CRegKey is not the
        best thing to model an XML persistence system on. On the other hand, XML can duplicate
        all of the functionality of the registry, but I didn't completely do that here.
     
     Some people like to get their settings from storage whenever the program needs it,
        as opposed to loading them all up at startup.
        I'm sure that if you instantiate and
        load XMLDoc and use XMLKey to get a value very time you need one, it'll be nowhere
        near as fast as the registry, which is kept
        in RAM. Maybe performance would be good
        if you load XMLDoc at startup and keep it loaded all the time the application is
        running, and just open XMLKeys when you want a value. 
     You can't structure a file saved by CXMLDoc the same as a .NET Application settings
        file because CXMLKey stores values in elements rather than attributes. 
     CXMLKey does not yet provide binary blob support, so it won't be any good for WTL8/atlwince.h/CAppInfoBase.
        It could drop in to WTL::CRecentDocumentList, though. 
     I just found out about Pascal Hurni's fabulous 
            Settings Storage at CodeProject! I hope somebody still finds this work useful. 
     This is my first code article on my web site. I hope eventually that my writing
        will be focused, complete and concise. 
    Download Files
    
    Source Code (9 Kb) Includes
    XMLKey classes, test/demo console application and VS 2005 project file 
     
     Requires ATL version 7, available with Visual Studio, or in the 
            Platform SDK
     
    
        Revisions
    
      
        | 2007-12-06 | 
        Change Open & Query methods to return S_FALSE when valueName
    not found and E_ABORT when QueryStringValue(TSTR) has insufficient buffer space. | 
       
      
        | 2007-11-28 | 
        Initial release. | 
       
     
 |