diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/CcmmXslTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/CcmmXslTest.java new file mode 100644 index 000000000000..eea4bc4c8925 --- /dev/null +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/CcmmXslTest.java @@ -0,0 +1,1118 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.xoai.tests.stylesheets; + +import static org.dspace.xoai.tests.support.XmlMatcherBuilder.xml; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; + +import org.dspace.xoai.tests.support.XmlMatcherBuilder; +import org.junit.Test; + +/** + * Tests for the CCMM (Czech Common Metadata Model) 1.1.0 OAI-PMH crosswalk. + * + *

The CCMM schema is an xs:sequence, so element order is part of validity, and every + * controlled value has to come from the register CCMM names for it. Validating against the + * schema itself would pull xml.xsd and GML over the network at build time, so the invariants + * that matter are asserted here instead: the order of the dataset children, and closed-set + * guards that fail if any off-register string or unresolvable IRI reappears.

+ * + * @see CCMM Schema + * @see Issue #1145 + */ +public class CcmmXslTest extends AbstractXSLTest { + + private static final String CCMM_NS = "https://schema.ccmm.cz/research-data/1.1"; + private static final String XML_NS = "http://www.w3.org/XML/1998/namespace"; + private static final String MAIN = "xoai-ccmm-test.xml"; + private static final String MINIMAL = "xoai-ccmm-minimal-test.xml"; + private static final String APPROX = "xoai-ccmm-approximate-date-test.xml"; + private static final String STOCK = "xoai-ccmm-stock-dspace-test.xml"; + private static final String DATES = "xoai-ccmm-date-shapes-test.xml"; + private static final String CLARIN = "xoai-ccmm-clarin-access-test.xml"; + private static final String ENUM = "xoai-ccmm-enumerated-date-test.xml"; + private static final String ACA = "xoai-ccmm-academic-licence-test.xml"; + private static final String BARE = "xoai-ccmm-no-identifier-test.xml"; + private static final String FLAG = "xoai-ccmm-restricted-flag-test.xml"; + private static final String EMBARGO = "xoai-ccmm-embargo-test.xml"; + private static final String CLIP = "xoai-ccmm-newsreel-clip-test.xml"; + private static final String TOOL = "xoai-ccmm-tool-test.xml"; + + @Test + public void ccmmCanTransformInput() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath("//ccmm:title", equalTo("Czech NLP Dataset v2.0")))); + } + + @Test + public void ccmmContainsPublicationYear() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath("//ccmm:publication_year", equalTo("2025")))); + } + + @Test + public void ccmmContainsIdentifier() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier[1]/ccmm:value", equalTo("11234/1-5678")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier[1]/ccmm:iri", + equalTo("http://hdl.handle.net/11234/1-5678")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier[1]/ccmm:scheme/ccmm:label", equalTo("Handle")))); + } + + @Test + public void ccmmKeepsIdentifiersOtherThanTheItemUri() throws Exception { + // dc.identifier.other is an external identifier and must not be dropped + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:identifier[ccmm:value='ISLRN-123-456-789'])", equalTo("1")))); + } + + @Test + public void ccmmContainsCreator() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:qualified_relation[1]/ccmm:relation/ccmm:person/ccmm:name", + equalTo("Novak, Jan")))); + } + + @Test + public void ccmmSplitsPersonNamesIntoGivenAndFamily() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:person[ccmm:name='Novak, Jan']/ccmm:family_name", equalTo("Novak")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:person[ccmm:name='Novak, Jan']/ccmm:given_name", equalTo("Jan")))); + } + + @Test + public void ccmmTypesCorporateCreditsAsOrganizationsNotPeople() throws Exception { + // a newsreel producer is not a natural person + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:qualified_relation[ccmm:relation/ccmm:organization/ccmm:name='Aktualita'])", + equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:person[ccmm:name='Aktualita'])", equalTo("0")))); + } + + @Test + public void ccmmDropsAnvlPlaceholderAgents() throws Exception { + // "(:unav) Unknown author" names nobody and must not become an agent + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:person[contains(ccmm:name, '(:un')])", equalTo("0")))); + } + + @Test + public void ccmmEmitsCreatorAndPublisherEvenWhenTheRecordNamesNeither() throws Exception { + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:qualified_relation[ccmm:role/ccmm:label='Creator'])", equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:qualified_relation[ccmm:role/ccmm:label='Publisher'])", + equalTo("1")))); + } + + @Test + public void ccmmContainsSubjects() throws Exception { + // the FORD field leads the subject list, so a keyword is matched by value, not position + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:subject/ccmm:title[.='linguistics'])", equalTo("1")))); + } + + @Test + public void ccmmHierarchicalSubjectKeepsThePathAsAClassificationCode() throws Exception { + // "People::Masaryk ..." is a path through a thesaurus, not a subject heading + String result = apply("ccmm.xsl").to(resource(MAIN)); + // the FORD field also carries a classification code, so match on the code itself + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:classification_code=" + + "'People::Masaryk Tomas Garrigue (1850-1937)']/ccmm:title", + equalTo("Masaryk Tomas Garrigue (1850-1937)")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:subject/ccmm:classification_code" + + "[.='People::Masaryk Tomas Garrigue (1850-1937)'])", + equalTo("1")))); + } + + @Test + public void ccmmContainsResourceType() throws Exception { + // META-SHARE "corpus" maps to the COAR resource type "dataset" + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:resource_type/ccmm:iri", + equalTo("http://purl.org/coar/resource_type/c_ddb1")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:resource_type/ccmm:label[1]", equalTo("dataset")))); + } + + @Test + public void ccmmResourceTypeUnderstandsTheStockDspaceTypeList() throws Exception { + String result = apply("ccmm.xsl").to(resource(STOCK)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:resource_type/ccmm:iri", + equalTo("http://purl.org/coar/resource_type/c_6501")))); + } + + @Test + public void ccmmContainsDescription() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:description[ccmm:description_type/ccmm:label='Abstract']/ccmm:description_text", + equalTo("A sample dataset for testing CCMM crosswalk output in the OAI-PMH protocol.")))); + } + + @Test + public void ccmmUnqualifiedDescriptionIsNotAssertedToBeAnAbstract() throws Exception { + // dc.description without a qualifier says nothing about what kind of description it is + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:description[ccmm:description_text='Built from newspaper text collected in 2024.']" + + "/ccmm:description_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/DescriptionType/Other")))); + } + + @Test + public void ccmmLicenseCarriesTheLicenceNameNotTheAccessCategory() throws Exception { + // dc.rights.label is PUB/ACA/RES, which is a download gate, not the name of a licence + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:terms_of_use/ccmm:license/ccmm:iri", + equalTo("https://creativecommons.org/licenses/by/4.0/")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:terms_of_use/ccmm:license/ccmm:label", + equalTo("Creative Commons - Attribution 4.0 International")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:license/ccmm:label[.='PUB' or .='ACA' or .='RES'])", equalTo("0")))); + } + + @Test + public void ccmmContainsAccessRights() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:terms_of_use/ccmm:access_rights/ccmm:iri", + equalTo("http://purl.org/coar/access_right/c_abf2")))); + } + + @Test + public void ccmmAccessRightsFollowTheClarinLicenceGate() throws Exception { + // A PUB licence with files is downloadable by anyone, whatever DefaultAccessStatusHelper + // computed from the primary bitstream's policies. + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:terms_of_use/ccmm:access_rights/ccmm:label", + equalTo("open access")))); + } + + @Test + public void ccmmAnItemWithNoFilesIsMetadataOnlyNotRestricted() throws Exception { + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:terms_of_use/ccmm:access_rights/ccmm:iri", + equalTo("http://purl.org/coar/access_right/c_14cb")))); + } + + @Test + public void ccmmContainsPrimaryLanguage() throws Exception { + // CCMM: "Use IRI identifier from the register + // http://publications.europa.eu/resource/authority/language" + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:primary_language/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/language/CES")))); + } + + @Test + public void ccmmOtherLanguageKeepsItsRegisterIri() throws Exception { + // document order is preserved and duplicates removed without reordering, so the second + // distinct code is the first other_language + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:other_language/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/language/ENG")))); + } + + @Test + public void ccmmAcceptsIso6391AndRegionSubtags() throws Exception { + // stock DSpace stores "en_US"; the register keys on the uppercased 639-3 code + String result = apply("ccmm.xsl").to(resource(STOCK)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:primary_language/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/language/ENG")))); + } + + @Test + public void ccmmContainsAlternateTitle() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:alternate_title/ccmm:title", equalTo("CND 2.0")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:alternate_title/ccmm:alternate_title_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/AlternateTitle/TranslatedTitle")))); + } + + @Test + public void ccmmPublisherUsesPublisherRole() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:qualified_relation[ccmm:role/ccmm:label='Publisher']" + + "/ccmm:relation/ccmm:organization/ccmm:name", + equalTo("Charles University, Faculty of Mathematics and Physics, " + + "Institute of Formal and Applied Linguistics")))); + } + + @Test + public void ccmmContributorOtherKeepsItsOwnRole() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:qualified_relation[ccmm:role/ccmm:label='Other']/ccmm:role/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/AgentRole/Contributor/Other")))); + } + + @Test + public void ccmmContainsMetadataIdentification() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:metadata_identification/ccmm:conforms_to_standard/ccmm:iri", + equalTo("https://schema.ccmm.cz/research-data/1.1.0")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:metadata_identification/ccmm:conforms_to_standard/ccmm:label", + equalTo("CCMM RD 1.1.0")))); + } + + @Test + public void ccmmMetadataRecordCarriesItsOwnOaiIdentifier() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:metadata_identification/ccmm:iri", + equalTo("oai:lindat.mff.cuni.cz:11234/1-5678")))); + } + + @Test + public void ccmmMetadataRecordHasADataManager() throws Exception { + // dsv.ttl: the relationship shall contain at least one agent with role "Data Manager" + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:metadata_identification/ccmm:qualified_relation/ccmm:role/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/AgentRole/Contributor/DataManager")))); + } + + @Test + public void ccmmCanTransformBasicXoaiInput() throws Exception { + // the default xoai-test1.xml exercises the paths where almost every field is missing + String result = apply("ccmm.xsl").to(resource("xoai-test1.xml")); + assertThat(result, is(ccmm().withXPath("//ccmm:title", equalTo("Test Webpage")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:identifier)", equalTo("2")))); + // dc.identifier.issn names its own scheme; it used to be dropped entirely + assertThat(result, is(ccmm().withXPath( + "//ccmm:identifier[ccmm:value='123456789']/ccmm:scheme/ccmm:label", equalTo("ISSN")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:terms_of_use)", equalTo("1")))); + } + + // ---- Fallback scenario tests ---- + + @Test + public void ccmmFallbackTitleIsUntitled() throws Exception { + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath("//ccmm:dataset/ccmm:title", equalTo("Untitled")))); + } + + @Test + public void ccmmFallbackPublicationYearIs9999() throws Exception { + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath("//ccmm:dataset/ccmm:publication_year", equalTo("9999")))); + } + + @Test + public void ccmmRecordWithNoKeywordsStillCarriesTheFordSubject() throws Exception { + // subject is 1..n; the mandatory FORD field satisfies it, so no placeholder is needed + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:subject)", equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:subject/ccmm:classification_code", equalTo("60203")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:subject/ccmm:title[.='unspecified'])", equalTo("0")))); + } + + @Test + public void ccmmFallbackIdentifierUsesOthersHandle() throws Exception { + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier/ccmm:value", equalTo("99999/test-1")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier/ccmm:iri", + equalTo("http://hdl.handle.net/99999/test-1")))); + } + + @Test + public void ccmmFallbackRepositoryDeclaresItselfUnavailable() throws Exception { + // an absent repository name is stated as the ANVL ":unav", not invented + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:metadata_identification/ccmm:qualified_relation" + + "/ccmm:relation/ccmm:organization/ccmm:name", + equalTo(":unav")))); + } + + @Test + public void ccmmFallbackLicenseIsEmptyAndRightsTextBecomesTheLabel() throws Exception { + // With no licence URI, ccmm:license carries the wording as its label rather than a + // made-up IRI; license_document allows a label without one. + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:terms_of_use/ccmm:license/ccmm:iri)", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:terms_of_use/ccmm:license/ccmm:label", + equalTo("All rights reserved")))); + } + + @Test + public void ccmmOriginalRepositoryIsTheRepositoryUrl() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:metadata_identification/ccmm:original_repository/ccmm:iri", + equalTo("https://lindat.mff.cuni.cz/repository/")))); + } + + @Test + public void ccmmOriginalRepositoryIsNeverTheItemItself() throws Exception { + // without repository/url there is no repository to name, and a handle URL is not one + String result = apply("ccmm.xsl").to(resource(STOCK)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:metadata_identification/ccmm:original_repository/ccmm:iri", + equalTo("urn:x-ccmm:repository-unknown")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:original_repository[contains(ccmm:iri, '123/456')])", equalTo("0")))); + } + + @Test + public void ccmmXmlLangFollowsTheXoaiLanguageWrapper() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject/ccmm:title[.='korpus']/@xml:lang", equalTo("cs")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject/ccmm:title[.='Korpus']/@xml:lang", equalTo("de")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject/ccmm:title[.='linguistics']/@xml:lang", equalTo("en")))); + } + + // ---- Contact, distribution, relations ---- + + @Test + public void ccmmContainsANamedContactPoint() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:contact_point/ccmm:person/ccmm:name", equalTo("Rosa, Rudolf")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:contact_point/ccmm:person/ccmm:contact_point/ccmm:email", + equalTo("rosa@example.org")))); + } + + @Test + public void ccmmContainsTheRepositoryContactAsAnOrganization() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:contact_point/ccmm:organization/ccmm:contact_point/ccmm:email", + equalTo("lindat-help@ufal.mff.cuni.cz")))); + } + + @Test + public void ccmmBitstreamsBecomeDownloadableDistributions() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution/ccmm:distribution_downloadable_file/ccmm:title", + equalTo("corpus.txt")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:byte_size", equalTo("2048")))); + // format carries the EU file-type codelist value, media_type carries IANA + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:format/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/file-type/TXT")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:format/ccmm:label", equalTo("Plain text")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:media_type/ccmm:iri", + equalTo("https://www.iana.org/assignments/media-types/text/plain")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:media_type/ccmm:label", equalTo("text/plain")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:checksum/ccmm:checksum_value", + equalTo("ee1c4e448a8f9f838df348dfee1fc11f")))); + } + + @Test + public void ccmmRelationQualifiersBecomeTypedRelatedResources() throws Exception { + // the register has no "Replaces"; superseding is Obsoletes + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:related_resource[ccmm:iri='http://hdl.handle.net/11234/1-1111']" + + "/ccmm:resource_relation_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/RelationType/Obsoletes")))); + } + + @Test + public void ccmmOwningCollectionBecomesAPartOfRelation() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:related_resource[ccmm:title='Test Collection']" + + "/ccmm:resource_relation_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/RelationType/IsPartOf")))); + } + + @Test + public void ccmmProjectPageAndDemoBecomeRelatedResources() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:related_resource[ccmm:resource_url='https://example.org/czech-nlp-dataset'])", + equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:related_resource[ccmm:resource_url='https://example.org/demo'])", + equalTo("1")))); + } + + @Test + public void ccmmContainsLocationForACoveredPlace() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath("//ccmm:location/ccmm:name", equalTo("Praha")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:location/ccmm:relation_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/LocationRelation/Refers")))); + } + + @Test + public void ccmmContainsValidationResult() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:validation_result/ccmm:label", equalTo("Validated by the repository")))); + } + + @Test + public void ccmmFundingReferenceSpellsOutTheFunderAndTheProgramme() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:funding_reference/ccmm:local_identifier", equalTo("731015")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:funding_reference/ccmm:funding_program", equalTo("H2020")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:funding_reference/ccmm:funder/ccmm:organization/ccmm:name", + equalTo("European Commission")))); + } + + @Test + public void ccmmNonGrantInfoUrisNeverInventAFunder() throws Exception { + // info:eu-repo/semantics/... is not a grant agreement + String result = apply("ccmm.xsl").to(resource(STOCK)); + assertThat(result, is(ccmm().withXPath("count(//ccmm:funding_reference)", equalTo("0")))); + } + + // ---- Controlled vocabularies ---- + + @Test + public void ccmmCreatorRoleUsesCcmmAgentRoleCodelist() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:qualified_relation[1]/ccmm:role/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/AgentRole/Creator")))); + } + + @Test + public void ccmmDateTypeUsesTheRegistersOwnLabels() throws Exception { + // the register's English prefLabel is "Date Issued", not the concept id "Issued" + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:time_reference[1]/ccmm:date_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/TimeReference/Issued")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:time_reference[1]/ccmm:date_type/ccmm:label[1]", + equalTo("Date Issued")))); + } + + @Test + public void ccmmEmitsNoOffRegisterVocabularyStrings() throws Exception { + // a closed-set guard: these are the strings and hosts that were wrong before, and no + // future edit may bring them back + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:date_type/ccmm:label[.='Issued' or .='Created'" + + " or .='Accepted' or .='Available'])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:iri[contains(., 'model.ccmm.cz')])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:iri[contains(., 'w3.org/ns/iana')])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:iri[contains(., 'RelationType/Replaces')" + + " or contains(., 'RelationType/IsReplacedBy')])", equalTo("0")))); + } + + // ---- Element order: the CCMM dataset is an xs:sequence ---- + + @Test + public void ccmmEmitsTheDatasetChildrenInSchemaOrder() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:identifier[preceding-sibling::ccmm:title])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:title[preceding-sibling::ccmm:publication_year])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:publication_year[preceding-sibling::ccmm:time_reference])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:resource_type[preceding-sibling::ccmm:terms_of_use])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:terms_of_use[preceding-sibling::ccmm:subject])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:subject[preceding-sibling::ccmm:location])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:related_resource[preceding-sibling::ccmm:distribution])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:distribution[preceding-sibling::ccmm:validation_result])", + equalTo("0")))); + } + + @Test + public void ccmmEmitsNestedElementsInSchemaOrder() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:person/ccmm:name[preceding-sibling::ccmm:given_name])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:subject/ccmm:title[preceding-sibling::ccmm:classification_code])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:terms_of_use/ccmm:access_rights[preceding-sibling::ccmm:license])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:terms_of_use/ccmm:license[preceding-sibling::ccmm:contact_point])", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:time_reference/ccmm:temporal_representation" + + "[preceding-sibling::ccmm:date_type])", equalTo("0")))); + } + + // ---- CLARIN approximate dates (dc.date.issued = "0000") ---- + + @Test + public void ccmmApproximateDateSetsPublicationYearToTheStartOfTheRange() throws Exception { + String result = apply("ccmm.xsl").to(resource(APPROX)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:publication_year", equalTo("1930")))); + } + + @Test + public void ccmmApproximateDateRangeBecomesTimeInterval() throws Exception { + String result = apply("ccmm.xsl").to(resource(APPROX)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:time_reference/ccmm:temporal_representation/ccmm:time_interval" + + "/ccmm:beginning/ccmm:date", equalTo("1930-01-01")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:time_reference/ccmm:temporal_representation/ccmm:time_interval" + + "/ccmm:end/ccmm:date", equalTo("1950-12-31")))); + } + + @Test + public void ccmmApproximateDateKeepsOriginalTextVerbatim() throws Exception { + String result = apply("ccmm.xsl").to(resource(APPROX)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:time_reference/ccmm:date_information", equalTo("cca 1930-1950")))); + } + + @Test + public void ccmmEmitsExactlyOneIssuedAndOneCreatedTimeReference() throws Exception { + // CCMM requires a Created time reference, and requires publication_year to equal the + // year of the Issued date - which several Issued references could not satisfy. + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Issued'])", equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Created'])", equalTo("1")))); + } + + @Test + public void ccmmApproximateDateNeverEmitsYearZero() throws Exception { + String result = apply("ccmm.xsl").to(resource(APPROX)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:time_instant[starts-with(ccmm:date, '0000')])", equalTo("0")))); + } + + // ---- Dates that no format string can parse ---- + + @Test + public void ccmmEveryEmittedDateIsAValidXsDate() throws Exception { + String result = apply("ccmm.xsl").to(resource(DATES)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:date[string-length(normalize-space(.)) != 10])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:date[normalize-space(.) = ''])", equalTo("0")))); + } + + @Test + public void ccmmUnparsableDateProducesNoTimeReferenceAtAll() throws Exception { + // "n.d." is one of two dc.date.available values; only the parsable one survives + String result = apply("ccmm.xsl").to(resource(DATES)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Available'])", + equalTo("1")))); + } + + @Test + public void ccmmYearOnlyAndYearMonthDatesArePadded() throws Exception { + String result = apply("ccmm.xsl").to(resource(DATES)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Issued']" + + "/ccmm:temporal_representation/ccmm:time_instant/ccmm:date", + equalTo("2014-01-01")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Accepted']" + + "/ccmm:temporal_representation/ccmm:time_instant/ccmm:date", + equalTo("2011-12-01")))); + } + + @Test + public void ccmmDateTimeValuesKeepTheirTimeAndUseTheDateTimeElement() throws Exception { + // time_instant is a choice of date_time or date; truncating a timestamp into date + // would silently drop the time + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Accepted']" + + "/ccmm:temporal_representation/ccmm:time_instant/ccmm:date_time", + equalTo("2025-06-15T10:30:00Z")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Accepted']" + + "/ccmm:temporal_representation/ccmm:time_instant/ccmm:date)", + equalTo("0")))); + } + + @Test + public void ccmmPublicationYearAlwaysMatchesTheIssuedReference() throws Exception { + // dsv.ttl requires the two to agree; the fixtures carry different years so this can fail + for (String fixture : new String[] { MAIN, MINIMAL, APPROX, STOCK, DATES }) { + String result = apply("ccmm.xsl").to(resource(fixture)); + assertThat(fixture, result, is(ccmm().withXPath( + "substring((//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Issued']" + + "//ccmm:date | //ccmm:time_reference[ccmm:date_type/ccmm:label='Date Issued']" + + "//ccmm:date_time)[1], 1, 4) = string(//ccmm:dataset/ccmm:publication_year)", + equalTo("true")))); + } + } + + @Test + public void ccmmPubLicenceWithFilesIsOpenEvenWhenAccessStatusSaysRestricted() throws Exception { + // 1161 live records are anonymously downloadable while access-status calls them restricted + String result = apply("ccmm.xsl").to(resource(CLARIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:access_rights/ccmm:iri", + equalTo("http://purl.org/coar/access_right/c_abf2")))); + } + + @Test + public void ccmmEnumeratedYearsNeverBecomeAContinuousInterval() throws Exception { + // "1920, 1932" names two years; the source never claims the years between them + String result = apply("ccmm.xsl").to(resource(ENUM)); + assertThat(result, is(ccmm().withXPath("count(//ccmm:time_interval)", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:publication_year", equalTo("1932")))); + } + + @Test + public void ccmmCreatedUsesTheStatedYearOfCreation() throws Exception { + // local.additional.metadata states field_year; the deposit year is not a creation date + String result = apply("ccmm.xsl").to(resource(ENUM)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Created']" + + "/ccmm:temporal_representation/ccmm:time_instant/ccmm:date", + equalTo("1787-01-01")))); + } + + @Test + public void ccmmTitleIsNeverAConcatenationOfSeveralValues() throws Exception { + String result = apply("ccmm.xsl").to(resource(STOCK)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:title", equalTo("A Stock DSpace Item")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:title)", equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:alternate_title/ccmm:title", + equalTo("Second Title Nobody Entered As One")))); + } + + @Test + public void ccmmUnparsableAccessionDateProducesNoTimeReference() throws Exception { + String result = apply("ccmm.xsl").to(resource(DATES)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:time_reference[ccmm:date_type/ccmm:label='Date Accepted'])", + equalTo("1")))); + } + + @Test + public void ccmmUriIdentifierSchemeUsesTheIanaRegistry() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:related_resource[ccmm:resource_url='https://example.org/czech-nlp-dataset']" + + "/ccmm:identifier/ccmm:scheme/ccmm:iri", + equalTo("https://www.iana.org/assignments/uri-schemes")))); + } + + @Test + public void ccmmAcademicLicenceIsRestrictedEvenWhenAccessStatusSaysOpen() throws Exception { + // the CLARIN identity form gates the download; open.access on the bitstream does not + String result = apply("ccmm.xsl").to(resource(ACA)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:access_rights/ccmm:iri", + equalTo("http://purl.org/coar/access_right/c_16ec")))); + } + + @Test + public void ccmmRestrictedAccessFlagOutranksAPubLicence() throws Exception { + // ItemUtils sets others/restrictedAccess when a bitstream licence demands the identity + // form; that gate outranks the PUB licence category + String result = apply("ccmm.xsl").to(resource(FLAG)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:access_rights/ccmm:iri", + equalTo("http://purl.org/coar/access_right/c_16ec")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:access_rights/ccmm:label", + equalTo("restricted access")))); + } + + @Test + public void ccmmEmbargoedAccessStatusMapsToTheCoarEmbargoTerm() throws Exception { + // with no CLARIN licence category present, others/access-status decides + String result = apply("ccmm.xsl").to(resource(EMBARGO)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:access_rights/ccmm:iri", + equalTo("http://purl.org/coar/access_right/c_f1cf")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:access_rights/ccmm:label", + equalTo("embargoes access")))); + } + + @Test + public void ccmmEveryRecordCarriesAFrascatiFordSubject() throws Exception { + // CCMM: "At least one subject must be a value from FRASCATI FORD vocabulary." The XSD + // does not enforce it, a CCMM conformance check does. + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:subject_scheme/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/SubjectCategory/")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:subject_scheme/ccmm:label[@xml:lang='en']", + equalTo("OECD FORD Subject Category (Frascati)")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:subject[ccmm:subject_scheme])", equalTo("1")))); + } + + @Test + public void ccmmFordSubjectCarriesTheConceptIriCodeAndBothLabels() throws Exception { + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/SubjectCategory/60000/60200/60203")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:classification_code", equalTo("60203")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:title[@xml:lang='en']", equalTo("Linguistics")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:title[@xml:lang='cs']", equalTo("Lingvistika")))); + } + + @Test + public void ccmmNewsreelClipIsClassifiedAsHistoryNotLinguistics() throws Exception { + // a digitised newsreel is a primary historical source, not a language resource + String result = apply("ccmm.xsl").to(resource(CLIP)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:classification_code", equalTo("60101")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:title[@xml:lang='en']", equalTo("History")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:title[@xml:lang='cs']", equalTo("Historie")))); + } + + @Test + public void ccmmToolIsClassifiedAsComputerScience() throws Exception { + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:classification_code", equalTo("10201")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:subject[ccmm:subject_scheme]/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/SubjectCategory/10000/10200/10201")))); + } + + @Test + public void ccmmFordSubjectKeepsTheSchemaElementOrder() throws Exception { + // subject is a sequence: iri, title+, definition*, classification_code?, subject_scheme? + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:subject/ccmm:iri[preceding-sibling::ccmm:title])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:subject/ccmm:subject_scheme[following-sibling::ccmm:classification_code])", + equalTo("0")))); + } + + @Test + public void ccmmDownloadUrlIsTheEndpointThatServesTheBytes() throws Exception { + // XOAI builds the bitstream url as the DSpace 6 UI path; under DSpace 7 that path is + // answered by the Angular application and returns HTML, so it is not a download link + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:download_url/ccmm:iri", + equalTo("https://lindat.mff.cuni.cz/repository/server/api/core/bitstreams/" + + "18341be0-03ad-4a22-a1c8-4cd293c3f5da/content")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:download_url/ccmm:iri[contains(., '/bitstream/')])", equalTo("0")))); + } + + @Test + public void ccmmBitstreamWithoutAUuidKeepsTheUrlXoaiSupplied() throws Exception { + // nothing to build a REST URL from, so the source url is better than no url + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file[ccmm:title='morphodita.zip']" + + "/ccmm:download_url/ccmm:iri", + equalTo("https://lindat.mff.cuni.cz/repository/bitstream/1/10/morphodita.zip")))); + } + + @Test + public void ccmmDatasetCarriesItsOwnResolvableIri() throws Exception { + // dataset/iri is optional in the XSD but it is the only resolvable identity the + // published record has + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "/ccmm:dataset/ccmm:iri", equalTo("http://hdl.handle.net/11234/1-5678")))); + } + + @Test + public void ccmmDatasetIriFallsBackToTheOthersHandle() throws Exception { + // a record with no dc.identifier.uri still has a Handle, and that is its landing page + String result = apply("ccmm.xsl").to(resource(MINIMAL)); + assertThat(result, is(ccmm().withXPath( + "/ccmm:dataset/ccmm:iri", equalTo("http://hdl.handle.net/99999/test-1")))); + } + + @Test + public void ccmmIdentifierValueIsTheNotationAndIriIsTheResolvableForm() throws Exception { + // CCMM types value as skos:notation - the identifier WITHIN its scheme - and keeps the + // resolvable form in iri, which is the split CCMM's own samples use + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier[ccmm:scheme/ccmm:label='Handle']/ccmm:value", + equalTo("11234/1-5678")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier[ccmm:scheme/ccmm:label='Handle']/ccmm:iri", + equalTo("http://hdl.handle.net/11234/1-5678")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:identifier/ccmm:value[starts-with(., 'http')" + + " and ../ccmm:scheme/ccmm:label='Handle'])", + equalTo("0")))); + } + + @Test + public void ccmmMandatoryLicenceIsNeverAnEmptyElement() throws Exception { + // license is 1..1; a record that states no rights gets the standard statement for + // exactly that case rather than a void element + String result = apply("ccmm.xsl").to(resource(STOCK)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:license/ccmm:iri", + equalTo("http://rightsstatements.org/vocab/UND/1.0/")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:terms_of_use/ccmm:license/ccmm:label", equalTo("Copyright Undetermined")))); + } + + @Test + public void ccmmLanguageCarriesTheRegisterIriAndNoInventedLabel() throws Exception { + // the scope note asks for the register's label "and/or" its code; DSpace's own string is + // not the register's, so only the IRI is published + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:primary_language/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/language/CES")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:primary_language/ccmm:label)", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:other_language/ccmm:label)", equalTo("0")))); + } + + @Test + public void ccmmUnregisteredMediaTypeGetsNoIanaIriButKeepsItsEuFormat() throws Exception { + // RFC 6838 reserves the "x-" subtype prefix for types that are NOT registered, so an + // IANA IRI for one would not resolve; the mandatory EU format carries it instead + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file[ccmm:title='models.tar.gz']/ccmm:format/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/file-type/GZIP")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:distribution_downloadable_file[ccmm:title='models.tar.gz']/ccmm:media_type)", + equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:distribution_downloadable_file[ccmm:title='morphodita.zip']/ccmm:media_type)", + equalTo("1")))); + } + + @Test + public void ccmmPackedMultiValuePlaceBecomesOneLocationEach() throws Exception { + // DSpace packs repeated values as "A||B"; that is two places, not one name + String result = apply("ccmm.xsl").to(resource(CLIP)); + assertThat(result, is(ccmm().withXPath("count(//ccmm:location)", equalTo("2")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:location/ccmm:name[.='Germany'])", equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:location/ccmm:name[.='Austria'])", equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:location/ccmm:name[contains(., '||')])", equalTo("0")))); + } + + @Test + public void ccmmUnqualifiedRelationUriStillGetsARelationType() throws Exception { + // dc.relation.uri asserts a relation without saying which, and the register has a term + // for exactly that; a related resource with no type cannot be told apart from any other + String result = apply("ccmm.xsl").to(resource(CLIP)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:related_resource[ccmm:iri='http://hdl.handle.net/20.500.12801/3900007-01']" + + "/ccmm:resource_relation_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/RelationType/Other")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:related_resource[not(ccmm:resource_relation_type)])", equalTo("0")))); + } + + @Test + public void ccmmAlternateTitleAlwaysStatesWhatKindItIs() throws Exception { + // dc.title.alternative says it is a translation; a plain second dc.title only says the + // resource has another name, which the register calls AlternativeTitle + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:alternate_title[not(ccmm:alternate_title_type)])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:alternate_title[ccmm:title='CND 2.0']/ccmm:alternate_title_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/AlternateTitle/TranslatedTitle")))); + } + + @Test + public void ccmmSecondPlainTitleBecomesAnAlternativeTitle() throws Exception { + // the two titles sit in different XOAI language wrappers, which is how DSpace stores + // them whenever they differ in language + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:alternate_title[ccmm:title='MorphoDiTa']/ccmm:alternate_title_type/ccmm:iri", + equalTo("https://vocabs.ccmm.cz/registry/codelist/AlternateTitle/AlternativeTitle")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:alternate_title[ccmm:title='MorphoDiTa']/ccmm:alternate_title_type/ccmm:label", + equalTo("Alternative Title")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:title", equalTo("MorphoDiTa Morphological Tagger")))); + } + + @Test + public void ccmmDoiWrittenAsAUrlStillYieldsTheBareDoiAsItsNotation() throws Exception { + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier[ccmm:scheme/ccmm:label='DOI']/ccmm:value", + equalTo("10.5555/morphodita")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier[ccmm:scheme/ccmm:label='DOI']/ccmm:iri", + equalTo("https://doi.org/10.5555/morphodita")))); + } + + @Test + public void ccmmAffiliationInTheEmailSlotIsNotPublishedAsAnEmail() throws Exception { + // local.contact.person is positional "Given;Family;email;affiliation"; a record that + // omits the email shifts the affiliation into its place + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:contact_point/ccmm:person/ccmm:name", equalTo("Strakova, Jana")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:contact_point/ccmm:person/ccmm:contact_point/ccmm:email)", + equalTo("0")))); + } + + @Test + public void ccmmSameValueUnderTwoLanguageWrappersIsOneElement() throws Exception { + // DSpace stores one value under several wrappers; two byte-identical siblings are one + // value in the CCMM model and would be displayed twice by any consumer + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:subject/ccmm:title[.='tagger'])", equalTo("1")))); + } + + @Test + public void ccmmUrlFollowedByProseIsATitleNotAnIdentifier() throws Exception { + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:related_resource/ccmm:iri[contains(., ' ')])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:related_resource/ccmm:title", + equalTo("https://ufal.mff.cuni.cz/morphodita Project page")))); + } + + @Test + public void ccmmAccessUrlIsTheLandingPageAndDownloadUrlIsTheFile() throws Exception { + // CCMM: access URL "shall be the web page (not a document file)"; the bitstream belongs + // in download_url + String result = apply("ccmm.xsl").to(resource(MAIN)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:access_url/ccmm:iri", + equalTo("http://hdl.handle.net/11234/1-5678")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:access_url/ccmm:iri[contains(., '/bitstream/')])", equalTo("0")))); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:access_url/ccmm:iri[. = //ccmm:download_url/ccmm:iri])", equalTo("0")))); + } + + @Test + public void ccmmQuickTimeVideoMapsToTheMovEuFileType() throws Exception { + String result = apply("ccmm.xsl").to(resource(CLIP)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:format/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/file-type/MOV")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:media_type/ccmm:label", + equalTo("video/quicktime")))); + } + + @Test + public void ccmmZipArchiveMapsToTheZipEuFileType() throws Exception { + String result = apply("ccmm.xsl").to(resource(TOOL)); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:format/ccmm:iri", + equalTo("http://publications.europa.eu/resource/authority/file-type/ZIP")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:distribution_downloadable_file/ccmm:format/ccmm:label", equalTo("ZIP")))); + } + + @Test + public void ccmmAlwaysEmitsAtLeastOneIdentifier() throws Exception { + // identifier is 1..unbounded and this record carries none of the usual sources + String result = apply("ccmm.xsl").to(resource(BARE)); + assertThat(result, is(ccmm().withXPath( + "count(//ccmm:dataset/ccmm:identifier)", equalTo("1")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier/ccmm:value", + equalTo("oai:repo.example.org:no-handle-1")))); + assertThat(result, is(ccmm().withXPath( + "//ccmm:dataset/ccmm:identifier/ccmm:scheme/ccmm:iri", + equalTo("https://www.iana.org/assignments/uri-schemes")))); + } + + private XmlMatcherBuilder ccmm() { + // XmlMatcherBuilder's NamespaceContext is a plain map, so the reserved "xml" prefix has + // to be registered explicitly or JAXP cannot compile an @xml:lang step. + return xml() + .withNamespace("ccmm", CCMM_NS) + .withNamespace("xml", XML_NS); + } +} diff --git a/dspace-oai/src/test/resources/xoai-ccmm-academic-licence-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-academic-licence-test.xml new file mode 100644 index 000000000000..2f9d95f23c0c --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-academic-licence-test.xml @@ -0,0 +1,64 @@ + + + + + + + + + 2018-06-01 + + + + + + CLARIN ACA End-User Licence Agreement + + + + ACA + + + + + + An Academic Use Corpus + + + + + + ORIGINAL + + + corpus.zip + application/zip + 4096 + https://lindat.mff.cuni.cz/repository/bitstream/1/3/corpus.zip + + + + + + 11234/1-9 + oai:lindat.mff.cuni.cz:11234/1-9 + + open.access + + + + LINDAT/CLARIAH-CZ + https://lindat.mff.cuni.cz/repository/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-approximate-date-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-approximate-date-test.xml new file mode 100644 index 000000000000..c871b29948d9 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-approximate-date-test.xml @@ -0,0 +1,69 @@ + + + + + + + + Film professionals 1 + + + + + + http://hdl.handle.net/20.500.12801/3901034-01 + + + + + + + 0000 + + + + + 2021-03-08T12:00:00Z + + + + + + oral history + + + + + + + + cca 1930-1950 + + + + + + 20.500.12801/3901034-01 + oai:lindat.mff.cuni.cz:20.500.12801/3901034-01 + 2025-01-01 00:00:00.000 + + restricted + + + + LINDAT/CLARIAH-CZ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-clarin-access-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-clarin-access-test.xml new file mode 100644 index 000000000000..473c024ea272 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-clarin-access-test.xml @@ -0,0 +1,65 @@ + + + + + + + + + 2019-01-01 + + + + + + Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International + + + + PUB + + + + + + A Publicly Licensed Film + + + + + + ORIGINAL + + + film.mp4 + video/mp4 + 1048576 + https://lindat.mff.cuni.cz/repository/bitstream/1/2/film.mp4 + + + + + + 20.500.12801/1-2 + oai:lindat.mff.cuni.cz:20.500.12801/1-2 + + + restricted + + + + LINDAT/CLARIAH-CZ + https://lindat.mff.cuni.cz/repository/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-date-shapes-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-date-shapes-test.xml new file mode 100644 index 000000000000..86d73b68a4ba --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-date-shapes-test.xml @@ -0,0 +1,50 @@ + + + + + + + + + 2014 + + + + + 2011-12 + no date recorded + + + + + 2008-12-01 + n.d. + + + + + + Date Shapes + + + + + 123/789 + oai:repo.example.org:123/789 + + + Example Repository + https://repo.example.org/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-embargo-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-embargo-test.xml new file mode 100644 index 000000000000..5cadede786a9 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-embargo-test.xml @@ -0,0 +1,54 @@ + + + + + + + + + 2023-09-01 + + + + + + An Embargoed Dataset + + + + + + ORIGINAL + + + embargoed.csv + text/csv + 512 + https://repo.example.org/bitstream/1/5/embargoed.csv + + + + + + 123/999 + oai:repo.example.org:123/999 + + embargo + + + + Example Repository + https://repo.example.org/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-enumerated-date-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-enumerated-date-test.xml new file mode 100644 index 000000000000..24c8c33cce84 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-enumerated-date-test.xml @@ -0,0 +1,56 @@ + + + + + + + + + 0000 + + + + + + A Compiled Newsreel + + + + + + + + 1920, 1932 + + + + + + + Nid:2307 +Begin year of resource creation (field_year):1787 +Readily Available (field_resource_available):Yes + + + + + + 20.500.12801/3-4 + oai:lindat.mff.cuni.cz:20.500.12801/3-4 + + + LINDAT/CLARIAH-CZ + https://lindat.mff.cuni.cz/repository/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-minimal-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-minimal-test.xml new file mode 100644 index 000000000000..2bc87af073b6 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-minimal-test.xml @@ -0,0 +1,34 @@ + + + + + + + + All rights reserved + + + + + 99999/test-1 + oai:test.repository:99999/test-1 + 2025-01-01 00:00:00.000 + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-newsreel-clip-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-newsreel-clip-test.xml new file mode 100644 index 000000000000..3bff76031013 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-newsreel-clip-test.xml @@ -0,0 +1,86 @@ + + + + + + + + + 1943-11-15 + + + + + + + PUB + + + + + + + Germany||Austria + + + + + + + http://hdl.handle.net/20.500.12801/3900007-01 + + + + + + akce charitativni + + + + + A Christmas Party in Lucerna for Poor Children + + + + + clip + + + + + + ORIGINAL + + + segment.mov + video/quicktime + 104857600 + https://lindat.mff.cuni.cz/repository/bitstream/1/9/segment.mov + + + + + + 20.500.12801/3900007-02 + oai:lindat.mff.cuni.cz:20.500.12801/3900007-02 + + open.access + + + + LINDAT/CLARIAH-CZ + https://lindat.mff.cuni.cz/repository/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-no-identifier-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-no-identifier-test.xml new file mode 100644 index 000000000000..ce821c775160 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-no-identifier-test.xml @@ -0,0 +1,37 @@ + + + + + + + + A Record With No Identifier + + + + + + 2021-02-03 + + + + + + oai:repo.example.org:no-handle-1 + + + Example Repository + https://repo.example.org/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-restricted-flag-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-restricted-flag-test.xml new file mode 100644 index 000000000000..28204ea44eea --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-restricted-flag-test.xml @@ -0,0 +1,66 @@ + + + + + + + + + 2020-04-01 + + + + + + Creative Commons - Attribution 4.0 International + + + + PUB + + + + + + A Gated Public Resource + + + + + + ORIGINAL + + + data.zip + application/zip + 8192 + https://lindat.mff.cuni.cz/repository/bitstream/1/4/data.zip + + + + + + 11234/1-11 + oai:lindat.mff.cuni.cz:11234/1-11 + true + + open.access + + + + LINDAT/CLARIAH-CZ + https://lindat.mff.cuni.cz/repository/ + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-stock-dspace-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-stock-dspace-test.xml new file mode 100644 index 000000000000..942085774973 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-stock-dspace-test.xml @@ -0,0 +1,72 @@ + + + + + + + + + Smith, Alice + + + + + + + 2024-05-01 + + + + + + + http://hdl.handle.net/123/456 + + + + + + + en_US + + + + + + info:eu-repo/semantics/openAccess + + info:eu-repo/semantics/altIdentifier/doi/10.1234/xyz + + + + + A Stock DSpace Item + Second Title Nobody Entered As One + + + + + Article + + + + + 123/456 + oai:repo.example.org:123/456 + + + Example Repository + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-test.xml new file mode 100644 index 000000000000..783746511ba8 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-test.xml @@ -0,0 +1,229 @@ + + + + + + + + + Novak, Jan + Svobodova, Marie + + Aktualita + (:unav) Unknown author + + + + + Dvorak, Petr + + + + + + + Praha + + + + + + + 2025-06-15T10:30:00Z + + + + + 2025-06-15T10:30:00Z + + + + + 2025-03-20 + + + + + + text/plain + + + + + + http://hdl.handle.net/11234/1-5678 + + + + + ISLRN-123-456-789 + + + + + + + A sample dataset for testing CCMM crosswalk output in the OAI-PMH protocol. + + + + Built from newspaper text collected in 2024. + + + + + + ces + eng + + + + + + Charles University, Faculty of Mathematics and Physics, Institute of Formal and Applied Linguistics + + + + + info:eu-repo/grantAgreement/EC/H2020/731015/EU//ELEXIS + + + + http://hdl.handle.net/11234/1-1111 + + + + + + Creative Commons - Attribution 4.0 International + + + + https://creativecommons.org/licenses/by/4.0/ + + + + + + PUB + + + + + + + https://example.org/czech-nlp-dataset + + + + + + linguistics + Czech language + NLP + + People::Masaryk Tomas Garrigue (1850-1937) + + + korpus + + + Korpus + + + + + Czech NLP Dataset v2.0 + + + + CND 2.0 + + + + + + corpus + + + + + + + + Rudolf;Rosa;rosa@example.org;Charles University + + + + + + + Czech + English + + + + + + + https://example.org/demo + + + + + + + + + true + + + + + + + ORIGINAL + + + corpus.txt + corpus.txt + text/plain + 2048 + https://lindat.mff.cuni.cz/repository/bitstream/11234/1-5678/1/corpus.txt + ee1c4e448a8f9f838df348dfee1fc11f + MD5 + 1 + 18341be0-03ad-4a22-a1c8-4cd293c3f5da + + + + + + 11234/1-5678 + oai:lindat.mff.cuni.cz:11234/1-5678 + Test Collection + 2025-06-15 10:30:00.000 + + + open.access + + + + https://lindat.mff.cuni.cz/repository/ + LINDAT/CLARIAH-CZ + lindat-help@ufal.mff.cuni.cz + + diff --git a/dspace-oai/src/test/resources/xoai-ccmm-tool-test.xml b/dspace-oai/src/test/resources/xoai-ccmm-tool-test.xml new file mode 100644 index 000000000000..d351e33a739a --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-ccmm-tool-test.xml @@ -0,0 +1,105 @@ + + + + + + + + + 2021-05-20 + + + + + + + https://doi.org/10.5555/morphodita + + + + + + + https://ufal.mff.cuni.cz/morphodita Project page + + + + + + tagger + + + tagger + + + + + MorphoDiTa Morphological Tagger + + + MorphoDiTa + + + + + toolService + + + + + + + + Jana;Strakova;Charles University + + + + + + + + + tool + + + + + + + ORIGINAL + + + morphodita.zip + application/zip + 4096 + https://lindat.mff.cuni.cz/repository/bitstream/1/10/morphodita.zip + + + models.tar.gz + application/x-gzip + 8192 + https://lindat.mff.cuni.cz/repository/bitstream/1/10/models.tar.gz + + + + + + 11234/1-2000 + oai:lindat.mff.cuni.cz:11234/1-2000 + + + LINDAT/CLARIAH-CZ + https://lindat.mff.cuni.cz/repository/ + + diff --git a/dspace/config/crosswalks/oai/metadataFormats/ccmm.xsl b/dspace/config/crosswalks/oai/metadataFormats/ccmm.xsl new file mode 100644 index 000000000000..013e577b4fc8 --- /dev/null +++ b/dspace/config/crosswalks/oai/metadataFormats/ccmm.xsl @@ -0,0 +1,2067 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + abkaframharaasmaze + bakbelbulbenbodbre + boscatchecosceschv + cymdandeudivelleng + epospaesteusfasfin + faofrafrygleglaglg + gujhauhebhinhrvhat + hunhyeindislitajpn + javkatkazkhmkankor + kurkirlatltzlaolit + lavmlgmrimkdmalmon + marmsamltmyanobnep + nldnnonorocioripan + polpusporronrussan + sndsinslkslvsomsqi + srpsweswatamteltgk + thatukturtatuigukr + urduzbviewolxhoyid + yorzhozul + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10201 + 60101 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/AgentRole/Contributor/DataManager + Data Manager + + + + + + + + + + + + + https://schema.ccmm.cz/research-data/1.1.0 + CCMM RD 1.1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://hdl.handle.net/ + Handle + + + + + + + + + + https://www.iana.org/assignments/uri-schemes + URI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/AlternateTitle/TranslatedTitle + Translated Title + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/AlternateTitle/AlternativeTitle + Alternative Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :unkn + + https://vocabs.ccmm.cz/registry/codelist/AgentRole/Creator + Creator + + + + + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/AgentRole/Publisher + Publisher + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/TimeReference/Issued + Date Issued + Datum vydání + + + + + + + Issue date unknown; the year of the repository ingest date is used instead. + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/TimeReference/Created + Date Created + Datum vytvoÅ™ení + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/TimeReference/Accepted + Date Accepted + Datum pÅ™ijetí + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/TimeReference/Available + Date Available + Datum zveÅ™ejnÄ›ní + + + + + + + + + + + + + + + + + + http://purl.org/coar/resource_type/c_ddb1 + dataset + + + http://purl.org/coar/resource_type/c_5ce6 + software + + + http://purl.org/coar/resource_type/c_12ce + video + + + http://purl.org/coar/resource_type/c_18cc + sound + + + http://purl.org/coar/resource_type/c_ecc8 + still image + + + http://purl.org/coar/resource_type/c_18cf + text + + + http://purl.org/coar/resource_type/c_18cw + musical notation + + + http://purl.org/coar/resource_type/c_86bc + bibliography + + + http://purl.org/coar/resource_type/c_e059 + learning object + + + http://purl.org/coar/resource_type/c_e9a0 + interactive resource + + + + http://purl.org/coar/resource_type/c_6501 + journal article + + + http://purl.org/coar/resource_type/c_2f33 + book + + + http://purl.org/coar/resource_type/c_3248 + book part + + + http://purl.org/coar/resource_type/c_46ec + thesis + + + http://purl.org/coar/resource_type/c_db06 + doctoral thesis + + + http://purl.org/coar/resource_type/c_bdcc + master thesis + + + http://purl.org/coar/resource_type/c_7a1f + bachelor thesis + + + http://purl.org/coar/resource_type/c_816b + preprint + + + http://purl.org/coar/resource_type/c_18gh + technical report + + + http://purl.org/coar/resource_type/c_5794 + conference paper + + + http://purl.org/coar/resource_type/c_8544 + lecture + + + http://purl.org/coar/resource_type/c_15cd + patent + + + http://purl.org/coar/resource_type/c_1843 + other + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://purl.org/coar/access_right/c_16ec + restricted access + + + http://purl.org/coar/access_right/c_abf2 + open access + + + http://purl.org/coar/access_right/c_abf2 + open access + + + http://purl.org/coar/access_right/c_f1cf + + embargoes access + + + + http://purl.org/coar/access_right/c_14cb + metadata only access + + + + http://purl.org/coar/access_right/c_16ec + restricted access + + + + + + + + + + + + + + + + + + + + http://rightsstatements.org/vocab/UND/1.0/ + Copyright Undetermined + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OECD FORD Subject Category (Frascati) + Oborové třídění OECD FORD (Frascati) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/Abstract + Abstract + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/Other + Other + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/TechnicalInfo + Technical Info + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/Other + Other + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/Other + Other + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/Other + Other + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/TechnicalInfo + Technical Info + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/TechnicalInfo + Technical Info + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/DescriptionType/TechnicalInfo + Technical Info + + + + + + + + + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/LocationRelation/Refers + Refers to the location + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + European Commission + Ministry of Education, Youth and Sports of the Czech Republic + Czech Science Foundation + Technology Agency of the Czech Republic + + + + + + + + + + + + + + + + + + + European Union + National funding + + :unkn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://vocabs.ccmm.cz/registry/codelist/RelationType/IsReferencedBy + is referenced by + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IsPartOf + HasPart + Obsoletes + IsObsoletedBy + IsReferencedBy + References + IsVersionOf + HasVersion + Requires + IsRequiredBy + IsDerivedFrom + IsVariantFormOf + IsOriginalFormOf + + Other + + + + + + is part of + has part + obsoletes + is obsoleted by + is referenced by + references + is version of + has version + requires + is required by + is derived from + is variant form of + is original form of + other + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://portal.issn.org/ + ISSN + + + https://www.isbn-international.org/ + ISBN + + + https://doi.org/ + DOI + + + https://hdl.handle.net/ + Handle + + + https://www.iana.org/assignments/uri-schemes + URI + + + + + + + + + + + Local identifier + + + + + diff --git a/dspace/config/crosswalks/oai/xoai.xml b/dspace/config/crosswalks/oai/xoai.xml index 723aa02d7311..cd502b454e7e 100644 --- a/dspace/config/crosswalks/oai/xoai.xml +++ b/dspace/config/crosswalks/oai/xoai.xml @@ -24,6 +24,7 @@ + This is the default context of the DSpace OAI-PMH data provider. @@ -102,6 +103,7 @@ + This context exports items following the openaire data archive rules. @@ -238,6 +240,16 @@ http://w3id.org/meta-share/meta-share/ ../Schema/ELG-SHARE.xsd + + + ccmm-xml + metadataFormats/ccmm.xsl + https://schema.ccmm.cz/research-data/1.1 + https://techlib.github.io/CCMM/dataset/schema.xsd +