Dev:Using xsi:type

From railML 3 Wiki
Jump to navigation Jump to search

Using xsi:type
 

💡 Important: Before starting using any or anyAttribute please read carefully and understand the first section!  

When using xsi:type and what to respect before starting?

  • Before implementing any extensions to the railML® schema, please consider Dev:Extending_railML#when.
  • The means described in this page (using the xsi:type-attribute) is required for railML® 3.2 and higher. For all versions of railML® 2 plus for railML® 3.1, the proposed means are xs:any, xs:anyAttribute and rail:tOtherEnumerationValue, comp. Dev:UsingAny. If you are interested in the background of this decision, you can find more info here.
  • Please, take care of appropriate namespace handling.

How to use xsi:type

Example

Schema extension

In the following example extension two separate ways of extending railML 3.2 are shown. They are different in that they follow different approaches on how to model a certain information. The general approach is the same for both.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:rail="https://www.railml.org/schemas/3.2" xmlns:example="https://www.somedomain.net/2022/exampleExtension" targetNamespace="https://www.somedomain.net/2022/exampleExtension" elementFormDefault="qualified" attributeFormDefault="qualified" version="0.1">
	<!--With xmlns:rail="https://www.railml.org/schemas/3.2" we declare the railML namespace, with xmlns:example="https://www.somedomain.net/2022/exampleExtension" 
		and targetNamespace="https://www.somedomain.net/2022/exampleExtension" we declare the target namespace of our extension. 
		With version="0.1" we specify the version of our extension (this obviously is optional).

		Next we import necessary dependencies...
	-->
	<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
	<xs:import namespace="https://www.railml.org/schemas/3.2" schemaLocation="https://schemas.railml.org/3.2/railml3.xsd"/>
	
	<!--Then we define our extension. We can define our own types and extend existing ones with that. This is basically only limited by what the limits of xsd are." -->
	<xs:complexType name="ExtendedTrack">
		<xs:complexContent>
			<xs:extension base="rail:Track">
				<xs:sequence>
					<xs:element name="priceCategory" type="example:PriceCategory" minOccurs="1" maxOccurs="unbounded">
						<xs:annotation>
							<xs:documentation>
								With this the original track defined in railML is extended with a new element named "priceCategory" which is 
								defined as type "PriceCategory" in the example namespace "https://www.somedomain.net/2022/exampleExtension".
							</xs:documentation>
						</xs:annotation>
					</xs:element>
				</xs:sequence>
				<xs:attribute name="additionalExampleComment" type="xs:string"/>
			</xs:extension>
		</xs:complexContent>
	</xs:complexType>

	<xs:simpleType name="CategoryNumber">
		<xs:restriction base="xs:nonNegativeInteger">
			<xs:enumeration value="1">
				<xs:annotation>
					<xs:documentation>Price category 1 - associated with normal fares.</xs:documentation>
				</xs:annotation>
			</xs:enumeration>
			<xs:enumeration value="2">
				<xs:annotation>
					<xs:documentation>Price category 2 - associated with express fares.</xs:documentation>
				</xs:annotation>
			</xs:enumeration>
			<xs:enumeration value="3">
				<xs:annotation>
					<xs:documentation>Price category 1 - associated with tourist fares.</xs:documentation>
				</xs:annotation>
			</xs:enumeration>
			<xs:enumeration value="4">
				<xs:annotation>
					<xs:documentation>Price category 1 - associated with some other example fares.</xs:documentation>
				</xs:annotation>
			</xs:enumeration>
			<xs:enumeration value="5">
				<xs:annotation>
					<xs:documentation>Price category 1 - associated with even more example fares.</xs:documentation>
				</xs:annotation>
			</xs:enumeration>
		</xs:restriction>
	</xs:simpleType>

	<xs:complexType name="PriceCategory">
		<xs:sequence>
			<!-- We are free to define our custom type as we like. We can also use types that already exist in railML -->
			<xs:element name="name" type="rail:Name" minOccurs="1" maxOccurs="unbounded"/>
		</xs:sequence>
		<xs:attribute name="categoryNumber" type="xs:nonNegativeInteger" use="required"/>
		<xs:attribute name="priceMultiplier" type="xs:float"/>
	</xs:complexType>
	
	<xs:complexType name="ExtendedOperationalPoint">
		<xs:complexContent>
			<xs:extension base="rail:OperationalPoint">
			<xs:sequence>
				<xs:element name="priceCategory" type="example:PriceCategory" minOccurs="1" maxOccurs="unbounded">
					<xs:annotation>
						<xs:documentation>
							With this we extended the original operational point the the same way we did before with the track.
						</xs:documentation>
					</xs:annotation>
				</xs:element>
			</xs:sequence>
			</xs:extension>
		</xs:complexContent>
	</xs:complexType>
	
	<!--
		Another possible approach:
	 -->
	<xs:complexType name="ExtendedFunctionalInfrastructure">
		<xs:complexContent>
			<xs:extension base="rail:FunctionalInfrastructure">
				<xs:sequence>
					<xs:element name="priceCategories" type="example:PriceCategories" minOccurs="0" maxOccurs="1"/>
				</xs:sequence>
			</xs:extension>
		</xs:complexContent>
	</xs:complexType>

	<xs:complexType name="PriceCategories">
		<xs:sequence>
			<xs:element name="priceCategory" type="example:AlternativeApproachPriceCategory" minOccurs="1" maxOccurs="unbounded"/>
		</xs:sequence>
	</xs:complexType>

	<!--
		With this it is possible to locate the price category the same way all other elements in railML 3 infrastructure are located. Of course it would also be possible to combine both approaches.
	-->
	<xs:complexType name="AlternativeApproachPriceCategory">
		<xs:complexContent>
			<xs:extension base="rail:FunctionalInfrastructureEntity">
				<xs:sequence>
					<xs:element name="name" type="rail:Name" minOccurs="1" maxOccurs="unbounded"/>
				</xs:sequence>
				<xs:attribute name="categoryNumber" type="xs:nonNegativeInteger" use="required"/>
				<xs:attribute name="priceMultiplier" type="xs:float"/>
			</xs:extension>
		</xs:complexContent>
	</xs:complexType>
	
