0d0ca22b5c9c5b5ea0dd20b976709958fc62fda7
[Evergreen.git] / Open-ILS / src / extras / import / marc2bre.pl
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4
5 use lib '/openils/lib/perl5/';
6
7 use Error qw/:try/;
8 use OpenILS::Utils::Fieldmapper;
9 use Digest::MD5 qw/md5_hex/;
10 use OpenSRF::Utils::JSON;
11 use Data::Dumper;
12 use Unicode::Normalize;
13 use Encode;
14
15 use FileHandle;
16 use Time::HiRes qw/time/;
17 use Getopt::Long;
18 use MARC::Batch;
19 use MARC::File::XML ( BinaryEncoding => 'utf-8' );
20 use MARC::Charset;
21 use DBI;
22
23 #MARC::Charset->ignore_errors(1);
24
25 my ($id_field, $id_subfield, $recid, $user, $config, $idlfile, $marctype, $keyfile, $dontuse_file, $enc, $force_enc, @files, @trash_fields, @req_fields, $use901, $quiet) =
26         ('', 'a', 0, 1, '/openils/conf/opensrf_core.xml', '/openils/conf/fm_IDL.xml', 'USMARC');
27
28 my ($db_driver,$db_host,$db_name,$db_user,$db_pw) =
29         ('Pg','localhost','evergreen','postgres','postgres');
30
31 GetOptions( 'marctype=s'       => \$marctype,
32             'startid=i'        => \$recid,
33             'idfield=s'        => \$id_field,
34             'idsubfield=s'     => \$id_subfield,
35             'user=s'           => \$user,
36             'encoding=s'       => \$enc,
37             'hard_encoding'    => \$force_enc,
38             'keyfile=s'        => \$keyfile,
39             'config=s'         => \$config,
40             'file=s'           => \@files,
41             'required_field=s' => \@req_fields,
42             'trash=s'          => \@trash_fields,
43             'xml_idl=s'        => \$idlfile,
44             'dontuse=s'        => \$dontuse_file,
45             "db_driver=s"      => \$db_driver,
46             "db_host=s"        => \$db_host,
47             "db_name=s"        => \$db_name,
48             "db_user=s"        => \$db_user,
49             "db_pw=s"          => \$db_pw,
50             "use901"           => \$use901,
51             'quiet'            => \$quiet,
52           );
53
54 @trash_fields = split(/,/,join(',',@trash_fields));
55
56 if ($enc) {
57         MARC::Charset->ignore_errors(1);
58         MARC::Charset->assume_encoding($enc);
59 }
60
61 if (uc($marctype) eq 'XML') {
62         'open'->use(':utf8');
63 } else {
64         bytes->use();
65 }
66
67 @files = @ARGV if (!@files);
68
69 my @ses;
70 my @req;
71 my %processing_cache;
72
73 my $dsn = "dbi:$db_driver:host=$db_host;dbname=$db_name";
74
75 if (!$recid) {
76     my $table = 'biblio_record_entry';
77     $table = 'biblio.record_entry' if ($db_driver eq 'Pg');
78
79         my $dbh = DBI->connect($dsn,$db_user,$db_pw);
80         my $sth = $dbh->prepare("SELECT MAX(id) + 1 FROM $table");
81
82         $sth->execute;
83         $sth->bind_col(1, \$recid);
84         $sth->fetch;
85         $sth->finish;
86         $recid++;
87         $dbh->disconnect;
88 }
89
90 my %source_map = (
91         o  => 'OCLC',
92         i  => 'ISxN',
93         l  => 'LCCN',
94         s  => 'System',
95         g  => 'Gutenberg',
96 );
97
98 Fieldmapper->import(IDL => $idlfile);
99
100 my %keymap;
101 if ($keyfile) {
102         open F, $keyfile or die "Couldn't open key file $keyfile";
103         while (<F>) {
104                 if ( /^(\d+)\|(\S+)/o ) {
105                         $keymap{$1} = $2;
106                 }
107         }
108         close(F);
109 }
110
111 my %dontuse_id;
112 if ($dontuse_file) {
113         open F, $dontuse_file or die "Couldn't open used-id file $dontuse_file";
114         while (<F>) {
115                 chomp;
116                 s/^\s*//;
117                 s/\s*$//;
118                 $dontuse_id{$_} = 1;
119         }
120         close(F);
121 }
122
123 select STDERR; $| = 1;
124 select STDOUT; $| = 1;
125
126 my $batch = new MARC::Batch ( $marctype, @files );
127 $batch->strict_off();
128 $batch->warnings_off();
129
130 my %used_ids;
131 my $starttime = time;
132 my $rec;
133 my $count = 0;
134 PROCESS: while ( try { $rec = $batch->next } otherwise { $rec = -1 } ) {
135         next if ($rec == -1);
136
137         # Skip records that don't contain a required field (like '245', for example)
138         foreach my $req_field(@req_fields) {
139                 next PROCESS if !$rec->field("$req_field");
140         }
141         my $id;
142
143         $recid++;
144         while (exists $used_ids{$recid}) {
145                 $recid++;
146         }
147         $used_ids{$recid} = 1;
148
149         if ($id_field) {
150                 my $field = $rec->field($id_field);
151                 if ($field) {
152                         if ($field->is_control_field) {
153                                 $id = $field->data;
154                         } else {
155                                 $id = $field->subfield($id_subfield);
156                         }
157
158                         $id =~ s/\D+//gso;
159                 }
160                 $id = '' if (exists $dontuse_id{$id});
161         }
162
163         if (!$id) {
164                 $id = $recid;
165         }
166
167         if ($keyfile) {
168                 if (my $tcn = $keymap{$id}) {
169                         $rec->delete_field( $_ ) for ($rec->field($id_field));
170                         $rec->append_fields( MARC::Field->new( $id_field, '', '', $id_subfield, $tcn ) );
171                 } else {
172                         $count++;
173                         next;
174                 }
175         }
176
177         my $tcn;
178         if ($use901) {
179             $id = $rec->subfield('901' => 'c')
180         } else {
181             ($rec, $tcn) = preprocess($rec, $id);
182             $tcn->add_subfields(c => $id);
183
184             $rec->delete_field( $_ ) for ($rec->field($id_field));
185             $rec->append_fields( $tcn );
186
187             next unless $rec;
188         }
189
190         my $tcn_value = $rec->subfield('901' => 'a') || "SYS$id";
191         my $tcn_source = $rec->subfield('901' => 'b') || 'System';
192
193         (my $xml = $rec->as_xml_record()) =~ s/\n//sog;
194         $xml =~ s/^<\?xml.+\?\s*>//go;
195         $xml =~ s/>\s+</></go;
196         $xml =~ s/\p{Cc}//go;
197         $xml = entityize($xml,'D');
198         $xml =~ s/[\x00-\x1f]//go;
199
200         my $bib = new Fieldmapper::biblio::record_entry;
201         $bib->id($id);
202         $bib->active('t');
203         $bib->deleted('f');
204         $bib->marc($xml);
205         $bib->creator($user);
206         $bib->create_date('now');
207         $bib->editor($user);
208         $bib->edit_date('now');
209         $bib->tcn_source($tcn_source);
210         $bib->tcn_value($tcn_value);
211         $bib->last_xact_id('IMPORT-'.$starttime);
212
213         print OpenSRF::Utils::JSON->perl2JSON($bib)."\n";
214         $dontuse_id{$tcn_value} = 1;
215
216         $count++;
217
218         if (!$quiet && !($count % 50)) {
219                 print STDERR "\r$count\t". $count / (time - $starttime);
220         }
221 }
222
223 sub preprocess {
224         my $rec = shift;
225         my $id = shift;
226
227         my ($source, $value) = ('','');
228
229         $id = '' if (exists $dontuse_id{$id});
230
231         if (!$id) {
232                 my $f = $rec->field('001');
233                 $id = $f->data if ($f);
234                 $id = '' if (exists $dontuse_id{$id});
235         }
236
237         if (!$id || exists $dontuse_id{$source.$id}) {
238                 my $f = $rec->field('000');
239                 $id = $f->data if ($f);
240                 $source = 'g' if ($f); # only PG seems to use this
241         }
242
243         if (!$id || exists $dontuse_id{$source.$id}) {
244                 my $f = $rec->field('020');
245                 $id = $f->subfield('a') if ($f);
246                 $source = 'i' if ($f);
247         }
248
249         if (!$id || exists $dontuse_id{$source.$id}) {
250                 my $f = $rec->field('022');
251                 $id = $f->subfield('a') if ($f);
252                 $source = 'i' if ($f);
253         }
254
255         if (!$id || exists $dontuse_id{$source.$id}) {
256                 my $f = $rec->field('010');
257                 $id = $f->subfield('a') if ($f);
258                 $source = 'l' if ($f);
259         }
260
261         $rec->delete_field($_) for ($rec->field('901', $id_field, @trash_fields));
262
263         if ($id) {
264                 $id =~ s/\s*$//o;
265                 $id =~ s/^\s*//o;
266                 $id =~ s/^(\S+).*$/$1/o;
267
268                 $id = $source.$id if ($source);
269
270                 ($source, $value) = $id =~ /^(.)(.+)$/o;
271                 if ($id =~ /^o(\d+)$/o) {
272                         $id = "ocm$1";
273                         $source = 'o';
274                 }
275         }
276
277         if ($id && exists $dontuse_id{$id}) {
278                 warn "\n!!! TCN $id is already in use.  Using the record ID ($recid) as a system-generated TCN.\n";
279                 $id = '';
280         }
281
282         if (!$id) {
283                 $source = 's';
284                 $id = 's'.$recid;
285         }
286
287         my $tcn = MARC::Field->new(
288                 '901' => ('', ''),
289                 a => $id,
290                 b => do { $source_map{$source} || 'System' },
291         );
292
293         return ($rec,$tcn);
294 }
295
296 sub entityize {
297         my $stuff = shift;
298         my $form = shift;
299
300         if ($form and $form eq 'D') {
301                 $stuff = NFD($stuff);
302         } else {
303                 $stuff = NFC($stuff);
304         }
305
306         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
307         return $stuff;
308 }
309