]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/extras/import/marc2sre.pl.in
Merge branch 'opac-tt-poc' of git+ssh://yeti.esilibrary.com/home/evergreen/evergreen...
[working/Evergreen.git] / Open-ILS / src / extras / import / marc2sre.pl.in
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4
5 use OpenSRF::System;
6 use OpenSRF::EX qw/:try/;
7 use OpenSRF::Utils::SettingsClient;
8 use OpenILS::Application::AppUtils;
9 use OpenILS::Event;
10 use OpenILS::Utils::Fieldmapper;
11 use OpenILS::Utils::Normalize qw/naco_normalize/;
12 use OpenSRF::Utils::JSON;
13 use Unicode::Normalize;
14
15 use Time::HiRes qw/time/;
16 use Getopt::Long;
17 use MARC::Batch;
18 use MARC::File::XML ( BinaryEncoding => 'utf-8' );
19 use MARC::Charset;
20 use Pod::Usage;
21
22 MARC::Charset->ignore_errors(1);
23
24 # Command line options, with applicable defaults
25 my ($idsubfield, $prefix, $bibfield, $bibsubfield, @files, $libmap, $quiet, $help);
26 my $idfield = '004';
27 my $count = 1;
28 my $user = 'admin';
29 my $config = '@sysconfdir@/opensrf_core.xml';
30 my $marctype = 'USMARC';
31
32 my $parse_options = GetOptions(
33     'idfield=s' => \$idfield,
34     'idsubfield=s' => \$idsubfield,
35     'prefix=s'=> \$prefix,
36     'bibfield=s'=> \$bibfield,
37     'bibsubfield=s'=> \$bibsubfield,
38     'startid=i'=> \$count,
39     'user=s' => \$user,
40     'config=s' => \$config,
41     'marctype=s' => \$marctype,
42     'file=s' => \@files,
43     'libmap=s' => \$libmap,
44     'quiet' => \$quiet,
45     'help' => \$help,
46 );
47
48 if (!$parse_options or $help) {
49     pod2usage(0);
50 }
51
52 @files = @ARGV if (!@files);
53
54 my $U = 'OpenILS::Application::AppUtils';
55 my @ses;
56 my @req;
57 my %processing_cache;
58 my $lib_id_map;
59 if ($libmap) {
60     $lib_id_map = map_libraries_to_ID($libmap);
61 }
62
63 OpenSRF::System->bootstrap_client( config_file => $config );
64 Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
65
66 my ($result, $evt) = get_user_id($user);
67 if ($evt || !$result->id) {
68     print("Could not retrieve user with user name '$user'\n");
69     exit(0);
70 }
71
72 $user = $result->id;
73
74 select STDERR; $| = 1;
75 select STDOUT; $| = 1;
76
77 my $batch = new MARC::Batch ( $marctype, @files );
78 $batch->strict_off();
79 $batch->warnings_off();
80
81 my $starttime = time;
82 my $rec;
83 while ( try { $rec = $batch->next } otherwise { $rec = -1 } ) {
84     next if ($rec == -1);
85     my $id = $count;
86     my $record_field;
87     if ($idsubfield) {
88         $record_field = $rec->field($idfield, $idsubfield);
89     } else {
90         $record_field = $rec->field($idfield);
91     }
92
93     # Start by just using the counter as the record ID
94     my $record = $count;
95
96     # If we have identified a location for the bib record ID, grab that value
97     if ($record_field) {
98         $record = $record_field->data;
99     }
100
101     # If we didn't get a bib record ID, skip and move on to the next MFHD record
102     if (!$record) {
103         print STDERR "Could not find a bibliographic record ID link for record $count\n";
104         next;
105     }
106
107     # If we have been given bibfield / bibsubfield values, use those to find
108     # a matching bib record for $record and use _that_ as our record instead
109     if ($bibfield) {
110         my ($result, $evt) = map_id_to_bib($record);
111         if ($evt || !$result || !$result->record) {
112             print STDERR "Could not find matching bibliographic record for record $count\n";
113             next;
114         }
115         $record = $result->record;
116     } else {
117         # Strip the identifier down to a usable integer
118         $record =~ s/^.*?(\d+).*?$/$1/o;
119     }
120
121     (my $xml = $rec->as_xml_record()) =~ s/\n//sog;
122     $xml =~ s/^<\?xml.+\?\s*>//go;
123     $xml =~ s/>\s+</></go;
124     $xml =~ s/\p{Cc}//go;
125     $xml = OpenILS::Application::AppUtils->entityize($xml);
126     $xml =~ s/[\x00-\x1f]//go;
127
128     my $bib = new Fieldmapper::serial::record_entry;
129     $bib->id($id);
130     $bib->record($record);
131     $bib->active('t');
132     $bib->deleted('f');
133     $bib->marc($xml);
134     $bib->creator($user);
135     $bib->create_date('now');
136     $bib->editor($user);
137     $bib->edit_date('now');
138     $bib->last_xact_id('IMPORT-'.$starttime);
139
140     if ($libmap) {
141         my $lib_id = get_library_id($rec);
142         if ($lib_id) {
143             $bib->owning_lib($lib_id);
144         }
145     }
146
147     print OpenSRF::Utils::JSON->perl2JSON($bib)."\n";
148
149     $count++;
150
151     if (!$quiet && !($count % 20)) {
152         print STDERR "\r$count\t". $count / (time - $starttime);
153     }
154 }
155
156 # Generate a hash of library names (as found in the 852b in the MFHD record) to
157 # integers representing actor.org_unit ID values
158 sub map_libraries_to_ID {
159     my $map_filename = shift;
160
161     my %lib_id_map;
162
163     open(MAP_FH, '<', $map_filename) or die "Could not load [$map_filename] $!";
164     while (<MAP_FH>) {
165         my ($lib, $id) = $_ =~ /^(.*?)\t(.*?)$/;
166         $lib_id_map{$lib} = $id;
167     }
168
169     return \%lib_id_map;
170 }
171
172 # Look up the actor.org_unit.id value for this library name
173 sub get_library_id {
174     my $record = shift;
175
176     my $lib_name = $record->field('852')->subfield('b');
177     my $lib_id = $lib_id_map->{$lib_name};
178
179     return $lib_id;
180 }
181
182 # Get the actor.usr.id value for the given username
183 sub get_user_id {
184     my $username = shift;
185
186     my ($result, $evt);
187
188     $result = $U->cstorereq(
189         'open-ils.cstore.direct.actor.user.search',
190         { usrname => $username, deleted => 'f' }
191     );
192     $evt = OpenILS::Event->new('ACTOR_USR_NOT_FOUND') unless $result;
193
194     return ($result, $evt);
195 }
196
197 # Get the biblio.record_entry.id value for the given identifier
198 sub map_id_to_bib {
199     my $record = shift;
200
201     my ($result, $evt);
202
203     $record = naco_normalize($record);
204     if ($prefix) {
205         $record = "$prefix $record";
206     }
207
208     my %search = (
209         tag => $bibfield, 
210         value => naco_normalize($record)
211     );
212
213     if ($bibsubfield) {
214         $search{'subfield'} = $bibsubfield;
215     }
216
217     $result = $U->cstorereq(
218         'open-ils.cstore.direct.metabib.full_rec.search', \%search
219     );
220     $evt = OpenILS::Event->new('METABIB_FULL_REC_NOT_FOUND') unless $record;
221
222     return ($result, $evt);
223 }
224
225 __END__
226
227 =head1 NAME
228
229 marc2sre.pl - Convert MARC Format for Holdings Data (MFHD) records to SRE
230 (serial.record_entry) JSON objects 
231
232 =head1 SYNOPSIS
233
234 C<marc2sre.pl> [B<--config>=I<opensrf_core.conf>]
235 [[B<--idfield>=I<MARC-tag>[ B<--idsubfield>=I<MARC-code>]] [B<--start_id>=I<start-ID>]
236 [B<--user>=I<db-username>] [B<--marctype>=I<fileformat>]
237 [[B<--file>=I<MARC-filename>[, ...]] [B<--libmap>=I<map-file>] [B<--quiet>=I<quiet>]
238 [[B<--bibfield>=I<MARC-tag> [B<--bibsubfield>=<MARC-code>]]
239
240 =head1 DESCRIPTION
241
242 For one or more files containing MFHD records, iterate through the records
243 and generate SRE (serial.record_entry) JSON objects.
244
245 =head1 OPTIONS
246
247 =over
248
249 =item * B<-c> I<config-file>, B<--config>=I<config-file>
250
251 Specifies the OpenSRF configuration file used to connect to the OpenSRF router.
252 Defaults to F<@sysconfdir@/opensrf_core.xml>
253
254 =item * B<--idfield> I<MARC-field>
255
256 Specifies the MFHD field where the identifier of the corresponding
257 bibliographic record is found. Defaults to '004'.
258
259 =item * B<--idsubfield> I<MARC-code>
260
261 Specifies the MFHD subfield, if any, where the identifier of the corresponding
262 bibliographic record is found. This option is ignored unless it is accompanied
263 by the B<--idfield> option.  Defaults to null.
264
265 =item * B<-p> I<prefix> B<--prefix>=I<prefix>
266
267 Specifies the MARC code for the organization that should be prefixed to the
268 bibliographic record identifier. This option is ignored unless it is accompanied
269 by the B<--bibfield> option. Defaults to null.
270
271 =item * B<--bibfield> I<MARC-field>
272
273 Specifies the field in the bibliographic record that holds the identifier
274 value. Defaults to null.
275
276 =item * B<--bibsubfield> I<MARC-code>
277
278 Specifies the subfield in the bibliographic record, if any, that holds the
279 identifier value. This option is ignored unless it is accompanied by the
280 B<--bibfield> option. Defaults to null.
281
282 =item * B<-u> I<username>, B<--user>=I<username>
283
284 Specifies the Evergreen user that will own these serial records.
285
286 =item * B<-m> I<file-format>, B<--marctype>=I<file-format>
287
288 Specifies whether the files containg the MFHD records are in MARC21 ('MARC21')
289 or MARC21XML ('XML') format. Defaults to MARC21.
290
291 =item * B<-l> I<map-file>, B<--libmap>=I<map-file>
292
293 Points to a file to containing a mapping of library names to integers.
294 The integer represents the actor.org_unit.id value of the library. This enables
295 us to generate an ingest file that does not subsequently need to manually
296 manipulated.
297
298 The library name must correspond to the 'b' subfield of the 852 field.
299 Well, it does not have to, but you will have to modify this script
300 accordingly.
301
302 The format of the map file should be the name of the library, followed
303 by a tab, followed by the desired numeric ID of the library. For example:
304
305 BR1     4
306 BR2     5
307
308 =item * B<-q>, B<--quiet>
309
310 Suppresses the record counter output.
311
312 =back
313
314 =head1 EXAMPLES
315
316     marc2sre.pl --user admin --marctype XML --libmap library.map --file serial_holding.xml 
317
318 Processes MFHD records in the B<serial_holding.xml> file as a MARC21XML file,
319 using the default 004 control field for the source of the bibliographic record
320 ID and converting the ID to a plain integer for matching directly against the
321 B<biblio.record_entry.id> column. The file B<library.map> contains the mappings
322 of library names to integers, and the "admin" user will own the processed MFHD
323 records.
324
325     marc2sre.pl --idfield 004 --prefix ocolc --bibfield 035 --bibsubfield a --user cat1 serial_holding.mrc
326
327 B<WARNING>: The B<--bibfield> / B<--bibsubfield> options require one database
328 lookup per MFHD record and will greatly slow down your import. Avoid if at all
329 possible.
330
331 Processes MFHD records in the B<serial_holding.xml> file. The script pulls the
332 bibliographic record identifier from the 004 control field of the MFHD record
333 and searches for a matching value in the bibliographic record in data field
334 035, subfield a.  The prefix "ocolc" will be prepended to the bibliographic
335 record identifier to provide exact matchings against the
336 B<metabib.full_rec.value> column.  The "cat1" user will own the processed MFHD
337 records.
338
339 =head1 AUTHOR
340
341 Dan Scott <dscott@laurentian.ca>
342
343 =head1 COPYRIGHT AND LICENSE
344
345 Copyright 2010-2011 by Dan Scott
346
347 This program is free software; you can redistribute it and/or
348 modify it under the terms of the GNU General Public License
349 as published by the Free Software Foundation; either version 2
350 of the License, or (at your option) any later version.
351
352 This program is distributed in the hope that it will be useful,
353 but WITHOUT ANY WARRANTY; without even the implied warranty of
354 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
355 GNU General Public License for more details.
356
357 You should have received a copy of the GNU General Public License
358 along with this program; if not, write to the Free Software
359 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
360
361 =cut