Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

XMLProfile - a non-MFC, non-STL class to read and write XML profile files

4.88/5 (44 votes)
2 Jul 2008CPOL10 min read 2   1.9K  
XMLProfile implements a class to read and write XML profile files that is consistent with the MFC set of profile functions. Conversion functions to allow reading/writing both INI and XML profiles are included. A CWinApp-derived class provides drop-in replacement of the standard MFC profile functio

Introduction

Configuration files have now come full-circle in Microsoft's product roadplan. Originally, .ini files were used to store application-specific data (accessed by GetPrivateProfileXXX() functions) and also system data stored in Win.ini (accessed by GetProfileXXX() functions). With the introduction of Win95, developers were encouraged to use the registry instead:

"This function is provided only for compatibility with 16-bit Windows-based applications. Applications should store initialization information in the registry."

There were good reasons for doing this: applications were storing their .ini files in the Windows directory, or - even worse - dumping application data in Win.ini, and it was impossible to set up multi-user system using a single .ini file.

This of course spawned a whole new cottage industry of registry cleaners, to clean up the registry after misbehaving applications. Soon people were noticing that the registry was being filled up with huge amounts of application data, and began to wonder if this stuff should be put in stand-alone database.

There were also ease-of-use considerations. Even if an application behaved itself and stayed out of HKEY_LOCAL_MACHINE hive, it was still easier to just grab .ini file when you wanted to move application to another machine. Developers noticed that putting a separate .ini file in each user's CSIDL_APPDATA directory (C:\Documents and Settings\<username>\Application Data) was equivalent to using registry HKEY_CURRENT_USER hive. So some applications offered users choice: to use an .ini file, or to use registry.

Now with .Net .config files we are back to a type of .ini file, with a twist: the .config files are encoded in XML. This is probably a good thing, since XML is more accessible in cross-platform situations.

Motivation

Recently one of my clients discussed with me how to make his application more interoperable in his enterprise environment - multiple versions of Windows, Mac, and Linux. He was especially keen on using XML files as a transfer mechanism, but was concerned about the impact on code base, especially some MFC and WTL Windows applications.

I quickly sketched out XMLProfile, which would be encoded as XML and require minimal changes to legacy apps. Here is list of features I showed him:

screenshot No use of MFC or STL - My client wanted to avoid any dependencies that might cause future problems with cross-platform use, and had some specific concerns about use of MFC and STL. Of course, because of cross-platform considerations, this also meant that MSXML could not be used.
screenshot Use standard XML - This meant that the XML had to be accepted by all major parsing engines, to meet the goal of cross-platform use.
screenshot Backwards compatibility with ini files - .ini files have a two-tier structure, with sections containing keys (unique within a section) and their values. There is no limit (except practical ones) in XML files for nesting depth, but I did not want to introduce complexity when it was not needed, and obviously the legacy applications worked fine with only two levels. Here are examples of INI and XML profiles:
INI Profile
[Section1]  
key1=value1

[Section2]
key2=value2
key3=value3

[Section3]
key4=value4
key5=value5
key6=value6
XML Profile
<?xml version="1.0" encoding="utf-8"?>
<profile>
    <section name="Section1">
        <key name="key1" value="value1"/> 
    </section>
    <section name="Section2">
        <key name="key2" value="value2"/>
        <key name="key3" value="value3"/>
    </section>
    <section name="Section3">
        <key name="key4" value="value4"/>
        <key name="key5" value="value5"/>
        <key name="key6" value="value6"/>
    </section>
</profile>
screenshot Familiar API - This really is the complement to backwards compatibility requirement. Just like data format should be compatible, the API structure should also be intuitive to those familiar with .ini files.
screenshot Read XML or ini files, write XML and/or ini files - Of course it was a requirement to write XML, but to allow for any unexpected situations, I also wanted to provide for ini input and output. (As it turned out, this was very handy when converting old .ini files).
screenshot Support for CWinApp Profile functions - To make it as easy as possible to migrate to XML profiles, I defined CWinApp-derived class that replaced the CWinApp profile functions with compatible XML profile functions. This involved creating a CXMLWinApp class, which is demonstrated in the XMLWinAppTest demo.
screenshot Support for both Unicode (UTF-16) and ANSI - Support for Unicode was essential for the use of company's applications in international market.

