Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Simple Types and Facets

A simple type describes text: the value of an attribute, or the content of an element that has no children and no attributes. XSD has many simple types built in, and you make your own by restricting them, by making lists of them, and by joining them in unions.

Built-in types

These are the built-in types you'll use most. All of them are in the namespace http://www.w3.org/2001/XMLSchema, usually with the prefix xs. The examples come from the draft where it has them:

TypeValuesExamples
xs:stringany textFlag States
xs:tokentext without leading, trailing or double spaces; these are removedtrust200902
xs:booleantrue, false, 1, 0true
xs:decimalexact decimal numbers1.0
xs:integerwhole numbers3, -3
xs:positiveInteger, xs:nonNegativeIntegerwhole numbers above zero, or from zero21
xs:doublefloating-point numbers1.5E3, INF
xs:date, xs:time, xs:dateTimedates and times of ISO 86012026-10-21, 09:30:00
xs:gYear, xs:gYearMontha year, a month of a year1997, 2026-10
xs:durationa length of timePT5M, PT10M
xs:anyURIa URIhttps://www.rfc-editor.org/info/rfc2119
xs:NCNamean XML name without a colonreq-7
xs:ID, xs:IDREFa name unique in the document, and a reference to oneterm-flag
xs:languagea language tagen-GB

vane.xsd uses several of them directly: the year of a date is an xs:gYear, the version of rfc an xs:integer, the target of a reference an xs:anyURI.

Each type has a lexical space, the texts that are allowed, and a value space, the values they stand for. 21, 021 and +21 are three texts for one xs:positiveInteger. That matters when values are compared, as XPath compares typed values, not their texts.

Restricting a type

Most simple types you write are restrictions: a built-in type, with facets that narrow down its values. These are the first simple types of vane.xsd:

  <!-- Simple types -->

  <xs:simpleType name="anchor">
    <xs:restriction base="xs:NCName"/>
  </xs:simpleType>

  <xs:simpleType name="draft-name">
    <xs:restriction base="xs:string">
      <xs:pattern value="draft-[a-z0-9]+(-[a-z0-9]+)*-[0-9]{2}"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="keyword">
    <xs:annotation>
      <xs:documentation>The requirement levels of BCP 14 (RFC 2119, RFC 8174).</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="MUST"/>
      <xs:enumeration value="MUST NOT"/>
      <xs:enumeration value="REQUIRED"/>
      <xs:enumeration value="SHALL"/>
      <xs:enumeration value="SHALL NOT"/>
      <xs:enumeration value="SHOULD"/>
      <xs:enumeration value="SHOULD NOT"/>
      <xs:enumeration value="RECOMMENDED"/>
      <xs:enumeration value="NOT RECOMMENDED"/>
      <xs:enumeration value="MAY"/>
      <xs:enumeration value="OPTIONAL"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="month">
    <xs:restriction base="xs:string">
      <xs:enumeration value="January"/>
      <xs:enumeration value="February"/>
      <xs:enumeration value="March"/>
      <xs:enumeration value="April"/>
      <xs:enumeration value="May"/>
      <xs:enumeration value="June"/>
      <xs:enumeration value="July"/>
      <xs:enumeration value="August"/>
      <xs:enumeration value="September"/>
      <xs:enumeration value="October"/>
      <xs:enumeration value="November"/>
      <xs:enumeration value="December"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="day">
    <xs:restriction base="xs:positiveInteger">
      <xs:maxInclusive value="31"/>
    </xs:restriction>
  </xs:simpleType>

The facets in them, and the others XSD has:

  • pattern is a regular expression that the whole text must match. The syntax is the one of XPath's matches, without ^ and $: a pattern always matches the whole value. draft-[a-z0-9]+(-[a-z0-9]+)*-[0-9]{2} is draft-, words of small letters and digits joined by hyphens, and a two-digit version.
  • enumeration lists the allowed values, one facet for each value. A keyword is one of the eleven key words of BCP 14, and a month one of the twelve names xml2rfc writes.
  • minInclusive, maxInclusive, minExclusive and maxExclusive limit ordered values: numbers, dates, durations. A day is a positive integer up to 31.
  • minLength, maxLength and length limit the length: the number of characters of a string, or the number of items of a list.
  • totalDigits and fractionDigits limit the digits of decimals.
  • whiteSpace says what happens to spaces: preserve keeps them, replace turns tabs and line ends into spaces, and collapse also removes leading, trailing and double spaces. xs:token is xs:string with whiteSpace set to collapse.

