Important note: It is with great sadness that we have decided to end-of-life Codalogic LMX. With XML becoming an increasingly legacy technology and Covid reducing new sales we can no longer viably support the product. As a result we will not be selling new licenses or renewing Annual Maintenance. We will honour existing Annual Maintenance commitments until they expire. We will be notifying existing licensees in due course.

If you have any questions about this, please contact us on .

Thank you.

Codalogic LMX Walkthrough

XML C++ Binding Concept

The Codalogic LMX code generator allows you to easily read and write XML data in C++ programs. The code generated by LMX de-serializes XML into application specific C++ objects, and serializes the C++ objects into XML.

As shown in the diagram, LMX uses a specification of your XML data to generate application specific C++ code. The specification can be an example of your XML data, an Annotated XML Example (AXE), or an XML XSD Schema. Using the generated code, your code can easily access the XML data.

For example, an XML example of:

    <TeamName>Luke</TeamName>

or an Annotated XML Example of:

    <TeamName>string</TeamName>

or an XSD schema snippet such as:

    <xs:element name="TeamName" type="xs:string"/>

generates class methods of the form:

    const lmx::tlmx_unicode_string & get_TeamName() const;
    lmx::elmx_error set_TeamName( const lmx::tlmx_unicode_string & s );

where lmx::tlmx_unicode_string is typedefd to std::string and you can use it in your code as:

    std::cout << my_object.get_TeamName() << "\n";

This quick guide explains how to use LMX.

You can download a ZIP file containing files for this example.

The XML Data Specification

The format of your XML data can be specified in a number of ways.

The simplest is an example of your XML data. For this introduction this might be:

<ProjectTeam xmlns="http://codalogic.com/schemas/team.xsd">
    <TeamName>Skywalkers</TeamName>
    <Member>
        <Name>Anakin</Name>
        <BrainPower>115</BrainPower>
    </Member>
    <Member>
        <Name>Luke</Name>
        <BrainPower>108</BrainPower>
    </Member>
</ProjectTeam>

Often a single example of your XML data isn't sufficient to capture all the variations that your XML supports. In this case you can add additional characters to annotate your example. Such an Annoated XML Example might look like:

<ProjectTeam xmlns="http://codalogic.com/schemas/team.xsd">
    <TeamName>string</TeamName>
    * <Member>
        <Name>string</Name>
        <BrainPower>unsignedInt</BrainPower>
    </Member>
</ProjectTeam>

If you have complex needs, you can also use an XML Schema (XSD) to describe your XML, for example:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns="http://codalogic.com/schemas/team.xsd"
        targetNamespace="http://codalogic.com/schemas/team.xsd"
        elementFormDefault="qualified">

    <xs:element name="ProjectTeam">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="TeamName" type="xs:string"/>
                <xs:element name="Member" type="TeamMember" 
                            minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="TeamMember">
        <xs:sequence>
            <xs:element name="Name" type="xs:string"/>
            <xs:element name="BrainPower" type="xs:unsignedInt"/>
        </xs:sequence>
    </xs:complexType>

</xs:schema>

Each of these specifications define an outer element called ProjectTeam, which contains an Element called TeamName and multiple Member elements. Each Member element has a Name for the member and a BrainPower value.

An example XML instance of these specifications is as follows:

<ProjectTeam xmlns="http://codalogic.com/schemas/team.xsd">
    <TeamName>Skywalkers</TeamName>
    <Member>
        <Name>Anakin</Name>
        <BrainPower>115</BrainPower>
    </Member>
    <Member>
        <Name>Luke</Name>
        <BrainPower>108</BrainPower>
    </Member>
    <Member>
        <Name>Leia</Name>
        <BrainPower>128</BrainPower>
    </Member>
</ProjectTeam>
As you can see, this project team is called Skywalkers, and has three members; Anakin, Luke and Leia. Their combined brain power is 351.

Generating C++ code from the XML specification

Codalogic LMX generates C++ code from the XML specification. There are a number of different versions of LMX, with both GUI and command-line interfaces running on Windows and Linux. In this intro we will use the Window GUI interface: WinLMX.

The simplest way to generate C++ code using WinLMX is to locate the XML specification file in the Windows File Explorer and drag and drop it onto an instance of WinLMX, or even a shortcut on your desktop. If you do that, WinLMX will end up looking something like:

WinLMX XML data binding with C++ code generator after drag-drop

Notice that the name of the XML specification file has been put in the "Base Schema File:" text box, and the "Output File Base Name:" text box has been automatically filled in with the name of the XML specification file, minus the .xsd file extension.

