From 2dfca455423a7c72a024e1f94c586397d07a792d Mon Sep 17 00:00:00 2001 From: pbarroca <pbarroca@git-test.afi-sa.fr> Date: Mon, 7 May 2012 13:28:54 +0000 Subject: [PATCH] =?UTF-8?q?Validation=20des=20r=C3=A9ponses=20OAI=20avec?= =?UTF-8?q?=20leur=20XSD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 3 + .../opac/views/scripts/oai/identify.xml.phtml | 4 +- library/Class/Xml/Oai/DublinCoreBuilder.php | 6 +- .../modules/opac/controllers/OAI-PMH.xsd | 317 ++++++++++++++++++ .../controllers/OAIControllerIdentifyTest.php | 8 + .../OAIControllerListIdentifiersTest.php | 8 + .../OAIControllerListMetadataFormatsTest.php | 8 + .../controllers/OAIControllerListSetsTest.php | 1 + .../Class/Notice/DublinCoreVisitorTest.php | 8 + tests/library/Class/Notice/oai_dc.xsd | 42 +++ .../library/Class/Notice/simpledc20021212.xsd | 78 +++++ 11 files changed, 479 insertions(+), 4 deletions(-) create mode 100644 tests/application/modules/opac/controllers/OAI-PMH.xsd create mode 100644 tests/library/Class/Notice/oai_dc.xsd create mode 100644 tests/library/Class/Notice/simpledc20021212.xsd diff --git a/.gitattributes b/.gitattributes index 560bcd7d890..e3521e58c11 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3437,6 +3437,7 @@ tests/application/modules/opac/controllers/CmsControllerTest.php -text tests/application/modules/opac/controllers/FormulaireContactTest.php -text tests/application/modules/opac/controllers/IndexControllerTranslationTest.php -text tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php -text +tests/application/modules/opac/controllers/OAI-PMH.xsd -text tests/application/modules/opac/controllers/OAIControllerGetRecordTest.php -text tests/application/modules/opac/controllers/OAIControllerIdentifyTest.php -text tests/application/modules/opac/controllers/OAIControllerListIdentifiersTest.php -text @@ -3499,6 +3500,8 @@ tests/library/Class/NewsletterMailingTest.php -text tests/library/Class/NewsletterSubscriptionTest.php -text tests/library/Class/NewsletterTest.php -text tests/library/Class/Notice/DublinCoreVisitorTest.php -text +tests/library/Class/Notice/oai_dc.xsd -text +tests/library/Class/Notice/simpledc20021212.xsd -text tests/library/Class/NoticeHtmlTest.php -text tests/library/Class/NoticeOAITest.php -text tests/library/Class/NoticeTest.php -text diff --git a/application/modules/opac/views/scripts/oai/identify.xml.phtml b/application/modules/opac/views/scripts/oai/identify.xml.phtml index 4978c923ce2..1182c0f5a52 100644 --- a/application/modules/opac/views/scripts/oai/identify.xml.phtml +++ b/application/modules/opac/views/scripts/oai/identify.xml.phtml @@ -8,9 +8,9 @@ <repositoryName><![CDATA[<?php echo $this->repositoryName;?>]]></repositoryName> <baseURL><?php echo $this->baseUrl; ?></baseURL> <protocolVersion>2.0</protocolVersion> + <adminEmail><![CDATA[<?php echo $this->adminEmail;?>]]></adminEmail> <earliestDatestamp><?php echo $this->earliestDatestamp; ?></earliestDatestamp> - <granularity>YYYY-MM-DD</granularity> <deletedRecord>no</deletedRecord> - <adminEmail><![CDATA[<?php echo $this->adminEmail;?>]]></adminEmail> + <granularity>YYYY-MM-DD</granularity> </Identify> </OAI-PMH> \ No newline at end of file diff --git a/library/Class/Xml/Oai/DublinCoreBuilder.php b/library/Class/Xml/Oai/DublinCoreBuilder.php index 0665eacba5b..3ad1b837ddf 100644 --- a/library/Class/Xml/Oai/DublinCoreBuilder.php +++ b/library/Class/Xml/Oai/DublinCoreBuilder.php @@ -23,8 +23,10 @@ class Class_Xml_Oai_DublinCoreBuilder extends Class_Xml_Builder { public function oai_dc($content) { return parent::_xmlString('oai_dc:dc', $content, - $this->attributesToString(array('xmlns:oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/', - 'xmlns:dc' => 'http://purl.org/dc/elements/1.1'))); + $this->attributesToString(array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/', + 'xmlns:oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd'))); } diff --git a/tests/application/modules/opac/controllers/OAI-PMH.xsd b/tests/application/modules/opac/controllers/OAI-PMH.xsd new file mode 100644 index 00000000000..3fce3b59db4 --- /dev/null +++ b/tests/application/modules/opac/controllers/OAI-PMH.xsd @@ -0,0 +1,317 @@ +<schema targetNamespace="http://www.openarchives.org/OAI/2.0/" + xmlns="http://www.w3.org/2001/XMLSchema" + xmlns:oai="http://www.openarchives.org/OAI/2.0/" + elementFormDefault="qualified" + attributeFormDefault="unqualified"> + + <annotation> + <documentation> + XML Schema which can be used to validate replies to all OAI-PMH + v2.0 requests. Herbert Van de Sompel, 2002-05-13. + Validated with XML Spy v.4.3 on 2002-05-13. + Validated with XSV 1.203.2.45/1.106.2.22 on 2002-05-13. + Added definition of protocolVersionType instead of using anonymous + type. No change of function. Simeon Warner, 2004-03-29. + Tightened definition of UTCdatetimeType to enforce the restriction + to UTC Z notation. Simeon Warner, 2004-09-14. + Corrected pattern matches for setSpecType and metadataPrefixType + to agree with protocol specification. Simeon Warner, 2004-10-12. + Spelling correction. Simeon Warner, 2008-12-07. + $Date: 2004/10/12 15:20:29 $ + </documentation> + </annotation> + + <element name="OAI-PMH" type="oai:OAI-PMHtype"/> + + <complexType name="OAI-PMHtype"> + <sequence> + <element name="responseDate" type="dateTime"/> + <element name="request" type="oai:requestType"/> + <choice> + <element name="error" type="oai:OAI-PMHerrorType" maxOccurs="unbounded"/> + <element name="Identify" type="oai:IdentifyType"/> + <element name="ListMetadataFormats" type="oai:ListMetadataFormatsType"/> + <element name="ListSets" type="oai:ListSetsType"/> + <element name="GetRecord" type="oai:GetRecordType"/> + <element name="ListIdentifiers" type="oai:ListIdentifiersType"/> + <element name="ListRecords" type="oai:ListRecordsType"/> + </choice> + </sequence> + </complexType> + + <complexType name="requestType"> + <annotation> + <documentation>Define requestType, indicating the protocol request that + led to the response. Element content is BASE-URL, attributes are arguments + of protocol request, attribute-values are values of arguments of protocol + request</documentation> + </annotation> + <simpleContent> + <extension base="anyURI"> + <attribute name="verb" type="oai:verbType" use="optional"/> + <attribute name="identifier" type="oai:identifierType" use="optional"/> + <attribute name="metadataPrefix" type="oai:metadataPrefixType" use="optional"/> + <attribute name="from" type="oai:UTCdatetimeType" use="optional"/> + <attribute name="until" type="oai:UTCdatetimeType" use="optional"/> + <attribute name="set" type="oai:setSpecType" use="optional"/> + <attribute name="resumptionToken" type="string" use="optional"/> + </extension> + </simpleContent> + </complexType> + + <simpleType name="verbType"> + <restriction base="string"> + <enumeration value="Identify"/> + <enumeration value="ListMetadataFormats"/> + <enumeration value="ListSets"/> + <enumeration value="GetRecord"/> + <enumeration value="ListIdentifiers"/> + <enumeration value="ListRecords"/> + </restriction> + </simpleType> + + <!-- define OAI-PMH error conditions --> + <!-- =============================== --> + + <complexType name="OAI-PMHerrorType"> + <simpleContent> + <extension base="string"> + <attribute name="code" type="oai:OAI-PMHerrorcodeType" use="required"/> + </extension> + </simpleContent> + </complexType> + + <simpleType name="OAI-PMHerrorcodeType"> + <restriction base="string"> + <enumeration value="cannotDisseminateFormat"/> + <enumeration value="idDoesNotExist"/> + <enumeration value="badArgument"/> + <enumeration value="badVerb"/> + <enumeration value="noMetadataFormats"/> + <enumeration value="noRecordsMatch"/> + <enumeration value="badResumptionToken"/> + <enumeration value="noSetHierarchy"/> + </restriction> + </simpleType> + + <!-- define OAI-PMH verb containers --> + <!-- ============================== --> + + <complexType name="IdentifyType"> + <sequence> + <element name="repositoryName" type="string"/> + <element name="baseURL" type="anyURI"/> + <element name="protocolVersion" type="oai:protocolVersionType"/> + <element name="adminEmail" type="oai:emailType" maxOccurs="unbounded"/> + <element name="earliestDatestamp" type="oai:UTCdatetimeType"/> + <element name="deletedRecord" type="oai:deletedRecordType"/> + <element name="granularity" type="oai:granularityType"/> + <element name="compression" type="string" minOccurs="0" maxOccurs="unbounded"/> + <element name="description" type="oai:descriptionType" + minOccurs="0" maxOccurs="unbounded"/> + </sequence> + </complexType> + + <complexType name="ListMetadataFormatsType"> + <sequence> + <element name="metadataFormat" type="oai:metadataFormatType" maxOccurs="unbounded"/> + </sequence> + </complexType> + + <complexType name="ListSetsType"> + <sequence> + <element name="set" type="oai:setType" maxOccurs="unbounded"/> + <element name="resumptionToken" type="oai:resumptionTokenType" minOccurs="0"/> + </sequence> + </complexType> + + <complexType name="GetRecordType"> + <sequence> + <element name="record" type="oai:recordType"/> + </sequence> + </complexType> + + <complexType name="ListRecordsType"> + <sequence> + <element name="record" type="oai:recordType" maxOccurs="unbounded"/> + <element name="resumptionToken" type="oai:resumptionTokenType" minOccurs="0"/> + </sequence> + </complexType> + + <complexType name="ListIdentifiersType"> + <sequence> + <element name="header" type="oai:headerType" maxOccurs="unbounded"/> + <element name="resumptionToken" type="oai:resumptionTokenType" minOccurs="0"/> + </sequence> + </complexType> + + <!-- define basic types used in replies to + GetRecord, ListRecords, ListIdentifiers --> + <!-- ======================================= --> + + <complexType name="recordType"> + <annotation> + <documentation>A record has a header, a metadata part, and + an optional about container</documentation> + </annotation> + <sequence> + <element name="header" type="oai:headerType"/> + <element name="metadata" type="oai:metadataType" minOccurs="0"/> + <element name="about" type="oai:aboutType" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + </complexType> + + <complexType name="headerType"> + <annotation> + <documentation>A header has a unique identifier, a datestamp, + and setSpec(s) in case the item from which + the record is disseminated belongs to set(s). + the header can carry a deleted status indicating + that the record is deleted.</documentation> + </annotation> + <sequence> + <element name="identifier" type="oai:identifierType"/> + <element name="datestamp" type="oai:UTCdatetimeType"/> + <element name="setSpec" type="oai:setSpecType" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + <attribute name="status" type="oai:statusType" use="optional"/> + </complexType> + + <simpleType name="identifierType"> + <restriction base="anyURI"/> + </simpleType> + + <simpleType name="statusType"> + <restriction base="string"> + <enumeration value="deleted"/> + </restriction> + </simpleType> + + <complexType name="metadataType"> + <annotation> + <documentation>Metadata must be expressed in XML that complies + with another XML Schema (namespace=#other). Metadata must be + explicitly qualified in the response.</documentation> + </annotation> + <sequence> + <any namespace="##other" processContents="strict"/> + </sequence> + </complexType> + + <complexType name="aboutType"> + <annotation> + <documentation>Data "about" the record must be expressed in XML + that is compliant with an XML Schema defined by a community.</documentation> + </annotation> + <sequence> + <any namespace="##other" processContents="strict"/> + </sequence> + </complexType> + + <complexType name="resumptionTokenType"> + <annotation> + <documentation>A resumptionToken may have 3 optional attributes + and can be used in ListSets, ListIdentifiers, ListRecords + responses.</documentation> + </annotation> + <simpleContent> + <extension base="string"> + <attribute name="expirationDate" type="dateTime" use="optional"/> + <attribute name="completeListSize" type="positiveInteger" use="optional"/> + <attribute name="cursor" type="nonNegativeInteger" use="optional"/> + </extension> + </simpleContent> + </complexType> + + <complexType name="descriptionType"> + <annotation> + <documentation>The descriptionType is used for the description + element in Identify and for setDescription element in ListSets. + Content must be compliant with an XML Schema defined by a + community.</documentation> + </annotation> + <sequence> + <any namespace="##other" processContents="strict"/> + </sequence> + </complexType> + + <simpleType name="UTCdatetimeType"> + <annotation> + <documentation>Datestamps are to either day (type date) + or to seconds granularity (type oai:UTCdateTimeZType)</documentation> + </annotation> + <union memberTypes="date oai:UTCdateTimeZType"/> + </simpleType> + + <simpleType name="UTCdateTimeZType"> + <restriction base="dateTime"> + <pattern value=".*Z"/> + </restriction> + </simpleType> + + <!-- define types used for Identify verb only --> + <!-- ======================================== --> + + <simpleType name="protocolVersionType"> + <restriction base="string"> + <enumeration value="2.0"/> + </restriction> + </simpleType> + + <simpleType name="emailType"> + <restriction base="string"> + <pattern value="\S+@(\S+\.)+\S+"/> + </restriction> + </simpleType> + + <simpleType name="deletedRecordType"> + <restriction base="string"> + <enumeration value="no"/> + <enumeration value="persistent"/> + <enumeration value="transient"/> + </restriction> + </simpleType> + + <simpleType name="granularityType"> + <restriction base="string"> + <enumeration value="YYYY-MM-DD"/> + <enumeration value="YYYY-MM-DDThh:mm:ssZ"/> + </restriction> + </simpleType> + + <!-- define types used for ListMetadataFormats verb only --> + <!-- =================================================== --> + + <complexType name="metadataFormatType"> + <sequence> + <element name="metadataPrefix" type="oai:metadataPrefixType"/> + <element name="schema" type="anyURI"/> + <element name="metadataNamespace" type="anyURI"/> + </sequence> + </complexType> + + <simpleType name="metadataPrefixType"> + <restriction base="string"> + <pattern value="[A-Za-z0-9\-_\.!~\*'\(\)]+"/> + </restriction> + </simpleType> + + <!-- define types used for ListSets verb --> + <!-- =================================== --> + + <complexType name="setType"> + <sequence> + <element name="setSpec" type="oai:setSpecType"/> + <element name="setName" type="string"/> + <element name="setDescription" type="oai:descriptionType" + minOccurs="0" maxOccurs="unbounded"/> + </sequence> + </complexType> + + <simpleType name="setSpecType"> + <restriction base="string"> + <pattern value="([A-Za-z0-9\-_\.!~\*'\(\)])+(:[A-Za-z0-9\-_\.!~\*'\(\)]+)*"/> + </restriction> + </simpleType> + +</schema> diff --git a/tests/application/modules/opac/controllers/OAIControllerIdentifyTest.php b/tests/application/modules/opac/controllers/OAIControllerIdentifyTest.php index 7d1d2a61ebd..e582df18427 100644 --- a/tests/application/modules/opac/controllers/OAIControllerIdentifyTest.php +++ b/tests/application/modules/opac/controllers/OAIControllerIdentifyTest.php @@ -53,6 +53,14 @@ class OAIControllerIdentifyTest extends AbstractControllerTestCase { } + /** @test */ + public function xmlShouldBeValid() { + $dom = new DOMDocument(); + $dom->loadXml($this->_response->getBody()); + $dom->schemaValidate('tests/application/modules/opac/controllers/OAI-PMH.xsd'); + } + + /** @test */ public function responseDateShouldBeNow() { $this->_xpath->assertXPathContentContains($this->_response->getBody(), diff --git a/tests/application/modules/opac/controllers/OAIControllerListIdentifiersTest.php b/tests/application/modules/opac/controllers/OAIControllerListIdentifiersTest.php index 2a5fb78690d..6353c83f36a 100644 --- a/tests/application/modules/opac/controllers/OAIControllerListIdentifiersTest.php +++ b/tests/application/modules/opac/controllers/OAIControllerListIdentifiersTest.php @@ -64,6 +64,14 @@ class OAIControllerListIdentifiersValidTest extends AbstractControllerTestCase { } + /** @test */ + public function xmlShouldBeValid() { + $dom = new DOMDocument(); + $dom->loadXml($this->_response->getBody()); + $dom->schemaValidate('tests/application/modules/opac/controllers/OAI-PMH.xsd'); + } + + /** @test */ public function requestVerbShouldBeListIdentifiers() { $this->_xpath->assertXPath($this->_xml, diff --git a/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php b/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php index 90c2e4e6e12..f85389ec16c 100644 --- a/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php +++ b/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php @@ -30,6 +30,14 @@ class OAIControllerListMetadataFormatsTest extends AbstractControllerTestCase { } + /** @test */ + public function xmlShouldBeValid() { + $dom = new DOMDocument(); + $dom->loadXml($this->_response->getBody()); + $dom->schemaValidate('tests/application/modules/opac/controllers/OAI-PMH.xsd'); + } + + /** @test */ public function requestVerbShouldBeListMetadataFormats() { $this->_xpath->assertXPath($this->_response->getBody(), diff --git a/tests/application/modules/opac/controllers/OAIControllerListSetsTest.php b/tests/application/modules/opac/controllers/OAIControllerListSetsTest.php index ecfbbf8109f..cf1f910269b 100644 --- a/tests/application/modules/opac/controllers/OAIControllerListSetsTest.php +++ b/tests/application/modules/opac/controllers/OAIControllerListSetsTest.php @@ -47,6 +47,7 @@ class OAIControllerListSetsTest extends AbstractControllerTestCase { } + /** @test */ public function setMusiqueShouldContainSpecMusic() { $this->_xpath->assertXpathContentContains($this->_response->getBody(), diff --git a/tests/library/Class/Notice/DublinCoreVisitorTest.php b/tests/library/Class/Notice/DublinCoreVisitorTest.php index c5d1a0ea134..80926d18bf4 100644 --- a/tests/library/Class/Notice/DublinCoreVisitorTest.php +++ b/tests/library/Class/Notice/DublinCoreVisitorTest.php @@ -50,6 +50,14 @@ class DublinCoreVisitorPotterTest extends DublinCoreVisitorTestCase { } + /** @test */ + public function xmlShouldBeValid() { + $dom = new DOMDocument(); + $dom->loadXml($this->_dublin_core_visitor->xml()); + $dom->schemaValidate('tests/library/Class/Notice/oai_dc.xsd'); + } + + /** @test */ public function identifierShouldBeHarryPotterSorciers() { $this->_xpath->assertXPathContentContains($this->_dublin_core_visitor->xml(), diff --git a/tests/library/Class/Notice/oai_dc.xsd b/tests/library/Class/Notice/oai_dc.xsd new file mode 100644 index 00000000000..0070f0e0f44 --- /dev/null +++ b/tests/library/Class/Notice/oai_dc.xsd @@ -0,0 +1,42 @@ +<schema targetNamespace="http://www.openarchives.org/OAI/2.0/oai_dc/" + xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns="http://www.w3.org/2001/XMLSchema" + elementFormDefault="qualified" attributeFormDefault="unqualified"> + +<annotation> + <documentation> + XML Schema 2002-03-18 by Pete Johnston. + Adjusted for usage in the OAI-PMH. + Schema imports the Dublin Core elements from the DCMI schema for unqualified Dublin Core. + 2002-12-19 updated to use simpledc20021212.xsd (instead of simpledc20020312.xsd) + </documentation> +</annotation> + +<import namespace="http://purl.org/dc/elements/1.1/" + schemaLocation="http://dublincore.org/schemas/xmls/simpledc20021212.xsd"/> + +<element name="dc" type="oai_dc:oai_dcType"/> + +<complexType name="oai_dcType"> + <choice minOccurs="0" maxOccurs="unbounded"> + <element ref="dc:title"/> + <element ref="dc:creator"/> + <element ref="dc:subject"/> + <element ref="dc:description"/> + <element ref="dc:publisher"/> + <element ref="dc:contributor"/> + <element ref="dc:date"/> + <element ref="dc:type"/> + <element ref="dc:format"/> + <element ref="dc:identifier"/> + <element ref="dc:source"/> + <element ref="dc:language"/> + <element ref="dc:relation"/> + <element ref="dc:coverage"/> + <element ref="dc:rights"/> + </choice> +</complexType> + +</schema> + diff --git a/tests/library/Class/Notice/simpledc20021212.xsd b/tests/library/Class/Notice/simpledc20021212.xsd new file mode 100644 index 00000000000..bea04f8ec5e --- /dev/null +++ b/tests/library/Class/Notice/simpledc20021212.xsd @@ -0,0 +1,78 @@ +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns="http://purl.org/dc/elements/1.1/" + targetNamespace="http://purl.org/dc/elements/1.1/" + elementFormDefault="qualified" + attributeFormDefault="unqualified"> + + <xs:annotation> + <xs:documentation xml:lang="en"> + Simple DC XML Schema, 2002-10-09 + by Pete Johnston (p.johnston@ukoln.ac.uk), + Carl Lagoze (lagoze@cs.cornell.edu), Andy Powell (a.powell@ukoln.ac.uk), + Herbert Van de Sompel (hvdsomp@yahoo.com). + This schema defines terms for Simple Dublin Core, i.e. the 15 + elements from the http://purl.org/dc/elements/1.1/ namespace, with + no use of encoding schemes or element refinements. + Default content type for all elements is xs:string with xml:lang + attribute available. + + Supercedes version of 2002-03-12. + Amended to remove namespace declaration for http://www.w3.org/XML/1998/namespace namespace, + and to reference lang attribute via built-in xml: namespace prefix. + xs:appinfo also removed. + </xs:documentation> + </xs:annotation> + + <xs:import namespace="http://www.w3.org/XML/1998/namespace" + schemaLocation="http://www.w3.org/2001/03/xml.xsd"> + </xs:import> + + <xs:element name="title" type="elementType"/> + <xs:element name="creator" type="elementType"/> + <xs:element name="subject" type="elementType"/> + <xs:element name="description" type="elementType"/> + <xs:element name="publisher" type="elementType"/> + <xs:element name="contributor" type="elementType"/> + <xs:element name="date" type="elementType"/> + <xs:element name="type" type="elementType"/> + <xs:element name="format" type="elementType"/> + <xs:element name="identifier" type="elementType"/> + <xs:element name="source" type="elementType"/> + <xs:element name="language" type="elementType"/> + <xs:element name="relation" type="elementType"/> + <xs:element name="coverage" type="elementType"/> + <xs:element name="rights" type="elementType"/> + + <xs:group name="elementsGroup"> + <xs:sequence> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element ref="title"/> + <xs:element ref="creator"/> + <xs:element ref="subject"/> + <xs:element ref="description"/> + <xs:element ref="publisher"/> + <xs:element ref="contributor"/> + <xs:element ref="date"/> + <xs:element ref="type"/> + <xs:element ref="format"/> + <xs:element ref="identifier"/> + <xs:element ref="source"/> + <xs:element ref="language"/> + <xs:element ref="relation"/> + <xs:element ref="coverage"/> + <xs:element ref="rights"/> + </xs:choice> + </xs:sequence> + </xs:group> + + <xs:complexType name="elementType"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute ref="xml:lang" use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + +</xs:schema> + + -- GitLab