Complex Types
HydraExpress represents each
complexType defined by the schema as a class. The name of the class is the name of the
complexType or the name of the
element that contains the
complexType, modified according to the rules described in
“Names and Identifiers” . HydraExpress creates a header file and a source file for each class. The files are named after the class with the appropriate extension added (
.h for header files,
.cpp for source files).
Each generated complexType class has a corresponding marshaling class whose name is the same but with “Marshal” appended. For example, the generated class SomeClass has the corresponding class SomeClassMarshal.
The generated complexType class contains unmarshal methods that populate an instance of the class from an XML document, and marshal methods that write out the class as an XML document. These methods call the associated generated marshaling class, which performs the marshaling.
HydraExpress represents the elements and attributes contained by the complexType as data members of the generated class. The generated class contains an accessor and a mutator for each data member. For elements or attributes that have minOccurs="0", the generated class also provides a method to query whether the class contains an instance of the element or attribute.
A generated class provides a default constructor and an equality operator, which create shallow copies, and a
clone() method that creates a deep copy of the handle. (See
“Working with the Handle/Body Classes.”) The class inherits a
virtual destructor and an assignment operator from the handle base class.
Accessors and Mutators
HydraExpress generates accessor and mutator methods for each child element and any attributes defined by the complex type definition. The method names follow conventional naming practices. For example, when a type contains a child element named thing HydraExpress generates an accessor named getThing and a mutator named setThing.
Marshal and Unmarshal
For each complex type, HydraExpress generates two marshal methods and four unmarshal methods. The marshal methods produce an XML element from the object. The unmarshal methods populate the object from an XML element contained in a string.
These marshal methods all call the generated marshaling class which actually performs the marshaling.
Marshal methods
The first overload of marshal takes two optional parameters:
std::string
marshal(bool includeChildTypeAttributes = false,
const rwsf::XmlName& elementName=DefaultElementName,
bool nilValue = false) const;
When includeChildTypeAttributes is true, the children of this element each contain an xsi:type attribute. The exact signature of this method depends on the type mapping used for xsd:string. For example, in classes generated with the ‑SourcePro flag, the method returns an RWCString.
The elementName parameter sets the name of the element. By default, the element uses the DefaultElementName created by the code generator. For a class that represents a top-level element, the DefaultElementName is the name of the element. For classes that represent a datatype, the DefaultElementName is the name of the datatype.
For the implications of the
nilValue parameter, see
“nillable Elements.”)
The second overload of marshal requires a writer instance as the first parameter:
void
marshal(rwsf::XmlWriter& writer,
bool includeChildTypeAttributes = false,
const rwsf::XmlName& elementName=DefaultElementName,
bool includeSelfTypeAttribute = false,
bool nilValue = false) const;
This method marshals the element into the provided
rwsf::XmlWriter. The
includeChildTypeAttributes parameter and the
elementName parameter behave as described above for the first overload of
marshal. The method includes an
xsi:type attribute on the element itself when
includeSelfTypeAttribute is
true. For the implications of the
nilValue parameter, see
“nillable Elements.”)
Unmarshal methods
An
unmarshal method populates an object with data from an XML document instance. The methods throw an
rwsf::XmlParseException when the XML document contains serious structural problems. The methods ignore minor defects. For example, an
unmarshal method silently ignores attributes not mentioned in the schema, but throws an exception if a required attribute is not present.
HydraExpress creates two static unmarshal methods, and two instance unmarshal methods. The static methods allow HydraExpress to unmarshal a derived class as a base class without determining the precise type of the class ahead of time.
The first static unmarshal method unmarshals XML content from an input string, using the specific (but optional) element name:
static void
unmarshal(const std::string& input, elementType& element,
const rwsf::XmlName& elementName = DefaultElementName);
This method unmarshals content into the element provided, using the elementName provided.
The elementType is the type of the class that contains the method. The type of the input argument is the type mapped to xsd:string. For example, in classes generated with the ‑SourcePro flag, the method accepts an RWCString as the first argument.
The second
static unmarshal method unmarshals an element from the provided
rwsf::XmlReader into the provided
element:
static void
unmarshal(rwsf::XmlReader& reader, elementType& element,
const rwsf::XmlName& elementName = DefaultElementName);
This method unmarshals content from the reader into the element provided, using the elementName provided. The elementType is the type of the class that contains the method.
The instance unmarshal methods have signatures similar to the static functions. Instance methods simply forward *this and the parameters provided to the static methods.
HydraExpress unmarshal methods convert the contents of the XML document to UTF-8, regardless of the original encoding of the document. The parser determines the original encoding of the document via the encoding declaration in the prolog, or by inspecting the first few bytes of the document if no encoding is declared.
Note that if HydraExpress determines that the document is UTF-8, then the parser does not perform character conversion on the document. See the class reference entry on
rwsf::XmlReader for details on the character sets that HydraExpress supports.
Types Derived By Restriction or Extension
For complex types derived by restriction or extension, HydraExpress generates a class that inherits from the class that represents the base type.
For types derived by restriction, the derived class overrides the marshal and unmarshal methods to marshal only the elements and attributes present in the derived type.
For types derived by extension, the derived class holds the data that is not present in the base type. The derived class overrides the marshal and unmarshal methods to correctly marshal the derived type.
The accessors for methods that return polymorphic types return the base type. The
getTypeId() returns the name of the type. To ensure that your logic behaves properly according to the actual type of the object, invoke the method
getTypeId() before casting the object to the derived type. See
“Accessing the Address” for an example.
Specific Support for Types Derived by Restriction
The following details HydraExpress’s level of support for complex schema types derived by restriction.
Supported:
• Types that use restriction with more restrictive facets, such as restricting the value set of a type (e.g., restricting an xsd:integer to values between 1 and 10).
• Restrictions that change the schema type, but do not result in a change in the C++ mapped type (e.g., restricting an xsd:integer to an xsd:positiveInteger).
Unsupported:
• Prohibiting previously optional types.
• Restricting maxOccurs from a value greater than one (including unbounded) to one occurrence.
• Restrictions that change the C++ mapped type, such as restricting from anyType.
any Element and anyAttribute
An any element declaration represents an XML element of any name and any type. HydraExpress can make no assumptions about the correct data binding for the contents of an any element. Therefore, HydraExpress maps an any declaration to the type mapped to xsd:string, typically std::string or RWCString. The unmarshal method of the class makes no attempt to interpret the content of the any element. The contents of that element are simply copied into the string. HydraExpress uses the name Any for an any element. The accessor for the element is named getAny and the mutator is named setAny.
If a complex type definition has an anyAttribute declaration, then any attributes that meet the specified criteria will be added to an attribute set. These attributes can be accessed and set using the getAnyAttribute() or setAnyAttribute() methods, respectively.
Optional Elements and Attributes
If an element declaration has a minOccurs attribute of 0, that element is optional. An attribute is optional unless the attribute declaration has a use attribute of required or prohibited.
For optional elements and attributes, HydraExpress generates a method named is<identifier>Set. The method returns the state of a bool maintained by the class. The default constructor initializes the bool to false. When an application sets a value for an optional element or attribute, the bool is set to true. HydraExpress also generates a set<identifier>Set method that directly sets the state of the bool.
The marshal function does not write out an element or attribute if the bool specifying whether the element or attribute has been set is false.
The unmarshal function sets the bool to true if the attribute or element exists in the XML document passed to the function.
nillable Elements
An element that contains the attribute nillable="true" may be represented in an instance document as either an empty element or an element that holds the content specified in the schema. For example, the declaration below specifies that an element of type NillableDecimal may either be empty or contain an xsd:decimal value:
...
<element name="NillableDecimal" type="xsd:decimal"
nillable="true"/>
...
HydraExpress creates a pair of boolean methods for working with nillable elements. The is<identifier>ValueSet method returns true if the element holds a value, or false if it is null. The set<identifier>ValueSet method sets the state directly.
When <identifier> is represented by a class created by HydraExpress, calling set<identifier> updates is<identifier>ValueSet. That is, setting the value to an object that is nil sets is<identifier>ValueSet to false, while setting the value to an object that is not nil sets is<identifier>ValueSet to true. For elements that are represented by built-in types, setting the value does not change the state of is<identifier>ValueSet.
An element may be optional as well as being nil. In that case, the is<identifier>Set method determines whether the marshal method produces an element. The is<identifier>ValueSet method determines whether the element produced is empty.
Notice that whether an element is omitted, marshaled as a nil value, or fully marshaled depends on the state of is<identifier>Set (if present) and is<identifier>ValueSet in the containing object. This produces the correct behavior in most cases. Notice, however, that it is possible for the contained element to be inconsistent with the is<identifier>ValueSet flag, as shown in the samples below:
PurchaseOrder po;
Comments comments; // By default, contains no
// value for xsi:nil
po.setComments(comments); // Updates isCommentsValueSet
// to true
po.getComments.setXsiNil(true); // Error! does not update
// isCommentsValueSet
For collections, as described in the next section, the is<identifier>ValueSet flag is implemented as a std:vector of flags.
Collections
HydraExpress defines a collection as an element that has a maxOccurs attribute greater than 1 or an element that has a minOccurs attribute greater than 1 and no maxOccurs attribute. HydraExpress represents a collection as a std::vector specialized on the type of the element. Note that the marshal function does not enforce minOccurs and maxOccurs constraints on a collection.
Lists
Whenever HydraExpress encounters an XML Schema list definition, it maps the list to a std::vector specialized on the type contained by the list. HydraExpress creates a convenience typedef for the list. The accessor and mutator for the list use the typedef.
Substitution Groups
An XML Schema substitutionGroup defines one or more elements that may be used interchangeably for another element. The substitutable elements must be of the same type as, or a derived type of, the base element type. For example, elements USDate and EuroDate could be defined as belonging to the substitutionGroup for the base element Date. Anywhere in an instance document where the schema allows a Date, the allowable elements become Date, USDate, and EuroDate.
For each defined substitutionGroup, HydraExpress creates get and set methods on the base element. For the above example, the methods getDate() and setDate() would be generated. The get element returns the element value as an instance of the base element type. The set method sets the element value as an instance of the base element type.
Because the actual element name could be the name of the base element or of one of the allowable substitution elements, HydraExpress generates methods to get and set the element name. For the above example, these would be getDateName() and setDateName(). The getDateName() method could return the strings “Date”, “USDate”, or “EuroDate”, depending on the actual element name in the instance document. The setDateName() would be used to set the correct element name before marshaling an instance document.
Unions
An XML Schema union defines a collection of types that can then be specified for an element. For example, the union paymentTypes could contain the member types purchaseOrderReference and creditCardNumber. The element payment could then be defined as being of type paymentTypes, meaning that the payment element could contain either a reference to a purchase order or a credit card number.
There is no requirement that the types in a union be the same or even similar types. HydraExpress makes no attempt determine exactly what types are in the union. An element specified with a union type has get and set methods that return and set the value with the type mapped for xsd:string, typically std::string or RWCString.
Key and Keyref Elements
HydraExpress provides limited support for the key and keyref schema elements. These schema elements serve two main purposes:
• referencing an element defined by a key using a keyref
• guaranteeing uniqueness of the value or set of values defined by a key
The HydraExpress implementation supports only the first purpose. Uniqueness of the values defined by the key element is not guaranteed. Furthermore, the XML Schema element unique, whose sole purpose is to guarantee uniqueness of a set of values, is not supported in any way.
To understand the key and keyref elements, let’s take as an example the purchase order schema’s Items element:
<xsd:complexType name="Items">
<xsd:sequence>
<xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="productName" type="xsd:string"/>
<xsd:element name="quantity">
<xsd:simpleType>
<xsd:restriction base="xsd:positiveInteger">
<xsd:maxExclusive value="100"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="USPrice" type="xsd:decimal"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="partNum" type="SKU" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
The Items element contains a sequence of item elements. These elements contain information about the purchase of a particular item in a particular purchase order. But perhaps this information is not sufficient to determine whether it is possible to fulfill the order. For example, there is no information about the number of such items currently held in inventory. How can we access this additional information?
There might exist another datatype called Inventory, which also has an Items element containing a sequence of item elements. The Inventory contains item elements for every product the company sells, and these elements might contain much more information than the item elements in the purchase order. It would be convenient, therefore, to have an easy way to access the corresponding inventory item element from a purchase order item.
We could create a keyref like this:
<key name="inventoryItems">
<selector xpath="Inventory/Items/item"/>
<field xpath="@partNum"/>
</key>
The XPath expression defined by the xpath attribute of the selector element produces a result node set of all the item elements in the Inventory. The xpath attribute of the field element specifies the value used to access a particular item element in the result node set. Of course, to be sure of getting the right item element, the attribute value partNum should be unique, but keep in mind that HydraExpress does not guarantee this uniqueness.
The items in a PurchaseOrder could then be linked to items in the Inventory through a keyref:
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="USAddress"/>
<xsd:element name="billTo" type="USAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="items" type="Items">
<keyref name="poItems" refer="inventoryItems">
<selector xpath="item"/>
<field xpath="@partNum"/>
</keyref>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>
For each
keyref, HydraExpress generates a
getnameKeyRef() and a
setnameKeyRef() method. For the above example, these would be
getpoItemsKeyRef() and
setpoItemsKeyRef() methods. The
get method would return the corresponding
item from
Inventory, as a
rwsf::XmlBindingHandle instance. This object could be used to retrieve inventory data useful in processing the purchase order. The
set method sets the
item in
Inventory to the
rwsf::XmlBindingHandle instance provided. This might be used, for example, to change the
item instance in the
Inventory to reflect the new value for quantity on hand after deducting the number of items needed to fulfill the purchase order.
Content Model Groups
XML Schema supports constraints on a group of elements. Grouping may occur within a complexType or within a standalone group element that is then referenced from within a complexType.
A complexType element or a group element may contain the following constraints:
• One or more choice elements. This element declares that the document may contain one, and only one, of the types listed in the choice.
• One or more sequence elements. This element requires that the document contain the content types specified in the sequence. The names and the order of the content types must match the order in which they appear in the sequence.
• An all element. The types specified in an all element may occur in any order. An all element cannot be combined with other elements. That is, an element that has an all element as a child may not have any other children.
Note that the all, sequence, and choice elements themselves may have any value for minOccurs and maxOccurs. The code that HydraExpress generates depends on the elements within the group and the value of the minOccurs and maxOccurs attributes.
Single occurrence all or sequence elements
When a sequence element or all element has minOccurs of 1 and maxOccurs of 1 (the default), HydraExpress does not explicitly represent the sequence or all element. Instead, HydraExpress includes the content of the group in the class definition. In other words, for the schema fragment below:
<complexType name="Book">
<sequence>
<element name="author" type="string"/>
<element name="title" type="string"/>
</sequence>
</complexType>
HydraExpress creates a Book class with the accessor methods getAuthor and getTitle and the mutator methods setAuthor and setTitle. There is no need to explicitly represent the sequence constraint.
Single occurrence or optional choice element
For a choice element that has a minOccurs of 1 or 0 and a maxOccurs of 1, HydraExpress generates an enumeration that lists the allowable choices. The class that contains the choices also contains an accessor to specify which choice is selected in a given object. The schema below illustrates a simple example:
<complexType name="userName">
<choice>
<element name="fullName" type="string" />
<element name="lastName" type="string" />
<element name="nickName" type="string" />
</choice>
</complexType>
The generated class will have the following enum definition and accessor method:
enum choice {choiceUnknown, FullNameChoice,
LastNameChoice,NickNameChoice};
choice getChoice();
The generated UserName class also contains accessors and mutators for the fullName, lastName, and nickName elements. An instance of the UserName class may contain all of the elements simultaneously.
The first time a choice is encountered for a type, HydraExpress uses the name choice to represent the choice. This provides a simple mapping for typical schemas that we have seen. If more than one choice appears in the same type definition, HydraExpress uses the name choice2. Subsequent choices increment the number at the end of the name.
NOTE >> If an element exists named “choice”, HydraExpress renames the choice element to choiceElement1.
The marshal/unmarshal logic relies on the choice member of the class to determine which element should be set (on unmarshal) or written out (on marshal). Each time an element is set with a different value, if it belongs to a choice content model definition, the choice member will be automatically updated to reflect that the element last set is the one that should be written out. This can be manually overridden by the user by calling the mutator method directly.
Multiple occurrence or optional all and sequence, multiple occurrence choice
HydraExpress generates an additional class when a sequence or all has a minOccurs of 0 or a maxOccurs greater than 1, or when a choice has a maxOccurs greater than 1. In this case, the name of the new class is formed using the name of the element that contains the group, the type of the group constraint, and the word Type. If more than one group constraint of the same type occurs within the same element, the second class has the number 2 appended to the name. Subsequent groups of the same type increment the number.
For example, given the schema fragment below:
<complexType name="BookLocations">
<sequence maxOccurs="unbounded">
<element name="Location" type="string"/>
<element name="InStock" type="int"/>
</sequence>
<attribute name="ISBN" type="string"/>
</complexType>
HydraExpress creates a class named BookLocations and a class named BookLocationsSequenceType. Class BookLocationsSequenceType represents a single occurrence of the sequence in the BookLocations type. BookLocationsSequenceType provides the accessor methods getLocation and getInStock and the mutator methods setLocation and setInStock. Class BookLocations contains a vector of BookLocationsSequenceType objects and provides accessors and mutators for that vector. Class BookLocations typedefs BookLocationsSequenceTypeVector to the actual type of the vector.
Top-level groups
XML Schemas can define a top-level group, and then use references to the group elsewhere in the schema. Here is an example:
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://roguewave.com/examples/xmlbinding"
xmlns:tns="http://roguewave.com/examples/binding">
<group name="Measurement">
<choice>
<element name="Miles" type="float"/>
<element name="Kilometers" type="float"/>
</choice>
</group>
<complexType name="Distance">
<sequence>
<element name="City1" type="string"/>
<element name="City2" type="string"/>
<group ref="tns:Measurement"/>
</sequence>
</complexType>
</schema>
For the above schema, the Distance class is generated with these functions:
• [get|set]City1(), [get|set]City2(), [get|set]Miles(), and [get|set]Kilometers()
• getChoice() because of the choice construct
Here is a more complex example that has complex types under the model group:
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://roguewave.com/examples/xmlbinding"
xmlns:tns="http://roguewave.com/examples/xmlbinding">
<group name="Measurement">
<sequence>
<element name="Inches">
<complexType>
<sequence><element name="val" type="string"/></sequence>.
</complexType>
</element>
</sequence>
</group>
<complexType name="Width">
<sequence>
<element name="Object">
<complexType>
<sequence><element name="val" type="string"/></sequence>.
</complexType>
</element>
<group ref="tns:Measurement"/>
</sequence>
</complexType>
<complexType name="Length">
<sequence>
<element name="Object">
<complexType>
<sequence><element name="val" type="string"/></sequence>.
</complexType>
</element>
<group ref="tns:Measurement"/>
</sequence>
</complexType>
</schema>
The generated output for the above schema would have:
• A MeasurementInches class with the functions:
— [get|set]Val()
• A Width class with the functions:
— [get|set]WidthObject()
— [get|set]MeasurementInches()
• A WidthObject class with the functions:
— [get|set]Val()
• A Length class with the functions:
— [get|set]LengthObject()
— [get|set]MeasurementInches()
• A LengthObject class with the functions:
— [get|set]Val()
Since there are five complex types (Length, Width, the anonymous complex type declarations for the two Object elements, and the Inches element in the Measurement model group), HydraExpress generates five classes. As described in the preceding sections, the classes MeasurementInches, LengthObject, and WidthObject represent the elements, since the complex types are anonymous declarations. The Width and Length classes represent the two named complex types.
The
MeasurementInches,
LengthObject, and
WidthObejct classes are named the way they are because they are child elements of another element, complex type, or model group. Thus the class name is formed by concatenating the parent name and the element name (see
“Names and Identifiers” ).
Finally, the MeasurementInches class is used from within both the Width and Length classes because both types reference the same model group.
Attribute Groups
HydraExpress supports attribute groups. HydraExpress does not generate a class for the attribute groups since attributes can only be simple types. Instead it expands the attribute group in the complex type definition that is referencing the attribute group. An example of the mapping for this is shown in example below:
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://roguewave.com/examples/xmlbinding"
xmlns:tns="http://roguewave.com/examples/xmlbinding">
<element name="Book">
<complexType>
<sequence>
<group ref="tns:BookElements"/>
</sequence>
<attributeGroup ref="tns:BookAttributes"/>
</complexType>
</element>
<group name="BookElements">
<sequence>
<element name="Title" type="string" minOccurs="1"
maxOccurs="1"/>
<element name="Author" type="string" minOccurs="1"
maxOccurs="unbounded"/>
<element name="Date" type="string" minOccurs="1"
maxOccurs="1"/>
<element name="ISBN" type="string" minOccurs="1"
maxOccurs="1"/>
<element name="Publisher" type="string" minOccurs="1"
maxOccurs="1"/>
</sequence>
</group>
<attributeGroup name="BookAttributes">
<attribute name="CoverType" type="string"/>
<attribute name="Height" type="float"/>
<attribute name="Width" type="float"/>
<attribute name="NumberOfPages" type="int"/>
</attributeGroup>
</schema>
Only one complex type is defined in the schema, so only one class is generated: Book. This class would contain the following methods:
• [get|set]CoverType(), [is|set]CoverTypeSet()
• [get|set]Height(), [is|set]HeightSet()
• [get|set]Width(), [is|set]WidthSet()
• [get|set]NumberOfPages(), [is|set]NumberOfPagesSet()
• [get|set]Title()
• [get|set]Date()
• [get|set]ISBN()
• [get|set]Publisher()
• [get|set]AuthorVector()
Attributes, as previously noted, are always simple types, so HydraExpress does not generate a class for them, or for the attribute group. The attributes in the attribute group are treated like any other attributes, with accessor methods and is...Set() and set...Set() methods for determining whether an attribute is present in the instance document.