Tree/Customization guide

From Code Synthesis Wiki

(Difference between revisions)
Jump to: navigation, search
Revision as of 17:16, 26 September 2006
Boris (Talk | contribs)

← Previous diff
Revision as of 17:24, 26 September 2006
Boris (Talk | contribs)
(Customizing the generated type - the complex case)
Next diff →
Line 335: Line 335:
class catalog; class catalog;
 + }
 +
 +This header file won't compile unless we supply the definitions or at least forward declarations of 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 content of the <code>people-custom-fwd.hxx</code> file is straightforward:
 +
 + // people-custom-fwd.hxx
 +
 + namespace people
 + {
 + template <typename base>
 + class person_impl;
 +
 + template <typename base>
 + class superman_impl;
 +
 + template <typename base>
 + class batman_impl;
} }

Revision as of 17:24, 26 September 2006

Note: this guide is a work in progress

Introduction

XSD provides you with mechanisms to customize the generated type system for the C++/Tree mapping. Common customization examples include:

  • using a different type for one of the built-in types (e.g., boost::gregorian::date from the Boost libraries for xsd:date)
  • adding a member function or data member to one or more generated types (e.g., add a print() function)
  • adding virtual functions to the base type of a type hierarchy and implementing them in the derived types (e.g., when using xsi:type dynamic typing and/or substitution groups)

XSD provided 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 XML Schema. The name is unqualified since the target namespace of the schema being compiled is always assumed. The optional type component is a C++ type name that should be used instead of the generated type. If type is empty or not specified then the same name as the XML Schema type 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 we compile the above schema with the following option:

--custom-type names

The generated code will look like this:

namespace hello
{
  class names;
}

The compiler simply generated a forward declaration for names expecting you to provide the implementation. Let's say we want to use the my_names type instead. Then we can use the following option:

--custom-type names=my_names

The generated code will 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 the generated one:

--custom-type names=/names_base

This will result in the following C++ code:

namespace hello
{
  class names_base;
  class names;

  class names_base
  {
    ...
  };
}

In our implementation of the names class we can use names_base as the 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 regular expressions to match several 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 the complete set of steps necessary to do a simple customization. All the code in this section is taken from the contacts example which can be found in the examples/cxx/tree/custom/contacts directory of the XSD distribution. Our schema looks like this:

<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 generated 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 the compiler that we are providing our own implemntation for the contact type as well instructs it to generate the standard mapping with the name contact_base. 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 will define our own 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::type* = 0);

    contact (const contact&,
             xml_schema::flags = 0,
             xml_schema::type* = 0);

    virtual contact*
    _clone (xml_schema::flags = 0,
            xml_schema::type* = 0) const;

    // Our customizations.
    //
  public:
    void
    print (std::ostream&) const;
  };
}

The implementation of our 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::type* container)
      : contact_base (e, f, container)
  {
  }

  contact::
  contact (const contact& c,
           xml_schema::flags f,
           xml_schema::type* container)
      : contact_base (c, f, container)
  {
  }

  contact* contact::
  _clone (xml_schema::flags f,
          xml_schema::type* container) const
  {
    return new contact (*this, f, container);
  }

  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 some 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 is also a base for some other type in the same schema file. The 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 the code in this section is taken from the taxonomy example which can be found in the examples/cxx/tree/custom/taxonomy directory of the XSD distribution. The schema for this example looks as follows:

<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 the --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 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 the definitions or at least forward declarations of 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 content of the people-custom-fwd.hxx file is straightforward:

// people-custom-fwd.hxx

namespace people
{
  template <typename base>
  class person_impl;

  template <typename base>
  class superman_impl;

  template <typename base>
  class batman_impl;
}
Personal tools