From c85af4e79a7f00c981e7803d4658bfd853329fe7 Mon Sep 17 00:00:00 2001 From: Oliver Bertuch Date: Mon, 18 Jul 2022 13:10:46 +0200 Subject: [PATCH] feat(data): add OAI XSLT support #15 Inspired by Zenodo, ePrint and others, this library may now serve implementing applications an XSLT to transfrom OAI-PMH responses into nice HTML. Browsing an OAI-PMH endpoint with a cleaner UI makes way more fun. This commit includes a copy of https://github.com/eprints/eprints3.4/blob/2de59384b47d7196ab0e20af68caed45ec38b937/lib/static/oai2.xsl which is licensed under LGPL-3.0. As this library does not interfere with its content and has not performed any non-whitespace changes, it should not conflict with this library shipping under a BSD 3-clause license. An issue has been opened to reach out and avoid any conflicts. See also: https://github.com/eprints/eprints/issues/521 --- docs/README.md | 7 + .../main/java/io/gdcc/xoai/xml/XmlWriter.java | 9 + .../gdcc/xoai/dataprovider/DataProvider.java | 31 + .../src/main/resources/oai2.xsl | 662 ++++++++++++++++++ .../xoai/dataprovider/DataProviderTest.java | 7 + 5 files changed, 716 insertions(+) create mode 100644 xoai-data-provider/src/main/resources/oai2.xsl diff --git a/docs/README.md b/docs/README.md index 2f646884..3cbddefd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -148,6 +148,13 @@ It happily accepts an `InputStream`, being read and written as a raw response to and serve from the main application (Dataverse) without jumping through the loops of the XML transformer for each item, which is MUCH faster. +Using `DataProvider.getOaiXslt()` an implementing application can retrieve a local copy +of https://github.com/eprints/eprints/blob/3.3/lib/static/oai2.xsl to serve it +from a local (servlet) endpoint. It allows for a much nicer UI experience when +accessing the OAI-PMH endpoint with a browser. Applications may provide a hyperref +to the endpoint (or other static location) by using `xmlWriter.writeStylesheet()` +in their OAI servlet. + ## Advanced Configuration **NOTE: THE FOLLOWING HAS BEEN COPIED FROM THE DSPACE WIKI AND IS OF LIMITED USE diff --git a/xoai-common/src/main/java/io/gdcc/xoai/xml/XmlWriter.java b/xoai-common/src/main/java/io/gdcc/xoai/xml/XmlWriter.java index 7a31c5f1..54884945 100644 --- a/xoai-common/src/main/java/io/gdcc/xoai/xml/XmlWriter.java +++ b/xoai-common/src/main/java/io/gdcc/xoai/xml/XmlWriter.java @@ -160,4 +160,13 @@ public void write(ResumptionToken.Value value) throws XmlWriteException { throw new XmlWriteException(e); } } + + public void writeStylesheet(String href) throws XMLStreamException { + if (href == null) { + throw new XMLStreamException( + "May not pass a null hyper reference to the XSLT processing instruction"); + } + super.writeProcessingInstruction( + "xml-stylesheet", "type=\"text/xsl\" href=\"" + href + "\""); + } } diff --git a/xoai-data-provider/src/main/java/io/gdcc/xoai/dataprovider/DataProvider.java b/xoai-data-provider/src/main/java/io/gdcc/xoai/dataprovider/DataProvider.java index dfc7ca34..22c05296 100644 --- a/xoai-data-provider/src/main/java/io/gdcc/xoai/dataprovider/DataProvider.java +++ b/xoai-data-provider/src/main/java/io/gdcc/xoai/dataprovider/DataProvider.java @@ -27,6 +27,10 @@ import io.gdcc.xoai.model.oaipmh.ResumptionToken; import io.gdcc.xoai.model.oaipmh.verbs.Verb.Type; import io.gdcc.xoai.services.api.DateProvider; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -189,4 +193,31 @@ public OAIPMH handle(Request request) { return this.errorsHandler.handle(oaipmh, e); } } + + /** + * Let an implementing application retrieve the OAI XSLT for nicer UI of results. The app needs + * to create some endpoint to serve the content and reference that endpoint in {@link + * io.gdcc.xoai.xml.XmlWriter#writeStylesheet(String)}. The app might also choose to cache the + * String instead of re-reading it. + * + * @return The XSLT document as complete String + */ + public static final String getOaiXSLT() { + // get resource from classpath + ClassLoader classLoader = DataProvider.class.getClassLoader(); + URL oaiXsltResource = classLoader.getResource("oai2.xsl"); + + // Prevent errors if file not found or could not be loaded + if (oaiXsltResource == null) { + log.warn("Could not find or load OAI XSLT file, class loader returned null"); + return ""; + } + + try (InputStream inStream = oaiXsltResource.openStream()) { + return new String(inStream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + log.warn("Could not read OAI XSLT file", e); + return ""; + } + } } diff --git a/xoai-data-provider/src/main/resources/oai2.xsl b/xoai-data-provider/src/main/resources/oai2.xsl new file mode 100644 index 00000000..f4de37b0 --- /dev/null +++ b/xoai-data-provider/src/main/resources/oai2.xsl @@ -0,0 +1,662 @@ + + + + + + + + + + + + + + + +td.value { + vertical-align: top; + padding-left: 1em; + padding: 3px; +} +td.key { + background-color: #e0e0ff; + padding: 3px; + text-align: right; + border: 1px solid #c0c0c0; + white-space: nowrap; + font-weight: bold; + vertical-align: top; +} +.dcdata td.key { + background-color: #ffffe0; +} +body { + margin: 1em 2em 1em 2em; +} +h1, h2, h3 { + font-family: sans-serif; + clear: left; +} +h1 { + padding-bottom: 4px; + margin-bottom: 0px; +} +h2 { + margin-bottom: 0.5em; +} +h3 { + margin-bottom: 0.3em; + font-size: medium; +} +.link { + border: 1px outset #88f; + background-color: #c0c0ff; + padding: 1px 4px 1px 4px; + font-size: 80%; + text-decoration: none; + font-weight: bold; + font-family: sans-serif; + color: black; +} +.link:hover { + color: red; +} +.link:active { + color: red; + border: 1px inset #88f; + background-color: #a0a0df; +} +.oaiRecord, .oaiRecordTitle { + background-color: #f0f0ff; + border-style: solid; + border-color: #d0d0d0; +} +h2.oaiRecordTitle { + background-color: #e0e0ff; + font-size: medium; + font-weight: bold; + padding: 10px; + border-width: 2px 2px 0px 2px; + margin: 0px; +} +.oaiRecord { + margin-bottom: 3em; + border-width: 2px; + padding: 10px; +} + +.results { + margin-bottom: 1.5em; +} +ul.quicklinks { + margin-top: 2px; + padding: 4px; + text-align: left; + border-bottom: 2px solid #ccc; + border-top: 2px solid #ccc; + clear: left; +} +ul.quicklinks li { + font-size: 80%; + display: inline; + list-stlye: none; + font-family: sans-serif; +} +p.intro { + font-size: 80%; +} + + + + + + + + + OAI 2.0 Request Results + + + +
+

OAI 2.0 Request Results

+ +

You are viewing an HTML version of the XML OAI response. To see the underlying XML use your web browsers view source option. More information about this XSLT is at the bottom of the page.

+
+ +
+ +

About the XSLT

+

An XSLT file has converted the OAI-PMH 2.0 responses into XHTML which looks nice in a browser which supports XSLT such as Mozilla, Firebird and Internet Explorer. The XSLT file was created by Christopher Gutteridge at the University of Southampton as part of the GNU EPrints system, and is freely redistributable under the GPL.

If you want to use the XSL file on your own OAI interface you may but due to the way XSLT works you must install the XSL file on the same server as the OAI script, you can't just link to this copy.

+
+ + +
+ + + + + + + + + + + + +
Datestamp of response
Request URL
+ + + +

OAI Error(s)

+

The request could not be completed due to the following error or errors.

+
+ +
+
+ +

Request was of type .

+
+ + + + + + +
+
+
+
+ + + + + + + + +
Error Code
+

+
+ + + + + + + + + + + + + + + + + + +
Repository Name
Base URL
Protocol Version
Earliest Datestamp
Deleted Record Policy
Granularity
+ + +
+ + + Admin Email + + + + + + +

Unsupported Description Type

+

The XSL currently does not support this type of description.

+
+ +
+
+ + + + + +

