Tree/Customization guide
From Code Synthesis Wiki
Revision as of 08:27, 1 October 2006 Boris (Talk | contribs) (→Customizing the generated type - the complex case) ← Previous diff |
Current revision Ker (Talk | contribs) (→Customizing the generated type — the complex case (using c++11)) |
||
Line 1: | Line 1: | ||
- | '''Note: this guide is a work in progress''' | ||
- | |||
== Introduction == | == Introduction == | ||
Line 6: | Line 4: | ||
* using a different type for one of the XML Schema built-in types (e.g., <code>boost::gregorian::date</code> from the Boost libraries for <code>xsd:date</code>) | * using a different type for one of the XML Schema built-in types (e.g., <code>boost::gregorian::date</code> from the Boost libraries for <code>xsd:date</code>) | ||
- | * adding a member function or data member to a generated type (e.g., add the <code>print()</code> function) | + | * adding a member function or a data member to a generated type (e.g., add the <code>print()</code> function) |
- | * adding virtual functions to the base type of a hierarchy and implementing them in the derived types (e.g., when using <code>xsi:type</code> dynamic typing and/or substitution groups) | + | * adding virtual functions to the base type of a hierarchy and implementing them in the derived types (e.g., when using <code>xsi:type</code>-based dynamic typing and/or substitution groups) |
- | XSD provided two command-line options, <code>--custom-type</code> and <code>--custom-type-regex</code>, that allow you to specify which types should be customized and how these types will be customized. The format for the <code>--custom-type</code> option is as follows: | + | XSD provides two command-line options, <code>--custom-type</code> and <code>--custom-type-regex</code>, that allow you to specify which types should be customized and how these types will be customized. The format for the <code>--custom-type</code> option is as follows: |
--custom-type name[=type[/base]] | --custom-type name[=type[/base]] | ||
Line 108: | Line 106: | ||
to match several type names at once. For more information on this option refer to the [http://www.codesynthesis.com/projects/xsd/documentation/xsd.xhtml XSD command line interface documentation (man pages)]. | to match several type names at once. For more information on this option refer to the [http://www.codesynthesis.com/projects/xsd/documentation/xsd.xhtml XSD command line interface documentation (man pages)]. | ||
- | == Customizing the generated type - the simple case == | + | == Customizing the generated type — the simple case == |
- | Let's now look at a complete set of steps necessary to do a simple customization. All code in this section is taken from the <code>contacts</code> example which can be found in the <code>examples/cxx/tree/custom/contacts</code> directory of the XSD distribution. Our schema looks like this: | + | Let's now look at a complete set of steps necessary to do a simple customization. All code in this section is taken from the <code>contacts</code> example which can be found in the <code>examples/cxx/tree/custom/</code> directory of the XSD distribution. Our schema looks like this: |
- | <!-- contacts.xsd --> | + | <!-- contacts.xsd --> |
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" | <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" | ||
xmlns:cts="http://www.codesynthesis.com/contacts" | xmlns:cts="http://www.codesynthesis.com/contacts" | ||
Line 156: | Line 154: | ||
// | // | ||
public: | public: | ||
- | contact (const name::type&, | + | contact (const name_type&, |
- | const email::type&, | + | const email_type&, |
- | const phone::type&); | + | const phone_type&); |
contact (const xercesc::DOMElement&, | contact (const xercesc::DOMElement&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
contact (const contact&, | contact (const contact&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
virtual contact* | virtual contact* | ||
_clone (xml_schema::flags = 0, | _clone (xml_schema::flags = 0, | ||
- | xml_schema::type* = 0) const; | + | xml_schema::container* = 0) const; |
// Our customizations. | // Our customizations. | ||
Line 191: | Line 189: | ||
{ | { | ||
contact:: | contact:: | ||
- | contact (const name::type& n, | + | contact (const name_type& n, |
- | const email::type& e, | + | const email_type& e, |
- | const phone::type& p) | + | const phone_type& p) |
: contact_base (n, e, p) | : contact_base (n, e, p) | ||
{ | { | ||
Line 201: | Line 199: | ||
contact (const xercesc::DOMElement& e, | contact (const xercesc::DOMElement& e, | ||
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : contact_base (e, f, container) | + | : contact_base (e, f, c) |
{ | { | ||
} | } | ||
contact:: | contact:: | ||
- | contact (const contact& c, | + | contact (const contact& x, |
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : contact_base (c, f, container) | + | : contact_base (x, f, c) |
{ | { | ||
} | } | ||
Line 216: | Line 214: | ||
contact* contact:: | contact* contact:: | ||
_clone (xml_schema::flags f, | _clone (xml_schema::flags f, | ||
- | xml_schema::type* container) const | + | xml_schema::container* c) const |
{ | { | ||
- | return new contact (*this, f, container); | + | return new contact (*this, f, c); |
} | } | ||
Line 251: | Line 249: | ||
std::auto_ptr<catalog> c (catalog_ (argv[1])); | std::auto_ptr<catalog> c (catalog_ (argv[1])); | ||
- | for (catalog::contact::const_iterator i (c->contact ().begin ()); | + | for (catalog::contact_const_iterator i (c->contact ().begin ()); |
i != c->contact ().end (); ++i) | i != c->contact ().end (); ++i) | ||
{ | { | ||
Line 258: | Line 256: | ||
} | } | ||
- | At this stage you may be wondering why all this works. After all the <code>contact</code> class is used in the generated code even though it hasn't been defined - its definition is only available at the end of the generated header file (remember the <code>--hxx-prologue</code> option). The reason why this works has two parts to it: | + | At this stage you may be wondering why all this works. After all the <code>contact</code> class is used in the generated code even though it hasn't been defined — its definition is only available at the end of the generated header file (remember the <code>--hxx-prologue</code> option). The reason why this works has two parts to it: |
* the compiler generates forward declaration for types that we customize | * the compiler generates forward declaration for types that we customize | ||
Line 265: | Line 263: | ||
As a result, the customization technique outlined in this section works as long as you are not customizing a type that acts as a base for some other type in the same schema file. An alternative approach that works even in the case of inheritance is described in the next section. | As a result, the customization technique outlined in this section works as long as you are not customizing a type that acts as a base for some other type in the same schema file. An alternative approach that works even in the case of inheritance is described in the next section. | ||
- | == Customizing the generated type - the complex case == | + | == Customizing the generated type — the complex case == |
- | This section presents the complex case where the types being customized are inherited from in the same schema. All code in this section is taken from the <code>taxonomy</code> example which can be found in the <code>examples/cxx/tree/custom/taxonomy</code> directory of the XSD distribution. The schema for this example looks as follows: | + | This section presents the complex case where the types being customized are inherited from in the same schema. All code in this section is taken from the <code>taxonomy</code> example which can be found in the <code>examples/cxx/tree/custom/</code> directory of the XSD distribution. The schema for this example looks as follows: |
- | <!-- people.xsd --> | + | <!-- people.xsd --> |
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" | <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" | ||
xmlns:ppl="http://www.codesynthesis.com/people" | xmlns:ppl="http://www.codesynthesis.com/people" | ||
Line 343: | Line 341: | ||
} | } | ||
- | This header file won't compile unless we supply definitions - or at least forward declarations - for the <code>person_impl</code>, <code>superman_impl</code>, and <code>batman_impl</code> class templates. The <code>--fwd-prologue</code> option does exactly that: | + | This header file won't compile unless we supply definitions — or at least forward declarations — for the <code>person_impl</code>, <code>superman_impl</code>, and <code>batman_impl</code> class templates. The <code>--fwd-prologue</code> option does exactly that: |
it adds the <code>#include "people-custom-fwd.hxx"</code> line at the beginning of the generated <code>people-fwd.hxx</code>. The <code>people-custom-fwd.hxx</code> file provides forward declarations for the above mentioned class templates: | it adds the <code>#include "people-custom-fwd.hxx"</code> line at the beginning of the generated <code>people-fwd.hxx</code>. The <code>people-custom-fwd.hxx</code> file provides forward declarations for the above mentioned class templates: | ||
Line 377: | Line 375: | ||
public: | public: | ||
person_impl (const xml_schema::string& name); | person_impl (const xml_schema::string& name); | ||
+ | person_impl (std::auto_ptr<xml_schema::string>& name); | ||
person_impl (const xercesc::DOMElement&, | person_impl (const xercesc::DOMElement&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
person_impl (const person_impl&, | person_impl (const person_impl&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
virtual person_impl* | virtual person_impl* | ||
_clone (xml_schema::flags = 0, | _clone (xml_schema::flags = 0, | ||
- | xml_schema::type* = 0) const; | + | xml_schema::container* = 0) const; |
public: | public: | ||
Line 400: | Line 399: | ||
public: | public: | ||
superman_impl (const xml_schema::string& name, bool can_fly); | superman_impl (const xml_schema::string& name, bool can_fly); | ||
+ | superman_impl (std::auto_ptr<xml_schema::string>& name, bool can_fly); | ||
superman_impl (const xercesc::DOMElement&, | superman_impl (const xercesc::DOMElement&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
superman_impl (const superman_impl&, | superman_impl (const superman_impl&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
virtual superman_impl* | virtual superman_impl* | ||
_clone (xml_schema::flags = 0, | _clone (xml_schema::flags = 0, | ||
- | xml_schema::type* = 0) const; | + | xml_schema::container* = 0) const; |
public: | public: | ||
Line 426: | Line 426: | ||
unsigned int wing_span); | unsigned int wing_span); | ||
+ | batman_impl (std::auto_ptr<xml_schema::string>& name, | ||
+ | bool can_fly, | ||
+ | unsigned int wing_span); | ||
+ | |||
batman_impl (const xercesc::DOMElement&, | batman_impl (const xercesc::DOMElement&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
batman_impl (const batman_impl&, | batman_impl (const batman_impl&, | ||
xml_schema::flags = 0, | xml_schema::flags = 0, | ||
- | xml_schema::type* = 0); | + | xml_schema::container* = 0); |
virtual batman_impl* | virtual batman_impl* | ||
_clone (xml_schema::flags = 0, | _clone (xml_schema::flags = 0, | ||
- | xml_schema::type* = 0) const; | + | xml_schema::container* = 0) const; |
public: | public: | ||
Line 444: | Line 448: | ||
} | } | ||
- | Definitions of custom types look very similar to the one we saw in the previous section. The only two differences are the use of templates with the base class as a parameter and the inclusion of the <code>people-fwd.hxx</code> file. The latter allows us to refer to the generated as well as XML Schema built-in types, e.g., <code>xml_schema::string</code>. | + | Definitions of custom types look very similar to the one we saw in the previous section. The only two differences are the use of templates with the base class as a parameter and the inclusion of the <code>people-fwd.hxx</code> file. The latter allows us to refer to the generated as well as XML Schema built-in types, e.g., <code>xml_schema::string</code>. Note that we could also use the type aliases for element and attribute types instead of the actual types, as shown in the following code fragment: |
+ | |||
+ | namespace people | ||
+ | { | ||
+ | template <typename base> | ||
+ | class person_impl: public base | ||
+ | { | ||
+ | public: | ||
+ | person_impl (const typename base::name_type& name); | ||
+ | ... | ||
+ | }; | ||
+ | } | ||
The implementation of our class templates is placed into the <code>people-custom.cxx</code> file: | The implementation of our class templates is placed into the <code>people-custom.cxx</code> file: | ||
Line 461: | Line 476: | ||
person_impl<base>:: | person_impl<base>:: | ||
person_impl (const xml_schema::string& name) | person_impl (const xml_schema::string& name) | ||
+ | : base (name) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | template <typename base> | ||
+ | person_impl<base>:: | ||
+ | person_impl (std::auto_ptr<xml_schema::string>& name) | ||
: base (name) | : base (name) | ||
{ | { | ||
Line 469: | Line 491: | ||
person_impl (const xercesc::DOMElement& e, | person_impl (const xercesc::DOMElement& e, | ||
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : base (e, f, container) | + | : base (e, f, c) |
{ | { | ||
} | } | ||
Line 476: | Line 498: | ||
template <typename base> | template <typename base> | ||
person_impl<base>:: | person_impl<base>:: | ||
- | person_impl (const person_impl& p, | + | person_impl (const person_impl& x, |
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : base (p, f, container) | + | : base (x, f, c) |
{ | { | ||
} | } | ||
Line 485: | Line 507: | ||
template <typename base> | template <typename base> | ||
person_impl<base>* person_impl<base>:: | person_impl<base>* person_impl<base>:: | ||
- | _clone (xml_schema::flags f, xml_schema::type* container) const | + | _clone (xml_schema::flags f, xml_schema::container* c) const |
{ | { | ||
- | return new person_impl (*this, f, container); | + | return new person_impl (*this, f, c); |
} | } | ||
Line 507: | Line 529: | ||
superman_impl<base>:: | superman_impl<base>:: | ||
superman_impl (const xml_schema::string& name, bool can_fly) | superman_impl (const xml_schema::string& name, bool can_fly) | ||
+ | : base (name, can_fly) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | template <typename base> | ||
+ | superman_impl<base>:: | ||
+ | superman_impl (std::auto_ptr<xml_schema::string>& name, bool can_fly) | ||
: base (name, can_fly) | : base (name, can_fly) | ||
{ | { | ||
Line 515: | Line 544: | ||
superman_impl (const xercesc::DOMElement& e, | superman_impl (const xercesc::DOMElement& e, | ||
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : base (e, f, container) | + | : base (e, f, c) |
{ | { | ||
} | } | ||
Line 522: | Line 551: | ||
template <typename base> | template <typename base> | ||
superman_impl<base>:: | superman_impl<base>:: | ||
- | superman_impl (const superman_impl& s, | + | superman_impl (const superman_impl& x, |
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : base (s, f, container) | + | : base (x, f, c) |
{ | { | ||
} | } | ||
Line 532: | Line 561: | ||
superman_impl<base>* superman_impl<base>:: | superman_impl<base>* superman_impl<base>:: | ||
_clone (xml_schema::flags f, | _clone (xml_schema::flags f, | ||
- | xml_schema::type* container) const | + | xml_schema::container* c) const |
{ | { | ||
- | return new superman_impl (*this, f, container); | + | return new superman_impl (*this, f, c); |
} | } | ||
Line 559: | Line 588: | ||
batman_impl<base>:: | batman_impl<base>:: | ||
batman_impl (const xml_schema::string& name, | batman_impl (const xml_schema::string& name, | ||
+ | bool can_fly, | ||
+ | unsigned int wing_span) | ||
+ | : base (name, can_fly, wing_span) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | template <typename base> | ||
+ | batman_impl<base>:: | ||
+ | batman_impl (std::auto_ptr<xml_schema::string>& name, | ||
bool can_fly, | bool can_fly, | ||
unsigned int wing_span) | unsigned int wing_span) | ||
Line 569: | Line 607: | ||
batman_impl (const xercesc::DOMElement& e, | batman_impl (const xercesc::DOMElement& e, | ||
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : base (e, f, container) | + | : base (e, f, c) |
{ | { | ||
} | } | ||
Line 576: | Line 614: | ||
template <typename base> | template <typename base> | ||
batman_impl<base>:: | batman_impl<base>:: | ||
- | batman_impl (const batman_impl& s, | + | batman_impl (const batman_impl& x, |
xml_schema::flags f, | xml_schema::flags f, | ||
- | xml_schema::type* container) | + | xml_schema::container* c) |
- | : base (s, f, container) | + | : base (x, f, c) |
{ | { | ||
} | } | ||
Line 586: | Line 624: | ||
batman_impl<base>* batman_impl<base>:: | batman_impl<base>* batman_impl<base>:: | ||
_clone (xml_schema::flags f, | _clone (xml_schema::flags f, | ||
- | xml_schema::type* container) const | + | xml_schema::container* c) const |
{ | { | ||
- | return new batman_impl (*this, f, container); | + | return new batman_impl (*this, f, c); |
} | } | ||
Line 604: | Line 642: | ||
} | } | ||
- | Again, the implementation should look familiar. The two differences compared to the previous section that are worth mentioning are explicit template instantiations and qualification of member functions with <code>this-></code>. The explicit template instantiations is a nice optimization that allows us to keep the implementation details in the source file (instead of putting them into the header) and prevent object code bloat that could result from instantiating our member functions in several translation units. This technique works well in our case because for each class template we know the only template argument it will ever be instantiated with - the generated base class. | + | Again, the implementation should look familiar. The two differences compared to the previous section that are worth mentioning are explicit template instantiations and qualification of member functions with <code>this-></code>. The explicit template instantiations is a nice optimization that allows us to keep the implementation details in the source file (instead of putting them into the header) and prevent object code bloat that could result from instantiating our member functions in several translation units. This technique works well in our case because for each class template we know the only template argument it will ever be instantiated with — the generated base class. |
Qualification of member functions (<code>name</code>, <code>can_fly</code>, <code>wing_span</code>) with <code>this-></code> is necessary because they are declared in the base and do not have any arguments that depend on the template argument. For more information on this aspec of C++ templates see your favorite C++ book (e.g., "C++ Templates" by Vandevoorde and Josuttis). | Qualification of member functions (<code>name</code>, <code>can_fly</code>, <code>wing_span</code>) with <code>this-></code> is necessary because they are declared in the base and do not have any arguments that depend on the template argument. For more information on this aspec of C++ templates see your favorite C++ book (e.g., "C++ Templates" by Vandevoorde and Josuttis). | ||
Line 625: | Line 663: | ||
std::auto_ptr<catalog> c (catalog_ (argv[1])); | std::auto_ptr<catalog> c (catalog_ (argv[1])); | ||
- | for (catalog::person::const_iterator i (c->person ().begin ()); | + | for (catalog::person_const_iterator i (c->person ().begin ()); |
i != c->person ().end (); ++i) | i != c->person ().end (); ++i) | ||
{ | { | ||
i->print (cerr); | i->print (cerr); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | To summarize, the customization technique presented in this section consists of the following steps: | ||
+ | |||
+ | # Write <code>*-custom-fwd.hxx</code> with forward decelarations of all your class templates. | ||
+ | # Request generation of the forward declaration file with the <code>--generate-forward</code> option. | ||
+ | # Use the <code>--fwd-prologue</code> option to include <code>*-custom-fwd.hxx</code> at the beginning of the generated forward declaration file. | ||
+ | # Write <code>*-custom.hxx</code> with definitions of all your class templates. Include the generated forward declaration file to gain access to the generated and XML Schema built-in types. | ||
+ | # Use the <code>--hxx-prologue</code> option to include <code>*-custom.hxx</code> at the beginning of the generated header file. | ||
+ | # Use the <code>--custom-type</code> and/or <code>--custom-type-regex</code> options to request type customizations. | ||
+ | # Write <code>*-custom.cxx</code> with implementations of all your class templates. Include the generated header file at the beginning. Use explicit template instantiation to instantiate each template with the corresponding base class. | ||
+ | |||
+ | == Customizing the generated type — the complex case (using c++11)== | ||
+ | |||
+ | This has only been tested with Microsoft Visual C++ Compiler Nov 2013 CTP or newer, but will most likely work with gcc 4.8 and clang 3.x | ||
+ | |||
+ | The code and lots of text in this section has shamelessly been taken from the previous section, but the previous section is not required to understand this one. | ||
+ | The schema for this example looks as follows: | ||
+ | |||
+ | <!-- people.xsd --> | ||
+ | <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" | ||
+ | xmlns:ppl="http://www.codesynthesis.com/people" | ||
+ | targetNamespace="http://www.codesynthesis.com/people"> | ||
+ | |||
+ | <xsd:complexType name="person"> | ||
+ | <xsd:sequence> | ||
+ | <xsd:element name="name" type="xsd:string"/> | ||
+ | </xsd:sequence> | ||
+ | </xsd:complexType> | ||
+ | |||
+ | <xsd:complexType name="superman"> | ||
+ | <xsd:complexContent> | ||
+ | <xsd:extension base="ppl:person"> | ||
+ | <xsd:attribute name="can-fly" type="xsd:boolean" use="required"/> | ||
+ | </xsd:extension> | ||
+ | </xsd:complexContent> | ||
+ | </xsd:complexType> | ||
+ | |||
+ | <xsd:complexType name="batman"> | ||
+ | <xsd:complexContent> | ||
+ | <xsd:extension base="ppl:superman"> | ||
+ | <xsd:attribute name="wing-span" type="xsd:unsignedInt" use="required"/> | ||
+ | </xsd:extension> | ||
+ | </xsd:complexContent> | ||
+ | </xsd:complexType> | ||
+ | |||
+ | <xsd:complexType name="catalog"> | ||
+ | <xsd:sequence> | ||
+ | <xsd:element name="person" type="ppl:person" maxOccurs="unbounded"/> | ||
+ | </xsd:sequence> | ||
+ | </xsd:complexType> | ||
+ | |||
+ | <xsd:element name="catalog" type="ppl:catalog"/> | ||
+ | |||
+ | </xsd:schema> | ||
+ | |||
+ | We would like to add the virtual <code>print()</code> function to the base of the hierarchy (the <code>person</code> type) and override it in each derived type (<code>superman</code> and <code>batman</code>). The first step is to compile our schema. We will use the following XSD options (explained below): | ||
+ | |||
+ | --generate-polymorphic | ||
+ | --custom-type "person=person_impl<person_base>/person_base" | ||
+ | --custom-type "superman=superman_impl<superman_base>/superman_base" | ||
+ | --custom-type "batman=batman_impl<batman_base>/batman_base" | ||
+ | --generate-forward | ||
+ | --fwd-prologue '#include "people-custom-fwd.hxx"' | ||
+ | --hxx-prologue '#include "people-custom.hxx"' | ||
+ | --std c++11 | ||
+ | |||
+ | We use the <code>--generate-polymorphic</code> option because our instance documents will be using the <code>xsi:type</code>-based dynamic typing. | ||
+ | |||
+ | The set of <code>--custom-type</code> options should look familiar by now. They tell the compiler that we are customizing the <code>person</code>, <code>superman</code>, and <code>batman</code> types, that the <code>*_impl<*_base></code> class template instantiations should be used instead, and that the original mapping should still be generated but renamed to <code>*_base</code>. | ||
+ | |||
+ | The <code>--generate-forward</code> option triggers generation of the <code>people-fwd.hxx</code> forward declaration file. The content of the <code>people-fwd.hxx</code> file will look along these lines: | ||
+ | |||
+ | // people-fwd.hxx - automatically generated | ||
+ | |||
+ | namespace xml_schema | ||
+ | { | ||
+ | ... // Declarations for the XML Schema built-in types. | ||
+ | } | ||
+ | |||
+ | namespace people | ||
+ | { | ||
+ | class person_base; | ||
+ | typedef person_impl<person_base> person; | ||
+ | |||
+ | class superman_base; | ||
+ | typedef superman_impl<superman_base> superman; | ||
+ | |||
+ | class batman_base; | ||
+ | typedef batman_impl<batman_base> batman; | ||
+ | |||
+ | class catalog; | ||
+ | } | ||
+ | |||
+ | This header file won't compile unless we supply definitions — or at least forward declarations — for the <code>person_impl</code>, <code>superman_impl</code>, and <code>batman_impl</code> class templates. The <code>--fwd-prologue</code> option does exactly that: | ||
+ | it adds the <code>#include "people-custom-fwd.hxx"</code> line at the beginning of the generated <code>people-fwd.hxx</code>. The <code>people-custom-fwd.hxx</code> file (you need to create this) provides forward declarations for the above mentioned class templates: | ||
+ | |||
+ | // people-custom-fwd.hxx | ||
+ | |||
+ | namespace people | ||
+ | { | ||
+ | template <typename base> | ||
+ | class person_impl; | ||
+ | |||
+ | template <typename base> | ||
+ | class superman_impl; | ||
+ | |||
+ | template <typename base> | ||
+ | class batman_impl; | ||
+ | } | ||
+ | |||
+ | Unlike the simple case, when types being customized are inherited from in the same schema file, we cannot include definitions of custom types at the end of the genearted header file. That is why we are using the <code>--hxx-prologue</code> option instead of <code>--hxx-epilogue</code> to include <code>people-custom.hxx</code>. Since the custom type definitions are included before the generated base types (<code>person_base</code>, <code>superman_base</code>, and <code>batman_base</code>) are defined we have to use class templates instead of plain classes as we did in the previous section. The content you need to create in <code>people-custom.hxx</code> is presented below: | ||
+ | |||
+ | // people-custom.hxx | ||
+ | |||
+ | #include <iosfwd> // std::ostream | ||
+ | |||
+ | #include "people-fwd.hxx" | ||
+ | |||
+ | namespace people | ||
+ | { | ||
+ | // | ||
+ | // | ||
+ | template <typename base> | ||
+ | class person_impl: public base | ||
+ | { | ||
+ | public: | ||
+ | using base::base; | ||
+ | virtual person_impl* | ||
+ | _clone (xml_schema::flags = 0, | ||
+ | xml_schema::container* = 0) const override; | ||
+ | public: | ||
+ | virtual void | ||
+ | print (std::ostream&) const; | ||
+ | }; | ||
+ | |||
+ | template <typename base> | ||
+ | class superman_impl: public base | ||
+ | { | ||
+ | public: | ||
+ | using base::base; | ||
+ | virtual superman_impl* | ||
+ | _clone (xml_schema::flags = 0, | ||
+ | xml_schema::container* = 0) const override; | ||
+ | public: | ||
+ | virtual void | ||
+ | print (std::ostream&) const override; | ||
+ | }; | ||
+ | |||
+ | template <typename base> | ||
+ | class batman_impl: public base | ||
+ | { | ||
+ | public: | ||
+ | using base::base; | ||
+ | virtual batman_impl* | ||
+ | _clone (xml_schema::flags = 0, | ||
+ | xml_schema::container* = 0) const override; | ||
+ | public: | ||
+ | virtual void | ||
+ | print (std::ostream&) const override; | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | The "override" specifier makes sure that the compiler throws an error if the _clone function is specified wrongly. C++11 allows us to simply inherit all constructors by "using base::base;". This simplifies our classes significantly compared with the previous section. Definitions of custom types look very similar to the one we saw in the simple-case section. The only two differences are the use of templates with the base class as a parameter and the inclusion of the <code>people-fwd.hxx</code> file. The latter allows us to refer to the generated as well as XML Schema built-in types, e.g., <code>xml_schema::string</code>. | ||
+ | |||
+ | The implementation of our class templates is placed into the <code>people-custom.cxx</code> file: | ||
+ | |||
+ | // people-custom.cxx | ||
+ | |||
+ | #include <ostream> | ||
+ | |||
+ | #include "people.hxx" | ||
+ | |||
+ | namespace people | ||
+ | { | ||
+ | // person_impl | ||
+ | // | ||
+ | template <typename base> | ||
+ | void person_impl<base>:: | ||
+ | print (std::ostream& os) const | ||
+ | { | ||
+ | os << this->name () << std::endl; | ||
+ | } | ||
+ | |||
+ | template <typename base> | ||
+ | person_impl<base>* person_impl<base>:: | ||
+ | _clone (xml_schema::flags f, xml_schema::container* c) const | ||
+ | { | ||
+ | return new person_impl (*this, f, c); | ||
+ | } | ||
+ | |||
+ | // Explicitly instantiate person_impl class template for person_base. | ||
+ | // | ||
+ | template class person_impl<person_base>; | ||
+ | |||
+ | |||
+ | // superman_impl | ||
+ | // | ||
+ | template <typename base> | ||
+ | void superman_impl<base>:: | ||
+ | print (std::ostream& os) const | ||
+ | { | ||
+ | if (this->can_fly ()) | ||
+ | os << "Flying superman "; | ||
+ | else | ||
+ | os << "Superman "; | ||
+ | |||
+ | os << this->name () << std::endl; | ||
+ | } | ||
+ | |||
+ | template <typename base> | ||
+ | superman_impl<base>* superman_impl<base>:: | ||
+ | _clone (xml_schema::flags f, xml_schema::container* c) const | ||
+ | { | ||
+ | return new superman_impl (*this, f, c); | ||
+ | } | ||
+ | |||
+ | // Explicitly instantiate superman_impl class template for superman_base. | ||
+ | // | ||
+ | template class superman_impl<superman_base>; | ||
+ | |||
+ | // batman_impl | ||
+ | // | ||
+ | |||
+ | |||
+ | template <typename base> | ||
+ | void batman_impl<base>:: | ||
+ | print (std::ostream& os) const | ||
+ | { | ||
+ | os << "Batman " << this->name () << " with " << | ||
+ | this->wing_span () << "m wing span" << std::endl; | ||
+ | } | ||
+ | |||
+ | template <typename base> | ||
+ | batman_impl<base>* batman_impl<base>:: | ||
+ | _clone (xml_schema::flags f, xml_schema::container* c) const | ||
+ | { | ||
+ | return new batman_impl (*this, f, c); | ||
+ | } | ||
+ | |||
+ | // Explicitly instantiate batman_impl class template for batman_base. | ||
+ | // | ||
+ | template class batman_impl<batman_base>; | ||
+ | } | ||
+ | |||
+ | |||
+ | Again, the implementation should look familiar. The two differences compared to the previous section that are worth mentioning are explicit template instantiations and qualification of member functions with <code>this-></code>. The explicit template instantiations is a nice optimization that allows us to keep the implementation details in the source file (instead of putting them into the header) and prevent object code bloat that could result from instantiating our member functions in several translation units. This technique works well in our case because for each class template we know the only template argument it will ever be instantiated with — the generated base class. | ||
+ | |||
+ | Qualification of member functions (<code>name</code>, <code>can_fly</code>, <code>wing_span</code>) with <code>this-></code> is necessary because they are declared in the base and do not have any arguments that depend on the template argument. For more information on this aspect of C++ templates see your favorite C++ book (e.g., "C++ Templates" by Vandevoorde and Josuttis). | ||
+ | |||
+ | The client code that uses our customizations is presented below: | ||
+ | |||
+ | #include <memory> // std::unique_ptr | ||
+ | #include <iostream> | ||
+ | |||
+ | #include "people.hxx" | ||
+ | |||
+ | using std::cerr; | ||
+ | using std::endl; | ||
+ | |||
+ | int | ||
+ | main (int argc, char* argv[]) | ||
+ | { | ||
+ | |||
+ | std::unique_ptr<people::catalog> c (people::catalog_ (argv[1])); | ||
+ | |||
+ | for (people::person& person : c->person ()) | ||
+ | { | ||
+ | person.print (cerr); | ||
} | } | ||
} | } | ||
Line 644: | Line 952: | ||
== Customizing the XML Schema built-in types == | == Customizing the XML Schema built-in types == | ||
- | This section shows how to use custom types for the XML Schema built-in types. All code in this section is taken from the <code>calendar</code> example which can be found in the <code>examples/cxx/tree/custom/calendar</code> directory of the XSD distribution. The schema for this example looks as follows: | + | This section shows how to use custom types for the XML Schema built-in types. All code in this section is taken from the <code>calendar</code> example which can be found in the <code>examples/cxx/tree/custom/</code> directory of the XSD distribution. The schema for this example looks as follows: |
- | <!-- calendar.xsd --> | + | <!-- calendar.xsd --> |
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" | <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" | ||
xmlns:cal="http://www.codesynthesis.com/calendar" | xmlns:cal="http://www.codesynthesis.com/calendar" | ||
Line 670: | Line 978: | ||
</xsd:schema> | </xsd:schema> | ||
- | In this example we would like to map the <code>xsd:data</code> built-in type to the <code>boost::gregorian::date</code> class from the Boost <code>date_time</code> library. | + | In this example we would like to map the <code>xsd:date</code> built-in type to the <code>boost::gregorian::date</code> class from the Boost <code>date_time</code> library. |
- | Normally, when you compile your schema, code for the XML Schema built-in types is generated inline in the resulting header file. In order to customize the built-in types we will need to generate this code into a separate header file (remember that we can only customize types from the current schema's target namespace). To achive this we will use the <code>--generate-xml-schema</code> option. This option instructs the compiler to generate code for the XML Schema namespace into a separate header file. The fake schema file provided to the compiler does not have to exist and is only used to derive the name of the generated header file. For more information on this option see the [http://www.codesynthesis.com/projects/xsd/documentation/xsd.xhtml XSD command line interface documentation (man pages)]. We will use the following command line to generate a header for the XML Schema namespace: | + | Normally, when you compile your schema, code for the XML Schema built-in types is generated inline in the resulting header file. In order to customize the built-in types we will need to generate this code into a separate header file (remember that we can only customize types from the current schema's target namespace). To achive this we will use the <code>--generate-xml-schema</code> option. This option instructs XSD to generate code for the XML Schema namespace into a separate header file. The fake schema file provided to the compiler does not have to exist and is only used to derive the name of the generated header file. For more information on this option see the [http://www.codesynthesis.com/projects/xsd/documentation/xsd.xhtml XSD command line interface documentation (man pages)]. We will use the following command line to generate the header for the XML Schema namespace: |
xsd cxx-tree --generate-xml-schema --custom-type date --hxx-epilogue '#include "xml-schema-custom.hxx"' xml-schema.xsd | xsd cxx-tree --generate-xml-schema --custom-type date --hxx-epilogue '#include "xml-schema-custom.hxx"' xml-schema.xsd | ||
- | This will result in the <code>xml-schema.hxx</code> file. We also used the <code>--custom-type</code> option to customize the <code>xsd:date</code> type and the <code>--hxx-epilogue</code> to include <code>xml-schema-custom.hxx</code> at the end of | + | This will result in the <code>xml-schema.hxx</code> file. We also used the <code>--custom-type</code> option to customize the <code>xsd:date</code> type and the <code>--hxx-epilogue</code> to include <code>xml-schema-custom.hxx</code> at the end of the generated <code>xml-schema.hxx</code>. The <code>xml-schema-custom.hxx</code> file is where we define our <code>date</code> class: |
- | generated <code>xml-schema.hxx</code>. The <code>xml-schema-custom.hxx</code> file is where we define our <code>date</code> | + | |
- | type: | + | |
// xml-schema-custom.hxx | // xml-schema-custom.hxx | ||
Line 690: | Line 996: | ||
{ | { | ||
public: | public: | ||
- | date (const xercesc::DOMElement&, flags = 0, type* = 0); | + | date (const xercesc::DOMElement&, flags = 0, container* = 0); |
- | date (const xercesc::DOMAttr&, flags = 0, type* = 0); | + | date (const xercesc::DOMAttr&, flags = 0, container* = 0); |
- | date (const date&, flags = 0, type* = 0); | + | date (const std::string&, |
+ | const xercesc::DOMElement*, | ||
+ | flags = 0, | ||
+ | container* = 0); | ||
+ | |||
+ | date (const date&, flags = 0, container* = 0); | ||
virtual date* | virtual date* | ||
- | _clone (flags = 0, type* = 0) const; | + | _clone (flags = 0, container* = 0) const; |
}; | }; | ||
} | } | ||
- | Our <code>date</code> class inherits from both <code>xml_schema::simple_type</code> (all simple types should inherit from <code>xml_schema::simple_type</code> and complex types from <code>xml_schema::type</code>) and <code>boost::gregorian::date</code>. | + | The <code>date</code> class inherits from both <code>xml_schema::simple_type</code> (all simple types should inherit from <code>xml_schema::simple_type</code> and complex types from <code>xml_schema::type</code>) and <code>boost::gregorian::date</code>. |
The implementation of the <code>date</code> class is placed into the <code>xml-schema-custom.cxx</code> file: | The implementation of the <code>date</code> class is placed into the <code>xml-schema-custom.cxx</code> file: | ||
Line 705: | Line 1,016: | ||
#include <xsd/cxx/xml/string.hxx> // xml::transcode | #include <xsd/cxx/xml/string.hxx> // xml::transcode | ||
+ | |||
#include "xml-schema.hxx" | #include "xml-schema.hxx" | ||
Line 714: | Line 1,026: | ||
{ | { | ||
date:: | date:: | ||
- | date (const xercesc::DOMElement& e, flags f, type* container) | + | date (const xercesc::DOMElement& e, flags f, container* c) |
- | : simple_type (e, f, container), | + | : simple_type (e, f, c), |
gregorian::date ( | gregorian::date ( | ||
from_simple_string ( | from_simple_string ( | ||
Line 723: | Line 1,035: | ||
date:: | date:: | ||
- | date (const xercesc::DOMAttr& a, flags f, type* container) | + | date (const xercesc::DOMAttr& a, flags f, container* c) |
- | : simple_type (a, f, container), | + | : simple_type (a, f, c), |
gregorian::date ( | gregorian::date ( | ||
from_simple_string ( | from_simple_string ( | ||
Line 732: | Line 1,044: | ||
date:: | date:: | ||
- | date (const date& d, flags f, type* container) | + | date (const std::string& s, |
- | : simple_type (d, f, container), | + | const xercesc::DOMElement* e, |
+ | flags f, | ||
+ | container* c) | ||
+ | : simple_type (s, e, f, c), | ||
+ | gregorian::date (from_simple_string (s)) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | date:: | ||
+ | date (const date& d, flags f, container* c) | ||
+ | : simple_type (d, f, c), | ||
gregorian::date (d) | gregorian::date (d) | ||
{ | { | ||
Line 739: | Line 1,061: | ||
date* date:: | date* date:: | ||
- | _clone (flags f, type* container) const | + | _clone (flags f, container* c) const |
{ | { | ||
- | return new date (*this, f, container); | + | return new date (*this, f, c); |
} | } | ||
} | } | ||
Line 748: | Line 1,070: | ||
encoding (UTF-16) to the current code page. | encoding (UTF-16) to the current code page. | ||
- | We don't need to do anything special when compiling our schema except for using the <code>--extern-xml-schema</code> option to instruct | + | We don't need to do anything special when compiling our schema except for using the <code>--extern-xml-schema</code> option to instruct XSD to include the previously generated <code>xml-schema.hxx</code> instaed of generating the XML Schema namespace inline: |
- | the compiler to include the previously generated <code>xml-schema.hxx</code> instaed of generating the XML Schema namespace inline: | + | |
xsd cxx-tree --extern-xml-schema xml-schema.xsd calendar.xsd | xsd cxx-tree --extern-xml-schema xml-schema.xsd calendar.xsd | ||
Line 761: | Line 1,082: | ||
#include <memory> // std::auto_ptr | #include <memory> // std::auto_ptr | ||
#include <iostream> | #include <iostream> | ||
+ | |||
#include "calendar.hxx" | #include "calendar.hxx" | ||
Line 773: | Line 1,095: | ||
std::auto_ptr<events> c (events_ (argv[1])); | std::auto_ptr<events> c (events_ (argv[1])); | ||
- | for (events::event::const_iterator i (c->event ().begin ()); | + | for (events::event_const_iterator i (c->event ().begin ()); |
i != c->event ().end (); ++i) | i != c->event ().end (); ++i) | ||
{ | { | ||
Line 781: | Line 1,103: | ||
} | } | ||
- | We use the <code>operator<<</code> operator provided by the <code>boost::gregorian::date</code> class to print the date in the calendar. | + | In this example, the <code>operator<<</code> operator provided by the <code>boost::gregorian::date</code> class is used to print dates in the calendar. |
+ | |||
+ | The <code>xsd:anyType</code> type, which is mapped to <code>xml_schema::type</code>, is a base type for every generated and built-in type in the C++/Tree mapping. Because of this property it is often useful to customize this type in order to provide a common functionality to all types in an object model. One special requirement when customizing <code>anyType</code> is that you should always inherit your version from the default implementation. For an example on how to customize <code>anyType</code> refer to the <code>comments</code> example which can be found in the <code>examples/cxx/tree/custom/</code> directory of the XSD distribution. | ||
+ | |||
+ | == Additional examples == | ||
+ | |||
+ | The following additional examples, found in the <code>examples/cxx/tree/custom/</code> directory, show various applications of the type customization mechanism: | ||
+ | |||
+ | * The <code>wildcard</code> example shows how to customize type's parsing constructor and serialization operator to handle the <code>xsd:anyAttribute</code> wildcard. | ||
+ | |||
+ | * The <code>comments</code> example shows how to customize the <code>anyType</code> built-in type in order to retain XML comments in the object model. | ||
+ | |||
+ | * The <code>double</code> example shows how to customize parsing and serialization code for the <code>xsd:double</code> XML Schema built-in type. It can be used as a guide on how to customize built-in XML Schema types that are mapped to fundamental C++ types. |
Current revision
Contents |
Introduction
XSD provides you with mechanisms to customize the generated type system in the C++/Tree mapping. Common customization examples include:
- using a different type for one of the XML Schema built-in types (e.g.,
boost::gregorian::date
from the Boost libraries forxsd:date
) - adding a member function or a data member to a generated type (e.g., add the
print()
function) - adding virtual functions to the base type of a hierarchy and implementing them in the derived types (e.g., when using
xsi:type
-based dynamic typing and/or substitution groups)
XSD provides two command-line options, --custom-type
and --custom-type-regex
, that allow you to specify which types should be customized and how these types will be customized. The format for the --custom-type
option is as follows:
--custom-type name[=type[/base]]
The name component specifies the name of a type as defined in the schema being compiled. The name is unqualified since the target namespace of the schema is always assumed. The optional type component is the name of a C++ type that should be used instead of the generated type. If type is empty or not specified then the type name from the schema (i.e., name) is assumed. Finally, if the optional base component is provided then the standard mapping for the type is still generated but with this name. A few examples should make all this clear. Suppose we have the following schema:
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.com/hello"> <complexType name="names"> <sequence> <element name="name" type="string"/> </sequence> </complexType> </schema>
Compiling this schema without specifying any options will result in the following C++ code:
namespace hello { class names { ... }; }
Now if we compile the above schema with the following option:
--custom-type names
The generated code would look like this:
namespace hello { class names; }
The compiler simply generated a forward declaration for names
expecting us to provide the implementation. Let's say we want to use the defined elsewhere my_names
class instead. Then we could use the following option:
--custom-type names=my_names
The generated code would look like this:
namespace hello { typedef my_names names; }
What if we wanted to use class template names
which is defined in namespace templates
? Then we could use the following option:
--custom-type names=::templates::names<char>
The resulting code would look like this:
namespace hello { typedef ::templates::names<char> names; }
Now what if all we want to do is add a simple function to the names
type? It would be too much work if we had to implement all the code that gets generated ourselves. Fortunately we don't have to. We can ask the compiler to generate the standard mapping with a different name and then inherit our custom type from it:
--custom-type names=/names_base
This would result in the following C++ code:
namespace hello { class names_base; class names; class names_base { ... }; }
Later, in our implementation of the names
class, we can use names_base
as a base. Here is another example:
--custom-type names=::templates::names<names_base>/names_base
This results in the following C++ code being generated:
namespace hello { class names_base; typedef ::templates::names<names_base> names; class names_base { ... }; }
While this example may seem a bit far-fetched this technique is actually used in some special cases as will be shown later.
The --custom-type-regex
option is similar to --custom-type
except it allows you to use Perl-like regular expressions
to match several type names at once. For more information on this option refer to the XSD command line interface documentation (man pages).
Customizing the generated type — the simple case
Let's now look at a complete set of steps necessary to do a simple customization. All code in this section is taken from the contacts
example which can be found in the examples/cxx/tree/custom/
directory of the XSD distribution. Our schema looks like this:
<!-- contacts.xsd --> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cts="http://www.codesynthesis.com/contacts" targetNamespace="http://www.codesynthesis.com/contacts"> <xsd:complexType name="contact"> <xsd:sequence> <xsd:element name="name" type="xsd:string"/> <xsd:element name="email" type="xsd:string"/> <xsd:element name="phone" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="catalog"> <xsd:sequence> <xsd:element name="contact" type="cts:contact" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:element name="catalog" type="cts:catalog"/> </xsd:schema>
We would like to add the print()
function to the contact
type. The first step is to compile our schema. We will use the following XSD options (explained below):
--custom-type contact=/contact_base --hxx-epilogue '#include "contacts-custom.hxx"'
The first option tells XSD that we are providing our own implemntation for the contact
type as well instructs it to generate the standard mapping with contact_base
as a name. We will use the generated contact_base
class as a base for our implementation of contact
. The second option instructs the compiler to add #include "contacts-custom.hxx"
at the end of the generated header file. The contacts-custom.hxx
file is where we define our contact
class:
// contacts-custom.hxx #include <iosfwd> // std::ostream namespace contacts { class contact: public contact_base { // The following constructor signatures are copied from // contact_base except for the copy constructor and the // _clone function where we had to change the type from // contact_base to contact. // public: contact (const name_type&, const email_type&, const phone_type&); contact (const xercesc::DOMElement&, xml_schema::flags = 0, xml_schema::container* = 0); contact (const contact&, xml_schema::flags = 0, xml_schema::container* = 0); virtual contact* _clone (xml_schema::flags = 0, xml_schema::container* = 0) const; // Our customizations. // public: void print (std::ostream&) const; }; }
The implementation of the contact
class is placed into the contacts-custom.cxx
file:
// contacts-custom.cxx #include <ostream> #include "contacts.hxx" namespace contacts { contact:: contact (const name_type& n, const email_type& e, const phone_type& p) : contact_base (n, e, p) { } contact:: contact (const xercesc::DOMElement& e, xml_schema::flags f, xml_schema::container* c) : contact_base (e, f, c) { } contact:: contact (const contact& x, xml_schema::flags f, xml_schema::container* c) : contact_base (x, f, c) { } contact* contact:: _clone (xml_schema::flags f, xml_schema::container* c) const { return new contact (*this, f, c); } void contact:: print (std::ostream& os) const { os << name () << " e| " << email () << " t| " << phone () << std::endl; } }
There are two things worth noting about this implementation. First, note that we include contacts.hxx
instead of contacts-custom.hxx
. This is important since contacts-custom.hxx
is not self-sufficient; for
example, it refernces but does not define contact_base
. Second, note that all constructors are implemented
by forwarding to the corresponding contact_base
constructors.
And that's pretty much it. The only piece left is the client code that uses our customizations:
// driver.cxx #include <memory> // std::auto_ptr #include <iostream> #include "contacts.hxx" using std::cerr; using std::endl; int main (int argc, char* argv[]) { using namespace contacts; std::auto_ptr<catalog> c (catalog_ (argv[1])); for (catalog::contact_const_iterator i (c->contact ().begin ()); i != c->contact ().end (); ++i) { i->print (cerr); } }
At this stage you may be wondering why all this works. After all the contact
class is used in the generated code even though it hasn't been defined — its definition is only available at the end of the generated header file (remember the --hxx-prologue
option). The reason why this works has two parts to it:
- the compiler generates forward declaration for types that we customize
- the C++/Tree mapping is designed in such a way that a forward declaration is sufficient for all uses of a type in a header file except for inheritance
As a result, the customization technique outlined in this section works as long as you are not customizing a type that acts as a base for some other type in the same schema file. An alternative approach that works even in the case of inheritance is described in the next section.
Customizing the generated type — the complex case
This section presents the complex case where the types being customized are inherited from in the same schema. All code in this section is taken from the taxonomy
example which can be found in the examples/cxx/tree/custom/
directory of the XSD distribution. The schema for this example looks as follows:
<!-- people.xsd --> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ppl="http://www.codesynthesis.com/people" targetNamespace="http://www.codesynthesis.com/people"> <xsd:complexType name="person"> <xsd:sequence> <xsd:element name="name" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="superman"> <xsd:complexContent> <xsd:extension base="ppl:person"> <xsd:attribute name="can-fly" type="xsd:boolean" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="batman"> <xsd:complexContent> <xsd:extension base="ppl:superman"> <xsd:attribute name="wing-span" type="xsd:unsignedInt" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="catalog"> <xsd:sequence> <xsd:element name="person" type="ppl:person" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:element name="catalog" type="ppl:catalog"/> </xsd:schema>
We would like to add the virtual print()
function to the base of the hierarchy (the person
type) and override it in each derived type (superman
and batman
). The first step is to compile our schema. We will use the following XSD options (explained below):
--generate-polymorphic --custom-type "person=person_impl<person_base>/person_base" --custom-type "superman=superman_impl<superman_base>/superman_base" --custom-type "batman=batman_impl<batman_base>/batman_base" --generate-forward --fwd-prologue '#include "people-custom-fwd.hxx"' --hxx-prologue '#include "people-custom.hxx"'
We use the --generate-polymorphic
option because our instance documents will be using the xsi:type
-based dynamic typing.
The set of --custom-type
options should look familiar by now. They tell the compiler that we are customizing the person
, superman
, and batman
types, that the *_impl<*_base>
class template instantiations should be used instead, and that the original mapping should still be generated but renamed to *_base
.
The --generate-forward
option triggers generation of the people-fwd.hxx
forward declaration file. The content of the people-fwd.hxx
file will look along these lines:
// people-fwd.hxx - automatically generated namespace xml_schema { ... // Declarations for the XML Schema built-in types. } namespace people { class person_base; typedef person_impl<person_base> person; class superman_base; typedef superman_impl<superman_base> superman; class batman_base; typedef batman_impl<batman_base> batman; class catalog; }
This header file won't compile unless we supply definitions — or at least forward declarations — for the person_impl
, superman_impl
, and batman_impl
class templates. The --fwd-prologue
option does exactly that:
it adds the #include "people-custom-fwd.hxx"
line at the beginning of the generated people-fwd.hxx
. The people-custom-fwd.hxx
file provides forward declarations for the above mentioned class templates:
// people-custom-fwd.hxx namespace people { template <typename base> class person_impl; template <typename base> class superman_impl; template <typename base> class batman_impl; }
Unlike the simple case, when types being customized are inherited from in the same schema file, we cannot include definitions of custom types at the end of the genearted header file. That is why we are using the --hxx-prologue
option instead of --hxx-epilogue
to include people-custom.hxx
. Since the custom type definitions are included before the generated base types (person_base
, superman_base
, and batman_base
) are defined we have to use class templates instead of plain classes as we did in the previous section. The content of people-custom.hxx
is presented below:
// people-custom.hxx #include <iosfwd> // std::ostream #include "people-fwd.hxx" namespace people { // // template <typename base> class person_impl: public base { public: person_impl (const xml_schema::string& name); person_impl (std::auto_ptr<xml_schema::string>& name); person_impl (const xercesc::DOMElement&, xml_schema::flags = 0, xml_schema::container* = 0); person_impl (const person_impl&, xml_schema::flags = 0, xml_schema::container* = 0); virtual person_impl* _clone (xml_schema::flags = 0, xml_schema::container* = 0) const; public: virtual void print (std::ostream&) const; }; template <typename base> class superman_impl: public base { public: superman_impl (const xml_schema::string& name, bool can_fly); superman_impl (std::auto_ptr<xml_schema::string>& name, bool can_fly); superman_impl (const xercesc::DOMElement&, xml_schema::flags = 0, xml_schema::container* = 0); superman_impl (const superman_impl&, xml_schema::flags = 0, xml_schema::container* = 0); virtual superman_impl* _clone (xml_schema::flags = 0, xml_schema::container* = 0) const; public: virtual void print (std::ostream&) const; }; template <typename base> class batman_impl: public base { public: batman_impl (const xml_schema::string& name, bool can_fly, unsigned int wing_span); batman_impl (std::auto_ptr<xml_schema::string>& name, bool can_fly, unsigned int wing_span); batman_impl (const xercesc::DOMElement&, xml_schema::flags = 0, xml_schema::container* = 0); batman_impl (const batman_impl&, xml_schema::flags = 0, xml_schema::container* = 0); virtual batman_impl* _clone (xml_schema::flags = 0, xml_schema::container* = 0) const; public: virtual void print (std::ostream&) const; }; }
Definitions of custom types look very similar to the one we saw in the previous section. The only two differences are the use of templates with the base class as a parameter and the inclusion of the people-fwd.hxx
file. The latter allows us to refer to the generated as well as XML Schema built-in types, e.g., xml_schema::string
. Note that we could also use the type aliases for element and attribute types instead of the actual types, as shown in the following code fragment:
namespace people { template <typename base> class person_impl: public base { public: person_impl (const typename base::name_type& name); ... }; }
The implementation of our class templates is placed into the people-custom.cxx
file:
// people-custom.cxx #include <ostream> #include "people.hxx" namespace people { // person_impl // template <typename base> person_impl<base>:: person_impl (const xml_schema::string& name) : base (name) { } template <typename base> person_impl<base>:: person_impl (std::auto_ptr<xml_schema::string>& name) : base (name) { } template <typename base> person_impl<base>:: person_impl (const xercesc::DOMElement& e, xml_schema::flags f, xml_schema::container* c) : base (e, f, c) { } template <typename base> person_impl<base>:: person_impl (const person_impl& x, xml_schema::flags f, xml_schema::container* c) : base (x, f, c) { } template <typename base> person_impl<base>* person_impl<base>:: _clone (xml_schema::flags f, xml_schema::container* c) const { return new person_impl (*this, f, c); } template <typename base> void person_impl<base>:: print (std::ostream& os) const { os << this->name () << std::endl; } // Explicitly instantiate person_impl class template for person_base. // template class person_impl<person_base>; // superman_impl // template <typename base> superman_impl<base>:: superman_impl (const xml_schema::string& name, bool can_fly) : base (name, can_fly) { } template <typename base> superman_impl<base>:: superman_impl (std::auto_ptr<xml_schema::string>& name, bool can_fly) : base (name, can_fly) { } template <typename base> superman_impl<base>:: superman_impl (const xercesc::DOMElement& e, xml_schema::flags f, xml_schema::container* c) : base (e, f, c) { } template <typename base> superman_impl<base>:: superman_impl (const superman_impl& x, xml_schema::flags f, xml_schema::container* c) : base (x, f, c) { } template <typename base> superman_impl<base>* superman_impl<base>:: _clone (xml_schema::flags f, xml_schema::container* c) const { return new superman_impl (*this, f, c); } template <typename base> void superman_impl<base>:: print (std::ostream& os) const { if (this->can_fly ()) os << "Flying superman "; else os << "Superman "; os << this->name () << std::endl; } // Explicitly instantiate superman_impl class template for superman_base. // template class superman_impl<superman_base>; // batman_impl // template <typename base> batman_impl<base>:: batman_impl (const xml_schema::string& name, bool can_fly, unsigned int wing_span) : base (name, can_fly, wing_span) { } template <typename base> batman_impl<base>:: batman_impl (std::auto_ptr<xml_schema::string>& name, bool can_fly, unsigned int wing_span) : base (name, can_fly, wing_span) { } template <typename base> batman_impl<base>:: batman_impl (const xercesc::DOMElement& e, xml_schema::flags f, xml_schema::container* c) : base (e, f, c) { } template <typename base> batman_impl<base>:: batman_impl (const batman_impl& x, xml_schema::flags f, xml_schema::container* c) : base (x, f, c) { } template <typename base> batman_impl<base>* batman_impl<base>:: _clone (xml_schema::flags f, xml_schema::container* c) const { return new batman_impl (*this, f, c); } template <typename base> void batman_impl<base>:: print (std::ostream& os) const { os << "Batman " << this->name () << " with " << this->wing_span () << "m wing span" << std::endl; } // Explicitly instantiate batman_impl class template for batman_base. // template class batman_impl<batman_base>; }
Again, the implementation should look familiar. The two differences compared to the previous section that are worth mentioning are explicit template instantiations and qualification of member functions with this->
. The explicit template instantiations is a nice optimization that allows us to keep the implementation details in the source file (instead of putting them into the header) and prevent object code bloat that could result from instantiating our member functions in several translation units. This technique works well in our case because for each class template we know the only template argument it will ever be instantiated with — the generated base class.
Qualification of member functions (name
, can_fly
, wing_span
) with this->
is necessary because they are declared in the base and do not have any arguments that depend on the template argument. For more information on this aspec of C++ templates see your favorite C++ book (e.g., "C++ Templates" by Vandevoorde and Josuttis).
The client code that uses our customizations is presented below:
#include <memory> // std::auto_ptr #include <iostream> #include "people.hxx" using std::cerr; using std::endl; int main (int argc, char* argv[]) { using namespace people; std::auto_ptr<catalog> c (catalog_ (argv[1])); for (catalog::person_const_iterator i (c->person ().begin ()); i != c->person ().end (); ++i) { i->print (cerr); } }
To summarize, the customization technique presented in this section consists of the following steps:
- Write
*-custom-fwd.hxx
with forward decelarations of all your class templates. - Request generation of the forward declaration file with the
--generate-forward
option. - Use the
--fwd-prologue
option to include*-custom-fwd.hxx
at the beginning of the generated forward declaration file. - Write
*-custom.hxx
with definitions of all your class templates. Include the generated forward declaration file to gain access to the generated and XML Schema built-in types. - Use the
--hxx-prologue
option to include*-custom.hxx
at the beginning of the generated header file. - Use the
--custom-type
and/or--custom-type-regex
options to request type customizations. - Write
*-custom.cxx
with implementations of all your class templates. Include the generated header file at the beginning. Use explicit template instantiation to instantiate each template with the corresponding base class.
Customizing the generated type — the complex case (using c++11)
This has only been tested with Microsoft Visual C++ Compiler Nov 2013 CTP or newer, but will most likely work with gcc 4.8 and clang 3.x
The code and lots of text in this section has shamelessly been taken from the previous section, but the previous section is not required to understand this one. The schema for this example looks as follows:
<!-- people.xsd --> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ppl="http://www.codesynthesis.com/people" targetNamespace="http://www.codesynthesis.com/people"> <xsd:complexType name="person"> <xsd:sequence> <xsd:element name="name" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="superman"> <xsd:complexContent> <xsd:extension base="ppl:person"> <xsd:attribute name="can-fly" type="xsd:boolean" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="batman"> <xsd:complexContent> <xsd:extension base="ppl:superman"> <xsd:attribute name="wing-span" type="xsd:unsignedInt" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="catalog"> <xsd:sequence> <xsd:element name="person" type="ppl:person" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:element name="catalog" type="ppl:catalog"/> </xsd:schema>
We would like to add the virtual print()
function to the base of the hierarchy (the person
type) and override it in each derived type (superman
and batman
). The first step is to compile our schema. We will use the following XSD options (explained below):
--generate-polymorphic --custom-type "person=person_impl<person_base>/person_base" --custom-type "superman=superman_impl<superman_base>/superman_base" --custom-type "batman=batman_impl<batman_base>/batman_base" --generate-forward --fwd-prologue '#include "people-custom-fwd.hxx"' --hxx-prologue '#include "people-custom.hxx"' --std c++11
We use the --generate-polymorphic
option because our instance documents will be using the xsi:type
-based dynamic typing.
The set of --custom-type
options should look familiar by now. They tell the compiler that we are customizing the person
, superman
, and batman
types, that the *_impl<*_base>
class template instantiations should be used instead, and that the original mapping should still be generated but renamed to *_base
.
The --generate-forward
option triggers generation of the people-fwd.hxx
forward declaration file. The content of the people-fwd.hxx
file will look along these lines:
// people-fwd.hxx - automatically generated namespace xml_schema { ... // Declarations for the XML Schema built-in types. } namespace people { class person_base; typedef person_impl<person_base> person; class superman_base; typedef superman_impl<superman_base> superman; class batman_base; typedef batman_impl<batman_base> batman; class catalog; }
This header file won't compile unless we supply definitions — or at least forward declarations — for the person_impl
, superman_impl
, and batman_impl
class templates. The --fwd-prologue
option does exactly that:
it adds the #include "people-custom-fwd.hxx"
line at the beginning of the generated people-fwd.hxx
. The people-custom-fwd.hxx
file (you need to create this) provides forward declarations for the above mentioned class templates:
// people-custom-fwd.hxx namespace people { template <typename base> class person_impl; template <typename base> class superman_impl; template <typename base> class batman_impl; }
Unlike the simple case, when types being customized are inherited from in the same schema file, we cannot include definitions of custom types at the end of the genearted header file. That is why we are using the --hxx-prologue
option instead of --hxx-epilogue
to include people-custom.hxx
. Since the custom type definitions are included before the generated base types (person_base
, superman_base
, and batman_base
) are defined we have to use class templates instead of plain classes as we did in the previous section. The content you need to create in people-custom.hxx
is presented below:
// people-custom.hxx #include <iosfwd> // std::ostream #include "people-fwd.hxx" namespace people { // // template <typename base> class person_impl: public base { public: using base::base; virtual person_impl* _clone (xml_schema::flags = 0, xml_schema::container* = 0) const override; public: virtual void print (std::ostream&) const; }; template <typename base> class superman_impl: public base { public: using base::base; virtual superman_impl* _clone (xml_schema::flags = 0, xml_schema::container* = 0) const override; public: virtual void print (std::ostream&) const override; }; template <typename base> class batman_impl: public base { public: using base::base; virtual batman_impl* _clone (xml_schema::flags = 0, xml_schema::container* = 0) const override; public: virtual void print (std::ostream&) const override; }; }
The "override" specifier makes sure that the compiler throws an error if the _clone function is specified wrongly. C++11 allows us to simply inherit all constructors by "using base::base;". This simplifies our classes significantly compared with the previous section. Definitions of custom types look very similar to the one we saw in the simple-case section. The only two differences are the use of templates with the base class as a parameter and the inclusion of the people-fwd.hxx
file. The latter allows us to refer to the generated as well as XML Schema built-in types, e.g., xml_schema::string
.
The implementation of our class templates is placed into the people-custom.cxx
file:
// people-custom.cxx #include <ostream> #include "people.hxx" namespace people { // person_impl // template <typename base> void person_impl<base>:: print (std::ostream& os) const { os << this->name () << std::endl; } template <typename base> person_impl<base>* person_impl<base>:: _clone (xml_schema::flags f, xml_schema::container* c) const { return new person_impl (*this, f, c); } // Explicitly instantiate person_impl class template for person_base. // template class person_impl<person_base>; // superman_impl // template <typename base> void superman_impl<base>:: print (std::ostream& os) const { if (this->can_fly ()) os << "Flying superman "; else os << "Superman "; os << this->name () << std::endl; } template <typename base> superman_impl<base>* superman_impl<base>:: _clone (xml_schema::flags f, xml_schema::container* c) const { return new superman_impl (*this, f, c); } // Explicitly instantiate superman_impl class template for superman_base. // template class superman_impl<superman_base>; // batman_impl // template <typename base> void batman_impl<base>:: print (std::ostream& os) const { os << "Batman " << this->name () << " with " << this->wing_span () << "m wing span" << std::endl; } template <typename base> batman_impl<base>* batman_impl<base>:: _clone (xml_schema::flags f, xml_schema::container* c) const { return new batman_impl (*this, f, c); } // Explicitly instantiate batman_impl class template for batman_base. // template class batman_impl<batman_base>; }
Again, the implementation should look familiar. The two differences compared to the previous section that are worth mentioning are explicit template instantiations and qualification of member functions with this->
. The explicit template instantiations is a nice optimization that allows us to keep the implementation details in the source file (instead of putting them into the header) and prevent object code bloat that could result from instantiating our member functions in several translation units. This technique works well in our case because for each class template we know the only template argument it will ever be instantiated with — the generated base class.
Qualification of member functions (name
, can_fly
, wing_span
) with this->
is necessary because they are declared in the base and do not have any arguments that depend on the template argument. For more information on this aspect of C++ templates see your favorite C++ book (e.g., "C++ Templates" by Vandevoorde and Josuttis).
The client code that uses our customizations is presented below:
#include <memory> // std::unique_ptr #include <iostream> #include "people.hxx" using std::cerr; using std::endl; int main (int argc, char* argv[]) { std::unique_ptr<people::catalog> c (people::catalog_ (argv[1])); for (people::person& person : c->person ()) { person.print (cerr); } }
To summarize, the customization technique presented in this section consists of the following steps:
- Write
*-custom-fwd.hxx
with forward decelarations of all your class templates. - Request generation of the forward declaration file with the
--generate-forward
option. - Use the
--fwd-prologue
option to include*-custom-fwd.hxx
at the beginning of the generated forward declaration file. - Write
*-custom.hxx
with definitions of all your class templates. Include the generated forward declaration file to gain access to the generated and XML Schema built-in types. - Use the
--hxx-prologue
option to include*-custom.hxx
at the beginning of the generated header file. - Use the
--custom-type
and/or--custom-type-regex
options to request type customizations. - Write
*-custom.cxx
with implementations of all your class templates. Include the generated header file at the beginning. Use explicit template instantiation to instantiate each template with the corresponding base class.
Customizing the XML Schema built-in types
This section shows how to use custom types for the XML Schema built-in types. All code in this section is taken from the calendar
example which can be found in the examples/cxx/tree/custom/
directory of the XSD distribution. The schema for this example looks as follows:
<!-- calendar.xsd --> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cal="http://www.codesynthesis.com/calendar" targetNamespace="http://www.codesynthesis.com/calendar"> <xsd:complexType name="event"> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xsd:attribute name="title" type="xsd:string" use="required"/> <xsd:attribute name="date" type="xsd:date" use="required"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> <xsd:complexType name="events"> <xsd:sequence> <xsd:element name="event" type="cal:event" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:element name="events" type="cal:events"/> </xsd:schema>
In this example we would like to map the xsd:date
built-in type to the boost::gregorian::date
class from the Boost date_time
library.
Normally, when you compile your schema, code for the XML Schema built-in types is generated inline in the resulting header file. In order to customize the built-in types we will need to generate this code into a separate header file (remember that we can only customize types from the current schema's target namespace). To achive this we will use the --generate-xml-schema
option. This option instructs XSD to generate code for the XML Schema namespace into a separate header file. The fake schema file provided to the compiler does not have to exist and is only used to derive the name of the generated header file. For more information on this option see the XSD command line interface documentation (man pages). We will use the following command line to generate the header for the XML Schema namespace:
xsd cxx-tree --generate-xml-schema --custom-type date --hxx-epilogue '#include "xml-schema-custom.hxx"' xml-schema.xsd
This will result in the xml-schema.hxx
file. We also used the --custom-type
option to customize the xsd:date
type and the --hxx-epilogue
to include xml-schema-custom.hxx
at the end of the generated xml-schema.hxx
. The xml-schema-custom.hxx
file is where we define our date
class:
// xml-schema-custom.hxx #include <boost/date_time/gregorian/gregorian.hpp> // boost::gregorian::date namespace xml_schema { class date: public simple_type, public boost::gregorian::date { public: date (const xercesc::DOMElement&, flags = 0, container* = 0); date (const xercesc::DOMAttr&, flags = 0, container* = 0); date (const std::string&, const xercesc::DOMElement*, flags = 0, container* = 0); date (const date&, flags = 0, container* = 0); virtual date* _clone (flags = 0, container* = 0) const; }; }
The date
class inherits from both xml_schema::simple_type
(all simple types should inherit from xml_schema::simple_type
and complex types from xml_schema::type
) and boost::gregorian::date
.
The implementation of the date
class is placed into the xml-schema-custom.cxx
file:
// xml-schema-custom.cxx #include <xsd/cxx/xml/string.hxx> // xml::transcode #include "xml-schema.hxx" using namespace boost; using namespace boost::gregorian; namespace xml = xsd::cxx::xml; namespace xml_schema { date:: date (const xercesc::DOMElement& e, flags f, container* c) : simple_type (e, f, c), gregorian::date ( from_simple_string ( xml::transcode<char> (e.getTextContent ()))) { } date:: date (const xercesc::DOMAttr& a, flags f, container* c) : simple_type (a, f, c), gregorian::date ( from_simple_string ( xml::transcode<char> (a.getValue ()))) { } date:: date (const std::string& s, const xercesc::DOMElement* e, flags f, container* c) : simple_type (s, e, f, c), gregorian::date (from_simple_string (s)) { } date:: date (const date& d, flags f, container* c) : simple_type (d, f, c), gregorian::date (d) { } date* date:: _clone (flags f, container* c) const { return new date (*this, f, c); } }
As in the previous sections, we include xml-schema.hxx
, not xml-schema-custom.hxx
. We also use the transcode
function provided by the XSD runtime in order to convert element and attribute values from Xerces-C++
encoding (UTF-16) to the current code page.
We don't need to do anything special when compiling our schema except for using the --extern-xml-schema
option to instruct XSD to include the previously generated xml-schema.hxx
instaed of generating the XML Schema namespace inline:
xsd cxx-tree --extern-xml-schema xml-schema.xsd calendar.xsd
We use the same fake XML Schema file name (xml-schema.xsd
) as an argument to the --extern-xml-schema
option as we used to generate xml-schema.hxx
.
The client code for this example is presented below:
// driver.cxx #include <memory> // std::auto_ptr #include <iostream> #include "calendar.hxx" using std::cerr; using std::endl; int main (int argc, char* argv[]) { using namespace calendar; std::auto_ptr<events> c (events_ (argv[1])); for (events::event_const_iterator i (c->event ().begin ()); i != c->event ().end (); ++i) { cerr << i->date () << " " << i->title () << endl << *i << endl; } }
In this example, the operator<<
operator provided by the boost::gregorian::date
class is used to print dates in the calendar.
The xsd:anyType
type, which is mapped to xml_schema::type
, is a base type for every generated and built-in type in the C++/Tree mapping. Because of this property it is often useful to customize this type in order to provide a common functionality to all types in an object model. One special requirement when customizing anyType
is that you should always inherit your version from the default implementation. For an example on how to customize anyType
refer to the comments
example which can be found in the examples/cxx/tree/custom/
directory of the XSD distribution.
Additional examples
The following additional examples, found in the examples/cxx/tree/custom/
directory, show various applications of the type customization mechanism:
- The
wildcard
example shows how to customize type's parsing constructor and serialization operator to handle thexsd:anyAttribute
wildcard.
- The
comments
example shows how to customize theanyType
built-in type in order to retain XML comments in the object model.
- The
double
example shows how to customize parsing and serialization code for thexsd:double
XML Schema built-in type. It can be used as a guide on how to customize built-in XML Schema types that are mapped to fundamental C++ types.