David Maus

Unser kleines Geheimnis II: Verbesserte Zotero-Anbindung dank unAPI

Ende letzten Jahres erreichte uns die Anfrage aus der Fachbibliothek Wirtschaftswissenschaften, ob wir bei Gelegenheit einen Blick auf die Anbindung der Literaturverwaltung Zotero werfen können. Die an Zotero übertragenen Metadaten seien in vielen Fällen unvollständiger als in unserem Altsystem, dem Campus Katalog.

Was soll ich sagen? Es stimmt. Nach dem weniger erfolgreichen Versuch, die als COinS eingebundenen Metadaten zu verbessern und einer nochmaligen Lektüre der Zotero-Dokumentationen viel mir das Stichwort unAPI ins Auge.

Zotero kann die Metadaten bibliographischer Einheiten aus MARC21-Datensätze entnehmen, die über UnAPI bereitgestellt werden. Da das Internformat des zentralen Suchindex MARC21 XML und UnAPI eine recht überschaubare Spezifikation ist, habe ich in nur wenigen Handgriffen eine UnAPI-Schnittstelle implementiert und Trefferliste wie Detailansicht eingebaut.

<?phpdeclare(strict_types=1);namespace SUBHH\VuFind\KatalogPlus;use Laminas\Http\Request;use Laminas\Http\Response;use Laminas\Mvc\Controller\AbstractController;use Laminas\Mvc\MvcEvent;use VuFind\Record\Loader as RecordLoader;use VuFind\RecordDriver\AbstractBase as Record;use DOMDocument;use UnexpectedValueException;/** * Controller implementing the unAPI API. * * TODO: Factor out to separate package. */final class UnApiController extends AbstractController{    /** @var array<string, RecordSerializerInterface> */    private array $serializers;    private RecordLoader $loader;    /** @param array<string, RecordSerializerInterface> $serializers */    public function __construct (RecordLoader $loader, array $serializers)    {        $this->loader = $loader;        $this->serializers = $serializers;    }    public function onDispatch (MvcEvent $event) : mixed    {        $request = $event->getRequest();        if (!$request instanceof Request) {            throw new UnexpectedValueException();        }        $id = $request->getQuery('id');        $format = $request->getQuery('format');        if (!$id && $format) {            return $this->createHttpResponse(400);        }        if ($id && $format) {            return $this->createRecordResponse(strval($id), strval($format));        }        return $this->createFormatsResponse(strval($id));    }    private function createRecordResponse (string $id, string $format) : Response    {        $record = $this->findRecord($id);        if (!$record) {            return $this->createHttpResponse(404);        }        if (!array_key_exists($format, $this->serializers)) {            return $this->createHttpResponse(406);        }        $serializer = $this->serializers[$format];        if (!$serializer->accept($record)) {            return $this->createHttpResponse(406);        }        return $this->createHttpResponse(200, $serializer->getContentType(), $serializer->serialize($record));    }    private function createFormatsResponse (?string $id) : Response    {        if ($id) {            $record = $this->findRecord($id);            if (!$record) {                return $this->createHttpResponse(404);            }        } else {            $record = null;        }        $document = new DOMDocument();        $document->append($document->createElement('formats'));        if ($id) {            $document->documentElement->setAttribute('id', $id);        }        foreach ($this->serializers as $name => $serializer) {            if (!$record || $serializer->accept($record)) {                if ($format = $document->createElement('format')) {                    $format->setAttribute('name', $name);                    $format->setAttribute('type', $serializer->getContentType());                    $document->documentElement->append($format);                }            }        }        return $this->createHttpResponse($id ? 300 : 200, 'application/xml', $document->saveXML());    }    private function findRecord (string $id) : ?Record    {        if (strpos($id, ':') !== false) {            list($sourceId, $recordId) = explode(':', $id);            $record = $this->loader->load($recordId, $sourceId);            if ($record instanceof Record) {                return $record;            }        }        return null;    }    private function createHttpResponse (int $statusCode, ?string $contentType = null, ?string $content = null) : Response    {        $response = $this->getResponse();        if (!$response instanceof Response) {            throw new UnexpectedValueException();        }        $response->setStatusCode($statusCode);        if ($content) {            $response->setContent($content);            if ($contentType) {                $response->getHeaders()->addHeaderLine('Content-Type', $contentType);            }        }        return $response;    }}