OAI-Identifier

+ + + + + + + + + +
Scheme
Repository Identifier
Delimiter
Sample OAI Identifier
+
+ + + + + +

EPrints Description

+ +

Content

+ +
+ +

Submission Policy

+ +
+

Metadata Policy

+ +

Data Policy

+ + +
+ + + +

+
+ +
+
+
+ + +

Comment

+
+
+ + + + + +

Friends

+
    + +
+
+ + +
  • + +Identify
  • +
    + + + + + +

    Branding

    + + +
    + + +

    Icon

    + + + {br:title} + + + {br:title} + + +
    + + +

    Metadata Rendering Rule

    + + + + + + + +
    URL
    Namespace
    Mime Type
    +
    + + + + + + +

    Gateway Information

    + + + + + + + + + + + + + + +
    Source
    Description
    URL
    Notes
    +
    + + + Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Set

    + + + + +
    setName
    +
    + + + + + + +

    This is a list of metadata formats available for the record "". Use these links to view the metadata:

    +
    + +

    This is a list of metadata formats available from this archive.

    +
    +
    + +
    + + +

    Metadata Format

    + + + + + + + +
    metadataPrefix
    metadataNamespace
    schema
    +
    + + + + + + + + +

    OAI Record:

    +
    + + + +
    +
    + + +

    OAI Record Header

    + + + + + + +
    OAI Identifier + + oai_dc + formats +
    Datestamp
    + +

    This record has been deleted.

    +
    +
    + + + +

    "about" part of record container not supported by the XSL

    +
    + + +   + + + + + + + + + + setSpec + + Identifiers + Records + + + + + + + + +

    There are more results.

    + + + +
    resumptionToken: + +Resume
    +
    + + + + +

    Unknown Metadata Format

    +
    + +
    +
    + + + + +
    +

    Dublin Core Metadata (oai_dc)

    + + +
    +
    +
    + + +Title + + +Author or Creator + + +Subject and Keywords + + +Description + + +Publisher + + +Other Contributor + + +Date + + +Resource Type + + +Format + + +Resource Identifier + + +Source + + +Language + + +Relation + + + + + URL + URL not shown as it is very long. + + + + + + + + + + + + + +Coverage + + +Rights Management + + + + +
    + <></> +
    +
    + + + + + ="" + + + +.xmlSource { + font-size: 70%; + border: solid #c0c0a0 1px; + background-color: #ffffe0; + padding: 2em 2em 2em 0em; +} +.xmlBlock { + padding-left: 2em; +} +.xmlTagName { + color: #800000; + font-weight: bold; +} +.xmlAttrName { + font-weight: bold; +} +.xmlAttrValue { + color: #0000c0; +} + + +
    diff --git a/xoai-data-provider/src/test/java/io/gdcc/xoai/dataprovider/DataProviderTest.java b/xoai-data-provider/src/test/java/io/gdcc/xoai/dataprovider/DataProviderTest.java index 75f79101..f962ae1b 100644 --- a/xoai-data-provider/src/test/java/io/gdcc/xoai/dataprovider/DataProviderTest.java +++ b/xoai-data-provider/src/test/java/io/gdcc/xoai/dataprovider/DataProviderTest.java @@ -10,7 +10,9 @@ import static io.gdcc.xoai.model.oaipmh.verbs.Verb.Type.ListRecords; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyString; import io.gdcc.xoai.dataprovider.handlers.AbstractHandlerTest; import io.gdcc.xoai.dataprovider.request.RequestBuilder; @@ -151,4 +153,9 @@ protected static Matcher hasXPath(String xPath) { protected String write(final XmlWritable handle) throws XMLStreamException, XmlWriteException { return XmlWriter.toString(writer -> writer.write(handle)); } + + @Test + void readOaiXslt() { + assertThat(DataProvider.getOaiXSLT(), not(emptyString())); + } }