anchor restricts xs:NCName with no facets at all. That is still worth a name: the schema says what an anchor is once, and every attribute that holds one says type="anchor".

xs:annotation and xs:documentation hold text for people. They change nothing about validation, but tools can read them; the state machine's documentation in Documenting the Machine in XHTML does.

A draft with a mistake for three of the types:

$ sed -e 's/vane-protocol-01"/vane-protocol-1"/' \
      -e 's/month="October" day="21"/month="Oct" day="32"/' vane.xml \
  | sxml validate --schema=vane.xsd -
-:8:6: cvc-pattern-valid: attribute 'docName' has the value 'draft-example-vane-protocol-1', which is not valid for type 'draft-name': the value matches no pattern facet (XSD 1.1 Part 2, 4.3.4.4); the pattern facet is 'draft-[a-z0-9]+(-[a-z0-9]+)*-[0-9]{2}'
-:20:23: cvc-enumeration-valid: attribute 'month' has the value 'Oct', which is not valid for type 'month': the value is not one of the enumeration facets (XSD 1.1 Part 2, 4.3.5.4); the enumeration facets are 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', ...
-:20:35: cvc-maxInclusive-valid: attribute 'day' has the value '32', which is not valid for type 'day': the value is above the maxInclusive facet (XSD 1.1 Part 2, 4.3.7.4); the maxInclusive facet is '31'
-:7:1: cvc-assertion: element 'rfc' breaks the assertion 'not(@docName) or                        @docName = front/ser...' of an anonymous type derived from 'xs:anyType'

Each message names the facet that failed and the facet's value. When the text isn't even a value of the base type, as 26 isn't an xs:gYear in the chapter's first example, the message is cvc-datatype-valid.1.2.1 instead. The last message is a consequence of the first edit: the seriesInfo still says -01, so the two names no longer agree.

A restriction can restrict a type of your own, and the facets add up: a restriction of day with maxInclusive 29 allows 1 to 29. It can only narrow, never widen: a maxInclusive of 40 on a type whose maximum is 31 is an error in the schema.

$ echo '<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:simpleType name="day">
    <xs:restriction base="xs:positiveInteger"><xs:maxInclusive value="31"/></xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="long-day">
    <xs:restriction base="day"><xs:maxInclusive value="40"/></xs:restriction>
  </xs:simpleType>
</xs:schema>' | sxml validate --schema=- vane.xml
-:6:32: maxInclusive-valid-restriction: a maxInclusive facet must lie within the base type's bounds (XSD 1.1 Part 2, 4.3.7.4)

Lists

A list type holds several values in one text, separated by spaces. levels.xsd adds two types to the draft's, for a summary that names the key words a draft uses and the day of its next meeting:

levels.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Lists and unions over the simple types of vane.xsd. A summary of a
     draft names the key words it uses and the day of its next meeting:
       <summary levels="MUST SHOULD MAY" meeting="21"/> -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:include schemaLocation="vane.xsd"/>

  <!-- A list: key words separated by spaces, at most four of them. -->
  <xs:simpleType name="levels">
    <xs:restriction>
      <xs:simpleType>
        <xs:list itemType="keyword"/>
      </xs:simpleType>
      <xs:maxLength value="4"/>
    </xs:restriction>
  </xs:simpleType>

  <!-- A union: a day of the month, or the word "open". -->
  <xs:simpleType name="meeting-day">
    <xs:union memberTypes="day">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="open"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:union>
  </xs:simpleType>

  <xs:element name="summary">
    <xs:complexType>
      <xs:attribute name="levels" type="levels" use="required"/>
      <xs:attribute name="meeting" type="meeting-day"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

xs:include brings in the components of vane.xsd, so keyword and day can be used here; Namespaces and Larger Schemas says more about combining schema documents.

levels is a list of keyword, so MUST SHOULD MAY is three values, each a keyword. The list type is written inside a restriction, which adds a facet: on a list, length, minLength and maxLength count the items, so a summary names at most four levels.

$ echo '<summary levels="MUST SHOULD MAY" meeting="21"/>' | sxml validate --schema=levels.xsd -
$ echo '<summary levels="MUST SHOULD MAY MUST OPTIONAL"/>' | sxml validate --schema=levels.xsd -
-:1:10: cvc-maxLength-valid: attribute 'levels' has the value 'MUST SHOULD MAY MUST OPTIONAL', which is not valid for type 'levels': the value is longer than the maxLength facet (XSD 1.1 Part 2, 4.3.3.4); the maxLength facet is '4'

