Complex types¶
A complex type describes an element that has child elements, attributes, or
both. Everything in a UBL document — from the Invoice root down to a single
cac:PartyName — is an instance of some complex type. This page builds a
UBL-style invoice from its pieces, starting with content models and working up
to real aggregates.
The running example is a small UBL invoice. The three standard namespaces are:
cbc:(Common Basic Components) holds leaf values likecbc:IDandcbc:Name.cac:(Common Aggregate Components) holds containers likecac:Partyandcac:InvoiceLinethat nest other components.
Content models¶
A complex type's children are governed by a content model: a compositor that says which children may appear and in what order. There are three.
| Compositor | Meaning |
|---|---|
xsd:sequence |
the listed elements appear in order |
xsd:choice |
exactly one of the listed elements appears |
xsd:all |
each listed element appears at most once, in any order |
UBL uses xsd:sequence almost everywhere — document structure is positional, so
the order of cbc:ID, cbc:IssueDate, and the rest is fixed by the schema:
| sequence.xsd | |
|---|---|
xsd:sequencefixes the order: an instance must listID, thenIssueDate, thenDocumentCurrencyCode. Reordering them is invalid.
xsd:choice is for "one of these" — a payment that is either a card or a
transfer, never both:
| choice.xsd | |
|---|---|
xsd:all lets the children appear in any order, each at most once. It is rare
in UBL because document order matters, but it is the right tool when order
genuinely does not:
| all.xsd | |
|---|---|
Cardinality with minOccurs and maxOccurs¶
Each particle in a content model carries an occurrence range. The defaults are
minOccurs="1" and maxOccurs="1" — so a plain <xsd:element> means exactly
one. Two overrides cover almost everything:
- Optional:
minOccurs="0"— may be absent. - Repeating:
maxOccurs="unbounded"— may appear any number of times.
| cardinality.xsd | |
|---|---|
- No occurrence attributes, so the defaults apply: exactly one
cbc:ID. minOccurs="0"makescbc:Noteoptional — an instance may omit it.maxOccurs="unbounded"allows one or morecac:InvoiceLinechildren. (It still requires at least one, becauseminOccursdefaults to1.)
Mapping back to the instance: the single <cbc:ID>INV-001</cbc:ID> satisfies
the required element, an absent cbc:Note is fine because it is optional, and a
document may carry many <cac:InvoiceLine> blocks under the one repeating
particle.
minOccurs="0" maxOccurs="unbounded" is the fully optional list
Combine the two to allow zero or more: an element that may be missing entirely or repeated. This is the usual shape for optional collections.
Referencing global elements with ref¶
So far the sequences have used ref= rather than name=. This is the heart of
how UBL composes documents. The cbc: and cac: schemas declare each component
once as a global element:
| globals.xsd | |
|---|---|
A document type then assembles itself by referring to those globals instead
of redeclaring them. The ref attribute points at a global element by its
qualified name; the referenced element brings its own name and type:
ref="cbc:ID"reuses the globalIDelement from thecbcnamespace — same name, same type, declared in one place and used everywhere.
name declares, ref reuses
Use name= to define a new element and ref= to point at one that
already exists. UBL leans heavily on ref: the basic components are defined
once and referenced by every document and aggregate that needs them.
Attributes¶
Attributes are declared with xsd:attribute, after any content model. Each has
a name, a type, and an optional use that says whether it is required:
| attributes.xsd | |
|---|---|
use="optional"is the default and may be omitted;use="required"forces the attribute to be present. (A third value,use="prohibited", is used when restricting a type — see below.)
simpleContent: a value plus an attribute¶
This is the pattern people forget, and it is everywhere in UBL. Sometimes you
want an element whose body is a simple value — a number or string — but
which still carries an attribute. A plain simple type cannot do this (it has
no attributes); a normal complex type cannot do it either (its body is child
elements, not text). The bridge is xsd:simpleContent.
The canonical case is a UBL amount: a decimal value with a currencyID
attribute, as in <cbc:LineExtensionAmount currencyID="EUR">10.90</cbc:LineExtensionAmount>.
You define it by extending a simple base type to bolt an attribute onto it:
xsd:simpleContentsays the element's body is a simple value (text), not child elements — but the type may still declare attributes.xsd:extension base="xsd:decimal"keeps the body typed as a decimal and adds to it. The element's text content must be a validxsd:decimal.- The added attribute.
currencyIDis required here, so an amount without a currency is invalid.
Valid — decimal body, required attribute present:
<cbc:LineExtensionAmount currencyID="EUR">10.90</cbc:LineExtensionAmount>
Invalid — currencyID missing (it is use="required"):
<cbc:LineExtensionAmount>10.90</cbc:LineExtensionAmount>
Invalid — body is not a decimal:
<cbc:LineExtensionAmount currencyID="EUR">free</cbc:LineExtensionAmount>
Reach for simpleContent whenever a value needs an attribute
If an element has both text content and an attribute, you need
xsd:simpleContent with xsd:extension. A bare simple type cannot hold the
attribute, and ordinary complex content expects child elements instead of
text. Every UBL amount, quantity, code, and measure that carries a unit or
scheme attribute is built this way.
complexContent: deriving one complex type from another¶
xsd:simpleContent extends a simple base. Its counterpart xsd:complexContent
derives from another complex type — either adding to it (xsd:extension) or
narrowing it (xsd:restriction).
Extension appends particles to the base type's content model:
xsd:extension base="cac:AddressType"inherits the base type'sStreetNameandCityNameparticles.- The new particle is appended after the inherited ones, giving the
sequence
StreetName,CityName,PostalZone.
Restriction goes the other way, tightening the base — for example making an optional element required or removing an attribute. The derived type must remain a valid subset of the base.
UBL prefers composition over deep inheritance
Although XSD supports it, UBL almost never builds tall derivation
hierarchies. Instead it composes: a type is a sequence of refs to
other components. cac:PartyType references cac:PartyName, which
references cbc:Name, and so on. When reading UBL, expect nesting by
reference, not subclassing.
Building up an aggregate¶
Composition is best seen end to end. Here is the supplier party, built from a
cbc: leaf upward into the cac: aggregates the instance uses.
Start with the basic component — a name, declared once as a global:
PartyNameTypewraps the basiccbc:Name.PartyTypereferencescac:PartyName— an aggregate built from a smaller aggregate.SupplierPartyTypereferencescac:Party. Each layer adds a level of nesting by reference, matching the instance:cac:AccountingSupplierParty > cac:Party > cac:PartyName > cbc:Name.
The invoice line is the other aggregate in the running example. It combines a
plain cbc:ID with the simpleContent amount type from earlier:
| invoiceline.xsd | |
|---|---|
- A simple identifier for the line.
- The amount with its
currencyIDattribute — thesimpleContenttype at work inside an aggregate.
Pulling it together, the document type is just a sequence of references to these components, with cardinality set per UBL's rules:
The invoice.xml at the top of this page is a valid instance of this
schema: required ID, IssueDate, and DocumentCurrencyCode in order, a
supplier party nested four levels deep, and one InvoiceLine whose
LineExtensionAmount carries the required currencyID.
Next¶
Every leaf above bottomed out in a cbc: value typed as xsd:decimal,
xsd:string, or a date — and those base types can be constrained far more
tightly. Simple types and restrictions shows how
to bound, pattern-match, and enumerate the values that fill these elements.