<?xml version="1.0" encoding="UTF-8"?>
<!--
    The XML form of an xldr mapping specification. Reference it from a spec with

        <mappingSpec xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     xsi:noNamespaceSchemaLocation="https://ralfspoeth.github.io/xldr/schema/mapping-spec-0.21.xsd">

    which an editor validates against and which the reader ignores, since a spec
    carries no namespace of its own.

    What this schema cannot express, being XSD 1.0: that a field mapping carries
    exactly one value source, that a var carries one that is not a fieldSelector,
    and that an input declares exactly one of accepts or sentinel. Those are
    checked when the spec is read.

    Every element takes an optional comment attribute, which the reader ignores.
    An attribute this schema does not name is refused, an unknown one being far
    more often a misspelling than an annotation - so the annotation is named.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

    <!--
        The reader ignores elements it does not know, but a wildcard for them
        cannot be expressed here: XSD 1.0 forbids a content model in which an
        element could match both a named particle and a wildcard. Annotate an XML
        spec with XML comments, which are always allowed.
    -->
    <xs:element name="mappingSpec">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="input" type="input"/>
                <xs:element name="mapping" type="recordMapping" minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
            <xs:attribute name="comment" type="xs:string"/>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="input">
        <xs:sequence>
            <xs:element name="properties" type="properties" minOccurs="0"/>
            <xs:element name="var" type="var" minOccurs="0" maxOccurs="unbounded"/>
            <xs:element name="recordSelector" type="recordSelector" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
        <xs:attribute name="mimeType" type="xs:string" use="required"/>
        <!-- exactly one of the two, which XSD 1.0 cannot state -->
        <xs:attribute name="accepts" type="filePattern"/>
        <xs:attribute name="sentinel" type="filePattern"/>
        <xs:attribute name="comment" type="xs:string"/>
    </xs:complexType>

    <!--
        The settings of the adapter the mimeType selects, e.g. fieldSeparator,
        header, charset, dateFormat, numberFormat, locale, linesPerRecord or
        ns.<prefix>. Which of them mean anything depends on the adapter, so any
        attribute is allowed here.
    -->
    <xs:complexType name="properties">
        <xs:anyAttribute processContents="skip"/>
    </xs:complexType>

    <xs:complexType name="recordSelector">
        <xs:sequence>
            <xs:element name="fieldSelector" type="fieldSelector" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
        <xs:attribute name="name" type="xs:string" use="required"/>
        <!--
            optional: omit it where the whole file holds one kind of record,
            as in a CSV with a header or a fixed-length file
        -->
        <xs:attribute name="selector" type="xs:string"/>
        <xs:attribute name="comment" type="xs:string"/>
    </xs:complexType>

    <xs:complexType name="fieldSelector">
        <xs:attribute name="name" type="xs:string" use="required"/>
        <xs:attribute name="selector" type="xs:string" use="required"/>
        <xs:attribute name="type" type="dataType"/>
        <xs:attribute name="comment" type="xs:string"/>
    </xs:complexType>

    <!-- a var is evaluated with no record in hand, so fieldSelector is not among its sources -->
    <xs:complexType name="var">
        <xs:sequence>
            <xs:element name="lookup" type="lookup" minOccurs="0"/>
        </xs:sequence>
        <xs:attribute name="name" type="xs:string" use="required"/>
        <xs:attribute name="constant" type="xs:string"/>
        <xs:attribute name="var" type="xs:string"/>
        <xs:attribute name="expr" type="xs:string"/>
        <xs:attribute name="comment" type="xs:string"/>
    </xs:complexType>

    <xs:complexType name="recordMapping">
        <xs:sequence>
            <xs:element name="fieldMapping" type="fieldMapping" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
        <xs:attribute name="recordSelector" type="xs:string" use="required"/>
        <xs:attribute name="table" type="xs:string" use="required"/>
        <xs:attribute name="limit" type="xs:nonNegativeInteger"/>
        <xs:attribute name="comment" type="xs:string"/>
    </xs:complexType>

    <!--
        One target column and exactly one source, the lookup being a child
        element. A constant here is always a string: an attribute carries no
        type, and there is no way to write the null a JSON spec can.
    -->
    <xs:complexType name="fieldMapping">
        <xs:sequence>
            <xs:element name="lookup" type="lookup" minOccurs="0"/>
        </xs:sequence>
        <xs:attribute name="column" type="xs:string" use="required"/>
        <xs:attribute name="fieldSelector" type="xs:string"/>
        <xs:attribute name="constant" type="xs:string"/>
        <xs:attribute name="var" type="xs:string"/>
        <xs:attribute name="expr" type="xs:string"/>
        <xs:attribute name="comment" type="xs:string"/>
    </xs:complexType>

    <xs:complexType name="lookup">
        <xs:attribute name="table" type="xs:string" use="required"/>
        <xs:attribute name="column" type="xs:string" use="required"/>
        <xs:attribute name="keyColumn" type="xs:string" use="required"/>
        <xs:attribute name="fieldSelector" type="xs:string"/>
        <xs:attribute name="constant" type="xs:string"/>
        <xs:attribute name="var" type="xs:string"/>
        <xs:attribute name="expr" type="xs:string"/>
        <xs:attribute name="comment" type="xs:string"/>
    </xs:complexType>

    <!-- a file name pattern, in the glob: or regex: form of FileSystem.getPathMatcher -->
    <xs:simpleType name="filePattern">
        <xs:restriction base="xs:string">
            <xs:pattern value="(glob|regex):.+"/>
        </xs:restriction>
    </xs:simpleType>

    <!-- the Java type the value is delivered as; matched case-insensitively -->
    <xs:simpleType name="dataType">
        <xs:restriction base="xs:string">
            <xs:pattern value="[Tt][Ee][Xx][Tt]|[Ii][Nn][Tt][Ee][Gg][Rr][Aa][Ll]|[Ff][Pp]|[Dd][Ee][Cc][Ii][Mm][Aa][Ll]|[Dd][Aa][Tt][Ee]"/>
        </xs:restriction>
    </xs:simpleType>

</xs:schema>
