Assertions are a powerful new feature in 1.1 that allows you to specify additional constraints using XPath 2.0. This addresses a significant limitation in XML Schema 1.0 that prevented the definition of co-constraints, where one data item affects the validity of another. It also generally allows for much more complex validation criteria to be expressed.
This chapter covers assertions, as well as conditional type assignment which allows the type of an element to be determined by an XPath expression on its attributes. Conditional type assignment is also new in version 1.1. Although this feature is separate from assertions, it has similar syntax and some overlapping use cases.
Assertions are defined on types, rather than element or attribute declarations, so they are shared across all elements or attributes that have a particular type. Example 14–1 shows two types, one simple and one complex, that have assertions. For SizeType
, it is testing to make sure that the value is not equal to zero. For ProductType
, it is testing the validity of the product number, based on the department.
<xs:simpleType name="SizeType">
<xs:restriction base="xs:integer">
<xs:assertion test="$value != 0"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="size" type="SizeType"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
<xs:assert test="(@dept = 'ACC' and number > 500) or
(number < 300)"/>
</xs:complexType>
As you can see, two different elements are used: assertion
is used in simple types (and in simple content), and assert
is used in complex types. Both assertion
and assert
have a test
attribute that specifies an XPath expression. The XPath returns a Boolean (true/false) value. If the expression is true, the element or attribute is valid with respect to the assertion. If it is false, it is invalid.
Assertions are specified using XPath 2.0, which is a powerful language that includes over a hundred built-in functions and many operators. This chapter describes some of the XPath 2.0 functions, operators, and expression syntax that are most useful for assertions, but it is by no means complete. For a complete explanation of all XPath operators and syntax, you can refer to the XML Path Language (XPath) 2.0 recommendation at www.w3.org/TR/xpath20.
Syntactically, any XPath 2.0 is allowed in an assertion. However, one limitation of assertions is that your XPath expression has to stay within the scope of the type itself. It can only access attributes, content, and descendants of the element that has that type. It cannot access the parent or other ancestor elements, siblings, separate XML documents, or any other nondescendant elements. This means that for cross-element validation, the assertion needs to be specified on an ancestor type that contains all of the elements or attributes mentioned in the assertion.
Assertions in simple types are facets, and as such they appear alongside all the other facets inside a restriction
element. That facet is called assertion
, and its syntax is shown in Table 14–1.
Example 14–2 shows an assertion on a simple type. Simple type assertions are generally less complicated than those for complex types because there are no descendants, only a value to test. A special built-in variable is used to access that value, called $value
. There is no context item for a simple type assertion, so you cannot use a period (.
) to represent the current element or value1 like you might in some XPath expressions.
<xs:simpleType name="SizeType">
<xs:restriction base="xs:integer">
<xs:assertion test="$value != 0"/>
</xs:restriction>
</xs:simpleType>
The assertion
facet can also be used inside the restriction
element for complex types with simple content, just like any other facet. Example 14–3 shows two complex types with simple content, one restricting the other by adding an assertion. However, if you need to access the attributes of that type in the assertion, you should use an assert
instead, as shown later in Example 14–17.
<xs:complexType name="SizeType">
<xs:simpleContent>
<xs:extension base="xs:integer">
<xs:attribute name="system" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="RestrictedSizeType">
<xs:simpleContent>
<xs:restriction base="SizeType">
<xs:assertion test="$value != 0"/>
</xs:restriction>
</xs:simpleContent>
</xs:complexType>
You can specify multiple assertions on the same simple type, in which case they must all return true for the element or attribute to be valid. Values of type DepartmentCodeType
in Example 14–4 must be valid with respect to both specified assertions. Assertions can be combined with other facets, in any order. In fact, it is recommended that you continue to use other facets if they can express the constraint. For example, use the length
facet as shown rather than an assertion with a test of string-length($value) = 3
.
<xs:simpleType name="DepartmentCodeType">
<xs:restriction base="xs:token">
<xs:assertion test="not(contains($value,'X'))"/>
<xs:assertion test="substring($value,2,2) != '00'"/>
<xs:length value="3"/>
</xs:restriction>
</xs:simpleType>
The XPath 2.0 language allows a number of operators in its syntax, for example for performing comparisons and arithmetic operations. Table 14–2 shows some of the operators that are likely to be used in simple type assertions, along with examples.
Parentheses can be used in XPath to change the evaluation order of these operators. For example, by default, and
takes precedence over or
. The assertion in Example 14–5 uses parentheses around the first two comparisons to change the evaluation order. Without the parentheses, the second and third comparisons would have been combined by and
before evaluating the or
.
Parentheses can also be used to create a sequence of multiple values to test, as shown in the assertion in Example 14–6. The expression evaluates to true if the value is any of the three strings listed.
<xs:simpleType name="SizeType">
<xs:restriction base="xs:integer">
<xs:assertion test="($value < 12 or $value > 50)
and $value != 0"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="DepartmentCodeType">
<xs:restriction base="xs:token">
<xs:assertion test="$value = ('ACC','WMN','MEN')"/>
</xs:restriction>
</xs:simpleType>
XPath 2.0 includes over 100 built-in functions. Functions in XPath are called using a syntax that is probably familiar from other programming languages: the function name, followed by parentheses that contain the arguments to the function separated by commas. Table 14–3 provides a sample of built-in functions that would commonly be used in simple type assertions. For a complete list, refer to the XQuery 1.0 and XPath 2.0 Functions and Operators recommendation at www.w3.org/TR/xpath-functions.
These functions are all built in, and you do not need to use name-space prefixes on their names. Your schema processor may support additional implementation-defined functions that are in other namespaces. Typically, in simple type assertions, you will be passing $value
as one of the arguments. Table 14–4 shows some example values for simple type assertions that use common XPath functions.
Note that the matches
function interprets regular expressions slightly differently from the pattern
facet. The value of a pattern
facet is the regular expression for the whole string, with implied anchors at the beginning and the end. The matches
function, on the other hand, tests whether a string contains any substring that matches the pattern. To indicate that a pattern should match the start and/or end of the entire string, anchors ^
(for the start of a string) and $
(for the end of the string) must be used.
The examples in the table focus on assertions that cannot be expressed with other facets. For example, to simply test whether a value starts with ABC
, you could use a pattern, as in <xs:pattern value="ABC.*"/>
. However, it usually requires an assertion to express that a value must not match a pattern or an enumeration, or to indicate that processing should be case-sensitive.
XPath 2.0 is a type-aware language, meaning that the processor pays attention to the types of values when performing operations on them. It is not valid in XPath 2.0 to compare an integer to a string, at least not with converting one value to the other’s type. Likewise, the built-in functions require arguments to be of a specific type. For example, the substring
function will not accept an integer as the first argument, because it is expecting a string.
The processor is getting the information about the type of the value from the simple type definition itself. For example, if the simple type is a restriction of integer
, then the value will be treated like an integer. Example 14–7 shows three simple types that have type errors in their assertions.
1. SizeType
is in error because the value is an integer and it is being passed to the string-length
function which expects a string.
2. DepartmentCodeType
is in error because the value is a string but it is being compared to a number.
3. EffectiveDateTimeType
is in error because the value is a date/time but it is being compared to a string.
<xs:simpleType name="SizeType">
<xs:restriction base="xs:integer">
<xs:assertion test="string-length($value) < 2"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="DepartmentCodeType">
<xs:restriction base="xs:string">
<xs:assertion test="$value != 001"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="EffectiveDateTimeType">
<xs:restriction base="xs:dateTime">
<xs:assertion test="$value > '2000-01-01T12:00:00'"/>
</xs:restriction>
</xs:simpleType>
Some processors will treat these type errors like dynamic errors, meaning that they are not reported as errors in the schema. Instead, dynamic errors simply cause the assertion to return false, rendering the element or attribute in question invalid. Most processors will issue warnings in these cases, though. XPath syntax errors and other static errors, on the other hand, will be flagged as errors in the schema by your processor.
To correct type errors like these, one should consider whether the simple types are being derived from the correct primitive types to start with. If you are performing arithmetic operations on a value, perhaps it should have a numeric type rather than a string type. For these examples, let’s assume that the primitive types were chosen correctly.
SizeType
is really trying to limit the size of the integer. In this case, it makes sense to change it to use one of the bounds facets to limit the value of the integer, instead of trying to constrain its string representation.
For DepartmentCodeType
, both operands in the comparison need to have the same type (or have types derived from each other). You could convert the $value
to a numeric type, but the best approach here is to put quotes around the 001
to make it a string. Comparing them as strings takes into account the leading zeroes, which may be significant in a string-based department code.
For EffectiveDateType
, as with the previous example, the operands need to be of comparable types. We could convert $value
to a string, but then it would compare the values as strings instead of date/time values, which would mean that time zones may not be taken into account correctly. Instead, it is preferable to convert the second operand to a date/time type. This is done in XPath 2.0 using a type constructor, which is a special kind of function whose name is the appropriate built-in type name. It accepts a single argument, the value to be converted. For example, xs:dateTime('2000-01-01T12:00:00')
converts the string to a date/time.
Example 14–8 shows our three examples, corrected to reflect the types of the values.
In addition to the type constructor functions, there is a string
function that converts a value to a string, and a number
function that converts a value to a floating-point number (double
). Both of these functions also take a single argument, the value to be converted.
<xs:simpleType name="SizeType">
<xs:restriction base="xs:integer">
<xs:maxExclusive value="100"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="DepartmentCodeType">
<xs:restriction base="xs:string">
<xs:assertion test="$value != '001'"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="EffectiveDateTimeType">
<xs:restriction base="xs:dateTime">
<xs:assertion test="$value >
xs:dateTime('2000-01-01T12:00:00')"/>
</xs:restriction>
</xs:simpleType>
Like other facets, assertions are inherited when a simple type restricts another simple type. Any assertions that are specified in the restriction are added to the constraints on that value. In other words, a value must conform to the assertions on its simple type and on any other simple types it restricts, directly or indirectly. Values of type NonOverheadDepartmentCodeType
in Example 14–9 must conform both to the assertion in that type and to the one specified in DepartmentCodeType
.
<xs:simpleType name="DepartmentCodeType">
<xs:restriction base="xs:token">
<xs:assertion test="not(contains($value,'X'))"/>
<xs:length value="3"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="NonOverheadDepartmentCodeType">
<xs:restriction base="DepartmentCodeType">
<xs:assertion test="substring($value,2,2) != '00'"/>
</xs:restriction>
</xs:simpleType>
In most cases, $value
evaluates to a single atomic value. However, when the value has a list type and consists of multiple items, $value
is a sequence of multiple atomic values. You can still do a comparison, such as $value > 2
, but that will return true if at least one of the values in the list is greater than 2. You can refer to specific values in the list using numeric predicates—for example, $value[1]
to get the first value in the list. Example 14–10 shows an assertion on a list type stating that the first item in the list must be 0
.
When working with multiitem sequences, there are a number of additional XPath functions that are useful. They are listed in Table 14–5. As we will see later, these functions are also useful on complex type assertions when there are repeating children—another example of multiitem sequences.
<xs:simpleType name="SizeListType">
<xs:restriction>
<xs:simpleType>
<xs:list itemType="xs:integer"/>
</xs:simpleType>
<xs:assertion test="$value[1] = 0"/>
</xs:restriction>
</xs:simpleType>
Table 14–6 shows some additional examples of XPath tests that are appropriate for list types.
The assertions in Table 14–6 apply to the list as a whole. If you want to constrain every value in the list, it makes more sense to put the assertion on the item type instead. Example 14–11 is a simple type SizeType
that has one assertion on the item type of the list (testing that the value is less than 12) and one assertion on the list itself (testing the number of items in the list).
<xs:simpleType name="SizeListType">
<xs:restriction>
<xs:simpleType>
<xs:list>
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:assertion test="$value < 12"/>
</xs:restriction>
</xs:simpleType>
</xs:list>
</xs:simpleType>
<xs:assertion test="count($value) > 2"/>
</xs:restriction>
</xs:simpleType>
For assertions on complex types, the assert
element is used instead of assertion
. The assert
element, whose syntax is shown in Table 14–7, can appear in a complex type extension
or restriction
, or can appear directly as a child of complexType
if neither simpleContent
nor complexContent
is used.
Example 14–12 shows a constraint where the valid values of a child element (number
) depend on the value of an attribute (dept
). Constraints that cross multiple elements or attributes are sometimes called co-constraints and are a common use case for complex type assertions. If it were just a constraint on the number
child individually, for example that it must be greater than 500 or less than 300, the assertion could have been put on the simple type of the number
element. However, an assertion on the number
element’s simple type would not have access to the dept
attribute since it is out of scope, so the assertion must be moved up to the product
parent.
<xs:element name="product" type="ProductType"/>
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="size" type="xs:integer"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
<xs:assert test="(@dept = 'ACC' and number > 500) or
(number < 300)"/>
</xs:complexType>
The same XPath operators and functions described in Sections 14.1.1.1 on p. 355 and 14.1.1.2 on p. 357 can be used in these complex type assertions. One difference is that in complex content assertions, you do not use the $value
variable (since there is no simple value) but instead use element and attribute names to indicate the values to be tested. Attribute names are preceded by an at sign (@
) in XPath, as shown in the reference to @dept
.
When element or attribute names are used, they are known as path expressions, and they are evaluated relative to the element being validated. In Example 14–12, it is looking for a dept
that is an attribute of product
(or any other element of type ProductType
), and a number
element that is a direct child of product
.
Path expressions can also have multiple steps, separated by forward slashes, that access elements and attributes further down in the element content. Example 14–13 shows a complex type CatalogType
that is one level up from ProductType
. To access the number
child of product
from there, it uses the multistep path product/number
. Relative to catalog
, product/number
brings back multiple number
elements and passes them all as a sequence to the max
function, then compares that maximum to the maxNumber
attribute of catalog
.
As an alternative to specifying the exact path down to a descendant element, you can use the shortcut .//
before an element name to indicate a descendant. For example, .//number
, relative to catalog
, will bring back all number
elements that are descendants anywhere within the catalog, at any level.
Path expressions often involve predicates, which are Boolean expressions in square brackets that filter the elements and attributes returned by the expression. An element to which a predicate is applied is only returned if the Boolean expression returns true. For example, product[number > 500]
will test for products whose number is greater than 500. Table 14–8 shows some examples of assertions using predicates that could apply to CatalogType
from Example 14–13.
<xs:element name="catalog" type="CatalogType"/>
<xs:complexType name="CatalogType">
<xs:sequence>
<xs:element name="product" type="ProductType"
maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="maxNumber" type="xs:integer"/>
<xs:assert test="not(max(product/number) > @maxNumber)"/>
</xs:complexType>
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="size" type="xs:integer"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
</xs:complexType>
For the second example in Table 14–8, you might think that you can use product[number > 500]
to test that product numbers are greater than 500. However, that will return true if there is at least one product number greater than 500; it does not ensure that all of the products have a number greater than 500. Using the not
function, as shown in the table, works because it tests that there aren’t any that are less than 500.
You may have noticed that most of the examples in the table actually return product
elements rather than a Boolean true/false value. The results of XPaths used in assertions are automatically converted to a Boolean value. A sequence of one or more elements or attributes is treated as a “true” value, and an empty sequence (no elements or attributes) is treated as a “false” value.
The XPath 2.0 language also includes an if-then-else construct, known as a conditional expression, that is very useful for co-constraints. It uses if
, then
, and else
keywords and the else
clause is always required. The other syntactic requirement is that the if
expression has to be in parentheses. Conditional expressions can be nested so that one conditional expression is embedded inside the then
or else
clause of another conditional expression.
Example 14–14 shows such an assertion which tests for different values of the dept
attribute to determine the valid range of the number
child. Since the else
clause is always required, it simply calls the false
function in the last clause, which means that if the department was not one of the three specified departments, the product is not valid, regardless of the number
child.
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="size" type="xs:integer"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
<xs:assert test="if (@dept = 'ACC')
then number > 500
else if (@dept = 'WMN')
then number <= 300 and number > 200
else if (@dept = 'MEN')
then number < 200
else false()"/>
</xs:complexType>
Assertions are inherited by derived complex types. Any assertions that are specified in an extension or restriction are added to the constraints on that type. In other words, an element must conform to the assertions on its complex type and on any other complex types from which its type is derived. Elements of type ExtendedProductType
in Example 14–15 must conform both to the assertion in that type and the one specified in ProductType
.
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
<xs:assert test="(@dept = 'ACC' and number > 500) or
(number < 300)"/>
</xs:complexType>
<xs:complexType name="ExtendedProductType">
<xs:complexContent>
<xs:extension base="ProductType">
<xs:sequence>
<xs:element name="size" type="xs:integer" minOccurs="0"/>
</xs:sequence>
<xs:assert test="if (@dept = 'ACC')
then not(size)
else true()"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
Assertions are also inherited in restrictions. Elements of type RestrictedProductType
in Example 14–16 must conform both to the assertion in that type and the one specified in ProductType
. Unlike the content model, which needs to be respecified in the restricted type definition, assertions are inherited automatically.
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string" minOccurs="0"/>
<xs:element name="size" type="xs:integer" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
<xs:assert test="(@dept = 'ACC' and number > 500) or
(number < 300)"/>
</xs:complexType>
<xs:complexType name="RestrictedProductType">
<xs:complexContent>
<xs:restriction base="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="size" type="xs:integer" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"
use="required"/>
<xs:assert test="if (@dept = 'ACC')
then not(size)
else true()"/>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
When a complex type with simple content extends a simple type, it can use assert
to add assertions to the simple type. This is useful as an alternative to using assertion
to restrict the content type, because the assert
allows access to the attributes while the assertion
doesn’t. The $value
variable can be used in this case; just like with simple types, $value
will contain the content of the element, with an appropriate data type. Example 14–17 shows an assertion that tests both the system
attribute and the value of the element.
<xs:complexType name="SizeType">
<xs:simpleContent>
<xs:extension base="xs:integer">
<xs:attribute name="system" type="xs:string"/>
<xs:assert test="if (@system='US')
then $value < 20
else $value >= 20"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
If your schema has a target namespace, it is necessary to correctly prefix the element and attribute names used in XPath expressions. Example 14–18 shows a schema with a target namespace as well as a name-space declaration that maps that namespace to the prefix prod
. The element names used in the assertion XPaths are then prefixed with prod
to indicate that they are in that namespace. Otherwise, the processor would be looking for those elements in no namespace.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://datypic.com/prod"
xmlns:prod="http://datypic.com/prod"
elementFormDefault="qualified">
<xs:element name="product" type="ProductType"/>
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="size" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
<xs:assert test="(@dept = 'ACC' and prod:number > 500) or
(prod:number < 300)"/>
<xs:assert test="if (@dept = 'ACC')
then not(prod:size)
else true()"/>
</xs:complexType>
</xs:schema>
Note the fact that elementFormDefault
is set to qualified
, which is what puts the locally declared number
and size
in the target namespace. Otherwise, you wouldn’t need to prefix their names in the XPath, since they would be unqualified. For more information on qualified and unqualified element names, see Section 6.3 on p. 98.
You might expect to be able to declare a default namespace, such as xmlns="http://datypic.com/prod"
, to avoid having to prefix the element names. However, regular default namespace declarations do not apply to XPath expressions. You can, however, use an xpathDefaultNamespace
attribute to designate the default namespace for all unprefixed element names that are used in the XPath. As with regular default namespace declarations, xpathDefaultNamespace
does not affect attribute names.
Example 14–19 uses the xpathDefaultNamespace
attribute on the schema
element. This means that the element names number
and size
in the XPaths are interpreted as being in the http://datypic.com/prod namespace. It is not looking for the dept
attribute in that namespace. This is appropriate since the attributeFormDefault
is defaulting to unqualified
, meaning that locally declared attributes are in no namespace.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://datypic.com/prod"
xmlns:prod="http://datypic.com/prod"
elementFormDefault="qualified"
xpathDefaultNamespace="http://datypic.com/prod">
<xs:element name="product" type="prod:ProductType"/>
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="size" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="dept" type="xs:string"/>
<xs:assert test="(@dept = 'ACC' and number > 500) or
(number < 300)"/>
<xs:assert test="if (@dept = 'ACC')
then not(size)
else true()"/>
</xs:complexType>
</xs:schema>
Instead of containing a specific namespace name, the xpathDefaultNamespace
attribute can contain one of three special keywords:
• ##targetNamespace
, indicating that the default XPath namespace is the same as the target namespace
• ##defaultNamespace
, indicating that the default XPath namespace is the namespace that is declared as the default (with an xmlns=
attribute)
• ##local
, indicating that there is no default XPath namespace
In Example 14–19, changing the value to ##targetNamespace
would have the same meaning, since http://datypic.com/prod is the target namespace.
It is most convenient to specify xpathDefaultNamespace
on the schema
element, in which case it applies to all XPath expressions in the schema document. It can also be specified on (and is relevant to) the following elements:
• The assert
and assertion
elements, where it affects the test
attribute
• The alternative
element, where it affects the test
attribute
• The selector
and field
elements, where it affects the xpath
attribute
If xpathDefaultNamespace
does not appear on one of these elements, the value is taken from the schema. If no value is provided for the schema, the default value is no namespace, meaning that unprefixed element names are interpreted as not being in a namespace.
Another new feature in XML Schema 1.1 is conditional type assignment, which allows for the type of an element to be assigned based on the values and/or presence of its attributes. A set of type alternatives are specified using alternative
elements, which appear as children of the element declaration.
The syntax of an alternative
element is shown in Table 14–9. It has a test
attribute that specifies the condition under which that type is selected, in the form of an XPath 2.0 expression. It also indicates the type if this condition is true, which is signified either by a type
attribute or an anonymous simpleType
or complexType
child.
Example 14–20 shows an example of conditional type assignment where there are three type alternatives:
1. The first alternative indicates that if the value of the dept
attribute is ACC
, the type assigned to the element declaration is AccessoryType
.
2. The second alternative indicates that if the value of the dept
attribute is either WMN
or MEN
, the type assigned is ClothingType
.
3. The third alternative has no test
attribute, indicating that ProductType
is the default type if neither of the two other alternatives apply.
<xs:element name="product">
<xs:alternative test="@dept='ACC'" type="AccessoryType"/>
<xs:alternative test="@dept='WMN' or @dept='MEN'"
type="ClothingType"/>
<xs:alternative type="ProductType"/>
</xs:element>
The processor will run through the alternatives and choose the first one in order whose test
returns true. If none of the tests return true, and there is a default type specified by an alternative with no test
attribute, as there is in Example 14–20, that alternative indicates the type.
It is also possible to use type alternatives even though you have already declared a type in the usual way, giving element
a type
attribute or a simpleType
or complexType
child. An example is shown in Example 14–21, where the type
attribute is used on element
to assign the type ProductType
to the element.
This is saying that ProductType
is the type for product
unless one of the alternatives applies. It is similar to the previous example, but defining it this way comes with the additional constraint that the type alternatives must be derived from the declared type. In this case, AccessoryType
and ClothingType
must be derived (directly or indirectly) from ProductType
.
<xs:element name="product" type="ProductType">
<xs:alternative test="@dept='ACC'" type="AccessoryType"/>
<xs:alternative test="@dept='WMN' or @dept='MEN'"
type="ClothingType"/>
</xs:element>
A third possibility is that neither a declared type nor a default type is specified, as in Example 14–22. In that case, if no alternatives apply, a product
element can contain any well-formed XML; its type is anyType
.
<xs:element name="product">
<xs:alternative test="@dept='ACC'" type="AccessoryType"/>
<xs:alternative test="@dept='WMN' or @dept='MEN'"
type="ClothingType"/>
</xs:element>
Only a very small subset of the XPath 2.0 syntax is allowed in the test
attribute by default, although some implementations may choose to support a more complete subset. The only XPath functions and operators that are allowed are:
• and
and or
Boolean operators
• Comparison operators (=
, !=
, <
, <=
, >
, >=
)
• The not
function
• The type constructor functions
In addition, the XPath expression can only access the attributes of the element being validated. It cannot access its parent or ancestors, and it cannot even access its children or descendants like assertions can.
Additional example values for the test
attribute are shown in Table 14–10.
The last example in the table makes use of the integer
type constructor function to ensure that the two values are being compared as numbers. Otherwise, they would be compared as strings, and a string 100
is considered to be less than a string 99
.
This highlights an important difference between assertions and conditional type assignment with regard to types in XPath. In assertions, type information is used in the XPath expressions because there is only one type to consider. In the case of conditional type assignment, the type has not even been assigned yet, so it is impossible to determine the types of the attributes. When num
is compared to a literal integer, as in the second-to-last example, it is automatically converted to an integer. But when num
and maxNum
are compared to each other, and neither has a type, they need to be converted to integers to ensure that they are compared appropriately.
A special built-in simple type named error
(in the XML Schema namespace) is defined for use in conditional type assignment.1 It is used to indicate that a validation error should be raised under certain conditions.
Example 14–23 uses the error
type to raise an error if the dept
attribute is equal to anything other than ACC
, WMN
, or MEN
.
<xs:element name="product">
<xs:alternative test="@dept='ACC'" type="AccessoryType"/>
<xs:alternative test="@dept='WMN' or @dept='MEN'"
type="ClothingType"/>
<xs:alternative type="xs:error"/>
</xs:element>
It doesn’t have to just be the last alternative, with no test, that uses the error
type. It can be used with a test, and as an earlier alternative, as shown in Example 14–24. This example will raise an error if the product does not have a dept
attribute.
<xs:element name="product">
<xs:alternative test="not(@dept)" type="xs:error"/>
<xs:alternative test="@dept='ACC'" type="AccessoryType"/>
<xs:alternative test="@dept='WMN' or @dept='MEN'"
type="ClothingType"/>
</xs:element>
As with assertions, if the schema has a target namespace, you may need to pay attention to namespace prefixes in your XPath. It is less likely to be an issue because you are only using attribute names and it is less common for attributes to be in the target namespace of the schema. However, if an attribute is in a namespace, for example because it is globally declared or because attributeFormDefault
is set to qualified
, its name does need to be prefixed.
Example 14–25 shows a revised example where dept
is globally declared, which means that it is in the target namespace. The XPath must now reflect the target namespace, so a prod
prefix is added to dept
wherever it appears in the XPaths.
As with assertions, the xpathDefaultNamespace
attribute affects the XPaths in type alternatives. However, since it does not affect attribute names, it is unlikely to be useful in conditional type assignment.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://datypic.com/prod"
xmlns:prod="http://datypic.com/prod"
elementFormDefault="qualified">
<xs:element name="product" type="prod:ProductType">
<xs:alternative test="@prod:dept='ACC'"
type="prod:AccessoryType"/>
<xs:alternative test="@prod:dept='WMN' or @prod:dept='MEN'"
type="prod:ClothingType"/>
</xs:element>
<xs:complexType name="ProductType">
<xs:sequence>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
<xs:attribute ref="prod:dept"/>
</xs:complexType>
<xs:attribute name="dept" type="xs:string"/>
<!--...-->
</xs:schema>
Section 7.6 on p. 126 introduced the concept of inherited attributes, which are relevant not just to an element but also to its descendant elements. Example 14–26 shows an instance example where a language
attribute is intended to be inherited by the first title
element from its parent workTitles
.
<workTitles language="en">
<title>Time Transfixed</title>
<title language="fr">La Durée poignardée</title>
</workTitles>
Example 14–27 is a schema that defines a type alternative for the title
element. It says that if the language is English, it has the type EnglishTitleType
, meaning that the contents can only contain basic Latin characters (which is admittedly very simplified). Otherwise, the title
element has the less restrictive type TitleType
that allows any string.
<xs:element name="workTitles" type="WorkTitlesType"/>
<xs:complexType name="WorkTitlesType">
<xs:sequence>
<xs:element name="title" maxOccurs="unbounded"
type="TitleType">
<xs:alternative test="@language='en'"
type="EnglishTitleType"/>
</xs:element>
</xs:sequence>
<xs:attribute name="language" type="xs:language"
inheritable="true"/>
</xs:complexType>
<xs:complexType name="EnglishTitleType">
<xs:simpleContent>
<xs:restriction base="TitleType">
<xs:pattern value="p{IsBasicLatin}+"/>
</xs:restriction>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="TitleType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="language" type="xs:language"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
The interesting thing about this example is that although the first title
element does not have a language
attribute in its start tag in the instance, in the XPath expressions it is treated as if it does, because the attribute is inherited. The instance in Example 14–26 is valid according to this schema.