making reality adn the docs agree
[migration-tools.git] / Equinox-Migration / lib / Equinox / Migration / SubfieldMapper.pm
index dd32d31..5071a85 100644 (file)
@@ -51,7 +51,7 @@ Returns a E::M::STL object.
 sub new {
     my ($class, %args) = @_;
 
-    my $self = bless { conf   => { mods => { multi => 1, biblevel => 1} },
+    my $self = bless { conf   => { mods => undef },
                        fields => {},
                        tags   => {} }, $class;
 
@@ -60,15 +60,32 @@ sub new {
             $self->{conf}{file} = $args{file};
             $self->generate;
         } else {
-            die "Can't open  file: $!\n";
+            die "Can't open file: $!\n";
         }
     }
 
+    if ($args{mods}) {
+        die "Argument 'mods' is wrong type\n"
+          unless (ref $args{mods} eq "ARRAY");
+        for my $mod ( @{$args{mods}} )
+          { $self->{conf}{mods}{$mod} = 1 }
+    }
+
     return $self;
 }
 
+=head2 has
 
-=head2 generate
+Ask it whether your mapping has various things, and it'll let you know.
+
+    $sm->has('fieldname')      # is this fieldname mapped?
+    $sm->has(901)              # are there any mappings for this tag?
+    $sm->has(650,'c')          # is this tag/subfield combo mapped?
+    $sm->has('name', 245, 'a') # is this name mapped to 245$a?
+
+Returns 1 if true, 0 if false.
+
+FIXME: use named params instead of positional
 
 =cut
 
@@ -88,17 +105,75 @@ sub has {
         if (defined $chunks[2]) {
             return 1 if ( $self->{fields}{$chunks[0]}{tag} eq $chunks[1] and
                           $self->{fields}{$chunks[0]}{sub} eq $chunks[2] );
-            return undef;
+            return 0;
         } elsif (defined $chunks[1]) {
             return 1 if ( $self->{fields}{$chunks[0]}{tag} eq $chunks[1] );
-            return undef;
+            return 0;
         } else {
             return 1 if ( defined $self->{fields}{$chunks[0]} );
-            return undef;
+            return 0;
         }
     }
 }
 
