removed C4::Dates from koha-ffzg.psgi
[koha.git] / Koha / SimpleMARC.pm
index 3ffe768..de76cd3 100644 (file)
@@ -17,15 +17,16 @@ our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
 
 our @EXPORT = qw(
   read_field
+  add_field
   update_field
   copy_field
+  copy_and_replace_field
   move_field
   delete_field
   field_exists
   field_equals
 );
 
-our $VERSION = '0.01';
 
 our $debug = 0;
 
@@ -73,98 +74,208 @@ at your option, any later version of Perl 5 you may have available.
 =cut
 
 sub copy_field {
-  my ( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName, $regex, $n, $dont_erase ) = @_;
-
-  if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
-
-  my @values = read_field( $record, $fromFieldName, $fromSubfieldName );
-  @values = ( $values[$n-1] ) if ( $n );
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fromFieldName = $params->{from_field};
+    my $fromSubfieldName = $params->{from_subfield};
+    my $toFieldName = $params->{to_field};
+    my $toSubfieldName = $params->{to_subfield};
+    my $regex = $params->{regex};
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
+
+
+    if (   not $fromSubfieldName
+        or $fromSubfieldName eq ''
+        or not $toSubfieldName
+        or $toSubfieldName eq '' ) {
+        _copy_move_field(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                to_field      => $toFieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'copy',
+            }
+        );
+    } else {
+        _copy_move_subfield(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                from_subfield => $fromSubfieldName,
+                to_field      => $toFieldName,
+                to_subfield   => $toSubfieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'copy',
+            }
+        );
+    }
+}
 
-  if ( $regex and $regex->{search} ) {
-    $regex->{modifiers} //= q||;
-    my @available_modifiers = qw( i g );
-    my $modifiers = q||;
-    for my $modifier ( split //, $regex->{modifiers} ) {
-        $modifiers .= $modifier
-            if grep {/$modifier/} @available_modifiers;
+sub copy_and_replace_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fromFieldName = $params->{from_field};
+    my $fromSubfieldName = $params->{from_subfield};
+    my $toFieldName = $params->{to_field};
+    my $toSubfieldName = $params->{to_subfield};
+    my $regex = $params->{regex};
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
+
+
+    if ( not $fromSubfieldName or $fromSubfieldName eq ''
+      or not $toSubfieldName or $toSubfieldName eq ''
+    ) {
+        _copy_move_field(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                to_field      => $toFieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'replace',
+            }
+        );
+    } else {
+        _copy_move_subfield(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                from_subfield => $fromSubfieldName,
+                to_field      => $toFieldName,
+                to_subfield   => $toSubfieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'replace',
+            }
+        );
     }
-    foreach my $value (@values) {
-        if ( $modifiers =~ m/^(ig|gi)$/ ) {
-            $value =~ s/$regex->{search}/$regex->{replace}/ig;
-        }
-        elsif ( $modifiers eq 'i' ) {
-            $value =~ s/$regex->{search}/$regex->{replace}/i;
-        }
-        elsif ( $modifiers eq 'g' ) {
-            $value =~ s/$regex->{search}/$regex->{replace}/g;
-        }
-        else {
-            $value =~ s/$regex->{search}/$regex->{replace}/;
-        }
+}
+
+sub update_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my @values = @{ $params->{values} };
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if ( ! ( $record && $fieldName ) ) { return; }
+
+    if ( not $subfieldName or $subfieldName eq '' ) {
+        # FIXME I'm not sure the actual implementation is correct.
+        die "This action is not implemented yet";
+        #_update_field({ record => $record, field => $fieldName, values => \@values });
+    } else {
+        _update_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, values => \@values, field_numbers => $field_numbers });
     }
-  }
-  update_field( $record, $toFieldName, $toSubfieldName, $dont_erase, @values );
 }
 
-=head2 update_field
+=head2 add_field
 
