/[Biblio-Isis]/trunk/lib/Biblio/Isis.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /trunk/lib/Biblio/Isis.pm

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.26