From 391a6d32619978aa3c1e624743479a0d3fe45f89 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Thu, 21 May 2009 19:10:50 -0500 Subject: [PATCH] bug 2001, 2432: improve unAPI implementation Improves Koha's unAPI support as follows: * The implementation no longer depends on there being an accessible Zebra or SRW server. Instead, responses are created by running the bib MARCXML through local stylesheets. * unAPI now works for all bibs, not just ones that have an ISBN. The ID format is changed from koha:isbn:ISBN to koha:biblionumber:BIBNUM. * unAPI now correctly advertises the formats it supports. * This implementation now passes validation testing at http://validator.unapi.info/ . * MODS3 support now works correctly. As a consequence, this patch fixes bug 2432 (Zotero support when using the XSLT OPAC stylesheets). TODO: as additonal XSLT stylesheets are created to convert UNIMARC bibs to additional formats, the stylesheet map in opac/unapi should be updated. Signed-off-by: Galen Charlton --- .../intranet-tmpl/prog/en/xslt/identity.xsl | 11 + .../prog/en/includes/doc-head-close.inc | 2 +- .../prog/en/modules/opac-detail.tmpl | 12 +- .../prog/en/xslt/MARC21slim2OPACDetail.xsl | 5 +- .../prog/en/xslt/UNIMARCslim2OPACDetail.xsl | 7 +- opac/unapi | 240 ++++++++++++++---- 6 files changed, 209 insertions(+), 68 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/xslt/identity.xsl diff --git a/koha-tmpl/intranet-tmpl/prog/en/xslt/identity.xsl b/koha-tmpl/intranet-tmpl/prog/en/xslt/identity.xsl new file mode 100644 index 0000000000..e26ccb7a23 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/xslt/identity.xsl @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc b/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc index d6eb9b7989..7710751f53 100644 --- a/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc +++ b/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc @@ -74,4 +74,4 @@ }); //]]> - +/cgi-bin/koha/unapi" /> diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tmpl b/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tmpl index f02a1a7ce2..e25ebc6d60 100644 --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tmpl +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tmpl @@ -110,14 +110,14 @@ Physical details: - + "> + "> - - - - ISBN: "> - + + + + ISBN: ISSN: diff --git a/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACDetail.xsl b/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACDetail.xsl index b237c64e72..6b79942c57 100644 --- a/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACDetail.xsl +++ b/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACDetail.xsl @@ -213,12 +213,13 @@ + + ISBN: - - + .; diff --git a/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACDetail.xsl b/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACDetail.xsl index 988ebdc9b3..0e4837edb3 100644 --- a/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACDetail.xsl +++ b/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACDetail.xsl @@ -279,14 +279,13 @@ + + ISBN: - - - - + . diff --git a/opac/unapi b/opac/unapi index cd17881730..b49e7f651b 100755 --- a/opac/unapi +++ b/opac/unapi @@ -1,77 +1,207 @@ #!/usr/bin/perl -use CGI; + +# Copyright 2008-2009 LibLime +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA + use strict; use warnings; -use C4::Context; -use XML::Simple; -use LWP::Simple; -use LWP::UserAgent; -use HTTP::Request::Common; +=head1 NAME + +unapi - implement unAPI for the OPAC + +=head1 SYNOPSIS + +Retrieve http://library.example.org/cgi-bin/koha/unapi?id=koha:biblionumber:123&format=oai_dc + +=head1 DESCRIPTION + +Implements unAPI , a small HTTP API for retrieving structured +content from a web application. The primary application of unAPI in Koha is to +allow tools such as Zotero to identify and grab bibliographic record metadata in +an XML format such as OAI DC, RSS2, MARCXML, or MODS. + +=cut + +use CGI; +use C4::Context; +use C4::Biblio; +use XML::LibXML; +use XML::LibXSLT; -my $cgi = new CGI; +my $cgi = CGI->new(); binmode(STDOUT, "utf8"); #output as utf8 -my $baseurl = C4::Context->preference('OPACBaseURL'); -warn "Warning: OPACBaseURL not set in system preferences" unless $baseurl; -my $id = $cgi->param('id'); +=head1 VARIABLES + +=head2 $format_to_stylesheet_map + +This hashref of hashrefs maps from a MARC flavour and unAPI format +to the stylesheet that should be used to transform the bib MARCXML +to the desired output format. As new MARC XSLT stylesheets are added, +(particularly for UNIMARC), this map should be updated. Of course, +if/when we add support for emitting a format that is not genreated +by a stylesheet, the structure of this variable will have to be changed. +At present, this doubles as the list of output formats supported by +this unAPI implementation. + +=cut + +my $format_to_stylesheet_map = { + 'MARC21' => { + 'marcxml' => 'identity.xsl', + 'marcxml-full' => 'identity.xsl', + 'mods' => 'MARC21slim2MODS.xsl', + 'mods-full' => 'MARC21slim2MODS.xsl', + 'mods3' => 'MARC21slim2MODS3-1.xsl', + 'mods3-full' => 'MARC21slim2MODS3-1.xsl', + 'oai_dc' => 'MARC21slim2OAIDC.xsl', + 'rdfdc', => 'MARC21slim2RDFDC.xsl', + 'rss2' => 'MARC21slim2RSS2.xsl', + 'rss2-full' => 'MARC21slim2RSS2.xsl', + 'srw_dc' => 'MARC21slim2SRWDC.xsl', + }, + 'UNIMARC' => { + 'marcxml' => 'identity.xsl', + 'marcxml-full' => 'identity.xsl', + 'oai_dc' => 'UNIMARCslim2OAIDC.xsl', + }, +}; + +=head2 $format_info + +This hashref maps from unAPI output formats to the elements +used to describe them in an unAPI format request. + +=cut + +my $format_info = { + 'marcxml' => q(), + 'marcxml-full' => q(), + 'mods' => q(), + 'mods-full' => q(), + 'mods3' => q(), + 'mods3-full' => q(), + 'oai_dc' => q(), + 'rdfdc' => q(), + 'rss2' => q(), + 'rss2-full' => q(), + 'srw_dc' => q(), +}; + +my $id = $cgi->param('id'); my $format = $cgi->param('format'); -if ($id && $format) { - # koha:isbn:0152018484 - if ($id =~ /isbn/) { - $id =~ s/koha:isbn://; +if (not defined $format) { + emit_formats($id); +} elsif ($id) { - # two ways to do this, one via the SRU Zebra server (fast) - # FIXME - getting the SRU URL this way is purely guesswork - $baseurl =~ s/:\d+$//; # parse off OPAC port - my $url = "$baseurl:9998/biblios?version=1.1&operation=searchRetrieve&query=$id&startRecord=1&maximumRecords=20&recordSchema=$format"; - my $content= get($url); + # koha:biblionumber:0152018484 + if ($id =~ /koha:biblionumber:(\d+)/) { + my $biblionumber = $1; - # the other via XSL parsing (not as fast) - unless ($content) { - + my $content; eval { - my $conn = C4::Context->Zconn('biblioserver'); - $conn->option(preferredRecordSyntax => $format); - my $rs = $conn->search_pqf('@attr 1=7 '.$id); - my $n = $rs->size(); - $content = $rs->record(0)->raw(); + my $marcxml = GetXmlBiblio($biblionumber); + unless (defined $marcxml) { + # no bib, so 404 + print $cgi->header( -status => '404 record not found'); + exit 0; + } + + my $transformer = get_transformer($format); + unless (defined $transformer) { + print $cgi->header( -status => '406 invalid format requested' ); + exit 0; + } + my $parser = XML::LibXML->new(); + my $record_dom = $parser->parse_string( $marcxml ); + $record_dom = $transformer->transform( $record_dom ); + $content = $record_dom->toString(); }; if ($@) { - print "Error ", $@->code(), ": ", $@->message(), "\n"; + print $cgi->header( -status => '500 internal error ' . $@->code() . ": " . $@->message() ); + exit 0; } - } print $cgi->header( -type =>'application/xml' ); print $content; + } else { + # ID is obviously wrong, so 404 + print $cgi->header( -status => '404 record not found'); + exit 0; + } +} else { + # supplied a format but no id - caller is doing it wrong + print $cgi->header( -status => '400 bad request - if you specify format, must specify id'); + exit 0; +} + +exit 0; + +sub emit_formats { + my $id = shift; + + if (defined $id) { + print $cgi->header( -type =>'application/xml', -status => '300 multiple choices' ); + } else { + print $cgi->header( -type =>'application/xml' ); + } + + print "\n"; + if (defined $id) { + print qq(\n); + } else { + print "\n"; + } + + my $marcflavour = uc(C4::Context->preference('marcflavour')); + foreach my $format (sort keys %{ $format_to_stylesheet_map->{$marcflavour} }) { + print $format_info->{$format}, "\n"; } + print "\n"; + return; } -else { - -print $cgi->header( -type =>'application/xml' ); - -print " - - - - - - - - - - - - - - - - - - - - -"; + +sub get_transformer { + my $format = lc shift; + + my $marcflavour = uc(C4::Context->preference('marcflavour')); + return unless $format_to_stylesheet_map->{$marcflavour}->{$format}; + + my $xslt_file = C4::Context->config('intranetdir') . + "/koha-tmpl/intranet-tmpl/prog/en/xslt/" . + $format_to_stylesheet_map->{$marcflavour}->{$format}; + + my $parser = XML::LibXML->new(); + my $xslt = XML::LibXSLT->new(); + my $style_doc = $parser->parse_file( $xslt_file ); + my $stylesheet = $xslt->parse_stylesheet( $style_doc ); + + return $stylesheet; } + +=head1 AUTHOR + +Koha Development team + +Originally written by Joshua Ferraro + +Improved by Galen Charlton + +=cut -- 2.39.5