File Coverage

blib/lib/Biblio/Isis.pm
Criterion Covered Total %
statement 186 221 84.1
branch 68 122 55.7
condition 23 43 53.4
subroutine 16 16 100.0
pod 9 9 100.0
total 302 411 73.4


line stmt bran cond sub pod time code
1             package Biblio::Isis;
2 2     2   71809 use strict;
  2         5  
  2         72  
3              
4 2     2   14 use Carp;
  2         4  
  2         158  
5 2     2   10 use File::Glob qw(:globally :nocase);
  2         8  
  2         277  
6              
7             BEGIN {
8 2     2   9 use Exporter ();
  2         2  
  2         42  
9 2     2   10 use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         4  
  2         229  
10 2     2   3 $VERSION = 0.24;
11 2         40 @ISA = qw (Exporter);
12             #Give a hoot don't pollute, do not export more than needed by default
13 2         4 @EXPORT = qw ();
14 2         4 @EXPORT_OK = qw ();
15 2         770 %EXPORT_TAGS = ();
16              
17             }
18              
19             =head1 NAME
20              
21             Biblio::Isis - Read CDS/ISIS, WinISIS and IsisMarc database
22              
23             =head1 SYNOPSIS
24              
25             use Biblio::Isis;
26              
27             my $isis = new Biblio::Isis(
28             isisdb => './cds/cds',
29             );
30              
31             for(my $mfn = 1; $mfn <= $isis->count; $mfn++) {
32             print $isis->to_ascii($mfn),"\n";
33             }
34              
35             =head1 DESCRIPTION
36              
37             This module will read ISIS databases created by DOS CDS/ISIS, WinIsis or
38             IsisMarc. It can be used as perl-only alternative to OpenIsis module which
39             seems to depriciate it's old C bindings for perl.
40              
41             It can create hash values from data in ISIS database (using C),
42             ASCII dump (using C) or just hash with field names and packed
43             values (like C<^asomething^belse>).
44              
45             Unique feature of this module is ability to C records.
46             It will also skip zero sized fields (OpenIsis has a bug in XS bindings, so
47             fields which are zero sized will be filled with random junk from memory).
48              
49             It also has support for identifiers (only if ISIS database is created by
50             IsisMarc), see C.
51              
52             This module will always be slower than OpenIsis module which use C
53             library. However, since it's written in perl, it's platform independent (so
54             you don't need C compiler), and can be easily modified. I hope that it
55             creates data structures which are easier to use than ones created by
56             OpenIsis, so reduced time in other parts of the code should compensate for
57             slower performance of this module (speed of reading ISIS database is
58             rarely an issue).
59              
60             =head1 METHODS
61              
62             =cut
63              
64             # my $ORDN; # Nodes Order
65             # my $ORDF; # Leafs Order
66             # my $N; # Number of Memory buffers for nodes
67             # my $K; # Number of buffers for first level index
68             # my $LIV; # Current number of Index Levels
69             # my $POSRX; # Pointer to Root Record in N0x
70             # my $NMAXPOS; # Next Available position in N0x
71             # my $FMAXPOS; # Next available position in L0x
72             # my $ABNORMAL; # Formal BTree normality indicator
73              
74             #
75             # some binary reads
76             #
77              
78             =head2 new
79              
80             Open ISIS database
81              
82             my $isis = new Biblio::Isis(
83             isisdb => './cds/cds',
84             read_fdt => 1,
85             include_deleted => 1,
86             hash_filter => sub {
87             my ($v,$field_number) = @_;
88             $v =~ s#foo#bar#g;
89             },
90             debug => 1,
91             join_subfields_with => ' ; ',
92             );
93              
94             Options are described below:
95              
96             =over 5
97              
98             =item isisdb
99              
100             This is full or relative path to ISIS database files which include
101             common prefix of C<.MST>, and C<.XRF> and optionally C<.FDT> (if using
102             C option) files.
103              
104             In this example it uses C<./cds/cds.MST> and related files.
105              
106             =item read_fdt
107              
108             Boolean flag to specify if field definition table should be read. It's off
109             by default.
110              
111             =item include_deleted
112              
113             Don't skip logically deleted records in ISIS.
114              
115             =item hash_filter
116              
117             Filter code ref which will be used before data is converted to hash. It will
118             receive two arguments, whole line from current field (in C<< $_[0] >>) and
119             field number (in C<< $_[1] >>).
120              
121             =item debug
122              
123             Dump a B of debugging output even at level 1. For even more increase level.
124              
125             =item join_subfields_with
126              
127             Define delimiter which will be used to join repeatable subfields. This
128             option is included to support lagacy application written against version
129             older than 0.21 of this module. By default, it disabled. See L.
130              
131             =item ignore_empty_subfields
132              
133             Remove all empty subfields while reading from ISIS file.
134              
135             =back
136              
137             =cut
138              
139             sub new {
140 6     6 1 1244 my $class = shift;
141 6         14 my $self = {};
142 6         17 bless($self, $class);
143              
144 6 50       39 croak "new needs database name (isisdb) as argument!" unless ({@_}->{isisdb});
145              
146 6         20 foreach my $v (qw{isisdb debug include_deleted hash_filter join_subfields_with ignore_empty_subfields}) {
147 36 100       192 $self->{$v} = {@_}->{$v} if defined({@_}->{$v});
148             }
149              
150 6         1491 my @isis_files = grep(/\.(FDT|MST|XRF|CNT)$/i,glob($self->{isisdb}."*"));
151              
152 6         24 foreach my $f (@isis_files) {
153 22 50       104 my $ext = $1 if ($f =~ m/\.(\w\w\w)$/);
154 22         65 $self->{lc($ext)."_file"} = $f;
155             }
156              
157 6         23 my @must_exist = qw(mst xrf);
158 6 50       21 push @must_exist, "fdt" if ($self->{read_fdt});
159              
160 6         11 foreach my $ext (@must_exist) {
161 12 50       41 unless ($self->{$ext."_file"}) {
162 0         0 carp "missing ",uc($ext)," file in ",$self->{isisdb};
163 0         0 return;
164             }
165             }
166              
167 6 50       21 if ($self->{debug}) {
168 0         0 print STDERR "## using files: ",join(" ",@isis_files),"\n";
169 0         0 eval "use Data::Dump";
170              
171 0 0       0 if (! $@) {
172 0         0 *Dumper = *Data::Dump::dump;
173             } else {
174 2     2   1290 use Data::Dumper;
  2         11170  
  2         5355  
175             }
176             }
177              
178             # if you want to read .FDT file use read_fdt argument when creating class!
179 6 50 33     24 if ($self->{read_fdt} && -e $self->{fdt_file}) {
180              
181             # read the $db.FDT file for tags
182 0         0 my $fieldzone=0;
183              
184 0 0       0 open(my $fileFDT, $self->{fdt_file}) || croak "can't read '$self->{fdt_file}': $!";
185 0         0 binmode($fileFDT);
186              
187 0         0 while (<$fileFDT>) {
188 0         0 chomp;
189 0 0       0 if ($fieldzone) {
190 0         0 my $name=substr($_,0,30);
191 0         0 my $tag=substr($_,50,3);
192              
193 0         0 $name =~ s/\s+$//;
194 0         0 $tag =~ s/\s+$//;
195              
196 0         0 $self->{'TagName'}->{$tag}=$name;
197             }
198              
199 0 0       0 if (/^\*\*\*/) {
200 0         0 $fieldzone=1;
201             }
202             }
203            
204 0         0 close($fileFDT);
205             }
206              
207             # Get the Maximum MFN from $db.MST
208              
209 6 50       262 open($self->{'fileMST'}, $self->{mst_file}) || croak "can't open '$self->{mst_file}': $!";
210 6         26 binmode($self->{'fileMST'});
211              
212             # MST format: (* = 32 bit signed)
213             # CTLMFN* always 0
214             # NXTMFN* MFN to be assigned to the next record created
215             # NXTMFB* last block allocated to master file
216             # NXTMFP offset to next available position in last block
217             # MFTYPE always 0 for user db file (1 for system)
218 6 50       44 seek($self->{'fileMST'},4,0) || croak "can't seek to offset 0 in MST: $!";
219              
220 6         10 my $buff;
221              
222 6 50       141 read($self->{'fileMST'}, $buff, 4) || croak "can't read NXTMFN from MST: $!";
223 6   33     40 $self->{'NXTMFN'}=unpack("V",$buff) || croak "NXTNFN is zero";
224              
225 6 50       19 print STDERR "## self ",Dumper($self),"\n" if ($self->{debug});
226              
227             # open files for later
228 6 50       189 open($self->{'fileXRF'}, $self->{xrf_file}) || croak "can't open '$self->{xrf_file}': $!";
229 6         19 binmode($self->{'fileXRF'});
230              
231 6 50       617 $self ? return $self : return undef;
232             }
233              
234             =head2 count
235              
236             Return number of records in database
237              
238             print $isis->count;
239              
240             =cut
241              
242             sub count {
243 6     6 1 12932 my $self = shift;
244 6         30 return $self->{'NXTMFN'} - 1;
245             }
246              
247             =head2 fetch
248              
249             Read record with selected MFN
250              
251             my $rec = $isis->fetch(55);
252              
253             Returns hash with keys which are field names and values are unpacked values
254             for that field like this:
255              
256             $rec = {
257             '210' => [ '^aNew York^cNew York University press^dcop. 1988' ],
258             '990' => [ '2140', '88', 'HAY' ],
259             };
260              
261             =cut
262              
263             sub fetch {
264 29     29 1 4249 my $self = shift;
265              
266 29   33     92 my $mfn = shift || croak "fetch needs MFN as argument!";
267              
268             # is mfn allready in memory?
269 29   100     95 my $old_mfn = $self->{'current_mfn'} || -1;
270 29 100       96 return $self->{record} if ($mfn == $old_mfn);
271              
272 22 50       76 print STDERR "## fetch: $mfn\n" if ($self->{debug});
273              
274             # XXX check this?
275 22         122 my $mfnpos=($mfn+int(($mfn-1)/127))*4;
276              
277 22 50       57 print STDERR "## seeking to $mfnpos in file '$self->{xrf_file}'\n" if ($self->{debug});
278 22         208 seek($self->{'fileXRF'},$mfnpos,0);
279              
280 22         33 my $buff;
281              
282             # delete old record
283 22         206 delete $self->{record};
284              
285             # read XRFMFB abd XRFMFP
286 22         226 read($self->{'fileXRF'}, $buff, 4);
287 22         74 my $pointer=unpack("V",$buff);
288 22 50       54 if (! $pointer) {
289 0 0       0 if ($self->{include_deleted}) {
290 0         0 return;
291             } else {
292 0         0 warn "pointer for MFN $mfn is null\n";
293 0         0 return;
294             }
295             }
296              
297             # check for logically deleted record
298 22 100       95 if ($pointer & 0x80000000) {
299 4 50       15 print STDERR "## record $mfn is logically deleted\n" if ($self->{debug});
300 4         9 $self->{deleted} = $mfn;
301              
302 4 100       16 return unless $self->{include_deleted};
303              
304             # abs
305 3         6 $pointer = ($pointer ^ 0xffffffff) + 1;
306             }
307              
308 21         34 my $XRFMFB = int($pointer/2048);
309 21         37 my $XRFMFP = $pointer - ($XRFMFB*2048);
310              
311             # (XRFMFB - 1) * 512 + XRFMFP
312             # why do i have to do XRFMFP % 1024 ?
313              
314 21         50 my $blk_off = (($XRFMFB - 1) * 512) + ($XRFMFP % 512);
315              
316 21 50       47 print STDERR "## pointer: $pointer XRFMFB: $XRFMFB XRFMFP: $XRFMFP offset: $blk_off\n" if ($self->{'debug'});
317              
318             # Get Record Information
319              
320 21 50       199 seek($self->{'fileMST'},$blk_off,0) || croak "can't seek to $blk_off: $!";
321              
322 21 50       181 read($self->{'fileMST'}, $buff, 4) || croak "can't read 4 bytes at offset $blk_off from MST file: $!";
323 21         50 my $value=unpack("V",$buff);
324              
325 21 50       50 print STDERR "## offset for rowid $value is $blk_off (blk $XRFMFB off $XRFMFP)\n" if ($self->{debug});
326              
327 21 50       60 if ($value!=$mfn) {
328 0 0       0 if ($value == 0) {
329 0 0       0 print STDERR "## record $mfn is physically deleted\n" if ($self->{debug});
330 0         0 $self->{deleted} = $mfn;
331 0         0 return;
332             }
333              
334 0         0 carp "Error: MFN ".$mfn." not found in MST file, found $value";
335 0         0 return;
336             }
337              
338 21         44 read($self->{'fileMST'}, $buff, 14);
339              
340 21         66 my ($MFRL,$MFBWB,$MFBWP,$BASE,$NVF,$STATUS) = unpack("vVvvvv", $buff);
341              
342 21 50       52 print STDERR "## MFRL: $MFRL MFBWB: $MFBWB MFBWP: $MFBWP BASE: $BASE NVF: $NVF STATUS: $STATUS\n" if ($self->{debug});
343              
344 21 50       52 warn "MFRL $MFRL is not even number" unless ($MFRL % 2 == 0);
345              
346 21 50       45 warn "BASE is not 18+6*NVF" unless ($BASE == 18 + 6 * $NVF);
347              
348             # Get Directory Format
349              
350 21         28 my @FieldPOS;
351             my @FieldLEN;
352 0         0 my @FieldTAG;
353              
354 21         684 read($self->{'fileMST'}, $buff, 6 * $NVF);
355              
356 21         31 my $rec_len = 0;
357              
358 21         63 for (my $i = 0 ; $i < $NVF ; $i++) {
359              
360 405         872 my ($TAG,$POS,$LEN) = unpack("vvv", substr($buff,$i * 6, 6));
361              
362 405 50       856 print STDERR "## TAG: $TAG POS: $POS LEN: $LEN\n" if ($self->{debug});
363              
364             # The TAG does not exists in .FDT so we set it to 0.
365             #
366             # XXX This is removed from perl version; .FDT file is updated manually, so
367             # you will often have fields in .MST file which aren't in .FDT. On the other
368             # hand, IsisMarc doesn't use .FDT files at all!
369              
370             #if (! $self->{TagName}->{$TAG}) {
371             # $TAG=0;
372             #}
373              
374 405         527 push @FieldTAG,$TAG;
375 405         462 push @FieldPOS,$POS;
376 405         421 push @FieldLEN,$LEN;
377              
378 405         895 $rec_len += $LEN;
379             }
380              
381             # Get Variable Fields
382              
383 21         56 read($self->{'fileMST'},$buff,$rec_len);
384              
385 21 50       55 print STDERR "## rec_len: $rec_len poc: ",tell($self->{'fileMST'})."\n" if ($self->{debug});
386              
387 21         59 for (my $i = 0 ; $i < $NVF ; $i++) {
388             # skip zero-sized fields
389 405 50       786 next if ($FieldLEN[$i] == 0);
390              
391 405         641 my $v = substr($buff,$FieldPOS[$i],$FieldLEN[$i]);
392              
393 405 50       961 if ( $self->{ignore_empty_subfields} ) {
394 0         0 $v =~ s/(\^\w)+(\^\w)/$2/g;
395 0         0 $v =~ s/\^\w$//; # last on line?
396 0 0       0 next if ($v eq '');
397             }
398              
399 405         410 push @{$self->{record}->{$FieldTAG[$i]}}, $v;
  405         1830  
400             }
401              
402 21         39 $self->{'current_mfn'} = $mfn;
403              
404 21 50       44 print STDERR Dumper($self),"\n" if ($self->{debug});
405              
406 21         184 return $self->{'record'};
407             }
408              
409             =head2 mfn
410              
411             Returns current MFN position
412              
413             my $mfn = $isis->mfn;
414              
415             =cut
416              
417             # This function should be simple return $self->{current_mfn},
418             # but if new is called with _hack_mfn it becomes setter.
419             # It's useful in tests when setting $isis->{record} directly
420              
421             sub mfn {
422 17     17 1 35 my $self = shift;
423 17         83 return $self->{current_mfn};
424             };
425              
426              
427             =head2 to_ascii
428              
429             Returns ASCII output of record with specified MFN
430              
431             print $isis->to_ascii(42);
432              
433             This outputs something like this:
434              
435             210 ^aNew York^cNew York University press^dcop. 1988
436             990 2140
437             990 88
438             990 HAY
439              
440             If C is specified when calling C it will display field names
441             from C<.FDT> file instead of numeric tags.
442              
443             =cut
444              
445             sub to_ascii {
446 11     11 1 4077 my $self = shift;
447              
448 11   33     35 my $mfn = shift || croak "need MFN";
449              
450 11   50     25 my $rec = $self->fetch($mfn) || return;
451              
452 11         32 my $out = "0\t$mfn";
453              
454 11         16 foreach my $f (sort keys %{$rec}) {
  11         112  
455 164         308 my $fn = $self->tag_name($f);
456 164         307 $out .= "\n$fn\t".join("\n$fn\t",@{$self->{record}->{$f}});
  164         450  
457             }
458              
459 11         29 $out .= "\n";
460              
461 11         85 return $out;
462             }
463              
464             =head2 to_hash
465              
466             Read record with specified MFN and convert it to hash
467              
468             my $hash = $isis->to_hash($mfn);
469              
470             It has ability to convert characters (using C) from ISIS
471             database before creating structures enabling character re-mapping or quick
472             fix-up of data.
473              
474             This function returns hash which is like this:
475              
476             $hash = {
477             '210' => [
478             {
479             'c' => 'New York University press',
480             'a' => 'New York',
481             'd' => 'cop. 1988'
482             }
483             ],
484             '990' => [
485             '2140',
486             '88',
487             'HAY'
488             ],
489             };
490              
491             You can later use that hash to produce any output from ISIS data.
492              
493             If database is created using IsisMarc, it will also have to special fields
494             which will be used for identifiers, C and C like this:
495              
496             '200' => [
497             {
498             'i1' => '1',
499             'i2' => ' '
500             'a' => 'Goa',
501             'f' => 'Valdo D\'Arienzo',
502             'e' => 'tipografie e tipografi nel XVI secolo',
503             }
504             ],
505              
506             In case there are repeatable subfields in record, this will create
507             following structure:
508              
509             '900' => [ {
510             'a' => [ 'foo', 'bar', 'baz' ],
511             }]
512              
513             Or in more complex example of
514              
515             902 ^aa1^aa2^aa3^bb1^aa4^bb2^cc1^aa5
516              
517             it will create
518              
519             902 => [
520             { a => ["a1", "a2", "a3", "a4", "a5"], b => ["b1", "b2"], c => "c1" },
521             ],
522              
523             This behaviour can be changed using C option to L,
524             in which case C will always create single value for each subfield.
525             This will change result to:
526              
527              
528              
529             This method will also create additional field C<000> with MFN.
530              
531             There is also more elaborative way to call C like this:
532              
533             my $hash = $isis->to_hash({
534             mfn => 42,
535             include_subfields => 1,
536             });
537              
538             Each option controll creation of hash:
539              
540             =over 4
541              
542             =item mfn
543              
544             Specify MFN number of record
545              
546             =item include_subfields
547              
548             This option will create additional key in hash called C which will
549             have original record subfield order and index to that subfield like this:
550              
551             902 => [ {
552             a => ["a1", "a2", "a3", "a4", "a5"],
553             b => ["b1", "b2"],
554             c => "c1",
555             subfields => ["a", 0, "a", 1, "a", 2, "b", 0, "a", 3, "b", 1, "c", 0, "a", 4],
556             } ],
557              
558             =item join_subfields_with
559              
560             Define delimiter which will be used to join repeatable subfields. You can
561             specify option here instead in L if you want to have per-record control.
562              
563             =item hash_filter
564              
565             You can override C defined in L using this option.
566              
567             =back
568              
569             =cut
570              
571             sub to_hash {
572 6     6 1 11 my $self = shift;
573              
574              
575 6   33     18 my $mfn = shift || confess "need mfn!";
576 6         8 my $arg;
577              
578 6         13 my $hash_filter = $self->{hash_filter};
579              
580 6 100       25 if (ref($mfn) eq 'HASH') {
581 4         6 $arg = $mfn;
582 4   33     13 $mfn = $arg->{mfn} || confess "need mfn in arguments";
583 4 100       12 $hash_filter = $arg->{hash_filter} if ($arg->{hash_filter});
584             }
585              
586             # init record to include MFN as field 000
587 6         18 my $rec = { '000' => [ $mfn ] };
588              
589 6   50     18 my $row = $self->fetch($mfn) || return;
590              
591 6   100     30 my $j_rs = $arg->{join_subfields_with} || $self->{join_subfields_with};
592 6 100       20 $j_rs = $self->{join_subfields_with} unless(defined($j_rs));
593 6         12 my $i_sf = $arg->{include_subfields};
594              
595 6         9 foreach my $f_nr (keys %{$row}) {
  6         19  
596 18         25 foreach my $l (@{$row->{$f_nr}}) {
  18         40  
597              
598             # filter output
599 30 100       98 $l = $hash_filter->($l, $f_nr) if ($hash_filter);
600 30 50       16732 next unless defined($l);
601              
602 30         38 my $val;
603             my $r_sf; # repeatable subfields in this record
604              
605             # has identifiers?
606 30 50       75 ($val->{'i1'},$val->{'i2'}) = ($1,$2) if ($l =~ s/^([01 #])([01 #])\^/\^/);
607              
608             # has subfields?
609 30 50       83 if ($l =~ m/\^/) {
610 30         91 foreach my $t (split(/\^/,$l)) {
611 132 100       254 next if (! $t);
612 102         208 my ($sf,$v) = (substr($t,0,1), substr($t,1));
613             # XXX this might be option, but why?
614 102 50 33     400 next unless (defined($v) && $v ne '');
615             # warn "### $f_nr^$sf:$v",$/ if ($self->{debug} > 1);
616              
617 102 100       295 if (ref( $val->{$sf} ) eq 'ARRAY') {
    100          
618              
619 18         22 push @{ $val->{$sf} }, $v;
  18         50  
620              
621             # record repeatable subfield it it's offset
622 18 100 100     78 push @{ $val->{subfields} }, ( $sf, $#{ $val->{$sf} } ) if (! $j_rs && $i_sf);
  3         6  
  3         8  
623 18         43 $r_sf->{$sf}++;
624              
625             } elsif (defined( $val->{$sf} )) {
626              
627             # convert scalar field to array
628 12         40 $val->{$sf} = [ $val->{$sf}, $v ];
629              
630 12 100 100     53 push @{ $val->{subfields} }, ( $sf, 1 ) if (! $j_rs && $i_sf);
  2         6  
631 12         29 $r_sf->{$sf}++;
632              
633             } else {
634 72         150 $val->{$sf} = $v;
635 72 100       168 push @{ $val->{subfields} }, ( $sf, 0 ) if ($i_sf);
  12         42  
636             }
637             }
638             } else {
639 0         0 $val = $l;
640             }
641              
642 30 100       78 if ($j_rs) {
643 4         17 map {
644 10         21 $val->{$_} = join($j_rs, @{ $val->{$_} });
  4         7  
645             } keys %$r_sf
646             }
647              
648 30         36 push @{$rec->{$f_nr}}, $val;
  30         111  
649             }
650             }
651              
652 6         38 return $rec;
653             }
654              
655             =head2 tag_name
656              
657             Return name of selected tag
658              
659             print $isis->tag_name('200');
660              
661             =cut
662              
663             sub tag_name {
664 164     164 1 248 my $self = shift;
665 164   50     414 my $tag = shift || return;
666 164   33     695 return $self->{'TagName'}->{$tag} || $tag;
667             }
668              
669              
670             =head2 read_cnt
671              
672             Read content of C<.CNT> file and return hash containing it.
673              
674             print Dumper($isis->read_cnt);
675              
676             This function is not used by module (C<.CNT> files are not required for this
677             module to work), but it can be useful to examine your index (while debugging
678             for example).
679              
680             =cut
681              
682             sub read_cnt {
683 1     1 1 2 my $self = shift;
684              
685 1 50       5 croak "missing CNT file in ",$self->{isisdb} unless ($self->{cnt_file});
686              
687             # Get the index information from $db.CNT
688            
689 1 50       43 open(my $fileCNT, $self->{cnt_file}) || croak "can't read '$self->{cnt_file}': $!";
690 1         20 binmode($fileCNT);
691              
692 1         13 my $buff;
693              
694 1 50       21 read($fileCNT, $buff, 26) || croak "can't read first table from CNT: $!";
695 1         5 $self->unpack_cnt($buff);
696              
697 1 50       5 read($fileCNT, $buff, 26) || croak "can't read second table from CNT: $!";
698 1         3 $self->unpack_cnt($buff);
699              
700 1         12 close($fileCNT);
701              
702 1         8 return $self->{cnt};
703             }
704              
705             =head2 unpack_cnt
706              
707             Unpack one of two 26 bytes fixed length record in C<.CNT> file.
708              
709             Here is definition of record:
710              
711             off key description size
712             0: IDTYPE BTree type s
713             2: ORDN Nodes Order s
714             4: ORDF Leafs Order s
715             6: N Number of Memory buffers for nodes s
716             8: K Number of buffers for first level index s
717             10: LIV Current number of Index Levels s
718             12: POSRX Pointer to Root Record in N0x l
719             16: NMAXPOS Next Available position in N0x l
720             20: FMAXPOS Next available position in L0x l
721             24: ABNORMAL Formal BTree normality indicator s
722             length: 26 bytes
723              
724             This will fill C<$self> object under C with hash. It's used by C.
725              
726             =cut
727              
728             sub unpack_cnt {
729 2     2 1 4 my $self = shift;
730              
731 2         7 my @flds = qw(ORDN ORDF N K LIV POSRX NMAXPOS FMAXPOS ABNORMAL);
732              
733 2   50     9 my $buff = shift || return;
734 2         17 my @arr = unpack("vvvvvvVVVv", $buff);
735              
736 2 50       8 print STDERR "unpack_cnt: ",join(" ",@arr),"\n" if ($self->{'debug'});
737              
738 2         3 my $IDTYPE = shift @arr;
739 2         5 foreach (@flds) {
740 18         65 $self->{cnt}->{$IDTYPE}->{$_} = abs(shift @arr);
741             }
742             }
743              
744             1;
745              
746             =head1 BUGS
747              
748             Some parts of CDS/ISIS documentation are not detailed enough to exmplain
749             some variations in input databases which has been tested with this module.
750             When I was in doubt, I assumed that OpenIsis's implementation was right
751             (except for obvious bugs).
752              
753             However, every effort has been made to test this module with as much
754             databases (and programs that create them) as possible.
755              
756             I would be very greatful for success or failure reports about usage of this
757             module with databases from programs other than WinIsis and IsisMarc. I had
758             tested this against ouput of one C-based application, but I don't
759             know any details about it's version.
760              
761             =head1 VERSIONS
762              
763             As this is young module, new features are added in subsequent version. It's
764             a good idea to specify version when using this module like this:
765              
766             use Biblio::Isis 0.23
767              
768             Below is list of changes in specific version of module (so you can target
769             older versions if you really have to):
770              
771             =over 8
772              
773             =item 0.24
774              
775             Added C
776              
777             =item 0.23
778              
779             Added C to L
780              
781             Fixed bug with documented C in L which wasn't
782             implemented
783              
784             =item 0.22
785              
786             Added field number when calling C
787              
788             =item 0.21
789              
790             Added C to L and L.
791              
792             Added C to L.
793              
794             =item 0.20
795              
796             Added C<< $isis->mfn >>, support for repeatable subfields and
797             C<< $isis->to_hash({ mfn => 42, ... }) >> calling convention
798              
799             =back
800              
801             =head1 AUTHOR
802              
803             Dobrica Pavlinusic
804             CPAN ID: DPAVLIN
805             dpavlin@rot13.org
806             http://www.rot13.org/~dpavlin/
807              
808             This module is based heavily on code from C library to read ISIS files V0.1.1
809             written in php and (c) 2000 Franck Martin and released under LGPL.
810              
811             =head1 COPYRIGHT
812              
813             This program is free software; you can redistribute
814             it and/or modify it under the same terms as Perl itself.
815              
816             The full text of the license can be found in the
817             LICENSE file included with this module.
818              
819              
820             =head1 SEE ALSO
821              
822             L for CDS/ISIS manual appendix F, G and H which describe file format
823              
824             OpenIsis web site L
825              
826             perl4lib site L
827