Skip to content

Instantly share code, notes, and snippets.

@dmj
Last active April 3, 2023 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmj/9a79e92e216d15aa74a472004efdd379 to your computer and use it in GitHub Desktop.
Save dmj/9a79e92e216d15aa74a472004efdd379 to your computer and use it in GitHub Desktop.
<?php
namespace SUBHH\VuFind\RecordDescription;
use SUBHH\VuFind\SolrMarc\SolrMarc;
interface DescriptionProviderInterface
{
/** @return array<string, DisplayValueInterface[]> */
public function createDescription (SolrMarc $record) : array;
}
<?php
namespace SUBHH\VuFind\RecordDescription;
class DisplayValue implements DisplayValueInterface
{
/** @var string */
private $value;
/** @var ?string */
private $prefix;
/** @var ?string */
private $suffix;
/** @var ?string */
private $textdomain = null;
/** @var bool */
private $translatable = false;
public function __construct (string $value)
{
$this->value = $value;
}
public function isTranslatable () : bool
{
return $this->translatable;
}
public function setIsTranslatable (bool $translatable) : void
{
$this->translatable = $translatable;
}
public function setTextDomain (string $textdomain = null) : void
{
$this->textdomain = $textdomain;
}
public function getTextDomain () : ?string
{
return $this->textdomain;
}
public function getValue () : string
{
return $this->value;
}
public function getSuffix () : ?string
{
return $this->suffix;
}
public function getPrefix () : ?string
{
return $this->prefix;
}
public function setSuffix (string $suffix) : void
{
$this->suffix = $suffix;
}
public function setPrefix (string $prefix) : void
{
$this->prefix = $prefix;
}
public function __toString () : string
{
return $this->getValue();
}
public static function newInstance (string $value, bool $isTranslatable = false) : DisplayValue
{
$value = new DisplayValue($value);
$value->setIsTranslatable($isTranslatable);
return $value;
}
/**
* @param string[] $values
* @return DisplayValue[]
*/
public static function newInstances (array $values, bool $isTranslatable = false) : array
{
$instances = array();
foreach ($values as $value) {
$instances[] = static::newInstance($value, $isTranslatable);
}
return $instances;
}
}
<?php
namespace SUBHH\VuFind\RecordDescription;
interface DisplayValueInterface
{
public function isTranslatable () : bool;
public function getValue () : string;
}
<?php
namespace SUBHH\VuFind\RecordDescription;
use ArrayIterator;
use Iterator;
class DisplayValueSequence implements DisplayValueInterface
{
/** @var DisplayValueInterface[] */
private $members = array();
/** @var string */
private $separator;
public function __construct (string $separator = ' ')
{
$this->separator = $separator;
}
public function append (DisplayValueInterface $member) : void
{
$this->members[] = $member;
}
public function getIterator () : Iterator
{
return new ArrayIterator($this->members);
}
public function getSeparator () : string
{
return $this->separator;
}
public function isTranslatable () : bool
{
return false;
}
public function getValue () : string
{
$values = array();
foreach ($this->members as $member) {
$values[] = $member->getValue();
}
return implode($this->getSeparator(), $values);
}
}
<?php
namespace SUBHH\VuFind\RecordDescription;
use SUBHH\VuFind\SolrMarc\SolrMarc;
use VuFind\Marc\MarcReader;
use UnexpectedValueException;
class FullDescriptionProvider implements DescriptionProviderInterface
{
/** @return array<string, DisplayValueInterface[]> */
public function createDescription (SolrMarc $record) : array
{
$reader = $record->getMarcReader();
$description = array();
$description['Title'] = [$this->getTitle($reader)];
$description['Identifier'] = [new DisplayValue($record->getUniqueID())];
$description['Preceding Title'] = $this->getPrecedingTitle($reader);
$description['Succeeding Title'] = $this->getSucceedingTitle($reader);
$description['Title Variant'] = $this->getTitleVariant($reader);
$description['Work Title'] = $this->getUniformTitle($reader);
$description['Summary'] = $this->getSummary($reader);
$description['Persons'] = $this->getPersons($reader);
$description['Corporate Bodies'] = $this->getCorporateBody($reader);
$description['Congresses'] = $this->getCongresses($reader);
$description['Media Type'] = DisplayValue::newInstances($record->getFormats(), true);
$description['Form Note'] = $this->getFormNote($reader);
$description['Language'] = $this->getLanguages($reader);
$description['Extent'] = DisplayValue::newInstances($reader->getFieldsSubfields('300', ['a', 'b', 'c', 'e']));
$description['Published'] = $this->getPublicationStatement($reader);
$description['Containing Work'] = $this->getHostItem($reader);
$description['Included Items'] = $this->getIncludedItems($reader);
$description['Edition'] = DisplayValue::newInstances($reader->getFieldsSubfields('250', ['a'], null));
$description['Note'] = DisplayValue::newInstances($reader->getFieldsSubfields('500', ['a'], null));
$description['Bibliographic Context'] = $this->getBibliographicContext($reader);
$description['Basisklassifikation'] = $this->getKeywordsBKL($reader);
$description['RVK'] = $this->getKeywordsRVK($reader);
$description['Other Classifications'] = $this->getOtherClassifications($reader);
$description['Keywords'] = $this->getKeywords($reader);
$description['Links'] = $this->getExternalLinks($reader);
$description['ISBN'] = $this->getIsbn($reader);
$description['ISSN'] = $this->getIssn($reader);
$description['Series'] = $this->getSeries($reader);
return array_filter($description);
}
/** @return DisplayValueInterface[] */
private function getFormNote (MarcReader $reader) : array
{
$notes = array();
foreach ($reader->getFieldsSubfields('655', ['a', 'b', 'v', 'x', 'y', 'z'], ' ; ') as $value) {
$notes[] = new DisplayValue($value);
}
return $notes;
}
/** @return DisplayValueInterface[] */
private function getOtherClassifications (MarcReader $reader) : array
{
$classes = array();
foreach ($reader->getFields('983') as $field) {
if ($reader->getSubfield($field, '2') === '22') {
if ($value = $reader->getSubfield($field, 'a')) {
$value = new SearchLink($value);
$value->setSearchType('Subject');
$value->setSearchTerm($value);
$classes[] = $value;
}
}
}
return $classes;
}
/** @return DisplayValueInterface[] */
private function getBibliographicContext (MarcReader $reader) : array
{
$context = array();
foreach ($reader->getFields('776') as $field) {
if ($title = $reader->getSubfield($field, 't')) {
$value = new SearchLink($title);
$this->annotateRelatedSearchLink($reader, $field, $value);
if ($prefix = array_filter($this->getSubfields($field, 'i', 'n'))) {
$value->setPrefix(implode(' ', $prefix) . ': ');
}
if ($suffix = $reader->getSubfield($field, 'd')) {
$value->setSuffix(' – ' . $suffix);
}
$context[] = $value;
}
}
return $context;
}
/** @return DisplayValueInterface[] */
private function getUniformTitle (MarcReader $reader) : array
{
$titles = array();
foreach ($reader->getFields('240') as $field) {
if ($title = $reader->getSubfield($field, 'a')) {
$value = new DisplayValueSequence();
$title = new SearchLink($title);
$title->setSearchTerm($title);
$title->setSearchTermQuote('"');
$value->append($title);
if ($authorField = $this->getDataField($reader, '100')) {
if ($name = $reader->getSubfield($authorField, 'a')) {
$value->append(new DisplayValue($name));
$rolecode = $reader->getSubfield($field, '4') ?: 'oth';
$role = new DisplayValue($rolecode);
$role->setPrefix('[');
$role->setSuffix(']');
$role->setIsTranslatable(true);
$role->setTextDomain('CreatorRoles');
$value->append($role);
}
}
$titles[] = $value;
}
}
return $titles;
}
/** @return DisplayValueInterface[] */
private function getPrecedingTitle (MarcReader $reader) : array
{
$titles = array();
foreach ($reader->getFields('780') as $field) {
if ($title = $reader->getSubfield($field, 't')) {
$value = new SearchLink($title);
$this->annotateRelatedSearchLink($reader, $field, $value);
$titles[] = $value;
}
}
return $titles;
}
/** @return DisplayValueInterface[] */
private function getSucceedingTitle (MarcReader $reader) : array
{
$titles = array();
foreach ($reader->getFields('785') as $field) {
if ($title = $reader->getSubfield($field, 't')) {
$value = new SearchLink($title);
$this->annotateRelatedSearchLink($reader, $field, $value);
$titles[] = $value;
}
}
return $titles;
}
/** @return DisplayValueInterface[] */
private function getHostItem (MarcReader $reader) : array
{
$hosts = array();
foreach ($reader->getFields('773') as $field) {
$title = $reader->getSubfield($field, 't') ?: implode($reader->getFieldsSubfields('245', ['a']));
if ($title) {
list($relationship, $pubinfo, $parts) = $this->getSubfields($field, 'i', 'd', 'g');
$value = new SearchLink($title);
if ($relationship) {
$value->setPrefix($relationship . ': ');
}
if ($pubinfo || $parts) {
if ($pubinfo && $parts) {
$value->setSuffix('.- ' . $pubinfo . ', ' . $parts);
} else {
$value->setSuffix('.- ' . $pubinfo . $parts);
}
}
$this->annotateRelatedSearchLink($reader, $field, $value);
$hosts[] = $value;
}
}
return $hosts;
}
/** @return DisplayValueInterface[] */
private function getCongresses (MarcReader $reader) : array
{
$congresses = array();
foreach ($reader->getFields('111') as $field) {
list($name, $date, $place) = $this->getSubfields($field, 'a', 'd', 'c');
if ($name) {
if ($date) {
$name .= ', ' . $date;
}
if ($place) {
$name .= ', ' . $place;
}
$congress = new SearchLink($name);
$congress->setSearchType('Title');
$congresses[] = $congress;
}
}
return $congresses;
}
/** @return DisplayValueInterface[] */
private function getIncludedItems (MarcReader $reader) : array
{
$items = array_merge(
DisplayValue::newInstances($reader->getFieldsSubfields('501', ['a'], null)),
DisplayValue::newInstances($reader->getFieldsSubfields('505', ['a'], null))
);
return $items;
}
/** @return DisplayValueInterface[] */
private function getSeries (MarcReader $reader) : array
{
$series = array();
$fields = array_merge(
$reader->getFields('830')
);
foreach ($fields as $field) {
if ($label = $reader->getSubfield($field, 'a')) {
$value = new SearchLink($label);
$this->annotateRelatedSearchLink($reader, $field, $value);
if ($volume = $reader->getSubfield($field, 'v')) {
$value->setSuffix(' - ' . $volume);
}
$series[] = $value;
}
}
return $series;
}
/** @return DisplayValueInterface[] */
private function getExternalLinks (MarcReader $reader) : array
{
$links = array();
foreach ($reader->getFields('856') as $field) {
if ($field['i2'] === '2') {
if ($linkTarget = $reader->getSubfield($field, 'u')) {
$link = new ExternalLink($linkTarget);
$description = implode(' ', $this->getSubfields($field, '3', 'z'));
$link->setLinkLabel($description ?: $linkTarget);
$links[] = $link;
}
}
}
return $links;
}
/** @return DisplayValueInterface[] */
private function getCorporateBody (MarcReader $reader) : array
{
$corporations = array();
$fields = array_merge(
[$this->getDataField($reader, '110')],
$reader->getFields('710')
);
foreach ($fields as $field) {
if ($name = $reader->getSubfield($field, 'a')) {
$value = new DisplayValueSequence();
$corporation = new SearchLink($name);
$corporation->setSearchTermQuote('"');
$corporation->setSearchType('Person');
$value->append($corporation);
if ($date = $reader->getSubfield($field, 'd')) {
$value->append(new DisplayValue($date));
}
$rolecode = $reader->getSubfield($field, '4') ?: 'oth';
$role = new DisplayValue($rolecode);
$role->setPrefix('[');
$role->setSuffix(']');
$role->setIsTranslatable(true);
$role->setTextDomain('CreatorRoles');
$value->append($role);
$corporations[] = $value;
}
}
return $corporations;
}
/** @return DisplayValueInterface[] */
private function getPublicationStatement (MarcReader $reader) : array
{
$publicationStatements = array();
foreach ($reader->getFields('264') as $field) {
list($place, $publisher, $date) = $this->getSubfields($field, 'a', 'b', 'c');
$statement = $place;
if ($place && ($publisher || $date)) {
$statement .= ': ';
}
if ($publisher) {
$statement .= $publisher;
}
if ($publisher && $date) {
$statement .= ' ';
}
if ($date) {
$statement .= $date;
}
if ($statement) {
$publicationStatements[] = new DisplayValue($statement);
}
}
foreach ($reader->getFields('501') as $field) {
$publicationStatements[] = new DisplayValue($reader->getSubfield($field, 'a'));
}
return $publicationStatements;
}
/** @return DisplayValueInterface[] */
private function getKeywords (MarcReader $reader) : array
{
$keywords = array();
$fields = array_merge(
$reader->getFields('600'),
$reader->getFields('610'),
$reader->getFields('630'),
$reader->getFields('650'),
$reader->getFields('651'),
);
foreach ($fields as $field) {
if ($main = $reader->getSubfield($field, 'a')) {
$main = array_merge(
[$main],
$reader->getSubfields($field, 'z'),
$reader->getSubfields($field, 'x'),
$reader->getSubfields($field, 'y'),
);
$term = implode(', ', $main);
$keyword = new SearchLink($term);
$keyword->setSearchTerm($term);
$keyword->setSearchTermQuote('"');
$keyword->setSearchType('Subject');
$keywords[] = $keyword;
}
}
$keywords = array_merge($keywords, $this->getKeywordChain($reader));
return $keywords;
}
/** @return DisplayValueInterface[] */
private function getKeywordChain (MarcReader $reader) : array
{
$keywords = array();
$fields = array();
foreach ($reader->getFields('689') as $field) {
if (ctype_digit($field['i1']) && ctype_digit($field['i2'])) {
$index = intval($field['i1']);
$pos = intval($field['i2']);
$term = $reader->getSubfield($field, 'a');
$member = new SearchLink($term);
$member->setSearchTerm('"' . addcslashes($term, '"') . '"');
$member->setSearchType('Subject');
$fields[$index][$pos] = $member;
}
}
ksort($fields);
foreach ($fields as $members) {
$chain = new DisplayValueSequence(' / ');
foreach ($members as $member) {
$chain->append($member);
}
$keywords[] = $chain;
}
return $keywords;
}
/** @return DisplayValueInterface[] */
private function getKeywordsRVK (MarcReader $reader) : array
{
$keywords = array();
foreach ($reader->getFields('936') as $field) {
if ($field['i1'] === 'r' && $field['i2'] === 'v') {
if ($term = $reader->getSubfield($field, 'a')) {
if ($label = $reader->getSubfield($field, 'b')) {
$label = $term . ' / ' . $label;
} else {
$label = $term;
}
$keyword = new SearchLink($label);
$keyword->setSearchTerm($term);
$keyword->setSearchType('Class');
if ($suffix = $reader->getSubfields($field, 'k')) {
$keyword->setSuffix(' [' . implode(', ', $suffix) . ']');
}
$keywords[] = $keyword;
}
}
}
return $keywords;
}
/** @return DisplayValueInterface[] */
private function getKeywordsBKL (MarcReader $reader) : array
{
$keywords = array();
foreach ($reader->getFields('936') as $field) {
if ($field['i1'] === 'b' && $field['i2'] === 'k') {
if ($term = $reader->getSubfield($field, 'a')) {
$label = array_filter($this->getSubfields($field, 'a', 'j', 'x'));
$keyword = new SearchLink(implode(' ', $label));
$keyword->setSearchTerm($term);
$keyword->setSearchType('BK');
$keywords[] = $keyword;
}
}
}
return $keywords;
}
/** @return DisplayValueInterface[] */
private function getPersons (MarcReader $reader) : array
{
$persons = array();
$fields = array_merge(
[$this->getDataField($reader, '100')],
$reader->getFields('700')
);
foreach ($fields as $field) {
if ($name = $reader->getSubfield($field, 'a')) {
$value = new DisplayValueSequence();
$person = new SearchLink($name);
$person->setSearchType('Person');
$person->setSearchTermQuote('"');
$value->append($person);
if ($date = $reader->getSubfield($field, 'd')) {
$value->append(new DisplayValue($date));
}
$rolecode = $reader->getSubfield($field, '4') ?: 'oth';
$role = new DisplayValue($rolecode);
$role->setPrefix('[');
$role->setSuffix(']');
$role->setIsTranslatable(true);
$role->setTextDomain('CreatorRoles');
$value->append($role);
$persons[] = $value;
}
}
return $persons;
}
/** @return DisplayValueInterface[] */
private function getLanguages (MarcReader $reader) : array
{
$languages = array();
foreach ($reader->getFieldsSubfields('041', ['a'], null) as $code) {
$language = new DisplayValue($code);
$language->setIsTranslatable(true);
$languages[] = $language;
}
return $languages;
}
/** @return DisplayValueInterface[] */
private function getSummary (MarcReader $reader) : array
{
return DisplayValue::newInstances($reader->getFieldsSubfields('520', ['a'], null));
}
/** @return DisplayValue */
private function getTitle (MarcReader $reader) : DisplayValue
{
// Note: 245 is non-repeatable
if ($field = $this->getDataField($reader, '245')) {
$title = $reader->getSubfield($field, 'a');
if ($subtitle = $reader->getSubfield($field, 'b')) {
$title .= ' : ' . $subtitle;
}
if ($medium = $reader->getSubfield($field, 'h')) {
$title .= ' / ' . $medium;
}
foreach ($field['subfields'] as $subfield) {
if ($subfield['code'] === 'n') {
$title .= ' / ' . $subfield['data'];
}
if ($subfield['code'] === 'p') {
$title .= ', ' . $subfield['data'];
}
}
if ($resp = $reader->getSubfield($field, 'c')) {
$title .= ' : ' . $resp;
}
return new DisplayValue($title);
}
return new DisplayValue('no title');
}
/** @return DisplayValueInterface[] */
private function getTitleVariant (MarcReader $reader) : array
{
$titles = array();
foreach ($reader->getFields('246') as $field) {
if ($field['i2'] !== '3') {
if ($title = $reader->getSubfield($field, 'a')) {
$value = new DisplayValue($title);
$titles[] = $value;
}
}
}
return $titles;
}
/** @return DisplayValueInterface[] */
private function getIssn (MarcReader $reader) : array
{
$issns = array();
foreach ($reader->getFields('022') as $field) {
if ($issn = $reader->getSubfield($field, 'a')) {
$value = new SearchLink($issn);
$value->setSearchType('ISN');
$value->setSearchTerm($value);
$issns[] = $value;
}
}
return $issns;
}
/** @return DisplayValueInterface[] */
private function getIsbn (MarcReader $reader) : array
{
$isbns = array();
foreach ($reader->getFields('020') as $field) {
list($isbn, $label) = $this->getSubfields($field, 'a', '9');
if ($isbn) {
$value = new SearchLink($label ?: $isbn);
$value->setSearchType('ISN');
$value->setSearchTerm($isbn);
$isbns[] = $value;
}
}
return $isbns;
}
// private function getControlField (MarcReader $reader, string $tag) : ?string
// {
// $field = $reader->getField($tag);
// if ($field) {
// if (is_array($field)) {
// throw new UnexpectedValueException();
// }
// return $field;
// }
// return null;
// }
/** @return array<mixed> */
private function getDataField (MarcReader $reader, string $tag) : array
{
$field = $reader->getField($tag);
if ($field && !is_array($field)) {
throw new UnexpectedValueException();
}
return (array)$field;
}
/**
* @param array<mixed> $field
* @return string[] | null[]
*/
private function getSubfields (array $field, string ...$codes) : array
{
$values = array_fill(0, count($codes), null);
foreach ($field['subfields'] as $subfield) {
$key = array_search($subfield['code'], $codes, true);
if ($key !== false) {
$values[$key] = $subfield['data'];
unset($codes[$key]);
}
}
return $values;
}
private function annotateRelatedSearchLink (MarcReader $reader, array $field, SearchLink $value) : void
{
foreach ($reader->getSubfields($field, 'w') as $id) {
if (str_starts_with($id, '(DE-627)')) {
$value->setSearchTerm((string)substr($id, 8));
$value->setSearchType('Id');
return;
}
}
foreach ($reader->getSubfields($field, 'w') as $id) {
if (str_starts_with($id, '(DE-600)')) {
$value->setSearchTerm('(DE-599)ZDB' . (string)substr($id, 8));
$value->setSearchType('Numbers');
$value->setSearchTermQuote('"');
return;
}
}
if ($issn = $reader->getSubfield($field, 'x')) {
$value->setSearchTerm($issn);
$value->setSearchType('Isn');
return;
}
$value->setSearchType('Title');
$value->setSearchTermQuote('"');
}
}
<?php
namespace SUBHH\VuFind\RecordDescription;
final class ResourceLocator
{
public static function getResourceDirectory () : string
{
return __DIR__ . '/../../resources/subhh-vufind-recorddescription';
}
}
<?php
namespace SUBHH\VuFind\RecordDescription;
use SUBHH\VuFind\SolrMarc\SolrMarc;
use VuFind\Marc\MarcReader;
use UnexpectedValueException;
class ShortDescriptionProvider implements DescriptionProviderInterface
{
/** @return array<string, DisplayValueInterface[]> */
public function createDescription (SolrMarc $record) : array
{
$reader = $record->getMarcReader();
$description = array();
$description['Title'] = new DisplayValue($record->getTitle());
$description['Creator'] = $this->getPersons($reader);
$description['Publication'] = $this->getPublicationStatement($reader);
return array_filter($description);
}
private function getPersons (MarcReader $reader) : ?DisplayValueInterface
{
if ($field = $this->getDataField($reader, '100')) {
if ($name = $reader->getSubfield($field, 'a')) {
$person = new SearchLink($name);
$person->setSearchTerm($name);
$person->setSearchType('Person');
$person->setSearchTermQuote('"');
return $person;
}
}
return null;
}
private function getPublicationStatement (MarcReader $reader) : ?DisplayValueInterface
{
if ($field = $this->getDataField($reader, '264')) {
list($place, $publisher, $date) = $this->getSubfields($field, 'a', 'b', 'c');
$statement = $place;
if ($place && ($publisher || $date)) {
$statement .= ': ';
}
if ($publisher) {
$statement .= $publisher;
}
if ($publisher && $date) {
$statement .= ' ';
}
if ($date) {
$statement .= $date;
}
if ($statement) {
return new DisplayValue($statement);
}
}
return null;
}
/** @return array<mixed> */
private function getDataField (MarcReader $reader, string $tag) : array
{
$field = $reader->getField($tag);
if ($field && !is_array($field)) {
throw new UnexpectedValueException();
}
return (array)$field;
}
/**
* @param array<mixed> $field
* @return string[] | null[]
*/
private function getSubfields (array $field, string ...$codes) : array
{
$values = array_fill(0, count($codes), null);
foreach ($field['subfields'] as $subfield) {
$key = array_search($subfield['code'], $codes, true);
if ($key !== false) {
$values[$key] = $subfield['data'];
unset($codes[$key]);
}
}
return $values;
}
}
<?php
namespace SUBHH\VuFind\RecordDescription;
use SUBHH\VuFind\SolrMarc\SolrMarc;
use Laminas\View\Helper\AbstractHelper;
final class ViewHelper extends AbstractHelper
{
/** @var DescriptionProviderInterface */
private $provider;
public function __construct (DescriptionProviderInterface $provider)
{
$this->provider = $provider;
}
public function renderDisplayValue (DisplayValueInterface $value) : ?string
{
$view = $this->getView();
if ($view) {
switch (get_class($value)) {
case SearchLink::class:
return $view->render('SearchLink', ['value' => $value]);
case ExternalLink::class:
return $view->render('ExternalLink', ['value' => $value]);
case DisplayValueSequence::class:
return $view->render('DisplayValueSequence', ['sequence' => $value]);
case DisplayValue::class:
return $view->render('DisplayValue', ['value' => $value]);
default:
return $view->render('DisplayValueInterface', ['value' => $value]);
}
}
return null;
}
/** @return array<string, DisplayValueInterface[]> */
public function getDescription (SolrMarc $record) : array
{
return $this->provider->createDescription($record);
}
public function __invoke () : self
{
return $this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment