Skip to content

Commit 6d549e0

Browse files
milanmajchrakclaude
andcommitted
Fix CCMM crosswalk: transform errors, vocabularies, access rights, dates
Verified against all 2628 live LINDAT records; every output validates against CCMM 1.1.0 in XSD 1.1 (0 invalid). - fix XPTY0004: doc:field[@name='value'] returns a sequence, and records with two language variants of a field passed it to concat()/contains(). 425 of 2628 records aborted; the resulting OAIException becomes HTTP 500 for the whole ListRecords page, so all 32 pages failed and nothing was harvestable - replace the model.ccmm.cz IRIs, which all return 404, with the registers CCMM 1.1.0 names in its own specification (en/dsv.ttl scope notes): COAR resource types and access rights, vocabs.ccmm.cz AgentRole / TimeReference / DescriptionType, EU Publications Office for languages - dc.publisher now maps to AgentRole/Publisher; Distributor does not exist at that level of the codelist - derive access_rights from others/access-status, which DSpace computes in DefaultAccessStatusHelper from the actual READ policies. The previous branches were dead (local.embargo.termslift occurs 0 times) or inverted (others/@restrictedaccess is set on 24 records, all of them open access). Correct for 746 of 2628 records before, 2628 of 2628 now - handle the CLARIN "0000" unknown-date convention and local.approximateDate.issued the way ClarinDateService does in the UI: the local field replaces dc.date.issued, a range becomes a time_interval and the original string is kept verbatim in ccmm:date_information - emit exactly one Issued time reference plus a Created one, as the CCMM profile requires; publication_year and the Issued year now always agree - stop emitting an invented licence IRI; fall back to others/cc and otherwise leave ccmm:license empty and move the wording to terms_of_use/description - the minimal test fixture used to produce schema-invalid output (no time_reference at all); it no longer can Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c9b5b4c commit 6d549e0

4 files changed

Lines changed: 440 additions & 137 deletions

File tree

dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/CcmmXslTest.java

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,14 @@ public void ccmmContainsSubjects() throws Exception {
6363

6464
@Test
6565
public void ccmmContainsResourceType() throws Exception {
66+
// META-SHARE "corpus" maps to the COAR resource type "dataset"
6667
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-test.xml"));
6768
assertThat(result, is(ccmm().withXPath(
68-
"//ccmm:dataset/ccmm:resource_type/ccmm:label",
69-
equalTo("corpus"))));
69+
"//ccmm:dataset/ccmm:resource_type/ccmm:iri",
70+
equalTo("http://purl.org/coar/resource_type/c_ddb1"))));
71+
assertThat(result, is(ccmm().withXPath(
72+
"//ccmm:dataset/ccmm:resource_type/ccmm:label[1]",
73+
equalTo("dataset"))));
7074
}
7175

7276
@Test
@@ -95,10 +99,14 @@ public void ccmmContainsAccessRights() throws Exception {
9599

96100
@Test
97101
public void ccmmContainsPrimaryLanguage() throws Exception {
102+
// CCMM: "Use IRI identifier from the register
103+
// http://publications.europa.eu/resource/authority/language"
98104
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-test.xml"));
99105
assertThat(result, is(ccmm().withXPath(
100-
"//ccmm:dataset/ccmm:primary_language/ccmm:label",
101-
equalTo("ces"))));
106+
"//ccmm:dataset/ccmm:primary_language/ccmm:iri",
107+
equalTo("http://publications.europa.eu/resource/authority/language/CES"))));
108+
assertThat(result, is(ccmm().withXPath(
109+
"count(//ccmm:dataset/ccmm:primary_language/ccmm:label)", equalTo("0"))));
102110
}
103111

104112
@Test
@@ -110,10 +118,10 @@ public void ccmmContainsAlternateTitle() throws Exception {
110118
}
111119

112120
@Test
113-
public void ccmmContainsPublisher() throws Exception {
121+
public void ccmmPublisherUsesPublisherRole() throws Exception {
114122
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-test.xml"));
115123
assertThat(result, is(ccmm().withXPath(
116-
"//ccmm:dataset/ccmm:qualified_relation[ccmm:role/ccmm:label='Distributor']/ccmm:relation/ccmm:organization/ccmm:name",
124+
"//ccmm:dataset/ccmm:qualified_relation[ccmm:role/ccmm:label='Publisher']/ccmm:relation/ccmm:organization/ccmm:name",
117125
equalTo("Charles University, Faculty of Mathematics and Physics, Institute of Formal and Applied Linguistics"))));
118126
}
119127

@@ -176,12 +184,98 @@ public void ccmmFallbackRepositoryNameIsUnknown() throws Exception {
176184
}
177185

178186
@Test
179-
public void ccmmFallbackLicenseIsUnspecified() throws Exception {
180-
// When dc.rights.uri is missing but dc.rights text exists, license IRI is unspecified
187+
public void ccmmFallbackLicenseIsEmptyAndRightsTextBecomesDescription() throws Exception {
188+
// When no licence URI is known, ccmm:license stays empty (which is valid) instead of
189+
// carrying a made-up IRI; the free-text wording moves to terms_of_use/description.
181190
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-minimal-test.xml"));
182191
assertThat(result, is(ccmm().withXPath(
183-
"//ccmm:dataset/ccmm:terms_of_use/ccmm:license/ccmm:iri",
184-
equalTo("https://model.ccmm.cz/vocabulary/ccmm/license/unspecified"))));
192+
"count(//ccmm:dataset/ccmm:terms_of_use/ccmm:license/ccmm:iri)", equalTo("0"))));
193+
assertThat(result, is(ccmm().withXPath(
194+
"//ccmm:dataset/ccmm:terms_of_use/ccmm:description",
195+
equalTo("All rights reserved"))));
196+
}
197+
198+
// ---- Controlled vocabularies ----
199+
200+
@Test
201+
public void ccmmCreatorRoleUsesCcmmAgentRoleCodelist() throws Exception {
202+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-test.xml"));
203+
assertThat(result, is(ccmm().withXPath(
204+
"//ccmm:dataset/ccmm:qualified_relation[1]/ccmm:role/ccmm:iri",
205+
equalTo("https://vocabs.ccmm.cz/registry/codelist/AgentRole/Creator"))));
206+
}
207+
208+
@Test
209+
public void ccmmDateTypeUsesCcmmTimeReferenceCodelist() throws Exception {
210+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-test.xml"));
211+
assertThat(result, is(ccmm().withXPath(
212+
"//ccmm:dataset/ccmm:time_reference[1]/ccmm:date_type/ccmm:iri",
213+
equalTo("https://vocabs.ccmm.cz/registry/codelist/TimeReference/Issued"))));
214+
}
215+
216+
// ---- Access rights come from DSpace's own computed access status ----
217+
218+
@Test
219+
public void ccmmAccessRightsFollowAccessStatus() throws Exception {
220+
// others/access-status = open.access
221+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-test.xml"));
222+
assertThat(result, is(ccmm().withXPath(
223+
"//ccmm:dataset/ccmm:terms_of_use/ccmm:access_rights/ccmm:iri",
224+
equalTo("http://purl.org/coar/access_right/c_abf2"))));
225+
}
226+
227+
@Test
228+
public void ccmmAccessRightsFallBackToRestricted() throws Exception {
229+
// No others/access-status at all: never claim open access
230+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-minimal-test.xml"));
231+
assertThat(result, is(ccmm().withXPath(
232+
"//ccmm:dataset/ccmm:terms_of_use/ccmm:access_rights/ccmm:iri",
233+
equalTo("http://purl.org/coar/access_right/c_16ec"))));
234+
}
235+
236+
// ---- CLARIN approximate dates (dc.date.issued = "0000") ----
237+
238+
@Test
239+
public void ccmmApproximateDateSetsPublicationYearToEarliestAttestedYear() throws Exception {
240+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-approximate-date-test.xml"));
241+
assertThat(result, is(ccmm().withXPath(
242+
"//ccmm:dataset/ccmm:publication_year", equalTo("1930"))));
243+
}
244+
245+
@Test
246+
public void ccmmApproximateDateRangeBecomesTimeInterval() throws Exception {
247+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-approximate-date-test.xml"));
248+
assertThat(result, is(ccmm().withXPath(
249+
"//ccmm:time_reference/ccmm:temporal_representation/ccmm:time_interval"
250+
+ "/ccmm:beginning/ccmm:date", equalTo("1930-01-01"))));
251+
assertThat(result, is(ccmm().withXPath(
252+
"//ccmm:time_reference/ccmm:temporal_representation/ccmm:time_interval"
253+
+ "/ccmm:end/ccmm:date", equalTo("1950-12-31"))));
254+
}
255+
256+
@Test
257+
public void ccmmApproximateDateKeepsOriginalTextVerbatim() throws Exception {
258+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-approximate-date-test.xml"));
259+
assertThat(result, is(ccmm().withXPath(
260+
"//ccmm:time_reference/ccmm:date_information", equalTo("cca 1930-1950"))));
261+
}
262+
263+
@Test
264+
public void ccmmEmitsExactlyOneIssuedAndOneCreatedTimeReference() throws Exception {
265+
// CCMM requires a Created time reference, and requires publication_year to equal the
266+
// year of the Issued date - which several Issued references could not satisfy.
267+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-test.xml"));
268+
assertThat(result, is(ccmm().withXPath(
269+
"count(//ccmm:time_reference[ccmm:date_type/ccmm:label='Issued'])", equalTo("1"))));
270+
assertThat(result, is(ccmm().withXPath(
271+
"count(//ccmm:time_reference[ccmm:date_type/ccmm:label='Created'])", equalTo("1"))));
272+
}
273+
274+
@Test
275+
public void ccmmApproximateDateNeverEmitsYearZero() throws Exception {
276+
String result = apply("ccmm.xsl").to(resource("xoai-ccmm-approximate-date-test.xml"));
277+
assertThat(result, is(ccmm().withXPath(
278+
"count(//ccmm:time_instant[starts-with(ccmm:date, '0000')])", equalTo("0"))));
185279
}
186280

