improvements to INSTALL.debian, adding Symbols for currencies adding \n to make bulkm...
[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 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 # Uncomment the line below and use MARC::File::XML again when it works better.
16 # -- thd
17 # use MARC::File::XML;
18 use MARC::Record;
19 use MARC::Batch;
20 use MARC::Charset;
21
22 # According to kados, an undocumented feature of setting MARC::Charset to 
23 # ignore_errors(1) is that errors are not ignored.  Instead of deleting the 
24 # whole subfield when a character does not translate properly from MARC8 into 
25 # UTF-8, just the problem characters are deleted.  This should solve at least 
26 # some of the fixme problems for fMARC8ToUTF8().
27
28 # Problems remain if there are MARC 21 records where 000/09 is set incorrectly. 
29 # -- thd.
30 # MARC::Charset->ignore_errors(1);
31
32 use C4::Context;
33 use C4::Biblio;
34 use C4::Items;
35 use Unicode::Normalize;
36 use Time::HiRes qw(gettimeofday);
37 use Getopt::Long;
38 binmode(STDOUT, ":utf8");
39
40 use Getopt::Long;
41
42 my ( $input_marc_file, $number) = ('',0);
43 my ($version, $delete, $test_parameter, $skip_marc8_conversion, $char_encoding, $verbose, $commit, $fk_off);
44
45 $|=1;
46
47 GetOptions(
48     'commit:f'    => \$commit,
49     'file:s'    => \$input_marc_file,
50     'n:f' => \$number,
51     'h' => \$version,
52     'd' => \$delete,
53     't' => \$test_parameter,
54     's' => \$skip_marc8_conversion,
55     'c:s' => \$char_encoding,
56     'v:s' => \$verbose,
57     'fk' => \$fk_off,
58 );
59
60 # FIXME:  Management of error conditions needed for record parsing problems
61 # and MARC8 character sets with mappings to Unicode not yet included in 
62 # MARC::Charset.  The real world rarity of these problems is not fully tested.
63 # Unmapped character sets will throw a warning currently and processing will 
64 # continue with the error condition.  A fairly trivial correction should 
65 # address some record parsing and unmapped character set problems but I need 
66 # time to implement a test and correction for undef subfields and revert to 
67 # MARC8 if mappings are missing. -- thd
68 sub fMARC8ToUTF8($$) {
69     my ($record) = shift;
70     my ($verbose) = shift;
71     
72     foreach my $field ($record->fields()) {
73         if ($field->is_control_field()) {
74             ; # do nothing -- control fields should not contain non-ASCII characters
75         } else {
76             my @subfieldsArray;
77             my $fieldName = $field->tag();
78             my $indicator1Value = $field->indicator(1);
79             my $indicator2Value = $field->indicator(2);
80             foreach my $subfield ($field->subfields()) {
81                 my $subfieldName = $subfield->[0];
82                 my $subfieldValue = $subfield->[1];
83                 my $utf8sf = MARC::Charset::marc8_to_utf8($subfieldValue);
84                 unless (defined $utf8sf) {
85                     # For now, we're being very strict about
86                     # error during the MARC8 conversion, so return
87                     # if there's a problem.
88                     return;
89                 }
90                 $subfieldValue = NFC($utf8sf); # Normalization Form C to assist
91                                                # some browswers (e.g., Firefox on OS X)
92                                                # that have issues with decomposed characters
93                                                # in certain fonts.
94     
95                 # Alas, MARC::Field::update() does not work correctly.
96                 ## push (@subfieldsArray, $subfieldName, $subfieldValue);
97     
98                 push @subfieldsArray, [$subfieldName, $subfieldValue];
99             }
100     
101             # Alas, MARC::Field::update() does not work correctly.
102             #
103             # The first instance in the field of a of a repeated subfield
104             # overwrites the content from later instances with the content
105             # from the first instance.
106             ## $field->update(@subfieldsArray);
107     
108             foreach my $subfieldRow(@subfieldsArray) {
109                 my $subfieldName = $subfieldRow->[0];
110                 $field->delete_subfields($subfieldName);
111             }
112             foreach my $subfieldRow(@subfieldsArray) {
113                 $field->add_subfields(@$subfieldRow);
114             }
115     
116             if ($verbose) {
117                 if ($verbose >= 2) {
118                     # Reading the indicator values again is not necessary.
119                     # They were not converted.
120                     # $indicator1Value = $field->indicator(1);
121                     # $indicator2Value = $field->indicator(2);
122                     # $indicator1Value =~ s/ /#/;
123                     # $indicator2Value =~ s/ /#/;
124                     print "\nCONVERTED TO UTF-8:\n" . $fieldName . ' ' .
125                             $indicator1Value .
126                     $indicator2Value;
127                     foreach my $subfield ($field->subfields()) {
128                         my $subfieldName = $subfield->[0];
129                         my $subfieldValue = $subfield->[1];
130                         print " \$" . $subfieldName . ' ' . $subfieldValue;
131                     }
132                 }
133             }
134             if ($verbose) {
135                 if ($verbose >= 2) {
136                     print "\n" if $verbose;
137                 }
138             }
139         }
140     }
141
142     # must set Leader/09 to 'a' to indicate that
143     # record is now in UTF-8
144     my $leader = $record->leader();
145     substr($leader, 9, 1) = 'a';
146     $record->leader($leader);
147
148     $record->encoding('UTF-8');
149     return 1;
150 }
151
152
153 if ($version || ($input_marc_file eq '')) {
154     print <<EOF
155 small script to import an iso2709 file into Koha.
156 parameters :
157 \th : this version/help screen
158 \tfile /path/to/file/to/dump : the file to import
159 \tv : verbose mode. 1 means "some infos", 2 means "MARC dumping"
160 \tfk : Turn off foreign key checks during import.
161 \tn : the number of records to import. If missing, all the file is imported
162 \tcommit : the number of records to wait before performing a 'commit' operation
163 \tt : test mode : parses the file, saying what he would do, but doing nothing.
164 \ts : skip automatic conversion of MARC-8 to UTF-8.  This option is 
165 \t    provided for debugging.
166 \tc : the characteristic MARC flavour. At the moment, only MARC21 and UNIMARC 
167 \tsupported. MARC21 by default.
168 \td : delete EVERYTHING related to biblio in koha-DB before import  :tables :
169 \t\tbiblio, \tbiblioitems,\titems
170 IMPORTANT : don't use this script before you've entered and checked your MARC parameters tables twice (or more!).
171 Otherwise, the import won't work correctly and you will get invalid data.
172
173 SAMPLE : 
174 \t\$ export KOHA_CONF=/etc/koha.conf
175 \t\$ perl misc/migration_tools/bulkmarcimport.pl -d -commit 1000 -file /home/jmf/koha.mrc -n 3000
176 EOF
177 ;#'
178 exit;
179 }
180
181 my $dbh = C4::Context->dbh;
182
183 # save the CataloguingLog property : we don't want to log a bulkmarcimport. It will slow the import & 
184 # will create problems in the action_logs table, that can't handle more than 1 entry per second per user.
185 my $CataloguingLog = C4::Context->preference('CataloguingLog');
186 $dbh->do("UPDATE systempreferences SET value=0 WHERE variable='CataloguingLog'");
187
188 if ($delete) {
189     print "deleting biblios\n";
190     $dbh->do("truncate biblio");
191     $dbh->do("truncate biblioitems");
192     $dbh->do("truncate items");
193     $dbh->do("truncate zebraqueue");
194 }
195 if ($fk_off) {
196         $dbh->do("SET FOREIGN_KEY_CHECKS = 0");
197 }
198 if ($test_parameter) {
199     print "TESTING MODE ONLY\n    DOING NOTHING\n===============\n";
200 }
201
202 my $marcFlavour = C4::Context->preference('marcflavour') || 'MARC21';
203
204 print "Characteristic MARC flavour: $marcFlavour\n" if $verbose;
205 my $starttime = gettimeofday;
206 my $batch = MARC::Batch->new( 'USMARC', $input_marc_file );
207 $batch->warnings_off();
208 $batch->strict_off();
209 my $i=0;
210 my $commitnum = 50;
211
212 if ($commit) {
213
214 $commitnum = $commit;
215
216 }
217
218 $dbh->{AutoCommit} = 0;
219 RECORD: while ( my $record = $batch->next() ) {
220     $i++;
221     print ".";
222     print "\r$i" unless $i % 100;
223
224     if ($record->encoding() eq 'MARC-8' and not $skip_marc8_conversion) {
225         unless (fMARC8ToUTF8($record, $verbose)) {
226             warn "ERROR: failed to perform character conversion for record $i\n";
227             next RECORD;            
228         }
229     }
230
231     unless ($test_parameter) {
232         my ( $biblionumber, $biblioitemnumber, $itemnumbers_ref, $errors_ref );
233         eval { ( $biblionumber, $biblioitemnumber ) = AddBiblio($record, '', { defer_marc_save => 1 }) };
234         if ( $@ ) {
235             warn "ERROR: Adding biblio $biblionumber failed: $@\n";
236             next RECORD;
237         } 
238         eval { ( $itemnumbers_ref, $errors_ref ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
239         if ( $@ ) {
240             warn "ERROR: Adding items to bib $biblionumber failed: $@\n";
241             # if we failed because of an exception, assume that 
242             # the MARC columns in biblioitems were not set.
243             ModBiblioMarc( $record, $biblionumber, '' );
244             next RECORD;
245         } 
246         if ($#{ $errors_ref } > -1) { 
247             report_item_errors($biblionumber, $errors_ref);
248         }
249
250         $dbh->commit() if (0 == $i % $commitnum);
251     }
252     last if $i == $number;
253 }
254 $dbh->commit();
255
256
257 if ($fk_off) {
258         $dbh->do("SET FOREIGN_KEY_CHECKS = 1");
259 }
260
261 # restore CataloguingLog
262 $dbh->do("UPDATE systempreferences SET value=$CataloguingLog WHERE variable='CataloguingLog'");
263
264 my $timeneeded = gettimeofday - $starttime;
265 print "\n$i MARC records done in $timeneeded seconds\n";
266
267 exit 0;
268
269 sub report_item_errors {
270     my $biblionumber = shift;
271     my $errors_ref = shift;
272
273     foreach my $error (@{ $errors_ref }) {
274         my $msg = "Item not added (bib $biblionumber, item tag #$error->{'item_sequence'}, barcode $error->{'item_barcode'}): ";
275         my $error_code = $error->{'error_code'};
276         $error_code =~ s/_/ /g;
277         $msg .= "$error_code $error->{'error_information'}";
278         print $msg, "\n";
279     }
280 }