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