prev | table of contents | next |
Among the data types of the XML Schema language there is an inseparable
pair of types operating complementary to each other: xsd:ID
and xsd:IDREF
. They let you represent references to XML
elements. This can be put to good use in several circumstances,
i.e., whenever you need linkage in addition to the natural parent-to-child
relationship.
Our first example uses references for factoring out frequently occuring
elements. Below is a schema snippet defining XML elements dealing with
airports and flights, which are scheduled from one airport to another
one. A naive schema definition would simply have two AirportType
sub-elements for each flight:
<xsd:complexType name="AirportType"> <xsd:attribute name="LocId" type="xsd:string" use="required"/> <xsd:attribute name="Name" type="xsd:string" use="required"/> </xsd:complexType> <xsd:complexType name="FlightType"> <xsd:all> <xsd:element name="From" type="AirportType"/> <xsd:element name="To" type="AirportType"/> <xsd:element name="Carrier" type="xsd:string"/> <xsd:element name="DepTime" type="xsd:time"/> <xsd:element name="ArrTime" type="xsd:time"/> </xsd:all> <xsd:attribute name="Number" type="xsd:int" use="required"/> </xsd:complexType>Instead of copying an element of class
AirportType
(biggish, aren't they) into all places where it is used, we'll now
change our schema to employ element linkage. An AirportType
element is presented only once, in full, and a reference is inserted
for the From
and To
sub-elements of
FlightType
where the original element was. Our document
type should be defined along the lines of TimetableType
,
bundling the actual AirportType
elements and the list of
flights.
<xsd:complexType name="AirportType"> <xsd:attribute name="LocId" type="xsd:ID" use="required"/> <xsd:attribute name="Name" type="xsd:string" use="required"/> </xsd:complexType> <xsd:complexType name="FlightType"> <xsd:all> <xsd:element name="From" type="xsd:IDREF"/> <xsd:element name="To" type="xsd:IDREF"/> <xsd:element name="Carrier" type="xsd:string"/> <xsd:element name="DepTime" type="xsd:time"/> <xsd:element name="ArrTime" type="xsd:time"/> </xsd:all> <xsd:attribute name="Number" type="xsd:int" use="required"/> </xsd:complexType> <xsd:complexType name="TimetableType"> <xsd:sequence> <xsd:element name="Airports" type="AirportType" maxOccurs="unbounded"/> <xsd:element name="Flight" type="FlightType" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType>The only change required within
AirportType
is the definition
of the attribute Id
as xsd:ID
, and
FlightType
has xsd:IDREF
as the type for
From
and To
. We'll continue to use the IATA
Location Identifier, which we would have used as an airport identification
anyway, as the string implementing the XML link. (If there is no attribute
that could serve as an identification right away, it may very well be
possible to compose one from the element's other attributes. Otherwise
you'll just have to use some synthetic key.) The resulting Java code for
AirportType
has the usual JavaBean layout. More interesting
is the code for the class FlightType
:
public class FlightType { protected Object from; protected Object to; protected String carrier; protected XMLGregorianCalendar depTime; protected XMLGregorianCalendar arrTime; protected int number; public Object getFrom() { return from; } public void setFrom(Object value) { this.from = value; } public Object getTo() { return to; } public void setTo(Object value) { this.to = value; } // ...(more getters and setters) }The elements
From
and To
are now represented as
Object
references. So, to retrieve the origin of some flight,
all you'll have to code is
(AirportType)flight.getFrom()The destination's IATA Location Identifier is obtained by
((AirportType)flight.getTo()).getLocId()Don't blame JAXB for not making the
From
and To
sub-elements AirportType
references. The XML type
xsd:ID
is a universal reference type, and
xsd:IDREF
is the union
of all reference values. Nevertheless, unmarshalling will automatically
create FlightType
objects that are actually linked to their
airports, saving you the hassle of establishing this linkage yourself.
Notice that by dropping the necessity to have a full-blown XML element
for From
and To
, we could now define this value pair
as a couple of attributes.
Keep in mind that XML requires the values of xsd:ID
(or,
same thing, the ones of xsd:IDREF
) to be unique across all of
your XML document. As soon as we begin to think about adding bookings to
our XML data, flight identifiers would have to make their appearance as
another class of values for xsd:ID
. There is a good chance
that location identifiers and flight identifiers (a concatenation of
the carrier id and some number) don't clash, but you'd better research
this thoroughly before you commit yourself.
Another excellent reason for using references is the representation of linked data structures such as trees, lists or, in general, graphs. The example given below is for railway buffs. It demonstrates how a track layout can be represented by linking elements such as sets of points (or switches) and tracks to each other, creating the graph that represents the topology of a shunting yard. (Signals are omitted for brevity's sake.)
<xsd:simpleType name="GroupType"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="SWITCH"/> <xsd:enumeration value="TRACK"/> </xsd:restriction> </xsd:simpleType> <xsd:complexType name="ElementType"> <xsd:attribute name="Id" type="xsd:ID" use="required"/> <xsd:attribute name="Group" type="GroupType" use="required"/> <xsd:attribute name="Number" type="xsd:int" use="required"/> <xsd:attribute name="Name" type="xsd:string" use="optional" default=""/> </xsd:complexType> <xsd:complexType name="PointLeftRightType"> <xsd:complexContent> <xsd:extension base="ElementType"> <xsd:attribute name="point" type="xsd:IDREF"/> <xsd:attribute name="left" type="xsd:IDREF"/> <xsd:attribute name="right" type="xsd:IDREF"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="EastWestType"> <xsd:complexContent> <xsd:extension base="ElementType"> <xsd:attribute name="east" type="xsd:IDREF"/> <xsd:attribute name="west" type="xsd:IDREF"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="ShuntingYardType"> <xsd:choice maxOccurs="unbounded"> <xsd:element name="Switch" type="PointLeftRightType"/> <xsd:element name="Track" type="EastWestType"/> </xsd:choice> </xsd:complexType>From the generated classes, we select
EastWestType
for a closer
inspection. Again, there is Object
as the type used in the getters
and setters for the attributes linking to the neighbours.
public class EastWestType extends ElementType { protected Object east; protected Object west; public Object getEast() { return east; } public void setEast(Object value) { this.east = value; } public Object getWest() { return west; } public void setWest(Object value) { this.west = value; } }
But wait - what do we do with dead-end tracks? Or with tracks that lead out of
the shunting yard, i.e., line tracks? We'll need some sort of replacement for
Java's null
. Simply using "null"
or ""
as an IDREF
value will cause problems as soon as the XML is
validated, because, by definition, there must be some element where
that string is an id value. One solution would be to define all the link
attributes as optional,
but this makes it difficult to discriminate an inadvertently dropped element
from an intentionally omitted one. One solution that avoids this ambiguity
is to use a single artificial element, perhaps in a category of its own, as
the "null" element. We extend ShuntingYardType
accordingly:
<xsd:complexType name="NullType"> <xsd:attribute name="Id" type="xsd:ID" use="required"/> </xsd:complexType> <xsd:complexType name="ShuntingYardType"> <xsd:choice maxOccurs="unbounded"> <xsd:element name="Switch" type="PointLeftRightType"/> <xsd:element name="Track" type="EastWestType"/> <xsd:element name="Null" type="NullType"/> </xsd:choice> </xsd:complexType>This will give you a class
NullType
, and one element of that
type with an arbitrary Id
value - perhaps
Id="null"
- provides a null element that is comfortably
distinguishable from all the actual trackside equipment by class as well
as by its Id
value. Here is the Java code JAXB generates for it:
public class NullType extends ElementType { }No, this is no typesetting accident: this class is indeed empty because it doesn't need any additions to its base class. Its name is distinction enough.
In the XML file you would have one extra element, e.g., the Null
element as shown below.
<Track Id="TRACK_168" Group="TRACK" Number="168" Name="b101" east="SWITCH_42" west="null"/> <Null Id="null" Group="NULL" Number="0"/>The generic element attributes
Group
and Number
could be set to arbitrary values, but we might just as well use some
null values there, too.
prev | table of contents | next |