LMX allows you to tailor the generated code to your preferences. For this intro, in addition to the code to read and write the XML, we want LMX to generate a file with a main() function in it with some basic test code. This can be done on WinLMX's Basic Options tab (checkbox half way down on the right-hand side):

Codalogic WinLMX XML data binding with C++ test framework generation

We have also selected the "Do Not Use Nested Classes" option.

LMX also allows you to store your settings in a configuration file so that you can easily re-build your project at a later time.

All you have to do now to generate the C++ code is press the "Compile..." button. In this case LMX will generate the following files:

  • A .h C++ header file called team.h
  • A .cpp C++ file called team.cpp
  • An HTML documentation file called team.html.
  • A file called team-main.cpp which contains the main() and test code that we wanted.

Inspecting the generated C++ code

Show Generated .h File

Show Generated .cpp File

Show Generated .html File

If you look at the generated .h you will notice that there are two classes defined - c_ProjectTeam and c_TeamMember - that correspond to the complex types defined in the XML Schema:

class c_ProjectTeam
{
private:
    // Element(s)
    lmx::ct_simple_non_pod_single< lmx::tlmx_unicode_string > m_TeamName;
    lmx::ct_complex_multi< c_TeamMember > m_Member;
...

and:

class c_TeamMember
{
private:
    // Element(s)
    lmx::ct_simple_non_pod_single< lmx::tlmx_unicode_string > m_Name;
    lmx::ct_simple_pod_single< lmx::tlmx_uns32 > m_BrainPower;
...

As you can see, they have data members corresponding to the element declarations in the XML Schema.

Moving down the c_ProjectTeam class, you will see a number of contructors and a destructor, for example:

    LMX_GDECL c_ProjectTeam();
    LMX_GDECL c_ProjectTeam( const c_ProjectTeam &ar_rhs );
    LMX_GDECL c_ProjectTeam & operator =( const c_ProjectTeam &ar_rhs );
    LMX_GDECL void swap( c_ProjectTeam &ar_rhs );
    // Convenience constructors
    LMX_GDECL c_ProjectTeam( const char ac_file_name[], lmx::elmx_error *ap_error );
    #ifdef _MSC_VER
        LMX_GDECL c_ProjectTeam( const wchar_t ac_file_name[], lmx::elmx_error *ap_error );
    #endif
    LMX_GDECL c_ProjectTeam( const char *ap_memory, size_t a_memory_size, lmx::elmx_error *ap_error );
    LMX_GDECL c_ProjectTeam( const std::string &ar_string, lmx::elmx_error *ap_error );
    LMX_GDECL virtual ~c_ProjectTeam();
These allow you to construct instances of the object from various sources of XML, such as a file or memory.

For example, you could use these constructors to create an object from the XML contained in the file my_file.xml using the code:

    lmx::elmx_error error;
    const c_ProjectTeam my_object( "my_file.xml", &error );

Further down the C++ class you will see the accessor methods, for example:

    // Element(s)
    //    TeamName --> xs:string
    LMX_GDECL const lmx::tlmx_unicode_string & get_TeamName() const;
    LMX_GDECL lmx::elmx_error set_TeamName( const lmx::tlmx_unicode_string & a );

    //    Member --> TeamMember[0..*]
    LMX_GDECL const c_TeamMember & get_Member( size_t a_index ) const; // For read access
    LMX_GDECL lmx::elmx_error append_Member();               // For write access
    LMX_GDECL c_TeamMember & back_Member();                  // For write access
    LMX_GDECL lmx::elmx_error insert_Member( size_t a_index ); // For write access
    LMX_GDECL c_TeamMember & get_Member( size_t a_index );   // For read/write access
    LMX_GDECL void  delete_Member( size_t a_index );
    LMX_GDECL void  clear_Member();
    LMX_GDECL c_TeamMember & assign_Member( size_t a_index, const c_TeamMember & a ); // Deep copy
    LMX_GDECL size_t size_Member() const;

Once again, the accessor method names correspond to the elements declared in the XML specification.

The methods for setting and getting the TeamName are quite straight forward. You will notice that the type used by these methods is lmx::tlmx_unicode_string. This can be typedefed to std::wstring, std::string, or, with possibly a little more work, to any other string type you prefer to use. Swapping between using std::string and std::wstring is particularly easy, and can be done by setting a #define in your project setup.

The accessor methods for Member are a bit more involved. This is because there can be more than one instance of Member and so there are methods to insert and extract instances of Member. If not all of these operations are required, the LMX code generator supports a flag that causes only a minimal set of methods to be generated (although often an optimizing compiler will not generate code for methods that are not used).

LMX also generates methods to read in XML, via unmarshal() methods and write out XML, via marshal() methods:

