Overhaul the marc2sre.pl script for importing MFHD records
authordbs <dbs@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 6 Apr 2011 03:28:39 +0000 (03:28 +0000)
committerdbs <dbs@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 6 Apr 2011 03:28:39 +0000 (03:28 +0000)
Add --bibfield / --bibsubfield options to support retrieving the
bib record ID by searching the specified field/subfield for the
identifier, instead of assuming that the identifier =
biblio.record_entry.id. Avoiding the lookup will be much faster, but
some sites want to start clean with their bibliographic record IDs,
so this supports them.

Remove the need for passing in the owning user's password and simply do
a lookup by username in the actor.usr table. This is a bit more secure
as the password isn't exposed on the command line and you can easily set
someone else as the owner of the records without having to know their
password.

Add significant internal help via Pod::Usage.

git-svn-id: svn://svn.open-ils.org/ILS/trunk@19989 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/extras/import/marc2sre.pl

index cad3af2..44d6786 100755 (executable)
@@ -2,19 +2,13 @@
 use strict;
 use warnings;
 
-use lib '/openils/lib/perl5/';
-
 use OpenSRF::System;
-use OpenSRF::Application;
 use OpenSRF::EX qw/:try/;
-use OpenSRF::AppSession;
-use OpenSRF::MultiSession;
 use OpenSRF::Utils::SettingsClient;
 use OpenILS::Application::AppUtils;
+use OpenILS::Event;
 use OpenILS::Utils::Fieldmapper;
-use Digest::MD5 qw/md5_hex/;
 use OpenSRF::Utils::JSON;
-use Data::Dumper;
 use Unicode::Normalize;
 
 use Time::HiRes qw/time/;
@@ -22,27 +16,40 @@ use Getopt::Long;
 use MARC::Batch;
 use MARC::File::XML ( BinaryEncoding => 'utf-8' );
 use MARC::Charset;
+use Pod::Usage;
 
 MARC::Charset->ignore_errors(1);
 
-my ($idfield, $count, $user, $password, $config, $marctype, $idsubfield, @files, @trash_fields, $quiet, $libmap) =
-       ('001', 1, 'admin', 'open-ils', '/openils/conf/opensrf_core.xml', 'USMARC');
+# Command line options, with applicable defaults
+my ($idsubfield, $bibfield, $bibsubfield, @files, $libmap, $quiet, $help);
+my $idfield = '004';
+my $count = 1;
+my $user = 'admin';
+my $config = '/openils/conf/opensrf_core.xml';
+my $marctype = 'USMARC';
 