XMLProfile Concepts and Facilities

Loading and Saving Profiles

These are functions for loading and saving profile files:

FunctionDescription
BOOL Load(LPCTSTR lpszFileName, ENCODING eEncoding = Default)Loads the XML profile
BOOL LoadIni(LPCTSTR lpszFileName, ENCODING eEncoding = Default)Loads INI profile
BOOL Save(LPCTSTR lpszFileName, ENCODING eEncoding = Default)Saves XML profile
BOOL SaveIni(LPCTSTR lpszIniFile, ENCODING eEncoding = Default)Saves profile as INI

The Load() function loads XML profile data from file, using second parameter (eEncoding) to determine how the file is encoded, and what conversion (if any) should be applied. The following table shows effect of various combinations of eEncoding, compiler preprocessor symbol _UNICODE, and Byte Order Mark (BOM) that is read from the file:

eEncoding_UNICODE SymbolBOMConversion Applied
AnsiNot definedN/ANone; file assumed to be ANSI
AnsiDefinedN/AContents converted to Unicode
UnicodeNot definedN/AContents converted to ANSI
UnicodeDefinedN/ANone; file assumed to be Unicode
DefaultNot definedNot presentNone; file assumed to be ANSI
DefaultNot definedPresentContents converted to ANSI
DefaultDefinedNot presentContents converted to Unicode
DefaultDefinedPresentNone; file assumed to be Unicode

The Save() function is somewhat simpler in its handling of the Default encoding option. When saving, Default simply means use Unicode encoding if _UNICODE is defined, otherwise use ANSI.

Low-Level Functions

These are low-level functions of CXmlProfile:

FunctionDescription
void Close(BOOL bSave)Close profile file
void DeleteContents()Empties data contents of section and key lists, frees memory
void Dump()Dumps contents of profile via OutputDebugString()
CXmlKeyListElement * FindKey(LPCTSTR lpszSectionName, LPCTSTR lpszName)FindKey retrieves key named lpszName in section named lpszSectionName
CXmlKeyListElement * FindKey(CXmlSectionListElement *pSection, LPCTSTR lpszName)FindKey retrieves key named lpszName in section specified by pSection
CXmlSectionListElement * FindSection(LPCTSTR lpszName)FindSection retrieves section named lpszName
CWTLString GetKey(LPCTSTR lpszSectionName, LPCTSTR lpszKeyName, LPCTSTR lpszDefault)GetKey retrieves key named lpszName in section specified by pSection
UINT GetKeys(LPCTSTR lpszSectionName, CXmlProfileKey **ppKeyBuffer)GetKeys retrieves all the keys in a section
UINT GetNumKeys(LPCTSTR lpszSectionName)GetNumKeys retrieves the number of keys in a section
UINT GetNumSections()GetNumSections retrieves the number of sections in the profile
BOOL GetReadOnly(CXmlKeyListElement *pKey)GetReadOnly retrieves the readonly status of a key
BOOL GetReadOnly(CXmlSectionListElement *pSection)GetReadOnly retrieves the readonly status of a section
UINT GetSections(CXmlProfileSectionEntry **ppSSectionArray)GetSections retrieves all the sections in the profile
BOOL IsFileOpen()IsFileOpen checks if profile file is open
BOOL IsKey(LPCTSTR lpszSectionName, LPCTSTR lpszKeyName)IsKey checks if key exists
BOOL IsSection(LPCTSTR lpszSection)IsSection checks if section exists
CXmlKeyListElement * SetKey(LPCTSTR lpszSectionName, LPCTSTR lpszKeyName, LPCTSTR lpszValue, BOOL bReadOnly = FALSE)SetKey sets the value lpszValue in the key named lpszKeyName in section named lpszSectionName
CXmlKeyListElement * SetKey(CXmlSectionListElement *pSection, LPCTSTR lpszKeyName, LPCTSTR lpszValue, BOOL bReadOnly = FALSE)SetKey sets the value lpszValue in the key named lpszKeyName in section specified by pSection
BOOL SetKeys(LPCTSTR lpszSectionName, CXmlProfileKey *pKeyBuffer, UINT nKeys)SetKeys creates/updates keys in a section
void SetReadOnly(CXmlSectionListElement *pSection, BOOL bReadOnly)SetReadOnly sets the readonly status of a section
void SetReadOnly(CXmlKeyListElement *pKey, BOOL bReadOnly)SetReadOnly sets the readonly status of a key
BOOL Validate()Validates contents of profile