-  update_field( $record, $fieldName, $subfieldName, $dont_erase, $value[, $value,[ $value ... ] ] );
+  add_field({
+      record   => $record,
+      field    => $fieldName,
+      subfield => $subfieldName,
+      values   => \@values,
+      field_numbers => $field_numbers,
+  });
 
-  Updates a field with the given value, creating it if neccessary.
+  Adds a new field/subfield with supplied value(s).
+  This function always add a new field as opposed to 'update_field' which will
+  either update if field exists and add if it does not.
 
-  If multiple values are supplied, they will be used to update a list of repeatable fields
-  until either the fields or the values are all used.
+=cut
 
-  If a single value is supplied for a repeated field, that value will be used to update
-  each of the repeated fields.
 
-=cut
+sub add_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my @values = @{ $params->{values} };
+    my $field_numbers = $params->{field_numbers} // [];
 
-sub update_field {
-  my ( $record, $fieldName, $subfieldName, $dont_erase, @values ) = @_;
+    if ( ! ( $record && $fieldName ) ) { return; }
+    if ( $fieldName > 10 ) {
+        foreach my $value ( @values ) {
+            my $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $value );
+            $record->append_fields( $field );
+        }
+    } else {
+        foreach my $value ( @values ) {
+            my $field = MARC::Field->new( $fieldName, $value );
+            $record->append_fields( $field );
+        }
+    }
+}
 
-  if ( ! ( $record && $fieldName ) ) { return; }
+sub _update_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my @values = @{ $params->{values} };
 
-  my $i = 0;
-  my $field;
-  if ( $subfieldName ) {
+    my $i = 0;
     if ( my @fields = $record->field( $fieldName ) ) {
-      unless ( $dont_erase ) {
         @values = ($values[0]) x scalar( @fields )
-          if @values == 1;
-        foreach my $field ( @fields ) {
-          $field->update( "$subfieldName" => $values[$i++] );
-        }
-      }
-      if ( $i <= scalar ( @values ) - 1 ) {
+            if @values == 1;
         foreach my $field ( @fields ) {
-          foreach my $j ( $i .. scalar( @values ) - 1) {
-            $field->add_subfields( "$subfieldName" => $values[$j] );
-          }
+            $field->update( $values[$i++] );
         }
-      }
     } else {
-      ## Field does not exist, create it.
-      foreach my $value ( @values ) {
-        $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $values[$i++] );
-        $record->append_fields( $field );
-      }
+        ## Field does not exists, create it
+        if ( $fieldName < 10 ) {
+            foreach my $value ( @values ) {
+                my $field = MARC::Field->new( $fieldName, $value );
+                $record->append_fields( $field );
+            }
+        } else {
+            warn "Invalid operation, trying to add a new field without subfield";
+        }
     }
-  } else { ## No subfield
-    if ( my @fields = $record->field( $fieldName ) ) {
-      @values = ($values[0]) x scalar( @fields )
-        if @values == 1;
-      foreach my $field ( @fields ) {
-        $field->update( $values[$i++] );
-      }
+}
+
+sub _update_subfield {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my @values = @{ $params->{values} };
+    my $dont_erase = $params->{dont_erase};
+    my $field_numbers = $params->{field_numbers} // [];
+    my $i = 0;
+
+    my @fields = $record->field( $fieldName );
+
+    if ( @$field_numbers ) {
+        @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
+    }
+
+    if ( @fields ) {
+        unless ( $dont_erase ) {
+            @values = ($values[0]) x scalar( @fields )
+                if @values == 1;
+            foreach my $field ( @fields ) {
+                $field->update( "$subfieldName" => $values[$i++] );
+            }
+        }
+        if ( $i <= scalar ( @values ) - 1 ) {
+            foreach my $field ( @fields ) {
+                foreach my $j ( $i .. scalar( @values ) - 1) {
+                    $field->add_subfields( "$subfieldName" => $values[$j] );
+                }
+            }
+        }
     } else {
-      ## Field does not exists, create it
-      foreach my $value ( @values ) {
-        $field = MARC::Field->new( $fieldName, $value );
-        $record->append_fields( $field );
-      }
+        ## Field does not exist, create it.
+        foreach my $value ( @values ) {
+            my $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $values[$i++] );
+            $record->append_fields( $field );
+        }
     }
-  }
 }
 
 =head2 read_field