-GetOptions(
+my $parse_options = GetOptions(
        'idfield=s'     => \$idfield,
        'idsubfield=s'  => \$idsubfield,
+    'bibfield=s'=> \$bibfield,
+    'bibsubfield=s'=> \$bibsubfield,
        'startid=i'     => \$count,
        'user=s'        => \$user,
-       'password=s'    => \$password,
        'config=s'      => \$config,
        'marctype=s'    => \$marctype,
        'file=s'        => \@files,
        'libmap=s'      => \$libmap,
        'quiet'         => \$quiet,
+       'help'          => \$help,
 );
 
+if (!$parse_options or $help) {
+    pod2usage(0);
+}
+
 @files = @ARGV if (!@files);
 
+my $U = 'OpenILS::Application::AppUtils';
 my @ses;
 my @req;
 my %processing_cache;
@@ -54,7 +61,13 @@ if ($libmap) {
 OpenSRF::System->bootstrap_client( config_file => $config );
 Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
 
-$user = OpenILS::Application::AppUtils->check_user_session( login($user,$password) )->id;
+my ($result, $evt) = get_user_id($user);
+if ($evt || !$result->id) {
+    print("Could not retrieve user with user name '$user'\n");
+    exit(0);
+}
+
+$user = $result->id;
 
 select STDERR; $| = 1;
 select STDOUT; $| = 1;
@@ -84,6 +97,16 @@ while ( try { $rec = $batch->next } otherwise { $rec = -1 } ) {
                $record =~ s/^.*?(\d+).*?$/$1/o;
        }
 
+    # If we have been given bibfield / bibsubfield values, use those to find
+    # a matching bib record for $record and use _that_ as our record instead
+    if ($bibfield) {
+        my ($result, $evt) = map_id_to_bib($record);
+        if ($evt || !$result->record) {
+            print("Could not find matching bibliographic record for $record\n");
+        }
+        $record = $result->record;
+    }
+
        (my $xml = $rec->as_xml_record()) =~ s/\n//sog;
        $xml =~ s/^<\?xml.+\?\s*>//go;
        $xml =~ s/>\s+</></go;
@@ -119,43 +142,137 @@ while ( try { $rec = $batch->next } otherwise { $rec = -1 } ) {
        }
 }
 
-sub login {        
-       my( $username, $password, $type ) = @_;
+# Generate a hash of library names (as found in the 852b in the MFHD record) to
+# integers representing actor.org_unit ID values
+sub map_libraries_to_ID {
+       my $map_filename = shift;
+
+       my %lib_id_map;
+
+       open(MAP_FH, '<', $map_filename) or die "Could not load [$map_filename] $!";
+       while (<MAP_FH>) {
+               my ($lib, $id) = $_ =~ /^(.*?)\t(.*?)$/;
+               $lib_id_map{$lib} = $id;
+       }
+
+       return \%lib_id_map;
+}
+
+# Look up the actor.org_unit.id value for this library name
+sub get_library_id {
+       my $record = shift;
+
+       my $lib_name = $record->field('852')->subfield('b');
+       my $lib_id = $lib_id_map->{$lib_name};
+
+       return $lib_id;
+}
+
+# Get the actor.usr.id value for the given username
+sub get_user_id {
+    my $username = shift;
+
+    my ($result, $evt);
+
+    $result = $U->cstorereq(
+        'open-ils.cstore.direct.actor.user.search',
+        { usrname => $username, deleted => 'f' }
+    );
+    $evt = OpenILS::Event->new('ACTOR_USR_NOT_FOUND') unless $result;
+
+    return ($result, $evt);
+}
+
+# Get the biblio.record_entry.id value for the given identifier; note that this
+# approach uses a wildcard to match anything that precedes the identifier value
+sub map_id_to_bib {
+    my $record = shift;
+
+    my ($result, $evt);
+
+    my %search = (
+        tag => $bibfield, 
+        value => { like => '%' . $record }
+    );
+
+    if ($bibsubfield) {
+        $search{'subfield'} = $bibsubfield;
+    }
+
+    $result = $U->cstorereq(
+        'open-ils.cstore.direct.metabib.full_rec.search', \%search
+    );
+    $evt = OpenILS::Event->new('METABIB_FULL_REC_NOT_FOUND') unless $record;
+
+    return ($result, $evt);
+}
+
+__END__
+
+=head1 NAME
+
+marc2sre.pl - Convert MARC Format for Holdings Data (MFHD) records to SRE
+(serial.record_entry) JSON objects 
+
+=head1 SYNOPSIS
+
+C<marc2sre.pl> [B<--config>=I<opensrf_core.conf>]
+[[B<--idfield>=I<MARC-tag>[ B<--idsubfield>=I<MARC-code>]] [B<--start_id>=I<start-ID>]
+[B<--user>=I<db-username>] [B<--marctype>=I<fileformat>]
+[[B<--file>=I<MARC-filename>[, ...]] [B<--libmap>=I<map-file>] [B<--quiet>=I<quiet>]
+[[B<--bibfield>=I<MARC-tag> [B<--bibsubfield>=<MARC-code>]]
+
+=head1 DESCRIPTION
+
+For one or more files containing MFHD records, iterate through the records
+and generate SRE (serial.record_entry) JSON objects.
+
+=head1 OPTIONS
+
+=over
 
-       $type |= "staff"; 
+=item * B<-c> I<config-file>, B<--config>=I<config-file>
 
-       my $seed = OpenILS::Application::AppUtils->simplereq(
-               'open-ils.auth',
-               'open-ils.auth.authenticate.init',
-               $username
-       );
+Specifies the OpenSRF configuration file used to connect to the OpenSRF router.
+Defaults to F</openils/conf/opensrf_core.xml>
 
-       die("No auth seed. Couldn't talk to the auth server") unless $seed;
+=item * B<--idfield> I<MARC-field>
 
-       my $response = OpenILS::Application::AppUtils->simplereq(
-               'open-ils.auth',
-               'open-ils.auth.authenticate.complete',
-                {       username => $username,
-                        password => md5_hex($seed . md5_hex($password)),
-                        type => $type });
+Specifies the MFHD field where the identifier of the corresponding
+bibliographic record is found. Defaults to '004'.
 
-        die("No auth response returned on login.") unless $response;
+=item * B<--idsubfield> I<MARC-code>
 
-        my $authtime = $response->{payload}->{authtime};
-        my $authtoken = $response->{payload}->{authtoken};
+Specifies the MFHD subfield, if any, where the identifier of the corresponding
+bibliographic record is found. This option is ignored unless it is accompanied
+by the B<--idfield> option.  Defaults to null.
 
-       die("Login failed for user $username!") unless $authtoken;
+=item * B<--bibfield> I<MARC-field>
 
-        return $authtoken;
-}       
+Specifies the field in the bibliographic record that holds the identifier
+value. Defaults to null.
 
-=head2
+=item * B<--bibsubfield> I<MARC-code>
 
-map_libraries_to_ID
+Specifies the subfield in the bibliographic record, if any, that holds the
+identifier value. This option is ignored unless it is accompanied by the
+B<--bibfield> option. Defaults to null.
 
-Parses a file to return a hash of library names to integers representing
-the actor.org_unit.id value of the library. This enables us to generate
-an ingest file that does not subsequently need to manually manipulated.
+=item * B<-u> I<username>, B<--user>=I<username>
+
+Specifies the Evergreen user that will own these serial records.
+
+=item * B<-m> I<file-format>, B<--marctype>=I<file-format>
+
+Specifies whether the files containg the MFHD records are in MARC21 ('MARC21')
+or MARC21XML ('XML') format. Defaults to MARC21.
+
+=item * B<-l> I<map-file>, B<--libmap>=I<map-file>
+
+Points to a file to containing a mapping of library names to integers.
+The integer represents the actor.org_unit.id value of the library. This enables
+us to generate an ingest file that does not subsequently need to manually
+manipulated.
 
 The library name must correspond to the 'b' subfield of the 852 field.
 Well, it does not have to, but you will have to modify this script
@@ -167,27 +284,41 @@ by a tab, followed by the desired numeric ID of the library. For example:
 BR1    4
 BR2    5
 
-=cut
+=item * B<-q>, B<--quiet>
 
-sub map_libraries_to_ID {
-       my $map_filename = shift;
+Suppresses the record counter output.
 
-       my %lib_id_map;
+=back
 
-       open(MAP_FH, '<', $map_filename) or die "Could not load [$map_filename] $!";
-       while (<MAP_FH>) {
-               my ($lib, $id) = $_ =~ /^(.*?)\t(.*?)$/;
-               $lib_id_map{$lib} = $id;
-       }
+=head1 EXAMPLES
 
-       return \%lib_id_map;
-}
+    marc2sre.pl --idfield 004 --bibfield 035 --bibsubfield a --user cat1 serial_holding.xml
 
-sub get_library_id {
-       my $record = shift;
+Processes MFHD records in the B<serial_holding.xml> file. The script pulls the
+bibliographic record identifier from the 004 control field of the MFHD record
+and searches for a matching value in the bibliographic record in data field
+035, subfield a. The "cat1" user will own the processed MFHD records.
 
-       my $lib_name = $record->field('852')->subfield('b');
-       my $lib_id = $lib_id_map->{$lib_name};
+=head1 AUTHOR
 
-       return $lib_id;
-}
+Dan Scott <dscott@laurentian.ca>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2010-2011 by Dan Scott
+
+This program 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.
+
+This program 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 this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+=cut