The items of a list are separated by whitespace, always. Two of the key words have a space inside, so a list of keywords can't hold them: MUST NOT is the two items MUST and NOT, and NOT is not a keyword:

$ echo '<summary levels="MUST NOT"/>' | sxml validate --schema=levels.xsd -
-:1:10: cvc-enumeration-valid: attribute 'levels' has the value 'MUST NOT', which is not valid for type 'levels': the value is not one of the enumeration facets (XSD 1.1 Part 2, 4.3.5.4)

A list type is the right choice for names and numbers, which never contain spaces. Lists are handy in attributes, where you can't repeat an element. XSD's own xs:IDREFS and xs:NMTOKENS are lists.

Unions

A union type allows the values of any of its member types. A meeting-day is a day, or the word open. The member types are tried in order, and the first that accepts the text gives the value its type. 32 is not a day, and soon is not open, so neither is a meeting-day:

$ echo '<summary levels="MAY" meeting="open"/>' | sxml validate --schema=levels.xsd -
$ echo '<summary levels="MAY" meeting="32"/>' | sxml validate --schema=levels.xsd -
-:1:23: cvc-datatype-valid.1.2.1: attribute 'meeting' has the value '32', which is not valid for type 'meeting-day'

The members can be named, with memberTypes="day", or written inside the xs:union, as the token is in levels.xsd, or both.

What validation does to values

A validated element or attribute has the value its type says, not the text it was written with. import schema gives a query the schema's declarations and validate validates a node and returns a typed copy (Typed Data and Schema-Aware Queries explains both). Then the values show what the schema did:

$ sxml xquery --method=adaptive --expression='
    import schema default element namespace "" at "levels.xsd";
    for $summary in (<summary levels=" MUST  MAY " meeting="07"/>,
                     <summary levels="MAY" meeting="open"/>)
    let $valid := validate { $summary }
    return (count(data($valid/@levels)), data($valid/@meeting),
            type-of(data($valid/@meeting)))'
2
7
"Q{}day"
1
"open"
"xs:token"

The levels are two values, because their type is a list, and the extra spaces are gone. 07 is the number 7, and its type is day, the member of the union that accepted it; open is a token. Q{}day is the type day in no namespace, written as an expanded name.

The same works on the draft. Its year is a year, so it compares with xs:gYear("2010"), and its day is a number:

$ sxml xquery --method=adaptive --context-item=vane.xml --expression='
    import schema default element namespace "" at "vane.xsd";
    let $draft := validate { / }
    return (
      data($draft/rfc/front/date/@day) + 1,
      $draft//reference[front/date/@year lt xs:gYear("2010")]/@anchor/string()
    )'
22
"RFC2119"
"RFC5234"

Whitespace is part of the value too. keyword restricts xs:string, whose whiteSpace is preserve, so a line break inside a bcp14, as an editor might make when it wraps a long line, makes the key word invalid (the message writes the line break as a space, so that each message stays on one line):

$ sed 's/<bcp14>MUST NOT</<bcp14>MUST\n        NOT</' vane.xml | sxml validate --schema=vane.xsd -
-:94:37: cvc-enumeration-valid: element 'bcp14' has the value 'MUST         NOT', which is not valid for type 'keyword': the value is not one of the enumeration facets (XSD 1.1 Part 2, 4.3.5.4); the enumeration facets are 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', ...

Had keyword restricted xs:token, the line break and the spaces would have collapsed to one space, and the text would be MUST NOT. Choose xs:token for words and names that people type, and xs:string when every space counts.

Facets that don't fit

Not every facet applies to every type: fractionDigits makes sense for decimals, not for strings, and a minimum above the maximum allows nothing. Such mistakes are errors in the schema, and sxml reports them before it reads any document:

$ echo '<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:simpleType name="anchor">
    <xs:restriction base="xs:NCName">
      <xs:fractionDigits value="2"/>
    </xs:restriction>
  </xs:simpleType>
</xs:schema>' | sxml validate --schema=- vane.xml
-:4:7: cos-applicable-facets: the facet does not apply to the base type's variety and primitive type (XSD 1.1 Part 2, 4.1.5)

XSD 1.1 adds two facets: assertion, an XPath condition on the value, and explicitTimezone, which requires or forbids a timezone on dates and times. XSD 1.1 Features shows both.