]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/support-scripts/authority_control_fields.pl.in
LP#1539084: webstaff: sort column picker entries by class path and column labels
[Evergreen.git] / Open-ILS / src / support-scripts / authority_control_fields.pl.in
1 #!/usr/bin/perl
2 # Copyright (C) 2010-2011 Laurentian University
3 # Author: Dan Scott <dscott@laurentian.ca>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
15
16 use strict;
17 use warnings;
18 use DBI;
19 use Getopt::Long;
20 use MARC::Record;
21 use MARC::File::XML (BinaryEncoding => 'UTF-8');
22 use MARC::Charset;
23 use OpenSRF::System;
24 use OpenILS::Utils::Fieldmapper;
25 use OpenSRF::Utils::SettingsClient;
26 use OpenSRF::EX qw/:try/;
27 use Encode;
28 use Unicode::Normalize;
29 use OpenILS::Application::AppUtils;
30 use Data::Dumper;
31 use Pod::Usage qw/ pod2usage /;
32
33 MARC::Charset->assume_unicode(1);
34
35 my ($start_id, $end_id, $refresh);
36 my ($days_back);
37 my $bootstrap = '@sysconfdir@/opensrf_core.xml';
38 my @records;
39
40 my %options;
41 my $result = GetOptions(
42     \%options,
43     'configuration=s' => \$bootstrap,
44     'record=i' => \@records,
45     'refresh' => \$refresh,
46     'all', 'help',
47     'start_id=i' => \$start_id,
48     'end_id=i' => \$end_id,
49     'days_back=i' => \$days_back,
50 );
51
52 if (!$result or $options{help}) {
53     pod2usage(0);
54 }
55
56 if ($start_id && $days_back) {
57     print "Can't use both start ID and days back!\n";
58     exit;
59 }
60
61 OpenSRF::System->bootstrap_client(config_file => $bootstrap);
62 Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
63
64 # must be loaded and initialized after the IDL is parsed
65 use OpenILS::Utils::CStoreEditor;
66 OpenILS::Utils::CStoreEditor::init();
67
68 my $e = OpenILS::Utils::CStoreEditor->new;
69 my $undeleted;
70 if ($options{all}) {
71     # get a list of all non-deleted records from Evergreen
72     # open-ils.cstore open-ils.cstore.direct.biblio.record_entry.id_list.atomic {"deleted":"f"}
73     $undeleted = $e->request( 
74         'open-ils.cstore.direct.biblio.record_entry.id_list.atomic', 
75         [{deleted => 'f'}, {id => { '>' => 0}}]
76     );
77     @records = @$undeleted;
78 }
79
80 if ($start_id and $end_id) {
81     @records = ($start_id .. $end_id);
82 }
83
84 if (defined $days_back) { 
85 @records=();
86
87 # Grab DB information from local settings
88 my $sc = OpenSRF::Utils::SettingsClient->new;
89 my $db_driver = $sc->config_value( reporter => setup => database => 'driver' );
90 my $db_host = $sc->config_value( reporter => setup => database => 'host' );
91 my $db_port = $sc->config_value( reporter => setup => database => 'port' );
92 my $db_name = $sc->config_value( reporter => setup => database => 'db' );
93 if (!$db_name) {
94     $db_name = $sc->config_value( reporter => setup => database => 'name' );
95     print STDERR "WARN: <database><name> is a deprecated setting for database name. For future compatibility, you should use <database><db> instead." if $db_name;
96 }
97 my $db_user = $sc->config_value( reporter => setup => database => 'user' );
98 my $db_pw = $sc->config_value( reporter => setup => database => 'pw' );
99
100 die "Unable to retrieve database connection information from the settings server" unless ($db_driver && $db_host && $db_port && $db_name && $db_user);
101
102 my $dsn = "dbi:" . $db_driver . ":dbname=" . $db_name .';host=' . $db_host . ';port=' . $db_port;
103 my $dbh = DBI->connect($dsn,$db_user,$db_pw, {AutoCommit => 1, pg_enable_utf8 => 1, RaiseError => 1}) or die "database connection error";
104
105 # SQL Used to gather a list of ID's
106 my $idstatement = $dbh->prepare("SELECT DISTINCT(id) AS id FROM biblio.record_entry where (date(create_date) = date(now()) or date(edit_date) = date((NOW() - '$days_back day'::interval)))");
107
108 # Load the list of ID's into the records array
109 $idstatement->execute();
110     while (my $ref = $idstatement->fetchrow_hashref()) {
111         my $id_ref = $ref->{"id"};   # the column name in our sql query is "id"
112         push(@records, $id_ref);
113     }
114 }
115
116 # print Dumper($undeleted, \@records);
117
118 # Hash of controlled fields & subfields in bibliographic records, and their
119 # corresponding controlling fields & subfields in the authority record
120 #
121 # So, if the bib 650$a can be controlled by an auth 150$a, that maps to:
122 # 650 => { a => { 150 => 'a'}}
123 my %controllees = (
124     100 => { a => { 100 => 'a' },
125              b => { 100 => 'b' },
126              c => { 100 => 'c' },
127              d => { 100 => 'd' },
128              f => { 100 => 'f' },
129              g => { 100 => 'g' },
130              j => { 100 => 'j' },
131              k => { 100 => 'k' },
132              l => { 100 => 'l' },
133              n => { 100 => 'n' },
134              p => { 100 => 'p' },
135              q => { 100 => 'q' },
136              t => { 100 => 't' },
137              u => { 100 => 'u' },
138     },
139     110 => { a => { 110 => 'a' },
140              b => { 110 => 'b' },
141              c => { 110 => 'c' },
142              d => { 110 => 'd' },
143              f => { 110 => 'f' },
144              g => { 110 => 'g' },
145              k => { 110 => 'k' },
146              l => { 110 => 'l' },
147              n => { 110 => 'n' },
148              p => { 110 => 'p' },
149              t => { 110 => 't' },
150              u => { 110 => 'u' },
151     },
152     111 => { a => { 111 => 'a' },
153              c => { 111 => 'c' },
154              d => { 111 => 'd' },
155              e => { 111 => 'e' },
156              f => { 111 => 'f' },
157              g => { 111 => 'g' },
158              j => { 111 => 'j' },
159              k => { 111 => 'k' },
160              l => { 111 => 'l' },
161              n => { 111 => 'n' },
162              p => { 111 => 'p' },
163              q => { 111 => 'q' },
164              t => { 111 => 't' },
165              u => { 111 => 'u' },
166     },
167     130 => { a => { 130 => 'a' },
168              d => { 130 => 'd' },
169              f => { 130 => 'f' },
170              g => { 130 => 'g' },
171              h => { 130 => 'h' },
172              k => { 130 => 'k' },
173              l => { 130 => 'l' },
174              m => { 130 => 'm' },
175              n => { 130 => 'n' },
176              o => { 130 => 'o' },
177              p => { 130 => 'p' },
178              r => { 130 => 'r' },
179              s => { 130 => 's' },
180              t => { 130 => 't' },
181     },
182     600 => { a => { 100 => 'a' },
183              b => { 100 => 'b' },
184              c => { 100 => 'c' },
185              d => { 100 => 'd' },
186              f => { 100 => 'f' },
187              g => { 100 => 'g' },
188              h => { 100 => 'h' },
189              j => { 100 => 'j' },
190              k => { 100 => 'k' },
191              l => { 100 => 'l' },
192              m => { 100 => 'm' },
193              n => { 100 => 'n' },
194              o => { 100 => 'o' },
195              p => { 100 => 'p' },
196              q => { 100 => 'q' },
197              r => { 100 => 'r' },
198              s => { 100 => 's' },
199              t => { 100 => 't' },
200              v => { 100 => 'v' },
201              x => { 100 => 'x' },
202              y => { 100 => 'y' },
203              z => { 100 => 'z' },
204     },
205     610 => { a => { 110 => 'a' },
206              b => { 110 => 'b' },
207              c => { 110 => 'c' },
208              d => { 110 => 'd' },
209              f => { 110 => 'f' },
210              g => { 110 => 'g' },
211              h => { 110 => 'h' },
212              k => { 110 => 'k' },
213              l => { 110 => 'l' },
214              m => { 110 => 'm' },
215              n => { 110 => 'n' },
216              o => { 110 => 'o' },
217              p => { 110 => 'p' },
218              r => { 110 => 'r' },
219              s => { 110 => 's' },
220              t => { 110 => 't' },
221              v => { 110 => 'v' },
222              x => { 110 => 'x' },
223              y => { 110 => 'y' },
224              z => { 110 => 'z' },
225     },
226     611 => { a => { 111 => 'a' },
227              c => { 111 => 'c' },
228              d => { 111 => 'd' },
229              e => { 111 => 'e' },
230              f => { 111 => 'f' },
231              g => { 111 => 'g' },
232              h => { 111 => 'h' },
233              j => { 111 => 'j' },
234              k => { 111 => 'k' },
235              l => { 111 => 'l' },
236              n => { 111 => 'n' },
237              p => { 111 => 'p' },
238              q => { 111 => 'q' },
239              s => { 111 => 's' },
240              t => { 111 => 't' },
241              v => { 111 => 'v' },
242              x => { 111 => 'x' },
243              y => { 111 => 'y' },
244              z => { 111 => 'z' },
245     },
246     630 => { a => { 130 => 'a' },
247              d => { 130 => 'd' },
248              f => { 130 => 'f' },
249              g => { 130 => 'g' },
250              h => { 130 => 'h' },
251              k => { 130 => 'k' },
252              l => { 130 => 'l' },
253              m => { 130 => 'm' },
254              n => { 130 => 'n' },
255              o => { 130 => 'o' },
256              p => { 130 => 'p' },
257              r => { 130 => 'r' },
258              s => { 130 => 's' },
259              t => { 130 => 't' },
260              v => { 130 => 'v' },
261              x => { 130 => 'x' },
262              y => { 130 => 'y' },
263              z => { 130 => 'z' },
264     },
265     648 => { a => { 148 => 'a' },
266              v => { 148 => 'v' },
267              x => { 148 => 'x' },
268              y => { 148 => 'y' },
269              z => { 148 => 'z' },
270     },
271     650 => { a => { 150 => 'a' },
272              b => { 150 => 'b' },
273              v => { 150 => 'v' },
274              x => { 150 => 'x' },
275              y => { 150 => 'y' },
276              z => { 150 => 'z' },
277     },
278     651 => { a => { 151 => 'a' },
279              v => { 151 => 'v' },
280              x => { 151 => 'x' },
281              y => { 151 => 'y' },
282              z => { 151 => 'z' },
283     },
284     655 => { a => { 155 => 'a' },
285              v => { 155 => 'v' },
286              x => { 155 => 'x' },
287              y => { 155 => 'y' },
288              z => { 155 => 'z' },
289     },
290     700 => { a => { 100 => 'a' },
291              b => { 100 => 'b' },
292              c => { 100 => 'c' },
293              d => { 100 => 'd' },
294              f => { 100 => 'f' },
295              g => { 100 => 'g' },
296              j => { 100 => 'j' },
297              k => { 100 => 'k' },
298              l => { 100 => 'l' },
299              n => { 100 => 'n' },
300              p => { 100 => 'p' },
301              q => { 100 => 'q' },
302              t => { 100 => 't' },
303              u => { 100 => 'u' },
304     },
305     710 => { a => { 110 => 'a' },
306              b => { 110 => 'b' },
307              c => { 110 => 'c' },
308              d => { 110 => 'd' },
309              f => { 110 => 'f' },
310              g => { 110 => 'g' },
311              k => { 110 => 'k' },
312              l => { 110 => 'l' },
313              n => { 110 => 'n' },
314              p => { 110 => 'p' },
315              t => { 110 => 't' },
316              u => { 110 => 'u' },
317     },
318     711 => { a => { 111 => 'a' },
319              c => { 111 => 'c' },
320              d => { 111 => 'd' },
321              e => { 111 => 'e' },
322              f => { 111 => 'f' },
323              g => { 111 => 'g' },
324              j => { 111 => 'j' },
325              k => { 111 => 'k' },
326              l => { 111 => 'l' },
327              n => { 111 => 'n' },
328              p => { 111 => 'p' },
329              q => { 111 => 'q' },
330              t => { 111 => 't' },
331              u => { 111 => 'u' },
332     },
333     730 => { a => { 130 => 'a' },
334              d => { 130 => 'd' },
335              f => { 130 => 'f' },
336              g => { 130 => 'g' },
337              h => { 130 => 'h' },
338              k => { 130 => 'k' },
339              l => { 130 => 'l' },
340              m => { 130 => 'm' },
341              n => { 130 => 'n' },
342              o => { 130 => 'o' },
343              p => { 130 => 'p' },
344              r => { 130 => 'r' },
345              s => { 130 => 's' },
346              t => { 130 => 't' },
347     },
348     751 => { a => { 151 => 'a' },
349              v => { 151 => 'v' },
350              x => { 151 => 'x' },
351              y => { 151 => 'y' },
352              z => { 151 => 'z' },
353     },
354     800 => { a => { 100 => 'a' },
355              b => { 100 => 'b' },
356              c => { 100 => 'c' },
357              d => { 100 => 'd' },
358              e => { 100 => 'e' },
359              f => { 100 => 'f' },
360              g => { 100 => 'g' },
361              j => { 100 => 'j' },
362              k => { 100 => 'k' },
363              l => { 100 => 'l' },
364              n => { 100 => 'n' },
365              p => { 100 => 'p' },
366              q => { 100 => 'q' },
367              t => { 100 => 't' },
368              u => { 100 => 'u' },
369              4 => { 100 => '4' },
370     },    
371     830 => { a => { 130 => 'a' },
372              d => { 130 => 'd' },
373              f => { 130 => 'f' },
374              g => { 130 => 'g' },
375              h => { 130 => 'h' },
376              k => { 130 => 'k' },
377              l => { 130 => 'l' },
378              m => { 130 => 'm' },
379              n => { 130 => 'n' },
380              o => { 130 => 'o' },
381              p => { 130 => 'p' },
382              r => { 130 => 'r' },
383              s => { 130 => 's' },
384              t => { 130 => 't' },
385     },
386 );
387
388 foreach my $rec_id (@records) {
389     # print "$rec_id\n";
390
391     # State variable; was the record changed?
392     my $changed = 0;
393
394     # get the record
395     my $record = $e->retrieve_biblio_record_entry($rec_id);
396     next unless $record;
397     # print Dumper($record);
398
399     try {
400         my $marc = MARC::Record->new_from_xml($record->marc());
401
402         # get the list of controlled fields
403         my @c_fields = keys %controllees;
404
405         foreach my $c_tag (@c_fields) {
406             my @c_subfields = keys %{$controllees{"$c_tag"}};
407             # print "Field: $field subfields: ";
408             # foreach (@subfields) { print "$_ "; }
409
410             # Get the MARCXML from the record and check for controlled fields/subfields
411             my @bib_fields = ($marc->field($c_tag));
412             foreach my $bib_field (@bib_fields) {
413                 # print $_->as_formatted(); 
414
415                 if ($refresh and defined(scalar($bib_field->subfield('0')))) {
416                     $bib_field->delete_subfield(code => '0');
417                     $changed = 1;
418                 }
419                     
420                 my %match_subfields;
421                 my $match_tag;
422                 my @searches;
423                 foreach my $c_subfield (@c_subfields) {
424                     my @sf_values = $bib_field->subfield($c_subfield);
425                     if (@sf_values) {
426                         # Give me the first element of the list of authority controlling tags for this subfield
427                         # XXX Will we need to support more than one controlling tag per subfield? Probably. That
428                         # will suck. Oh well, leave that up to Ole to implement.
429                         $match_subfields{$c_subfield} = (keys %{$controllees{$c_tag}{$c_subfield}})[0];
430                         $match_tag = $match_subfields{$c_subfield};
431                         push @searches, map {{term => $_, subfield => $c_subfield}} @sf_values;
432                     }
433                 }
434                 # print Dumper(\%match_subfields);
435                 next if !$match_tag;
436
437                 my @tags = ($match_tag);
438
439                 # print "Controlling tag: $c_tag and match tag $match_tag\n";
440                 # print Dumper(\@tags, \@searches);
441
442                 # Now we've built up a complete set of matching controlled
443                 # subfields for this particular field; let's check to see if
444                 # we have a matching authority record
445                 my $session = OpenSRF::AppSession->create("open-ils.search");
446                 my $validates = $session->request("open-ils.search.authority.validate.tag.id_list", 
447                     "tags", \@tags, "searches", \@searches
448                 )->gather();
449                 $session->disconnect();
450
451                 # print Dumper($validates);
452
453                 # Protect against failed (error condition) search request
454                 if (!$validates) {
455                     print STDERR "Search for matching authority failed; record # $rec_id\n";
456                     next if (!$changed);
457                 }
458
459                 # Only add linking if one or more was found, but we may have changed
460                 # the record already if in --refresh mode.
461                 if (scalar(@$validates) > 0) {
462
463                     # Iterate through the returned authority record IDs to delete any
464                     # matching $0 subfields already in the bib record
465                     foreach my $auth_zero (@$validates) {
466                         $bib_field->delete_subfield(code => '0', match => qr/\)$auth_zero$/);
467                     }
468     
469                     # Okay, we have a matching authority control; time to
470                     # add the magical subfield 0. Use the first returned auth
471                     # record as a match.
472                     my $auth_id = @$validates[0];
473                     my $auth_rec = $e->retrieve_authority_record_entry($auth_id);
474                     my $auth_marc = MARC::Record->new_from_xml($auth_rec->marc());
475                     my $cni = $auth_marc->field('003')->data();
476                     
477                     $bib_field->add_subfields('0' => "($cni)$auth_id");
478                     $changed = 1;
479                 }
480             }
481         }
482         if ($changed) {
483             my $editor = OpenILS::Utils::CStoreEditor->new(xact=>1);
484             # print $marc->as_formatted();
485             my $xml = $marc->as_xml_record();
486             $xml =~ s/\n//sgo;
487             $xml =~ s/^<\?xml.+\?\s*>//go;
488             $xml =~ s/>\s+</></go;
489             $xml =~ s/\p{Cc}//go;
490             $xml = OpenILS::Application::AppUtils->entityize($xml);
491
492             $record->marc($xml);
493             $editor->update_biblio_record_entry($record);
494             $editor->commit();
495         }
496     } otherwise {
497         my $err = shift;
498         print STDERR "\nRecord # $rec_id : $err\n";
499         import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire process
500     }
501 }
502
503 __END__
504
505 =head1 NAME
506
507 authority_control_fields.pl - Controls fields in bibliographic records with authorities in Evergreen
508
509 =head1 SYNOPSIS
510
511 C<authority_control_fields.pl> [B<--configuration>=I<opensrf_core.conf>] [B<--refresh>]
512 [[B<--record>=I<record>[ B<--record>=I<record>]]] | [B<--all>] | [B<--start_id>=I<start-ID> B<--end_id>=I<end-ID>]
513
514 =head1 DESCRIPTION
515
516 For a given set of records:
517
518 =over
519
520 =item * Iterate through the list of fields that are controlled fields
521
522 =item * Iterate through the list of subfields that are controlled for
523 that given field
524
525 =item * Search for a matching authority record for that combination of
526 field + subfield(s)
527
528 =over
529
530 =item * If we find a match, then add a $0 subfield to that field identifying
531 the controlling authority record
532
533 =item * If we do not find a match, then insert a row into an "uncontrolled"
534 table identifying the record ID, field, and subfield(s) that were not controlled
535
536 =back
537
538 =item * Iterate through the list of floating subdivisions
539
540 =over
541
542 =item * If we find a match, then add a $0 subfield to that field identifying
543 the controlling authority record
544
545 =item * If we do not find a match, then insert a row into an "uncontrolled"
546 table identifying the record ID, field, and subfield(s) that were not controlled
547
548 =back
549
550 =item * If we changed the record, update it in the database
551
552 =back
553
554 =head1 OPTIONS
555
556 =over
557
558 =item * B<-c> I<config-file>, B<--configuration>=I<config-file>
559
560 Specifies the OpenSRF configuration file used to connect to the OpenSRF router.
561 Defaults to F<@sysconfdir@/opensrf_core.xml>
562
563 =item * B<-r> I<record-ID>, B<--record>=I<record-ID>
564
565 Specifies the bibliographic record ID (found in the C<biblio.record_entry.id>
566 column) of the record to process. This option may be specified more than once
567 to process multiple records in a single run.
568
569 =item * B<-a>, B<--all>
570
571 Specifies that all bibliographic records should be processed. For large
572 databases, this may take an extraordinarily long amount of time.
573
574 =item * B<-r>, B<--refresh>
575
576 Specifies that all authority links should be removed from the target
577 bibliographic record(s).  This will effectively rewrite all authority
578 linking anew.
579
580 =item * B<-s> I<start-ID>, B<--start_id>=I<start-ID>
581
582 Specifies the starting ID of the range of bibliographic records to process.
583 This option is ignored unless it is accompanied by the B<-e> or B<--end_id>
584 option.
585
586 =item * B<-e> I<end-ID>, B<--end_id>=I<end-ID>
587
588 Specifies the ending ID of the range of bibliographic records to process.
589 This option is ignored unless it is accompanied by the B<-s> or B<--start>
590 option.
591
592 =back
593
594 =head1 EXAMPLES
595
596     authority_control_fields.pl --start_id 1 --end_id 50000
597
598 Processes the bibliographic records with IDs between 1 and 50,000 using the
599 default OpenSRF configuration file for connection information.
600
601 =head1 AUTHOR
602
603 Dan Scott <dscott@laurentian.ca>
604
605 =head1 COPYRIGHT AND LICENSE
606
607 Copyright 2010-2011 by Dan Scott
608
609 This program is free software; you can redistribute it and/or
610 modify it under the terms of the GNU General Public License
611 as published by the Free Software Foundation; either version 2
612 of the License, or (at your option) any later version.
613
614 This program is distributed in the hope that it will be useful,
615 but WITHOUT ANY WARRANTY; without even the implied warranty of
616 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
617 GNU General Public License for more details.
618
619 You should have received a copy of the GNU General Public License
620 along with this program; if not, write to the Free Software
621 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
622
623 =cut
624