<?xml version="1.0"?>
<grammar
  xmlns="http://relaxng.org/ns/structure/1.0"
  xmlns:sch="http://purl.oclc.org/dsdl/schematron"
  ns="http://www.isocat.org/ns/dcif"
  datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
>
	
	<sch:ns uri="http://www.isocat.org/ns/dcif" prefix="dcif"/>
	
	<sch:pattern name="known language">
		<sch:rule context="dcif:language" role="warning">
			<sch:let name="lang" value="."/>
			<sch:let name="languages" value="document('http://www.isocat.org/rest/info/languages.xml')"/>
			<sch:assert test="exists(index-of(($languages//language/@tag,tokenize($languages//language/@tags,'\s+')),$lang))">This language isn't known by ISOcat.</sch:assert>
		</sch:rule>
	</sch:pattern>
	
	<sch:pattern name="known profile">
		<sch:rule context="dcif:profile">
			<sch:let name="prof" value="."/>
			<sch:let name="profiles" value="document('http://www.isocat.org/rest/info/profiles.xml')"/>
			<sch:assert test="$profiles//profile/@name = $prof">This profile isn't known by ISOcat.</sch:assert>
		</sch:rule>
	</sch:pattern>
	
	<sch:pattern name="known data type">
		<sch:rule context="dcif:dataType">
			<sch:let name="type" value="."/>
			<sch:let name="types" value="document('http://www.isocat.org/rest/info/data-types.xml')"/>
			<sch:assert test="$types//datatype/@name = $type">This data type isn't known by ISOcat.</sch:assert>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="known rule type">
		<sch:rule context="dcif:ruleType">
			<sch:let name="type" value="."/>
			<sch:let name="types" value="document('http://www.isocat.org/rest/info/rule-types.xml')"/>
			<sch:assert test="$types//rule-type/@name = $type">This data type isn't known by ISOcat.</sch:assert>
		</sch:rule>
	</sch:pattern>
	
	<sch:pattern name="a value has to be a simple DC">
		<sch:rule context="dcif:value">
			<sch:assert test="@pid = /dcif:dataCategorySelection/dcif:dataCategory[@type='simple']/@pid">Only simple DCs can be values.</sch:assert>
		</sch:rule>
	</sch:pattern>
	
	<start>
		<ref name="dataCategorySelection"/>
	</start>

	<define name="dataCategorySelection">
		<element name="dataCategorySelection">
			<attribute name="dcif-version">
				<value>1.1</value>
			</attribute>
			<optional>
				<attribute name="name">
					<text/>
				</attribute>
			</optional>
			<optional>
				<attribute name="owner">
					<text/>
				</attribute>
			</optional>
			<interleave>
				<optional>
					<ref name="globalInformation"/>
				</optional>
				<oneOrMore>
					<ref name="dataCategory"/>
				</oneOrMore>
			</interleave>
		</element>
	</define>
	
	<define name="globalInformation">
		<element name="globalInformation">
			<oneOrMore>
				<choice>
					<text/>
					<ref name="any"/>
				</choice>
			</oneOrMore>
		</element>
	</define>

	<define name="dataCategory">
		<element name="dataCategory">
			<attribute name="pid">
				<data type="anyURI"/>
			</attribute>
			<optional> <!-- always allow a summary of the DC -->
				<ref name="dataCategorySummary"/>
			</optional>
			<optional> <!-- always allow DC lineage links -->
				<ref name="dataCategoryLineage"/>
			</optional>
			<choice>
				<group> <!-- in 'list' mode we only have the DCs type -->
					<attribute name="type">
						<choice>
							<value>complex</value>
							<value>simple</value>
						</choice>
					</attribute>
				</group>
				<group> <!-- in the 'domains' mode we only have the DCs conceptual domain -->
					<choice>
						<ref name="complexDataCategory"/>
						<ref name="simpleDataCategory"/>
					</choice>
				</group>
				<group> <!-- in the 'all' mode we have all the descriptive info -->
					<interleave>
						<ref name="administrationInformationSection"/>
						<ref name="descriptionSection"/>
						<choice>
							<ref name="complexDataCategory"/>
							<ref name="simpleDataCategory"/>
						</choice>
					</interleave>
				</group>
			</choice>
		</element>
	</define>
	
	<define name="dataCategorySummary">
		<attribute name="identifier">
			<data type="string"/>
		</attribute>
		<attribute name="owner">
			<data type="string"/>
		</attribute>
		<attribute name="version">
			<data type="string"/>
		</attribute>
		<optional>
			<attribute name="name">
				<data type="string"/>
			</attribute>
		</optional>
		<optional>
			<attribute name="definition">
				<data type="string"/>
			</attribute>
		</optional>
	</define>
	
	<define name="dataCategoryLineage">
		<optional>
			<attribute name="first">
				<data type="anyURI"/>
			</attribute>
		</optional>
		<optional>
			<attribute name="previous">
				<data type="anyURI"/>
			</attribute>
		</optional>
		<optional>
			<attribute name="next">
				<data type="anyURI"/>
			</attribute>
		</optional>
		<optional>
			<attribute name="latest">
				<data type="anyURI"/>
			</attribute>
		</optional>
	</define>

	<define name="administrationInformationSection">
		<element name="administrationInformationSection">
			<interleave>
				<ref name="administrationRecord"/>
			</interleave>
		</element>
	</define>
	
	<define name="administrationRecord">
		<element name="administrationRecord">
			<interleave>
				<element name="identifier">
					<data type="NCName"/>
				</element>
				<element name="version">
					<data type="string"/>
				</element>
				<element name="registrationStatus">
					<choice>
						<value>private</value>
						<value>candidate</value>
						<value>standard</value>
						<value>deprecated</value>
						<value>superseded</value>
					</choice>
				</element>
				<optional>
					<element name="origin">
						<data type="string"/>
					</element>
				</optional>
				<optional>
					<element name="justification">
						<data type="string"/>
					</element>
				</optional>
				<zeroOrMore>
					<element name="explanatoryComment">
						<data type="string"/>
						<optional>
							<attribute name="xml:lang">
								<data type="language"/>
							</attribute>
						</optional>
					</element>
				</zeroOrMore>
				<zeroOrMore>
					<element name="unresolvedIssue">
						<data type="string"/>
						<optional>
							<attribute name="xml:lang">
								<data type="language"/>
							</attribute>
						</optional>
					</element>
				</zeroOrMore>
				<optional>
					<element name="effectiveDate">
						<data type="date"/>
					</element>
				</optional>
				<optional>
					<element name="untilDate">
						<data type="date"/>
					</element>
				</optional>
				<element name="creation">
					<interleave>
						<element name="creationDate" >
							<data type="date"/>
						</element>
						<element name="changeDescription" >
							<data type="string"/>
							<optional>
								<attribute name="xml:lang">
									<data type="language"/>
								</attribute>
							</optional>
						</element>
					</interleave>
				</element>
				<optional>
					<element name="lastChange">
						<interleave>
							<element name="lastChangeDate" >
								<data type="date"/>
							</element>
							<element name="changeDescription" >
								<data type="string"/>
								<optional>
									<attribute name="xml:lang">
										<data type="language"/>
									</attribute>
								</optional>
							</element>
						</interleave>
					</element>
				</optional>
			</interleave>
		</element>
	</define>
	
	<define name="descriptionSection">
		<element name="descriptionSection">
			<interleave>
				<oneOrMore>
					<element name="profile">
						<data type="string"/>
					</element>
				</oneOrMore>
				<oneOrMore>
					<ref name="languageSection"/>
				</oneOrMore>
				<zeroOrMore>
					<ref name="dataElementNameSection"/>
				</zeroOrMore>
			</interleave>
			<sch:pattern name="mandatory english language section with a name section and a definition">
				<sch:rule context="dcif:descriptionSection">
					<sch:assert test="dcif:languageSection[dcif:language=('en','eng')][exists(dcif:definitionSection)][exists(dcif:nameSection)]">All DCs should have an english language section with a definition and a name.</sch:assert>
				</sch:rule>
			</sch:pattern>
		</element>
	</define>
	
	<define name="languageSection">
		<element name="languageSection">
			<interleave>
				<element name="language">
					<data type="language"/>
				</element>
				<zeroOrMore>
					<element name="definitionSection">
						<interleave>
							<element name="definition">
								<data type="string"/>
								<optional>
									<attribute name="xml:lang">
										<data type="language"/>
									</attribute>
								</optional>
							</element>
							<element name="source">
								<data type="string"/>
							</element>
							<optional>
								<element name="note">
									<data type="string"/>
									<optional>
										<attribute name="xml:lang">
											<data type="language"/>
										</attribute>
									</optional>
								</element>
							</optional>
						</interleave>
					</element>
				</zeroOrMore>
				<zeroOrMore>
					<ref name="exampleSection"/>
				</zeroOrMore>
				<zeroOrMore>
					<ref name="explanationSection"/>
				</zeroOrMore>
				<zeroOrMore>
					<element name="note">
						<data type="string"/>
						<optional>
							<attribute name="xml:lang">
								<data type="language"/>
							</attribute>
						</optional>
					</element>
				</zeroOrMore>
				<zeroOrMore>
					<ref name="nameSection"/>
				</zeroOrMore>
			</interleave>
			<sch:pattern name="one language section per language">
				<sch:rule context="dcif:languageSection">
					<sch:assert test="not(dcif:language = (preceding-sibling::dcif:languageSection,following-sibling::dcif:languageSection)/dcif:language)">Only one language section per language per DC.</sch:assert>
				</sch:rule>
			</sch:pattern>
		</element>
	</define>
	
	<define name="exampleSection">
		<element name="exampleSection">
			<interleave>
				<element name="example">
					<data type="string"/>
					<optional>
						<attribute name="xml:lang">
							<data type="language"/>
						</attribute>
					</optional>
				</element>
				<optional>
					<element name="source">
						<data type="string"/>
					</element>
				</optional>
			</interleave>
		</element>
	</define>
	
	<define name="explanationSection">
		<element name="explanationSection">
			<interleave>
				<element name="explanation">
					<data type="string"/>
					<optional>
						<attribute name="xml:lang">
							<data type="language"/>
						</attribute>
					</optional>
				</element>
				<optional>
					<element name="source">
						<data type="string"/>
					</element>
				</optional>
			</interleave>
		</element>
	</define>
	
	<define name="nameSection">
		<element name="nameSection">
			<interleave>
				<element name="name">
					<optional>
						<attribute name="xml:lang">
							<data type="language"/>
						</attribute>
					</optional>
					<data type="string"/>
				</element>
				<element name="nameStatus">
					<choice>
						<value>standardized name</value>
						<value>preferred name</value>
						<value>admitted name</value>
						<value>deprecated name</value>
						<value>superseded name</value>
					</choice>
				</element>
			</interleave>
		</element>
	</define>
	
	<define name="dataElementNameSection">
		<element name="dataElementNameSection">
			<interleave>
				<element name="dataElementName">
					<data type="string"/>
				</element>
				<element name="source">
					<data type="string"/>
				</element>
			</interleave>
		</element>
	</define>
	
	<define name="complexDataCategory">
		<attribute name="type">
			<value>complex</value>
		</attribute>
		<choice>
			<ref name="openDataCategory"/>
			<ref name="closedDataCategory"/>
			<ref name="constrainedDataCategory"/>
		</choice>
		<sch:pattern name="all conceptual domains should have the same data type">
			<sch:rule context="dcif:dataCategory[@type='complex']">
				<sch:assert test="not(dcif:conceptualDomain/dcif:dataType != .//dcif:conceptualDomain/dcif:dataType)">All conceptual domains of a complex DC should have the same data type.</sch:assert>
			</sch:rule>
		</sch:pattern>
	</define>
	
	<define name="openDataCategory">
		<interleave>
			<ref name="openConceptualDomain"/>
			<zeroOrMore>
				<ref name="constrainedLinguisticSection"/>
			</zeroOrMore>
		</interleave>
	</define>
	
	<define name="openConceptualDomain">
		<element name="conceptualDomain">
			<attribute name="type">
				<value>open</value>
			</attribute>
			<element name="dataType">
				<data type="string"/>
			</element>
		</element>
	</define>
	
	<define name="closedDataCategory">
		<interleave>
			<oneOrMore>
				<ref name="profileValueDomain"/>
			</oneOrMore>
			<zeroOrMore>
				<ref name="closedLinguisticSection"/>
			</zeroOrMore>
		</interleave>
		<sch:pattern name="for each profile there should be a profile value domain">
			<sch:rule context="dcif:dataCategory[@type='complex'][dcif:conceptualDomain[@type='closed']]">
				<sch:assert test="dcif:descriptionSection/dcif:profile = dcif:conceptualDomain/dcif:profile">For each profile a closed DC is a member there should be a profile specific conceptual domain.</sch:assert>
			</sch:rule>
		</sch:pattern>
	</define>
	
	<define name="profileValueDomain">
		<element name="conceptualDomain">
			<attribute name="type">
				<value>closed</value>
			</attribute>
			<interleave>
				<element name="dataType">
					<data type="string"/>
				</element>
				<element name="profile">
					<data type="string"/>
				</element>
				<oneOrMore>
					<element name="value">
						<attribute name="pid">
							<data type="anyURI"/>
						</attribute>
						<optional> <!-- always allow a summary of the DC -->
							<ref name="dataCategorySummary"/>
						</optional>
					</element>
				</oneOrMore>
			</interleave>
			<sch:pattern name="one profile value domain per profile">
				<sch:rule context="dcif:dataCategory/dcif:conceptualDomain[@type='closed']">
					<sch:assert test="not(dcif:profile = (preceding-sibling::dcif:conceptualDomain,following-sibling::dcif:conceptualDomain)/dcif:profile)">Only one profile specific conceptual domain per profile per DC.</sch:assert>
				</sch:rule>
			</sch:pattern>
			<sch:pattern name="a profile value domain can only exists for a profile the DC is a member of">
				<sch:rule context="dcif:dataCategory/dcif:conceptualDomain[@type='closed']">
					<sch:assert test="dcif:profile = ancestor::dcif:dataCategory/dcif:descriptionSection/dcif:profile">A profile specific conceptual domain can only exist for a profile the closed DC is a member of.</sch:assert>
				</sch:rule>
			</sch:pattern>
			<sch:pattern name="all simple DCs in a profile value domain should be member of that profile">
				<!-- for the Private profile ISOcat allows exceptions for non-standardized DCs -->
				<sch:rule context="dcif:dataCategory/dcif:conceptualDomain[@type='closed'][dcif:profile='Private']/dcif:value" role="warning">
					<sch:let name="prof" value="parent::dcif:conceptualDomain/dcif:profile"/>
					<sch:let name="pid" value="@pid"/>
					<sch:assert test="/dcif:dataCategorySelection/dcif:dataCategory[@pid=$pid]/dcif:descriptionSection/dcif:profile=$prof">A simple DC used as a value in a profile specific conceptual domain should be a member of that profile.</sch:assert>
				</sch:rule>
				<!-- all other profiles have a strict rule -->
				<sch:rule context="dcif:dataCategory/dcif:conceptualDomain[@type='closed'][dcif:profile!='Private']/dcif:value">
					<sch:let name="prof" value="parent::dcif:conceptualDomain/dcif:profile"/>
					<sch:let name="pid" value="@pid"/>
					<sch:assert test="/dcif:dataCategorySelection/dcif:dataCategory[@pid=$pid]/dcif:descriptionSection/dcif:profile=$prof">A simple DC used as a value in a profile specific conceptual domain should be a member of that profile.</sch:assert>
				</sch:rule>
			</sch:pattern>
		</element>
	</define>
	
	<define name="closedLinguisticSection">
		<element name="linguisticSection">
			<attribute name="type">
				<value>closed</value>
			</attribute>
			<interleave>
				<ref name="linguisticSection"/>
				<optional>
					<ref name="valueDomain"/>
				</optional>
			</interleave>
		</element>
	</define>
	
	<define name="linguisticSection">
		<interleave>
			<element name="language">
				<data type="language"/>
			</element>
			<zeroOrMore>
				<ref name="exampleSection"/>
			</zeroOrMore>
			<zeroOrMore>
				<ref name="explanationSection"/>
			</zeroOrMore>
			<zeroOrMore>
				<element name="note">
					<data type="string"/>
					<optional>
						<attribute name="xml:lang">
							<data type="language"/>
						</attribute>
					</optional>
					<sch:pattern name="one linguistic section per language">
						<sch:rule context="dcif:linguisticSection">
							<sch:assert test="not(dcif:language = (preceding-sibling::dcif:linguisticSection,following-sibling::dcif:linguisticSection)/dcif:language)">Only one language section per language per DC.</sch:assert>
						</sch:rule>
					</sch:pattern>
				</element>
			</zeroOrMore>
		</interleave>
	</define>
	
	<define name="valueDomain">
		<element name="conceptualDomain">
			<attribute name="type">
				<value>closed</value>
			</attribute>
			<interleave>
				<element name="dataType">
					<data type="string"/>
				</element>
				<oneOrMore>
					<element name="value">
						<attribute name="pid">
							<data type="anyURI"/>
						</attribute>
						<optional> <!-- always allow a summary of the DC -->
							<ref name="dataCategorySummary"/>
						</optional>
					</element>
				</oneOrMore>
			</interleave>
			<sch:pattern name="a value domain of a linguistic section can't extend the closed DCs value domain">
				<sch:rule context="dcif:dataCategory/dcif:linguisticSection[@type='closed']/dcif:conceptualDomain/dcif:value">
					<sch:assert test="@pid = ancestor::dcif:dataCategory/dcif:conceptualDomain/dcif:value/@pid">A value in a linguistic section related conceptual domain should already be an allowed value in one of the closed DCs conceptual domains.</sch:assert>
				</sch:rule>
			</sch:pattern>
		</element>
	</define>
	
	<define name="constrainedDataCategory">
		<interleave>
			<oneOrMore>
				<ref name="conceptualDomainRule"/>
			</oneOrMore>
			<zeroOrMore>
				<ref name="constrainedLinguisticSection"/>
			</zeroOrMore>
		</interleave>
	</define>
	
	<define name="conceptualDomainRule">
		<element name="conceptualDomain">
			<attribute name="type">
				<value>constrained</value>
			</attribute>
			<interleave>
				<element name="dataType">
					<data type="string"/>
				</element>
				<element name="ruleType">
					<data type="string"/>
				</element>
				<oneOrMore>
					<element name="rule">
						<oneOrMore>
							<choice>
								<text/>
								<ref name="any"/>
							</choice>
						</oneOrMore>
					</element>
				</oneOrMore>
			</interleave>
			<sch:pattern name="one conceptual domain per rule type">
				<sch:rule context="dcif:conceptualDomain[@type='constrained']">
					<sch:assert test="not(dcif:ruleType = (preceding-sibling::dcif:conceptualDomain,following-sibling::dcif:conceptualDomain)/dcif:ruleType)">Only one conceptual domain per rule type.</sch:assert>
				</sch:rule>
			</sch:pattern>
		</element>
	</define>

	<define name="constrainedLinguisticSection">
		<element name="linguisticSection">
			<attribute name="type">
				<value>constrained</value>
			</attribute>
			<interleave>
				<ref name="linguisticSection"/>
				<zeroOrMore>
					<ref name="conceptualDomainRule"/>
				</zeroOrMore>
			</interleave>
		</element>
	</define>
	
	<define name="simpleDataCategory">
		<attribute name="type">
			<value>simple</value>
		</attribute>
		<optional>
			<element name="isA">
				<attribute name="pid">
					<data type="anyURI"/>
				</attribute>
				<optional> <!-- always allow a summary of the DC -->
					<ref name="dataCategorySummary"/>
				</optional>
				<sch:pattern name="the parent DC of a simple DC has to be a simple DC">
					<sch:rule context="dcif:isA">
						<sch:assert test="@pid = /dcif:dataCategorySelection/dcif:dataCategory[@type='simple']/@pid">Only simple DCs can be parent DCs.</sch:assert>
					</sch:rule>
				</sch:pattern>
				<!-- TODO: cycles aren't allowed, so check if the simple DC isn't an ancestor of itself. Can this be done with Schematron? Can we use an XSLT 2 function? -->
				<sch:pattern name="the parent DC of a simple DC can't be the same DC">
					<sch:rule context="dcif:isA">
						<sch:assert test="@pid != ancestor::dcif:dataCategory/@pid">A simple DCs can't be its own parent.</sch:assert>
					</sch:rule>
				</sch:pattern>
			</element>
		</optional>
	</define>
	
	<define name="any">
		<element>
			<anyName>
				<except>
					<nsName ns="http://www.isocat.org/ns/dcif"/>
				</except>
			</anyName>
			<zeroOrMore>
				<attribute>
					<anyName>	
						<except>
							<nsName ns="http://www.isocat.org/ns/dcif"/>
						</except>
					</anyName>
				</attribute>
			</zeroOrMore>
			<zeroOrMore>
				<choice>
					<text/>
					<ref name="any"/>
				</choice>
			</zeroOrMore>
		</element>
	</define>
	
</grammar>

