Extending The wxXmlSerializer

There are many built-in data types supported directly by the wxXS but aside of them, also custom data types can be processed by the library. For this case, the wxXS provides set of code macros and classes suitable for a creation and registration of user-defined I/O handlers.

In the following example a new I/O handler suitable for serialization/deserialization of wxColourData class is created and used. Note, that the example application is one of sample projects distributed together with the library source code. It is highly recommended to study reference documentation as well as the supplied sample projects for full understanding of discussed topics.

So, first of all, the programmer must declare new I/O handler class and define code macros used for marking of a serialized data members. This should be done in appropriate header file. The declaration code can be as follows:

////////////////////////////////////////////
// Declaration of new I/O handler
////////////////////////////////////////////

// Declaration of a class 'xsColourDataPropIO' encapsulating
// the custom  property I/O handler for 'wxColouData' data type.
XS_DECLARE_IO_HANDLER(wxColourData, xsColourDataPropIO);

// Code macros which create new serialized wxColourData property
#define XS_SERIALIZE_COLOURDATA(x, name)
XS_SERIALIZE_PROPERTY(x, wxT("colourdata"), name);

#define XS_SERIALIZE_COLOURDATA_EX(x, name, def)
XS_SERIALIZE_PROPERTY_EX(x,wxT("colourdata"), name, xsColourDataPropIO::ToString(def));

Let us discuss the code listed above in more details. The macro XS_DECLARE_IO_HANDLERS declares a new class called xsColourDataPropIO suitable for processing of data members with data type wxColourData which is a class provided by the wxWidgets used for data transfer between an application and the color picker dialog. User-defined macros XS_SERIALIZE_COLOURDATA and XS_SERIALIZE_COLOURDATA_EX can be later used in the implementation code to mark wxColourData class members in the similar way as the standard XS_SERIALIZE macro. Note that the text string “colourdata” must be a unique identifier used for identification of this data type in serialized XMLstructure.

Now see the implementation code:

// Define custom data I/O handler
XS_DEFINE_IO_HANDLER(wxColourData, xsColourDataPropIO);

// Two following static member functions of the data handler class MUST
// be defined manualy:
// wxString xsPropIO::ToString(T value) -> creates a string representation of the given value:
wxString xsColourDataPropIO::ToString(wxColourData value)
{
    wxString out;
    out << xsColourPropIO::ToString(value.GetColour());
    for(int i = 0; i < 16; i++)
    {
        out << wxT("|") << xsColourPropIO::ToString(value.GetCustomColour(i));
    }
    return out;
}

// T xsPropIO::FromString(const wxString& value) -> converts data from 
// given string representation to its relevant value:
wxColourData xsColourDataPropIO::FromString(const wxString& value)
{
    wxColourData data;
    if(!value.IsEmpty())
    {        
        int i = 0;
        wxStringTokenizer tokens(value, wxT("|"), wxTOKEN_STRTOK);
        data.SetColour(xsColourPropIO::FromString(tokens.GetNextToken()));
        while(tokens.HasMoreTokens())
        {
            data.SetCustomColour(i, xsColourPropIO::FromString(tokens.GetNextToken()));
            i++;
        }
    }
    return data;
}

The most of the implementation effort is hidden in the XS_DEFINE_IO_HANDLER macro. Here, the programmer must manually create only two static functions responsible for conversion of processed data value to its string representation and vice versa. These static functions are then internally used be core library classes for serialization and deserialization but they can be used also for any other purposes. For example, in the code above you can see similar static functions called xsColourPropIO::FromString() and xsColourPropIO::ToString() defined by built-in I/O handler designed for processing of wxColour data members.

Now, let's declare and define a class containing member data of type wxColourData and mark it for serialization via previously user-defined I/O handler:

////////////////////////////////////////////
// CustomDataSampleApp class
////////////////////////////////////////////

bool CustomDataSampleApp::OnInit()
{
    // load application settings if the configuration file exists, otherwise
    // create new settings class object with default values
    // initialize serializer (m_XmlIO class member)

    m_XmlIO.SetSerializerOwner(wxT("CustomDataSampleApp"));
    m_XmlIO.SetSerializerRootName(wxT("settings"));
    m_XmlIO.SetSerializerVersion(wxT("1.0.0"));

    // register new property I/O handler 'xsColourDataPropIO' for data type with name 'colourdata'
    XS_REGISTER_IO_HANDLER(wxT("colourdata"), xsColourDataPropIO);

    // create serialized settings class object manualy with default values
    m_pSettings = new Settings();

    // insert settings class object into serializer as its root node
    m_XmlIO.SetRootItem(m_pSettings);

    if( wxFileExists(wxT("settings.xml")) )
    {
        // load settings from configuration file
        m_XmlIO.DeserializeFromXml(wxT("settings.xml"));
    }

    // do some other initialization step ...
    return true;
}

Note that new macros XS_DECLARE_CLONABLE_CLASS and XS_DECLARE_CLONABLE_CLASS were used in the code. These macros differ from DECLARE_DYNAMIC_CLASS and IMPLEMENT_DYNAMIC_CLASS macros in such way that they implement also Clone() function for the class, which can be used for retrieving the exact copy of the class instance. This function is further used by wxXmlSerializer::CopyItems() member function and its copy constructor so a whole serializer's content can be copied in a single program line.

The last step needed for proper initialization of the new I/O handler class is its registration. It should be done as soon as possible, typically in the application initialization code. For registration of the I/O handler, the XS_REGISTER_IO_HANDLER macro can be used as shown in the following code:

////////////////////////////////////////////
// CustomDataSampleApp class 
////////////////////////////////////////////

bool CustomDataSampleApp::OnInit()
{
    // load application settings if the configuration file exists, otherwise 
    // create new settings class object with default values
    // initialize serializer (m_XmlIO class member)
    m_XmlIO.SetSerializerOwner(wxT("CustomDataSampleApp"));
    m_XmlIO.SetSerializerRootName(wxT("settings"));
    m_XmlIO.SetSerializerVersion(wxT("1.0.0"));
    // register new property I/O handler 'xsColourDataPropIO' for data type with name 'colourdata'
    XS_REGISTER_IO_HANDLER(wxT("colourdata"), xsColourDataPropIO);
    // create serialized settings class object 
    // manualy with default values
    m_pSettings = new Settings();
    // insert settings class object into serializer as its root node
    m_XmlIO.SetRootItem(m_pSettings);
    if( wxFileExists(wxT("settings.xml")) )
    {
        // load settings from configuration file
        m_XmlIO.DeserializeFromXml(wxT("settings.xml"));
    }
    // do some other initialization step ...
    return true;
}