@@ -179,76 +290,151 @@ sub update_field {
 =cut
 
 sub read_field {
-  my ( $record, $fieldName, $subfieldName, $n ) = @_;
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if ( not $subfieldName or $subfieldName eq '' ) {
+        _read_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
+    } else {
+        _read_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
+    }
+}
 
-  my @fields = $record->field( $fieldName );
+sub _read_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $field_numbers = $params->{field_numbers} // [];
 
-  return map { $_->data() } @fields unless $subfieldName;
+    my @fields = $record->field( $fieldName );
 
-  my @subfields;
-  foreach my $field ( @fields ) {
-    my @sf = $field->subfield( $subfieldName );
-    push( @subfields, @sf );
-  }
+    return unless @fields;
 
-  if ( $n ) {
-    return $subfields[$n-1];
-  } else {
-    return @subfields;
-  }
+    return map { $_->data() } @fields
+        if $fieldName < 10;
+
+    my @values;
+    if ( @$field_numbers ) {
+        for my $field_number ( @$field_numbers ) {
+            if ( $field_number <= scalar( @fields ) ) {
+                for my $sf ( $fields[$field_number - 1]->subfields ) {
+                    push @values, $sf->[1];
+                }
+            }
+        }
+    } else {
+        foreach my $field ( @fields ) {
+            for my $sf ( $field->subfields ) {
+                push @values, $sf->[1];
+            }
+        }
+    }
+
+    return @values;
+}
+
+sub _read_subfield {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my $field_numbers = $params->{field_numbers} // [];
+
+    my @fields = $record->field( $fieldName );
+
+    return unless @fields;
+
+    my @values;
+    foreach my $field ( @fields ) {
+        my @sf = $field->subfield( $subfieldName );
+        push( @values, @sf );
+    }
+
+    if ( @values and @$field_numbers ) {
+        @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
+    }
+
+    return @values;
 }
 
 =head2 field_exists
 
-  $bool = field_exists( $record, $fieldName[, $subfieldName ]);
+  @field_numbers = field_exists( $record, $fieldName[, $subfieldName ]);
 
