Search code examples
xmldesign-patternsxsdreusability

Reusing XML schema elements/types without child complextypes


I have defined a schema of types that are commonly used in many other schemas.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://www.exampleURI.com/MyTypes" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mytypes="http://www.exampleURI.com/MyTypes">
    <xs:complexType name="Order">
        <xs:sequence>
            <xs:element name="Customer" type="mytypes:Customer" minOccurs="1" maxOccurs="1"/>
            <xs:element name="Date" type="xs:date" minOccurs="1" maxOccurs="1"/>
            <xs:element name="Products" type="mytypes:Products" minOccurs="1" maxOccurs="1"/>
            <xs:element name="Total" type="xs:decimal" minOccurs="1" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Product">
        <xs:sequence>
            <xs:element name="Name" type="xs:string" minOccurs="1" maxOccurs="1"/>
            <xs:element name="Variation" type="mytypes:Variation" minOccurs="0" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Variation">
        <xs:sequence>
            <xs:element name="Color" type="xs:string" minOccurs="1" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Customer">
        <xs:sequence>
            <xs:element name="Discount" type="xs:int" minOccurs="1" maxOccurs="1"/>
            <xs:element name="Name" type="xs:string" minOccurs="1" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Products">
        <xs:sequence>
            <xs:element name="Product" type="mytypes:Product" minOccurs="1" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

Often I would like to reuse these types, but not with all their ComplexType subelements. For example the following OrderList response has to contain Order and Customer data, but not Product data. When I reference the Order type from previous schema, it will include Order with all it's subelements.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://www.exampleURI.com/OrderList" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:s1="http://www.exampleURI.com/OrderList" xmlns:mytypes="http://www.exampleURI.com/MyTypes">
    <xs:import namespace="http://www.exampleURI.com/MyTypes" schemaLocation="types.xsd"/>
    <xs:element name="OrderListResponse" type="s1:OrderListResponse"/>
    <xs:complexType name="ResponseData">
        <xs:sequence>
            <xs:element name="id" type="xs:long" minOccurs="1" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Orders">
        <xs:sequence>
            <xs:element name="Order" type="mytypes:Order" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="OrderListResponse">
        <xs:sequence>
            <xs:element name="Orders" type="s1:Orders" minOccurs="1" maxOccurs="1"/>
            <xs:element name="Responsedata" type="s1:ResponseData" minOccurs="1" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

My goals:

  • Define the OrderListResponse so that only SimpleType elements are returned under Order.
  • Add only Order.Customer ComplexType to the response.
  • When I'm making backwards compatible changes to MyTypes schema (for example a new optional element), then they should be included in OrderListResponse only if they are SimpleTypes (string, date, etc).

Is there any kind of patter to achieve these goals or should I simply copy-paste the MyTypes schema types to all other schemas and remove unwanted subelements?


Solution

  • If I'm understanding your issue correctly, the decorator pattern sounds like it would address your needs, which you can implement in XML using the extension element. Start by defining an OrderSimple type containing the simple elements that will be shared. Then create an OrderComplex type that extends OrderSimple to add the complex elements you've defined. Of course, you can name these however you want. So for example:

    <xs:complexType name="OrderSimple">
        <xs:sequence>
            <xs:element name="Date" type="xs:date" minOccurs="1" maxOccurs="1"/>
            <xs:element name="Total" type="xs:decimal" minOccurs="1" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="OrderComplex">
        <xs:complexContent>
            <xs:extension base="OrderSimple">
                <xs:sequence>
                    <xs:element name="Customer" type="mytypes:Customer" minOccurs="1" maxOccurs="1"/>
                    <xs:element name="Products" type="mytypes:Products" minOccurs="1" maxOccurs="1"/>
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>
    

    Then all you would have to do is reference the type that's appropriate for the responses you are constructing.

    An alternative method might be to simply set the minOccurs attribute of the complexTypes so they can be null in the response, depending on how "pure" you want your response object(s) to be.