]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/support-scripts/marc_export
Add an "--all" option to marc_export and silence some warnings
[working/Evergreen.git] / Open-ILS / src / support-scripts / marc_export
1 #!/usr/bin/perl
2 # vim:et:sw=4:ts=4:
3 use strict;
4 use warnings;
5 use bytes;
6
7 use OpenSRF::System;
8 use OpenSRF::EX qw/:try/;
9 use OpenSRF::AppSession;
10 use OpenSRF::Utils::JSON;
11 use OpenSRF::Utils::SettingsClient;
12 use OpenILS::Application::AppUtils;
13 use OpenILS::Utils::Fieldmapper;
14 use OpenILS::Utils::CStoreEditor;
15
16 use MARC::Record;
17 use MARC::File::XML;
18 use UNIVERSAL::require;
19
20 use Time::HiRes qw/time/;
21 use Getopt::Long;
22
23
24 my @formats = qw/USMARC UNIMARC XML BRE ARE/;
25
26 my ($config,$format,$encoding,$location,$dollarsign,$idl,$help,$holdings,$timeout,$export_mfhd,$type,$all_records) = ('/openils/conf/opensrf_core.xml','USMARC','MARC8','','$',0,undef,undef,0,undef,'biblio',undef);
27
28 GetOptions(
29         'help'       => \$help,
30         'items'      => \$holdings,
31         'mfhd'       => \$export_mfhd,
32         'all'        => \$all_records,
33         'location=s' => \$location,
34         'money=s'    => \$dollarsign,
35         'config=s'   => \$config,
36         'format=s'   => \$format,
37         'type=s'     => \$type,
38         'xml-idl=s'  => \$idl,
39         'encoding=s' => \$encoding,
40         'timeout=i'  => \$timeout,
41 );
42
43 if ($help) {
44 print <<"HELP";
45 This script exports MARC authority, bibliographic, and serial holdings
46 records from an Evergreen database. 
47
48 Input to this script can consist of a list of record IDs, with one record ID
49 per line, corresponding to the record ID in the Evergreen database table of
50 your requested record type.
51
52 Alternately, passing the --all option will attempt to export all records of
53 the specified type from the Evergreen database. The --all option starts at
54 record ID 1 and increments the ID by 1 until the largest ID in the database
55 is retrieved. This may not be very efficient for databases with large gaps
56 in their ID sequences.
57
58 Usage: $0 [options]
59  --help or -h       This screen.
60  --config or -c     Configuration file [/openils/conf/opensrf_core.xml]
61  --format or -f     Output format (USMARC, UNIMARC, XML, BRE, ARE) [USMARC]
62  --encoding or -e   Output encoding (UTF-8, ISO-8859-?, MARC8) [MARC8]
63  --xml-idl or -x    Location of the IDL XML
64  --timeout          Timeout for exporting a single record; increase if you
65                     are using --holdings and are exporting records that
66                     have a lot of items attached to them.
67  --type or -t       Record type (BIBLIO, AUTHORITY) [BIBLIO]
68  --all or -a        Export all records; ignores input list
69
70  Additional options for type = 'BIBLIO':
71  --items or -i      Include items (holdings) in the output
72  --money            Currency symbol to use in item price field [\$]
73  --mfhd             Export serial MFHD records for associated bib records
74                     Not compatible with --format=BRE
75  --location or -l   MARC Location Code for holdings from
76                     http://www.loc.gov/marc/organizations/orgshome.html
77
78 Examples:
79
80 To export a set of USMARC records in a file named "output_file" based on the
81 IDs contained in a file named "list_of_ids":
82   cat list_of_ids | $0 > output_file
83
84 To export a set of MARC21XML authority records in a file named "output.xml"
85 for all authority records in the database:
86   $0 --format XML --type AUTHORITY --all > output.xml
87
88 HELP
89     exit;
90 }
91
92 $type = lc($type);
93 $format = uc($format);
94 $encoding = uc($encoding);
95
96 binmode(STDOUT, ':raw') if ($encoding ne 'UTF-8');
97 binmode(STDOUT, ':utf8') if ($encoding eq 'UTF-8');
98
99 if (!grep { $format eq $_ } @formats) {
100     die "Please select a supported format.  ".
101         "Right now that means one of [".
102         join('|',@formats). "]\n";
103 }
104
105 if ($format ne 'XML') {
106     my $type = 'MARC::File::' . $format;
107     $type->require;
108 }
109
110 if ($timeout <= 0) {
111     # set default timeout and/or correct silly user who 
112     # supplied a negative timeout; default timeout of
113     # 300 seconds if exporting items determined empirically.
114     $timeout = $holdings ? 300 : 1;
115 }
116
117 OpenSRF::System->bootstrap_client( config_file => $config );
118
119 if (!$idl) {
120     $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
121 }
122
123 Fieldmapper->import(IDL => $idl);
124
125 my $ses = OpenSRF::AppSession->create('open-ils.cstore');
126 OpenILS::Utils::CStoreEditor::init();
127 my $editor = OpenILS::Utils::CStoreEditor->new();
128
129 print <<HEADER if ($format eq 'XML');
130 <?xml version="1.0" encoding="$encoding"?>
131 <collection xmlns='http://www.loc.gov/MARC21/slim'>
132 HEADER
133
134 my %orgs;
135 my %shelves;
136
137 my $flesh = {};
138
139 if ($holdings) {
140     get_bib_locations();
141 }
142
143 my $start = time;
144 my $last_time = time;
145 my %count = ('bib' => 0, 'did' => 0);
146 my $speed = 0;
147
148 if ($all_records) {
149     my $top_record = 0;
150     if ($type eq 'biblio') {
151         $top_record = $editor->search_biblio_record_entry([
152             {deleted => 'f'},
153             {order_by => { 'bre' => 'id DESC' }, limit => 1}
154         ])->[0]->id;
155     } elsif ($type eq 'authority') {
156         $top_record = $editor->search_authority_record_entry([
157             {deleted => 'f'},
158             {order_by => { 'are' => 'id DESC' }, limit => 1}
159         ])->[0]->id;
160     }
161     for (my $i = 0; $i++ < $top_record;) {
162         export_record($i);
163     }
164 } else {
165     while ( my $i = <> ) {
166         export_record($i);
167     }
168 }
169
170 print "</collection>\n" if ($format eq 'XML');
171
172 $speed = $count{did} / (time - $start);
173 my $time = time - $start;
174 print STDERR <<DONE;
175
176 Exports Attempted : $count{bib}
177 Exports Completed : $count{did}
178 Overall Speed     : $speed
179 Total Time Elapsed: $time seconds
180
181 DONE
182
183 sub export_record {
184     my $id = shift;
185
186     my $bib; 
187
188     my $r = $ses->request( "open-ils.cstore.direct.$type.record_entry.retrieve", $id, $flesh );
189     my $s = $r->recv(timeout => $timeout);
190     if (!$s) {
191         warn "\n!!!!! Failed trying to read record $id\n";
192         return;
193     }
194     if ($r->failed) {
195         warn "\n!!!!!! Failed trying to read record $id: " . $r->failed->stringify . "\n";
196         return;
197     }
198     if ($r->timed_out) {
199         warn "\n!!!!!! Timed out trying to read record $id\n";
200         return;
201     }
202     $bib = $s->content;
203     $r->finish;
204
205     $count{bib}++;
206     return unless $bib;
207
208     if ($format eq 'ARE' or $format eq 'BRE') {
209         print OpenSRF::Utils::JSON->perl2JSON($bib);
210         stats();
211         $count{did}++;
212         return;
213     }
214
215     try {
216
217         my $r = MARC::Record->new_from_xml( $bib->marc, $encoding, $format );
218         if ($type eq 'biblio') {
219             add_bib_holdings($bib, $r);
220         }
221
222         if ($format eq 'XML') {
223             my $xml = $r->as_xml_record;
224             $xml =~ s/^<\?.+?\?>$//mo;
225             print $xml;
226         } elsif ($format eq 'UNIMARC') {
227             print $r->as_usmarc;
228         } elsif ($format eq 'USMARC') {
229             print $r->as_usmarc;
230         }
231
232         $count{did}++;
233
234     } otherwise {
235         my $e = shift;
236         warn "\n$e\n";
237         import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire export
238     };
239
240     if ($export_mfhd and $type eq 'biblio') {
241         my $mfhds = $editor->search_serial_record_entry({record => $id, deleted => 'f'});
242         foreach my $mfhd (@$mfhds) {
243             try {
244                 my $r = MARC::Record->new_from_xml( $mfhd->marc, $encoding, $format );
245
246                 if ($format eq 'XML') {
247                     my $xml = $r->as_xml_record;
248                     $xml =~ s/^<\?.+?\?>$//mo;
249                     print $xml;
250                 } elsif ($format eq 'UNIMARC') {
251                     print $r->as_usmarc;
252                 } elsif ($format eq 'USMARC') {
253                     print $r->as_usmarc;
254                 }
255             } otherwise {
256                 my $e = shift;
257                 warn "\n$e\n";
258                 import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire export
259             };
260         }
261     }
262
263     stats() if (! ($count{bib} % 50 ));
264 }
265
266 sub stats {
267     try {
268         no warnings;
269
270         $speed = $count{did} / (time - $start);
271
272         my $speed_now = ($count{did} - $count{did_last}) / (time - $count{time_last});
273         my $cn_speed = $count{cn} / (time - $start);
274         my $cp_speed = $count{cp} / (time - $start);
275
276         printf STDERR "\r  $count{did} of $count{bib} @  \%0.4f/s ttl / \%0.4f/s rt ".
277                 "($count{cn} CNs @ \%0.4f/s :: $count{cp} CPs @ \%0.4f/s)\r",
278                 $speed,
279                 $speed_now,
280                 $cn_speed,
281                 $cp_speed;
282     } otherwise {};
283     $count{did_last} = $count{did};
284     $count{time_last} = time;
285 }
286
287 sub get_bib_locations {
288     print STDERR "Retrieving Org Units ... ";
289     my $r = $ses->request( 'open-ils.cstore.direct.actor.org_unit.search', { id => { '!=' => undef } } );
290
291     while (my $o = $r->recv) {
292         die $r->failed->stringify if ($r->failed);
293         $o = $o->content;
294         last unless ($o);
295         $orgs{$o->id} = $o;
296     }
297     $r->finish;
298     print STDERR "OK\n";
299
300     print STDERR "Retrieving Shelving locations ... ";
301     $r = $ses->request( 'open-ils.cstore.direct.asset.copy_location.search', { id => { '!=' => undef } } );
302
303     while (my $s = $r->recv) {
304         die $r->failed->stringify if ($r->failed);
305         $s = $s->content;
306         last unless ($s);
307         $shelves{$s->id} = $s;
308     }
309     $r->finish;
310     print STDERR "OK\n";
311
312     $flesh = { flesh => 2, flesh_fields => { bre => [ 'call_numbers' ], acn => [ 'copies' ] } };
313 }
314
315 sub add_bib_holdings {
316     my $bib = shift;
317     my $r = shift;
318
319     my $cn_list = $bib->call_numbers;
320     if ($cn_list && @$cn_list) {
321
322         $count{cn} += @$cn_list;
323     
324         my $cp_list = [ map { @{ $_->copies } } @$cn_list ];
325         if ($cp_list && @$cp_list) {
326
327             my %cn_map;
328             push @{$cn_map{$_->call_number}}, $_ for (@$cp_list);
329                             
330             for my $cn ( @$cn_list ) {
331                 my $cn_map_list = $cn_map{$cn->id};
332
333                 for my $cp ( @$cn_map_list ) {
334                     $count{cp}++;
335                             
336                     $r->append_fields(
337                         MARC::Field->new(
338                             852, '4', '', 
339                             a => $location,
340                             b => $orgs{$cn->owning_lib}->shortname,
341                             b => $orgs{$cp->circ_lib}->shortname,
342                             c => $shelves{$cp->location}->name,
343                             j => $cn->label,
344                             ($cp->circ_modifier ? ( g => $cp->circ_modifier ) : ()),
345                             p => $cp->barcode,
346                             ($cp->price ? ( y => $dollarsign.$cp->price ) : ()),
347                             ($cp->copy_number ? ( t => $cp->copy_number ) : ()),
348                             ($cp->ref eq 't' ? ( x => 'reference' ) : ()),
349                             ($cp->holdable eq 'f' ? ( x => 'unholdable' ) : ()),
350                             ($cp->circulate eq 'f' ? ( x => 'noncirculating' ) : ()),
351                             ($cp->opac_visible eq 'f' ? ( x => 'hidden' ) : ()),
352                         )
353                     );
354
355                     stats() if (! ($count{cp} % 100 ));
356                 }
357             }
358         }
359     }
360 }