2 # Import an iso2709 file into Koha 3
7 # find Koha's Perl modules
8 # test carefully before changing this
10 eval { require "$FindBin::Bin/../kohalib.pl" };
14 use MARC::File::USMARC;
26 use C4::MarcModificationTemplates;
29 use Unicode::Normalize;
30 use Time::HiRes qw(gettimeofday);
36 use Koha::SearchEngine;
37 use Koha::SearchEngine::Search;
39 use open qw( :std :encoding(UTF-8) );
40 binmode( STDOUT, ":encoding(UTF-8)" );
41 my ( $input_marc_file, $number, $offset) = ('',0,0);
42 my ($version, $delete, $test_parameter, $skip_marc8_conversion, $char_encoding, $verbose, $commit, $fk_off,$format,$biblios,$authorities,$keepids,$match, $isbn_check, $logfile);
43 my ( $insert, $filters, $update, $all, $yamlfile, $authtypes, $append );
45 my ($sourcetag,$sourcesubfield,$idmapfl, $dedup_barcode);
48 my $marc_mod_template = '';
49 my $marc_mod_template_id = -1;
54 'commit:f' => \$commit,
55 'file:s' => \$input_marc_file,
57 'o|offset:f' => \$offset,
60 't|test' => \$test_parameter,
61 's' => \$skip_marc8_conversion,
62 'c:s' => \$char_encoding,
68 'k|keepids:s' => \$keepids,
69 'b|biblios' => \$biblios,
70 'a|authorities' => \$authorities,
71 'authtypes:s' => \$authtypes,
72 'filter=s@' => \$filters,
76 'match=s@' => \$match,
77 'i|isbn' => \$isbn_check,
79 'y:s' => \$sourcesubfield,
80 'idmap:s' => \$idmapfl,
81 'cleanisbn!' => \$cleanisbn,
82 'yaml:s' => \$yamlfile,
83 'dedupbarcode' => \$dedup_barcode,
84 'framework=s' => \$framework,
85 'custom:s' => \$localcust,
86 'marcmodtemplate:s' => \$marc_mod_template,
88 $biblios ||= !$authorities;
90 my $writemode = ($append) ? "a" : "w";
92 pod2usage( -msg => "\nYou must specify either --biblios or --authorities, not both.\n", -exitval ) if $biblios && $authorities;
99 if ($version || ($input_marc_file eq '')) {
100 pod2usage( -verbose => 2 );
104 if(defined $localcust) { #local customize module
106 $localcust= $localcust||'LocalChanges'; #default name
107 $localcust=~ s/^.*\/([^\/]+)$/$1/; #extract file name only
108 $localcust=~ s/\.pm$//; #remove extension
109 my $fqcust= $FindBin::Bin."/$localcust.pm"; #try migration_tools dir
114 print "WARNING: customize module $localcust.pm not found!\n";
118 require $localcust if $localcust;
119 $localcust=\&customize if $localcust;
122 if($marc_mod_template ne '') {
123 my @templates = GetModificationTemplates();
124 foreach my $this_template (@templates) {
125 if($this_template->{'name'} eq $marc_mod_template) {
126 if($marc_mod_template_id < 0) {
127 $marc_mod_template_id = $this_template->{'template_id'};
129 print "WARNING: MARC modification template name " .
130 "'$marc_mod_template' matches multiple templates. " .
131 "Please rename these templates\n";
136 if($marc_mod_template_id < 0) {
137 die "Can't located MARC modification template '$marc_mod_template'\n";
141 my $dbh = C4::Context->dbh;
142 my $heading_fields=get_heading_fields();
144 if (defined $idmapfl) {
145 open(IDMAP,">$idmapfl") or die "cannot open $idmapfl \n";
148 if ((not defined $sourcesubfield) && (not defined $sourcetag)){
154 # Disable logging for the biblios and authorities import operation. It would unnecessarily
157 # Disable the syspref cache so we can change logging settings
158 C4::Context->disable_syspref_cache();
159 # Save current CataloguingLog and AuthoritiesLog sysprefs values
160 my $CataloguingLog = C4::Context->preference( 'CataloguingLog' );
161 my $AuthoritiesLog = C4::Context->preference( 'AuthoritiesLog' );
162 # Disable logging for both
163 C4::Context->set_preference( 'CataloguingLog', 0 );
164 C4::Context->set_preference( 'AuthoritiesLog', 0 );
167 $dbh->do("SET FOREIGN_KEY_CHECKS = 0");
173 print "deleting biblios\n";
174 $dbh->do("truncate biblio");
175 $dbh->do("truncate biblioitems");
176 $dbh->do("truncate items");
179 print "deleting authorities\n";
180 $dbh->do("truncate auth_header");
182 $dbh->do("truncate zebraqueue");
187 if ($test_parameter) {
188 print "TESTING MODE ONLY\n DOING NOTHING\n===============\n";
191 my $marcFlavour = C4::Context->preference('marcflavour') || 'MARC21';
193 print "Characteristic MARC flavour: $marcFlavour\n" if $verbose;
194 my $starttime = gettimeofday;
196 my $fh = IO::File->new($input_marc_file); # don't let MARC::Batch open the file, as it applies the ':utf8' IO layer
197 if (defined $format && $format =~ /XML/i) {
198 # ugly hack follows -- MARC::File::XML, when used by MARC::Batch,
199 # appears to try to convert incoming XML records from MARC-8
200 # to UTF-8. Setting the BinaryEncoding key turns that off
201 # TODO: see what happens to ISO-8859-1 XML files.
202 # TODO: determine if MARC::Batch can be fixed to handle
203 # XML records properly -- it probably should be
204 # be using a proper push or pull XML parser to
205 # extract the records, not using regexes to look
206 # for <record>.*</record>.
207 $MARC::File::XML::_load_args{BinaryEncoding} = 'utf-8';
208 my $recordformat= ($marcFlavour eq "MARC21"?"USMARC":uc($marcFlavour));
209 #UNIMARC Authorities have a different way to manage encoding than UNIMARC biblios.
210 $recordformat=$recordformat."AUTH" if ($authorities and $marcFlavour ne "MARC21");
211 $MARC::File::XML::_load_args{RecordFormat} = $recordformat;
212 $batch = MARC::Batch->new( 'XML', $fh );
214 $batch = MARC::Batch->new( 'USMARC', $fh );
216 $batch->warnings_off();
217 $batch->strict_off();
219 my $commitnum = $commit ? $commit : 50;
224 print "Skipping file offset: $offset records\n";
225 $batch->next() while ($offset--);
228 my ($tagid,$subfieldid);
233 ( $tagid, $subfieldid ) =
234 GetMarcFromKohaField( "biblio.biblionumber", $framework );
238 # the SQL query to search on isbn
239 my $sth_isbn = $dbh->prepare("SELECT biblionumber,biblioitemnumber FROM biblioitems WHERE isbn=?");
241 $dbh->{AutoCommit} = 0;
244 $loghandle= IO::File->new($logfile, $writemode) ;
245 print $loghandle "id;operation;status\n";
248 my $searcher = Koha::SearchEngine::Search->new(
252 ? $Koha::SearchEngine::AUTHORITIES_INDEX
253 : $Koha::SearchEngine::BIBLIOS_INDEX
261 eval { $record = $batch->next() };
263 print "Bad MARC record $i: $@ skipped\n";
264 # FIXME - because MARC::Batch->next() combines grabbing the next
265 # blob and parsing it into one operation, a correctable condition
266 # such as a MARC-8 record claiming that it's UTF-8 can't be recovered
267 # from because we don't have access to the original blob. Note
268 # that the staging import can deal with this condition (via
269 # C4::Charset::MarcToUTF8Record) because it doesn't use MARC::Batch.
272 # skip if we get an empty record (that is MARC valid, but will result in AddBiblio failure
273 last unless ( $record );
275 if( ($verbose//1)==1 ) { #no dot for verbose==2
276 print "." . ( $i % 100==0 ? "\n$i" : '' );
279 # transcode the record to UTF8 if needed & applicable.
280 if ($record->encoding() eq 'MARC-8' and not $skip_marc8_conversion) {
281 # FIXME update condition
282 my ($guessed_charset, $charset_errors);
283 ($record, $guessed_charset, $charset_errors) = MarcToUTF8Record($record, $marcFlavour.(($authorities and $marcFlavour ne "MARC21")?'AUTH':''));
284 if ($guessed_charset eq 'failed') {
285 warn "ERROR: failed to perform character conversion for record $i\n";
289 SetUTF8Flag($record);
290 if($marc_mod_template_id > 0) {
291 print "Modifying MARC\n";
292 ModifyRecordWithTemplate( $marc_mod_template_id, $record );
294 &$localcust($record) if $localcust;
296 # remove trailing - in isbn (only for biblios, of course)
297 if ($biblios && $cleanisbn) {
298 my $tag = $marcFlavour eq 'UNIMARC' ? '010' : '020';
299 my $field = $record->field($tag);
300 my $isbn = $field && $field->subfield('a');
303 $field->update('a' => $isbn);
307 # search for duplicates (based on Local-number)
309 $originalid = GetRecordId( $record, $tagid, $subfieldid );
312 my $query = build_query( $match, $record );
313 my $server = ( $authorities ? 'authorityserver' : 'biblioserver' );
314 $debug && warn $query;
315 my ( $error, $results, $totalhits ) = $searcher->simple_search_compat( $query, 0, 3, [$server] );
316 # changed to warn so able to continue with one broken record
317 if ( defined $error ) {
318 warn "unable to search the database for duplicates : $error";
319 printlog( { id => $id || $originalid || $match, op => "match", status => "ERROR" } ) if ($logfile);
322 $debug && warn "$query $server : $totalhits";
323 if ( $results && scalar(@$results) == 1 ) {
324 my $marcrecord = C4::Search::new_record_from_zebra( $server, $results->[0] );
325 SetUTF8Flag($marcrecord);
326 $id = GetRecordId( $marcrecord, $tagid, $subfieldid );
327 if ( $authorities && $marcFlavour ) {
328 #Skip if authority in database is the same as the on in database
329 if ( $marcrecord->field('005') && $record->field('005') &&
330 $marcrecord->field('005')->data && $record->field('005')->data &&
331 $marcrecord->field('005')->data >= $record->field('005')->data ) {
333 $yamlhash->{$originalid}->{'authid'} = $id;
335 # we recover all subfields of the heading authorities
337 foreach my $field ( $marcrecord->field("2..") ) {
338 push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
340 $yamlhash->{$originalid}->{'subfields'} = \@subfields;
345 } elsif ( $results && scalar(@$results) > 1 ) {
346 $debug && warn "more than one match for $query";
348 $debug && warn "nomatch for $query";
351 if ($keepids && $originalid) {
353 if ( length($keepids) == 3 ) {
354 $storeidfield = MARC::Field->new( $keepids, $originalid );
356 $storeidfield = MARC::Field->new( substr( $keepids, 0, 3 ), "", "", substr( $keepids, 3, 1 ), $originalid );
358 $record->insert_fields_ordered($storeidfield);
359 $record->delete_field( $record->field($tagid) );
361 foreach my $stringfilter (@$filters) {
362 if ( length($stringfilter) == 3 ) {
363 foreach my $field ( $record->field($stringfilter) ) {
364 $record->delete_field($field);
365 $debug && warn "removed : ", $field->as_string;
367 } elsif ($stringfilter =~ /([0-9]{3})([a-z0-9])(.*)/) {
369 my $removesubfield = $2;
370 my $removematch = $3;
371 if ( ( $removetag > "010" ) && $removesubfield ) {
372 foreach my $field ( $record->field($removetag) ) {
373 $field->delete_subfield( code => "$removesubfield", match => $removematch );
374 $debug && warn "Potentially removed : ", $field->subfield($removesubfield);
379 unless ($test_parameter) {
381 use C4::AuthoritiesMarc;
382 my $authtypecode=GuessAuthTypeCode($record, $heading_fields);
383 my $authid= ($id?$id:GuessAuthId($record));
384 if ($authid && GetAuthority($authid) && $update ){
385 ## Authority has an id and is in database : Replace
386 eval { ( $authid ) = ModAuthority($authid,$record, $authtypecode) };
388 warn "Problem with authority $authid Cannot Modify";
389 printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ERROR"}) if ($logfile);
392 printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ok"}) if ($logfile);
395 elsif (defined $authid) {
396 ## An authid is defined but no authority in database : add
397 eval { ( $authid ) = AddAuthority($record,$authid, $authtypecode) };
399 warn "Problem with authority $authid Cannot Add ".$@;
400 printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
403 printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
407 ## True insert in database
408 eval { ( $authid ) = AddAuthority($record,"", $authtypecode) };
410 warn "Problem with authority $authid Cannot Add".$@;
411 printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
414 printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
418 $yamlhash->{$originalid}->{'authid'} = $authid;
420 foreach my $field ( $record->field("2..") ) {
421 push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
423 $yamlhash->{$originalid}->{'subfields'} = \@subfields;
427 my ( $biblionumber, $biblioitemnumber, $itemnumbers_ref, $errors_ref );
429 # check for duplicate, based on ISBN (skip it if we already have found a duplicate with match parameter
430 if (!$biblionumber && $isbn_check && $isbn) {
431 # warn "search ISBN : $isbn";
432 $sth_isbn->execute($isbn);
433 ($biblionumber,$biblioitemnumber) = $sth_isbn->fetchrow;
435 if (defined $idmapfl) {
436 if ($sourcetag < "010"){
437 if ($record->field($sourcetag)){
438 my $source = $record->field($sourcetag)->data();
439 printf(IDMAP "%s|%s\n",$source,$biblionumber);
442 my $source=$record->subfield($sourcetag,$sourcesubfield);
443 printf(IDMAP "%s|%s\n",$source,$biblionumber);
446 # create biblio, unless we already have it ( either match or isbn )
449 $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
452 eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
454 warn "ERROR: Edit biblio $biblionumber failed: $@\n";
455 printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
458 printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ok" } ) if ($logfile);
461 printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "warning : already in database" } ) if ($logfile);
465 eval { ( $biblionumber, $biblioitemnumber ) = AddBiblio( $record, '', { defer_marc_save => 1 } ) };
467 warn "ERROR: Adding biblio $biblionumber failed: $@\n";
468 printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ERROR" } ) if ($logfile);
471 printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ok" } ) if ($logfile);
474 printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "warning : not in database" } ) if ($logfile);
477 eval { ( $itemnumbers_ref, $errors_ref ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
478 my $error_adding = $@;
479 # Work on a clone so that if there are real errors, we can maybe
481 my $clone_record = $record->clone();
482 C4::Biblio::_strip_item_fields($clone_record, '');
483 # This sets the marc fields if there was an error, and also calls
485 ModBiblioMarc( $clone_record, $biblionumber, $framework );
486 if ( $error_adding ) {
487 warn "ERROR: Adding items to bib $biblionumber failed: $error_adding";
488 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
489 # if we failed because of an exception, assume that
490 # the MARC columns in biblioitems were not set.
494 printlog({id=>$id||$originalid||$biblionumber, op=>"insert",status=>"ok"}) if ($logfile);
496 if ($dedup_barcode && grep { exists $_->{error_code} && $_->{error_code} eq 'duplicate_barcode' } @$errors_ref) {
497 # Find the record called 'barcode'
498 my ($tag, $sub) = C4::Biblio::GetMarcFromKohaField('items.barcode', $framework);
499 # Now remove any items that didn't have a duplicate_barcode error,
500 # erase the barcodes on items that did, and re-add those items.
502 foreach my $i (0 .. $#{$errors_ref}) {
503 my $ref = $errors_ref->[$i];
504 if ($ref && ($ref->{error_code} eq 'duplicate_barcode')) {
505 $dupes{$ref->{item_sequence}} = 1;
506 # Delete the error message because we're going to
508 delete $errors_ref->[$i];
512 foreach my $field ($record->field($tag)) {
515 # Here we remove the barcode
516 $field->delete_subfield(code => $sub);
518 # otherwise we delete the field because we don't want
520 $record->delete_fields($field);
523 # Now re-add the record as before, adding errors to the prev list
525 eval { ( $itemnumbers_ref, $more_errors ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
527 warn "ERROR: Adding items to bib $biblionumber failed: $@\n";
528 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
529 # if we failed because of an exception, assume that
530 # the MARC columns in biblioitems were not set.
531 ModBiblioMarc( $record, $biblionumber, $framework );
534 printlog({id=>$id||$originalid||$biblionumber, op=>"insert",status=>"ok"}) if ($logfile);
536 push @$errors_ref, @{ $more_errors };
538 if ($#{ $errors_ref } > -1) {
539 report_item_errors($biblionumber, $errors_ref);
541 $yamlhash->{$originalid} = $biblionumber if ($yamlfile);
543 $dbh->commit() if (0 == $i % $commitnum);
545 print $record->as_formatted()."\n" if ($verbose//0)==2;
546 last if $i == $number;
549 $dbh->{AutoCommit} = 1;
553 $dbh->do("SET FOREIGN_KEY_CHECKS = 1");
556 # Restore CataloguingLog
557 C4::Context->set_preference( 'CataloguingLog', $CataloguingLog );
558 # Restore AuthoritiesLog
559 C4::Context->set_preference( 'AuthoritiesLog', $AuthoritiesLog );
561 my $timeneeded = gettimeofday - $starttime;
562 print "\n$i MARC records done in $timeneeded seconds\n";
564 print $loghandle "file : $input_marc_file\n";
565 print $loghandle "$i MARC records done in $timeneeded seconds\n";
569 open my $yamlfileout, q{>}, "$yamlfile" or die "cannot open $yamlfile \n";
570 print $yamlfileout Dump($yamlhash);
575 my $marcrecord=shift;
580 return $marcrecord->field($tag)->data() if $marcrecord->field($tag);
583 if ($marcrecord->field($tag)){
584 return $marcrecord->subfield($tag,$subfield);
593 foreach my $matchingpoint (@$match){
594 my $string = build_simplequery($matchingpoint,$record);
595 push @searchstrings,$string if (length($string)>0);
598 $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
605 return join(" $op ",@searchstrings);
607 sub build_simplequery {
611 my ($index,$recorddata)=split /,/,$element;
612 if ($recorddata=~/(\d{3})(.*)/) {
613 my ($tag,$subfields) =($1,$2);
614 foreach my $field ($record->field($tag)){
615 if (length($field->as_string("$subfields"))>0){
616 push @searchstrings,"$index:\"".$field->as_string("$subfields")."\"";
621 $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
628 return join(" $op ",@searchstrings);
630 sub report_item_errors {
631 my $biblionumber = shift;
632 my $errors_ref = shift;
634 foreach my $error (@{ $errors_ref }) {
636 my $msg = "Item not added (bib $biblionumber, item tag #$error->{'item_sequence'}, barcode $error->{'item_barcode'}): ";
637 my $error_code = $error->{'error_code'};
638 $error_code =~ s/_/ /g;
639 $msg .= "$error_code $error->{'error_information'}";
644 my $logelements=shift;
645 print $loghandle join( ";", map { defined $_ ? $_ : "" } @$logelements{qw<id op status>} ), "\n";
647 sub get_heading_fields{
650 $headingfields=YAML::LoadFile($authtypes);
651 $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
652 $debug && warn YAML::Dump($headingfields);
654 unless ($headingfields){
655 $headingfields=$dbh->selectall_hashref("SELECT auth_tag_to_report, authtypecode from auth_types",'auth_tag_to_report',{Slice=>{}});
656 $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
658 return $headingfields;
663 bulkmarcimport.pl - Import bibliographic/authority records into Koha
667 $ export KOHA_CONF=/etc/koha.conf
668 $ perl misc/migration_tools/bulkmarcimport.pl -d -commit 1000 \\
669 -file /home/jmf/koha.mrc -n 3000
673 Don't use this script before you've entered and checked your MARC parameters
674 tables twice (or more!). Otherwise, the import won't work correctly and you
675 will get invalid data.
683 This version/help screen
685 =item B<-b, -biblios>
687 Type of import: bibliographic records
689 =item B<-a, -authorities>
691 Type of import: authority records
693 =item B<-file>=I<FILE>
695 The I<FILE> to import
699 Verbose mode. 1 means "some infos", 2 means "MARC dumping"
703 Turn off foreign key checks during import.
705 =item B<-n>=I<NUMBER>
707 The I<NUMBER> of records to import. If missing, all the file is imported
709 =item B<-o, -offset>=I<NUMBER>
711 File offset before importing, ie I<NUMBER> of records to skip.
713 =item B<-commit>=I<NUMBER>
715 The I<NUMBER> of records to wait before performing a 'commit' operation
719 File logs actions done for each record and their status into file
723 If specified, data will be appended to the logfile. If not, the logfile will be erased for each execution.
727 Test mode: parses the file, saying what it would do, but doing nothing.
731 Skip automatic conversion of MARC-8 to UTF-8. This option is provided for
734 =item B<-c>=I<CHARACTERISTIC>
736 The I<CHARACTERISTIC> MARC flavour. At the moment, only I<MARC21> and
737 I<UNIMARC> are supported. MARC21 by default.
741 Delete EVERYTHING related to biblio in koha-DB before import. Tables: biblio,
744 =item B<-m>=I<FORMAT>
746 Input file I<FORMAT>: I<MARCXML> or I<ISO2709> (defaults to ISO2709)
750 file yamlfile with authoritiesTypes and distinguishable record field in order
751 to store the correct authtype
755 yaml file format a yaml file with ids
759 list of fields that will not be imported. Can be any from 000 to 999 or field,
760 subfield and subfield's matching value such as 200avalue
764 if set, only insert when possible
768 if set, only updates (any biblio should have a matching record)
772 if set, do whatever is required
774 =item B<-k, -keepids>=<FIELD>
776 Field store ids in I<FIELD> (useful for authorities, where 001 contains the
777 authid for Koha, that can contain a very valuable info for authorities coming
778 from LOC or BNF. useless for biblios probably)
780 =item B<-match>=<FIELD>
782 I<FIELD> matchindex,fieldtomatch matchpoint to use to deduplicate fieldtomatch
783 can be either 001 to 999 or field and list of subfields as such 100abcde
787 If set, a search will be done on isbn, and, if the same isbn is found, the
788 biblio is not added. It's another method to deduplicate. B<-match> & B<-isbn>
793 Clean ISBN fields from entering biblio records, ie removes hyphens. By default,
794 ISBN are cleaned. --nocleanisbn will keep ISBN unchanged.
798 Source bib I<TAG> for reporting the source bib number
800 =item B<-y>=I<SUBFIELD>
802 Source I<SUBFIELD> for reporting the source bib number
804 =item B<-idmap>=I<FILE>
806 I<FILE> for the koha bib and source id
810 Store ids in 009 (useful for authorities, where 001 contains the authid for
811 Koha, that can contain a very valuable info for authorities coming from LOC or
812 BNF. useless for biblios probably)
814 =item B<-dedupbarcode>
816 If set, whenever a duplicate barcode is detected, it is removed and the attempt
817 to add the record is retried, thereby giving the record a blank barcode. This
818 is useful when something has set barcodes to be a biblio ID, or similar
819 (usually other software.)
823 This is the code for the framework that the requested records will have attached
824 to them when they are created. If not specified, then the default framework
827 =item B<-custom>=I<MODULE>
829 This parameter allows you to use a local module with a customize subroutine
830 that is called for each MARC record.
831 If no filename is passed, LocalChanges.pm is assumed to be in the
832 migration_tools subdirectory. You may pass an absolute file name or a file name
833 from the migration_tools directory.
835 =item B<-marcmodtemplate>=I<TEMPLATE>
837 This parameter allows you to specify the name of an existing MARC
838 modification template to apply as the MARC records are imported (these
839 templates are created in the "MARC modification templates" tool in Koha).
840 If not specified, no MARC modification templates are used (default).