]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/support-scripts/authority_control_fields.pl
Merge branch 'master' of git+ssh://yeti.esilibrary.com/home/evergreen/evergreen-equin...
[Evergreen.git] / Open-ILS / src / support-scripts / authority_control_fields.pl
1 #!/usr/bin/perl
2 # Copyright (C) 2010 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;
22 use OpenSRF::System;
23 use OpenILS::Utils::Fieldmapper;
24 use OpenSRF::Utils::SettingsClient;
25 use Encode;
26 use Unicode::Normalize;
27 use OpenILS::Application::AppUtils;
28 use Data::Dumper;
29 use Pod::Usage qw/ pod2usage /;
30
31 my ($start_id, $end_id);
32 my $bootstrap = '/openils/conf/opensrf_core.xml';
33 my @records;
34
35 my %options;
36 my $result = GetOptions(
37     \%options,
38     'configuration=s' => \$bootstrap,
39     'record=i' => \@records,
40     'all', 'help',
41     'start_id=i' => \$start_id,
42     'end_id=i' => \$end_id,
43 );
44
45 if (!$result or $options{help}) {
46     pod2usage(0);
47 }
48
49 OpenSRF::System->bootstrap_client(config_file => $bootstrap);
50 Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
51
52 # must be loaded and initialized after the IDL is parsed
53 use OpenILS::Utils::CStoreEditor;
54 OpenILS::Utils::CStoreEditor::init();
55
56 my $editor = OpenILS::Utils::CStoreEditor->new;
57 my $undeleted;
58 if ($options{all}) {
59     # get a list of all non-deleted records from Evergreen
60     # open-ils.cstore open-ils.cstore.direct.biblio.record_entry.id_list.atomic {"deleted":"f"}
61     $undeleted = $editor->request( 
62         'open-ils.cstore.direct.biblio.record_entry.id_list.atomic', 
63         [{deleted => 'f'}, {id => { '>' => 0}}]
64     );
65     @records = @$undeleted;
66 }
67
68 if ($start_id and $end_id) {
69     @records = ($start_id .. $end_id);
70 }
71 # print Dumper($undeleted, \@records);
72
73 # Hash of controlled fields & subfields in bibliographic records, and their
74 # corresponding controlling fields & subfields in the authority record
75 #
76 # So, if the bib 650$a can be controlled by an auth 150$a, that maps to:
77 # 650 => { a => { 150 => 'a'}}
78 my %controllees = (
79     100 => { a => { 100 => 'a' },
80              b => { 100 => 'b' },
81              c => { 100 => 'c' },
82              d => { 100 => 'd' },
83              e => { 100 => 'e' },
84              f => { 100 => 'f' },
85              g => { 100 => 'g' },
86              j => { 100 => 'j' },
87              k => { 100 => 'k' },
88              l => { 100 => 'l' },
89              n => { 100 => 'n' },
90              p => { 100 => 'p' },
91              q => { 100 => 'q' },
92              t => { 100 => 't' },
93              u => { 100 => 'u' },
94              4 => { 100 => '4' },
95     },
96     110 => { a => { 110 => 'a' },
97              b => { 110 => 'b' },
98              c => { 110 => 'c' },
99              d => { 110 => 'd' },
100              e => { 110 => 'e' },
101              f => { 110 => 'f' },
102              g => { 110 => 'g' },
103              k => { 110 => 'k' },
104              l => { 110 => 'l' },
105              n => { 110 => 'n' },
106              p => { 110 => 'p' },
107              t => { 110 => 't' },
108              u => { 110 => 'u' },
109              4 => { 110 => '4' },
110     },
111     111 => { a => { 111 => 'a' },
112              c => { 111 => 'c' },
113              d => { 111 => 'd' },
114              e => { 111 => 'e' },
115              f => { 111 => 'f' },
116              g => { 111 => 'g' },
117              j => { 111 => 'j' },
118              k => { 111 => 'k' },
119              l => { 111 => 'l' },
120              n => { 111 => 'n' },
121              p => { 111 => 'p' },
122              q => { 111 => 'q' },
123              t => { 111 => 't' },
124              u => { 111 => 'u' },
125              4 => { 111 => '4' },
126     },
127     130 => { a => { 130 => 'a' },
128              d => { 130 => 'd' },
129              f => { 130 => 'f' },
130              g => { 130 => 'g' },
131              h => { 130 => 'h' },
132              k => { 130 => 'k' },
133              l => { 130 => 'l' },
134              m => { 130 => 'm' },
135              n => { 130 => 'n' },
136              o => { 130 => 'o' },
137              p => { 130 => 'p' },
138              r => { 130 => 'r' },
139              s => { 130 => 's' },
140              t => { 130 => 't' },
141     },
142     600 => { a => { 100 => 'a' },
143              b => { 100 => 'b' },
144              c => { 100 => 'c' },
145              d => { 100 => 'd' },
146              e => { 100 => 'e' },
147              f => { 100 => 'f' },
148              g => { 100 => 'g' },
149              h => { 100 => 'h' },
150              j => { 100 => 'j' },
151              k => { 100 => 'k' },
152              l => { 100 => 'l' },
153              m => { 100 => 'm' },
154              n => { 100 => 'n' },
155              o => { 100 => 'o' },
156              p => { 100 => 'p' },
157              q => { 100 => 'q' },
158              r => { 100 => 'r' },
159              s => { 100 => 's' },
160              t => { 100 => 't' },
161              v => { 100 => 'v' },
162              x => { 100 => 'x' },
163              y => { 100 => 'y' },
164              z => { 100 => 'z' },
165              4 => { 100 => '4' },
166     },
167     610 => { a => { 110 => 'a' },
168              b => { 110 => 'b' },
169              c => { 110 => 'c' },
170              d => { 110 => 'd' },
171              e => { 110 => 'e' },
172              f => { 110 => 'f' },
173              g => { 110 => 'g' },
174              h => { 110 => 'h' },
175              k => { 110 => 'k' },
176              l => { 110 => 'l' },
177              m => { 110 => 'm' },
178              n => { 110 => 'n' },
179              o => { 110 => 'o' },
180              p => { 110 => 'p' },
181              r => { 110 => 'r' },
182              s => { 110 => 's' },
183              t => { 110 => 't' },
184              v => { 110 => 'v' },
185              x => { 110 => 'x' },
186              y => { 110 => 'y' },
187              z => { 110 => 'z' },
188     },
189     611 => { a => { 111 => 'a' },
190              c => { 111 => 'c' },
191              d => { 111 => 'd' },
192              e => { 111 => 'e' },
193              f => { 111 => 'f' },
194              g => { 111 => 'g' },
195              h => { 111 => 'h' },
196              j => { 111 => 'j' },
197              k => { 111 => 'k' },
198              l => { 111 => 'l' },
199              n => { 111 => 'n' },
200              p => { 111 => 'p' },
201              q => { 111 => 'q' },
202              s => { 111 => 's' },
203              t => { 111 => 't' },
204              v => { 111 => 'v' },
205              x => { 111 => 'x' },
206              y => { 111 => 'y' },
207              z => { 111 => 'z' },
208     },
209     630 => { a => { 130 => 'a' },
210              d => { 130 => 'd' },
211              f => { 130 => 'f' },
212              g => { 130 => 'g' },
213              h => { 130 => 'h' },
214              k => { 130 => 'k' },
215              l => { 130 => 'l' },
216              m => { 130 => 'm' },
217              n => { 130 => 'n' },
218              o => { 130 => 'o' },
219              p => { 130 => 'p' },
220              r => { 130 => 'r' },
221              s => { 130 => 's' },
222              t => { 130 => 't' },
223              v => { 130 => 'v' },
224              x => { 130 => 'x' },
225              y => { 130 => 'y' },
226              z => { 130 => 'z' },
227     },
228     648 => { a => { 148 => 'a' },
229              v => { 148 => 'v' },
230              x => { 148 => 'x' },
231              y => { 148 => 'y' },
232              z => { 148 => 'z' },
233     },
234     650 => { a => { 150 => 'a' },
235              b => { 150 => 'b' },
236              v => { 150 => 'v' },
237              x => { 150 => 'x' },
238              y => { 150 => 'y' },
239              z => { 150 => 'z' },
240     },
241     651 => { a => { 151 => 'a' },
242              v => { 151 => 'v' },
243              x => { 151 => 'x' },
244              y => { 151 => 'y' },
245              z => { 151 => 'z' },
246     },
247     655 => { a => { 155 => 'a' },
248              v => { 155 => 'v' },
249              x => { 155 => 'x' },
250              y => { 155 => 'y' },
251              z => { 155 => 'z' },
252     },
253     700 => { a => { 100 => 'a' },
254              b => { 100 => 'b' },
255              c => { 100 => 'c' },
256              d => { 100 => 'd' },
257              e => { 100 => 'e' },
258              f => { 100 => 'f' },
259              g => { 100 => 'g' },
260              j => { 100 => 'j' },
261              k => { 100 => 'k' },
262              l => { 100 => 'l' },
263              n => { 100 => 'n' },
264              p => { 100 => 'p' },
265              q => { 100 => 'q' },
266              t => { 100 => 't' },
267              u => { 100 => 'u' },
268              4 => { 100 => '4' },
269     },
270     710 => { a => { 110 => 'a' },
271              b => { 110 => 'b' },
272              c => { 110 => 'c' },
273              d => { 110 => 'd' },
274              e => { 110 => 'e' },
275              f => { 110 => 'f' },
276              g => { 110 => 'g' },
277              k => { 110 => 'k' },
278              l => { 110 => 'l' },
279              n => { 110 => 'n' },
280              p => { 110 => 'p' },
281              t => { 110 => 't' },
282              u => { 110 => 'u' },
283              4 => { 110 => '4' },
284     },
285     711 => { a => { 111 => 'a' },
286              c => { 111 => 'c' },
287              d => { 111 => 'd' },
288              e => { 111 => 'e' },
289              f => { 111 => 'f' },
290              g => { 111 => 'g' },
291              j => { 111 => 'j' },
292              k => { 111 => 'k' },
293              l => { 111 => 'l' },
294              n => { 111 => 'n' },
295              p => { 111 => 'p' },
296              q => { 111 => 'q' },
297              t => { 111 => 't' },
298              u => { 111 => 'u' },
299              4 => { 111 => '4' },
300     },
301     730 => { a => { 130 => 'a' },
302              d => { 130 => 'd' },
303              f => { 130 => 'f' },
304              g => { 130 => 'g' },
305              h => { 130 => 'h' },
306              k => { 130 => 'k' },
307              l => { 130 => 'l' },
308              m => { 130 => 'm' },
309              n => { 130 => 'n' },
310              o => { 130 => 'o' },
311              p => { 130 => 'p' },
312              r => { 130 => 'r' },
313              s => { 130 => 's' },
314              t => { 130 => 't' },
315     },
316     751 => { a => { 151 => 'a' },
317              v => { 151 => 'v' },
318              x => { 151 => 'x' },
319              y => { 151 => 'y' },
320              z => { 151 => 'z' },
321     },
322 );
323
324 foreach my $rec_id (@records) {
325     # print "$rec_id\n";
326
327     my $e = OpenILS::Utils::CStoreEditor->new(xact=>1);
328     # State variable; was the record changed?
329     my $changed;
330
331     # get the record
332     my $record = $e->retrieve_biblio_record_entry($rec_id);
333     next unless $record;
334     # print Dumper($record);
335
336     my $marc = MARC::Record->new_from_xml($record->marc());
337
338     # get the list of controlled fields
339     my @c_fields = keys %controllees;
340
341     foreach my $c_tag (@c_fields) {
342         my @c_subfields = keys %{$controllees{"$c_tag"}};
343         # print "Field: $field subfields: ";
344         # foreach (@subfields) { print "$_ "; }
345
346         # Get the MARCXML from the record and check for controlled fields/subfields
347         my @bib_fields = ($marc->field($c_tag));
348         foreach my $bib_field (@bib_fields) {
349             # print $_->as_formatted(); 
350             my %match_subfields;
351             my $match_tag;
352             my @searches;
353             foreach my $c_subfield (@c_subfields) {
354                 my $sf = $bib_field->subfield($c_subfield);
355                 if ($sf) {
356                     # Give me the first element of the list of authority controlling tags for this subfield
357                     # XXX Will we need to support more than one controlling tag per subfield? Probably. That
358                     # will suck. Oh well, leave that up to Ole to implement.
359                     $match_subfields{$c_subfield} = (keys %{$controllees{$c_tag}{$c_subfield}})[0];
360                     $match_tag = $match_subfields{$c_subfield};
361                     push @searches, {term => $sf, subfield => $c_subfield};
362                 }
363             }
364             # print Dumper(\%match_subfields);
365             next if !$match_tag;
366
367             my @tags = ($match_tag);
368
369             # print "Controlling tag: $c_tag and match tag $match_tag\n";
370             # print Dumper(\@tags, \@searches);
371
372             # Now we've built up a complete set of matching controlled
373             # subfields for this particular field; let's check to see if
374             # we have a matching authority record
375             my $session = OpenSRF::AppSession->create("open-ils.search");
376             my $validates = $session->request("open-ils.search.authority.validate.tag.id_list", 
377                 "tags", \@tags, "searches", \@searches
378             )->gather();
379             $session->disconnect();
380
381             # print Dumper($validates);
382
383             if (scalar(@$validates) == 0) {
384                 next;
385             }
386
387             # Iterate through the returned authority record IDs to delete any
388             # matching $0 subfields already in the bib record
389             foreach my $auth_zero (@$validates) {
390                 $bib_field->delete_subfield(code => '0', match => qr/\)$auth_zero$/);
391             }
392
393             # Okay, we have a matching authority control; time to
394             # add the magical subfield 0. Use the first returned auth
395             # record as a match.
396             my $auth_id = @$validates[0];
397             my $auth_rec = $e->retrieve_authority_record_entry($auth_id);
398             my $auth_marc = MARC::Record->new_from_xml($auth_rec->marc());
399             my $cni = $auth_marc->field('003')->data();
400             
401             $bib_field->add_subfields('0' => "($cni)$auth_id");
402             $changed = 1;
403         }
404     }
405     if ($changed) {
406         # print $marc->as_formatted();
407         my $xml = $marc->as_xml_record();
408         $xml =~ s/\n//sgo;
409         $xml =~ s/^<\?xml.+\?\s*>//go;
410         $xml =~ s/>\s+</></go;
411         $xml =~ s/\p{Cc}//go;
412         $xml = OpenILS::Application::AppUtils->entityize($xml);
413
414         $record->marc($xml);
415         $e->update_biblio_record_entry($record);
416     }
417     $e->commit();
418 }
419
420 __END__
421
422 =head1 NAME
423
424 authority_control_fields.pl - Controls fields in bibliographic records with authorities in Evergreen
425
426 =head1 SYNOPSIS
427
428 C<authority_control_fields.pl> [B<--configuration>=I<opensrf_core.conf>]
429 [[B<--record>=I<record>[ B<--record>=I<record>]]] | [B<--all>] | [B<--start_id>=I<start-ID> B<--end_id>=I<end-ID>]
430
431 =head1 DESCRIPTION
432
433 For a given set of records:
434
435 =over
436
437 =item * Iterate through the list of fields that are controlled fields
438
439 =item * Iterate through the list of subfields that are controlled for
440 that given field
441
442 =item * Search for a matching authority record for that combination of
443 field + subfield(s)
444
445 =over
446
447 =item * If we find a match, then add a $0 subfield to that field identifying
448 the controlling authority record
449
450 =item * If we do not find a match, then insert a row into an "uncontrolled"
451 table identifying the record ID, field, and subfield(s) that were not controlled
452
453 =back
454
455 =item * Iterate through the list of floating subdivisions
456
457 =over
458
459 =item * If we find a match, then add a $0 subfield to that field identifying
460 the controlling authority record
461
462 =item * If we do not find a match, then insert a row into an "uncontrolled"
463 table identifying the record ID, field, and subfield(s) that were not controlled
464
465 =back
466
467 =item * If we changed the record, update it in the database
468
469 =back
470
471 =head1 OPTIONS
472
473 =over
474
475 =item * B<-c> I<config-file>, B<--configuration>=I<config-file>
476
477 Specifies the OpenSRF configuration file used to connect to the OpenSRF router.
478 Defaults to F</openils/conf/opensrf_core.xml>
479
480 =item * B<-r> I<record-ID>, B<--record>=I<record-ID>
481
482 Specifies the bibliographic record ID (found in the C<biblio.record_entry.id>
483 column) of the record to process. This option may be specified more than once
484 to process multiple records in a single run.
485
486 =item * B<-a>, B<--all>
487
488 Specifies that all bibliographic records should be processed. For large
489 databases, this may take an extraordinarily long amount of time.
490
491 =item * B<-s> I<start-ID>, B<--start_id>=I<start-ID>
492
493 Specifies the starting ID of the range of bibliographic records to process.
494 This option is ignored unless it is accompanied by the B<-e> or B<--end_id>
495 option.
496
497 =item * B<-e> I<end-ID>, B<--end_id>=I<end-ID>
498
499 Specifies the ending ID of the range of bibliographic records to process.
500 This option is ignored unless it is accompanied by the B<-s> or B<--start>
501 option.
502
503 =back
504
505 =head1 EXAMPLES
506
507     authority_control_fields.pl --start_id 1 --end_id 50000
508
509 Processes the bibliographic records with IDs between 1 and 50,000 using the
510 default OpenSRF configuration file for connection information.
511
512 =head1 AUTHOR
513
514 Dan Scott <dscott@laurentian.ca>
515
516 =head1 COPYRIGHT AND LICENSE
517
518 Copyright 2010-2011 by Dan Scott
519
520 This program is free software; you can redistribute it and/or
521 modify it under the terms of the GNU General Public License
522 as published by the Free Software Foundation; either version 2
523 of the License, or (at your option) any later version.
524
525 This program is distributed in the hope that it will be useful,
526 but WITHOUT ANY WARRANTY; without even the implied warranty of
527 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
528 GNU General Public License for more details.
529
530 You should have received a copy of the GNU General Public License
531 along with this program; if not, write to the Free Software
532 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
533
534 =cut
535