-  Returns true if the field exits, false otherwise.
+  Returns the field numbers or an empty array.
 
 =cut
 
 sub field_exists {
-  my ( $record, $fieldName, $subfieldName ) = @_;
+  my ( $params ) = @_;
+  my $record = $params->{record};
+  my $fieldName = $params->{field};
+  my $subfieldName = $params->{subfield};
 
   if ( ! $record ) { return; }
 
-  my $return = 0;
-  if ( $fieldName && $subfieldName ) {
-    $return = $record->field( $fieldName ) && $record->subfield( $fieldName, $subfieldName );
-  } elsif ( $fieldName ) {
-    $return = $record->field( $fieldName ) && 1;
+  my @field_numbers = ();
+  my $current_field_number = 1;
+  for my $field ( $record->field( $fieldName ) ) {
+    if ( $subfieldName ) {
+      push @field_numbers, $current_field_number
+        if $field->subfield( $subfieldName );
+    } else {
+      push @field_numbers, $current_field_number;
+    }
+    $current_field_number++;
   }
 
-  return $return;
+  return \@field_numbers;
 }
 
 =head2 field_equals
 
-  $bool = field_equals( $record, $value, $fieldName[, $subfieldName[, $regex [, $n ] ] ]);
+  $bool = field_equals( $record, $value, $fieldName[, $subfieldName[, $regex ] ]);
 
   Returns true if the field equals the given value, false otherwise.
 
   If a regular expression ( $regex ) is supplied, the value will be compared using
   the given regex. Example: $regex = 'sought_text'
 
-  If $n is passed, the Nth field of a repeatable series will be used for comparison.
-  Set $n to 1 or leave empty for a non-repeatable field.
-
 =cut
 
 sub field_equals {
-  my ( $record, $value, $fieldName, $subfieldName, $regex, $n ) = @_;
-  $n = 1 unless ( $n ); ## $n defaults to first field of a repeatable field series
+  my ( $params ) = @_;
+  my $record = $params->{record};
+  my $value = $params->{value};
+  my $fieldName = $params->{field};
+  my $subfieldName = $params->{subfield};
+  my $is_regex = $params->{is_regex};
 
   if ( ! $record ) { return; }
 
-  my @field_values = read_field( $record, $fieldName, $subfieldName, $n );
-  my $field_value = $field_values[$n-1];
-
-  if ( $regex ) {
-    return $field_value =~ m/$value/;
-  } else {
-    return $field_value eq $value;
+  my @field_numbers = ();
+  my $current_field_number = 1;
+  FIELDS: for my $field ( $record->field( $fieldName ) ) {
+    my @subfield_values = $subfieldName
+        ? $field->subfield( $subfieldName )
+        : map { $_->[1] } $field->subfields;
+
+    SUBFIELDS: for my $subfield_value ( @subfield_values ) {
+      if (
+          (
+              $is_regex and $subfield_value =~ m/$value/
+          ) or (
+              $subfield_value eq $value
+          )
+      ) {
+          push @field_numbers, $current_field_number;
+          last SUBFIELDS;
+      }
+    }
+    $current_field_number++;
   }
+
+  return \@field_numbers;
 }
 
 =head2 move_field
@@ -265,14 +451,46 @@ sub field_equals {
 =cut
 
 sub move_field {
-  my ( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName, $regex, $n ) = @_;
-  copy_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName, $regex, $n , 'dont_erase' );
-  delete_field( $record, $fromFieldName, $fromSubfieldName, $n );
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fromFieldName = $params->{from_field};
+    my $fromSubfieldName = $params->{from_subfield};
+    my $toFieldName = $params->{to_field};
+    my $toSubfieldName = $params->{to_subfield};
+    my $regex = $params->{regex};
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if (   not $fromSubfieldName
+        or $fromSubfieldName eq ''
+        or not $toSubfieldName
+        or $toSubfieldName eq '' ) {
+        _copy_move_field(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                to_field      => $toFieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'move',
+            }
+        );
+    } else {
+        _copy_move_subfield(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                from_subfield => $fromSubfieldName,
+                to_field      => $toFieldName,
+                to_subfield   => $toSubfieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'move',
+            }
+        );
+    }
 }
 
-=head2 delete_field
+=head2 _delete_field
 
-  delete_field( $record, $fieldName[, $subfieldName [, $n ] ] );
+  _delete_field( $record, $fieldName[, $subfieldName [, $n ] ] );
 
   Deletes the given field.
 
@@ -282,22 +500,152 @@ sub move_field {
 =cut
 
 sub delete_field {
-  my ( $record, $fieldName, $subfieldName, $n ) = @_;
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if ( not $subfieldName or $subfieldName eq '' ) {
+        _delete_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
+    } else {
+        _delete_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
+    }
+}
 
-  my @fields = $record->field( $fieldName );
+sub _delete_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $field_numbers = $params->{field_numbers} // [];
 
-  @fields = ( $fields[$n-1] ) if ( $n );
+    my @fields = $record->field( $fieldName );
 
-  if ( @fields && !$subfieldName ) {
+    if ( @$field_numbers ) {
+        @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
+    }
     foreach my $field ( @fields ) {
-      $record->delete_field( $field );
+        $record->delete_field( $field );
     }
-  } elsif ( @fields && $subfieldName ) {
+}
+
+sub _delete_subfield {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my $field_numbers = $params->{field_numbers} // [];
+
+    my @fields = $record->field( $fieldName );
+
+    if ( @$field_numbers ) {
+        @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
+    }
+
     foreach my $field ( @fields ) {
-      $field->delete_subfield( code => $subfieldName );
+        $field->delete_subfield( code => $subfieldName );
     }
-  }
 }
 
