OAI PMH - digitalutsc/islandora_lite_docs GitHub Wiki

The Drupal OAI-PMH module exposes entities as Dublin Core and MODS in an OAI-PMH endpoint using Views, REST, and a metadata mapping module of your choice.

Installation

rest_oai_pmh is a Drupal module and can be installed via composer.

The current release (2.0.0-beta8) as of March 6th does need this patch https://www.drupal.org/files/issues/2022-06-01/mods_view_render.patch. The patch has been merged into the dev version, thus future release will not require this patch.

Configuration

Please see here to learn about how to configure OAI-PMH to expose Dublin Core format. The following instructions about configuration to express entities metadata in MODS format.

  1. Modify the template mods.html.twig found in the module's templates folder to suit the specific needs for the repository. An Islandora MODS template is below.
  2. Create a View to map entity fields to their MODS element.
    • The View should have a contextual filter for content ID.
    • For each field you wish to display, add it to the list of fields and create a label for the field, setting it to the variable name used within the twig template (for the Islandora MODS template, refer to the table of labels below).
  3. Set the View machine name and display name in the REST OAI-PMH configuration form (/admin/config/services/rest/oai-pmh).

Islandora MODS Template

<titleInfo>
  <title lang="eng">{{ elements.title }}</title>
  {% if elements.subtitle is not empty%}
    <subTitle>{{ elements.subtitle }}</subTitle>
  {% endif %}
</titleInfo>
{% for personal_role_name in elements.personal_role_names |split ('|') %}
{% set personal_role_name_info = personal_role_name |split(':') %}
  <name type="personal">
    <role>
      <roleTerm type="text">{{ personal_role_name_info[0] }}</roleTerm>
    </role>
    <namePart>{{ personal_role_name_info[1] }}</namePart>
    <affiliation>{{ personal_role_name_info[2] }}</affiliation>
  </name>
{% endfor %}
{% for corporate_role_name in elements.corporate_role_names |split ('|') %}
{% set corporate_role_name_info = corporate_role_name |split(':') %}
  <name type="corporate">
    <role>
      <roleTerm type="text">{{ corporate_role_name_info[0] }}</roleTerm>
    </role>
    <namePart>{{ corporate_role_name_info[1] }}</namePart>
    <affiliation>{{ corporate_role_name_info[2] }}</affiliation>
  </name>
{% endfor %}
<typeOfResource>{{ elements.typeofresource }}</typeOfResource>
<genre>{{ elements.genre }} </genre>
<abstract>{{ elements.description }}</abstract>
<language>
  {% for language in elements.language_iso6392b |split ('|') %}
    <languageTerm authority="iso639-2b" type="code">{{ language|trim }}</languageTerm>
  {% endfor %}
</language>
<originInfo>
  <publisher>{{ elements.publisher }}</publisher>
  <place>
    <placeTerm type="text">{{ elements.published_place }}</placeTerm>
    <placeTerm authority="marccountry">{{ elements.published_place_marccountry }}</placeTerm> 	            		
  </place>
  <dateCreated keyDate="yes">{{ elements.datecreated_rad14b5 }}</dateCreated>
  {% if elements.datecreated_start_iso8601 is not empty%}
    <dateCreated point="start">{{ elements.datecreated_start_iso8601 }}</dateCreated>
  {% endif %}
  {% if elements.datecreated_end_iso8601 is not empty%}
    <dateCreated point="end">{{ elements.datecreated_end_iso8601 }}</dateCreated>
  {% endif %}
  <copyrightDate>{{ elements.datecopyright_iso8601 }}</copyrightDate>
</originInfo>
<physicalDescription>
  <form authority="smd">{{ elements.physicaldescription_form }}</form>
  <extent>{{ elements.physicaldescription_extent }}</extent>
  <reformattingQuality>{{ elements.physicaldescription_reformatting_quality }}</reformattingQuality>
  <digitalOrigin>{{ elements.physicaldescription_digitalorigin }}</digitalOrigin>
  <internetMediaType>{{ elements.physicaldescription_internetmediatype }}</internetMediaType>
  <note>{{ elements.physicaldescription_note }}</note>        	
