Keys and References
Types check each value on its own. Some rules are about values
together: no two states have the same id, every transition goes to a
state that exists, no event is declared twice. XSD's identity
constraints express them: xs:unique, xs:key and xs:keyref.
The machine's keys
The end of scxml.xsd declares three constraints on the scxml
element:
<!-- Every state has its own id, and every target and initial state is
one of them. -->
<xs:key name="state-id">
<xs:selector xpath=".//sc:state | .//sc:final"/>
<xs:field xpath="@id"/>
</xs:key>
<xs:keyref name="transition-target" refer="sc:state-id">
<xs:selector xpath=".//sc:transition"/>
<xs:field xpath="@target"/>
</xs:keyref>
<xs:keyref name="initial-state" refer="sc:state-id">
<xs:selector xpath=". | .//sc:state"/>
<xs:field xpath="@initial"/>
</xs:keyref>
</xs:element>
and vane-data.xsd declares one on the event declarations:
<xs:element name="events">
<xs:complexType>
<xs:sequence>
<xs:element name="event" maxOccurs="unbounded">
<xs:complexType>
<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>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:unique name="event-name">
<xs:selector xpath="vane:event"/>
<xs:field xpath="@name"/>
</xs:unique>
</xs:element>
An identity constraint has three parts:
- where it is declared: on the element whose content it covers. The
rule holds within each such element. The state ids are unique in
the whole machine, because the key is declared on
scxml; the event names are unique within eachvane:events. - a
selector, a path from that element to the elements the rule is about: everystateandfinalat any depth, everytransition, or eachvane:event; - one or more
fields, paths from each selected element to the values that make its key sequence, here@id,@target,@initialor@name.
The three kinds differ in what they require:
xs:unique: no two selected elements have the same key sequence. Elements that lack a field are left out.xs:key: the same, and every selected element must have every field. Every state has an id.xs:keyref: the key sequence of each selected element must be one of a key's (or a unique's) sequences. That's a foreign key, as in a database: a target is a state id. A transition without a target, asstatus.requestin thestablestate, is left out; so are the states without aninitial.
Checking a machine
This small machine has a mistake for each of the machine's rules:
bad-target.scxml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- A small machine with mistakes in its state ids and targets. -->
<scxml xmlns="http://www.w3.org/2005/07/scxml"
version="1.0" name="small_flag" initial="off">
<state id="stable">
<transition event="check.passed" target="rolling"/>
</state>
<state id="rolling-out">
<transition event="kill.request" target="stable"/>
</state>
<state id="stable" initial="steady">
<state id="steady"/>
</state>
</scxml>
$ sxml validate --schema=scxml.xsd bad-target.scxml
bad-target.scxml:11:3: cvc-identity-constraint.4.2.2: element 'Q{http://www.w3.org/2005/07/scxml}state': its key sequence is that of an earlier member (key 'Q{http://www.w3.org/2005/07/scxml}state-id')
bad-target.scxml:6:5: cvc-identity-constraint.4.3: element 'Q{http://www.w3.org/2005/07/scxml}transition': its key sequence is no key sequence of the referenced key (keyref 'Q{http://www.w3.org/2005/07/scxml}transition-target')
bad-target.scxml:9:5: cvc-identity-constraint.4.3: element 'Q{http://www.w3.org/2005/07/scxml}transition': its key sequence is no key sequence of the referenced key (keyref 'Q{http://www.w3.org/2005/07/scxml}transition-target')
bad-target.scxml:3:1: cvc-identity-constraint.4.3: element 'Q{http://www.w3.org/2005/07/scxml}scxml': its key sequence is no key sequence of the referenced key (keyref 'Q{http://www.w3.org/2005/07/scxml}initial-state')
- The third state has the id of the first,
stable. - The first transition goes to
rolling, which is no state's id: the state isrolling-out. - The second goes to
stable, which is an id, but twice: a keyref can't refer to a key value that is not unique, so it is reported too. Fix the duplicate first, and this one goes away. - The machine's
initialnamesoff, which doesn't exist. The nestedinitial="steady"is fine.
The elements and the constraints are written as expanded names,
Q{namespace}local, because the document writes them without a
prefix; an element with a prefix, such as vane:event below, is
written with it.
Each message names the constraint, so you can find it in the schema. They are reported in groups, by constraint, at the end of the element that the constraint is declared on: only then are all the values known.
A field's value must also be valid for its type, or it is no key
value at all. A state id is an xs:NCName, so an id with spaces in it
is a mistake of the attribute, and then of the key: the state has no
id that the key could hold, which xs:key doesn't allow, and the
transition from checking to paused now names no state:
$ sed 's/<state id="paused"/<state id="paused for now"/' flag.scxml \
| sxml validate --schema=scxml.xsd -
-:80:10: cvc-datatype-valid.1.2.1: attribute 'id' has the value 'paused for now', which is not valid for type 'xs:NCName'
-:80:3: cvc-identity-constraint.4.2.1: element 'Q{http://www.w3.org/2005/07/scxml}state': a key field has no value (key 'Q{http://www.w3.org/2005/07/scxml}state-id')
-:63:5: cvc-identity-constraint.4.3: element 'Q{http://www.w3.org/2005/07/scxml}transition': its key sequence is no key sequence of the referenced key (keyref 'Q{http://www.w3.org/2005/07/scxml}transition-target')
The first message is the one to fix; the other two follow from it. (In
xs:unique, a field without a valid value leaves the element out,
as a missing field does; Reading Validation Errors
shows that on the draft's anchors.)
The event declarations have their own rule:
duplicate-event.scxml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- A machine that declares one of its events twice. -->
<scxml xmlns="http://www.w3.org/2005/07/scxml"
xmlns:vane="urn:example:vane"
version="1.0" name="small_flag" initial="stable">
<vane:events>
<vane:event name="kill.request"/>
<vane:event name="rollout.request" vane:payload="vane:rollout"/>
<vane:event name="kill.request"/>
</vane:events>
<state id="stable">
<transition event="rollout.request" target="rolling-out"/>
</state>
<state id="rolling-out">
<transition event="kill.request" target="stable"/>
</state>
</scxml>
$ sxml validate --schema=scxml.xsd duplicate-event.scxml
duplicate-event.scxml:9:5: cvc-identity-constraint.4.1: element 'vane:event': its key sequence is that of an earlier member (unique 'Q{urn:example:vane}event-name')
Values are compared as typed values, not as text. An event name is an
xs:token, whose spaces collapse, so name=" kill.request" would be a
duplicate too; and when a field's type is xs:integer, 7 and 007
are the same key.
Paths in constraints
The paths of selector and field are a small subset of XPath: child
steps, .// at the start, . for the element itself, @ for
attributes, *, and | to combine paths. There are no predicates and
no functions. . | .//sc:state in initial-state selects the machine
and every state, the elements that may have an initial.
Names with a namespace need a prefix that the schema binds, as
scxml.xsd binds sc. Without it, .//transition means transitions
in no namespace, and in a machine, where every transition is in
SCXML's namespace, the keyref silently selects nothing. Only the two
other mistakes are left:
$ sed 's|xpath=".//sc:transition"|xpath=".//transition"|' scxml.xsd > loose.xsd
$ sxml validate --schema=loose.xsd bad-target.scxml
bad-target.scxml:11:3: cvc-identity-constraint.4.2.2: element 'Q{http://www.w3.org/2005/07/scxml}state': its key sequence is that of an earlier member (key 'Q{http://www.w3.org/2005/07/scxml}state-id')
bad-target.scxml:3:1: cvc-identity-constraint.4.3: element 'Q{http://www.w3.org/2005/07/scxml}scxml': its key sequence is no key sequence of the referenced key (keyref 'Q{http://www.w3.org/2005/07/scxml}initial-state')
In XSD 1.1, xpathDefaultNamespace="##targetNamespace" on xs:schema
(or on a selector or field) makes unprefixed names in the paths
mean the target namespace, which avoids the mistake.
The value of refer is a QName too: the keyrefs say
refer="sc:state-id", because the key is a component of the target
namespace like any other.
Keys or IDs?
xs:ID and xs:IDREF are simple types with a similar purpose: an ID
is unique in the whole document, and an IDREF must be one of the
document's IDs. They are older and simpler, but blunt: one namespace of
ids for the whole document, only names (no numbers or dates), and only
one field. The Recommendation's own schema for SCXML gives state ids
the type xs:ID. Use keys when the rule belongs to a part of the
document, or when the key is a number, a date or several values.
Choosing between xs:key and xs:unique is choosing whether the field
is required. The draft's schema, vane.xsd, makes its anchors unique
with an xs:unique, because most paragraphs have no anchor: only
requirements do. With an xs:key in its place, every selected element
must have one, and the draft has six paragraphs without (vane.xml
here is a link to the draft in the spec directory, and vane.xsd to
its schema):
$ sed 's/<xs:unique name="anchor">/<xs:key name="anchor">/; s|</xs:unique>|</xs:key>|' vane.xsd \
| sxml validate --schema=- vane.xml 2>&1 | head -3
vane.xml:25:7: cvc-identity-constraint.4.2.1: element 't': a key field has no value (key 'anchor')
vane.xml:35:7: cvc-identity-constraint.4.2.1: element 't': a key field has no value (key 'anchor')
vane.xml:42:9: cvc-identity-constraint.4.2.1: element 't': a key field has no value (key 'anchor')
A keyref may refer to a unique as well as to a key, which is what the
draft's xref-target does.