Extensible Markup Language (XML) is a popular serialization format for data exchange and storage.
While there are many possibilities to represent a model of an Asset Administration Shell in XML, we provide our "official" XML schema definition (XSD) to foment interoperability between different tools and systems.
Below we explain in more detail how our schema is constructed, point the user to the examples and finally give some background information on our particular schema design.
The root element of our XML is an XML element representing the instance of Environment . This environment contains three aggregations, corresponding to all Identifiable classes:
- AssetAdministrationShell's,
- Submodel's, and
- ConceptDescription's.
To simplify exploration of the XML data, identifiable instances are only available at the level of the environment, and nowhere else.
We now continue to see how to serialize the instances and their properties.
Building blocks of an XML document include only XML elements, XML attributes and text enclosed in an element. XML elements can nest children elements. Using these building blocks, we map an AAS model to XML.
Before we look into how to represent instances of classes, let us start bottom-up and see first how individual properties are represented.
We represent each property of a class with an XML element whose name corresponds to the
property in the meta-model. The name is given in camel-case where all abbreviations are
left as capitalized (dataSpecificationIec61360
instead
of dataSpecificationIEC61360
).
It is common in UML to use singular form for aggregations, which is the case for the
meta-model. This is, however, in contrast to programming code, where plural form for
sequences is common. Since the naming of XML elements has direct influence on the
programming code, we name the properties in plural form diverging from the name in the
meta-model. For example, submodelElements
instead of submodelElement
in case
of Submodel/submodelElement property in the meta-model.
In cases where plural form made no sense for a property, we kept it as-is in singular
form (e.g., isCaseOf
). The full list of exceptions is
available as code in aas-core-meta.
While some meta-model properties are indeed very apt to be succinctly represented as XML attributes, we decided not to use XML attributes at all for three reasons.
First, the XML attribute must be a string, and therefore does not allow for structured data to be represented with it. As the meta-model evolves, we need to be able to gracefully evolve the schema with as little breakages as possible. An XML attribute puts a limit in so far that an attribute can only be represented as string. Moreover, as the schema evolves, making diff's is important to trace the changes. This is much harder when the attributes switch from an XML attribute to an XML element.
Second, many classes contain a mix of primitive properties and structured properties. If we allowed XML attributes, the former would be represented as XML attributes, while the latter would be represented as XML elements. This leads to confusion where the writer is forced to go back and forth in the specification, and always double-check whether a property should be represented as an XML attribute or an XML element.
Third, we automatically generate the schema from a machine-readable meta-model representation (see Section Background below). The mix of XML attributes and elements would have complicated the code and prolonged the development.
We finally decided that the drawbacks outlined above outweighed the advantage in form of succinct representation.
If a property has cardinality 0..1
or 0..*
and is not specified in the instance,
the element is simply omitted.
The property values given in the meta-model as simple data types (see Section 5.7.12 Primitive and Simple Data Types of the meta-model) are serialized as text enclosed in the XML element corresponding to the property. Please see Section Why no XML attributes? on why we do not serialize them as XML attributes.
The byte arrays (BlobType
in meta-model) are serialized as Base64-encoded text.
Simple type rdf:langString
is serialized as if it were a proper meta-model class
with properties language
and text
. See the following
Section Instances of Classes as Attribute Values about how to serialize instances of
classes in general as that section directly applies to rdf:langString
.
To serialize instances of meta-model classes as values of properties, we need to nest them somehow within the XML element corresponding to the property. This is not as trivial as it seems.
If the property type is a concrete or abstract class with descendants, the deserializer needs to know the exact concrete class at the time of de-serialization. However, this information is obviously missing in the meta-model. For example, the property Submodel/submodelElement tells the deserializer that its items are instances of SubmodelElement, but the deserializer does not know which concrete deserialization method to apply to understand the nested XML data: is it Property , Range or some other descendant of SubmodelElement?
Therefore, the information about the concrete type of the serialized instance must be encoded somehow in XML when the type in the meta-model is too vague. This nugget of information is usually called a discriminator (e.g., see OpenAPI 3 specification on polymorphism).
On the other hand, when the meta-model mandates the type of property as a concrete class without descendants, the deserializer needs no additional information as the deserialization method is given by the meta-model. There is thus no need to include the discriminator in the serialization, and a redundant discriminator would only clutter the XML document.
We therefore distinguish two serializations of instances: one with discriminator, and one without, respectively.
We use the XML element named according to the concrete class at the serialization as the discriminator. The properties of the instance are nested below this discriminator XML element.
Let us make an example. The example will be agnostic of the particular meta-model
version, so that it can age well across different versions. We fantasize a meta-model
class SomeAbstractClass
and assume it has two descendant classes, SomeConcreteClass
and AnotherConcreteClass
. Let us assume yet another class, YetAnotherClass
, entails
the property someProperty
of type SomeAbstractClass
.
Here is how the XML structure corresponding to YetAnotherClass/someProperty
would look
like in this fantasized case, where the value is an instance of SomeConcreteClass
:
<someProperty>
<SomeConcreteClass>
<!--
Serialized properties of SomeConcreteClass
-->
</SomeConcreteClass>
</someProperty>
If the value is an instance of AnotherConcreteClass
, the serialization becomes:
<someProperty>
<AnotherConcreteClass>
<!--
Serialized properties of AnotherConcreteClass
-->
</AnotherConcreteClass>
</someProperty>
The abstract class, SomeAbstractClass
, does not show up in the serialization at all,
as it is redundant information.
While this approach is succinct in terms of additional XML elements, but it comes with a
caveat. Namely, if we introduce descendants to AnotherConcreteClass
, the
property someProperty
becomes polymorph, and we need to introduce
backwards-incompatible schema changes to allow for the [discriminator].
If the concrete type of the property at deserialization is unambiguous by the meta-model, we omit the discriminator to reduce the clutter. The instance is simply serialized as a sequence of XML elements corresponding to its properties.
Let us fantasize yet another example, similar to the one in
Section Instances Serialized with Discriminator. We will again draw an example such
that it is agnostic of meta-model version for better evolution of this Readme. Assume a
class SomeClass
with a property SomeClass/someProperty
. Now imagine the type
of someProperty
to be the class AnotherClass
. The class AnotherClass
has
properties AnotherClass/anotherProperty
and AnotherClass/yetAnotherProperty
. The
class AnotherClass
has no descendants, so the concrete type
of SomeClass/someProperty
is unambiguous.
Here is how the XML structure would look like:
<someProperty>
<anotherProperty>
<!-- ... -->
</anotherProperty>
<yetAnotherProperty>
<!-- ... -->
</yetAnotherProperty>
</someProperty>
The type information about AnotherClass
is omitted, as the type of
the SomeClass/someProperty
is fixed in the meta-model.
Many properties in the meta-model do not represent a single value (be it primitive or structured as a class), but aggregate instances of meta-model classes. For example, Submodel/submodelElement aggregates instances of SubmodelElement's.
If we just concatenated all the properties of the instances, we would not know which property belongs to which instance (or such distinction would be complicated). We need a delimiter!
Following the approach described in Section Instances Serialized with Discriminator, we delimit the instances simply by nesting them beneath the discriminator elements. If the type of the list items is a concrete class, we nest beneath the discriminator element regardless.
For example, here is an XML snippet of an example submodel elements, where the first element is a Property, the second one is a Range and the third is a Property:
<submodel>
<submodelElements>
<!-- First element -->
<property>
<!-- ... some properties ... -->
</property>
<!-- Second element -->
<range>
<!-- ... another properties ... -->
</range>
<!-- Third element -->
<property>
<!-- ... yet another properties ... -->
</property>
</submodelElements>
</submodel>
We explicitly forbid empty lists in XML to avoid confusion about properties of
cardinality 0..*
. Namely, an empty list is semantically equal to an omitted
property (according to the meta-model). Thus, the XML element representing an
aggregation must be omitted if the aggregation is empty.
The following snippet is therefore invalid:
<submodel>
<submodelElements/>
<!-- other properties -->
</submodel>
... and should be written as:
<submodel>
<!-- other properties -->
</submodel>
We fixed the order of the properties to match the meta-model for readability.
This is reflected in usage of xs:sequence
throughout the XML schema.
Enumerations are serialized according to the exact values of enumeration literals in the meta-model as text.
For example, the enumeration literal EntityType/CoManagedEntity is serialized
as CoManagedEntity
, while the literal Direction/input as input
.
There is an abstract definition of data specifications as templates in the meta-model ( see Section 6 Predefined Data Specification Templates). This definition does not specify, though, how to access them from within an Environment, which is a requirement for many systems. To address this practical issue, the meta-model indicates that they should be embedded in serializations (see Section 7.2.5 Embedded Data Specifications).
We therefore add additional XML element, named embeddedDataSpecifications
, in the XML
representations of HasDataSpecification class, and omit dataSpecification
property
by design. The embedded data specifications are serialized just as all the other classes
of the meta-model, following the procedure outlined above.
The XML elements representing the AAS model are explicitly required to live in our namespace. The namespace corresponds to the version of the meta-model.
For example, the serialization for the meta-model V3.0RC02
lives in the
namespace https://admin-shell.io/aas/3/0/RC02
.
XML schemas tend to grow very complex, very quickly. Our schema is no exception. While we described so far how an XML document looks like for a concrete AAS model, let us briefly give you an overview of the schema beneath it.
At this point, we only outline its structure in broad brushes. Please refer to the actual file schema/xml/AAS.xsd for more details.
For each class, we define a xs:group
which lays out the order (as a
nested xs:sequence
) and type of the XML elements corresponding to the properties of
the class. The inheritance is dealt by nesting an additional xs:group
element within
the sequence with the ref
attribute.
The individual properties are defined with xs:element
in the xs:sequence
.
For example:
<xs:group
name="administrativeInformation">
<xs:sequence>
<xs:group
ref="hasDataSpecification"/>
<xs:element
name="version"
minOccurs="0"
maxOccurs="1">
<xs:simpleType>
<xs:restriction
base="xs:string">
<xs:minLength
value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<!-- ... More elements come here ... -->
</xs:sequence>
</xs:group>
For each class, we define a xs:complexType
, name it with an _t
prefix and refer the
complex type to the corresponding group. The complex types are necessary so that we can
use them to specify aggregations.
For example, here is the definition of submodel_t
:
<xs:complexType
name="submodel_t">
<xs:sequence>
<xs:group
ref="submodel"/>
</xs:sequence>
</xs:complexType>
Here it is used in the definition of the aggregation:
<xs:group
name="environment">
<!-- ... -->
<xs:element
name="submodels"
minOccurs="0"
maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element
name="submodel"
type="submodel_t"
minOccurs="1"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- ... -->
</xs:group>
If a class has one or more descendants, we define an xs:group
with the _choice
suffix. This is necessary so that we can enforce a closed set of concrete classes at the
de/serialization. In particular, we want to ensure that the discriminator is given
correctly (see Section Instances Serialized with Discriminator).
Here is an example of a choice:
<xs:group
name="submodelElement_choice">
<xs:choice>
<!-- ... -->
<xs:element
name="property"
type="property_t"/>
<xs:element
name="range"
type="range_t"/>
<!-- ... -->
</xs:choice>
</xs:group>
Here the choice is enforced in another group:
<xs:group
name="submodel">
<xs:sequence>
<!-- ... -->
<xs:element
name="submodelElements"
minOccurs="0"
maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:group
ref="submodelElement_choice"
minOccurs="1"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:group>
Examples of the XML serializations can be found in schemas/xml/examples/ folder.
When we started with the project, the schema had been manually written. One or two schema designers would sit down, follow the book and translate it into XML schema by best effort. This allowed for a lot of artistic freedom, but eventually caused problems due to mismatches with other serializations or internal inconsistencies. Especially as the meta-model evolved, maintaining the schema and keeping it up-to-date with the meta-model proved to be difficult.
While the handwritten schema is arguably elegant, the maintenance is too demanding. Therefore, we developed a schema generator based on the machine-readable representation of the meta-model. The generator is provided in aas-core-codegen project, while the meta-model lives in aas-core-meta.
This allowed us to evolve the XML schema more quickly while keeping it in sync with other serialization schemas and SDKs. However, we had to give up on elegant parts of the schema, and had to straightjacket the schema into form that can be programmatically generated. For example, all properties are serialized as XML elements, and we could not use XML attributes.