fetch tests
[Biblio-Isis] / IsisDB.pm
index 26cc11f..d8919d2 100644 (file)
--- a/IsisDB.pm
+++ b/IsisDB.pm
@@ -2,12 +2,14 @@ package IsisDB;
 use strict;
 
 use Carp;
+use File::Glob qw(:globally :nocase);
+
 use Data::Dumper;
 
 BEGIN {
        use Exporter ();
        use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
-       $VERSION     = 0.01;
+       $VERSION     = 0.07;
        @ISA         = qw (Exporter);
        #Give a hoot don't pollute, do not export more than needed by default
        @EXPORT      = qw ();
@@ -18,19 +20,43 @@ BEGIN {
 
 =head1 NAME
 
-IsisDB - Read CDS/ISIS database
+IsisDB - Read CDS/ISIS, WinISIS and IsisMarc database
 
 =head1 SYNOPSIS
 
-  use IsisDB
+  use IsisDB;
+
   my $isis = new IsisDB(
        isisdb => './cds/cds',
   );
 
+  for(my $mfn = 1; $mfn <= $isis->{'maxmfn'}; $mfn++) {
+       print $isis->to_ascii($mfn),"\n";
+  }
+
 =head1 DESCRIPTION
 
-This module will read CDS/ISIS databases and create hash values out of it.
-It can be used as perl-only alternative to OpenIsis module.
+This module will read ISIS databases created by DOS CDS/ISIS, WinIsis or
+IsisMarc. It can be used as perl-only alternative to OpenIsis module.
+
+It can create hash values from data in ISIS database (using C<to_hash>),
+ASCII dump (using C<to_ascii>) or just hash with field names and packed
+values (like C<^asomething^belse>).
+
+Unique feature of this module is ability to C<include_deleted> records.
+It will also skip zero sized fields (OpenIsis has a bug in XS bindings, so
+fields which are zero sized will be filled with random junk from memory).
+
+It also has support for identifiers (only if ISIS database is created by
+IsisMarc), see C<to_hash>.
+
+This will module will always be slower than OpenIsis module which use C
+library. However, since it's written in perl, it's platform independent (so
+you don't need C compiler), and can be easily modified. I hope that it
+creates data structures which are easier to use than ones created by
+OpenIsis, so reduced time in other parts of the code should compensate for
+slower performance of this module (speed of reading ISIS database is
+rarely an issue).
 
 =head1 METHODS
 
@@ -50,21 +76,18 @@ It can be used as perl-only alternative to OpenIsis module.
 # some binary reads
 #
 
-sub Read32 {
-       my $self = shift;
-
-       my $f = shift || die "Read32 needs file handle";
-       read($$f,$b,4) || die "can't read 4 bytes from $$f from position ".tell($f);
-       return unpack("l",$b);
-}
-
 =head2 new
 
-Open CDS/ISIS database
+Open ISIS database
 
  my $isis = new IsisDB(
        isisdb => './cds/cds',
        read_fdt => 1,
+       include_deleted => 1,
+       hash_filter => sub {
+               my $v = shift;
+               $v =~ s#foo#bar#g;
+       },
        debug => 1,
  );
 
@@ -74,17 +97,28 @@ Options are described below:
 
 =item isisdb
 
-Prefix path to CDS/ISIS. It should contain full or relative path to database
-and common prefix of C<.FDT>, C<.MST>, C<.CNT>, C<.XRF> and C<.MST> files.
+This is full or relative path to ISIS database files which include
+common prefix of C<.MST>, and C<.XRF> and optionally C<.FDT> (if using
+C<read_fdt> option) files.
+
+In this example it uses C<./cds/cds.MST> and related files.
 
 =item read_fdt
 
 Boolean flag to specify if field definition table should be read. It's off
 by default.
 
+=item include_deleted
+
+Don't skip logically deleted records in ISIS.
+
+=item hash_filter
+
+Filter code ref which will be used before data is converted to hash.
+
 =item debug
 
-Dump a C<lot> of debugging output.
+Dump a B<lot> of debugging output.
 
 =back
 
@@ -97,17 +131,35 @@ sub new {
        my $self = {};
        bless($self, $class);
 
-       $self->{isisdb} = {@_}->{isisdb} || croak "new needs database name as argument!";
+       croak "new needs database name (isisdb) as argument!" unless ({@_}->{isisdb});
 
-       $self->{debug} = {@_}->{debug};
+       foreach my $v (qw{isisdb debug include_deleted hash_filter}) {
+               $self->{$v} = {@_}->{$v};
+       }
+
+       my @isis_files = grep(/\.(FDT|MST|XRF|CNT)$/i,glob($self->{isisdb}."*"));
+
+       foreach my $f (@isis_files) {
+               my $ext = $1 if ($f =~ m/\.(\w\w\w)$/);
+               $self->{lc($ext)."_file"} = $f;
+       }
+
+       my @must_exist = qw(mst xrf);
+       push @must_exist, "fdt" if ($self->{read_fdt});
+
+       foreach my $ext (@must_exist) {
+               croak "missing ",uc($ext)," file in ",$self->{isisdb} unless ($self->{$ext."_file"});
+       }
+
+       print STDERR "## using files: ",join(" ",@isis_files),"\n" if ($self->{debug});
 
        # if you want to read .FDT file use read_fdt argument when creating class!
-       if ({@_}->{read_fdt} && -e $self->{isisdb}.".FDT") {
+       if ($self->{read_fdt} && -e $self->{fdt_file}) {
 
                # read the $db.FDT file for tags
                my $fieldzone=0;
 
-               open(fileFDT, $self->{isisdb}.".FDT") || croak "can't read '$self->{isisdb}.FDT': $!";
+               open(fileFDT, $self->{fdt_file}) || croak "can't read '$self->{fdt_file}': $!";
 
                while (<fileFDT>) {
                        chomp;
@@ -131,7 +183,7 @@ sub new {
 
        # Get the Maximum MFN from $db.MST
 
-       open(fileMST,$self->{isisdb}.".MST") || croak "can't read '$self->{isisdb}.MST': $!";
+       open($self->{'fileMST'}, $self->{mst_file}) || croak "can't open '$self->{mst_file}': $!";
 
        # MST format:   (* = 32 bit signed)
        # CTLMFN*       always 0
@@ -139,17 +191,44 @@ sub new {
        # NXTMFB*       last block allocated to master file
        # NXTMFP        offset to next available position in last block
        # MFTYPE        always 0 for user db file (1 for system)
-       seek(fileMST,4,0);
-       $self->{'NXTMFN'}=$self->Read32(\*fileMST) || carp "NXTNFN is zero";
+       seek($self->{'fileMST'},4,0);
+
+       my $buff;
+
+       read($self->{'fileMST'}, $buff, 4);
+       $self->{'NXTMFN'}=unpack("l",$buff) || carp "NXTNFN is zero";
 
        # save maximum MFN
        $self->{'maxmfn'} = $self->{'NXTMFN'} - 1;
 
-       close(fileMST);
+
+
+
+       print STDERR Dumper($self),"\n" if ($self->{debug});
+
+       # open files for later
+       open($self->{'fileXRF'}, $self->{xrf_file}) || croak "can't open '$self->{xrf_file}': $!";
+
+       $self ? return $self : return undef;
+}
+
+=head2 read_cnt
+
+This function is not really used by module, but can be useful to find info
+about your index (if debugging it for example).
+
+  print Dumper($isis->read_cnt);
+
+=cut
+
+sub read_cnt  {
+       my $self = shift;
+
+       croak "missing CNT file in ",$self->{isisdb} unless ($self->{cnt_file});
 
        # Get the index information from $db.CNT
    
-       open(fileCNT, $self->{isisdb}.".CNT") || croak "can't read '$self->{isisdb}.CNT': $!";
+       open(fileCNT, $self->{cnt_file}) || croak "can't read '$self->{cnt_file}': $!";
 
        # There is two 26 Bytes fixed lenght records
 
@@ -173,88 +252,89 @@ sub new {
                my $buff = shift || return;
                my @arr = unpack("ssssssllls", $buff);
 
+               print STDERR "unpack_cnt: ",join(" ",@arr),"\n" if ($self->{'debug'});
+
                my $IDTYPE = shift @arr;
                foreach (@flds) {
-                       $self->{$IDTYPE}->{$_} = abs(shift @arr);
+                       $self->{cnt}->{$IDTYPE}->{$_} = abs(shift @arr);
                }
        }
 
        my $buff;
+
        read(fileCNT, $buff, 26);
        $self->unpack_cnt($buff);
 
        read(fileCNT, $buff, 26);
        $self->unpack_cnt($buff);
 
-
        close(fileCNT);
 
-       print Dumper($self) if ($self->{debug});
-
-       $self ? return $self : return undef;
+       return $self->{cnt};
 }
 
-=head2 GetMFN
+=head2 fetch
 
 Read record with selected MFN
 
-  my $rec = $isis->GetMFN(55);
+  my $rec = $isis->fetch(55);
 
 Returns hash with keys which are field names and values are unpacked values
-for that field.
+for that field like this:
+
+  $rec = {
+    '210' => [ '^aNew York^cNew York University press^dcop. 1988' ],
+    '990' => [ '2140', '88', 'HAY' ],
+  };
 
 =cut
 
-sub GetMFN {
+sub fetch {
        my $self = shift;
 
-       my $mfn = shift || croak "GetMFN needs MFN as argument!";
+       my $mfn = shift || croak "fetch needs MFN as argument!";
 
-       print "GetMFN: $mfn\n" if ($self->{debug});
+       # is mfn allready in memory?
+       my $old_mfn = $self->{'current_mfn'} || -1;
+       return if ($mfn == $old_mfn);
 
-       open(fileXRF, $self->{isisdb}.".XRF") || croak "can't open '$self->{isisdb}.XRF': $!";
+       print STDERR "## fetch: $mfn\n" if ($self->{debug});
 
        # XXX check this?
        my $mfnpos=($mfn+int(($mfn-1)/127))*4;
 
-       print "seeking to $mfnpos in file '$self->{isisdb}.XRF'\n" if ($self->{debug});
-       seek(fileXRF,$mfnpos,0);
+       print STDERR "## seeking to $mfnpos in file '$self->{xrf_file}'\n" if ($self->{debug});
+       seek($self->{'fileXRF'},$mfnpos,0);
+
+       my $buff;
 
        # read XRFMFB abd XRFMFP
-       my $pointer=$self->Read32(\*fileXRF);
+       read($self->{'fileXRF'}, $buff, 4);
+       my $pointer=unpack("l",$buff) || carp "pointer is null";
 
        my $XRFMFB = int($pointer/2048);
        my $XRFMFP = $pointer - ($XRFMFB*2048);
 
-       print "XRFMFB: $XRFMFB XRFMFP: $XRFMFP\n" if ($self->{debug});
-
-       # XXX fix this to be more readable!!
-       # e.g. (XRFMFB - 1) * 512 + XRFMFP
 
-       my $offset = $pointer;
-       my $offset2=int($offset/2048)-1;
-       my $offset22=int($offset/4096);
-       my $offset3=$offset-($offset22*4096);
-       if ($offset3>512) {
-               $offset3=$offset3-2048;
-       }
-       my $offset4=($offset2*512)+$offset3;
+       # (XRFMFB - 1) * 512 + XRFMFP
+       # why do i have to do XRFMFP % 1024 ?
 
-       print "$offset - $offset2 - $offset3 - $offset4\n" if ($self->{debug});
+       my $blk_off = (($XRFMFB - 1) * 512) + ($XRFMFP % 1024);
 
-       close(fileXRF);
+       print STDERR "## pointer: $pointer XRFMFB: $XRFMFB XRFMFP: $XRFMFP offset: $blk_off\n" if ($self->{'debug'});
 
        # Get Record Information
 
-       open(fileMST, $self->{isisdb}.".MST") || croak "can't open '$self->{isisdb}.MST': $!";
+       seek($self->{'fileMST'},$blk_off,0);
 
-       seek(fileMST,$offset4,0);
+       read($self->{'fileMST'}, $buff, 4);
+       my $value=unpack("l",$buff);
 
-       my $value=$self->Read32(\*fileMST);
+       print STDERR "## offset for rowid $value is $blk_off (blk $XRFMFB off $XRFMFP)\n" if ($self->{debug});
 
        if ($value!=$mfn) {
-print ("Error: The MFN:".$mfn." is not found in MST(".$value.")");    
-               return -1;      # XXX deleted record?
+               carp "Error: MFN ".$mfn." not found in MST(".$value.")";    
+               #return;                # XXX deleted record?
        }
 
 #      $MFRL=$self->Read16($fileMST);
@@ -264,12 +344,22 @@ print ("Error: The MFN:".$mfn." is not found in MST(".$value.")");
 #      $NVF=$self->Read16($fileMST);
 #      $STATUS=$self->Read16($fileMST);
 
-       my $buff;
-       read(fileMST, $buff, 14);
+       read($self->{'fileMST'}, $buff, 14);
 
        my ($MFRL,$MFBWB,$MFBWP,$BASE,$NVF,$STATUS) = unpack("slssss", $buff);
 
-       print "MFRL: $MFRL MFBWB: $MFBWB MFBWP: $MFBWP BASE: $BASE NVF: $NVF STATUS: $STATUS\n" if ($self->{debug});
+       print STDERR "## MFRL: $MFRL MFBWB: $MFBWB MFBWP: $MFBWP BASE: $BASE NVF: $NVF STATUS: $STATUS\n" if ($self->{debug});
+
+       # delete old record
+       delete $self->{record};
+
+       ## FIXME this is a bug
+       if (! $self->{'include_deleted'} && $MFRL < 0) {
+               print "## logically deleted record $mfn, skipping...\n" if ($self->{debug});
+               return;
+       }
+
+       warn "BASE is not 18+6*NVF" unless ($BASE == 18 + 6 * $NVF);
 
        # Get Directory Format
 
@@ -277,16 +367,19 @@ print ("Error: The MFN:".$mfn." is not found in MST(".$value.")");
        my @FieldLEN;
        my @FieldTAG;
 
+       read($self->{'fileMST'}, $buff, 6 * $NVF);
+
+       my $rec_len = 0;
+
        for (my $i = 0 ; $i < $NVF ; $i++) {
 
 #              $TAG=$self->Read16($fileMST);
 #              $POS=$self->Read16($fileMST);
 #              $LEN=$self->Read16($fileMST);
 
-               read(fileMST, $buff, 6);
-               my ($TAG,$POS,$LEN) = unpack("sss", $buff);
+               my ($TAG,$POS,$LEN) = unpack("sss", substr($buff,$i * 6, 6));
 
-               print "TAG: $TAG POS: $POS LEN: $LEN\n" if ($self->{debug});
+               print STDERR "## TAG: $TAG POS: $POS LEN: $LEN\n" if ($self->{debug});
 
                # The TAG does not exists in .FDT so we set it to 0.
                #
@@ -301,34 +394,45 @@ print ("Error: The MFN:".$mfn." is not found in MST(".$value.")");
                push @FieldTAG,$TAG;
                push @FieldPOS,$POS;
                push @FieldLEN,$LEN;
+
+               $rec_len += $LEN;
        }
 
        # Get Variable Fields
 
-       delete $self->{record};
+       read($self->{'fileMST'},$buff,$rec_len);
+
+       print STDERR "## rec_len: $rec_len poc: ",tell($self->{'fileMST'})."\n" if ($self->{debug});
 
        for (my $i = 0 ; $i < $NVF ; $i++) {
-               my $rec;
-               read(fileMST,$rec,$FieldLEN[$i]);
-               push @{$self->{record}->{$FieldTAG[$i]}}, $rec;
-       }
-       close(fileMST);
+               # skip zero-sized fields
+               next if ($FieldLEN[$i] == 0);
 
-       # The record is marked for deletion
-       if ($STATUS==1) {
-               return -1;
+               push @{$self->{record}->{$FieldTAG[$i]}}, substr($buff,$FieldPOS[$i],$FieldLEN[$i]);
        }
 
-       print Dumper($self) if ($self->{debug});
+       $self->{'current_mfn'} = $mfn;
+
+       print Dumper($self),"\n" if ($self->{debug});
 
        return $self->{'record'};
 }
 
 =head2 to_ascii
 
-Dump ascii output of selected MFN
+Dump ASCII output of record with specified MFN
+
+  print $isis->to_ascii(42);
+
+It outputs something like this:
+
+  210  ^aNew York^cNew York University press^dcop. 1988
+  990  2140
+  990   88
+  990  HAY
 
-  print $isis->to_ascii(55);
+If C<read_fdt> is specified when calling C<new> it will display field names
+from C<.FDT> file instead of numeric tags.
 
 =cut
 
@@ -337,14 +441,13 @@ sub to_ascii {
 
        my $mfn = shift || croak "need MFN";
 
-       my $rec = $self->GetMFN($mfn);
-
-print STDERR Dumper($rec);
+       my $rec = $self->fetch($mfn);
 
        my $out = "0\t$mfn";
 
        foreach my $f (sort keys %{$rec}) {
-               $out .= "\n$f\t".join("\n$f\t",@{$self->{record}->{$f}});
+               my $fn = $self->tag_name($f);
+               $out .= "\n$fn\t".join("\n$fn\t",@{$self->{record}->{$f}});
        }
 
        $out .= "\n";
@@ -352,378 +455,105 @@ print STDERR Dumper($rec);
        return $out;
 }
 
-################# old cruft which is not ported from php to perl
-
-=begin php
-
-  # Load the dictionary from the $db.L0x files.
-  # Not usefull Yet
-  
-  sub LoadDictionary()
-  {
-    $fileL01=fopen($self->{isisdb}.".L01","r");
-    rewind($fileL01);  
-
-    do
-    {
-
-      $POS=$self->Read32($fileL01);
-      $OCK=$self->Read16($fileL01);
-      $IT=$self->Read16($fileL01);
-      $PS=$self->Read32($fileL01);
-print "<br>PS:".$PS." ".$self->{ORDF}->{1}." ";
-      for ($i=0;$i<$OCK;$i++)
-      {
-        $KEY=fread($fileL01,10);
-       
-        print $KEY." ### ";
-
-        $INFO1=$self->Read32($fileL01);
-        $INFO2=$self->Read32($fileL01);
-
-        #L01Key->{$key}=array($INFO1,$INFO2);
-      }
-    
-      rewind($fileL01);
-      $offset=($PS-1)*(12+$self->{ORDF}->{1}*18*2);
-      fseek($fileL01,$offset);
-
-    } While (!feof($fileL01));
-
-    fclose($fileL01);
-  }
+=head2 to_hash
 
-  # self function search through the tree and returns an array of pointers to IFP
-  # The function must be recursive
-
-  sub SearchTree($search,$fileNB,$PUNT)
-  {       
-      $offset=(($PUNT-1)*(8+2*$self->{ORDN}->{1}*14)); 
-
-        rewind($fileNB1); 
-
-        fseek($fileNB,$offset);
-        $POS=$self->Read32($fileNB);
-        $OCK=$self->Read16($fileNB);
-        $IT=$self->Read16($fileNB);
-
-#print "<br>".$POS." - ".$OCK." - ".$IT;
-
-        $OLDPUNT=$POS;
-        $j=0;
-        for ($i=0;$i<$OCK;$i++)
-        {
-          $KEY=fread($fileNB,10);
-       
-          $PUNT=$self->Read32($fileNB);
-
-#print " ## ".chop($KEY)."(".$PUNT."-".$OLDPUNT.") ## "; 
-
-          If (strcmp($search,chop($KEY))<0)
-          {
-            break;
-          }
-          $OLDPUNT=$PUNT;   
-        }        
-#print $OLDPUNT; 
-        Return $OLDPUNT;
-  }
+Read record with specified MFN and convert it to hash
 
-  # Search ISIS for record containing search
-  # Return a sorted array of MFN
+  my $hash = $isis->to_hash($mfn);
 
-  sub Search($search)
-  {
+It has ability to convert characters (using C<hash_filter> from ISIS
+database before creating structures enabling character re-mapping or quick
+fix-up of data.
 
-  $search=strtoupper($search);
-#print "Searching....".$search." - ".$self->{POSRX}->{1}."<br>";
-    # first search .x01
-    
-
-    # Search in .N01  
-
-
-    $fileN01=fopen($self->{isisdb}.".N01","r");
-    $offset=(($self->{POSRX}->{1}-1)*(8+2*$self->{ORDN}->{1}*14));
-
-      do
-      {
-        rewind($fileN01); 
-
-        fseek($fileN01,$offset);
-        $POS=$self->Read32($fileN01);
-        $OCK=$self->Read16($fileN01);
-        $IT=$self->Read16($fileN01);
-
-#print "<br>".$POS." - ".$OCK." - ".$IT;
-
-        $OLDPUNT=$POS;
-        for ($i=0;$i<$OCK;$i++)
-        {
-          $KEY=fread($fileN01,10);
-       
-          $PUNT=$self->Read32($fileN01);
-
-#print " ## ".chop($KEY)."(".$PUNT."-".$OLDPUNT.") ## "; 
-
-          If (strcmp($search,chop($KEY))<0)
-          {
-            break;
-          }
-          $OLDPUNT=$PUNT;   
-        }
-        $offset=(($OLDPUNT-1)*(8+2*$self->{ORDN}->{1}*14));      
-      } while ($OLDPUNT>0);
-#print $OLDPUNT; 
-
-
-    fclose($fileN01);
-
-    # Now look for records in .L01 file
-    $fileL01=fopen($self->{isisdb}.".L01","r");
-    rewind($fileL01);
-
-    $offset=(-$OLDPUNT-1)*(12+$self->{ORDF}->{1}*18*2);
-    fseek($fileL01,$offset);
-
-    $POS=$self->Read32($fileL01);
-    $OCK=$self->Read16($fileL01);
-    $IT=$self->Read16($fileL01);
-    $PS=$self->Read32($fileL01);
-#print "<br>POS:".$POS." ".$self->{ORDF}->{1}." ";
-    for ($i=0;$i<$OCK;$i++)
-    {
-      $KEY=fread($fileL01,10);
-       
-#print $KEY." ### ";
-
-      $INFO1=$self->Read32($fileL01);
-      $INFO2=$self->Read32($fileL01);
-
-      If (strcmp($search,chop($KEY))==0)
-      {
-        break;
-      }
-    }    
-
-    fclose($fileL01);
-
-#print $INFO1."--".$INFO2;
-
-    # Now look in .IFP for the MFN
-    $fileIFP=fopen($self->{isisdb}.".IFP","r");
-    rewind($fileIFP);
-    $offset=($INFO1-1)*512+($INFO2*4);
-    fseek($fileIFP,$offset);   
-    $IFPBLK=$self->Read32($fileIFP);
-
-    $IFPNXTB=$self->Read32($fileIFP);
-    $IFPNXTP=$self->Read32($fileIFP);
-    $IFPTOTP=$self->Read32($fileIFP);
-    $IFPSEGP=$self->Read32($fileIFP);
-    $IFPSEGC=$self->Read32($fileIFP);
-
-
-#print "<br>IFP:".$IFPBLK." # ".$IFPNXTB." - ".$IFPNXTP." - ".$IFPTOTP." - ".$IFPSEGP." - ".$IFPSEGC;
-
-    rewind($fileIFP);
-    $offset=($INFO1-1)*512+24+($INFO2*4);
-    fseek($fileIFP,$offset);    
-    
-    $j=24+($INFO2*4);
-    $k=0;
-    $l=1;
-    $OLDPMFN="";
-    for ($i=0;$i<$IFPSEGP;$i++)
-    {
-      $B1=$self->Read8($fileIFP);
-      $B2=$self->Read8($fileIFP);
-      $B3=$self->Read8($fileIFP);
-      $B4=$self->Read8($fileIFP);
-      $B5=$self->Read8($fileIFP);
-      $B6=$self->Read8($fileIFP);
-      $B7=$self->Read8($fileIFP);
-      $B8=$self->Read8($fileIFP);
-
-      $PMFN=$B1*65536+$B2*256+$B3;
-      $PTAG=$B4*256+$B5;
-      $POCC=$B6;
-      $PCNT=$B7*256+$B8;
-
-      if ($OLDPMFN!=$PMFN)
-      {
-        if ($PMFN!=0)
-        {
-          $self->{MFNArray}->{$l}=$PMFN;
-          $OLDPMFN=$PMFN;
-          $l+=1;
-        }
-      }
-
-      $j=$j+8;
-#print "<br>".$PMFN."-".$PTAG." - ".$POCC." - ".$PCNT;
-#print "@@".$j."@@@@";
-      if ($j>=504)
-      {
-        if ($IFPNXTB==0 && $IFPNXTP==0)
-        {
-          $k=$k+1;
-          rewind($fileIFP);
-          $offset=($INFO1-1+$k)*512;  
-          fseek($fileIFP,$offset);      
-          $B=$self->Read32($fileIFP);
-#print "<br>-".$B."-<br>";
-          $j=0;
-        } else
-        {
-          rewind($fileIFP);
-          $offset=($IFPNXTB-1)*512;  
-          fseek($fileIFP,$offset);
-
-         $OLDIFPNXTB=$IFPNXTB;
-         $OLDIFPNXTP=$IFPNXTP;
-
-          $IFPBLK=$self->Read32($fileIFP);
-
-          $IFPNXTB=$self->Read32($fileIFP);
-          $IFPNXTP=$self->Read32($fileIFP);
-          $IFPTOTP=$self->Read32($fileIFP);
-          $IFPSEGP=$self->Read32($fileIFP);
-          $IFPSEGC=$self->Read32($fileIFP);
-
-          rewind($fileIFP);
-          $offset=($OLDIFPNXTB-1)*512+24+($OLDIFPNXTP*4);
-          fseek($fileIFP,$offset);    
-    
-          $j=24+($OLDIFPNXTP*4);
-          $k=0;
-          $j=0;
-        }
-      }
-
-    }    
-    fclose($fileIFP);
-    return $l-1;
-  }
+This function returns hash which is like this:
 
-=cut
+  $hash = {
+    '210' => [
+               {
+                 'c' => 'New York University press',
+                 'a' => 'New York',
+                 'd' => 'cop. 1988'
+               }
+             ],
+    '990' => [
+               '2140',
+               '88',
+               'HAY'
+             ],
+  };
 
-#
-# XXX porting from php left-over:
-#
-# do I *REALLY* need those methods, or should I use
-# $self->{something} directly?
-#
-# Probably direct usage is better!
-#
+You can later use that hash to produce any output from ISIS data.
 
-sub GetFieldName {
-       my $self = shift;
-       return $self->{FieldName};
-}
+If database is created using IsisMarc, it will also have to special fields
+which will be used for identifiers, C<i1> and C<i2> like this:
 
-sub GetTagName {
-       my $self = shift;
-       return $self->{TagName};
-}
+  '200' => [
+             {
+               'i1' => '1',
+               'i2' => ' '
+               'a' => 'Goa',
+               'f' => 'Valdo D\'Arienzo',
+               'e' => 'tipografie e tipografi nel XVI secolo',
+             }
+           ],
 
-sub GetFieldTag {
-       my $self = shift;
-       return $self->{FieldTAG};
-}
+This method will also create additional field C<000> with MFN.
 
-sub GetNextMFN {
-       my $self = shift;
-       return $self->{NXTMFN};
-}
+=cut
 
-sub GetMFNArray {
+sub to_hash {
        my $self = shift;
-       return $self->{MFNArray};
-}
-=begin php
-
-  sub Read32($fileNB)
-  {
-    $B1=ord(fread($fileNB,1));
-    $B2=ord(fread($fileNB,1));
-    $B3=ord(fread($fileNB,1));
-    $B4=ord(fread($fileNB,1));
-
-    if ($B4<=128)
-    {
-      $value=$B1+$B2*256+$B3*65536+$B4*16777216;
-    } else
-    {
-      $value=$self->Not8($B1)+$self->Not8($B2)*256+$self->Not8($B3)*65536+$self->Not8($B4)*16777216;
-      $value=-($value+1);
-    }
-#    print "(".$B1.",".$B2.",".$B3.",".$B4.":".$value.")";
-
-    return $value;   
-  }
-
-  sub Read24($fileNB)
-  {
-    $B1=ord(fread($fileNB,1));
-    $B2=ord(fread($fileNB,1));
-    $B3=ord(fread($fileNB,1));
 
-    $value=$B1+$B2*256+$B3*65536;
+       my $mfn = shift || confess "need mfn!";
 
-#    print "(".$B1.",".$B2.",".$B3.":".$value.")";
+       # init record to include MFN as field 000
+       my $rec = { '000' => [ $mfn ] };
 
-    return $value;   
-  }
+       my $row = $self->fetch($mfn);
 
-  sub Read16($fileNB)
-  {
-    $B1=ord(fread($fileNB,1));
-    $B2=ord(fread($fileNB,1));
+       foreach my $k (keys %{$row}) {
+               foreach my $l (@{$row->{$k}}) {
 
-    $value=$B1+$B2*256;
-#    print "(".$B1.",".$B2.":".$value.")";
+                       # filter output
+                       $l = $self->{'hash_filter'}->($l) if ($self->{'hash_filter'});
 
-    return $value;  
-  }
+                       my $val;
 
-  sub Read8($fileNB)
-  {
-    $B1=ord(fread($fileNB,1));
+                       # has identifiers?
+                       ($val->{'i1'},$val->{'i2'}) = ($1,$2) if ($l =~ s/^([01 #])([01 #])//);
 
-    $value=$B1;
-#    print "(".$value.")";
+                       # has subfields?
+                       if ($l =~ m/\^/) {
+                               foreach my $t (split(/\^/,$l)) {
+                                       next if (! $t);
+                                       $val->{substr($t,0,1)} = substr($t,1);
+                               }
+                       } else {
+                               $val = $l;
+                       }
 
-    return $value;  
-  }
+                       push @{$rec->{$k}}, $val;
+               }
+       }
 
-  sub Not8($value)
-  { 
-    $value=decbin($value);
-    if (strlen($value)<8)
-    {
-      $buffer="";
-      for($i=0;$i<(8-strlen($value));$i++)
-      {
-        $buffer.="0";
-      }
-      $value=$buffer.$value;
-    }
-    $value=ereg_replace("0","3",$value);
-    $value=ereg_replace("1","0",$value);
-    $value=ereg_replace("3","1",$value); 
-    $value=bindec($value);
-    return $value;
-  }
+       return $rec;
 }
 
+=head2 tag_name
+
+Return name of selected tag
+
+ print $isis->tag_name('200');
+
 =cut
 
+sub tag_name {
+       my $self = shift;
+       my $tag = shift || return;
+       return $self->{'TagName'}->{$tag} || $tag;
+}
+
 1;
-__END__
 
 =head1 BUGS
 
@@ -736,8 +566,8 @@ This module has been very lightly tested. Use with caution and report bugs.
        dpavlin@rot13.org
        http://www.rot13.org/~dpavlin/
 
-This module is based heavily on code from LIBISIS.PHP - Library to read ISIS files V0.1.1
-written in php and (c) 2000 Franck Martin - <franck@sopac.org> released under LGPL.
+This module is based heavily on code from C<LIBISIS.PHP> library to read ISIS files V0.1.1
+written in php and (c) 2000 Franck Martin <franck@sopac.org> and released under LGPL.
 
 =head1 COPYRIGHT
 
@@ -750,5 +580,7 @@ LICENSE file included with this module.
 
 =head1 SEE ALSO
 
-L<http://www.openisis.org|OpenIsis>, perl(1).
+OpenIsis web site L<http://www.openisis.org>
+
+perl4lib site L<http://perl4lib.perl.org>