-
Notifications
You must be signed in to change notification settings - Fork 0
OAI PMH
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.
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.
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.
- Modify the template
mods.html.twig
found in the module'stemplates
folder to suit the specific needs for the repository. An Islandora MODS template is below. - 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).
- Set the View machine name and display name in the REST OAI-PMH configuration form (
/admin/config/services/rest/oai-pmh
).
<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>
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.
MODS Element | Attribute | Label |
---|---|---|
title | lang="eng" | title |
subTitle | subtitle |
MODS Element | Attribute | Label |
---|---|---|
name | title="personal" | personal_role_names |
name | title="corporate" | corporate_role_names |
MODS Element | Attribute | Label |
---|---|---|
languageTerm | authority="iso639-2b" type="code" | language_iso6392b |
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 |
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 |
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 |
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 |
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 |
MODS Element | Attribute | Label |
---|---|---|
url | usage="primary display" | location_url |
physicalLocation | location_physical |
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 |
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 |
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.
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
.
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',
];
}
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',
],
];
}
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);
}