</xs:schema>

The idea was to model price categories that could be applied to tracks as well as to operational points. The contents are completely fictional and are not to be used in any operational context.

The first approach basically simply extends the original railml elements track and operationalPoint of railML 3.2 and adds a new sub element “priceCategory” that is required and can be repeated. The element consists of two attributes, one required and one optional as well as a repeatable subelement “name”.

The other approach is the idea of creating a price category as a new functional infrastructure element that can be located on the topology the same way all other functional infrastructure elements are. Regarding its content the second approach is no different than the first one.

Note that we are using elementFormDefault="qualified" here, as well as attributeFormDefault="qualified". The first one is a requirement for the demonstrated way of extending railML, otherwise it is not possible to achieve a valid xml based on the extension schema. The second one is optional, but will cause attributes that are defined to rather be considered manifestations of the still existing anyAttribute than a result of the extension described in the extension schema, thus causing problems with code generation and the use of generated code.

Please also note that these are just basic ideas on how to model a price category. They should demonstrate how to extend railML 3.2 and help understanding the basic principles.

Example of the first approach

<?xml version="1.0" encoding="UTF-8"?>
<railML 
	xmlns="https://www.railml.org/schemas/3.2" 
	xmlns:example="https://www.somedomain.net/2022/exampleExtension" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="https://www.somedomain.net/2022/exampleExtension ExampleExtension.xsd https://www.railml.org/schemas/3.2 https://schemas.railml.org/3.2/railml3.xsd" 
	version="0.1">
	
	<infrastructure id="is01">
		<functionalInfrastructure>
			<operationalPoints>
				<operationalPoint xsi:type="example:ExtendedOperationalPoint" id="op01">
					<!-- You can use all elements and attributes of the original operational point in addition to the ones we added -->
					<name name="Praha" language="cz"/>
					<name name="Prag" language="de"/>
					<name name="Prague" language="en"/>
					<example:priceCategory example:categoryNumber="2" example:priceMultiplier="2.1">
						<example:name name="Tourist fare" language="en"/>
					</example:priceCategory>
				</operationalPoint>
			</operationalPoints>

			<!-- The same applies to the extended tracks -->
			<tracks>
				<track xsi:type="example:ExtendedTrack" id="tr01" type="mainTrack"
					example:additionalExampleComment="This attribute was added just to show how to extend a type with an additional attribute.">
					<!-- You can use all elements and attributes of the original track in addition to the ones we added -->
					<designator register="_Tracks" entry="1234"/>
					<length value="500" type="physical"/>
					<example:priceCategory example:categoryNumber="1" example:priceMultiplier="1.0">
						<example:name name="Normal fare" language="en"/>
					</example:priceCategory>
					<example:priceCategory example:categoryNumber="3" example:priceMultiplier="1.5">
						<example:name name="Express fare" language="en"/>
					</example:priceCategory>
				</track>
				<track id="tr02" type="sidingTrack">
					<!-- You can also use the original type-->
					<designator register="_Tracks" entry="4321"/>
					<length value="1200" type="physical"/>
				</track>
			</tracks>
			
		</functionalInfrastructure>
	</infrastructure>
