Bug 24123: Fix import of UTF-8 encoded MARC21 MARCXML using bulkmarcimport (elastic...
[koha.git] / misc / migration_tools / bulkmarcimport.pl
1 #!/usr/bin/perl
2 # Import an iso2709 file into Koha 3
3
4 use Modern::Perl;
5 #use diagnostics;
6 BEGIN {
7     # find Koha's Perl modules
8     # test carefully before changing this
9     use FindBin;
10     eval { require "$FindBin::Bin/../kohalib.pl" };
11 }
12
13 # Koha modules used
14 use MARC::File::USMARC;
15 use MARC::File::XML;
16 use MARC::Record;
17 use MARC::Batch;
18 use MARC::Charset;
19
20 use Koha::Script;
21 use C4::Context;
22 use C4::Biblio;
23 use C4::Koha;
24 use C4::Debug;
25 use C4::Charset;
26 use C4::Items;
27 use C4::MarcModificationTemplates;
28
29 use YAML;
30 use Unicode::Normalize;
31 use Time::HiRes qw(gettimeofday);
32 use Getopt::Long;
33 use IO::File;
34 use Pod::Usage;
35
36 use Koha::Biblios;
37 use Koha::SearchEngine;
38 use Koha::SearchEngine::Search;
39
40 use open qw( :std :encoding(UTF-8) );
41 binmode( STDOUT, ":encoding(UTF-8)" );
42 my ( $input_marc_file, $number, $offset) = ('',0,0);
43 my ($version, $delete, $test_parameter, $skip_marc8_conversion, $char_encoding, $verbose, $commit, $fk_off,$format,$biblios,$authorities,$keepids,$match, $isbn_check, $logfile);
44 my ( $insert, $filters, $update, $all, $yamlfile, $authtypes, $append );
45 my $cleanisbn = 1;
46 my ($sourcetag,$sourcesubfield,$idmapfl, $dedup_barcode);
47 my $framework = '';
48 my $localcust;
49 my $marc_mod_template = '';
50 my $marc_mod_template_id = -1;
51
52 $|=1;
53
54 GetOptions(
55     'commit:f'    => \$commit,
56     'file:s'    => \$input_marc_file,
57     'n:f' => \$number,
58     'o|offset:f' => \$offset,
59     'h' => \$version,
60     'd' => \$delete,
61     't|test' => \$test_parameter,
62     's' => \$skip_marc8_conversion,
63     'c:s' => \$char_encoding,
64     'v:+' => \$verbose,
65     'fk' => \$fk_off,
66     'm:s' => \$format,
67     'l:s' => \$logfile,
68     'append' => \$append,
69     'k|keepids:s' => \$keepids,
70     'b|biblios' => \$biblios,
71     'a|authorities' => \$authorities,
72     'authtypes:s' => \$authtypes,
73     'filter=s@'     => \$filters,
74     'insert'        => \$insert,
75     'update'        => \$update,
76     'all'           => \$all,
77     'match=s@'    => \$match,
78     'i|isbn' => \$isbn_check,
79     'x:s' => \$sourcetag,
80     'y:s' => \$sourcesubfield,
81     'idmap:s' => \$idmapfl,
82     'cleanisbn!'     => \$cleanisbn,
83     'yaml:s'        => \$yamlfile,
84     'dedupbarcode' => \$dedup_barcode,
85     'framework=s' => \$framework,
86     'custom:s'    => \$localcust,
87     'marcmodtemplate:s' => \$marc_mod_template,
88 );
89 $biblios ||= !$authorities;
90 $insert  ||= !$update;
91 my $writemode = ($append) ? "a" : "w";
92
93 pod2usage( -msg => "\nYou must specify either --biblios or --authorities, not both.\n", -exitval ) if $biblios && $authorities;
94
95 if ($all) {
96     $insert = 1;
97     $update = 1;
98 }
99
100 if ($version || ($input_marc_file eq '')) {
101     pod2usage( -verbose => 2 );
102     exit;
103 }
104 if( $update && !( $match || $isbn_check ) ) {
105     warn "Using -update without -match or -isbn seems to be useless.\n";
106 }
107
108 if(defined $localcust) { #local customize module
109     if(!-e $localcust) {
110         $localcust= $localcust||'LocalChanges'; #default name
111         $localcust=~ s/^.*\/([^\/]+)$/$1/; #extract file name only
112         $localcust=~ s/\.pm$//;           #remove extension
113         my $fqcust= $FindBin::Bin."/$localcust.pm"; #try migration_tools dir
114         if(-e $fqcust) {
115             $localcust= $fqcust;
116         }
117         else {
118             print "WARNING: customize module $localcust.pm not found!\n";
119             exit 1;
120         }
121     }
122     require $localcust if $localcust;
123     $localcust=\&customize if $localcust;
124 }
125
126 if($marc_mod_template ne '') {
127     my @templates = GetModificationTemplates();
128     foreach my $this_template (@templates) {
129         if($this_template->{'name'} eq $marc_mod_template) {
130             if($marc_mod_template_id < 0) {
131                 $marc_mod_template_id = $this_template->{'template_id'};
132             } else {
133                 print "WARNING: MARC modification template name " .
134                 "'$marc_mod_template' matches multiple templates. " .
135                 "Please rename these templates\n";
136                 exit 1;
137             }
138         }
139     }
140     if($marc_mod_template_id < 0) {
141         die "Can't located MARC modification template '$marc_mod_template'\n";
142     } else {
143         print "Records will be modified using MARC modofication template: $marc_mod_template\n" if $verbose;
144     }
145 }
146
147 my $dbh = C4::Context->dbh;
148 my $heading_fields=get_heading_fields();
149
150 if (defined $idmapfl) {
151   open(IDMAP,">$idmapfl") or die "cannot open $idmapfl \n";
152 }
153
154 if ((not defined $sourcesubfield) && (not defined $sourcetag)){
155   $sourcetag="910";
156   $sourcesubfield="a";
157 }
158
159
160 # Disable logging for the biblios and authorities import operation. It would unnecessarily
161 # slow the import
162
163 # Disable the syspref cache so we can change logging settings
164 C4::Context->disable_syspref_cache();
165 # Save current CataloguingLog and AuthoritiesLog sysprefs values
166 my $CataloguingLog = C4::Context->preference( 'CataloguingLog' );
167 my $AuthoritiesLog = C4::Context->preference( 'AuthoritiesLog' );
168 # Disable logging for both
169 C4::Context->set_preference( 'CataloguingLog', 0 );
170 C4::Context->set_preference( 'AuthoritiesLog', 0 );
171
172 if ($fk_off) {
173         $dbh->do("SET FOREIGN_KEY_CHECKS = 0");
174 }
175
176
177 if ($delete) {
178         if ($biblios){
179         print "deleting biblios\n";
180         $dbh->do("DELETE FROM biblio");
181         $dbh->do("ALTER TABLE biblio AUTO_INCREMENT = 1");
182         $dbh->do("DELETE FROM biblioitems");
183         $dbh->do("ALTER TABLE biblioitems AUTO_INCREMENT = 1");
184         $dbh->do("DELETE FROM items");
185         $dbh->do("ALTER TABLE items AUTO_INCREMENT = 1");
186         }
187         else {
188         print "deleting authorities\n";
189         $dbh->do("truncate auth_header");
190         }
191     $dbh->do("truncate zebraqueue");
192 }
193
194
195
196 if ($test_parameter) {
197     print "TESTING MODE ONLY\n    DOING NOTHING\n===============\n";
198 }
199
200 my $marcFlavour = C4::Context->preference('marcflavour') || 'MARC21';
201
202 # The definition of $searcher must be before MARC::Batch->new
203 my $searcher = Koha::SearchEngine::Search->new(
204     {
205         index => (
206               $authorities
207             ? $Koha::SearchEngine::AUTHORITIES_INDEX
208             : $Koha::SearchEngine::BIBLIOS_INDEX
209         )
210     }
211 );
212
213 print "Characteristic MARC flavour: $marcFlavour\n" if $verbose;
214 my $starttime = gettimeofday;
215 my $batch;
216 my $fh = IO::File->new($input_marc_file); # don't let MARC::Batch open the file, as it applies the ':utf8' IO layer
217 if (defined $format && $format =~ /XML/i) {
218     # ugly hack follows -- MARC::File::XML, when used by MARC::Batch,
219     # appears to try to convert incoming XML records from MARC-8
220     # to UTF-8.  Setting the BinaryEncoding key turns that off
221     # TODO: see what happens to ISO-8859-1 XML files.
222     # TODO: determine if MARC::Batch can be fixed to handle
223     #       XML records properly -- it probably should be
224     #       be using a proper push or pull XML parser to
225     #       extract the records, not using regexes to look
226     #       for <record>.*</record>.
227     $MARC::File::XML::_load_args{BinaryEncoding} = 'utf-8';
228     my $recordformat= ($marcFlavour eq "MARC21"?"USMARC":uc($marcFlavour));
229 #UNIMARC Authorities have a different way to manage encoding than UNIMARC biblios.
230     $recordformat=$recordformat."AUTH" if ($authorities and $marcFlavour ne "MARC21");
231     $MARC::File::XML::_load_args{RecordFormat} = $recordformat;
232     $batch = MARC::Batch->new( 'XML', $fh );
233 } else {
234     $batch = MARC::Batch->new( 'USMARC', $fh );
235 }
236 $batch->warnings_off();
237 $batch->strict_off();
238 my $i=0;
239 my $commitnum = $commit ? $commit : 50;
240 my $yamlhash;
241
242 # Skip file offset
243 if ( $offset ) {
244     print "Skipping file offset: $offset records\n";
245     $batch->next() while ($offset--);
246 }
247
248 my ($tagid,$subfieldid);
249 if ($authorities){
250           $tagid='001';
251 }
252 else {
253    ( $tagid, $subfieldid ) =
254             GetMarcFromKohaField( "biblio.biblionumber" );
255         $tagid||="001";
256 }
257
258 # the SQL query to search on isbn
259 my $sth_isbn = $dbh->prepare("SELECT biblionumber,biblioitemnumber FROM biblioitems WHERE isbn=?");
260
261 $dbh->{AutoCommit} = 0;
262 my $loghandle;
263 if ($logfile){
264    $loghandle= IO::File->new($logfile, $writemode) ;
265    print $loghandle "id;operation;status\n";
266 }
267
268 RECORD: while (  ) {
269     my $record;
270     # get records
271     eval { $record = $batch->next() };
272     if ( $@ ) {
273         print "Bad MARC record $i: $@ skipped\n";
274         # FIXME - because MARC::Batch->next() combines grabbing the next
275         # blob and parsing it into one operation, a correctable condition
276         # such as a MARC-8 record claiming that it's UTF-8 can't be recovered
277         # from because we don't have access to the original blob.  Note
278         # that the staging import can deal with this condition (via
279         # C4::Charset::MarcToUTF8Record) because it doesn't use MARC::Batch.
280         next;
281     }
282     # skip if we get an empty record (that is MARC valid, but will result in AddBiblio failure
283     last unless ( $record );
284     $i++;
285     if( ($verbose//1)==1 ) { #no dot for verbose==2
286         print "." . ( $i % 100==0 ? "\n$i" : '' );
287     }
288
289     # transcode the record to UTF8 if needed & applicable.
290     if ($record->encoding() eq 'MARC-8' and not $skip_marc8_conversion) {
291         # FIXME update condition
292         my ($guessed_charset, $charset_errors);
293          ($record, $guessed_charset, $charset_errors) = MarcToUTF8Record($record, $marcFlavour.(($authorities and $marcFlavour ne "MARC21")?'AUTH':''));
294         if ($guessed_charset eq 'failed') {
295             warn "ERROR: failed to perform character conversion for record $i\n";
296             next RECORD;            
297         }
298     }
299     SetUTF8Flag($record);
300     if($marc_mod_template_id > 0) {
301     print "Modifying MARC\n" if $verbose;
302     ModifyRecordWithTemplate( $marc_mod_template_id, $record );
303     }
304     &$localcust($record) if $localcust;
305     my $isbn;
306     # remove trailing - in isbn (only for biblios, of course)
307     if( $biblios ) {
308         my $tag = $marcFlavour eq 'UNIMARC' ? '010' : '020';
309         my $field = $record->field($tag);
310         $isbn = $field && $field->subfield('a');
311         if ( $isbn && $cleanisbn ) {
312             $isbn =~ s/-//g;
313             $field->update('a' => $isbn);
314         }
315     }
316     my $id;
317     # search for duplicates (based on Local-number)
318     my $originalid;
319     $originalid = GetRecordId( $record, $tagid, $subfieldid );
320     if ($match) {
321         require C4::Search;
322         my $query = build_query( $match, $record );
323         my $server = ( $authorities ? 'authorityserver' : 'biblioserver' );
324         $debug && warn $query;
325         my ( $error, $results, $totalhits ) = $searcher->simple_search_compat( $query, 0, 3, [$server] );
326         # changed to warn so able to continue with one broken record
327         if ( defined $error ) {
328             warn "unable to search the database for duplicates : $error";
329             printlog( { id => $id || $originalid || $match, op => "match", status => "ERROR" } ) if ($logfile);
330             next RECORD;
331         }
332         $debug && warn "$query $server : $totalhits";
333         if ( $results && scalar(@$results) == 1 ) {
334             my $marcrecord = C4::Search::new_record_from_zebra( $server, $results->[0] );
335             SetUTF8Flag($marcrecord);
336             $id = GetRecordId( $marcrecord, $tagid, $subfieldid );
337             if ( $authorities && $marcFlavour ) {
338                 #Skip if authority in database is the same as the on in database
339                 if ( $marcrecord->field('005') && $record->field('005') &&
340                      $marcrecord->field('005')->data && $record->field('005')->data &&
341                      $marcrecord->field('005')->data >= $record->field('005')->data ) {
342                     if ($yamlfile) {
343                         $yamlhash->{$originalid}->{'authid'} = $id;
344
345                         # we recover all subfields of the heading authorities
346                         my @subfields;
347                         foreach my $field ( $marcrecord->field("2..") ) {
348                             push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
349                         }
350                         $yamlhash->{$originalid}->{'subfields'} = \@subfields;
351                     }
352                     next;
353                 }
354             }
355         } elsif ( $results && scalar(@$results) > 1 ) {
356             $debug && warn "more than one match for $query";
357         } else {
358             $debug && warn "nomatch for $query";
359         }
360     }
361     if ($keepids && $originalid) {
362             my $storeidfield;
363             if ( length($keepids) == 3 ) {
364                 $storeidfield = MARC::Field->new( $keepids, $originalid );
365             } else {
366                 $storeidfield = MARC::Field->new( substr( $keepids, 0, 3 ), "", "", substr( $keepids, 3, 1 ), $originalid );
367             }
368             $record->insert_fields_ordered($storeidfield);
369             $record->delete_field( $record->field($tagid) );
370     }
371     foreach my $stringfilter (@$filters) {
372         if ( length($stringfilter) == 3 ) {
373             foreach my $field ( $record->field($stringfilter) ) {
374                 $record->delete_field($field);
375                 $debug && warn "removed : ", $field->as_string;
376             }
377         } elsif ($stringfilter =~ /([0-9]{3})([a-z0-9])(.*)/) {
378             my $removetag = $1;
379             my $removesubfield = $2;
380             my $removematch = $3;
381             if ( ( $removetag > "010" ) && $removesubfield ) {
382                 foreach my $field ( $record->field($removetag) ) {
383                     $field->delete_subfield( code => "$removesubfield", match => $removematch );
384                     $debug && warn "Potentially removed : ", $field->subfield($removesubfield);
385                 }
386             }
387         }
388     }
389     unless ($test_parameter) {
390         if ($authorities){
391             use C4::AuthoritiesMarc;
392             my $authtypecode=GuessAuthTypeCode($record, $heading_fields);
393             my $authid= ($id?$id:GuessAuthId($record));
394             if ($authid && GetAuthority($authid) && $update ){
395             ## Authority has an id and is in database : Replace
396                 eval { ( $authid ) = ModAuthority($authid,$record, $authtypecode) };
397                 if ($@){
398                     warn "Problem with authority $authid Cannot Modify";
399                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ERROR"}) if ($logfile);
400                 }
401                                 else{
402                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ok"}) if ($logfile);
403                                 }
404             }  
405             elsif (defined $authid) {
406             ## An authid is defined but no authority in database : add
407                 eval { ( $authid ) = AddAuthority($record,$authid, $authtypecode) };
408                 if ($@){
409                     warn "Problem with authority $authid Cannot Add ".$@;
410                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
411                 }
412                                 else{
413                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
414                                 }
415             }
416                 else {
417             ## True insert in database
418                 eval { ( $authid ) = AddAuthority($record,"", $authtypecode) };
419                 if ($@){
420                     warn "Problem with authority $authid Cannot Add".$@;
421                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
422                 }
423                                 else{
424                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
425                                 }
426                 }
427             if ($yamlfile) {
428             $yamlhash->{$originalid}->{'authid'} = $authid;
429             my @subfields;
430             foreach my $field ( $record->field("2..") ) {
431                 push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
432             }
433             $yamlhash->{$originalid}->{'subfields'} = \@subfields;
434             }
435         }
436         else {
437             my ( $biblionumber, $biblioitemnumber, $itemnumbers_ref, $errors_ref );
438             $biblionumber = $id;
439             # check for duplicate, based on ISBN (skip it if we already have found a duplicate with match parameter
440             if (!$biblionumber && $isbn_check && $isbn) {
441     #         warn "search ISBN : $isbn";
442                 $sth_isbn->execute($isbn);
443                 ($biblionumber,$biblioitemnumber) = $sth_isbn->fetchrow;
444             }
445                 if (defined $idmapfl) {
446                                 if ($sourcetag < "010"){
447                                         if ($record->field($sourcetag)){
448                                           my $source = $record->field($sourcetag)->data();
449                                           printf(IDMAP "%s|%s\n",$source,$biblionumber);
450                                         }
451                             } else {
452                                         my $source=$record->subfield($sourcetag,$sourcesubfield);
453                                         printf(IDMAP "%s|%s\n",$source,$biblionumber);
454                           }
455                         }
456                                         # create biblio, unless we already have it ( either match or isbn )
457             if ($biblionumber) {
458                 eval{
459                     $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
460                 };
461                 if ($update) {
462                     eval { ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
463                     if ($@) {
464                         warn "ERROR: Edit biblio $biblionumber failed: $@\n";
465                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
466                         next RECORD;
467                     } else {
468                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ok" } ) if ($logfile);
469                     }
470                 } else {
471                     printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "warning : already in database" } ) if ($logfile);
472                 }
473             } else {
474                 if ($insert) {
475                     eval { ( $biblionumber, $biblioitemnumber ) = AddBiblio( $record, '', { defer_marc_save => 1 } ) };
476                     if ($@) {
477                         warn "ERROR: Adding biblio $biblionumber failed: $@\n";
478                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ERROR" } ) if ($logfile);
479                         next RECORD;
480                     } else {
481                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ok" } ) if ($logfile);
482                     }
483                 } else {
484                     warn "WARNING: Updating record ".($id||$originalid)." failed";
485                     printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "warning : not in database" } ) if ($logfile);
486                     next RECORD;
487                 }
488             }
489             eval { ( $itemnumbers_ref, $errors_ref ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
490             my $error_adding = $@;
491             # Work on a clone so that if there are real errors, we can maybe
492             # fix them up later.
493                         my $clone_record = $record->clone();
494             C4::Biblio::_strip_item_fields($clone_record, '');
495             # This sets the marc fields if there was an error, and also calls
496             # defer_marc_save.
497             ModBiblioMarc( $clone_record, $biblionumber, $framework );
498             if ( $error_adding ) {
499                 warn "ERROR: Adding items to bib $biblionumber failed: $error_adding";
500                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
501                 # if we failed because of an exception, assume that 
502                 # the MARC columns in biblioitems were not set.
503                 next RECORD;
504             }
505                         else{
506                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ok"}) if ($logfile);
507                         }
508             if ($dedup_barcode && grep { exists $_->{error_code} && $_->{error_code} eq 'duplicate_barcode' } @$errors_ref) {
509                 # Find the record called 'barcode'
510                 my ($tag, $sub) = C4::Biblio::GetMarcFromKohaField( 'items.barcode' );
511                 # Now remove any items that didn't have a duplicate_barcode error,
512                 # erase the barcodes on items that did, and re-add those items.
513                 my %dupes;
514                 foreach my $i (0 .. $#{$errors_ref}) {
515                     my $ref = $errors_ref->[$i];
516                     if ($ref && ($ref->{error_code} eq 'duplicate_barcode')) {
517                         $dupes{$ref->{item_sequence}} = 1;
518                         # Delete the error message because we're going to
519                         # retry this one.
520                         delete $errors_ref->[$i];
521                     }
522                 }
523                 my $seq = 0;
524                 foreach my $field ($record->field($tag)) {
525                     $seq++;
526                     if ($dupes{$seq}) {
527                         # Here we remove the barcode
528                         $field->delete_subfield(code => $sub);
529                     } else {
530                         # otherwise we delete the field because we don't want
531                         # two of them
532                         $record->delete_fields($field);
533                     }
534                 }
535                 # Now re-add the record as before, adding errors to the prev list
536                 my $more_errors;
537                 eval { ( $itemnumbers_ref, $more_errors ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
538                 if ( $@ ) {
539                     warn "ERROR: Adding items to bib $biblionumber failed: $@\n";
540                     printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
541                     # if we failed because of an exception, assume that
542                     # the MARC columns in biblioitems were not set.
543                     ModBiblioMarc( $record, $biblionumber, $framework );
544                     next RECORD;
545                 } else {
546                     printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ok"}) if ($logfile);
547                 }
548                 push @$errors_ref, @{ $more_errors };
549             }
550             if ($#{ $errors_ref } > -1) {
551                 report_item_errors($biblionumber, $errors_ref);
552             }
553             $yamlhash->{$originalid} = $biblionumber if ($yamlfile);
554         }
555         $dbh->commit() if (0 == $i % $commitnum);
556     }
557     print $record->as_formatted()."\n" if ($verbose//0)==2;
558     last if $i == $number;
559 }
560 $dbh->commit();
561 $dbh->{AutoCommit} = 1;
562
563
564 if ($fk_off) {
565         $dbh->do("SET FOREIGN_KEY_CHECKS = 1");
566 }
567
568 # Restore CataloguingLog
569 C4::Context->set_preference( 'CataloguingLog', $CataloguingLog );
570 # Restore AuthoritiesLog
571 C4::Context->set_preference( 'AuthoritiesLog', $AuthoritiesLog );
572
573 my $timeneeded = gettimeofday - $starttime;
574 print "\n$i MARC records done in $timeneeded seconds\n";
575 if ($logfile){
576   print $loghandle "file : $input_marc_file\n";
577   print $loghandle "$i MARC records done in $timeneeded seconds\n";
578   $loghandle->close;
579 }
580 if ($yamlfile) {
581     open my $yamlfileout, q{>}, "$yamlfile" or die "cannot open $yamlfile \n";
582     print $yamlfileout Dump($yamlhash);
583 }
584 exit 0;
585
586 sub GetRecordId{
587         my $marcrecord=shift;
588         my $tag=shift;
589         my $subfield=shift;
590         my $id;
591         if ($tag lt "010"){
592                 return $marcrecord->field($tag)->data() if $marcrecord->field($tag);
593         } 
594         elsif ($subfield){
595                 if ($marcrecord->field($tag)){
596                         return $marcrecord->subfield($tag,$subfield);
597                 }
598         }
599         return $id;
600 }
601 sub build_query {
602         my $match = shift;
603         my $record=shift;
604         my @searchstrings;
605         foreach my $matchingpoint (@$match){
606           my $string = build_simplequery($matchingpoint,$record);
607           push @searchstrings,$string if (length($string)>0);
608         }
609     my $QParser;
610     $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
611     my $op;
612     if ($QParser) {
613         $op = '&&';
614     } else {
615         $op = 'and';
616     }
617     return join(" $op ",@searchstrings);
618 }
619 sub build_simplequery {
620         my $element=shift;
621         my $record=shift;
622     my @searchstrings;
623     my ($index,$recorddata)=split /,/,$element;
624     if ($recorddata=~/(\d{3})(.*)/) {
625         my ($tag,$subfields) =($1,$2);
626         foreach my $field ($record->field($tag)){
627                   if (length($field->as_string("$subfields"))>0){
628               push @searchstrings,"$index:\"".$field->as_string("$subfields")."\"";
629                   }
630         }
631     }
632     my $QParser;
633     $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
634     my $op;
635     if ($QParser) {
636         $op = '&&';
637     } else {
638         $op = 'and';
639     }
640     return join(" $op ",@searchstrings);
641 }
642 sub report_item_errors {
643     my $biblionumber = shift;
644     my $errors_ref = shift;
645
646     foreach my $error (@{ $errors_ref }) {
647         next if !$error;
648         my $msg = "Item not added (bib $biblionumber, item tag #$error->{'item_sequence'}, barcode $error->{'item_barcode'}): ";
649         my $error_code = $error->{'error_code'};
650         $error_code =~ s/_/ /g;
651         $msg .= "$error_code $error->{'error_information'}";
652         print $msg, "\n";
653     }
654 }
655 sub printlog{
656         my $logelements=shift;
657     print $loghandle join( ";", map { defined $_ ? $_ : "" } @$logelements{qw<id op status>} ), "\n";
658 }
659 sub get_heading_fields{
660     my $headingfields;
661     if ($authtypes){
662         $headingfields=YAML::LoadFile($authtypes);
663         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
664         $debug && warn YAML::Dump($headingfields);
665     }
666     unless ($headingfields){
667         $headingfields=$dbh->selectall_hashref("SELECT auth_tag_to_report, authtypecode from auth_types",'auth_tag_to_report',{Slice=>{}});
668         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
669     }
670     return $headingfields;
671 }
672
673 =head1 NAME
674
675 bulkmarcimport.pl - Import bibliographic/authority records into Koha
676
677 =head1 USAGE
678
679  $ export KOHA_CONF=/etc/koha.conf
680  $ perl misc/migration_tools/bulkmarcimport.pl -d -commit 1000 \\
681     -file /home/jmf/koha.mrc -n 3000
682
683 =head1 WARNING
684
685 Don't use this script before you've entered and checked your MARC parameters
686 tables twice (or more!). Otherwise, the import won't work correctly and you
687 will get invalid data.
688
689 =head1 DESCRIPTION
690
691 =over
692
693 =item  B<-h>
694
695 This version/help screen
696
697 =item B<-b, -biblios>
698
699 Type of import: bibliographic records
700
701 =item B<-a, -authorities>
702
703 Type of import: authority records
704
705 =item B<-file>=I<FILE>
706
707 The I<FILE> to import
708
709 =item  B<-v>
710
711 Verbose mode. 1 means "some infos", 2 means "MARC dumping"
712
713 =item B<-fk>
714
715 Turn off foreign key checks during import.
716
717 =item B<-n>=I<NUMBER>
718
719 The I<NUMBER> of records to import. If missing, all the file is imported
720
721 =item B<-o, -offset>=I<NUMBER>
722
723 File offset before importing, ie I<NUMBER> of records to skip.
724
725 =item B<-commit>=I<NUMBER>
726
727 The I<NUMBER> of records to wait before performing a 'commit' operation
728
729 =item B<-l>
730
731 File logs actions done for each record and their status into file
732
733 =item B<-append>
734
735 If specified, data will be appended to the logfile. If not, the logfile will be erased for each execution.
736
737 =item B<-t, -test>
738
739 Test mode: parses the file, saying what it would do, but doing nothing.
740
741 =item B<-s>
742
743 Skip automatic conversion of MARC-8 to UTF-8.  This option is provided for
744 debugging.
745
746 =item B<-c>=I<CHARACTERISTIC>
747
748 The I<CHARACTERISTIC> MARC flavour. At the moment, only I<MARC21> and
749 I<UNIMARC> are supported. MARC21 by default.
750
751 =item B<-d>
752
753 Delete EVERYTHING related to biblio in koha-DB before import. Tables: biblio,
754 biblioitems, items
755
756 =item B<-m>=I<FORMAT>
757
758 Input file I<FORMAT>: I<MARCXML> or I<ISO2709> (defaults to ISO2709)
759
760 =item B<-authtypes>
761
762 file yamlfile with authoritiesTypes and distinguishable record field in order
763 to store the correct authtype
764
765 =item B<-yaml>
766
767 yaml file  format a yaml file with ids
768
769 =item B<-filter>
770
771 list of fields that will not be imported. Can be any from 000 to 999 or field,
772 subfield and subfield's matching value such as 200avalue
773
774 =item B<-insert>
775
776 if set, only insert when possible
777
778 =item B<-update>
779
780 if set, only updates (any biblio should have a matching record)
781
782 =item B<-all>
783
784 if set, do whatever is required
785
786 =item B<-k, -keepids>=<FIELD>
787
788 Field store ids in I<FIELD> (useful for authorities, where 001 contains the
789 authid for Koha, that can contain a very valuable info for authorities coming
790 from LOC or BNF. useless for biblios probably)
791
792 =item B<-match>=<FIELD>
793
794 I<FIELD> matchindex,fieldtomatch matchpoint to use to deduplicate fieldtomatch
795 can be either 001 to 999 or field and list of subfields as such 100abcde
796
797 =item B<-i,-isbn>
798
799 If set, a search will be done on isbn, and, if the same isbn is found, the
800 biblio is not added. It's another method to deduplicate.  B<-match> & B<-isbn>
801 can be both set.
802
803 =item B<-cleanisbn>
804
805 Clean ISBN fields from entering biblio records, ie removes hyphens. By default,
806 ISBN are cleaned. --nocleanisbn will keep ISBN unchanged.
807
808 =item B<-x>=I<TAG>
809
810 Source bib I<TAG> for reporting the source bib number
811
812 =item B<-y>=I<SUBFIELD>
813
814 Source I<SUBFIELD> for reporting the source bib number
815
816 =item B<-idmap>=I<FILE>
817
818 I<FILE> for the koha bib and source id
819
820 =item B<-keepids>
821
822 Store ids in 009 (useful for authorities, where 001 contains the authid for
823 Koha, that can contain a very valuable info for authorities coming from LOC or
824 BNF. useless for biblios probably)
825
826 =item B<-dedupbarcode>
827
828 If set, whenever a duplicate barcode is detected, it is removed and the attempt
829 to add the record is retried, thereby giving the record a blank barcode. This
830 is useful when something has set barcodes to be a biblio ID, or similar
831 (usually other software.)
832
833 =item B<-framework>
834
835 This is the code for the framework that the requested records will have attached
836 to them when they are created. If not specified, then the default framework
837 will be used.
838
839 =item B<-custom>=I<MODULE>
840
841 This parameter allows you to use a local module with a customize subroutine
842 that is called for each MARC record.
843 If no filename is passed, LocalChanges.pm is assumed to be in the
844 migration_tools subdirectory. You may pass an absolute file name or a file name
845 from the migration_tools directory.
846
847 =item B<-marcmodtemplate>=I<TEMPLATE>
848
849 This parameter allows you to specify the name of an existing MARC
850 modification template to apply as the MARC records are imported (these
851 templates are created in the "MARC modification templates" tool in Koha).
852 If not specified, no MARC modification templates are used (default).
853
854 =back
855
856 =cut
857