A Mapping of XML Schema Types To C#: 1 General Design Guidelines
A Mapping of XML Schema Types To C#: 1 General Design Guidelines
Wolfgang Gehring
In the following sections, we will show how each of the main constructs of
XML Schema can be mapped into C#. We will start with elements at top-level in
various scenarios, then look at particles, and then at nested elements in different
contexts. We will conclude with the mapping of complex elements and a quick
look at substitution groups.
It is assumed that the reader is familiar with XML Schema and with C#.
2
2 Top-level elements
Top-level elements are always mapped into classes. The next few sections de-
scribe the different possible types and content models of top-level elements to-
gether with their corresponding mappings by means of simple examples.
<schema ...>
<xs:element name="Price" type="xs:integer"/>
</schema>
becomes
class Price {
int _Content;
public int Content {
get{ return _Content; }
set{ _Content = value; }
}
}
becomes
class Price {
int _Content;
public int Content {
get{ return _Content; }
set{
if (value < 0)
throw( new(ArgumentOutOfRangeException()) );
else
3
_Content = value;
}
}
}
The validation of the restriction on the type is hidden in the property’s set-
method.
<xs:schema ...>
<xs:element name="Vehicle"/>
</xs:schema>
becomes
class Vehicle{
object _Content;
public object Content {
get{ return _Content; }
set{ _Content = value; }
}
}
}
<xs:schema ...>
<xs:element name="Name">
<xs:complexType>
...
</xs:complexType>
</xs:element>
</xs:schema>
class Name {
// translation of the content:
struct {
...
5
}
// Property for the content of the complex type
...
// Properties for the public interface of the complex type
<xs:schema ...>
<import book.xsd/>
<xs:element name="Fiction" type="book"/>
<xs:element name="NonFiction" type="book"/>
</xs:schema>
becomes
class Fiction {
book _Content;
public book Content {
get{ return _Content; }
set{ _Content = value; }
}
}
// Properties for the public interface of book
.....
} class NonFiction {
book _Content;
public book Content {
get{ return _Content; }
set{ _Content = value; }
}
}
struct P {
// recursive translation of all the particles inside P
// properties for every element and for every particle in the scope of P
6
Simple element here means: every element except those with anonymous
complex type, because those get mapped into classes (see section 2.4 and section
7), all other nested elements get mapped into fields.
For every propery we put [] to denote an array type iff minOccurs and max-
Occurs are not both equal to 1. The reason is obvious for maxOccurs > 1; if
minOccurs = 0, we want to have an array because in the case that the occurence
is actually = 0, we can have a null reference even for value types. Note that if
P is a <choice>, we want to put the [] on every element/property, because the
choice implies minOccurs = 0 on every element inside the choice.
– Example 1. General case of a sequence (with the default value 1 for minOc-
curs and maxOccurs)
<xs:element name="Book">
<xs:complexType>
<xs:sequence>
<xs:element name="Author" type="xs:string"/>
<xs:element name="Title" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
becomes
class Book {
struct sequence {
string _Author; // in the remainder of the document we
// will omit these for clarity
public string Author {
get{ return _Author; }
set{ _Author = value; }
}
string _Title;
public string Title {
get{ return _Title; }
set{ _Title = value; }
}
}
// Property for the content of the complex type
sequence _Content;
sequence Content {
get{ return _Content; }
set{ _Content = value; }
}
// Properties for the public interface of the complex type
[System.Xml.Serialization.XmlElementAttribute("Author")]
public string Author {
7
[System.Xml.Serialization.XmlElementAttribute("Author")]
string[] Author {get;}
[System.Xml.Serialization.XmlElementAttribute("Title")]
string[] Title {get;}
}
The difference between this example and example 1 is that the struct se-
quence[] as well as the (element-)properties outside of the struct are now
of array type. The (element-)properties inside the struct become an array
type iff not both minOccurs and maxOccurs on the corresponding element
are equal to 1. Also note that when we have an array, we only put get;, not
set; on the properties outside of the struct. This is because the elements
inside a sequende can only appear together, so in this example Author and
Title can only be paired. It is the Content-property’s task to ensure that
this constraint is not violated.
– Example 3. <Choice>
8
<xs:element name="Temperature">
<xs:complexType>
<xs:choice>
<xs:element name="Celsius" type="xs:integer" />
<xs:element name="Fahrenheit" type="xs:integer" />
</xs:choice>
</xs:complexType>
</xs:element>
becomes
class Temperature {
struct choice {
int[] Celsius {get;}
int[] Fahrenheit {get;}
}
choice content {get; set;};
[System.Xml.Serialization.XmlElementAttribute("Celsius")]
int[] Celsius {get;}
[System.Xml.Serialization.XmlElementAttribute("Fahrenheit")]
int[] Fahrenheit {get;}
}
<all> is a special case. The elements inside the <all> can appear in an arbitrary
order in the instance document; however, it is important which order was chosen.
becomes
string name;
inside the struct, but there is also a property added outside of the struct:
9
[System.Xml.Serialization.XmlElementAttribute("name")]
string name{get; set;}
The custom attribute holds the name of the element. It could be omitted in this
case, but it is important in cases where there are name conflicts. These can occur
because in XML it is possible to have a sequence with the same element twice,
but in a C# class two fields of the same name are prohibited. In such a case we
can resolve the name conflict by introducing an array that holds the clashing
elements. Thus, e.g.
becomes
[System.Xml.Serialization.XmlElementAttribute("name")]
string name0;
[System.Xml.Serialization.XmlElementAttribute("name")]
string name1;
string[] name {get;}
[System.Xml.Serialization.XmlElementAttribute("name")]
string[] name {get;}
<xs:element ref="author"/>
becomes
author author0;
string author {get; set;}
[System.Xml.Serialization.XmlElementAttribute("author")]
string name {get; set;}
Example:
personType name0;
personType name {get; set;}
[System.Xml.Serialization.XmlElementAttribute("name")]
personType name {get; set;}
Nested elements with anonymous complex type are transformed into classes
exactly in the same manner as top-level elements with anonymous complex type
(see section 2.4).
Example:
<xs:element name="quote">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:minInclusive value="0.0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
becomes
decimal quote;
[System.Xml.Serialization.XmlElementAttribute("quote")]
decimal quote {get; set;}
outside of the struct. Like in the case of top-level elements with an anonymous
simple type, the validation of the restriction is hidden inside the set-method.
11
</xs:complexType>
</xs:element>
</xs:schema>
becomes
class book {
// recursive translation of the content of the complex type
[XmlAttributeAttribute("year")]
int year {get; set;}
}
The field corresponding to the attribute gets an [XmlAttributeAttribute] custom
attribute with its name in order to specify that this field was derived from an
attribute rather than from an element (e.g. important when the original schema
has to be reconstructed).
<xs:complexType name="Person">
<xs:sequence>
<xs:element name="name" type="xs:string" />
<xs:element name="height" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Employee">
<xs:complexContent>
<xs:extension base="Person">
<xs:sequence>
<xs:element name="salary" type="xs:decimal"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
becomes
class Person {
struct sequence {
string name {get; set;}
int height {get; set;}
}
sequence content {get; set;}
string name {get; set;}
int height {get; set;}
}
14
In the case of restriction of simple content where only the value space of a simple
type is being restricted, the validation of that new constraint is hidden in the
property’s set-method.
The restriction of complex content with the omission of a field, and also the
restriction of simple content where the use of an attribute is being prohibited,
however, both correspond to class inheritance. We use a little trick to hide the
omitted field(s).
Example:
<xs:complexType name="Person">
<xs:sequence>
<xs:element name="name" type="xs:string" minOccurs="0"/>
<xs:element name="weight" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Anonymous">
<xs:complexContent>
<xs:restriction base="Person">
<xs:sequence>
<xs:element name="weight" type="xs:integer"/>
</xs:sequence>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
becomes
class Person {
struct sequence {
string[] name {get;}
int weight {get; set;}
}
sequence content {get; set;}
string[] name {get;}
15
// Hide Person::name
class Anonymous : Person {
new public class name { private name(){} }
}
15 Substitution groups
Substitution groups are another mechanism to define inheritance hierarchies. The
element marked as abstract is the head of the substitution group and becomes
an abstract superclass. The elements which belong to its substitution group are
descendants of the superclass.
Example:
<xs:element name="Vehicle" abstract="true"/>
<xs:element name="Car" substitutionGroup="Vehicle">
....
</xs:element>
becomes
abstract class Vehicle {}
class Car: Vehicle {
....
}
17 Conclusion
In this paper, we have shown a mapping from XML Schema Definition Language
to C#. To check the validity of our work, we implemented a compiler that uses
this mapping and translates XSD to C# (for most cases). Future work includes
examining roundtripping, i.e. the way from C# types back to XML Schema.
References
1. Skonnard, A, Gudgin, M.: Essential XML Quick Reference (2001)
2. Gunnerson, E.: A Programmer’s Introduction to C# (2000)
3. W3C Recommendation, XML Schema Part 0: Primer. See also Parts 1 and 2.
http://www.w3.org/TR/xmlschema-0/ .