+
+sub _copy_move_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fromFieldName = $params->{from_field};
+    my $toFieldName = $params->{to_field};
+    my $regex = $params->{regex};
+    my $field_numbers = $params->{field_numbers} // [];
+    my $action = $params->{action} || 'copy';
+
+    my @from_fields = $record->field( $fromFieldName );
+    if ( @$field_numbers ) {
+        @from_fields = map { $_ <= @from_fields ? $from_fields[ $_ - 1 ] : () } @$field_numbers;
+    }
+
+    my @new_fields;
+    for my $from_field ( @from_fields ) {
+        my $new_field = $from_field->clone;
+        $new_field->{_tag} = $toFieldName; # Should be replaced by set_tag, introduced by MARC::Field 2.0.4
+        if ( $regex and $regex->{search} ) {
+            for my $subfield ( $new_field->subfields ) {
+                my $value = $subfield->[1];
+                ( $value ) = _modify_values({ values => [ $value ], regex => $regex });
+                $new_field->update( $subfield->[0], $value );
+            }
+        }
+        if ( $action eq 'move' ) {
+            $record->delete_field( $from_field )
+        }
+        elsif ( $action eq 'replace' ) {
+            my @to_fields = $record->field( $toFieldName );
+            if ( @to_fields ) {
+                $record->delete_field( $to_fields[0] );
+            }
+        }
+        push @new_fields, $new_field;
+    }
+    $record->append_fields( @new_fields );
+}
+
+sub _copy_move_subfield {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fromFieldName = $params->{from_field};
+    my $fromSubfieldName = $params->{from_subfield};
+    my $toFieldName = $params->{to_field};
+    my $toSubfieldName = $params->{to_subfield};
+    my $regex = $params->{regex};
+    my $field_numbers = $params->{field_numbers} // [];
+    my $action = $params->{action} || 'copy';
+
+    my @values = read_field({ record => $record, field => $fromFieldName, subfield => $fromSubfieldName });
+    if ( @$field_numbers ) {
+        @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
+    }
+    _modify_values({ values => \@values, regex => $regex });
+    my $dont_erase = $action eq 'copy' ? 1 : 0;
+    _update_subfield({ record => $record, field => $toFieldName, subfield => $toSubfieldName, values => \@values, dont_erase => $dont_erase });
+
+    # And delete if it's a move
+    if ( $action eq 'move' ) {
+        _delete_subfield({
+            record => $record,
+            field => $fromFieldName,
+            subfield => $fromSubfieldName,
+            field_numbers => $field_numbers,
+        });
+    }
+}
+
+sub _modify_values {
+    my ( $params ) = @_;
+    my $values = $params->{values};
+    my $regex = $params->{regex};
+
+    if ( $regex and $regex->{search} ) {
+        $regex->{modifiers} //= q||;
+        my @available_modifiers = qw( i g );
+        my $modifiers = q||;
+        for my $modifier ( split //, $regex->{modifiers} ) {
+            $modifiers .= $modifier
+                if grep {/$modifier/} @available_modifiers;
+        }
+        foreach my $value ( @$values ) {
+            if ( $modifiers =~ m/^(ig|gi)$/ ) {
+                $value =~ s/$regex->{search}/$regex->{replace}/ig;
+            }
+            elsif ( $modifiers eq 'i' ) {
+                $value =~ s/$regex->{search}/$regex->{replace}/i;
+            }
+            elsif ( $modifiers eq 'g' ) {
+                $value =~ s/$regex->{search}/$regex->{replace}/g;
+            }
+            else {
+                $value =~ s/$regex->{search}/$regex->{replace}/;
+            }
+        }
+    }
+    return @$values;
+}
 1;
 __END__