9 use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
12 #Give a hoot don't pollute, do not export more than needed by default
21 IsisDB - Read CDS/ISIS database
26 my $isis = new IsisDB(
27 isisdb => './cds/cds',
32 This module will read CDS/ISIS databases and create hash values out of it.
33 It can be used as perl-only alternative to OpenIsis module.
39 # my $ORDN; # Nodes Order
40 # my $ORDF; # Leafs Order
41 # my $N; # Number of Memory buffers for nodes
42 # my $K; # Number of buffers for first level index
43 # my $LIV; # Current number of Index Levels
44 # my $POSRX; # Pointer to Root Record in N0x
45 # my $NMAXPOS; # Next Available position in N0x
46 # my $FMAXPOS; # Next available position in L0x
47 # my $ABNORMAL; # Formal BTree normality indicator
56 my $f = shift || die "Read32 needs file handle";
57 read($$f,$b,4) || die "can't read 4 bytes from $$f from position ".tell($f);
58 return unpack("l",$b);
63 Open CDS/ISIS database
65 my $isis = new IsisDB(
66 isisdb => './cds/cds',
73 Prefix path to CDS/ISIS. It should contain full or relative path to database
74 and common prefix of C<.FDT>, C<.MST>, C<.CNT>, C<.XRF> and C<.MST> files.
78 Boolean flag to specify if field definition table should be read. It's off
83 Dump a C<lot> of debugging output.
92 $self->{isisdb} = {@_}->{isisdb} || croak "new needs database name as argument!";
94 $self->{debug} = {@_}->{debug} || 1; # XXX remove debug always!
96 # if you want to read .FDT file use read_fdt argument when creating class!
97 if ({@_}->{read_fdt} && -e $self->{isisdb}.".FDT") {
99 # read the $db.FDT file for tags
102 open(fileFDT, $self->{isisdb}.".FDT") || croak "can't read '$self->{isisdb}.FDT': $!";
107 my $name=substr($_,0,30);
108 my $tag=substr($_,50,3);
113 $self->{'TagName'}->{$tag}=$name;
124 # Get the Maximum MFN from $db.MST
126 open(fileMST,$self->{isisdb}.".MST") || croak "can't read '$self->{isisdb}.MST': $!";
128 # MST format: (* = 32 bit signed)
130 # NXTMFN* MFN to be assigned to the next record created
131 # NXTMFB* last block allocated to master file
132 # NXTMFP offset to next available position in last block
133 # MFTYPE always 0 for user db file (1 for system)
135 $self->{'NXTMFN'}=$self->Read32(\*fileMST) || carp "NXTNFN is zero";
139 # Get the index information from $db.CNT
141 open(fileCNT, $self->{isisdb}.".CNT") || croak "can't read '$self->{isisdb}.CNT': $!";
143 # There is two 26 Bytes fixed lenght records
145 # 0: IDTYPE BTree type 16
146 # 2: ORDN Nodes Order 16
147 # 4: ORDF Leafs Order 16
148 # 6: N Number of Memory buffers for nodes 16
149 # 8: K Number of buffers for first level index 16
150 # 10: LIV Current number of Index Levels 16
151 # 12: POSRX* Pointer to Root Record in N0x 32
152 # 16: NMAXPOS* Next Available position in N0x 32
153 # 20: FMAXPOS* Next available position in L0x 32
154 # 24: ABNORMAL Formal BTree normality indicator 16
160 my @flds = qw(ORDN ORDF N K LIV POSRX NMAXPOS FMAXPOS ABNORMAL);
162 my $buff = shift || return;
163 my @arr = unpack("ssssssllls", $buff);
165 my $IDTYPE = shift @arr;
167 $self->{$IDTYPE}->{$_} = abs(shift @arr);
172 read(fileCNT, $buff, 26);
173 $self->unpack_cnt($buff);
175 read(fileCNT, $buff, 26);
176 $self->unpack_cnt($buff);
181 print Dumper($self) if ($self->{debug});
183 $self ? return $self : return undef;
187 # Get a record from the MFN
188 # Return the number of fields in the record.
189 # Return -1 if the record is marked for deletion
190 # The record is then extracted with call to GETs
195 my $mfn = shift || croak "GetMFN needs MFN as argument!";
197 print "GetMFN: $mfn\n" if ($self->{debug});
199 open(fileXRF, $self->{isisdb}.".XRF") || croak "can't open '$self->{isisdb}.XRF': $!";
202 my $mfnpos=($mfn+int(($mfn-1)/127))*4;
204 print "seeking to $mfnpos in file '$self->{isisdb}.XRF'\n" if ($self->{debug});
205 seek(fileXRF,$mfnpos,0);
207 # read XRFMFB abd XRFMFP
208 my $pointer=$self->Read32(\*fileXRF);
210 my $XRFMFB = int($pointer/2048);
211 my $XRFMFP = $pointer - ($XRFMFB*2048);
213 print "XRFMFB: $XRFMFB XRFMFP: $XRFMFP\n" if ($self->{debug});
215 # XXX fix this to be more readable!!
216 # e.g. (XRFMFB - 1) * 512 + XRFMFP
218 my $offset = $pointer;
219 my $offset2=int($offset/2048)-1;
220 my $offset22=int($offset/4096);
221 my $offset3=$offset-($offset22*4096);
223 $offset3=$offset3-2048;
225 my $offset4=($offset2*512)+$offset3;
227 print "$offset - $offset2 - $offset3 - $offset4\n" if ($self->{debug});
231 # Get Record Information
233 open(fileMST, $self->{isisdb}.".MST") || croak "can't open '$self->{isisdb}.MST': $!";
235 seek(fileMST,$offset4,0);
237 my $value=$self->Read32(\*fileMST);
240 print ("Error: The MFN:".$mfn." is not found in MST(".$value.")");
241 return -1; # XXX deleted record?
244 # $MFRL=$self->Read16($fileMST);
245 # $MFBWB=$self->Read32($fileMST);
246 # $MFBWP=$self->Read16($fileMST);
247 # $BASE=$self->Read16($fileMST);
248 # $NVF=$self->Read16($fileMST);
249 # $STATUS=$self->Read16($fileMST);
252 read(fileMST, $buff, 14);
254 my ($MFRL,$MFBWB,$MFBWP,$BASE,$NVF,$STATUS) = unpack("slssss", $buff);
256 print "MFRL: $MFRL MFBWB: $MFBWB MFBWP: $MFBWP BASE: $BASE NVF: $NVF STATUS: $STATUS\n" if ($self->{debug});
258 # Get Directory Format
264 for (my $i = 0 ; $i < $NVF ; $i++) {
266 # $TAG=$self->Read16($fileMST);
267 # $POS=$self->Read16($fileMST);
268 # $LEN=$self->Read16($fileMST);
270 read(fileMST, $buff, 6);
271 my ($TAG,$POS,$LEN) = unpack("sss", $buff);
273 print "TAG: $TAG POS: $POS LEN: $LEN\n" if ($self->{debug});
275 # The TAG does not exists in .FDT so we set it to 0.
277 # XXX This is removed from perl version; .FDT file is updated manually, so
278 # you will often have fields in .MST file which aren't in .FDT. On the other
279 # hand, IsisMarc doesn't use .FDT files at all!
281 #if (! $self->{TagName}->{$TAG}) {
290 # Get Variable Fields
292 for (my $i = 0 ; $i < $NVF ; $i++) {
294 read(fileMST,$rec,$FieldLEN[$i]);
295 $self->{record}->{$FieldTAG[$i]} = $rec;
299 # The record is marked for deletion
304 print Dumper($self) if ($self->{debug});
311 # Load the dictionary from the $db.L0x files.
316 $fileL01=fopen($self->{isisdb}.".L01","r");
322 $POS=$self->Read32($fileL01);
323 $OCK=$self->Read16($fileL01);
324 $IT=$self->Read16($fileL01);
325 $PS=$self->Read32($fileL01);
326 print "<br>PS:".$PS." ".$self->{ORDF}->{1}." ";
327 for ($i=0;$i<$OCK;$i++)
329 $KEY=fread($fileL01,10);
333 $INFO1=$self->Read32($fileL01);
334 $INFO2=$self->Read32($fileL01);
336 #L01Key->{$key}=array($INFO1,$INFO2);
340 $offset=($PS-1)*(12+$self->{ORDF}->{1}*18*2);
341 fseek($fileL01,$offset);
343 } While (!feof($fileL01));
348 # self function search through the tree and returns an array of pointers to IFP
349 # The function must be recursive
351 sub SearchTree($search,$fileNB,$PUNT)
353 $offset=(($PUNT-1)*(8+2*$self->{ORDN}->{1}*14));
357 fseek($fileNB,$offset);
359 $POS=$self->Read32($fileNB);
360 $OCK=$self->Read16($fileNB);
361 $IT=$self->Read16($fileNB);
363 #print "<br>".$POS." - ".$OCK." - ".$IT;
367 for ($i=0;$i<$OCK;$i++)
369 $KEY=fread($fileNB,10);
371 $PUNT=$self->Read32($fileNB);
373 #print " ## ".chop($KEY)."(".$PUNT."-".$OLDPUNT.") ## ";
375 If (strcmp($search,chop($KEY))<0)
385 # Search ISIS for record containing search
386 # Return a sorted array of MFN
391 $search=strtoupper($search);
392 #print "Searching....".$search." - ".$self->{POSRX}->{1}."<br>";
399 $fileN01=fopen($self->{isisdb}.".N01","r");
400 $offset=(($self->{POSRX}->{1}-1)*(8+2*$self->{ORDN}->{1}*14));
406 fseek($fileN01,$offset);
408 $POS=$self->Read32($fileN01);
409 $OCK=$self->Read16($fileN01);
410 $IT=$self->Read16($fileN01);
412 #print "<br>".$POS." - ".$OCK." - ".$IT;
415 for ($i=0;$i<$OCK;$i++)
417 $KEY=fread($fileN01,10);
419 $PUNT=$self->Read32($fileN01);
421 #print " ## ".chop($KEY)."(".$PUNT."-".$OLDPUNT.") ## ";
423 If (strcmp($search,chop($KEY))<0)
429 $offset=(($OLDPUNT-1)*(8+2*$self->{ORDN}->{1}*14));
430 } while ($OLDPUNT>0);
436 # Now look for records in .L01 file
437 $fileL01=fopen($self->{isisdb}.".L01","r");
440 $offset=(-$OLDPUNT-1)*(12+$self->{ORDF}->{1}*18*2);
441 fseek($fileL01,$offset);
443 $POS=$self->Read32($fileL01);
444 $OCK=$self->Read16($fileL01);
445 $IT=$self->Read16($fileL01);
446 $PS=$self->Read32($fileL01);
447 #print "<br>POS:".$POS." ".$self->{ORDF}->{1}." ";
448 for ($i=0;$i<$OCK;$i++)
450 $KEY=fread($fileL01,10);
454 $INFO1=$self->Read32($fileL01);
455 $INFO2=$self->Read32($fileL01);
457 If (strcmp($search,chop($KEY))==0)
465 #print $INFO1."--".$INFO2;
467 # Now look in .IFP for the MFN
468 $fileIFP=fopen($self->{isisdb}.".IFP","r");
470 $offset=($INFO1-1)*512+($INFO2*4);
471 fseek($fileIFP,$offset);
473 $IFPBLK=$self->Read32($fileIFP);
475 $IFPNXTB=$self->Read32($fileIFP);
476 $IFPNXTP=$self->Read32($fileIFP);
477 $IFPTOTP=$self->Read32($fileIFP);
478 $IFPSEGP=$self->Read32($fileIFP);
479 $IFPSEGC=$self->Read32($fileIFP);
482 #print "<br>IFP:".$IFPBLK." # ".$IFPNXTB." - ".$IFPNXTP." - ".$IFPTOTP." - ".$IFPSEGP." - ".$IFPSEGC;
485 $offset=($INFO1-1)*512+24+($INFO2*4);
486 fseek($fileIFP,$offset);
492 for ($i=0;$i<$IFPSEGP;$i++)
494 $B1=$self->Read8($fileIFP);
495 $B2=$self->Read8($fileIFP);
496 $B3=$self->Read8($fileIFP);
497 $B4=$self->Read8($fileIFP);
498 $B5=$self->Read8($fileIFP);
499 $B6=$self->Read8($fileIFP);
500 $B7=$self->Read8($fileIFP);
501 $B8=$self->Read8($fileIFP);
503 $PMFN=$B1*65536+$B2*256+$B3;
512 $self->{MFNArray}->{$l}=$PMFN;
519 #print "<br>".$PMFN."-".$PTAG." - ".$POCC." - ".$PCNT;
520 #print "@@".$j."@@@@";
523 if ($IFPNXTB==0 && $IFPNXTP==0)
527 $offset=($INFO1-1+$k)*512;
528 fseek($fileIFP,$offset);
529 $B=$self->Read32($fileIFP);
530 #print "<br>-".$B."-<br>";
535 $offset=($IFPNXTB-1)*512;
536 fseek($fileIFP,$offset);
538 $OLDIFPNXTB=$IFPNXTB;
539 $OLDIFPNXTP=$IFPNXTP;
541 $IFPBLK=$self->Read32($fileIFP);
543 $IFPNXTB=$self->Read32($fileIFP);
544 $IFPNXTP=$self->Read32($fileIFP);
545 $IFPTOTP=$self->Read32($fileIFP);
546 $IFPSEGP=$self->Read32($fileIFP);
547 $IFPSEGC=$self->Read32($fileIFP);
550 $offset=($OLDIFPNXTB-1)*512+24+($OLDIFPNXTP*4);
551 fseek($fileIFP,$offset);
553 $j=24+($OLDIFPNXTP*4);
567 # XXX porting from php left-over:
569 # do I *REALLY* need those methods, or should I use
570 # $self->{something} directly?
572 # Probably direct usage is better!
577 return $self->{FieldName};
582 return $self->{TagName};
587 return $self->{FieldTAG};
592 return $self->{NXTMFN};
597 return $self->{MFNArray};
603 $B1=ord(fread($fileNB,1));
604 $B2=ord(fread($fileNB,1));
605 $B3=ord(fread($fileNB,1));
606 $B4=ord(fread($fileNB,1));
610 $value=$B1+$B2*256+$B3*65536+$B4*16777216;
613 $value=$self->Not8($B1)+$self->Not8($B2)*256+$self->Not8($B3)*65536+$self->Not8($B4)*16777216;
616 # print "(".$B1.",".$B2.",".$B3.",".$B4.":".$value.")";
623 $B1=ord(fread($fileNB,1));
624 $B2=ord(fread($fileNB,1));
625 $B3=ord(fread($fileNB,1));
627 $value=$B1+$B2*256+$B3*65536;
629 # print "(".$B1.",".$B2.",".$B3.":".$value.")";
636 $B1=ord(fread($fileNB,1));
637 $B2=ord(fread($fileNB,1));
640 # print "(".$B1.",".$B2.":".$value.")";
647 $B1=ord(fread($fileNB,1));
650 # print "(".$value.")";
657 $value=decbin($value);
658 if (strlen($value)<8)
661 for($i=0;$i<(8-strlen($value));$i++)
665 $value=$buffer.$value;
667 $value=ereg_replace("0","3",$value);
668 $value=ereg_replace("1","0",$value);
669 $value=ereg_replace("3","1",$value);
670 $value=bindec($value);
682 This module has been very lightly tested. Use with caution and report bugs.
689 http://www.rot13.org/~dpavlin/
691 This module is based heavily on code from LIBISIS.PHP - Library to read ISIS files V0.1.1
692 written in php and (c) 2000 Franck Martin - <franck@sopac.org> released under LGPL.
696 This program is free software; you can redistribute
697 it and/or modify it under the same terms as Perl itself.
699 The full text of the license can be found in the
700 LICENSE file included with this module.
705 L<http://www.openisis.org|OpenIsis>, perl(1).