Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISSUE-87: CSL-citations as a twig extension & a field formatter: a tale of two converging paths #192

Merged
merged 49 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7425179
add citeproc as composer dependency
Apr 5, 2022
0fc4d11
Replace deprecated Twig_Extension deprecated class
Apr 6, 2022
16c1565
Add some boilerplate
Apr 6, 2022
0a77ad7
more boilerplate because I don't even understand the basics
Apr 6, 2022
1e5ae94
add boilerplate for citation formatter
Apr 7, 2022
3ae0d0d
Merge pull request #188 from esmero/ISSUE-187
aksm Apr 11, 2022
9a47457
just testing things out and trying to understand what I'm doing
Apr 12, 2022
7fa54d5
Get style list from CSL files and add them to settings form.
Apr 19, 2022
d1f21e9
Display selected citation styles in summary
Apr 21, 2022
de40516
Citation formatter code rendering from example JSON data (not a TWIG …
Apr 26, 2022
7b4679a
Merge remote-tracking branch 'origin/1.0.0-RC3' into ISSUE-87
Apr 26, 2022
e5aaaf5
Update composer for 1.0.0 upcoming release
DiegoPino Apr 1, 2022
572209c
tasks/24607546-cws-to-iab --- spitballing code for display book objec…
patdunlavey Mar 25, 2022
00bc018
tasks/24607546-cws-to-iab --- this is working now for my CWS using 'I…
patdunlavey Mar 30, 2022
09cd6cd
tasks/24607546-cws-to-iab --- added a bit of inline documentation cla…
patdunlavey Mar 30, 2022
a3bf56c
tasks/24607546-cws-to-iab --- reworked getImagesListApi3 to use the f…
patdunlavey Mar 31, 2022
116e866
tasks/24607546-cws-to-iab --- added sanity check for annotation.body.…
patdunlavey Mar 31, 2022
ef819ef
tasks/24607546-cws-to-iab --- removed commented out code that should …
patdunlavey Apr 1, 2022
98c9ce4
Moving to 1.0.0.x-dev dependencies
DiegoPino Apr 18, 2022
6349db0
Add dependencies to composer.
Apr 28, 2022
16fe13c
Spacing error on composer.json
Apr 28, 2022
73cc895
composer.json was missing a bracket.
Apr 28, 2022
49cfed9
Bracket in composer.json was on wrong line :(
Apr 28, 2022
3b6c0db
Still using example JSON string but passed from metatdata template.
Apr 29, 2022
9d99883
Basic logic for processing locales.
May 3, 2022
d3cf3ca
Remove label options from settings form.
May 3, 2022
d5092a3
Inlined all style select html and javascript for expediency's sake.
May 4, 2022
5ea6cf3
Moved Javascript to file and loaded as attachment. Still need to refa…
May 5, 2022
e2aa108
Check json_decode errors.
May 5, 2022
8cb6a17
Merge branch '1.0.0' into ISSUE-87
aksm May 5, 2022
3450b42
Refactor to use element ID passed from Drupal instead of hardcoded.
May 5, 2022
45edd2a
Just some starting boilerplate for drush commands.
May 12, 2022
5997dd6
Drush command downloads and extracts dependencies, but still lots of …
May 19, 2022
c250b1c
Update composer for 1.0.0 upcoming release
DiegoPino Apr 1, 2022
539ced2
tasks/24607546-cws-to-iab --- spitballing code for display book objec…
patdunlavey Mar 25, 2022
6e4afc6
tasks/24607546-cws-to-iab --- this is working now for my CWS using 'I…
patdunlavey Mar 30, 2022
8a28fdd
Just some cleanup.
May 19, 2022
d41d7ee
Add warning at top of settings form if folder of dependencies doesn't…
May 19, 2022
882d71a
Drupal coding standards on control structures. Still learning!
May 19, 2022
b363db6
Switch to versioned branches as recommended by README, especially sin…
May 20, 2022
479f0cd
Give drush user option to re-download and extract libraries in case s…
May 20, 2022
7c9f048
Additional cleanup. Refactor shared variables and functions. DRY!
May 21, 2022
b4e522b
Refactor to use vendor folder because citeproc automatically looks fo…
May 24, 2022
6ffcf09
First attempt at overriding CiteProc's StyleSheet class. Probably doi…
May 24, 2022
aea45be
Refactor to use Class to generate bibliography. Still need to attach …
May 25, 2022
b2ee16b
Just some cleanup.
May 25, 2022
d0d9015
Finally! Twig filter attaches library and functions. Still need to fi…
May 26, 2022
37ce25c
Generate unique IDs for multiple containers.
May 26, 2022
933734b
Merge branch '1.0.0' into ISSUE-87
DiegoPino Jun 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"require": {
"ml/json-ld": "^1.2",
"mtdowling/jmespath.php":"^2.5",
"strawberryfield/strawberryfield":"1.0.0.x-dev",
"strawberryfield/strawberryfield":"dev-1.0.0",
"league/html-to-markdown":"^5.0.0",
"erusev/parsedown": "^1.7",
"drupal/webform": "^5.23 || ^6.0",
Expand Down
34 changes: 34 additions & 0 deletions config/schema/format_strawberryfield.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -570,3 +570,37 @@ format_strawberryfield.jmespath_config.mapping:
jmespath_alternative_filter:
type: string
label: 'An alternative JMESPath filter expression'

field.formatter.settings.strawberry_citation_formatter:
type: mapping
label: 'Specific Config for strawberry_metadata_formatter using Twig'
mapping:
iiif_base_url:
type: string
label: 'Custom Public IIIF Server URL'
iiif_base_url_internal:
type: string
label: 'Custom internal IIIF Server URL'
json_key_source:
type: string
label: 'Strawberryfield/JSON key containing file URIs to display'
max_width:
type: integer
max_height:
type: integer
use_iiif_globals:
type: string
label: 'Whether to use global IIIF settings or not.'
label:
type: string
metadatadisplayentity_uuid:
type: string
metadatadisplayentity_uselabel:
type: string
citationstyle:
type: mapping
mapping:
type: string
label: 'style'
localekey:
type: string
10 changes: 10 additions & 0 deletions drush.services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
format_strawberryfield.commands:
class: Drupal\format_strawberryfield\Commands\LibrariesDrushCommands
arguments:
- '@http_client'
- '@state'
- '@datetime.time'
- '@file_system'
tags:
- { name: drush.command }
2 changes: 1 addition & 1 deletion format_strawberryfield.install
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,4 @@ function format_strawberryfield_update_8702() {
}
$message = "All Metadata Display Entities in Configurations updated to use UUIDs";
return $message;
}
}
10 changes: 10 additions & 0 deletions format_strawberryfield.libraries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,13 @@ lazyload_strawberry:
- format_strawberryfield/lozad
- core/jquery
- core/drupal

citations_strawberry:
version: 1.0
js:
js/citations_strawberry.js: {minified: false}
dependencies:
- core/jquery
- core/drupal
- core/jquery.once
- core/drupalSettings
2 changes: 1 addition & 1 deletion format_strawberryfield.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ services:
arguments: ['@strawberryfield.utility', '@config.factory']
format_strawberryfield.embargo_resolver:
class: Drupal\format_strawberryfield\EmbargoResolver
arguments: ['@config.factory', '@current_user', '@request_stack']
arguments: ['@config.factory', '@current_user', '@request_stack']
25 changes: 25 additions & 0 deletions js/citations_strawberry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(function ($, Drupal, drupalSettings) {
'use strict';
Drupal.behaviors.format_strawberryfield_citations = {
attach: function (context, settings) {
$('.bibliography').once('attach_citations')
.each(function (index, value) {
var theid = '#' + $(this).attr("id");
var bibliographyContainer = document.querySelector(theid);
var citationStyleSelector = bibliographyContainer.querySelector('.citation-style-selector');
var styledBibs = bibliographyContainer.querySelectorAll(".csl-bib-body-container");
citationStyleSelector.addEventListener("change", function() {
var selectedStyle = this.value;
styledBibs.forEach(function (el, i) {
var elClassList = el.classList;
if (elClassList.contains("hidden") && elClassList.contains(selectedStyle)) {
elClassList.remove("hidden");
} else if (!elClassList.contains("hidden") && !elClassList.contains(selectedStyle)) {
elClassList.add("hidden");
}
});
});
});
}
};
})(jQuery, Drupal, drupalSettings);
92 changes: 30 additions & 62 deletions js/plugin.iiif-iabookreader_strawberry.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,13 @@ BookReader.prototype.parseSequence = function (sequenceId) {
if (item['id'] === sequenceId) {
self.IIIFsequence.title = "Sequence";
self.IIIFsequence.bookUrl = "http://iiif.io";
self.IIIFsequence.imagesList = getImagesListApi3(self.jsonLd);
self.IIIFsequence.imagesList = getImagesListApi3(self.jsonLd.items);
self.numLeafs = self.IIIFsequence.imagesList.length;
}
} else {
self.IIIFsequence.title = "Sequence";
self.IIIFsequence.bookUrl = "http://iiif.io";
self.IIIFsequence.imagesList = getImagesListApi3(self.jsonLd);
self.IIIFsequence.imagesList = getImagesListApi3(self.jsonLd.items);
self.numLeafs = self.IIIFsequence.imagesList.length;
// return false;
// Just take the first one if no default one set
Expand All @@ -150,7 +150,7 @@ BookReader.prototype.parseSequence = function (sequenceId) {
} else {
self.IIIFsequence.title = "Sequence";
self.IIIFsequence.bookUrl = "http://iiif.io";
self.IIIFsequence.imagesList = getImagesList(sequence);
self.IIIFsequence.imagesList = getImagesListApi3(self.jsonLd);
self.numLeafs = self.IIIFsequence.imagesList.length;
return false;
// Just take the first one if no default one set
Expand Down Expand Up @@ -259,72 +259,40 @@ BookReader.prototype.parseSequence = function (sequenceId) {
return imagesList;
}

// This expects individual pages to be provided as the root level `items` in the jsonLd.
// If a structure range element exists, it is used to define the page order.
// This works with "IIIF Presentation API 3 Creative Works Series Manifest" as provided by
// https:/esmero/archipelago-deployment-live/blob/1.0.0-RC3/drupal/d8content/metadatadisplay_entity_14.json
function getImagesListApi3(jsonLd) {
function getImagesListApi3(items) {
var imagesList = [];
let items = jsonLd.items;
if(items.length > 0) {
// Use the first structure range, if present, to reorder the items.
if (jsonLd.structures[0].items.length > 0) {
// Create associative array of the items so we can use the first structure range to order them.
let itemsMap = {};
jQuery.each(items, function (index, item) {
itemsMap[item.id] = item;
});

// Zero out the items array, then push individual items back in in the order found in the structure range.
newitems = [];
jQuery.each(jsonLd.structures[0].items, function (index, structureItem) {
if(itemsMap.hasOwnProperty(structureItem.id)) {
newitems.push(itemsMap[structureItem.id]);
}
else {
console.log("Could not find \"" + structureItem.id + "\" in the IIIF 3.0 presentation manifest's items array.");
jQuery.each(items, function (index, item) {
if (item['type'] === 'Canvas') {
let imageObj = {
canvasHeight: item.height || 0,
canvasWidth: item.width || 0,
};
let annotationpages = item.items;
jQuery.each(annotationpages, function (index, annotationpage) {
if ((annotationpage['type'] === 'AnnotationPage') && (annotationpage['items'][0]['type'] === 'Annotation') && (annotationpage['items'][0]['body'])) {
let annotation = annotationpage['items'][0];
imageObj.serviceUrl = null;
if (annotation.body.hasOwnProperty('service') && annotation.body.service[0]['id'] && isValidHttpUrl(annotation.body.service[0]['id'])) {
imageObj.serviceUrl = annotation.body.service[0]['id'].replace(/\/$/, '');
}
imageObj.imageUrl = annotation.body.id || "";
// imageObj.imageUrl = imageObj.imageUrl.replace(/\/full\/full\/0\/default.jpg/, '/full/'+ imageObj.canvasWidth + ',/0/default.jpg');
imageObj.width = annotation.body.width || 0;
imageObj.height = annotation.body.height || 0;
imageObj.aspectRatio = (imageObj.width / imageObj.height) || 1;
imageObj.imageGetArgument = getURLArgument(annotation.body.id);

// Add it to the images list
if (!(/#xywh/).test(annotation.target)) {
imagesList.push(imageObj);
}
}
});
if(newitems.length == jsonLd.structures[0].items.length) {
items = newitems;
}
else {
console.log("Could not use structures to set page order because structure range item ids did not match item ids.");
}

}

// Extract data from each item.
jQuery.each(items, function (index, item) {
if (item['type'] === 'Canvas') {
let imageObj = {
canvasHeight: item.height || 0,
canvasWidth: item.width || 0,
};
let annotationpages = item.items;
jQuery.each(annotationpages, function (index, annotationpage) {
if ((annotationpage['type'] === 'AnnotationPage') && (annotationpage['items'][0]['type'] === 'Annotation') && (annotationpage['items'][0]['body'])) {
let annotation = annotationpage['items'][0];
imageObj.serviceUrl = null;
if (annotation.body.hasOwnProperty('service') && annotation.body.service[0] && annotation.body.service[0]['id'] && isValidHttpUrl(annotation.body.service[0]['id'])) {
imageObj.serviceUrl = annotation.body.service[0]['id'].replace(/\/$/, '');
}
imageObj.imageUrl = annotation.body.id || "";
imageObj.width = annotation.body.width || 0;
imageObj.height = annotation.body.height || 0;
imageObj.aspectRatio = (imageObj.width / imageObj.height) || 1;
imageObj.imageGetArgument = getURLArgument(annotation.body.id);
// Add it to the images list
if (!(/#xywh/).test(annotation.target)) {
imagesList.push(imageObj);
}
}
});

}
});

}
});

return imagesList;
}
Expand Down
153 changes: 153 additions & 0 deletions src/Commands/LibrariesDrushCommands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Drupal\format_strawberryfield\Commands;

use Drush\Commands\DrushCommands;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\State\StateInterface;
use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\Response;
use Drupal\Core\Archiver\Tar;
use Drupal\Component\Datetime\TimeInterface;

/**
* A Format Strawberryfield Drush commandfile.
*
*/
class LibrariesDrushCommands extends DrushCommands {

/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;

/**
* The state key value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;

/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;

/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;

/**
* Constructs the object.
*
* @param \GuzzleHttp\Client $http_client
* The HTTP client.
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file handler.
*/
public function __construct(
Client $http_client,
StateInterface $state,
TimeInterface $time,
FileSystemInterface $file_system
) {
parent::__construct();
$this->httpClient = $http_client;
$this->state = $state;
$this->time = $time;
$this->fileSystem = $file_system;
}

/**
* Downloads files from requested source to requested destination.
*
* @command archipelago:download
* @aliases archipelago-download
*/
public function downloadFiles($source, $destination) {
$io = $this->io();

$io->write("<info>$source</info>");

$directory = dirname($destination);
if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) {
$io->error("Could not create directory $directory.");
return 1;
}

try {
$response = $this->httpClient->get($source, ['sink' => $destination]);
}
catch (\Exception $exception) {
$io->error($exception->getMessage());
return 1;
}

$status_code = $response->getStatusCode();
Response::$statusTexts[$status_code];
$io->writeln(' [' . $status_code . ' ' . Response::$statusTexts[$status_code] . ']');

$io->success("Files have been downloaded into $destination directory.");
return $destination;
}

/**
* Downloads citeproc-php and dependencies.
*
* @command archipelago:download-citeproc-dependencies
* @aliases archipelago-download-citeproc-dependencies
*/
public function downloadCiteProcDependencies() {
$io = $this->io();
$file_system = $this->fileSystem;
$csl_root = DRUPAL_ROOT . '/libraries/citation-style-language';
if ($file_system->prepareDirectory($csl_root )) {
$io->note('Citeproc-php dependencies are present.');
return 1;
} else {
$io->note('Citeproc-php dependencies are not present.');
$csl_locales_path = $csl_root . '/locales';
$csl_styles_path = $csl_root . '/styles-distribution';
$csl_styles_url = 'https:/citation-style-language/styles-distribution/tarball/master';
$csl_locales_url = 'https:/citation-style-language/locales/tarball/master';
$tmp_dir = $file_system->getTempDirectory() ;
$csl_styles_destination = $this->downloadFiles($csl_styles_url, $tmp_dir . '/citeproc-styles.tar.gz');
$csl_locales_destination = $this->downloadFiles($csl_locales_url, $tmp_dir . '/citeproc-locales.tar.gz');
$csl_styles_tar = new TAR($csl_styles_destination);
$csl_locales_tar = new TAR($csl_locales_destination);

$csl_locales_files_all = $csl_locales_tar->listContents();
$csl_locales_files_extract = [];
$csl_locales_path_remove = explode('/', $csl_locales_files_all[0],0 )[0];
foreach ( $csl_locales_files_all as $csl_locales_file ) {
if (str_ends_with( $csl_locales_file, '.xml' ) || str_ends_with( $csl_locales_file, '.json' )) {
array_push( $csl_locales_files_extract, $csl_locales_file );
}
}
$csl_locales_tar->extract( $csl_root, $csl_locales_files_extract );
rename($csl_root . '/' . $csl_locales_path_remove, $csl_locales_path);

$csl_styles_files_all = $csl_styles_tar->listContents();
$csl_styles_files_extract = [];
$csl_styles_path_remove = explode('/', $csl_styles_files_all[0],0 )[0];
foreach ( $csl_styles_files_all as $csl_styles_file ) {
if (str_ends_with( $csl_styles_file, '.csl')) {
array_push( $csl_styles_files_extract, $csl_styles_file );
}
}
$csl_styles_tar->extract( $csl_root, $csl_styles_files_extract );
rename($csl_root . '/' . $csl_styles_path_remove, $csl_styles_path );
}
}
}
Loading