</railML>

In order to use the extended elements its necessary to provide an attribute xsi:type at the points in the xml tree that should refer to extended types rather than to the ones defined in standard railML. In other words, if the type of a tag should originate from a different namespace than the default namespace that is usually set to railML, an xsi:type attribute is necessary to indicate which particular type is meant. This is demonstrated above with the operationalPoint that is defined by the type ExtendedOperationalPoint in the namespace abbreviated with "example" (this abbreviation indicates the namespace "https://www.somedomain.net/2022/exampleExtension" as defined in the xmls first tag). It is also possible to mix standard tags with extended ones as demonstrated by the example of the two tracks. The first one is extended, while the second one is not.

As per our experience the resulting railML can be parsed by either generic parsers or code generated parsers that can be generated either based on the extension schema or the standard railML schema. In case of the former of course access to the extended elements is now available, however all non extended aspects of the extended elements are available, i. e. for the above operational point a generated parser based on the standard railML schema will be able to access the name elements as well as the id attribute, but will fail to access the price category element or any extended attributes.

Example of the second approach

<?xml version="1.0" encoding="UTF-8"?>
<railML 
	xmlns="https://www.railml.org/schemas/3.2" 
	xmlns:example="https://www.somedomain.net/2022/exampleExtension" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="https://www.somedomain.net/2022/exampleExtension ExampleExtension.xsd https://www.railml.org/schemas/3.2 https://schemas.railml.org/3.2/railml3.xsd" 
	version="0.1">
	<infrastructure id="is01">
		<topology>
			<netElements>
				<netElement id="ne01">
					<associatedPositioningSystem id="aps01">
						<intrinsicCoordinate id="ic01" intrinsicCoord="0"/>
					</associatedPositioningSystem>
				</netElement>
			</netElements>
			<networks>
				<network id="nw01">
					<level id="lv01"/>
				</network>
			</networks>
		</topology>

		<functionalInfrastructure xsi:type="example:ExtendedFunctionalInfrastructure">
			<operationalPoints>
				<operationalPoint id="op01"/>
				<operationalPoint id="op02"/>
			</operationalPoints>
			<tracks>
				<track id="tr01" type="mainTrack"/>
				<track id="tr02" type="mainTrack"/>
			</tracks>
			<example:priceCategories>
				<example:priceCategory id="pc01" example:categoryNumber="4" example:priceMultiplier="1.2">
					<linearLocation id="ll01">
						<associatedNetElement netElementRef="ne01" keepsOrientation="true"/>
					</linearLocation>
					<example:name name="Example category" language="en"/>
				</example:priceCategory>
			</example:priceCategories>
		</functionalInfrastructure>
	</infrastructure>