+=head2 field
+
+Given a tag and subfield code,
+
+    my $fname = $sm->field(945, 'p')
+
+return the name mapped to them. Returns C<undef> if no mapping exists.
+
+=cut
+
+sub field {
+    my ($self, $tag, $sub) = @_;
+    return undef unless (defined $tag and defined $sub);
+    return undef unless $self->has($tag, $sub);
+    return $self->{tags}{$tag}{$sub};
+}
+
+=head2 mods
+
+Returns the modifiers set on a mapping.
+
+    $self->mods('fieldname')
+
+If there are no modifiers, C<undef> will be returned. Else a listref
+will be returned.
+
+=cut
+
+sub mods {
+    my ($self, $field) = @_;
+    return undef unless $self->has($field);
+    return $self->{fields}{$field}{mods};
+}
+
+=head2 filters
+
+Returns the content filters set on a mapping
+
+    $self->filters('fieldname')
+
+If there are no filters, C<undef> will be returned. Else a listref
+will be returned.
+
+=cut
+
+sub filters {
+    my ($self, $field) = @_;
+    return undef unless $self->has($field);
+    return $self->{fields}{$field}{filt};
+}
+
+
+
+=head2 generate
+
+Generate initial mapping from file.
+
+=cut
 
 sub generate {
     my ($self, $file) = @_;
@@ -111,56 +186,127 @@ sub generate {
         chomp;
         my @tokens = split /\s+/;
 
-        if (defined $tokens[3]) {
-            $self->add( field => $tokens[0], tag => $tokens[1],
-                        sub   => $tokens[2], mod => $tokens[3] );
-        } else {
-            $self->add( field => $tokens[0], tag => $tokens[1], sub => $tokens[2] );
+        my $map = { mods => [], filt => [] };
+        $map->{field} = shift @tokens;
+        $map->{tag}   = shift @tokens;
+        while (my $tok = shift @tokens) {
+            last if ($tok =~ m/^#/);
+            if ($tok =~ m/^[a-z]:'/) {
+                $tok .= ' ' . shift @tokens
+                  until ($tokens[0] =~ m/'$/);
+                $tok .= ' ' . shift @tokens;
+                $tok =~ s/'//;
+                $tok =~ s/'$//;
+            }
+            if ($tok =~ m/^m:/)
+              { push @{$map->{mods}}, $tok }
+            elsif ($tok =~ m/^f:/)
+              { push @{$map->{filt}}, $tok }
+            elsif ($tok =~ m/^[a-z0-9]$/)
+              { $map->{sub} = $tok }
+            else
+              { die "Unknown chunk '$tok' at line $.\n" }
         }
+        $self->add($map);
     }
-
 }
 
+=head2 add
+
+Add new item to mapping. Not usually called directly from user code.
+
+    $sm->add( $map );
+
+Where C<$map> is a hashref that, at a minimum, looks like
+
+    { field => "value", tag => NNN, sub => X }
+
+and may also have the key/value pairs
+
+    mods => [ ITEMS ]
+    filt => [ ITEMS ]
+
+=cut
+
 sub add {
-    my ($self, %toks) = @_;
+    my ($self, $map) = @_;
+
+    # trim the mods and filters
+    my $mods = []; my %mods = ();
+    my $filt = []; my %filt = ();
+    for my $m (@{$map->{mods}}) {
+        die "Modifier collision '$m' at line $." if $mods{$m};
+        $m =~ s/^m://;
+        push @{$mods}, $m; $mods{$m} = 1;
+    }
+    for my $f (@{$map->{filt}}) {
+        die "Modifier collision '$f' at line $." if $filt{$f};
+        $f =~ s/^f://;
+        push @{$filt}, $f; $filt{$f} = 1;
+    }
+    $map->{mods} = $mods;
+    $map->{filt} = $filt;
 
     # check bits for validity
-    $self->validate(\%toks);
+    $self->validate($map);
+
+    # add data to the fields hash
+    $self->{fields}{ $map->{field} } = { tag => $map->{tag},
+                                         sub => $map->{sub},
+                                         mods => $map->{mods},
+                                         filt => $map->{filt}
+                                       };
+    # and to the tags hash
+    $self->{tags}{ $map->{tag} }{ $map->{sub} } = $map->{field};
+}
 
-    $toks{mod} = (defined $toks{mod}) ? $toks{mod} : 0;
+=head2 validate
 
-    $self->{fields}{$toks{field}} = { tag => $toks{tag}, sub => $toks{sub}, mod => $toks{mod}};
-    $self->{tags}{$toks{tag}}{$toks{sub}} = $toks{field};
-}
+Passed a reference to the hash given to C<add>, validate scans its
+contents for common errors and dies if there is an issue.
+
+    * field, tag, and sub are required
+    * fieldnames must start with a letter
+    * fieldnames must be unique
+    * tag must be between 0 and 999
+    * subfield code must be a single alphanumeric character
+    * tag+subfield can only be mapped once
+    * if a list of allowable mod values was given in the call to
+      C<new>, any modifiers must be on that list
+
+=cut
 
 sub validate {
-    my ($self, $toks) = @_;
+    my ($self, $map) = @_;
 
     $.= 1 unless defined $.;
 
     die "Required field missing (line $.)\n"
-      unless (defined $toks->{field} and defined $toks->{tag} and defined $toks->{sub});
+      unless (defined $map->{field} and defined $map->{tag} and defined $map->{sub});
 
     die "Fieldnames must start with letter (line $.)\n"
-     unless ($toks->{field} =~ /^\w/);
+     unless ($map->{field} =~ /^[a-zA-z]/);
 
     die "Invalid tag (line $.)\n"
-      if ($toks->{tag} =~ /\D/ or $toks->{tag} < 0 or $toks->{tag} > 999);
+      if ($map->{tag} =~ /[^\d\-]/ or $map->{tag} < 0 or $map->{tag} > 999);
 
     die "Invalid subfield code (line $.)\n"
-      if (length $toks->{sub} != 1 or $toks->{sub} =~ /[^a-z0-9]/);
+      if (length $map->{sub} != 1 or $map->{sub} =~ /[^a-zA-Z0-9]/);
 
-    # the next thing (if it exists), must be a comment or valid modifier
-    if (defined $toks->{mod}) {
-        die "Unknown chunk (line $.)\n"
-          unless (defined $self->{conf}{mods}{$toks->{mod}} or $toks->{mod} =~ /^#/);
+    # test mod names if we have a set to check against
+    if (defined $self->{conf}{mods}) {
+        for my $mod ( @{$map->{mods}} ) {
+            die "Modifier '$mod' not allowed\n"
+              unless $self->{conf}{mods}{$mod};
+        }
     }
 
     die "Fieldnames must be unique (line $.)\n"
-      if (defined $self->{fields}{$toks->{field}});
+      if (defined $self->{fields}{$map->{field}});
+
+    die "Subfields cannot be mapped twice (line $.)\n"
+      if (defined $self->{tags}{$map->{tag}}{$map->{sub}});
 
-    die "Subfields cannot be multimapped (line $.)\n"
-      if (defined $self->{tags}{$toks->{tag}}{$toks->{sub}});
 }