</physicalDescription>
<subject authority="local">
  {% for topic in elements.subject_topic |split ('|') %}
    <topic>{{ topic|trim }}</topic>
  {% endfor %}
  {% for geographic in elements.subject_geographic |split ('|') %}
    <geographic>{{ geographic|trim }}</geographic>
  {% endfor %}
  {% for temporal in elements.subject_temporal |split ('|') %}
    <temporal>{{ temporal|trim }}</temporal>
  {% endfor %}
  {% for subjectname_personal in elements.subject_name_personal |split ('|') %}
    <name type="personal">
      <namePart>{{ subjectname_personal|trim }}</namePart>
    </name>
  {% endfor %}
  {% for subjectname_corporate in elements.subject_name_corporate |split ('|') %}
    <name type="corporate">
      <namePart>{{ subjectname_corporate|trim }}</namePart>
    </name>
  {% endfor %}        
  <hierarchicalGeographic>
    <continent>{{ elements.subject_hierarchicalgeographic_continent }}</continent>
    <country>{{ elements.subject_hierarchicalgeographic_country }}</country>
    <state>{{ elements.subject_hierarchicalgeographic_state }}</state>
    <province>{{ elements.subject_hierarchicalgeographic_province }}</province>
    <region>{{ elements.subject_hierarchicalgeographic_region }}</region>
    <county>{{ elements.subject_hierarchicalgeographic_county }}</county>
    <island>{{ elements.subject_hierarchicalgeographic_island }}</island>
    <city>{{ elements.subject_hierarchicalgeographic_city }}</city>
    <citySection>{{ elements.subject_hierarchicalgeographic_citysection }}</citySection>
  </hierarchicalGeographic>
  <cartographics>
    <coordinates>{{ elements.subject_geographic_coordinates }}</coordinates>
  </cartographics>
</subject>
{% if elements.relateditem_title is not empty %}
  <relatedItem type="host">
    <titleInfo>
      <title>{{ elements.relateditem_title }}</title>
    </titleInfo>
  </relatedItem>
{% endif %}
{% if elements.relateditem_collection_title is not empty %}
  <relatedItem type="collection">
    <titleInfo>
      <title>{{ elements.relateditem_collection_title }}</title>
    </titleInfo>
  </relatedItem>
{% endif %}
{% if elements.accesscondition_restrictionandaccess is not empty%}
  <accessCondition type="restriction and access">{{ elements.accesscondition_restrictionandaccess }}</accessCondition>
{% endif %}
<accessCondition type="use and reproduction">{{ elements.accesscondition_useandreproduction }}</accessCondition>
<location>
  <url usage="primary display">{{ elements.location_url }}</url>
  {% if elements.location_physical is not empty %}
    <physicalLocation>{{ elements.location_physical }}</physicalLocation>
  {% endif %}