MFC-Compatible Functions

These are MFC-Compatible functions:

FunctionDescription
BOOL GetProfileBinary(LPCTSTR lpszSection, LPCTSTR lpszEntry, BYTE** ppData, UINT* pBytes)Retrieve binary data from an entry within a specified section of the profile
BOOL WriteProfileBinary(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPBYTE pData, UINT nBytes)Writes binary data into an entry within a specified section of the profile
UINT GetProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nDefault)Retrieves an integer value from an entry within a specified section of the profile
BOOL WriteProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nValue)Writes an integer value into an entry within a specified section of the profile
CWTLString GetProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszDefault)Retrieves a string from an entry within a specified section of the profile
BOOL WriteProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszValue)Writes a string into an entry within a specified section of the profile

XML Tags

The tags used in XML profile files may be changed as required:

CXmlProfile String VariableDefault Value
m_strKeyTag"key"
m_strNameTag"name" (used for both sections and keys)
m_strProfileTag"profile"
m_strReadOnlyTag"readonly"
m_strSectionTag"section"
m_strValueTag"value"

Implementation Notes

XMLProfile may be compiled without any dependence on MFC, by simply commenting out line
#include "stdafx.h"  // use of MFC is optional, unless CXmlWinApp is being used
in files XMLProfile.cpp and XMLParser.cpp. (If using CXmlWinApp class, then you should include this line).

Because XMLProfile makes heavy use of strings, I had to find replacement for MFC's CString class. I found such a class in WTL, and renamed it to CWTLString to avoid any conflicts. When MFC is being used, there is #define to redefine CWTLString to CString.

The other major problem was what to do about list handling. I wanted to avoid using both MFC's and STL's list classes, because of some configuration issues with my client's code. A colleague suggested I look at the doubly-linked list class written by Sam Buss, which proved to be a very good choice. I use this both in CXmlProfile and CXmlParser.

XMLProfile Demo

Here is what the XMLProfile demo looks like:

screenshot

The Convert dialog allows you to convert back and forth between INI files and XML files:

screenshot

The Test dialog exercises XMLProfile functions with automated tests:

screenshot

How to use

Step 1 - Add Files

To integrate XMLProfile into your app, you first need to add following files to your project:

  • XMLProfile.cpp
  • XMLProfile.h
  • XMLParser.cpp
  • XMLParser.h
  • CLinkedList.h
  • wtlstring.h

If you are not using MFC, you can set the .cpp files to Not using precompiled header in Visual Studio. Otherwise, you will get error

fatal error C1010: unexpected end of file while looking for precompiled header directive
If you want to use the CWinApp-based XML profile functions, you should also include:
  • XMLWinApp.cpp
  • XMLWinApp.h

Use of CXmlWinApp requires that you enable precompiled headers for XMLProfile.cpp and XMLParser.cpp.

Step 2 - Add Header File to Your Source Module

In the module where you want to use XMLProfile, include header file XMLProfile.h.

Step 3 - Add Code

See XMLProfileTestDlg.cpp and Test.cpp for examples of how to use.

The XMLProfileTest demo program writes test files to the exe directory when it is run. These files are not deleted, so that they may be viewed after the tests are run.

Using XMLProfile with MFC

The code used in XMLProfile may be compiled with or without MFC. To compile without MFC, make sure the line #include "stdafx.h" is commented out in files XmlParser.cpp and XmlProfile.cpp, and set both of these files to Not using precompiled header in Visual Studio.

To compile with MFC, make sure the line #include "stdafx.h" is not commented out in files XmlParser.cpp and XmlProfile.cpp, and set both of these files to Use precompiled header in Visual Studio.

References

Useful Links

More Articles

Here are some other articles on CodeProject that deal with profile and configuration files:

Revision History

Version 1.0 - 2008 July 2

  • Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)