187281
private XmlMatcherBuilder ccmm() {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
XOAI metadata XML exercising the CLARIN-DSpace approximate-date convention:
4+
dc.date.issued is the literal "0000" ("exact date unknown") and the real
5+
information lives in local.approximateDate.issued.
6+
-->
7+
<metadata xmlns="http://www.lyncode.com/xoai" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
8+
xsi:schemaLocation="http://www.lyncode.com/xoai http://www.lyncode.com/xsd/xoai.xsd">
9+
<element name="dc">
10+
<element name="title">
11+
<element name="none">
12+
<field name="value">Film professionals 1</field>
13+
</element>
14+
</element>
15+
<element name="identifier">
16+
<element name="uri">
17+
<element name="none">
18+
<field name="value">http://hdl.handle.net/20.500.12801/3901034-01</field>
19+
</element>
20+
</element>
21+
</element>
22+
<element name="date">
23+
<element name="issued">
24+
<element name="none">
25+
<field name="value">0000</field>
26+
</element>
27+
</element>
28+
<element name="accessioned">
29+
<element name="none">
30+
<field name="value">2021-03-08T12:00:00Z</field>
31+
</element>
32+
</element>
33+
</element>
34+
<element name="subject">
35+
<element name="none">
36+
<field name="value">oral history</field>
37+
</element>
38+
</element>
39+
</element>
40+
<element name="local">
41+
<element name="approximateDate">
42+
<element name="issued">
43+
<element name="none">
44+
<field name="value">cca 1930-1950</field>
45+
</element>
46+
</element>
47+
</element>
48+
</element>
49+
<element name="others">
50+
<field name="handle">20.500.12801/3901034-01</field>
51+
<field name="identifier">oai:lindat.mff.cuni.cz:20.500.12801/3901034-01</field>
52+
<field name="lastModifyDate">2025-01-01 00:00:00.000</field>
53+
<element name="access-status">
54+
<field name="value">restricted</field>
55+
</element>
56+
</element>
57+
<element name="repository">
58+
<field name="name">LINDAT/CLARIAH-CZ</field>
59+
</element>
60+
</metadata>

dspace-oai/src/test/resources/xoai-ccmm-test.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@
9999
<field name="handle">11234/1-5678</field>
100100
<field name="identifier">oai:lindat.mff.cuni.cz:11234/1-5678</field>
101101
<field name="lastModifyDate">2025-06-15 10:30:00.000</field>
102+
<!-- computed by org.dspace.access.status.DefaultAccessStatusHelper and injected by
103+
AccessStatusElementItemCompilePlugin; drives ccmm:access_rights -->
104+
<element name="access-status">
105+
<field name="value">open.access</field>
106+
</element>
102107
</element>
103108
<element name="repository">
104109
<field name="name">LINDAT/CLARIAH-CZ</field>

0 commit comments

Comments
 (0)