XSD 1.1 Features
XSD 1.1 keeps everything of XSD 1.0 and adds what users had missed
most: rules written in XPath, types chosen by attribute values, content
models that are open to extensions, and a clean way to change a schema
without copying it. sxml implements XSD 1.1, and validates XSD 1.0
schemas with the same engine. The machine's schemas already use some of
it, and vane-1.1.xsd, a stricter version of the annotations' schema,
uses the rest:
vane-1.1.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<!-- XSD 1.1 on the vane annotations: vane-data.xsd, overridden and
extended. Give it to the validator together with scxml.xsd: its
override then stands for vane-data.xsd, which scxml.xsd imports. -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
xmlns:vane="urn:example:vane"
targetNamespace="urn:example:vane"
elementFormDefault="qualified"
defaultAttributes="vane:traced"
vc:minVersion="1.1">
<xs:override schemaLocation="vane-data.xsd">
<!-- An event's type depends on whether it carries a payload. -->
<xs:element name="events">
<xs:complexType>
<xs:sequence>
<xs:element name="event" type="vane:event" maxOccurs="unbounded">
<xs:alternative test="@vane:payload" type="vane:event-with-payload"/>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:unique name="event-name">
<xs:selector xpath="vane:event"/>
<xs:field xpath="@name"/>
</xs:unique>
</xs:element>
<!-- A status may end with elements of other namespaces, so that a
vendor can add its own fields. -->
<xs:element name="status">
<xs:complexType>
<xs:openContent mode="suffix">
<xs:any namespace="##other" processContents="lax"/>
</xs:openContent>
<xs:sequence>
<xs:element name="flag" type="vane:flag-key"/>
<xs:element name="state" type="vane:state"/>
<xs:element name="failed-checks" type="vane:failure-count"/>
<xs:element name="retry-after" type="vane:seconds" minOccurs="0"/>
</xs:sequence>
<xs:assert test="exists(vane:retry-after) = (vane:state = 'paused')"/>
</xs:complexType>
</xs:element>
</xs:override>
<!-- A delay as SCXML writes it, such as "600s" or "250ms", of at most
an hour: the pattern checks the form, the assertion the size. -->
<xs:simpleType name="delay">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]+(s|ms)"/>
<xs:assertion test="xs:integer(replace($value, '[a-z]+$', ''))
le (if (ends-with($value, 'ms')) then 3600000 else 3600)"/>
</xs:restriction>
</xs:simpleType>
<!-- A moment in a log: a date and time that says its timezone. -->
<xs:simpleType name="moment">
<xs:restriction base="xs:dateTime">
<xs:explicitTimezone value="required"/>
</xs:restriction>
</xs:simpleType>
<!-- Every complex type of this document gets these attributes. -->
<xs:attributeGroup name="traced">
<xs:attribute ref="vane:implements"/>
</xs:attributeGroup>
<!-- An event without a payload: the XHTML description is optional. -->
<xs:complexType name="event">
<xs:sequence>
<xs:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"
minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" type="xs:token" use="required"/>
<xs:attribute ref="vane:payload"/>
</xs:complexType>
<!-- An event with a payload must say in XHTML what it carries. -->
<xs:complexType name="event-with-payload">
<xs:complexContent>
<xs:restriction base="vane:event">
<xs:sequence>
<xs:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"
minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" type="xs:token" use="required"/>
<xs:attribute ref="vane:payload" use="required"/>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
</xs:schema>
It is used together with scxml.xsd. The machine is valid by it too:
$ sxml validate --schema=vane-1.1.xsd --schema=scxml.xsd flag.scxml; echo "exit status $?"
exit status 0
Assertions on values
The assertion facet restricts a simple type by an XPath condition, in
which $value is the value being checked. SCXML writes the delay of a
send as a number and a unit, 600s or 250ms. A pattern can check the
form, and an assertion the size: delay allows at most an hour.
The second new facet, explicitTimezone, says whether dates and times
must have a timezone (required), must not have one (prohibited), or
may (optional, the default). A moment in a log is only one when its
timezone is known.
vane-data.xsd declares an element vane:value whose type is
xs:anySimpleType, any simple value at all, so that a value can be
checked against any of the schema's types with xsi:type:
$ for delay in 600s 250ms 2h 4000s; do \
echo "<vane:value xmlns:vane='urn:example:vane' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:type='vane:delay'>$delay</vane:value>" \
| sxml validate --schema=vane-1.1.xsd -; \
done
-:1:1: cvc-pattern-valid: element 'vane:value' has the value '2h', which is not valid for type 'vane:delay': the value matches no pattern facet (XSD 1.1 Part 2, 4.3.4.4); the pattern facet is '[0-9]+(s|ms)'
-:1:1: cvc-assertions-valid: the value '4000s' breaks an assertion of type 'vane:delay'
$ echo "<vane:value xmlns:vane='urn:example:vane' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:type='vane:moment'>2026-10-21T09:15:00</vane:value>" \
| sxml validate --schema=vane-1.1.xsd -
-:1:1: cvc-explicitTimezone-valid: element 'vane:value' has the value '2026-10-21T09:15:00', which is not valid for type 'vane:moment': the value's timezone is not what the explicitTimezone facet requires (XSD 1.1 Part 2, 4.3.14.4); the explicitTimezone facet is 'required'
Hours are not a unit SCXML has, and 4000 seconds is more than an hour. The moment has no timezone.
Assertions on elements
xs:assert in a complex type is a condition on the whole element, as
the draft's docName rule in Validating with XSD showed. The
context item is the element, with its children and attributes already
validated and typed. The machine's schemas have three:
- SCXML 1.0, section 3.5, says that a transition has an event, a
condition or a target, at least one of them.
scxml.xsdsaysexists(@event | @cond | @target)on the transition type. - Section 5.3.1 says that a
dataelement gets its value from anexpr, from asrc, or from its content, and from at most one of them:count((@expr, @src, *)) le 1. vane-data.xsdsays that a status has aretry-afterexactly when the flag is paused:exists(vane:retry-after) = (vane:state = 'paused'). The comparison of two booleans says "if and only if".
A machine and a status that break them:
$ sed -e 's|<data id="pauseSeconds" expr="300"|& src="durations.json"|' \
-e 's|<transition event="flag.evaluated">|<transition>|' flag.scxml \
| sxml validate --schema=scxml.xsd -
-:40:5: cvc-assertion: element 'Q{http://www.w3.org/2005/07/scxml}data' breaks the assertion 'count((@expr, @src, *)) le 1' of an anonymous type derived from 'xs:anyType'
-:75:5: cvc-assertion: element 'Q{http://www.w3.org/2005/07/scxml}transition' breaks the assertion 'exists(@event | @cond | @target)' of type 'Q{http://www.w3.org/2005/07/scxml}transition'
$ echo '<status xmlns="urn:example:vane"><flag>new-checkout</flag><state>stable</state><failed-checks>0</failed-checks><retry-after>300</retry-after></status>' \
| sxml validate --schema=vane-data.xsd -
-:1:1: cvc-assertion: element 'Q{urn:example:vane}status' breaks the assertion 'exists(vane:retry-after) = (vane:state = 'paused')' of an anonymous type derived from 'xs:anyType'
No structure could say any of the three. An assertion is reported at the end of its element, when all its content is known.
Conditional type assignment
xs:alternative chooses an element's type by a condition on its
attributes. The first alternative whose test is true gives the type;
one without a test is the fallback. The declared type is used when
no alternative applies, and each alternative's type must be derived
from it.
In vane-1.1.xsd, an event is a vane:event, whose XHTML
description is optional; an event with a vane:payload gets the type
vane:event-with-payload instead, a restriction that requires the
description: an event that carries data must say what it is. Without
its paragraph, status.response breaks the rule:
$ sed 's|<p xmlns="http://www.w3.org/1999/xhtml">The service answers with the state.</p>||' flag.scxml \
| sxml validate --schema=vane-1.1.xsd --schema=scxml.xsd -
-:22:5: cvc-complex-type.2.4: the content of element 'vane:event' is incomplete; expected an element of the wildcard's namespaces
The tests can only look at attributes, not children: the type must be
known before the children are read. An alternative whose type is
xs:error, a type that no element can be valid against, is how a
schema says "not this one at all".
Open content
xs:openContent lets elements that a content model doesn't name appear
among the ones it does. With mode="interleave", they may appear
anywhere; with mode="suffix", only at the end. The wildcard inside
says which elements. The status of vane-1.1.xsd is open at the end
for any element of another namespace, so a team that runs the service
can add its own fields, such as the error rate its checks measured:
$ echo '<status xmlns="urn:example:vane" xmlns:acme="urn:example:acme"><flag>new-checkout</flag><state>paused</state><failed-checks>3</failed-checks><retry-after>300</retry-after><acme:error-rate>0.07</acme:error-rate></status>' \
| sxml validate --schema=vane-1.1.xsd -; echo "exit status $?"
exit status 0
$ echo '<status xmlns="urn:example:vane" xmlns:acme="urn:example:acme"><flag>new-checkout</flag><acme:error-rate>0.07</acme:error-rate><state>paused</state><failed-checks>3</failed-checks><retry-after>300</retry-after></status>' \
| sxml validate --schema=vane-1.1.xsd -
-:1:89: cvc-complex-type.2.4: element 'acme:error-rate' is not allowed here; expected 'state'
-:1:128: cvc-complex-type.2.4: element 'state' is not allowed here; expected an element of another namespace
-:1:149: cvc-complex-type.2.4: element 'failed-checks' is not allowed here; expected an element of another namespace
-:1:181: cvc-complex-type.2.4: element 'retry-after' is not allowed here; expected an element of another namespace
In the middle, the error rate is not allowed. sxml takes it as the start of the suffix, so the fields after it are reported too: once the suffix has begun, only elements of other namespaces may follow.
xs:defaultOpenContent, at the top of a schema, gives every complex
type of the schema the same open content.
Default attributes
defaultAttributes on xs:schema names an attribute group that every
complex type of the schema document gets, unless it says
defaultAttributesApply="false". In vane-1.1.xsd, the group
traced holds vane:implements, so an event may now say which
requirements it implements, which vane-data.xsd doesn't allow:
$ sed 's|<vane:event name="check.passed">|<vane:event name="check.passed" vane:implements="req-4">|' flag.scxml > traced.scxml
$ sxml validate --schema=scxml.xsd traced.scxml
traced.scxml:10:37: cvc-complex-type.3.2.2: attribute 'vane:implements' is not allowed on element 'vane:event'
$ sxml validate --schema=vane-1.1.xsd --schema=scxml.xsd traced.scxml; echo "exit status $?"
exit status 0
Changing a schema: xs:override
vane-1.1.xsd doesn't copy vane-data.xsd: it names it in
xs:override, and replaces two of its components, the events and
status elements, keeping the rest. Every schema document that
includes or imports the overridden one sees the replacements, so
scxml.xsd's import of vane-data.xsd now finds the new events.
scxml.xsd itself is simplified in one place: its comment says that a
transition has at most one target. SCXML allows a list, target="motor light", to enter several states at once. This machine has one:
two-targets.scxml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- A transition with two targets: SCXML allows a list of states. -->
<scxml xmlns="http://www.w3.org/2005/07/scxml"
version="1.0" name="test_rig" initial="idle">
<state id="idle">
<transition event="test.start" target="motor light"/>
</state>
<state id="motor">
<transition event="test.stop" target="idle"/>
</state>
<state id="light"/>
</scxml>
scxml-targets.xsd overrides the transition type, with a list type for
the target. That is not enough: a keyref compares a field's whole value
with each key, and the list motor light is no state's id. The keyref
can't check each item of a list, so the override replaces the scxml
element too, and an assertion takes the keyref's place:
scxml-targets.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<!-- SCXML's full rule for targets: a transition may name several
states, as a list. scxml.xsd overridden: the transition type and the
scxml element, whose keyref for targets becomes an assertion. -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
xmlns:sc="http://www.w3.org/2005/07/scxml"
targetNamespace="http://www.w3.org/2005/07/scxml"
elementFormDefault="qualified"
vc:minVersion="1.1">
<xs:override schemaLocation="scxml.xsd">
<xs:complexType name="transition">
<xs:group ref="sc:executable" minOccurs="0" maxOccurs="unbounded"/>
<xs:attribute name="event" type="sc:event-descriptors"/>
<xs:attribute name="cond" type="xs:string"/>
<xs:attribute name="target" type="sc:state-ids"/>
<xs:attribute name="type" default="external">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="internal"/>
<xs:enumeration value="external"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:anyAttribute namespace="##other" processContents="lax"/>
<xs:assert test="exists(@event | @cond | @target)"/>
</xs:complexType>
<xs:element name="scxml">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="state" type="sc:state"/>
<xs:element name="final" type="sc:final"/>
<xs:element ref="sc:datamodel"/>
<xs:any namespace="##other" processContents="lax"/>
</xs:choice>
<xs:attribute name="version" type="xs:decimal" use="required" fixed="1.0"/>
<xs:attribute name="initial" type="xs:NCName"/>
<xs:attribute name="name" type="xs:NCName"/>
<xs:attribute name="datamodel" type="xs:NMTOKEN"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
<!-- A keyref compares the whole list with each id, so an
assertion checks the targets one by one. -->
<xs:assert test="every $target in .//sc:transition/data(@target)
satisfies $target = .//(sc:state | sc:final)/@id"/>
</xs:complexType>
<xs:key name="state-id">
<xs:selector xpath=".//sc:state | .//sc:final"/>
<xs:field xpath="@id"/>
</xs:key>
<xs:keyref name="initial-state" refer="sc:state-id">
<xs:selector xpath=". | .//sc:state"/>
<xs:field xpath="@initial"/>
</xs:keyref>
</xs:element>
</xs:override>
<xs:simpleType name="state-ids">
<xs:list itemType="xs:NCName"/>
</xs:simpleType>
</xs:schema>
$ sxml validate --schema=scxml-targets.xsd flag.scxml two-targets.scxml; echo "exit status $?"
exit status 0
$ sed 's/motor light/motor lights/' two-targets.scxml | sxml validate --schema=scxml-targets.xsd -
-:3:1: cvc-assertion: element 'Q{http://www.w3.org/2005/07/scxml}scxml' breaks the assertion 'every $target in .//sc:transition/data(@target) ...' of an anonymous type derived from 'xs:anyType'
The assertion catches the mistake, but it can only say that it failed,
not which target is wrong: the keyref's message pointed at the
transition. That is the trade the subset of scxml.xsd avoids, and a
reason to keep keys where they fit. Checking What the Schema
Cannot shows how a query reports each mistake on its
own.
Unlike XSD 1.0's xs:redefine, which sxml supports too, an override
doesn't have to be a restriction or extension of what it replaces.
Smaller changes
- In
xs:all, elements may havemaxOccursabove 1, and wildcards are allowed; a type whose content is anxs:allcan be extended by anotherxs:all. - Wildcards can exclude names with
notQNameand namespaces withnotNamespace. vc:minVersionandvc:maxVersion(in the namespacehttp://www.w3.org/2007/XMLSchema-versioning) on any schema element make it apply only to some versions of XSD, so a schema can be written today for processors of later versions. The schemas of this book sayvc:minVersion="1.1"onxs:schema: they need XSD 1.1, and a processor of an earlier version that knows the attribute skips them instead of misreading their assertions. sxml is version 1.1, sovc:minVersion="1.2"would hide an element from it.xs:error, as above, and the new built-in typesxs:dateTimeStamp(a dateTime with a required timezone),xs:dayTimeDuration,xs:yearMonthDurationandxs:anyAtomicType.