</railML>

In this second example the usage of the second approach is demonstrated. As before it is necessary to specify the type that has been extended. In this case the functionalInfrastructure container element, as we added something to it. As with the first example you can see that the usage of extended and non extended elements can be mixed. With this approach xsi:type only needs to be specified once, as all the other changes to the standard railML are on lower levels of the xml tree.

Disclaimer

It needs to be understood that extensions should only be used if the underlying requirements cannot be fulfilled with standard railML. If unsure it is a good idea to post a question in the forum or contact a railML coordinator directly. In general, it is a good idea to communicate extensions with railML.org as it allows us to understand the needs of the railML community and include new aspects needed by the users in a new version of railML.

Conclusion

In order to define an extension without xs:any it is necessary to define an extension type based on an existing railML type. The types available can be looked up in the documentation. Once this is done a resulting railML would declare the extension namespace as before and specify the

schema location in order to allow importing tools to validate. Usage of the extended elements is done by specifying an extended type using xsi:type.


TL;DR: So to sum it up, the following steps need to be followed:

  1. Make sure the topic you want to model is not already covered by standard railML
  2. Create a new XSD for your extension and declare your own namespace as its targetNamespace
  3. Import the necessary dependencies, most importantly the standard railML schemas
  4. Pick a type to extend from standard railML
  5. Create an extended complex type of the standard railML type picked in step 4
  6. Add whatever structure you need in order to properly model your data

In order to use the extension:

  1. Declare the railML namespace as the standard namespace
  2. Add your extension namespace
  3. Specify the location of your extension schema using xsi:schemaLocation
  4. Optionally specify the location of the standard railML schema using xsi:schemaLocation
  5. If you want to use your extended type(s) specify the type using xsi:type so that the importing system can know which declaration to follow

Background

Extension of railML in versions 2.x and 3.1

Extensions to railML have been around for a long time. Many suppliers find that some details of their particular use case may not have found their way into the standard. However, since partners may already support railML, the general structure of the data is already clear and can be exchanged while only a few details need adding. This common scenario often led to the implementation of extensions that allow adding these few extra points of data with minimal effort for both parties of the data exchange.

With railML 2.x as well as railML 3.1 these extensions were usually built on the basis of the <xs:any> any schema element that allows specification of any subelement for a railML element as long as it is defined in another namespace.

<xs:complexType name="eTrack">
	<xs:complexContent>
		<xs:extension base="rail:tTrack">
			<xs:sequence>
				<xs:element name="trackDescr" type="xs:string" ... />
				<xs:any namespace="##other" 
					processContents="strict"
					minOccurs="0"
					maxOccurs="unbounded">
				</xs:any>
			</xs:sequence>
		</xs:extension>
	</xs:complexContent>
</xs:complexType>

The new elements could be specified in a separate XSD but had one major drawback. It was not possible to specify for which element an extension element was meant. This means that if an extension element “fireStation” that described a firestation located at a certain point in the infrastructure could be used as presumably intended with OCPs, but it could also be used in a rather unpredictable way, like for example for derailers or signals. With the <xs:any> technique it is basically impossible to restrict usage of an extended element to a certain element. The reason for this is that when implementing the extension schema the validator cannot know if the extended element would be there due to the <xs:any> or due to the specified extra element. Thus, the extension XSD would not be valid and could not be used.

Goals of removal of anyElement

With removal of the <xs:any> this has changed. It now is possible to restrict the usage of newly introduced elements to particular railML base elements. It however requires a slightly different approach when defining these extensions. How this is to be done is described in the section above.

The benefit of this change is that now it is possible to validate an extended railML, including the validation of the extension elements. Additionally, as the new way of describing this more precise it allows for code generation. Before code generation was possible for standard railML, but the extensions could not be considered due to the indeterminacy of the placement of the extended elements.

For more information, please also take a look at the forum discussion, which can be found here.