f15dd16a99bf29c1b609f6c77c80a546407f301f
[sitka/sitka-tools.git] / marc_export_custom / marc_export_custom
1 #!/usr/bin/perl
2 # vim:et:sw=4:ts=4:
3 use strict;
4 use warnings;
5 use bytes;
6
7 use OpenSRF::System;
8 use OpenSRF::EX qw/:try/;
9 use OpenSRF::AppSession;
10 use OpenSRF::Utils::JSON;
11 use OpenSRF::Utils::SettingsClient;
12 use OpenILS::Application::AppUtils;
13 use OpenILS::Utils::Fieldmapper;
14 use OpenILS::Utils::CStoreEditor;
15
16 use MARC::Record;
17 use MARC::File::XML;
18 use UNIVERSAL::require;
19
20 use Time::HiRes qw/time/;
21 use Getopt::Long;
22
23 use Config::Simple;
24 use Data::Dumper;
25
26 my @formats = qw/USMARC UNIMARC XML BRE ARE/;
27
28 my ($config,$format,$encoding,$location,$dollarsign,$idl,$help,$holdings,$timeout,$export_mfhd,$type,$all_records) = ('/openils/conf/opensrf_core.xml','USMARC','MARC8','','$',0,undef,undef,0,undef,'biblio',undef);
29 my ($exclusion_ini,$collapse_to_depth, $output_file);
30 my $cfg;
31 my $force901;
32
33 GetOptions(
34         'help'       => \$help,
35         'items'      => \$holdings,
36         'mfhd'       => \$export_mfhd,
37         'all'        => \$all_records,
38         'location=s' => \$location,
39         'money=s'    => \$dollarsign,
40         'config=s'   => \$config,
41         'format=s'   => \$format,
42         'type=s'     => \$type,
43         'xml-idl=s'  => \$idl,
44         'encoding=s' => \$encoding,
45         'timeout=i'  => \$timeout,
46         'force901'  => \$force901,
47         'exclusion_ini=s' => \$exclusion_ini,
48         'collapse_to_depth=i' => \$collapse_to_depth,
49         'output-file=s' => \$output_file,
50 );
51
52 if ($exclusion_ini) {
53         die "exclusion ini file does not exist" unless (-r $exclusion_ini and -s $exclusion_ini);
54         $cfg = new Config::Simple($exclusion_ini) 
55 }
56
57 if ($help) {
58 print <<"HELP";
59 This script exports MARC authority, bibliographic, and serial holdings
60 records from an Evergreen database. 
61
62 Input to this script can consist of a list of record IDs, with one record ID
63 per line, corresponding to the record ID in the Evergreen database table of
64 your requested record type.
65
66 Alternately, passing the --all option will attempt to export all records of
67 the specified type from the Evergreen database. The --all option starts at
68 record ID 1 and increments the ID by 1 until the largest ID in the database
69 is retrieved. This may not be very efficient for databases with large gaps
70 in their ID sequences.
71
72 Usage: $0 [options]
73  --help or -h       This screen.
74  --config or -c     Configuration file [/openils/conf/opensrf_core.xml]
75  --format or -f     Output format (USMARC, UNIMARC, XML, BRE, ARE) [USMARC]
76  --encoding or -e   Output encoding (UTF-8, ISO-8859-?, MARC8) [MARC8]
77  --xml-idl or -x    Location of the IDL XML
78  --timeout          Timeout for exporting a single record; increase if you
79                     are using --holdings and are exporting records that
80                     have a lot of items attached to them.
81  --type or -t       Record type (BIBLIO, AUTHORITY) [BIBLIO]
82  --all or -a        Export all records; ignores input list
83
84  Additional options for type = 'BIBLIO':
85  --items or -i      Include items (holdings) in the output
86  --money            Currency symbol to use in item price field [\$]
87  --mfhd             Export serial MFHD records for associated bib records
88                     Not compatible with --format=BRE
89  --location or -l   MARC Location Code for holdings from
90                     http://www.loc.gov/marc/organizations/orgshome.html
91
92 Examples:
93
94 To export a set of USMARC records in a file named "output_file" based on the
95 IDs contained in a file named "list_of_ids":
96   cat list_of_ids | $0 > output_file
97
98 To export a set of MARC21XML authority records in a file named "output.xml"
99 for all authority records in the database:
100   $0 --format XML --type AUTHORITY --all > output.xml
101
102 HELP
103     exit;
104 }
105
106 $type = lc($type);
107 $format = uc($format);
108 $encoding = uc($encoding);
109
110 my $outfh;
111 my $real_stdout;
112 open($real_stdout, ">&STDOUT") or die "Can't dup STDOUT: $!";
113 if($output_file) {
114     open($outfh, '>', $output_file) or die "Can't open file for output $output_file: $!"; 
115 } else {
116     $outfh = $real_stdout;
117 }
118
119 binmode($outfh, ':raw') if ($encoding ne 'UTF-8');
120 binmode($outfh, ':utf8') if ($encoding eq 'UTF-8');
121
122 if (!grep { $format eq $_ } @formats) {
123     die "Please select a supported format.  ".
124         "Right now that means one of [".
125         join('|',@formats). "]\n";
126 }
127
128 if ($format ne 'XML') {
129     my $type = 'MARC::File::' . $format;
130     $type->require;
131 }
132
133 if ($timeout <= 0) {
134     # set default timeout and/or correct silly user who 
135     # supplied a negative timeout; default timeout of
136     # 300 seconds if exporting items determined empirically.
137     $timeout = $holdings ? 300 : 1;
138 }
139
140 OpenSRF::System->bootstrap_client( config_file => $config );
141
142 if (!$idl) {
143     $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
144 }
145
146 Fieldmapper->import(IDL => $idl);
147
148 my $ses = OpenSRF::AppSession->create('open-ils.cstore');
149 OpenILS::Utils::CStoreEditor::init();
150 my $editor = OpenILS::Utils::CStoreEditor->new();
151
152 print $outfh <<HEADER if ($format eq 'XML');
153 <?xml version="1.0" encoding="$encoding"?>
154 <collection xmlns='http://www.loc.gov/MARC21/slim'>
155 HEADER
156
157 my %orgs;
158 my %shelves;
159 my %statuses;
160 my %outypes;
161
162 my $flesh = {};
163
164 if ($holdings) {
165     get_bib_locations();
166 }
167
168 my $start = time;
169 my $last_time = time;
170 my %count = ('bib' => 0, 'did' => 0);
171 my $speed = 0;
172
173 if ($all_records) {
174     my $top_record = 0;
175     if ($type eq 'biblio') {
176         $top_record = $editor->search_biblio_record_entry([
177             {deleted => 'f'},
178             {order_by => { 'bre' => 'id DESC' }, limit => 1}
179         ])->[0]->id;
180     } elsif ($type eq 'authority') {
181         $top_record = $editor->search_authority_record_entry([
182             {deleted => 'f'},
183             {order_by => { 'are' => 'id DESC' }, limit => 1}
184         ])->[0]->id;
185     }
186     for (my $i = 0; $i++ < $top_record;) {
187         export_record($i);
188     }
189 } else {
190     while ( my $i = <> ) {
191         export_record($i);
192     }
193 }
194
195 print $outfh "</collection>\n" if ($format eq 'XML');
196
197 $speed = $count{did} / (time - $start);
198 my $time = time - $start;
199 print STDERR <<DONE;
200
201 Exports Attempted : $count{bib}
202 Exports Completed : $count{did}
203 Overall Speed     : $speed
204 Total Time Elapsed: $time seconds
205
206 DONE
207
208 sub export_record {
209     my $id = shift;
210
211     my $bib; 
212
213     my $r = $ses->request( "open-ils.cstore.direct.$type.record_entry.retrieve", $id, $flesh );
214     my $s = $r->recv(timeout => $timeout);
215     if (!$s) {
216         warn "\n!!!!! Failed trying to read record $id\n";
217         return;
218     }
219     if ($r->failed) {
220         warn "\n!!!!!! Failed trying to read record $id: " . $r->failed->stringify . "\n";
221         return;
222     }
223     if ($r->timed_out) {
224         warn "\n!!!!!! Timed out trying to read record $id\n";
225         return;
226     }
227     $bib = $s->content;
228     $r->finish;
229
230     $count{bib}++;
231     return unless $bib;
232
233     if ($format eq 'ARE' or $format eq 'BRE') {
234         print $outfh OpenSRF::Utils::JSON->perl2JSON($bib);
235         stats();
236         $count{did}++;
237         return;
238     }
239
240     try {
241
242         my $r = MARC::Record->new_from_xml( $bib->marc, $encoding, $format );
243         if ($type eq 'biblio') {
244             add_bib_holdings($bib, $r);
245         }
246
247         if($force901){
248             $r->delete_field( $r->field('901') );
249             $r->append_fields(
250                 MARC::Field->new(
251                     '901', ' ', ' ',
252                     a => $bib->tcn_value,
253                     b => $bib->tcn_source,
254                     c => $bib->id
255                 )
256             );
257         }
258
259         if ($format eq 'XML') {
260             my $xml = $r->as_xml_record;
261             $xml =~ s/^<\?.+?\?>$//mo;
262             print $outfh $xml;
263         } elsif ($format eq 'UNIMARC') {
264             print $outfh $r->as_usmarc;
265         } elsif ($format eq 'USMARC') {
266             print $outfh $r->as_usmarc;
267         }
268
269         $count{did}++;
270
271     } otherwise {
272         my $e = shift;
273         my $errorid = $id;
274         chomp($errorid);
275         chomp($e);
276         warn "\nERROR ON RECORD $errorid: $e\n";
277         import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire export
278     };
279
280     if ($export_mfhd and $type eq 'biblio') {
281         my $mfhds = $editor->search_serial_record_entry({record => $id, deleted => 'f'});
282         foreach my $mfhd (@$mfhds) {
283             try {
284                 my $r = MARC::Record->new_from_xml( $mfhd->marc, $encoding, $format );
285
286                 if($force901){
287                     $r->delete_field( $r->field('901') );
288                     $r->append_fields(
289                         MARC::Field->new(
290                             '901', ' ', ' ',
291                             a => $bib->tcn_value,
292                             b => $bib->tcn_source,
293                             c => $bib->id
294                         )
295                     );
296                 }
297
298                 if ($format eq 'XML') {
299                     my $xml = $r->as_xml_record;
300                     $xml =~ s/^<\?.+?\?>$//mo;
301                     print $outfh $xml;
302                 } elsif ($format eq 'UNIMARC') {
303                     print $outfh $r->as_usmarc;
304                 } elsif ($format eq 'USMARC') {
305                     print $outfh $r->as_usmarc;
306                 }
307             } otherwise {
308                 my $e = shift;
309                 my $errorid = chomp($id);
310                 chomp($e);
311                 warn "\nERROR ON MFHD RECORD $errorid: $e\n";
312                 import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire export
313             };
314         }
315     }
316
317     stats() if (! ($count{bib} % 50 ));
318 }
319
320 sub stats {
321     try {
322         no warnings;
323
324         $speed = $count{did} / (time - $start);
325
326         my $speed_now = ($count{did} - $count{did_last}) / (time - $count{time_last});
327         my $cn_speed = $count{cn} / (time - $start);
328         my $cp_speed = $count{cp} / (time - $start);
329
330         printf STDERR "\r  $count{did} of $count{bib} @  \%0.4f/s ttl / \%0.4f/s rt ".
331                 "($count{cn} CNs @ \%0.4f/s :: $count{cp} CPs @ \%0.4f/s)\r",
332                 $speed,
333                 $speed_now,
334                 $cn_speed,
335                 $cp_speed;
336     } otherwise {};
337     $count{did_last} = $count{did};
338     $count{time_last} = time;
339 }
340
341 sub get_bib_locations {
342     print STDERR "Retrieving Org Units ... ";
343     my $r = $ses->request( 'open-ils.cstore.direct.actor.org_unit.search', { id => { '!=' => undef } } );
344
345     while (my $o = $r->recv) {
346         die $r->failed->stringify if ($r->failed);
347         $o = $o->content;
348         last unless ($o);
349         $orgs{$o->id} = $o;
350     }
351     $r->finish;
352     print STDERR "OK\n";
353
354     print STDERR "Retrieving Copy statuses ... ";
355     $r = $ses->request( 'open-ils.cstore.direct.config.copy_status.search', { id => { '!=' => undef } } );
356
357     while (my $sta = $r->recv) {
358         die $r->failed->stringify if ($r->failed);
359         $sta = $sta->content;
360         last unless ($sta);
361         $statuses{$sta->id} = $sta;
362     }
363     $r->finish;
364     print STDERR "OK\n";
365
366     print STDERR "Retrieving OU types ... ";
367     $r = $ses->request( 'open-ils.cstore.direct.actor.org_unit_type.search', { id => { '!=' => undef } } );
368
369     while (my $outy = $r->recv) {
370         die $r->failed->stringify if ($r->failed);
371         $outy = $outy->content;
372         last unless ($outy);
373         $outypes{$outy->id} = $outy;
374     }
375     $r->finish;
376     print STDERR "OK\n";
377
378     print STDERR "Retrieving Shelving locations ... ";
379     $r = $ses->request( 'open-ils.cstore.direct.asset.copy_location.search', { id => { '!=' => undef } } );
380
381     while (my $s = $r->recv) {
382         die $r->failed->stringify if ($r->failed);
383         $s = $s->content;
384         last unless ($s);
385         $shelves{$s->id} = $s;
386     }
387     $r->finish;
388     print STDERR "OK\n";
389
390     $flesh = { flesh => 2, flesh_fields => { bre => [ 'call_numbers' ], acn => [ 'copies' ] } };
391 }
392
393 sub add_bib_holdings {
394     my $bib = shift;
395     my $r = shift;
396
397     my $cn_list = $bib->call_numbers;
398     if ($cn_list && @$cn_list) {
399
400         $count{cn} += @$cn_list;
401     
402         my $cp_list = [ map { @{ $_->copies } } @$cn_list ];
403         if ($cp_list && @$cp_list) {
404
405             my %cn_map;
406             push @{$cn_map{$_->call_number}}, $_ for (@$cp_list);
407                             
408             for my $cn ( @$cn_list ) {
409                 my $cn_map_list = $cn_map{$cn->id};
410
411                 COPYMAP: for my $cp ( @$cn_map_list ) {
412                     $count{cp}++;
413
414
415                     my $owninglib = $cn->owning_lib;
416                     my $circlib = $cp->circ_lib;
417                     my $printlib = $cp->circ_lib;
418
419                     if($cfg){
420                         my $thisorg = $orgs{$circlib};
421
422                         if($collapse_to_depth){
423                             while ( $outypes{ $thisorg->ou_type }->depth > $collapse_to_depth ){
424                                 my $localcfg = $cfg->param(-block=> $thisorg->shortname);
425                                 if( $localcfg->{'DontCollapse'} ){
426                                     last;
427                                 }
428                                 if($thisorg->parent_ou){
429                                     $thisorg = $orgs{$thisorg->parent_ou};
430                                     $printlib = $thisorg->id;
431                                 }
432                             }
433                         }
434
435                         $thisorg = $orgs{$circlib};
436
437
438                         while( $thisorg ){
439                             # load the local config from the .ini file for exclusions
440                             my $localcfg = $cfg->param(-block=> $thisorg->shortname);
441                             my $cfgparam;
442
443                             # if we see this setting, just skip that org
444
445                             $cfgparam = 'ExcludeEntireOrg'; 
446                             if( $localcfg->{$cfgparam} ) 
447                             { skipnote($bib->id, $cfgparam); next COPYMAP; } 
448
449                             # what follows are exclusion rules
450                     
451                             # Excluded Flags
452                             $cfgparam = 'Flags'; 
453                             if($localcfg->{$cfgparam}){
454                                 # this little line is just forcing scalars into an array so we can 'use strict' with Config::Simple
455                                 my @flags = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}));
456                                 if( grep { $_ eq 'reference' } @flags && $cp->ref eq 't')
457                                 { skipnote($bib->id,"Flags: reference"); next COPYMAP; } 
458                                 elsif( grep { $_ eq 'unholdable' } @flags && $cp->holdable eq 'f')
459                                 { skipnote($bib->id,"Flags: unholdable"); next COPYMAP; } 
460                                 elsif( grep { $_ eq 'circulate' } @flags && $cp->circulate eq 'f')
461                                 { skipnote($bib->id,"Flags: circulate"); next COPYMAP; } 
462                                 elsif( grep { $_ eq 'hidden' } @flags && $cp->opac_visible eq 'f')
463                                 { skipnote($bib->id,"Flags: hidden"); next COPYMAP; } 
464                             }
465
466                             # Excluded Circ Modifiers
467                             $cfgparam = 'CircMods'; 
468                             if($localcfg->{$cfgparam}){
469                                 my $circmod = $cp->circ_modifier || "";
470                                 my @circmods = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
471                                 if( grep { $_ eq $circmod } @circmods && @circmods)
472                                 { skipnote($bib->id,$cfgparam); next COPYMAP; } 
473                             }
474                             # Inverse rule -- only include specified Circ Mods
475                             $cfgparam = 'OnlyIncludeCircMods'; 
476                             if($localcfg->{$cfgparam}){
477                                 my $circmod = $cp->circ_modifier || "";
478                                 my @circmods = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
479                                 unless( grep { $_ and $_ eq $circmod } @circmods && @circmods)
480                                 { skipnote($bib->id,$cfgparam); next COPYMAP; } 
481                             }
482                             # Excluded Copy Statuses
483                             $cfgparam = 'Statuses'; 
484                             if($localcfg->{$cfgparam}){
485                                 my @statuses = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
486                                 if( grep { $_ eq $statuses{$cp->status}->name } @statuses && @statuses)
487                                 { skipnote($bib->id,$cfgparam); next COPYMAP; } 
488                             }
489                             # Excluded Locations
490                             $cfgparam = 'Locations'; 
491                             if($localcfg->{$cfgparam}){
492                                 my @locations = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
493                                 if( grep { $_ eq $shelves{$cp->location}->name } @locations && @locations)
494                                 { skipnote($bib->id,$cfgparam); next COPYMAP; }
495                             }
496                             # Inverse rule - Only use the specified locations
497                             $cfgparam = 'OnlyIncludeLocations'; 
498                             if($localcfg->{$cfgparam}){
499                                 my @locations = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{'Locations'}) );
500                                 unless( grep { $_ eq $shelves{$cp->location}->name } @locations && @locations)
501                                 { skipnote($bib->id,$cfgparam); next COPYMAP; } 
502                             }
503                             # exclude based on a regex match to location names
504                             $cfgparam = 'LocationRegex'; 
505                             if($localcfg->{$cfgparam}){
506                                 my @locregex = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
507                                 my $reg = $localcfg->{$cfgparam};
508                                 if( grep { $shelves{$cp->location}->name =~ m/($reg)/ } @locregex && @locregex)
509                                 { skipnote($bib->id,$cfgparam); next COPYMAP; }
510                             }
511                             # include based on a regex match to location names
512                             $cfgparam = 'OnlyIncludeLocationRegex'; 
513                             if($localcfg->{$cfgparam}){
514                                 my @locregex = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
515                                 my $reg = $localcfg->{$cfgparam};
516                                 unless( grep { $shelves{$cp->location}->name =~ m/($reg)/ } @locregex && @locregex)
517                                 { skipnote($bib->id,$cfgparam); next COPYMAP; } 
518                             }
519                             # Exclude based on a callno regex
520                             $cfgparam = 'CallNoRegex'; 
521                             if($localcfg->{$cfgparam}){
522                                 my @callnoregex = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
523                                 my $reg = $localcfg->{$cfgparam};
524                                 if( grep { $cn->label =~ m/($reg)/ } @callnoregex && @callnoregex)
525                                 { skipnote($bib->id,$cfgparam); next COPYMAP; }
526                             }
527                             # Include based on a callno regex
528                             $cfgparam = 'OnlyIncludeCallNoRegex'; 
529                             if($localcfg->{$cfgparam}){
530                                 my @callnoregex = ( (ref($localcfg->{$cfgparam}) eq "ARRAY") ? @{$localcfg->{$cfgparam}} : ($localcfg->{$cfgparam}) );
531                                 my $reg = $localcfg->{$cfgparam};
532                                 unless( grep { $cn->label =~ m/($reg)/ } @callnoregex && @callnoregex)
533                                 { skipnote($bib->id,$cfgparam); next COPYMAP; } 
534                             }
535
536                             # Trim call number to a float and exclude based on Dewey Range
537                             if($localcfg->{'DeweyGT'} || $localcfg->{'DeweyLT'}){
538                                 my $gt = $localcfg->{'DeweyGT'};
539                                 my $lt = $localcfg->{'DeweyLT'};
540
541                                 # FIXME if either config has an array just ditch for now
542                                 if (ref($gt) eq "ARRAY" or ref($lt) eq "ARRAY")
543                                 { skipnote($bib->id,""); next COPYMAP; } 
544                                 $gt =~ s/[^0-9\.]//g if $gt; #trim off anything not deweyish
545                                 $lt =~ s/[^0-9\.]//g if $lt; #trim off anything not deweyish
546
547                                 my $callno = $cn->label;
548                                 $callno =~ s/[^0-9\.]//g; #trim off anything not deweyish
549                                 print STDERR $callno;
550                                 #note that we are making big assumptions about the call numbers in the db 
551
552                                 # we have a range, exclude what's inbetween
553                                 if($lt && $gt){
554                                     if($callno > $gt and $callno < $lt)
555                                     { skipnote($bib->id,"Dewey LTGT"); next COPYMAP; } 
556                                 # we only have a top threshold, exclude everything below it
557                                 } elsif ($lt){
558                                     if($callno < $lt)
559                                     { skipnote($bib->id,"Dewey LT"); next COPYMAP; }
560                                 # we only have a bottom threshold, exclude everything above it
561                                 } elsif ($gt){
562                                     if($callno > $gt)
563                                     { skipnote($bib->id,"Dewey GT"); next COPYMAP; } 
564                                 }
565                             }
566
567                             if($thisorg->parent_ou){
568                                  $thisorg = $orgs{$thisorg->parent_ou}
569                             } else {
570                                 $thisorg = ();
571                             }
572                             
573                         }
574                     }
575
576                     $r->append_fields(
577                         MARC::Field->new(
578                             852, '4', '', 
579                             a => $location,
580                             b => $orgs{$printlib}->shortname,
581                             #b => $orgs{$owninglib}->shortname,
582                             #b => $orgs{$circlib}->shortname,
583                             c => $shelves{$cp->location}->name,
584                             j => $cn->label,
585                             ($cp->circ_modifier ? ( g => $cp->circ_modifier ) : ()),
586                             p => $cp->barcode,
587                             ($cp->price ? ( y => $dollarsign.$cp->price ) : ()),
588                             ($cp->copy_number ? ( t => $cp->copy_number ) : ()),
589                             ($cp->ref eq 't' ? ( x => 'reference' ) : ()),
590                             ($cp->holdable eq 'f' ? ( x => 'unholdable' ) : ()),
591                             ($cp->circulate eq 'f' ? ( x => 'noncirculating' ) : ()),
592                             ($cp->opac_visible eq 'f' ? ( x => 'hidden' ) : ()),
593                             z => $statuses{$cp->status}->name,
594                         )
595                     );
596
597
598
599                     stats() if (! ($count{cp} % 100 ));
600                 } # COPYMAP: for my $cp ( @$cn_map_list )
601             } # for my $cn ( @$cn_list )
602         } # if ($cp_list && @$cp_list)
603     } # if ($cn_list && @$cn_list) 
604 } # sub
605
606 sub skipnote { 
607         my $id = shift;
608         my $note = shift;
609         my $outf = *STDERR;
610         $outf = *STDOUT if($output_file) ;
611         printf($outf "Skipped %s due to config: %s\n",$id,$note); 
612 }