Import: XML Schema
Creates a data contract from an XML Schema (XSD) file.
Given this orders.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="orders">
<xs:complexType>
<xs:sequence>
<xs:element name="order_id" type="xs:string">
<xs:annotation><xs:documentation>Unique identifier of the order.</xs:documentation></xs:annotation>
</xs:element>
<xs:element name="order_timestamp" type="xs:dateTime"/>
<xs:element name="customer_id" type="xs:string"/>
<xs:element name="order_total" type="xs:integer"/>
<xs:element name="status" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Run:
datacontract import xsd --source orders.xsd
to produce the data contract:
version: 1.0.0
kind: DataContract
apiVersion: v3.2.0
id: my-data-contract
name: orders
status: draft
schema:
- name: orders
physicalType: object
logicalType: object
physicalName: orders
properties:
- name: order_id
physicalType: string
description: Unique identifier of the order.
logicalType: string
required: true
- name: order_timestamp
physicalType: dateTime
logicalType: timestamp
required: true
- name: customer_id
physicalType: string
logicalType: string
required: true
- name: order_total
physicalType: integer
logicalType: integer
required: true
- name: status
physicalType: string
logicalType: string
required: true
Install
The import needs the xml extra, which brings xmlschema to resolve includes, imports, groups, and type derivation:
pip install 'datacontract-cli[xml]'
Schemas
Each global element that no other element references becomes a schema, named after the element; its child elements and attributes become the schema's properties. A global element used only through ref is part of the element that references it, not a schema of its own. The contract is named after the single root element, or after the file when there are several.
Mapping
| XML Schema | Data contract |
|---|---|
| Global element | Schema with physicalType: object |
| Element with a complex type | logicalType: object with properties |
Element with maxOccurs greater than 1, or in an xs:sequence or xs:choice that repeats | logicalType: array with items; an element's own minOccurs/maxOccurs above 1 as minItems/maxItems |
minOccurs="0", nillable="true", inside xs:choice or an optional xs:sequence | not required |
xs:string, xs:token, xs:anyURI, xs:duration, xs:gYear, … | string |
xs:int, xs:long, xs:integer, xs:positiveInteger, xs:unsignedByte, … | integer, with the range of the type as minimum/maximum (xs:unsignedInt: 0 to 4294967295) unless a facet narrows it; xs:unsignedLong gets only its minimum, as its maximum is beyond the 64-bit integers the checks compare with. Sized types also get their ODCS format: xs:byte i8, xs:short i16, xs:int i32, xs:long i64, and u8 to u64 for the unsigned ones |
xs:decimal, xs:float, xs:double | number; xs:float and xs:double with the format f32 and f64 |
xs:boolean | boolean |
xs:date / xs:dateTime / xs:time | date / timestamp / time |
xs:enumeration | enum |
xs:pattern (several are alternatives) | logicalTypeOptions.pattern, anchored as ^(…)$ because XSD patterns match the whole value |
xs:length, xs:minLength, xs:maxLength | logicalTypeOptions.minLength / maxLength |
xs:minInclusive, xs:maxInclusive, xs:minExclusive, xs:maxExclusive | logicalTypeOptions.minimum / maximum / exclusiveMinimum / exclusiveMaximum; INF bounds nothing and is left out |
fixed on an element or attribute | enum with that one value |
xs:totalDigits, xs:fractionDigits | precision / scale custom properties, which the SQL exports read too (ODCS has no field for them) |
xs:union | string, with the member types in physicalType (date|string|integer) |
xs:list | string with physicalType: list: one space-separated text value |
xs:documentation | description |
Every property keeps the built-in XSD type it derives from in physicalType, such as dateTime or positiveInteger. A user-defined simple type contributes its facets and those of the types it derives from; a complex type contributes everything it inherits through xs:extension, xs:restriction, xs:group, and xs:attributeGroup. A type that contains itself stops at its first repetition, as an object without properties.
Attributes, text, and namespaces
ODCS has no notion of attributes, so the import marks them with custom properties. Given an element with an attribute and text, in a target namespace:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:example:shop" elementFormDefault="qualified">
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element name="price">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:decimal">
<xs:attribute name="currency" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="sku" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
the import creates:
schema:
- name: product
physicalType: object
customProperties:
- property: xmlNamespace
value: urn:example:shop
logicalType: object
physicalName: product
properties:
- name: price
physicalType: object
logicalType: object
required: true
properties:
- name: value
physicalType: decimal
customProperties:
- property: xmlNode
value: text
logicalType: number
- name: currency
physicalType: string
customProperties:
- property: xmlNode
value: attribute
logicalType: string
required: true
- name: sku
physicalType: string
customProperties:
- property: xmlNode
value: attribute
logicalType: string
required: true
xmlNode: attributemarks a property that is an attribute rather than a child element.xmlNode: textmarks the text of an element that has attributes; it is always namedvalue.xmlNamespaceon a schema holds the target namespace of its element. On a property, it holds the namespace of an element that is not in the schema's, and""an element in no namespace, such as the local elements of a schema withoutelementFormDefault="qualified".- An attribute named like a child element of the same element, or
valuenext to the element's text, gets an@prefix (@id), with a warning.
Testing XML files reads the text through xmlNode: text, and datacontract export xsd turns all three back into attributes, text content, and a target namespace, so a contract exports back to an equivalent XML Schema.
Includes and imports
xs:include, xs:import, and xs:redefine are followed for local files, relative to the schema that names them. Remote schemas (http://, https://) are never downloaded: they are skipped with a warning, and an element of a type they define becomes an object without properties. Rules of XML Schema the import does not depend on, such as an invalid restriction, are reported as warnings instead of failing the import.
Simplifications
ODCS cannot express everything an XML Schema can. The import warns about each simplification it makes and names the properties concerned, for example:
These properties are unions; ODCS has no union type, so they are strings with the member types in physicalType: order.reference
These properties contain their own type, so the repetition is an object without properties: category.parent
It warns about recursive types, unions, lists, wildcards, mixed content, identity constraints, substitution groups, XSD 1.1 assertions, attributes renamed with an @ prefix, and patterns with syntax only XSD has (\i, \c, character class subtraction), which are left out because datacontract test cannot run them.
Not imported
- Wildcards (
xs:any,xs:anyAttribute) and the text of mixed content. - Identity constraints (
xs:key,xs:keyref,xs:unique) and default or fixed values. - Substitution groups: an element is imported as declared, not with its substitutes.
- XSD 1.1 assertions (
xs:assert,xs:assertion). XSD 1.1 schemas are read, but their assertions are not imported.
All options: datacontract import xsd.