From a87744e7ccef614787f2799f64149cbc7582c94f Mon Sep 17 00:00:00 2001 From: dbs Date: Sun, 26 Apr 2009 01:31:26 +0000 Subject: [PATCH] Basic support for returning MFHD for display purposes Interim measure, while there is a direct linkage between sre and bre, is to invoke the bib_to_mfhd_hash method at record display time; probably should return a structure including a count of parsed MFHD records to shortcut cycling through all the arrays, but baby steps git-svn-id: svn://svn.open-ils.org/ILS/trunk@12987 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 16 ++ .../src/perlmods/OpenILS/Application/Search.pm | 1 + .../perlmods/OpenILS/Application/Search/Serial.pm | 109 +++++++ Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm | 316 +++++++++++---------- 4 files changed, 298 insertions(+), 144 deletions(-) create mode 100644 Open-ILS/src/perlmods/OpenILS/Application/Search/Serial.pm diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 36a57a076e..4a5d87876a 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2574,6 +2574,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Search.pm b/Open-ILS/src/perlmods/OpenILS/Application/Search.pm index fcce7e4816..c70deb2ec2 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Search.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Search.pm @@ -15,6 +15,7 @@ use OpenILS::Application::Search::Authority; use OpenILS::Application::Search::Z3950; use OpenILS::Application::Search::Zips; use OpenILS::Application::Search::CNBrowse; +use OpenILS::Application::Search::Serial; use OpenILS::Application::AppUtils; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Search/Serial.pm b/Open-ILS/src/perlmods/OpenILS/Application/Search/Serial.pm new file mode 100644 index 0000000000..008f7ff712 --- /dev/null +++ b/Open-ILS/src/perlmods/OpenILS/Application/Search/Serial.pm @@ -0,0 +1,109 @@ +package OpenILS::Application::Search::Serial; +use base qw/OpenILS::Application/; +use strict; use warnings; + + +use OpenSRF::Utils::JSON; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Utils::MFHDParser; +use OpenSRF::Utils::SettingsClient; +use OpenILS::Utils::CStoreEditor q/:funcs/; +use OpenSRF::Utils::Cache; +use Encode; + +use OpenSRF::Utils::Logger qw/:logger/; + +use Data::Dumper; + +use OpenSRF::Utils::JSON; + +use Time::HiRes qw(time); +use OpenSRF::EX qw(:try); +use Digest::MD5 qw(md5_hex); + +use XML::LibXML; +use XML::LibXSLT; + +use OpenILS::Const qw/:const/; + +use OpenILS::Application::AppUtils; +my $apputils = "OpenILS::Application::AppUtils"; +my $U = $apputils; + +my $pfx = "open-ils.search_"; + +=over + +=item * mfhd_to_hash + +=back + +Takes an MFHD record ID and returns a hash of holdings statements + +=cut + +sub mfhd_to_hash { + my ($self, $client, $id) = @_; + + my $session = OpenSRF::AppSession->create("open-ils.cstore"); + my $request = $session->request( + "open-ils.cstore.direct.serial.record_entry.retrieve", $id )->gather(1); + + my $u = OpenILS::Utils::MFHDParser->new(); + my $mfhd_hash = $u->generate_svr( $request->marc ); + + $session->disconnect(); + return $mfhd_hash; +} + +__PACKAGE__->register_method( + method => "mfhd_to_hash", + api_name => "open-ils.search.serial.record.mfhd.retrieve", + argc => 1, + note => "Given a serial record ID, return MFHD holdings" +); + +=over + +=item * bib_to_mfhd_hash + +=back + +Given a bib record ID, returns a hash of holdings statements + +=cut + +sub bib_to_mfhd_hash { + my ($self, $client, $bib) = @_; + + my $mfhd_hash; + + # miker suggested this way, but I'm too stupid to get it to work +# my $e = OpenILS::Utils::CStoreEditor->new(); +# my $mfhd = $e->search_serial_record_entry({bib=>$bib}); + + my @mfhd = $U->cstorereq( "open-ils.cstore.json_query.atomic", { + select => { sre => 'marc' }, + from => 'sre', + where => { record => $bib }, + distinct => 1 + }); + + if (!@mfhd or scalar(@mfhd) == 0) { + return undef; + } + + my $u = OpenILS::Utils::MFHDParser->new(); + $mfhd_hash = $u->generate_svr( $mfhd[0][0]->{marc} ); + + return $mfhd_hash; +} + +__PACKAGE__->register_method( + method => "bib_to_mfhd_hash", + api_name => "open-ils.search.serial.record.bib_to_mfhd.retrieve", + argc => 1, + note => "Given a bibliographic record ID, return MFHD holdings" +); + +1; diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm b/Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm index 5995de911d..36e8bcfeb3 100644 --- a/Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm +++ b/Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm @@ -1,168 +1,196 @@ -#!/usr/bin/perl -w -use strict; -use Date::Manip; - -# Parse MFHD patterns for http://www.loc.gov/marc/holdings/hd853855.html - -# Primary goal: -# Expected input: a chunk of MFHD, a start date, and # of issuances to project -# Expected output: a set of issuances projected forward from the start date, -# with issue/volume/dates/captions conforming to what the MFHD actually says - -# The thought had been to use Date::Manip to generate the actual dates for -# each issuance, like: -# -# # To find the 2nd Tuesday of every month -# @date = ParseRecur("0:1*2:2:0:0:0",$base,$start,$stop); - -# Secondary goal: generate automatic summary holdings -# (a la http://www.loc.gov/marc/holdings/hd863865.html) - -# Compressability comes from first indicator -sub parse_compressability { - my $c = shift || return undef; - - my %compressability = ( - '0' => 'Cannot compress or expand', - '1' => 'Can compress but cannot expand', - '2' => 'Can compress or expand', - '3' => 'Unknown', - '#' => 'Undefined' - ); - - if (exists $compressability{$c}) { - return $compressability{$c}; +package OpenILS::Utils::MFHDParser; +use strict; use warnings; + +use OpenSRF::EX qw/:try/; +use Time::HiRes qw(time); +use OpenILS::Utils::Fieldmapper; +use OpenSRF::Utils::SettingsClient; +use OpenSRF::Utils::Logger qw/$logger/; + +use OpenILS::Utils::MFHD; +use MARC::File::XML; +use Data::Dumper; + +sub new { return bless( {}, shift() ); } + +=head1 Subroutines + +=over + +=item * format_textual_holdings($field) + +=back + +Returns concatenated subfields $a with $z for textual holdings (866-868) + +=cut + +sub format_textual_holdings { + my ($self, $field) = @_; + my $holdings; + my $public_note; + + $holdings = $field->subfield('a'); + if (!$holdings) { + return undef; + } + + $public_note = $field->subfield('z'); + if ($public_note) { + return "$holdings - $public_note"; } - # 'Unknown compressability indicator - expected one of (0,1,2,3,#)'; - return undef; + return $holdings; } -# Caption evaluation comes from second indicator -sub caption_evaluation { - my $ce = shift || return undef; +=over - my %caption_evaluation = ( - '0' => 'Captions verified; all levels present', - '1' => 'Captions verified; all levels may not be present', - '2' => 'Captions unverified; all levels present', - '3' => 'Captions unverified; all levels may not be present', - '#' => 'Undefined', - ); +=item * mfhd_to_hash($mfhd_xml) - if (exists $caption_evaluation{$ce}) { - return $caption_evaluation{$ce}; +=back + +Returns a Perl hash containing fields of interest from the MFHD record + +=cut +sub mfhd_to_hash { + my ($self, $mfhd_xml) = @_; + + my $holdings = []; + my $supplements = []; + my $indexes = []; + my $current_holdings = []; + my $current_supplements = []; + my $current_indexes = []; + my $online = []; # Laurentian extension to MFHD standard + my $missing = []; # Laurentian extension to MFHD standard + my $incomplete = []; # Laurentian extension to MFHD standard + + my $marc = MARC::Record->new_from_xml($mfhd_xml); + my $mfhd = OpenILS::Utils::MFHD->new($marc); + + foreach my $field ($marc->field('866')) { + my $textual_holdings = $self->format_textual_holdings($field); + if ($textual_holdings) { + push @$holdings, $textual_holdings; + } + } + foreach my $field ($marc->field('867')) { + my $textual_holdings = $self->format_textual_holdings($field); + if ($textual_holdings) { + push @$supplements, $textual_holdings; + } + } + foreach my $field ($marc->field('868')) { + my $textual_holdings = $self->format_textual_holdings($field); + if ($textual_holdings) { + push @$indexes, $textual_holdings; + } } - # 'Unknown caption evaluation indicator - expected one of (0,1,2,3,#)'; - return undef; -} -# Start with frequency ($w) -# then overlay number of pieces of issuance ($p) -# then regularity pattern ($y) -my %frequency = ( - 'a' => 'annual', - 'b' => 'bimonthly', - 'c' => 'semiweekly', - 'd' => 'daily', - 'e' => 'biweekly', - 'f' => 'semiannual', - 'g' => 'biennial', - 'h' => 'triennial', - 'i' => 'three times a week', - 'j' => 'three times a month', - 'k' => 'continuously updated', - 'm' => 'monthly', - 'q' => 'quarterly', - 's' => 'semimonthly', - 't' => 'three times a year', - 'w' => 'weekly', - 'x' => 'completely irregular', -); - -sub parse_frequency { - my $freq = shift || return undef; - - if ($freq =~ m/^\d+$/) { - return "$freq times a year"; - } elsif (exists $frequency{$freq}) { - return $frequency{$freq}; + foreach my $cap_id ($mfhd->captions('853')) { + my @curr_holdings = $mfhd->holdings('863', $cap_id); + next unless scalar @curr_holdings; + foreach (@curr_holdings) { + push @$current_holdings, $_->format(); + } } - # unrecognized frequency specification - return undef; -} -# $x - Point at which the highest level increments or changes -# Interpretation of two-digit numbers in the 01-12 range depends on the publishing frequency -# More than one change can be passed in the subfield and are delimited by commas -sub chronology_change { - my $chronology_change = shift || return undef; - my @c_changes = split /,/, $chronology_change; - foreach my $change (@c_changes) { - if ($change == 21) { - + foreach my $cap_id ($mfhd->captions('854')) { + my @curr_supplements = $mfhd->holdings('864', $cap_id); + next unless scalar @curr_supplements; + foreach (@curr_supplements) { + push @$current_supplements, $_->format(); } } - return undef; -} -# Publication code : first character in regularity pattern ($y) -sub parse_publication_code { - my $c = shift || return undef; + foreach my $cap_id ($mfhd->captions('855')) { + my @curr_indexes = $mfhd->holdings('865', $cap_id); + next unless scalar @curr_indexes; + foreach (@curr_indexes) { + push @$current_indexes, $_->format(); + } + } - my %publication_code = ( - 'c' => 'combined', - 'o' => 'omitted', - 'p' => 'published', - '#' => 'undefined', - ); + # Laurentian extensions + foreach my $field ($marc->field('530')) { + my $online_stmt = $self->format_textual_holdings($field); + if ($online_stmt) { + push @$online, $online_stmt; + } + } - if (exists $publication_code{$c}) { - return $publication_code{$c}; + foreach my $field ($marc->field('590')) { + my $missing_stmt = $self->format_textual_holdings($field); + if ($missing_stmt) { + push @$missing, $missing_stmt; + } } - return undef; -} -# Chronology code : part of regularity pattern ($y) -sub parse_chronology_code { - my $c = shift || return undef; - - my %chronology_code = ( - 'd' => 'Day', - 'm' => 'Month', - 's' => 'Season', - 'w' => 'Week', - 'y' => 'Year', - 'e' => 'Enumeration', - ); - - if (exists $chronology_code{$c}) { - return $chronology_code{$c}; + foreach my $field ($marc->field('591')) { + my $incomplete_stmt = $self->format_textual_holdings($field); + if ($incomplete_stmt) { + push @$incomplete, $incomplete_stmt; + } } - return undef; -} -sub parse_regularity_pattern { - my $pattern = shift; - my ($pc, $cc, $cd) = $pattern =~ m{^(\w)(\w)(.+)$}; - - my $pub_code = parse_publication_code($pc); - my $chron_code = parse_chronology_code($cc); - my $chron_def = parse_chronology_definition($cd); - - return ($pub_code, $chron_code, $chron_def); + return { holdings => $holdings, current_holdings => $current_holdings, + supplements => $supplements, current_supplements => $current_supplements, + indexes => $indexes, current_indexes => $current_indexes, + missing => $missing, incomplete => $incomplete, }; } -sub parse_chronology_definition { - my $chron_def = shift || return undef; - # Well, this is where it starts to get hard, doesn't it? - return $chron_def; +=over + +=item * init_holdings_virtual_record() + +=back + +Initialize the serial virtual record (svr) instance + +=cut +sub init_holdings_virtual_record { + my $record = Fieldmapper::serial::virtual_record->new; + $record->holdings([]); + $record->current_holdings([]); + $record->supplements([]); + $record->current_supplements([]); + $record->indexes([]); + $record->current_indexes([]); + $record->online([]); + $record->missing([]); + $record->incomplete([]); + return $record; } -print parse_regularity_pattern("cddd"); -print "\n"; -print parse_regularity_pattern("38dd"); -print "\n"; +=over -1; +=item * init_holdings_virtual_record($mfhd) + +=back + +Given an MFHD record, return a populated svr instance + +=cut +sub generate_svr { + my ($self, $mfhd) = @_; + + if (!$mfhd) { + return undef; + } + + my $record = init_holdings_virtual_record(); + my $holdings = $self->mfhd_to_hash($mfhd); + + $record->holdings($holdings->{holdings}); + $record->current_holdings($holdings->{current_holdings}); + $record->supplements($holdings->{supplements}); + $record->current_supplements($holdings->{current_supplements}); + $record->indexes($holdings->{indexes}); + $record->current_indexes($holdings->{current_indexes}); + $record->online($holdings->{online}); + $record->missing($holdings->{missing}); + $record->incomplete($holdings->{incomplete}); -# :vim:noet:ts=4:sw=4: + return $record; +} + +1; -- 2.11.0