Codalogic LMX XML C++ Databinder Example
We show here
an example of how to use LMX for XML to C++ data binding.
Although it is brief, it covers many of the XML C++ databinding concepts, such as marshaling / unmarshaling XML and
how to interface with optional, singular and multiple types. It also shows how to work with XML schema complex types.
After trying this example, you may also like to download the XML SOAP example
whichs builds on the example described here.
Example
The example is based on one of the Purchase Order schemas in the XML Schema Primer specification. It
shows how to generate C++ code from the XML schema, and how to use the generated C++ binding code.
To follow the example you should download the following ZIP file:
lmx-example.zip. If you have not done so already, you can also
download the LMX XML C++ Databinder install file to generate the po.h
and
po.cpp
C++ files. Alternatively you can use the pre-generated files contained in the ZIP file.
Having downloaded the relevant files you should perform the following steps:
- Unzip the example file into a fresh directory.
- If you wish to generate the
po.h
and po.cpp
C++ files using LMX,
double-click on the file po.lmxprj
in Explorer. This should bring up WinLMX. Within WinLMX,
press the 'Compile...' button at the bottom of the window and the files will be generated.
(Press 'Exit' when complete.)
(Some example sections from the generated po.cpp
C++ file are shown below.)
- Or, you can copy the pre-generated C++ files located in the
Generated
sub-directory into the
example
directory.
- Using your IDE, open the relevant
poexample
workspace or solution file (poexamplevc6.dsw for VC++6 and poexamplevc7.sln for VC7.1++).
- The main part of the example is in the
handle_po()
function in the poexample.cpp
file. (The code for this is shown below.) Scanning the comments should tell you what is happening at each
stage.
- Compile, build and run.
- Observe the output gathered from the various parts of the input XML, the XML generated from modifying a
copy of the C++ objects, and the XML generated from the original unmodified C++ objects.
Conclusion
The example demonstrates how easy it is to use LMX XML C++ data binding to interface C++ with XML.
Although the example is short, it illustrates many of the key XML C++ data binding points, including
how to unmarshal and marshal XML, as well as read and writing to different types of data.
poexample.cpp
The C++ file
poexample.cpp
is the file containing the main part of the example. It is reproduced here
for your convenience.
// Read in the po XML
//-------------------
// Declare somehwere to store any error code
lmx::elmx_error l_error = lmx::ELMX_OK;
// Construct an instance of c_root from the XML contained in the file po.xml.
// (Code for c_root is generated by LMX)
c_root l_po( "po.xml", &l_error );
// If the XML was contained in memory, we could have done:
// c_root l_po( po_xml_buf, sizeof( po_xml_buf ), &l_error );
if( l_error != lmx::ELMX_OK )
{
std::cout << "An error occurred while reading XML\n";
return;
}
// Interrogate the parsed XML
//---------------------------
if( l_po.getchosen() == c_root::e_purchaseOrder )
{
// Make some intermediate variables so that components are easier to reference
const c_PurchaseOrderType *lp_pot = &l_po.get_purchaseOrder();
if( lp_pot->isset_orderDate() )
{
std::cout << "Ordered on: " << lp_pot->get_orderDate() << "\n";
if( lp_pot->get_orderDate().get_year() <= 2000 )
std::cout << "This was ordered last century - It is now overdue!\n";
}
const c_Items *lp_items = &lp_pot->get_items();
float l_total_cost = 0;
// Casts used to avoid vc7 warnings about converting size_t to unsigned int
std::cout << "Number of items = " << static_cast<unsigned int>(lp_items->size_item()) << "\n";
// Print out some information about each order item
for( size_t l_i=0; l_i<lp_items->size_item(); ++l_i )
{
// Casts used to avoid vc7 warnings about converting size_t to unsigned int
std::cout << "Item " << static_cast<unsigned int>(l_i + 1) << ": " <<
static_cast<unsigned int>( lp_items->get_item( l_i ).get_quantity() ) << " off - " <<
lmx::as_ascii( lp_items->get_item( l_i ).get_productName() ) <<
" (" << lmx::as_ascii( lp_items->get_item( l_i ).get_partNum() ) << ")";
// The comment is optional, so only print it out if it is present
if( lp_items->get_item( l_i ).isset_comment() )
std::cout << " [" << lmx::as_ascii( lp_items->get_item( l_i ).get_comment() ) << "]";
std::cout << " - $" << lp_items->get_item( l_i ).get_USPrice() << "\n";
l_total_cost += lp_items->get_item( l_i ).get_USPrice().get_scaled( 2 );
}
std::cout << "Total Cost: $" << l_total_cost/100 << "\n";
std::cout << "Ship to: " << lmx::as_ascii( lp_pot->get_shipTo().get_name() ) << "\n";
std::cout << "Bill to: " << lmx::as_ascii( lp_pot->get_billTo().get_name() ) << "\n";
std::cout << "\n";
// Modify the XML
//---------------
c_root l_alt_po( l_po ); // We don't actually need to create a new instance before modifying
l_alt_po.get_purchaseOrder().get_items().append_item();
c_Items::c_item *lp_item = &l_alt_po.get_purchaseOrder().get_items().back_item();
lp_item->set_productName( L"Fence" );
lp_item->set_quantity( 2 );
lp_item->set_partNum( L"100-AB" );
lp_item->set_USPrice( lmx::c_decimal( 12.95, 2U ) );
// We can also do:
// lp_item->set_USPrice( 12.95 ); // Although the schema needs to specify the fraction digits
// // facet in order to get the number of decimal places correct!
// lp_item->set_USPrice( lmx::c_decimal( 1295, 2 ) ); // e.g. 1295 / (10^2)
// lp_item->set_USPrice( "12.95" );
lp_item->set_comment( L"Will this stop the baby getting on the lawn?" );
assert( lp_item->is_occurs_ok() ); // Check sufficient elements and attributes added
// Write the modified version of the XML
//--------------------------------------
std::cout << "\n\nModified XML output\n";
if( l_alt_po.marshal( std::cout ) == lmx::ELMX_OK )
std::cout << "Modified XML written successfully\n";
else
std::cout << "Error writing Modified XML\n";
// Also marshal to a file
if( l_alt_po.marshal( "po-out.xml" ) == lmx::ELMX_OK )
std::cout << "Modified XML written successfully to file\n";
else
std::cout << "Error writing Modified XML to file\n";
}
// Write out the original XML
//---------------------------
std::cout << "\n\nXML output\n";
if( l_po.marshal( std::cout ) == lmx::ELMX_OK )
std::cout << "Original XML written successfully\n";
else
std::cout << "Error writing Original XML\n";
|
Generated Code Examples
The following is an extract of the function prototypes declared for the class
c_item
in
po.h
showing the facet and cardinality information applicable to each attribute and element. These
additional comments make programming easier.
// Attribute(s)
// partNum --> xs:string{/\d {3}-[A-Z]{2}/}
const lmx::tlmx_wide_string & get_partNum() const;
lmx::elmx_error set_partNum( const lmx::tlmx_wide_string & );
// Element(s)
// productName --> xs:string
const lmx::tlmx_wide_string & get_productName() const;
lmx::elmx_error set_productName( const lmx::tlmx_wide_string & );
// quantity --> xs:positiveInteger{1<=x<100}
lmx::tlmx_uns8 get_quantity() const;
lmx::elmx_error set_quantity( lmx::tlmx_uns8 );
// USPrice --> xs:decimal
const lmx::tc_decimal & get_USPrice() const;
lmx::elmx_error set_USPrice( const lmx::tc_decimal & );
// comment --> xs:string[0..1]
const lmx::tlmx_wide_string & get_comment() const;
lmx::elmx_error set_comment( const lmx::tlmx_wide_string & );
bool isset_comment() const;
void unset_comment();
// shipDate --> xs:date[0..1]
const lmx::tc_date & get_shipDate() const;
lmx::elmx_error set_shipDate( const lmx::tc_date & );
bool isset_shipDate() const;
void unset_shipDate();
|
This is the corresponding generated HTML documentation showing the abbreviated type notation
capturing the facets and cardinality of the various types. It is fully cross-linked
making navigation around the generated code easy.
item
Class name: | c_item |
Kind: | Element |
Model: | Sequence Complex Content |
|
partNum --> xs:string{/\d{3}-[A-Z]{2}/}
const lmx::tlmx_wide_string & | get_partNum | () const; |
lmx::elmx_error | set_partNum | ( const lmx::tlmx_wide_string & ); |
productName --> xs:string
const lmx::tlmx_wide_string & | get_productName | () const; |
lmx::elmx_error | set_productName | ( const lmx::tlmx_wide_string & ); |
quantity --> xs:positiveInteger{1<=x<100}
lmx::tlmx_uns8 | get_quantity | () const; |
lmx::elmx_error | set_quantity | ( lmx::tlmx_uns8 ); |
USPrice --> xs:decimal
const lmx::tc_decimal & | get_USPrice | () const; |
lmx::elmx_error | set_USPrice | ( const lmx::tc_decimal & ); |
comment --> xs:string[0..1]
const lmx::tlmx_wide_string & | get_comment | () const; |
lmx::elmx_error | set_comment | ( const lmx::tlmx_wide_string & ); |
bool | isset_comment | () const; |
void | unset_comment | (); |
shipDate --> xs:date[0..1]
const lmx::tc_date & | get_shipDate | () const; |
lmx::elmx_error | set_shipDate | ( const lmx::tc_date & ); |
bool | isset_shipDate | () const; |
void | unset_shipDate | (); |
Common Functions
bool | is_occurs_ok | () const; |
|
|
This is a section of the generated unmarshal code from po.cpp
. Notice the checks against the
various facets, and the checks that the document is correctly formed. The use of
ar_reader.handle_error
allows you to selectively override errors, or call exceptions
according to your preference.
*ap_event = ar_reader.tokenise( elem_event_map, *ap_name, lmx::EXNT_ELEM );
// ... intervening code removed ...
if( *ap_event == e_1_quantity )
{
bool l_end_of_element;
if( ( ! ar_reader.skip_start_tag( &l_end_of_element ) ||
l_end_of_element ) && (*ap_error = ar_reader.handle_error( lmx::ELMX_BAD_END_OF_START_TAG )) != lmx::ELMX_OK )
return false;
if( ar_reader.get_element_value( &l_value, lmx::EXWS_COLLAPSE ) )
{
if( ! ( lmx::is_valid_integer( l_value ) ) &&
(*ap_error = ar_reader.handle_error( lmx::ELMX_VALUE_BAD_FORMAT )) != lmx::ELMX_OK )
return false;
if( ! ( l_value >= limit_22_min ) &&
(*ap_error = ar_reader.handle_error( lmx::ELMX_VALUE_EXCEEDS_MIN )) != lmx::ELMX_OK )
return false;
if( ! ( l_value < limit_22_max ) &&
(*ap_error = ar_reader.handle_error( lmx::ELMX_VALUE_EXCEEDS_MAX )) != lmx::ELMX_OK )
return false;
lmx::v_to_o( _quantity, l_value );
present_quantity = true;
}
else if( (*ap_error = ar_reader.handle_error( lmx::ELMX_UNABLE_TO_READ_ELEMENT_VALUE )) != lmx::ELMX_OK )
return false;
|
The following is a section of the generated marshal code from po.cpp
. It has been designed to be
simple and easy to understand.
lmx::elmx_error c_USAddress::marshal( lmx::c_xml_writer &ar_writer, const char *p_name ) const
{
ar_writer() << '<' << p_name;
if( ! ar_writer.is_ns_attrs_written() )
ar_writer.write_ns_attrs( ns_map, false );
marshal_attributes( ar_writer );
ar_writer(false) << '>' << ar_writer.endl();
ar_writer.indent();
lmx::elmx_error l_error;
if( (l_error = marshal_child_elements( ar_writer )) != lmx::ELMX_OK )
return l_error;
ar_writer.undent();
ar_writer() << "</" << p_name << '>' << ar_writer.endl();
return lmx::ELMX_OK;
}
lmx::elmx_error c_USAddress::marshal_attributes( lmx::c_xml_writer &ar_writer ) const
{
if( present_country )
ar_writer(false) << " country=\"" << lmx::as_xml( _country ) << '"';
return lmx::ELMX_OK;
}
lmx::elmx_error c_USAddress::marshal_child_elements( lmx::c_xml_writer &ar_writer ) const
{
lmx::elmx_error l_error = lmx::ELMX_OK;
ar_writer() << "<name>" << lmx::as_xml( _name ) << "</name>" << ar_writer.endl();
ar_writer() << "<street>" << lmx::as_xml( _street ) << "</street>" << ar_writer.endl();
ar_writer() << "<city>" << lmx::as_xml( _city ) << "</city>" << ar_writer.endl();
ar_writer() << "<state>" << lmx::as_xml( _state ) << "</state>" << ar_writer.endl();
ar_writer() << "<zip>" << lmx::as_xml( _zip ) << "</zip>" << ar_writer.endl();
return lmx::ELMX_OK;
}
|