</location>
<identifier type="uri">{{ elements.identifier_uri}}</identifier>
<identifier type="local">{{ elements.identifier_local}}</identifier>
<identifier type="ark">{{ elements.identifier_ark}}</identifier> 	    	
<note>{{ elements.note }}</note>
<recordInfo>
  {% if elements.recordinfo_note_coursecode is not empty %}
  <recordInfoNote type="courseCode">{{ elements.recordinfo_note_coursecode }}</recordInfoNote>
  {% endif %}
  {% if elements.recordinfo_note_courseyear is not empty %}
    <recordInfoNote type="courseYear">{{ elements.recordinfo_note_courseyear }}</recordInfoNote>
  {% endif %}
  {% if elements.recordinfo_note_courseterm is not empty %}
    <recordInfoNote type="courseTerm">{{ elements.recordinfo_note_courseterm }}</recordInfoNote>
  {% endif %}
  <languageOfCataloging>
  {% if elements.recordinfo_cataloguing_language_iso6392b is not empty %}
    <languageTerm authority="iso639-2b" type="code">{{ elements.recordinfo_cataloguing_language_iso6392b }}</languageTerm>
  {% else %}
    {# Assume language of cataloguing is eng #}
    <languageTerm authority="iso639-2b" type="code">eng</languageTerm>
  {% endif %}
  </languageOfCataloging>
</recordInfo>

MODS Elements and Label Names (used in the above template)

For fields with multiple values, the separator under "Multiple field settings" should be same as the one you use in the twig template. In the above template, the pipe | character has been used.

Below, a detail mapping of common MODS field Drupal fields are provided, originally based on Islandora Defaults metadata profile.

titleInfo
MODS Element Attribute Label
title lang="eng" title
subTitle subtitle
name
MODS Element Attribute Label
name title="personal" personal_role_names
name title="corporate" corporate_role_names
language
MODS Element Attribute Label
languageTerm authority="iso639-2b" type="code" language_iso6392b
originInfo
MODS Element Attribute Label
publisher publisher
placeTerm type="text" published_place
placeTerm authority="marccountry" published_place_marccountry
dateCreated keyDate="yes" datecreated_rad14b5
dateCreated point="start" datecreated_start_iso8601
dateCreated point="end" datecreated_end_iso8601
copyrightDate datecopyright_iso8601
physicalDescription
MODS Element Attribute Label
form authority="smd" physicaldescription_form
extent physicaldescription_extent
reformattingQuality physicaldescription_reformatting_quality
digitalOrigin physicaldescription_digitalorigin
internetMediaType physicaldescription_internetmediatype
note physicaldescription_note
subject
MODS Element Attribute Label
topic subject_topic
geographic subject_geographic
temporal subject_temporal
name, namePart type="personal" subject_name_personal
name, namePart type="corporate" subject_name_corporate
cartographics, coordinates subject_geographic_coordinates
hierarchicalGeographic
MODS Element Attribute Label
continent subject_hierarchicalgeographic_continent
country subject_hierarchicalgeographic_country
state subject_hierarchicalgeographic_state
province subject_hierarchicalgeographic_province
region subject_hierarchicalgeographic_region
county subject_hierarchicalgeographic_county
island subject_hierarchicalgeographic_island
city subject_hierarchicalgeographic_city
citySection subject_hierarchicalgeographic_citysection
relatedItem

For relatedItem with type="host":

MODS Element Attribute Label
title relateditem_title

For relatedItem with type="collection":

MODS Element Attribute Label
title relateditem_collection_title
location
MODS Element Attribute Label
url usage="primary display" location_url
physicalLocation location_physical
recordInfo
MODS Element Attribute Label
recordInfoNote type="courseCode" recordinfo_note_coursecode
recordInfoNote type="courseTerm" recordinfo_note_courseterm
languageOfCataloging, languageTerm authority="iso639-2b" type="code" recordinfo_cataloguing_language_iso6392b
Top-Level Elements with no Subelements
MODS Element Attribute Label
typeOfResource typeofresource
genre genre
abstract description
accessCondition type="restriction and access" accesscondition_restrictionandaccess
accessCondition type="use and reproduction" accesscondition_useandreproduction
identifier type="uri" identifier_uri
identifier type="local" identifier_local
identifier type="ark" identifier_ark
note note

Custom Plugin Development for rest_oai_pmh

Below are a series of instructions to aid in developing a custom metadata plugin for the REST OAI-PMH module. In addition to the PHP plugin class, a html.twig template with the format of display for one entity should be prepared. This will be referenced in the plugin class and should be placed in the templates folder of the module.

Plugin Class

Create a new .php file in the folder src/Plugin/OaiMetadataMap. The class should extend OaiMetadataMapBase and include the required namespaces.

<?php

namespace Drupal\rest_oai_pmh\Plugin\OaiMetadataMap;

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\rest_oai_pmh\Plugin\OaiMetadataMapBase;
use Drupal\views\Views;

/**
 * @OaiMetadataMap(
 *  id = "mods",
 *  label = @Translation("MODS (View Mapping)"),
 *  metadata_format = "mods",
 *  template = {
 *    "type" = "module",
 *    "name" = "rest_oai_pmh",
 *    "directory" = "templates",
 *    "file" = "mods"
 *  }
 * )
 */
class Mods extends OaiMetadataMapBase {

The class comment contains important information on the metadata format and the template. The value of "file" under template should match the name of the template: in the case above, the template file should be named mods.html.twig.

The plugin class should implement three methods: getMetadataFormat, getMetadataWrapper, and transformRecord.

getMetadataFormat

This method provides information on the metadata format, and is used when the request made uses the verb ListMetadataFormats.

public function getMetadataFormat() {
    return [
        'metadataPrefix' => 'mods',
        'schema' => 'http://www.loc.gov/standards/mods/v3/mods-3-7.xsd',
        'metadataNamespace' => 'http://www.loc.gov/mods/v3',
    ];
}

getMetadataWrapper

This method provies the information found in the metadata wrapper found within each <record> when listing records.

public function getMetadataWrapper() {
    return [
        'mods' => [
        '@xmlns:mods' => 'http://www.loc.gov/mods/v3',
        '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
        '@xsi:schemaLocation' => 'http://www.loc.gov/mods/v3 http://www.loc.gov/standards/mods/v3/mods-3-7.xsd',
        ],
    ];
}

transformRecord

This method is called to render each entity that is exposed to OAI-PMH. Mapping information can be obtained in many different methods, such as an RDF mapping or a view. The variables within the final render array are the variables available for use in the twig template.

public function transformRecord(ContentEntityInterface $entity) {
    // Code to get field and mapping information and put into $render_array
    // ...

    return parent::build($render_array);
}
⚠️ **GitHub.com Fallback** ⚠️