    // Convenience marshal/unmarshal functions
    LMX_GDECL lmx::elmx_error marshal( const char ac_file_name[] ) const;
    #if defined( _MSC_VER ) && _MSC_VER >= 1400
        LMX_GDECL lmx::elmx_error marshal( const wchar_t ac_file_name[] ) const;
    #endif
    LMX_GDECL lmx::elmx_error marshal( std::string *ap_string ) const;
    LMX_GDECL lmx::elmx_error marshal( std::ostream &ar_sos ) const;
    LMX_GDECL lmx::elmx_error unmarshal( const char ac_file_name[] );
    #ifdef _MSC_VER
        LMX_GDECL lmx::elmx_error unmarshal( const wchar_t ac_file_name[] );
    #endif
    LMX_GDECL lmx::elmx_error unmarshal( const char *ap_memory, size_t a_memory_size );
    LMX_GDECL lmx::elmx_error unmarshal( const std::string &ar_string )
    {
        return unmarshal( ar_string.data(), ar_string.size() );
    }

For example, to marshal an object to XML simply by doing:

    my_object.marshal( "my_file.xml" );

Looking at the .cpp, you will see code such as:

if( ar_reader.get_current_event() == e_1000_BrainPower )
{   
    bool l_is_empty_element;
    if( ar_reader.get_simple_type_value( *ap_name, lmx::EXWS_COLLAPSE, 
            &ar_reader.value, ap_error, &l_is_empty_element ) )
    {   
        ar_reader.set_code_line( __LINE__ );
        *ap_error = ar_reader.unmarshal_child_element( m_BrainPower, validation_spec_2,
                                                       &elem_event_map[1] );
        if( *ap_error != lmx::ELMX_OK )
            return false;
    }
Note that in addition to reading in the actual values, the generated code also tests that the read values are of a suitable type and within the bounds specified by the schema.

Using the generated C++ code

We mentioned that LMX could be instructed to generate a file with a main() that contains test code. This is a good way to start exploring the generated code.

For this intro we'll look at a simple example:

#include <iostream>

#include "team.h"

int main( int argc, char *argv[] )
{
    const char *xml_file = "team.xml";

    if( argc >= 2 )
        xml_file = argv[1];

    lmx::elmx_error error;
    c_ProjectTeam project_team( xml_file, &error );

    if( error != lmx::ELMX_OK )
    {
        std::cout << "Unable to unmarshal\n";
        LMX_OUTPUT_DEBUG_ERROR( std::cout );    // Will only output debug info in debug mode
        return 1;
    }

    unsigned int total_brain_power = 0;
    for( size_t i=0; i < project_team.size_Member(); ++i )
        total_brain_power += project_team.get_Member( i ).get_BrainPower();

    std::cout <<
            "The total brain power for project team " <<
            lmx::convert_to_narrow( project_team.get_TeamName() ) <<
            " is " << total_brain_power << "\n";

    #ifdef _MSC_VER     // Allow for operation inside IDE
    std::cout << "----Press <Return> to continue----\n";
    std::cin.get();
    #endif

    return 0;
}
This code opens an XML files specified on the command line, sums the brain power of all the team members and prints the result to std::cout.

Looking at the code in more detail, after including iostream to enable console I/O, we have:

#include "team.h"
This is the generated C++ header file. When used in the basic form this is the only C++ XML data binding related header file that needs to be included in the code, which makes code easy to setup.

After performing some checking whether the command-line includes a file name argument, an XML instance is read in from the selected file name:

    lmx::elmx_error error;
    c_ProjectTeam project_team( xml_file, &error );
As you can see, this is a very simple operation, at the end of which the project_team C++ object will be populated with the data from the XML instance.

If, after unmarshalling the XML instance, you didn't want to change the contents of project_team you could unmarshal to a const instance of project_team, for example, by changing the second line to:

    const c_ProjectTeam project_team( xml_file, &error );

The error variable retrieves any error code if there are problems with the unmarshalling operation. The next section of code shows an example of how that error code can be used:

    if( error != lmx::ELMX_OK )
    {
        std::cout << "Unable to unmarshal\n";
        LMX_OUTPUT_DEBUG_ERROR( std::cout );    // Will only output debug info in debug mode
        return 1;
    }
Note that if you prefer, you can configure LMX to throw a C++ exception if an error occurs, rather than returning an error code.

The next task is to sum the brain power for the entire team. This is done using the following code:

    unsigned int total_brain_power = 0;
    for( size_t i=0; i < project_team.size_Member(); ++i )
        total_brain_power += project_team.get_Member( i ).get_BrainPower();
As there are multiple instances of Member within project_team, project_team.size_Member() is used to return how many instances there are, and project_team.get_Member( i ) returns a C++ reference to the zero based i-th instance. The get_BrainPower() method can then be called on this reference, and the brain power value is returned. Note that get_BrainPower() returns an actual C++ integer value and not a string containing the text version of an integer. Hence you do not to do a manual conversion from text to integer in your code.

You can see here that we are using application-specific named methods to access the data we want. The advantage of this is that if you type the wrong name into the code, a compile-time error will occur and you can fix it quickly. This is highly favorable to using generic methods that take the name of the desired elements and attributes as string parameters. If such string parameters are typed wrong, the error is only detected at run-time, which, with code that has a number of execution paths may only be detected a long time after the code is initially entered. The LMX C++ XML data binding approach therefore significantly reduces the potential for introducing such accidental bugs.

And, depending on the IDE you are using, another benefit is that IDE features such as intelliSense and automatic code completion can be used, making it not only easier to find out what child element and attribute methods are accessible for your current complex type, but also speeding up code entry.

When the total team brain power is calculated, it is printed to the screen. This is done using the following lines:

    std::cout <<
            "The total brain power for project team " <<
            lmx::convert_to_narrow( project_team.get_TeamName() ) <<
            " is " << total_brain_power << "\n";
Note the lmx::convert_to_narrow(). LMX is fully Unicode compliant, and out-of-the-box string values are returned as instances of std::wstring. However, LMX can be easily configured to return strings as instances of std::string. This can be done by setting an appropriate C++ #define in your project. We've chosen not to do this in this example because we wanted to keep the configuration side as transparent as possible for you. There are however, many such things that can be configured in LMX.

That's really it for this C++ XML data binding example. The lines that follow just give you an opportunity to see the output generated by the program when running inside an IDE.

If you want to experiment further, you can add the following lines after the result of the brain power calculation is output to std::cout.

    project_team.append_Member();
    project_team.back_Member().set_Name( L"Obi Wan" );
    project_team.back_Member().set_BrainPower( 200 );
            
    std::cout << "Revised XML:\n";
    if( project_team.marshal( std::cout ) != lmx::ELMX_OK )
    {
        std::cout << "Unable to marshal revised XML\n";
        LMX_OUTPUT_DEBUG_ERROR( std::cout );    // Will only output debug info in debug mode
        return 2;
    }
The first three lines add a new member to the project_team. The first of these actually adds a new c_TeamMember instance to the project_team collection. The following two set the name and brain power of the member using the project_team's back_Member() method which returns a reference to the last member in the collection in much the same way that C++ STL containers back() methods do.

The project_team.marshal( std::cout ) line marshals the project team object to std::cout. The lines that follow check that the operation was successful.

Building the generated code

Codalogic XML C++ data binding project files In addition to the generated code and your project code, to build an executable you will also need the LMX Runtime Supporting Software. This includes the LMX XML parser (lmxparse.cpp) and the code for XML Schema types such as dateTime and so on (lmxtypes.cpp). The code is available in source code form allowing the generated code to be cross-platform. You can either include these source files directly in your projects as shown in the example, or you can build them into a library or DLL aided by scripts provided as part of the LMX installer.

Concluding the LMX Quick Intro

That just about concludes our quick intro. We hope that you can see that LMX C++ XML data binding is very simple to use. C++ code generation can be as simple as dragging a file over the LMX application and pressing a 'Complile' button, marshalling and unmarshalling XML instances can be as simple as one line of code, and getting and setting data is very simple and intuitive. This all makes using LMX quick and easy to use.

The approach of using application-specific named methods also dramatically reduces the potential for introducing accidental coding bugs by allowing the C++ compiler to do more checking at compile-time, rather than (hopefully) detecting such errors at run-time.

Investigate Codalogic LMX further

Codalogic LMX XML C++ Databinder has many features, such a the ability to augment the generated classes with your own code, the ability to customize the names of methods, the ability to set and manage C++ and XML namespaces, the ability to customize string handling plus other aspects of XML to C++ type mapping, and much more. You can find more about the benefits of using LMX on the LMX Benefits Page.

To investigate further Codalogic LMX we suggest downloading the evaluation version of LMX. If you enter your e-mail address on the download page we can send you a 30-day evaluation license that enables full functionality.

What do you want to do now?

You can:

Providing exceptional support is very important to us. If you have questions about Codalogic LMX XML C++ Databinder, please send a message to .

Copyright © 2003-2024, Codalogic Ltd. All Rights Reserved.