Complex Types and Content Models
A complex type describes an element that has attributes, children,
or both. Its content model says which children it may have, how
many, and in which order. This section reads the rest of vane.xsd
from the top, and finds every kind of content XSD has in the draft.
Elements and their declarations
xs:element declares an element: its name and its type. An element
declared directly in xs:schema is global: it can be the document
element, and other declarations can refer to it with ref. The others
are local: they exist only inside the content model where they are
declared. In vane.xsd, rfc is the only global element, and every
other element is local.
A type can be named, with xs:complexType name="section" at the top
level, and used by name, type="section"; or anonymous, written
inside the element declaration, as for rfc and middle. Name a type
when it's used in more than one place, or when you want to refer to it
from XQuery or XSLT. vane.xsd names most of its types: front is
used by the draft and by every reference, text by every paragraph,
dt and dd, and section by sections at every depth.
Local declarations can give the same name different types in different
places. t in a section has the type text; name in a section is a
plain string; and front declares its own title, which is not the
title of anything else.
Sequences, choices and all
Here are the front matter and the section:
<!-- Sections -->
<xs:complexType name="section">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="t" type="text"/>
<xs:element name="dl" type="definition-list"/>
<xs:element name="sourcecode" type="sourcecode"/>
</xs:choice>
<xs:element name="section" type="section" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="anchor" type="anchor" use="required"/>
</xs:complexType>
<xs:complexType name="front">
<xs:sequence>
<xs:element name="title" type="title"/>
<xs:element name="seriesInfo" type="series-info" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="author" type="author" maxOccurs="unbounded"/>
<xs:element name="date" type="date"/>
<xs:element name="area" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="keyword" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="abstract" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="t" type="text" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
Three model groups combine the children:
xs:sequence: the children in this order. The front matter has a title, then the series information, the authors, the date, and the rest.xs:choice: exactly one of the alternatives, each time the choice occurs. After itsname, a section has any number of blocks, each a paragraph, a definition list or source code, in any order; then its subsections. Groups nest freely, so a choice inside a sequence is common.xs:all: each child at most once, in any order.vane.xsdhas none, but xml2rfc's address is a good use: an email, a phone number and a web address, in whatever order the author wrote them.
minOccurs and maxOccurs on an element or a group say how often it
may occur. Both are 1 by default; minOccurs="0" makes it optional, and
maxOccurs="unbounded" removes the upper limit. A draft has one or more
authors and zero or more keywords; on the section's choice, the two
attributes let the choice repeat.
xsd-author.xsd describes an author with a fuller address:
xsd-author.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<!-- An author with more of xml2rfc's address: an xs:all, whose children
may come in any order, and an attribute group shared by two types. -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- The names of a person, as xml2rfc writes them. -->
<xs:attributeGroup name="names">
<xs:attribute name="fullname" type="xs:string" use="required"/>
<xs:attribute name="initials" type="xs:string"/>
<xs:attribute name="surname" type="xs:string" use="required"/>
</xs:attributeGroup>
<xs:complexType name="address">
<xs:all>
<xs:element name="email" type="xs:string"/>
<xs:element name="phone" type="xs:string" minOccurs="0"/>
<xs:element name="uri" type="xs:anyURI" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:element name="author">
<xs:complexType>
<xs:sequence>
<xs:element name="organization" type="xs:string" minOccurs="0"/>
<xs:element name="address" type="address" minOccurs="0"/>
</xs:sequence>
<xs:attributeGroup ref="names"/>
</xs:complexType>
</xs:element>
<!-- A contact of the working group: the same names, no address. -->
<xs:element name="contact">
<xs:complexType>
<xs:attributeGroup ref="names"/>
<xs:attribute name="role" type="xs:token" default="chair"/>
</xs:complexType>
</xs:element>
</xs:schema>
The address may list its children in any order, but each only once:
$ echo '<author fullname="Ada Byron" surname="Byron"><address><uri>https://example.com/</uri><email>ada@example.com</email></address></author>' | sxml validate --schema=xsd-author.xsd -
$ echo '<author fullname="Ada Byron" surname="Byron"><address><email>ada@example.com</email><phone>1</phone><phone>2</phone></address></author>' | sxml validate --schema=xsd-author.xsd -
-:1:101: cvc-complex-type.2.4: element 'phone' is not allowed here; expected 'uri'
Attributes
xs:attribute declares an attribute of a complex type, always with a
simple type. Attributes are optional unless use="required": every
section must have an anchor, while a paragraph may have one. A
default value is added when the attribute is missing; a fixed value
is added too, and an attribute that is there must have exactly that
value. The version of rfc is fixed at 3.
The default is part of the validated document. A contact of
xsd-author.xsd that doesn't say its role has the role chair after
validation, and a draft that doesn't say its version has version 3, an
integer:
$ sxml xquery --method=adaptive --expression='
import schema default element namespace "" at "xsd-author.xsd";
(validate { <contact fullname="Grace Hopper" surname="Hopper"/> })/@role/string()'
"chair"
$ sxml xquery --method=adaptive --expression='
import schema default element namespace "" at "vane.xsd";
let $draft := validate {
<rfc>
<front><title>T</title><author fullname="A B" surname="B"/><date year="2026"/></front>
<middle><section anchor="intro"><name>Introduction</name></section></middle>
</rfc>
}
return ($draft/@version/string(), type-of(data($draft/@version)))'
"3"
"xs:integer"
Four kinds of content
The content of a complex type is one of four kinds, and the draft has all four:
- Element-only: children, and no text except whitespace between
them. That's
front,sectionandrfc. - Mixed: children with text between them, with
mixed="true". That'stext, the type of every paragraph, which allows the inline elements anywhere in its text, as often as you like. - Simple: text of a simple type, plus attributes, with
xs:simpleContent. Atitleis a string with anabbrevattribute; asourcecodeis a string with atypeand aname. - Empty: no content at all, only attributes. A complex type with no
model group is empty, as
dateandseriesInfoare.
Here are the mixed and the simple types:
<!-- Text: paragraphs and the inline elements in them -->
<xs:group name="inline">
<xs:choice>
<xs:element name="xref" type="xref"/>
<xs:element name="bcp14" type="keyword"/>
<xs:element name="em" type="xs:string"/>
<xs:element name="strong" type="xs:string"/>
<xs:element name="tt" type="xs:string"/>
<xs:element name="cref" type="cref"/>
</xs:choice>
</xs:group>
<xs:complexType name="xref" mixed="true">
<xs:attribute name="target" type="anchor" use="required"/>
</xs:complexType>
<xs:complexType name="cref" mixed="true">
<xs:attribute name="anchor" type="anchor"/>
<xs:attribute name="source" type="xs:string"/>
</xs:complexType>
<xs:complexType name="text" mixed="true">
<xs:group ref="inline" minOccurs="0" maxOccurs="unbounded"/>
<xs:attribute name="anchor" type="anchor"/>
</xs:complexType>
<xs:complexType name="definition-list">
<xs:sequence maxOccurs="unbounded">
<xs:element name="dt" type="text"/>
<xs:element name="dd" type="text"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="sourcecode">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="type" type="xs:token"/>
<xs:attribute name="name" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
xref and cref are mixed types without a model group: text, but no
child elements. That's how xml2rfc writes them: <xref target="term-flag">flag</xref> has the text a reader sees, and
<xref target="RFC2119"/> has none, so the renderer writes the
reference's name.
Mistakes in content
This draft breaks a rule of each kind:
bad-content.xml:
<rfc version="2">
<front>
<author fullname="Ada Byron" surname="Byron"/>
<title>Vane in Brief</title>
<date year="2026">October</date>
See the mailing list.
</front>
<middle>
<section>
<name>Introduction</name>
<t keyword="MUST">A flag <bcp14>MUST</bcp14> start stable.</t>
<name>Again</name>
</section>
</middle>
</rfc>
$ sxml validate --schema=vane.xsd bad-content.xml
bad-content.xml:1:6: cvc-au: attribute 'version' has the value '2', not its fixed value '3'
bad-content.xml:3:5: cvc-complex-type.2.4: element 'author' is not allowed here; expected 'title'
bad-content.xml:4:5: cvc-complex-type.2.4: element 'title' is not allowed here; expected 'author' or 'date'
bad-content.xml:5:23: cvc-complex-type.2.1: the text 'October' is not allowed: element 'date' has empty content
bad-content.xml:5:37: cvc-complex-type.2.3: the text ' See the mailing list. ' is not allowed: element 'front' has element-only content
bad-content.xml:9:5: cvc-complex-type.4: element 'section' lacks the required attribute 'anchor'
bad-content.xml:11:10: cvc-complex-type.3.2.2: attribute 'keyword' is not allowed on element 't'
bad-content.xml:12:7: cvc-complex-type.2.4: element 'name' is not allowed here; expected 't', 'dl', 'sourcecode' or 'section'
Read them in order:
- The version is 2, not the fixed 3.
- The author comes before the title. sxml takes the title as missing, goes on after the author, and then finds the title too late.
- The date has text, but its type is empty: the month belongs in the
monthattribute. - The front matter has text between its elements; only whitespace is allowed there.
- The section has no anchor, which is required.
- The paragraph has an attribute that
textdoesn't declare. Unlike elements in some other schema languages, attributes must be declared too. - The section has a second
name, after a paragraph, where only blocks and subsections may come.
cvc-complex-type.2.4 is the message you'll see most. It says which
element came where it isn't allowed, and lists the elements that the
content model would accept at that point, which usually shows you the
fix.
Reusing parts of content models
When several types share attributes or children, name the shared part
once. xs:group names a model group, and xs:attributeGroup a set of
attributes:
vane.xsdnames the inline elementsinline, andtextrefers to the group with<xs:group ref="inline" .../>. A schema for more of xml2rfc would refer to it from the other places that allow inline text, such as list items and table cells.xsd-author.xsdnames the attributes of a personnames, and bothauthorandcontactrefer to them.
$ echo '<contact surname="Hopper"/>' | sxml validate --schema=xsd-author.xsd -
-:1:1: cvc-complex-type.4: element 'contact' lacks the required attribute 'fullname'
A reference to a group is the same as writing its content in that place. Groups share parts; the next section shows how types share more, by deriving one type from another.