Changes to bulkmarcimport.pl
[koha.git] / misc / migration_tools / bulkmarcimport.pl
1 #!/usr/bin/perl
2 # Import an iso2709 file into Koha 3
3
4 use strict;
5 #use warnings;
6 #use diagnostics;
7 BEGIN {
8     # find Koha's Perl modules
9     # test carefully before changing this
10     use FindBin;
11     eval { require "$FindBin::Bin/../kohalib.pl" };
12 }
13
14 # Koha modules used
15 use MARC::File::USMARC;
16 use MARC::File::XML;
17 use MARC::Record;
18 use MARC::Batch;
19 use MARC::Charset;
20
21 use C4::Context;
22 use C4::Biblio;
23 use C4::Charset;
24 use C4::Items;
25 use Unicode::Normalize;
26 use Time::HiRes qw(gettimeofday);
27 use Getopt::Long;
28 use IO::File;
29
30 binmode(STDOUT, ":utf8");
31
32 my ( $input_marc_file, $number, $offset) = ('',0,0);
33 my ($version, $delete, $test_parameter, $skip_marc8_conversion, $char_encoding, $verbose, $commit, $fk_off,$format);
34 my ($sourcetag,$sourcesubfield,$idmapfl);
35
36 $|=1;
37
38 GetOptions(
39     'commit:f'    => \$commit,
40     'file:s'    => \$input_marc_file,
41     'n:f' => \$number,
42     'o|offset:f' => \$offset,
43     'h' => \$version,
44     'd' => \$delete,
45     't' => \$test_parameter,
46     's' => \$skip_marc8_conversion,
47     'c:s' => \$char_encoding,
48     'v:s' => \$verbose,
49     'fk' => \$fk_off,
50     'm:s' => \$format,
51     'x:s' => \$sourcetag,
52     'y:s' => \$sourcesubfield,
53     'idmap:s' => \$idmapfl,
54 );
55
56 if ($version || ($input_marc_file eq '')) {
57     print <<EOF
58 Small script to import bibliographic records into Koha.
59
60 Parameters:
61   h      this version/help screen
62   file   /path/to/file/to/dump: the file to import
63   v      verbose mode. 1 means "some infos", 2 means "MARC dumping"
64   fk     Turn off foreign key checks during import.
65   n      the number of records to import. If missing, all the file is imported
66   o      file offset before importing, ie number of records to skip.
67   commit the number of records to wait before performing a 'commit' operation
68   t      test mode: parses the file, saying what he would do, but doing nothing.
69   s      skip automatic conversion of MARC-8 to UTF-8.  This option is 
70          provided for debugging.
71   c      the characteristic MARC flavour. At the moment, only MARC21 and 
72          UNIMARC are supported. MARC21 by default.
73   d      delete EVERYTHING related to biblio in koha-DB before import. Tables:
74          biblio, biblioitems, titems
75   m      format, MARCXML or ISO2709 (defaults to ISO2709)
76   x      source bib tag for reporting the source bib number
77   y      source subfield for reporting the source bib number
78   idmap  file for the koha bib and source id
79   
80 IMPORTANT: don't use this script before you've entered and checked your MARC 
81            parameters tables twice (or more!). Otherwise, the import won't work 
82            correctly and you will get invalid data.
83
84 SAMPLE: 
85   \$ export KOHA_CONF=/etc/koha.conf
86   \$ perl misc/migration_tools/bulkmarcimport.pl -d -commit 1000 \\
87     -file /home/jmf/koha.mrc -n 3000
88 EOF
89 ;#'
90 exit;
91 }
92
93 if (defined $idmapfl) {
94   open(IDMAP,">$idmapfl") or die "cannot open $idmapfl \n";
95 }
96
97 if ((not defined $sourcesubfield) && (not defined $sourcetag)){
98   $sourcetag="910";
99   $sourcesubfield="a";
100 }
101
102 my $dbh = C4::Context->dbh;
103
104 # save the CataloguingLog property : we don't want to log a bulkmarcimport. It will slow the import & 
105 # will create problems in the action_logs table, that can't handle more than 1 entry per second per user.
106 my $CataloguingLog = C4::Context->preference('CataloguingLog');
107 $dbh->do("UPDATE systempreferences SET value=0 WHERE variable='CataloguingLog'");
108
109 if ($fk_off) {
110         $dbh->do("SET FOREIGN_KEY_CHECKS = 0");
111 }
112
113
114 if ($delete) {
115     print "deleting biblios\n";
116     $dbh->do("truncate biblio");
117     $dbh->do("truncate biblioitems");
118     $dbh->do("truncate items");
119     $dbh->do("truncate zebraqueue");
120 }
121
122
123
124 if ($test_parameter) {
125     print "TESTING MODE ONLY\n    DOING NOTHING\n===============\n";
126 }
127
128 my $marcFlavour = C4::Context->preference('marcflavour') || 'MARC21';
129
130 print "Characteristic MARC flavour: $marcFlavour\n" if $verbose;
131 my $starttime = gettimeofday;
132 my $batch;
133 my $fh = IO::File->new($input_marc_file); # don't let MARC::Batch open the file, as it applies the ':utf8' IO layer
134 if ($format =~ /XML/i) {
135     # ugly hack follows -- MARC::File::XML, when used by MARC::Batch,
136     # appears to try to convert incoming XML records from MARC-8
137     # to UTF-8.  Setting the BinaryEncoding key turns that off
138     # TODO: see what happens to ISO-8859-1 XML files.
139     # TODO: determine if MARC::Batch can be fixed to handle
140     #       XML records properly -- it probably should be
141     #       be using a proper push or pull XML parser to
142     #       extract the records, not using regexes to look
143     #       for <record>.*</record>.
144     $MARC::File::XML::_load_args{BinaryEncoding} = 'utf-8';
145     $batch = MARC::Batch->new( 'XML', $fh );
146 } else {
147     $batch = MARC::Batch->new( 'USMARC', $fh );
148 }
149 $batch->warnings_off();
150 $batch->strict_off();
151 my $i=0;
152 my $commitnum = $commit ? $commit : 50;
153
154
155 # Skip file offset
156 if ( $offset ) {
157     print "Skipping file offset: $offset records\n";
158     $batch->next() while ($offset--);
159 }
160
161 $dbh->{AutoCommit} = 0;
162 RECORD: while (  ) {
163     my $record;
164     eval { $record = $batch->next() };
165     if ( $@ ) {
166         print "Bad MARC record: skipped\n";
167         next;
168     }
169     last unless ( $record );
170     $i++;
171     print ".";
172     print "\r$i" unless $i % 100;
173     
174     if ($record->encoding() eq 'MARC-8' and not $skip_marc8_conversion) {
175         # FIXME update condition
176         my ($guessed_charset, $charset_errors);
177         ($record, $guessed_charset, $charset_errors) = MarcToUTF8Record($record, $marcFlavour);
178         if ($guessed_charset eq 'failed') {
179             warn "ERROR: failed to perform character conversion for record $i\n";
180             next RECORD;            
181         }
182     }
183
184     unless ($test_parameter) {
185         my ( $biblionumber, $biblioitemnumber, $itemnumbers_ref, $errors_ref );
186         eval { ( $biblionumber, $biblioitemnumber ) = AddBiblio($record, '', { defer_marc_save => 1 }) };
187         if ( $@ ) {
188             warn "ERROR: Adding biblio $biblionumber failed: $@\n";
189             next RECORD;
190         } 
191         if (defined $idmapfl) {
192           if ($sourcetag < "010"){
193             if ($record->field($sourcetag)){
194               my $source = $record->field($sourcetag)->data();
195               printf(IDMAP "%s|%s\n",$source,$biblionumber);
196             }
197           } else {
198             my $source=$record->subfield($sourcetag,$sourcesubfield);
199             printf(IDMAP "%s|%s\n",$source,$biblionumber);
200           }
201        }
202        
203         eval { ( $itemnumbers_ref, $errors_ref ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
204         if ( $@ ) {
205             warn "ERROR: Adding items to bib $biblionumber failed: $@\n";
206             # if we failed because of an exception, assume that 
207             # the MARC columns in biblioitems were not set.
208             ModBiblioMarc( $record, $biblionumber, '' );
209             next RECORD;
210         } 
211         if ($#{ $errors_ref } > -1) { 
212             report_item_errors($biblionumber, $errors_ref);
213         }
214
215         $dbh->commit() if (0 == $i % $commitnum);
216     }
217     last if $i == $number;
218 }
219 $dbh->commit();
220
221
222 if ($fk_off) {
223         $dbh->do("SET FOREIGN_KEY_CHECKS = 1");
224 }
225
226 # restore CataloguingLog
227 $dbh->do("UPDATE systempreferences SET value=$CataloguingLog WHERE variable='CataloguingLog'");
228
229 my $timeneeded = gettimeofday - $starttime;
230 print "\n$i MARC records done in $timeneeded seconds\n";
231
232 exit 0;
233
234 sub report_item_errors {
235     my $biblionumber = shift;
236     my $errors_ref = shift;
237
238     foreach my $error (@{ $errors_ref }) {
239         my $msg = "Item not added (bib $biblionumber, item tag #$error->{'item_sequence'}, barcode $error->{'item_barcode'}): ";
240         my $error_code = $error->{'error_code'};
241         $error_code =~ s/_/ /g;
242         $msg .= "$error_code $error->{'error_information'}";
243         print $msg, "\n";
244     }
245 }