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