Bug 17699: Add test descriptions
[koha.git] / Koha / SimpleMARC.pm
blobaaab6b647e033312a543e8ea2a4e3e9ce8626644
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 update_field
21 copy_field
22 copy_and_replace_field
23 move_field
24 delete_field
25 field_exists
26 field_equals
30 our $debug = 0;
32 =head1 NAME
34 SimpleMARC - Perl module for making simple MARC record alterations.
36 =head1 SYNOPSIS
38 use SimpleMARC;
40 =head1 DESCRIPTION
42 SimpleMARC is designed to make writing scripts
43 to modify MARC records simple and easy.
45 Every function in the modules requires a
46 MARC::Record object as its first parameter.
48 =head1 AUTHOR
50 Kyle Hall <lt>kyle.m.hall@gmail.com<gt>
52 =head1 COPYRIGHT AND LICENSE
54 Copyright (C) 2009 by Kyle Hall
56 This library is free software; you can redistribute it and/or modify
57 it under the same terms as Perl itself, either Perl version 5.8.7 or,
58 at your option, any later version of Perl 5 you may have available.
60 =head1 FUNCTIONS
62 =head2 copy_field
64 copy_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName[, $regex[, $n ] ] );
66 Copies a value from one field to another. If a regular expression ( $regex ) is supplied,
67 the value will be transformed by the given regex before being copied into the new field.
68 Example: $regex = { search => 'Old Text', replace => 'Replacement Text', modifiers => 'g' };
70 If $n is passed, copy_field will only copy the Nth field of the list of fields.
71 E.g. $n = 1 will only use the first field's value, $n = 2 will use only the 2nd field's value.
73 =cut
75 sub copy_field {
76 my ( $params ) = @_;
77 my $record = $params->{record};
78 my $fromFieldName = $params->{from_field};
79 my $fromSubfieldName = $params->{from_subfield};
80 my $toFieldName = $params->{to_field};
81 my $toSubfieldName = $params->{to_subfield};
82 my $regex = $params->{regex};
83 my $field_numbers = $params->{field_numbers} // [];
85 if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
88 if ( not $fromSubfieldName
89 or $fromSubfieldName eq ''
90 or not $toSubfieldName
91 or $toSubfieldName eq '' ) {
92 _copy_move_field(
93 { record => $record,
94 from_field => $fromFieldName,
95 to_field => $toFieldName,
96 regex => $regex,
97 field_numbers => $field_numbers,
98 action => 'copy',
101 } else {
102 _copy_move_subfield(
103 { record => $record,
104 from_field => $fromFieldName,
105 from_subfield => $fromSubfieldName,
106 to_field => $toFieldName,
107 to_subfield => $toSubfieldName,
108 regex => $regex,
109 field_numbers => $field_numbers,
110 action => 'copy',
116 sub copy_and_replace_field {
117 my ( $params ) = @_;
118 my $record = $params->{record};
119 my $fromFieldName = $params->{from_field};
120 my $fromSubfieldName = $params->{from_subfield};
121 my $toFieldName = $params->{to_field};
122 my $toSubfieldName = $params->{to_subfield};
123 my $regex = $params->{regex};
124 my $field_numbers = $params->{field_numbers} // [];
126 if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
129 if ( not $fromSubfieldName or $fromSubfieldName eq ''
130 or not $toSubfieldName or $toSubfieldName eq ''
132 _copy_move_field(
133 { record => $record,
134 from_field => $fromFieldName,
135 to_field => $toFieldName,
136 regex => $regex,
137 field_numbers => $field_numbers,
138 action => 'replace',
141 } else {
142 _copy_move_subfield(
143 { record => $record,
144 from_field => $fromFieldName,
145 from_subfield => $fromSubfieldName,
146 to_field => $toFieldName,
147 to_subfield => $toSubfieldName,
148 regex => $regex,
149 field_numbers => $field_numbers,
150 action => 'replace',
156 sub update_field {
157 my ( $params ) = @_;
158 my $record = $params->{record};
159 my $fieldName = $params->{field};
160 my $subfieldName = $params->{subfield};
161 my @values = @{ $params->{values} };
162 my $field_numbers = $params->{field_numbers} // [];
164 if ( ! ( $record && $fieldName ) ) { return; }
166 if ( not $subfieldName or $subfieldName eq '' ) {
167 # FIXME I'm not sure the actual implementation is correct.
168 die "This action is not implemented yet";
169 #_update_field({ record => $record, field => $fieldName, values => \@values });
170 } else {
171 _update_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, values => \@values, field_numbers => $field_numbers });
175 sub _update_field {
176 my ( $params ) = @_;
177 my $record = $params->{record};
178 my $fieldName = $params->{field};
179 my @values = @{ $params->{values} };
181 my $i = 0;
182 if ( my @fields = $record->field( $fieldName ) ) {
183 @values = ($values[0]) x scalar( @fields )
184 if @values == 1;
185 foreach my $field ( @fields ) {
186 $field->update( $values[$i++] );
188 } else {
189 ## Field does not exists, create it
190 if ( $fieldName < 10 ) {
191 foreach my $value ( @values ) {
192 my $field = MARC::Field->new( $fieldName, $value );
193 $record->append_fields( $field );
195 } else {
196 warn "Invalid operation, trying to add a new field without subfield";
201 sub _update_subfield {
202 my ( $params ) = @_;
203 my $record = $params->{record};
204 my $fieldName = $params->{field};
205 my $subfieldName = $params->{subfield};
206 my @values = @{ $params->{values} };
207 my $dont_erase = $params->{dont_erase};
208 my $field_numbers = $params->{field_numbers} // [];
209 my $i = 0;
211 my @fields = $record->field( $fieldName );
213 if ( @$field_numbers ) {
214 @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
217 if ( @fields ) {
218 unless ( $dont_erase ) {
219 @values = ($values[0]) x scalar( @fields )
220 if @values == 1;
221 foreach my $field ( @fields ) {
222 $field->update( "$subfieldName" => $values[$i++] );
225 if ( $i <= scalar ( @values ) - 1 ) {
226 foreach my $field ( @fields ) {
227 foreach my $j ( $i .. scalar( @values ) - 1) {
228 $field->add_subfields( "$subfieldName" => $values[$j] );
232 } else {
233 ## Field does not exist, create it.
234 foreach my $value ( @values ) {
235 my $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $values[$i++] );
236 $record->append_fields( $field );
241 =head2 read_field
243 my @values = read_field( $record, $fieldName[, $subfieldName, [, $n ] ] );
245 Returns an array of field values for the given field and subfield
247 If $n is given, it will return only the $nth value of the array.
248 E.g. If $n = 1, it return the 1st value, if $n = 3, it will return the 3rd value.
250 =cut
252 sub read_field {
253 my ( $params ) = @_;
254 my $record = $params->{record};
255 my $fieldName = $params->{field};
256 my $subfieldName = $params->{subfield};
257 my $field_numbers = $params->{field_numbers} // [];
259 if ( not $subfieldName or $subfieldName eq '' ) {
260 _read_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
261 } else {
262 _read_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
266 sub _read_field {
267 my ( $params ) = @_;
268 my $record = $params->{record};
269 my $fieldName = $params->{field};
270 my $field_numbers = $params->{field_numbers} // [];
272 my @fields = $record->field( $fieldName );
274 return unless @fields;
276 return map { $_->data() } @fields
277 if $fieldName < 10;
279 my @values;
280 if ( @$field_numbers ) {
281 for my $field_number ( @$field_numbers ) {
282 if ( $field_number <= scalar( @fields ) ) {
283 for my $sf ( $fields[$field_number - 1]->subfields ) {
284 push @values, $sf->[1];
288 } else {
289 foreach my $field ( @fields ) {
290 for my $sf ( $field->subfields ) {
291 push @values, $sf->[1];
296 return @values;
299 sub _read_subfield {
300 my ( $params ) = @_;
301 my $record = $params->{record};
302 my $fieldName = $params->{field};
303 my $subfieldName = $params->{subfield};
304 my $field_numbers = $params->{field_numbers} // [];
306 my @fields = $record->field( $fieldName );
308 return unless @fields;
310 my @values;
311 foreach my $field ( @fields ) {
312 my @sf = $field->subfield( $subfieldName );
313 push( @values, @sf );
316 if ( @values and @$field_numbers ) {
317 @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
320 return @values;
323 =head2 field_exists
325 @field_numbers = field_exists( $record, $fieldName[, $subfieldName ]);
327 Returns the field numbers or an empty array.
329 =cut
331 sub field_exists {
332 my ( $params ) = @_;
333 my $record = $params->{record};
334 my $fieldName = $params->{field};
335 my $subfieldName = $params->{subfield};
337 if ( ! $record ) { return; }
339 my @field_numbers = ();
340 my $current_field_number = 1;
341 for my $field ( $record->field( $fieldName ) ) {
342 if ( $subfieldName ) {
343 push @field_numbers, $current_field_number
344 if $field->subfield( $subfieldName );
345 } else {
346 push @field_numbers, $current_field_number;
348 $current_field_number++;
351 return \@field_numbers;
354 =head2 field_equals
356 $bool = field_equals( $record, $value, $fieldName[, $subfieldName[, $regex ] ]);
358 Returns true if the field equals the given value, false otherwise.
360 If a regular expression ( $regex ) is supplied, the value will be compared using
361 the given regex. Example: $regex = 'sought_text'
363 =cut
365 sub field_equals {
366 my ( $params ) = @_;
367 my $record = $params->{record};
368 my $value = $params->{value};
369 my $fieldName = $params->{field};
370 my $subfieldName = $params->{subfield};
371 my $is_regex = $params->{is_regex};
373 if ( ! $record ) { return; }
375 my @field_numbers = ();
376 my $current_field_number = 1;
377 FIELDS: for my $field ( $record->field( $fieldName ) ) {
378 my @subfield_values = $subfieldName
379 ? $field->subfield( $subfieldName )
380 : map { $_->[1] } $field->subfields;
382 SUBFIELDS: for my $subfield_value ( @subfield_values ) {
383 if (
385 $is_regex and $subfield_value =~ m/$value/
386 ) or (
387 $subfield_value eq $value
390 push @field_numbers, $current_field_number;
391 last SUBFIELDS;
394 $current_field_number++;
397 return \@field_numbers;
400 =head2 move_field
402 move_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName[, $regex [, $n ] ] );
404 Moves a value from one field to another. If a regular expression ( $regex ) is supplied,
405 the value will be transformed by the given regex before being moved into the new field.
406 Example: $regex = 's/Old Text/Replacement Text/'
408 If $n is passed, only the Nth field will be moved. $n = 1
409 will move the first repeatable field, $n = 3 will move the third.
411 =cut
413 sub move_field {
414 my ( $params ) = @_;
415 my $record = $params->{record};
416 my $fromFieldName = $params->{from_field};
417 my $fromSubfieldName = $params->{from_subfield};
418 my $toFieldName = $params->{to_field};
419 my $toSubfieldName = $params->{to_subfield};
420 my $regex = $params->{regex};
421 my $field_numbers = $params->{field_numbers} // [];
423 if ( not $fromSubfieldName
424 or $fromSubfieldName eq ''
425 or not $toSubfieldName
426 or $toSubfieldName eq '' ) {
427 _copy_move_field(
428 { record => $record,
429 from_field => $fromFieldName,
430 to_field => $toFieldName,
431 regex => $regex,
432 field_numbers => $field_numbers,
433 action => 'move',
436 } else {
437 _copy_move_subfield(
438 { record => $record,
439 from_field => $fromFieldName,
440 from_subfield => $fromSubfieldName,
441 to_field => $toFieldName,
442 to_subfield => $toSubfieldName,
443 regex => $regex,
444 field_numbers => $field_numbers,
445 action => 'move',
451 =head2 _delete_field
453 _delete_field( $record, $fieldName[, $subfieldName [, $n ] ] );
455 Deletes the given field.
457 If $n is passed, only the Nth field will be deleted. $n = 1
458 will delete the first repeatable field, $n = 3 will delete the third.
460 =cut
462 sub delete_field {
463 my ( $params ) = @_;
464 my $record = $params->{record};
465 my $fieldName = $params->{field};
466 my $subfieldName = $params->{subfield};
467 my $field_numbers = $params->{field_numbers} // [];
469 if ( not $subfieldName or $subfieldName eq '' ) {
470 _delete_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
471 } else {
472 _delete_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
476 sub _delete_field {
477 my ( $params ) = @_;
478 my $record = $params->{record};
479 my $fieldName = $params->{field};
480 my $field_numbers = $params->{field_numbers} // [];
482 my @fields = $record->field( $fieldName );
484 if ( @$field_numbers ) {
485 @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
487 foreach my $field ( @fields ) {
488 $record->delete_field( $field );
492 sub _delete_subfield {
493 my ( $params ) = @_;
494 my $record = $params->{record};
495 my $fieldName = $params->{field};
496 my $subfieldName = $params->{subfield};
497 my $field_numbers = $params->{field_numbers} // [];
499 my @fields = $record->field( $fieldName );
501 if ( @$field_numbers ) {
502 @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
505 foreach my $field ( @fields ) {
506 $field->delete_subfield( code => $subfieldName );
511 sub _copy_move_field {
512 my ( $params ) = @_;
513 my $record = $params->{record};
514 my $fromFieldName = $params->{from_field};
515 my $toFieldName = $params->{to_field};
516 my $regex = $params->{regex};
517 my $field_numbers = $params->{field_numbers} // [];
518 my $action = $params->{action} || 'copy';
520 my @from_fields = $record->field( $fromFieldName );
521 if ( @$field_numbers ) {
522 @from_fields = map { $_ <= @from_fields ? $from_fields[ $_ - 1 ] : () } @$field_numbers;
525 my @new_fields;
526 for my $from_field ( @from_fields ) {
527 my $new_field = $from_field->clone;
528 $new_field->{_tag} = $toFieldName; # Should be replaced by set_tag, introduced by MARC::Field 2.0.4
529 if ( $regex and $regex->{search} ) {
530 for my $subfield ( $new_field->subfields ) {
531 my $value = $subfield->[1];
532 ( $value ) = _modify_values({ values => [ $value ], regex => $regex });
533 $new_field->update( $subfield->[0], $value );
536 if ( $action eq 'move' ) {
537 $record->delete_field( $from_field )
539 elsif ( $action eq 'replace' ) {
540 my @to_fields = $record->field( $toFieldName );
541 if ( @to_fields ) {
542 $record->delete_field( $to_fields[0] );
545 push @new_fields, $new_field;
547 $record->append_fields( @new_fields );
550 sub _copy_move_subfield {
551 my ( $params ) = @_;
552 my $record = $params->{record};
553 my $fromFieldName = $params->{from_field};
554 my $fromSubfieldName = $params->{from_subfield};
555 my $toFieldName = $params->{to_field};
556 my $toSubfieldName = $params->{to_subfield};
557 my $regex = $params->{regex};
558 my $field_numbers = $params->{field_numbers} // [];
559 my $action = $params->{action} || 'copy';
561 my @values = read_field({ record => $record, field => $fromFieldName, subfield => $fromSubfieldName });
562 if ( @$field_numbers ) {
563 @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
565 _modify_values({ values => \@values, regex => $regex });
566 my $dont_erase = $action eq 'copy' ? 1 : 0;
567 _update_subfield({ record => $record, field => $toFieldName, subfield => $toSubfieldName, values => \@values, dont_erase => $dont_erase });
569 # And delete if it's a move
570 if ( $action eq 'move' ) {
571 _delete_subfield({
572 record => $record,
573 field => $fromFieldName,
574 subfield => $fromSubfieldName,
575 field_numbers => $field_numbers,
580 sub _modify_values {
581 my ( $params ) = @_;
582 my $values = $params->{values};
583 my $regex = $params->{regex};
585 if ( $regex and $regex->{search} ) {
586 $regex->{modifiers} //= q||;
587 my @available_modifiers = qw( i g );
588 my $modifiers = q||;
589 for my $modifier ( split //, $regex->{modifiers} ) {
590 $modifiers .= $modifier
591 if grep {/$modifier/} @available_modifiers;
593 foreach my $value ( @$values ) {
594 if ( $modifiers =~ m/^(ig|gi)$/ ) {
595 $value =~ s/$regex->{search}/$regex->{replace}/ig;
597 elsif ( $modifiers eq 'i' ) {
598 $value =~ s/$regex->{search}/$regex->{replace}/i;
600 elsif ( $modifiers eq 'g' ) {
601 $value =~ s/$regex->{search}/$regex->{replace}/g;
603 else {
604 $value =~ s/$regex->{search}/$regex->{replace}/;
608 return @$values;
611 __END__