Bug 18309: Add missing fields in biblio framework
[koha.git] / Koha / SimpleMARC.pm
blob5a916bdb030c9f61b49389532cc30c80527a3a24
1 package Koha::SimpleMARC;
3 # Copyright 2009 Kyle M. Hall <kyle.m.hall@gmail.com>
5 use Modern::Perl;
7 #use MARC::Record;
9 require Exporter;
11 our @ISA = qw(Exporter);
12 our %EXPORT_TAGS = ( 'all' => [ qw(
14 ) ] );
16 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
18 our @EXPORT = qw(
19 read_field
20 add_field
21 update_field
22 copy_field
23 copy_and_replace_field
24 move_field
25 delete_field
26 field_exists
27 field_equals
31 our $debug = 0;
33 =head1 NAME
35 SimpleMARC - Perl module for making simple MARC record alterations.
37 =head1 SYNOPSIS
39 use SimpleMARC;
41 =head1 DESCRIPTION
43 SimpleMARC is designed to make writing scripts
44 to modify MARC records simple and easy.
46 Every function in the modules requires a
47 MARC::Record object as its first parameter.
49 =head1 AUTHOR
51 Kyle Hall <lt>kyle.m.hall@gmail.com<gt>
53 =head1 COPYRIGHT AND LICENSE
55 Copyright (C) 2009 by Kyle Hall
57 This library is free software; you can redistribute it and/or modify
58 it under the same terms as Perl itself, either Perl version 5.8.7 or,
59 at your option, any later version of Perl 5 you may have available.
61 =head1 FUNCTIONS
63 =head2 copy_field
65 copy_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName[, $regex[, $n ] ] );
67 Copies a value from one field to another. If a regular expression ( $regex ) is supplied,
68 the value will be transformed by the given regex before being copied into the new field.
69 Example: $regex = { search => 'Old Text', replace => 'Replacement Text', modifiers => 'g' };
71 If $n is passed, copy_field will only copy the Nth field of the list of fields.
72 E.g. $n = 1 will only use the first field's value, $n = 2 will use only the 2nd field's value.
74 =cut
76 sub copy_field {
77 my ( $params ) = @_;
78 my $record = $params->{record};
79 my $fromFieldName = $params->{from_field};
80 my $fromSubfieldName = $params->{from_subfield};
81 my $toFieldName = $params->{to_field};
82 my $toSubfieldName = $params->{to_subfield};
83 my $regex = $params->{regex};
84 my $field_numbers = $params->{field_numbers} // [];
86 if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
89 if ( not $fromSubfieldName
90 or $fromSubfieldName eq ''
91 or not $toSubfieldName
92 or $toSubfieldName eq '' ) {
93 _copy_move_field(
94 { record => $record,
95 from_field => $fromFieldName,
96 to_field => $toFieldName,
97 regex => $regex,
98 field_numbers => $field_numbers,
99 action => 'copy',
102 } else {
103 _copy_move_subfield(
104 { record => $record,
105 from_field => $fromFieldName,
106 from_subfield => $fromSubfieldName,
107 to_field => $toFieldName,
108 to_subfield => $toSubfieldName,
109 regex => $regex,
110 field_numbers => $field_numbers,
111 action => 'copy',
117 sub copy_and_replace_field {
118 my ( $params ) = @_;
119 my $record = $params->{record};
120 my $fromFieldName = $params->{from_field};
121 my $fromSubfieldName = $params->{from_subfield};
122 my $toFieldName = $params->{to_field};
123 my $toSubfieldName = $params->{to_subfield};
124 my $regex = $params->{regex};
125 my $field_numbers = $params->{field_numbers} // [];
127 if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
130 if ( not $fromSubfieldName or $fromSubfieldName eq ''
131 or not $toSubfieldName or $toSubfieldName eq ''
133 _copy_move_field(
134 { record => $record,
135 from_field => $fromFieldName,
136 to_field => $toFieldName,
137 regex => $regex,
138 field_numbers => $field_numbers,
139 action => 'replace',
142 } else {
143 _copy_move_subfield(
144 { record => $record,
145 from_field => $fromFieldName,
146 from_subfield => $fromSubfieldName,
147 to_field => $toFieldName,
148 to_subfield => $toSubfieldName,
149 regex => $regex,
150 field_numbers => $field_numbers,
151 action => 'replace',
157 sub update_field {
158 my ( $params ) = @_;
159 my $record = $params->{record};
160 my $fieldName = $params->{field};
161 my $subfieldName = $params->{subfield};
162 my @values = @{ $params->{values} };
163 my $field_numbers = $params->{field_numbers} // [];
165 if ( ! ( $record && $fieldName ) ) { return; }
167 if ( not $subfieldName or $subfieldName eq '' ) {
168 # FIXME I'm not sure the actual implementation is correct.
169 die "This action is not implemented yet";
170 #_update_field({ record => $record, field => $fieldName, values => \@values });
171 } else {
172 _update_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, values => \@values, field_numbers => $field_numbers });
176 =head2 add_field
178 add_field({
179 record => $record,
180 field => $fieldName,
181 subfield => $subfieldName,
182 values => \@values,
183 field_numbers => $field_numbers,
186 Adds a new field/subfield with supplied value(s).
187 This function always add a new field as opposed to 'update_field' which will
188 either update if field exists and add if it does not.
190 =cut
193 sub add_field {
194 my ( $params ) = @_;
195 my $record = $params->{record};
196 my $fieldName = $params->{field};
197 my $subfieldName = $params->{subfield};
198 my @values = @{ $params->{values} };
199 my $field_numbers = $params->{field_numbers} // [];
201 if ( ! ( $record && $fieldName ) ) { return; }
202 if ( $fieldName > 10 ) {
203 foreach my $value ( @values ) {
204 my $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $value );
205 $record->append_fields( $field );
207 } else {
208 foreach my $value ( @values ) {
209 my $field = MARC::Field->new( $fieldName, $value );
210 $record->append_fields( $field );
215 sub _update_field {
216 my ( $params ) = @_;
217 my $record = $params->{record};
218 my $fieldName = $params->{field};
219 my @values = @{ $params->{values} };
221 my $i = 0;
222 if ( my @fields = $record->field( $fieldName ) ) {
223 @values = ($values[0]) x scalar( @fields )
224 if @values == 1;
225 foreach my $field ( @fields ) {
226 $field->update( $values[$i++] );
228 } else {
229 ## Field does not exists, create it
230 if ( $fieldName < 10 ) {
231 foreach my $value ( @values ) {
232 my $field = MARC::Field->new( $fieldName, $value );
233 $record->append_fields( $field );
235 } else {
236 warn "Invalid operation, trying to add a new field without subfield";
241 sub _update_subfield {
242 my ( $params ) = @_;
243 my $record = $params->{record};
244 my $fieldName = $params->{field};
245 my $subfieldName = $params->{subfield};
246 my @values = @{ $params->{values} };
247 my $dont_erase = $params->{dont_erase};
248 my $field_numbers = $params->{field_numbers} // [];
249 my $i = 0;
251 my @fields = $record->field( $fieldName );
253 if ( @$field_numbers ) {
254 @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
257 if ( @fields ) {
258 unless ( $dont_erase ) {
259 @values = ($values[0]) x scalar( @fields )
260 if @values == 1;
261 foreach my $field ( @fields ) {
262 $field->update( "$subfieldName" => $values[$i++] );
265 if ( $i <= scalar ( @values ) - 1 ) {
266 foreach my $field ( @fields ) {
267 foreach my $j ( $i .. scalar( @values ) - 1) {
268 $field->add_subfields( "$subfieldName" => $values[$j] );
272 } else {
273 ## Field does not exist, create it.
274 foreach my $value ( @values ) {
275 my $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $values[$i++] );
276 $record->append_fields( $field );
281 =head2 read_field
283 my @values = read_field( $record, $fieldName[, $subfieldName, [, $n ] ] );
285 Returns an array of field values for the given field and subfield
287 If $n is given, it will return only the $nth value of the array.
288 E.g. If $n = 1, it return the 1st value, if $n = 3, it will return the 3rd value.
290 =cut
292 sub read_field {
293 my ( $params ) = @_;
294 my $record = $params->{record};
295 my $fieldName = $params->{field};
296 my $subfieldName = $params->{subfield};
297 my $field_numbers = $params->{field_numbers} // [];
299 if ( not $subfieldName or $subfieldName eq '' ) {
300 _read_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
301 } else {
302 _read_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
306 sub _read_field {
307 my ( $params ) = @_;
308 my $record = $params->{record};
309 my $fieldName = $params->{field};
310 my $field_numbers = $params->{field_numbers} // [];
312 my @fields = $record->field( $fieldName );
314 return unless @fields;
316 return map { $_->data() } @fields
317 if $fieldName < 10;
319 my @values;
320 if ( @$field_numbers ) {
321 for my $field_number ( @$field_numbers ) {
322 if ( $field_number <= scalar( @fields ) ) {
323 for my $sf ( $fields[$field_number - 1]->subfields ) {
324 push @values, $sf->[1];
328 } else {
329 foreach my $field ( @fields ) {
330 for my $sf ( $field->subfields ) {
331 push @values, $sf->[1];
336 return @values;
339 sub _read_subfield {
340 my ( $params ) = @_;
341 my $record = $params->{record};
342 my $fieldName = $params->{field};
343 my $subfieldName = $params->{subfield};
344 my $field_numbers = $params->{field_numbers} // [];
346 my @fields = $record->field( $fieldName );
348 return unless @fields;
350 my @values;
351 foreach my $field ( @fields ) {
352 my @sf = $field->subfield( $subfieldName );
353 push( @values, @sf );
356 if ( @values and @$field_numbers ) {
357 @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
360 return @values;
363 =head2 field_exists
365 @field_numbers = field_exists( $record, $fieldName[, $subfieldName ]);
367 Returns the field numbers or an empty array.
369 =cut
371 sub field_exists {
372 my ( $params ) = @_;
373 my $record = $params->{record};
374 my $fieldName = $params->{field};
375 my $subfieldName = $params->{subfield};
377 if ( ! $record ) { return; }
379 my @field_numbers = ();
380 my $current_field_number = 1;
381 for my $field ( $record->field( $fieldName ) ) {
382 if ( $subfieldName ) {
383 push @field_numbers, $current_field_number
384 if $field->subfield( $subfieldName );
385 } else {
386 push @field_numbers, $current_field_number;
388 $current_field_number++;
391 return \@field_numbers;
394 =head2 field_equals
396 $bool = field_equals( $record, $value, $fieldName[, $subfieldName[, $regex ] ]);
398 Returns true if the field equals the given value, false otherwise.
400 If a regular expression ( $regex ) is supplied, the value will be compared using
401 the given regex. Example: $regex = 'sought_text'
403 =cut
405 sub field_equals {
406 my ( $params ) = @_;
407 my $record = $params->{record};
408 my $value = $params->{value};
409 my $fieldName = $params->{field};
410 my $subfieldName = $params->{subfield};
411 my $is_regex = $params->{is_regex};
413 if ( ! $record ) { return; }
415 my @field_numbers = ();
416 my $current_field_number = 1;
417 FIELDS: for my $field ( $record->field( $fieldName ) ) {
418 my @subfield_values;
419 if ( $field->is_control_field ) {
420 push @subfield_values, $field->data;
421 } else {
422 @subfield_values =
423 $subfieldName
424 ? $field->subfield($subfieldName)
425 : map { $_->[1] } $field->subfields;
428 SUBFIELDS: for my $subfield_value ( @subfield_values ) {
429 if (
431 $is_regex and $subfield_value =~ m/$value/
432 ) or (
433 $subfield_value eq $value
436 push @field_numbers, $current_field_number;
437 last SUBFIELDS;
440 $current_field_number++;
443 return \@field_numbers;
446 =head2 move_field
448 move_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName[, $regex [, $n ] ] );
450 Moves a value from one field to another. If a regular expression ( $regex ) is supplied,
451 the value will be transformed by the given regex before being moved into the new field.
452 Example: $regex = 's/Old Text/Replacement Text/'
454 If $n is passed, only the Nth field will be moved. $n = 1
455 will move the first repeatable field, $n = 3 will move the third.
457 =cut
459 sub move_field {
460 my ( $params ) = @_;
461 my $record = $params->{record};
462 my $fromFieldName = $params->{from_field};
463 my $fromSubfieldName = $params->{from_subfield};
464 my $toFieldName = $params->{to_field};
465 my $toSubfieldName = $params->{to_subfield};
466 my $regex = $params->{regex};
467 my $field_numbers = $params->{field_numbers} // [];
469 if ( not $fromSubfieldName
470 or $fromSubfieldName eq ''
471 or not $toSubfieldName
472 or $toSubfieldName eq '' ) {
473 _copy_move_field(
474 { record => $record,
475 from_field => $fromFieldName,
476 to_field => $toFieldName,
477 regex => $regex,
478 field_numbers => $field_numbers,
479 action => 'move',
482 } else {
483 _copy_move_subfield(
484 { record => $record,
485 from_field => $fromFieldName,
486 from_subfield => $fromSubfieldName,
487 to_field => $toFieldName,
488 to_subfield => $toSubfieldName,
489 regex => $regex,
490 field_numbers => $field_numbers,
491 action => 'move',
497 =head2 _delete_field
499 _delete_field( $record, $fieldName[, $subfieldName [, $n ] ] );
501 Deletes the given field.
503 If $n is passed, only the Nth field will be deleted. $n = 1
504 will delete the first repeatable field, $n = 3 will delete the third.
506 =cut
508 sub delete_field {
509 my ( $params ) = @_;
510 my $record = $params->{record};
511 my $fieldName = $params->{field};
512 my $subfieldName = $params->{subfield};
513 my $field_numbers = $params->{field_numbers} // [];
515 if ( not $subfieldName or $subfieldName eq '' ) {
516 _delete_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
517 } else {
518 _delete_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
522 sub _delete_field {
523 my ( $params ) = @_;
524 my $record = $params->{record};
525 my $fieldName = $params->{field};
526 my $field_numbers = $params->{field_numbers} // [];
528 my @fields = $record->field( $fieldName );
530 if ( @$field_numbers ) {
531 @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
533 foreach my $field ( @fields ) {
534 $record->delete_field( $field );
538 sub _delete_subfield {
539 my ( $params ) = @_;
540 my $record = $params->{record};
541 my $fieldName = $params->{field};
542 my $subfieldName = $params->{subfield};
543 my $field_numbers = $params->{field_numbers} // [];
545 my @fields = $record->field( $fieldName );
547 if ( @$field_numbers ) {
548 @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
551 foreach my $field ( @fields ) {
552 $field->delete_subfield( code => $subfieldName );
557 sub _copy_move_field {
558 my ( $params ) = @_;
559 my $record = $params->{record};
560 my $fromFieldName = $params->{from_field};
561 my $toFieldName = $params->{to_field};
562 my $regex = $params->{regex};
563 my $field_numbers = $params->{field_numbers} // [];
564 my $action = $params->{action} || 'copy';
566 my @from_fields = $record->field( $fromFieldName );
567 if ( @$field_numbers ) {
568 @from_fields = map { $_ <= @from_fields ? $from_fields[ $_ - 1 ] : () } @$field_numbers;
571 my @new_fields;
572 for my $from_field ( @from_fields ) {
573 my $new_field = $from_field->clone;
574 $new_field->{_tag} = $toFieldName; # Should be replaced by set_tag, introduced by MARC::Field 2.0.4
575 if ( $regex and $regex->{search} ) {
576 for my $subfield ( $new_field->subfields ) {
577 my $value = $subfield->[1];
578 ( $value ) = _modify_values({ values => [ $value ], regex => $regex });
579 $new_field->update( $subfield->[0], $value );
582 if ( $action eq 'move' ) {
583 $record->delete_field( $from_field )
585 elsif ( $action eq 'replace' ) {
586 my @to_fields = $record->field( $toFieldName );
587 if ( @to_fields ) {
588 $record->delete_field( $to_fields[0] );
591 push @new_fields, $new_field;
593 $record->append_fields( @new_fields );
596 sub _copy_move_subfield {
597 my ( $params ) = @_;
598 my $record = $params->{record};
599 my $fromFieldName = $params->{from_field};
600 my $fromSubfieldName = $params->{from_subfield};
601 my $toFieldName = $params->{to_field};
602 my $toSubfieldName = $params->{to_subfield};
603 my $regex = $params->{regex};
604 my $field_numbers = $params->{field_numbers} // [];
605 my $action = $params->{action} || 'copy';
607 my @values = read_field({ record => $record, field => $fromFieldName, subfield => $fromSubfieldName });
608 if ( @$field_numbers ) {
609 @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
611 _modify_values({ values => \@values, regex => $regex });
612 my $dont_erase = $action eq 'copy' ? 1 : 0;
613 _update_subfield({ record => $record, field => $toFieldName, subfield => $toSubfieldName, values => \@values, dont_erase => $dont_erase });
615 # And delete if it's a move
616 if ( $action eq 'move' ) {
617 _delete_subfield({
618 record => $record,
619 field => $fromFieldName,
620 subfield => $fromSubfieldName,
621 field_numbers => $field_numbers,
626 sub _modify_values {
627 my ( $params ) = @_;
628 my $values = $params->{values};
629 my $regex = $params->{regex};
631 if ( $regex and $regex->{search} ) {
632 $regex->{modifiers} //= q||;
633 my @available_modifiers = qw( i g );
634 my $modifiers = q||;
635 for my $modifier ( split //, $regex->{modifiers} ) {
636 $modifiers .= $modifier
637 if grep {/$modifier/} @available_modifiers;
639 foreach my $value ( @$values ) {
640 if ( $modifiers =~ m/^(ig|gi)$/ ) {
641 $value =~ s/$regex->{search}/$regex->{replace}/ig;
643 elsif ( $modifiers eq 'i' ) {
644 $value =~ s/$regex->{search}/$regex->{replace}/i;
646 elsif ( $modifiers eq 'g' ) {
647 $value =~ s/$regex->{search}/$regex->{replace}/g;
649 else {
650 $value =~ s/$regex->{search}/$regex->{replace}/;
654 return @$values;
657 __END__