From bdc2102b8babe70e2702d64fd774ca02a3e8d597 Mon Sep 17 00:00:00 2001 From: Kyle M Hall Date: Fri, 23 Jan 2015 07:11:36 -0500 Subject: [PATCH] Bug 12357 - Enhancements to RIS and BibTeX exporting Some libraries would like to be able to add arbitrary fields to both the RIS and BibTeX citation formats that a record can be saved as from the staff intranet and public catalog. In addition, they would like to be able to override the default record type and use Koha's itemtype as the record type for those formats as well. Test Plan: 1) Apply this patch 2) Run updatedatabase.pl 3) Add the following to the new syspref RisExportAdditionalFields: TY: 942$c LC: 010$a NT: [501$a, 505$g] 4) Find or create a record with an 010$a (lccn) field, a 501$a field, a 942$c field, and multiple 505$g fields. 5) Locate the record in the catalog, choose "Save" and select RIS 6) Inspect the downloaded file, note the replaced TY field, the LC field, and multiple NT fields 7) Add the following to the new syspref BibtexExportAdditionalFields: '@': 942$c lccn: 010$a notes: [501$a, 505$g] 9) Using the previously selected record, choose "Save" and select BIBTEX 10) Inspect the downloaded file, note the lccn, the multiple note fields, and the new record type value Signed-off-by: Frederic Demians --- C4/Record.pm | 71 ++++++++++++--- C4/Ris.pm | 97 +++++++++++++++----- installer/data/mysql/sysprefs.sql | 2 + installer/data/mysql/updatedatabase.pl | 16 +++ .../en/modules/admin/preferences/cataloguing.pref | 23 +++++ 5 files changed, 173 insertions(+), 36 deletions(-) diff --git a/C4/Record.pm b/C4/Record.pm index 16d3f54..8f2ad9c 100644 --- a/C4/Record.pm +++ b/C4/Record.pm @@ -34,6 +34,7 @@ use C4::Koha; #marc2csv use C4::XSLT (); use YAML; #marcrecords2csv use Text::CSV::Encoded; #marc2csv +use Koha::SimpleMARC qw(read_field); use vars qw($VERSION @ISA @EXPORT); @@ -326,8 +327,8 @@ sub marc2endnote { } my $fields = { DB => C4::Context->preference("LibraryName"), - Title => $marc_rec_obj->title(), - Author => $marc_rec_obj->author(), + Title => $marc_rec_obj->title(), + Author => $marc_rec_obj->author(), Publisher => $f710a, City => $f260a, Year => $marc_rec_obj->publication_date, @@ -345,7 +346,7 @@ sub marc2endnote { $template.="AB - Abstract\n" if $abstract; my ($text, $errmsg) = $style->format($template, $fields); return ($text); - + } =head2 marc2csv - Convert several records from UNIMARC to CSV @@ -464,7 +465,7 @@ sub marcrecord2csv { foreach (@marcfieldsarray) { my @result = split('=', $_); if (scalar(@result) == 2) { - push @marcfields, { header => $result[0], field => $result[1] }; + push @marcfields, { header => $result[0], field => $result[1] }; } else { push @marcfields, { field => $result[0] } } @@ -480,7 +481,7 @@ sub marcrecord2csv { my $field = $_->{field}; # Remove any blank char that might have unintentionally insered into the tag name - $field =~ s/\s+//g; + $field =~ s/\s+//g; # If we have a user-supplied header, we use it if (exists $_->{header}) { @@ -504,7 +505,7 @@ sub marcrecord2csv { } } $csv->combine(@marcfieldsheaders); - $output = $csv->string() . "\n"; + $output = $csv->string() . "\n"; } # For each marcfield to export @@ -529,7 +530,7 @@ sub marcrecord2csv { push @tmpfields, (defined $authvalues->{$subfield}) ? $authvalues->{$subfield} : $subfield; } } - push (@fieldstab, join($subfieldseparator, @tmpfields)); + push (@fieldstab, join($subfieldseparator, @tmpfields)); # Or a field } else { my @fields = ($record->field($marcfield)); @@ -557,7 +558,7 @@ sub marcrecord2csv { push @valuesarray, $value; } - push (@fieldstab, join($fieldseparator, @valuesarray)); + push (@fieldstab, join($fieldseparator, @valuesarray)); } }; @@ -599,7 +600,7 @@ sub changeEncoding { my $error; unless($flavour) {$flavour = C4::Context->preference("marcflavour")}; unless($to_encoding) {$to_encoding = "UTF-8"}; - + # ISO-2709 Record (MARC21 or UNIMARC) if (lc($format) =~ /^marc$/o) { # if we're converting encoding of an ISO2709 file, we need to roundtrip through XML @@ -610,7 +611,7 @@ sub changeEncoding { unless ($error) { ($error,$newrecord) = marcxml2marc($marcxml,$to_encoding,$flavour); } - + # MARCXML Record } elsif (lc($format) =~ /^marcxml$/o) { # MARCXML Record my $marc; @@ -708,14 +709,60 @@ sub marc2bibtex { ); } - $tex .= "\@book{"; + my $BibtexExportAdditionalFields = C4::Context->preference('BibtexExportAdditionalFields'); + my $additional_fields; + if ($BibtexExportAdditionalFields) { + $BibtexExportAdditionalFields = "$BibtexExportAdditionalFields\n\n"; + $additional_fields = eval { YAML::Load($BibtexExportAdditionalFields); }; + if ($@) { + warn "Unable to parse BibtexExportAdditionalFields : $@"; + $additional_fields = undef; + } + } + + if ( $additional_fields && $additional_fields->{'@'} ) { + my ( $f, $sf ) = split( /\$/, $additional_fields->{'@'} ); + my ( $type ) = read_field( { record => $record, field => $f, subfield => $sf, field_numbers => [1] } ); + + if ($type) { + $tex .= '@' . $type . '{'; + } + else { + $tex .= "\@book{"; + } + } + else { + $tex .= "\@book{"; + } + my @elt; for ( my $i = 0 ; $i < scalar( @bh ) ; $i = $i + 2 ) { next unless $bh[$i+1]; push @elt, qq|\t$bh[$i] = {$bh[$i+1]}|; } $tex .= join(",\n", $id, @elt); - $tex .= "\n}\n"; + $tex .= "\n"; + + if ($additional_fields) { + foreach my $bibtex_tag ( keys %$additional_fields ) { + next if $bibtex_tag eq '@'; + + my @fields = + ref( $additional_fields->{$bibtex_tag} ) eq 'ARRAY' + ? @{ $additional_fields->{$bibtex_tag} } + : $additional_fields->{$bibtex_tag}; + + for my $tag (@fields) { + my ( $f, $sf ) = split( /\$/, $tag ); + my @values = read_field( { record => $record, field => $f, subfield => $sf } ); + foreach my $v (@values) { + $tex .= qq(\t$bibtex_tag = {$v}\n); + } + } + } + } + + $tex .= "}\n"; return $tex; } diff --git a/C4/Ris.pm b/C4/Ris.pm index 137b99d..f212ca2 100644 --- a/C4/Ris.pm +++ b/C4/Ris.pm @@ -59,12 +59,14 @@ package C4::Ris; # # -#use strict; -#use warnings; +use Modern::Perl; use List::MoreUtils qw/uniq/; use vars qw($VERSION @ISA @EXPORT); +use C4::Biblio qw(GetMarcSubfieldStructureFromKohaField); +use Koha::SimpleMARC qw(read_field); + # set the version for version checking $VERSION = 3.07.00.049; @@ -76,6 +78,11 @@ $VERSION = 3.07.00.049; &marc2ris ); +our $utf; +our $intype; +our $marcprint; +our $protoyear; + =head1 marc2bibtex - Convert from UNIMARC to RIS @@ -101,25 +108,47 @@ sub marc2ris { close STDOUT; open STDOUT,'>', \$outvar; + ## First we should check the character encoding. This may be + ## MARC-8 or UTF-8. The former is indicated by a blank, the latter + ## by 'a' at position 09 (zero-based) of the leader + my $leader = $record->leader(); + if ( $intype eq "marc21" ) { + if ( $leader =~ /^.{9}a/ ) { + print "---\r\nUTF-8 data\r\n" if $marcprint; + $utf = 1; + } + else { + print "---\r\nMARC-8 data\r\n" if $marcprint; + } + } + ## else: other MARC formats do not specify the character encoding + ## we assume it's *not* UTF-8 + + my $RisExportAdditionalFields = C4::Context->preference('RisExportAdditionalFields'); + my $ris_additional_fields; + if ($RisExportAdditionalFields) { + $RisExportAdditionalFields = "$RisExportAdditionalFields\n\n"; + $ris_additional_fields = eval { YAML::Load($RisExportAdditionalFields); }; + if ($@) { + warn "Unable to parse RisExportAdditionalFields : $@"; + $ris_additional_fields = undef; + } + } - ## First we should check the character encoding. This may be - ## MARC-8 or UTF-8. The former is indicated by a blank, the latter - ## by 'a' at position 09 (zero-based) of the leader - my $leader = $record->leader(); - if ($intype eq "marc21") { - if ($leader =~ /^.{9}a/) { - print "---\r\nUTF-8 data\r\n" if $marcprint; - $utf = 1; - } - else { - print "---\r\nMARC-8 data\r\n" if $marcprint; - } - } - ## else: other MARC formats do not specify the character encoding - ## we assume it's *not* UTF-8 - - ## start RIS dataset - &print_typetag($leader); + ## start RIS dataset + if ( $ris_additional_fields && $ris_additional_fields->{TY} ) { + my ( $f, $sf ) = split( /\$/, $ris_additional_fields->{TY} ); + my ( $type ) = read_field( { record => $record, field => $f, subfield => $sf, field_numbers => [1] } ); + if ($type) { + print "TY - $type\r\n"; + } + else { + &print_typetag($leader); + } + } + else { + &print_typetag($leader); + } ## retrieve all author fields and collect them in a list my @author_fields; @@ -285,6 +314,25 @@ sub marc2ris { print_uri($record->field('856')); } + if ($ris_additional_fields) { + foreach my $ris_tag ( keys %$ris_additional_fields ) { + next if $ris_tag eq 'TY'; + + my @fields = + ref( $ris_additional_fields->{$ris_tag} ) eq 'ARRAY' + ? @{ $ris_additional_fields->{$ris_tag} } + : $ris_additional_fields->{$ris_tag}; + + for my $tag (@fields) { + my ( $f, $sf ) = split( /\$/, $tag ); + my @values = read_field( { record => $record, field => $f, subfield => $sf } ); + foreach my $v (@values) { + print "$ris_tag - $v\r\n"; + } + } + } + } + ## end RIS dataset print "ER - \r\n"; @@ -497,6 +545,7 @@ sub print_title { my $clean_title = $titlefield->subfield('a'); my $clean_subtitle = $titlefield->subfield('b'); +$clean_subtitle ||= q{}; $clean_title =~ s% *[/:;.]$%%; $clean_subtitle =~ s%^ *(.*) *[/:;.]$%$1%; @@ -777,7 +826,7 @@ sub get_keywords { } else { ## retrieve all available subfields - @kwsubfields = $kwfield->subfields(); + my @kwsubfields = $kwfield->subfields(); ## loop over all available subfield tuples foreach my $kwtuple (@kwsubfields) { @@ -891,9 +940,9 @@ sub pool_subx { ## loop over all notefields foreach my $notefield (@notefields) { - if ($notefield != undef) { + if (defined $notefield) { ## retrieve all available subfield tuples - @notesubfields = $notefield->subfields(); + my @notesubfields = $notefield->subfields(); ## loop over all subfield tuples foreach my $notetuple (@notesubfields) { @@ -967,7 +1016,7 @@ sub charconv { ## return unaltered if already utf-8 return @_; } - elsif ($uniout eq "t") { + elsif (my $uniout eq "t") { ## convert to utf-8 return marc8_to_utf8("@_"); } diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 9020b4b..c8eaf4b 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -66,6 +66,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('BasketConfirmations','1','always ask for confirmation.|do not ask for confirmation.','When closing or reopening a basket,','Choice'), ('BiblioAddsAuthorities','0',NULL,'If ON, adding a new biblio will check for an existing authority record and create one on the fly if one doesn\'t exist','YesNo'), ('BiblioDefaultView','normal','normal|marc|isbd','Choose the default detail view in the catalog; choose between normal, marc or isbd','Choice'), +('BibtexExportAdditionalFields', '', NULL , 'Define additional BibTex tags to export from MARC records in YAML format as an associative array with either a marc tag/subfield combination as the value, or a list of tag/subfield combinations.', 'textarea'), ('BlockExpiredPatronOpacActions','1',NULL,'Set whether an expired patron can perform opac actions such as placing holds or renew books, can be overridden on a per patron-type basis','YesNo'), ('BlockReturnOfWithdrawnItems','1','0','If enabled, items that are marked as withdrawn cannot be returned.','YesNo'), ('BorrowerMandatoryField','surname|cardnumber',NULL,'Choose the mandatory fields for a patron\'s account','free'), @@ -361,6 +362,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('ReturnLog','1',NULL,'If ON, enables the circulation (returns) log','YesNo'), ('ReturnToShelvingCart','0','','If set, when any item is \'checked in\', it\'s location code will be changed to CART.','YesNo'), ('reviewson','1','','If ON, enables patron reviews of bibliographic records in the OPAC','YesNo'), +('RisExportAdditionalFields', '', NULL , 'Define additional RIS tags to export from MARC records in YAML format as an associative array with either a marc tag/subfield combination as the value, or a list of tag/subfield combinations.', 'textarea'), ('RoutingListAddReserves','1','','If ON the patrons on routing lists are automatically added to holds on the issue.','YesNo'), ('RoutingListNote','To change this note edit RoutingListNote system preference.','70|10','Define a note to be shown on all routing lists','Textarea'), ('RoutingSerials','1',NULL,'If ON, serials routing is enabled','YesNo'), diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index dab1c62..c163383 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -9759,6 +9759,22 @@ if ( CheckVersion($DBversion) ) { SetVersion ($DBversion); } +$DBversion = "3.17.00.XXX"; +if ( CheckVersion($DBversion) ) { + $dbh->do(q{ + INSERT INTO systempreferences (variable,value,options,explanation,type) + VALUES ('RisExportAdditionalFields', '', NULL , 'Define additional RIS tags to export from MARC records in YAML format as an associative array with either a marc tag/subfield combination as the value, or a list of tag/subfield combinations.', 'textarea') + }); + + $dbh->do(q{ + INSERT INTO systempreferences (variable,value,options,explanation,type) + VALUES ('BibtexExportAdditionalFields', '', NULL , 'Define additional BibTex tags to export from MARC records in YAML format as an associative array with either a marc tag/subfield combination as the value, or a list of tag/subfield combinations.', 'textarea') + }); + + print "Upgrade to $DBversion done (Bug XXX - Add ability to export arbitrary MARC fields for RIS and BibTex)\n"; + SetVersion($DBversion); +} + =head1 FUNCTIONS =head2 TableExists($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref index 157bfc8..8e7278b 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref @@ -215,3 +215,26 @@ Cataloging: yes: "do" no: "don't" - attempt to match aggressively by trying all variations of the ISBNs in the imported record as a phrase in the ISBN fields of already cataloged records. Note that this preference has no effect if UseQueryParser is on. + Exporting: + - + - Include following fields when exporting BibTeX, + - pref: BibtexExportAdditionalFields + type: textarea + - "Use one line per tag in the format BT_TAG: TAG$SUBFIELD ( e.g. lccn: 010$a )" + - "
" + - "To specificy multiple marc tags/subfields as targets for a repeating BibTex tag, use the following format: BT_TAG: [TAG2$SUBFIELD1, TAG2$SUBFIELD2] ( e.g. notes: [501$a, 505$g] )" + - "
" + - "All values of repeating tags and subfields will be printed with the given BibTeX tag." + - "
" + - "Use '@' ( with quotes ) as the BT_TAG to replace the bibtex record type with a field value of your choosing." + - + - Include following fields when exporting RIS, + - pref: RisExportAdditionalFields + type: textarea + - "Use one line per tag in the format RIS_TAG: TAG$SUBFIELD ( e.g. LC: 010$a )" + - "
" + - "To specificy multiple marc tags/subfields as targets for a repeating RIS tag, use the following format: RIS_TAG: [TAG2$SUBFIELD1, TAG2$SUBFIELD2] ( e.g. NT: [501$a, 505$g] )" + - "
" + - "All values of repeating tags and subfields will be printed with the given RIS tag." + - "
" + - "Use of TY ( record type ) as a key will replace the default TY with the field value of your choosing." -- 1.7.2.5