File Coverage

blib/lib/MARC/Fast.pm
Criterion Covered Total %
statement 130 149 87.2
branch 42 68 61.7
condition 13 24 54.1
subroutine 12 12 100.0
pod 6 6 100.0
total 203 259 78.3


line stmt bran cond sub pod time code
1             package MARC::Fast;
2              
3 4     4   238220 use strict;
  4         12  
  4         407  
4 4     4   27 use Carp;
  4         8  
  4         1772  
5 4     4   37 use Data::Dump qw/dump/;
  4         17  
  4         486  
6              
7             BEGIN {
8 4     4   23 use Exporter ();
  4         8  
  4         242  
9 4     4   22 use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  4         8  
  4         591  
10 4     4   171 $VERSION = 0.12;
11 4         91 @ISA = qw (Exporter);
12             #Give a hoot don't pollute, do not export more than needed by default
13 4         17 @EXPORT = qw ();
14 4         9 @EXPORT_OK = qw ();
15 4         11495 %EXPORT_TAGS = ();
16             }
17              
18             =head1 NAME
19              
20             MARC::Fast - Very fast implementation of MARC database reader
21              
22             =head1 SYNOPSIS
23              
24             use MARC::Fast;
25              
26             my $marc = new MARC::Fast(
27             marcdb => 'unimarc.iso',
28             );
29              
30             foreach my $mfn ( 1 .. $marc->count ) {
31             print $marc->to_ascii( $mfn );
32             }
33              
34             For longer example with command line options look at L
35              
36             =head1 DESCRIPTION
37              
38             This is very fast alternative to C and C modules.
39              
40             It's is also very subtable for random access to MARC records (as opposed to
41             sequential one).
42              
43             =head1 METHODS
44              
45             =head2 new
46              
47             Read MARC database
48              
49             my $marc = new MARC::Fast(
50             marcdb => 'unimarc.iso',
51             quiet => 0,
52             debug => 0,
53             assert => 0,
54             hash_filter => sub {
55             my ($t, $record_number) = @_;
56             $t =~ s/foo/bar/;
57             return $t;
58             },
59             );
60              
61             =cut
62              
63             ################################################## subroutine header end ##
64              
65              
66             sub new {
67 6     6 1 1083 my $class = shift;
68 6         20 my $self = {@_};
69 6         15 bless ($self, $class);
70              
71 6 100       400 croak "need marcdb parametar" unless ($self->{marcdb});
72              
73 5 50       24 print STDERR "# opening ",$self->{marcdb},"\n" if ($self->{debug});
74              
75 5 100       567 open($self->{fh}, $self->{marcdb}) || croak "can't open ",$self->{marcdb},": $!";
76 4         18 binmode($self->{fh});
77              
78 4         18 $self->{count} = 0;
79              
80 4         113 while (! eof($self->{fh})) {
81 15         24 $self->{count}++;
82              
83             # save record position
84 15         19 push @{$self->{fh_offset}}, tell($self->{fh});
  15         39  
85              
86 15         20 my $leader;
87 15         74 my $len = read($self->{fh}, $leader, 24);
88              
89 15 100       126 if ($len < 24) {
90 2         298 warn "short read of leader, aborting\n";
91 2         9 $self->{count}--;
92 2         9 last;
93             }
94              
95             # Byte Name
96             # ---- ----
97             # 0-4 Record Length
98             # 5 Status (n=new, c=corrected and d=deleted)
99             # 6 Type of Record (a=printed material)
100             # 7 Bibliographic Level (m=monograph)
101             # 8-9 Blanks
102             # 10 Indictator count (2 for monographs)
103             # 11 Subfield code count (2 - 0x1F+subfield code itself)
104             # 12-16 Base address of data
105             # 17 Encoding level (blank=full level, 1=sublevel 1, 2=sublevel 2,
106             # 3=sublevel 3)
107             # 18 Descriptive Cataloguing Form (blank=record is full ISBD,
108             # n=record is in non-ISBD format, i=record is in
109             # an incomplete ISBD format)
110             # 19 Blank
111             # 20 Length of length field in directory (always 4 in UNIMARC)
112             # 21 Length of Starting Character Position in directory (always
113             # 5 in UNIMARC)
114             # 22 Length of implementation defined portion in directory (always
115             # 0 in UNIMARC)
116             # 23 Blank
117             #
118             # |0 45 89 |12 16|1n 450 |
119             # |xxxxxnam 22(.....) 45 <---
120              
121 13 50       32 print STDERR "REC ",$self->{count},": $leader\n" if ($self->{debug});
122              
123             # store leader for later
124 13         17 push @{$self->{leader}}, $leader;
  13         27  
125              
126             # skip to next record
127 13         26 my $o = substr($leader,0,5);
128 13 50       81 warn "# in record ", $self->{count}," record length isn't number but: ",dump($o),"\n" unless $o =~ m/^\d+$/;
129 13 50       42 if ($o > 24) {
130 13 50       186 seek($self->{fh},$o-24,1) if ($o);
131             } else {
132 0         0 last;
133             }
134              
135             }
136              
137 4         34 return $self;
138             }
139              
140             =head2 count
141              
142             Return number of records in database
143              
144             print $marc->count;
145              
146             =cut
147              
148             sub count {
149 9     9 1 448 my $self = shift;
150 9         46 return $self->{count};
151             }
152              
153             =head2 fetch
154              
155             Fetch record from database
156              
157             my $hash = $marc->fetch(42);
158              
159             First record number is C<1>
160              
161             =cut
162              
163             sub fetch {
164 51     51 1 379 my $self = shift;
165              
166 51         60 my $rec_nr = shift;
167              
168 51 100       107 if ( ! $rec_nr ) {
169 2         5 $self->{last_leader} = undef;
170 2         8 return;
171             }
172              
173 49         104 my $leader = $self->{leader}->[$rec_nr - 1];
174 49         76 $self->{last_leader} = $leader;
175 49 100       97 unless ($leader) {
176 1         269 carp "can't find record $rec_nr";
177 1         106 return;
178             };
179 48         76 my $offset = $self->{fh_offset}->[$rec_nr - 1];
180 48 50       130 unless (defined($offset)) {
181 0         0 carp "can't find offset for record $rec_nr";
182 0         0 return;
183             };
184              
185 48         91 my $reclen = substr($leader,0,5);
186 48         63 my $base_addr = substr($leader,12,5);
187              
188 48 50       138 print STDERR "# $rec_nr leader: '$leader' reclen: $reclen base addr: $base_addr [dir: ",$base_addr - 24,"]\n" if ($self->{debug});
189              
190 48         53 my $skip = 0;
191              
192 48 50       206 print STDERR "# seeking to $offset + 24\n" if ($self->{debug});
193              
194 48 50       1471 if ( ! seek($self->{fh}, $offset+24, 0) ) {
195 0         0 carp "can't seek to $offset: $!";
196 0         0 return;
197             }
198              
199 48 50       110 print STDERR "# reading ",$base_addr-24," bytes of dictionary\n" if ($self->{debug});
200              
201 48         50 my $directory;
202 48 50       820 if( ! read($self->{fh},$directory,$base_addr-24) ) {
203 0         0 carp "can't read directory: $!";
204 0         0 $skip = 1;
205             } else {
206 48 50       118 print STDERR "# $rec_nr directory: [",length($directory),"] '$directory'\n" if ($self->{debug});
207             }
208              
209 48 50       143 print STDERR "# reading ",$reclen-$base_addr," bytes of fields\n" if ($self->{debug});
210              
211 48         50 my $fields;
212 48 50       150 if( ! read($self->{fh},$fields,$reclen-$base_addr) ) {
213 0         0 carp "can't read fields: $!";
214 0         0 $skip = 1;
215             } else {
216 48 50       108 print STDERR "# $rec_nr fields: '$fields'\n" if ($self->{debug});
217             }
218              
219 48         54 my $row;
220              
221 48   66     1263 while (!$skip && $directory =~ s/(\d{3})(\d{4})(\d{5})//) {
222 907         2320 my ($tag,$len,$addr) = ($1,$2,$3);
223              
224 907 50       2256 if (($addr+$len) > length($fields)) {
225 0 0       0 print STDERR "WARNING: error in dictionary on record $rec_nr skipping...\n" if (! $self->{quiet});
226 0         0 $skip = 1;
227 0         0 next;
228             }
229              
230             # take field
231 907         1437 my $f = substr($fields,$addr,$len);
232 907 50       2185 print STDERR "tag/len/addr $tag [$len] $addr: '$f'\n" if ($self->{debug});
233              
234 907         886 push @{ $row->{$tag} }, $f;
  907         3810  
235              
236 907         1669 my $del = substr($fields,$addr+$len-1,1);
237              
238             # check field delimiters...
239 907 50 33     2347 if ($self->{assert} && $del ne chr(30)) {
240 0 0       0 print STDERR "WARNING: skipping record $rec_nr, can't find delimiter 30 got: '$del'\n" if (! $self->{quiet});
241 0         0 $skip = 1;
242 0         0 next;
243             }
244              
245 907 50 33     9561 if ($self->{assert} && length($f) < 2) {
246 0 0       0 print STDERR "WARNING: skipping field $tag from record $rec_nr because it's too short!\n" if (! $self->{quiet});
247 0         0 next;
248             }
249              
250             }
251              
252 48         192 return $row;
253             }
254              
255              
256             =head2 last_leader
257              
258             Returns leader of last record Led
259              
260             print $marc->last_leader;
261              
262             Added in version 0.08 of this module, so if you need it use:
263              
264             use MARC::Fast 0.08;
265              
266             to be sure that it's supported.
267              
268             =cut
269              
270             sub last_leader {
271 12     12 1 25 my $self = shift;
272 12         50 return $self->{last_leader};
273             }
274              
275              
276             =head2 to_hash
277              
278             Read record with specified MFN and convert it to hash
279              
280             my $hash = $marc->to_hash( $mfn, include_subfields => 1,
281             hash_filter => sub { my ($l,$tag) = @_; return $l; }
282             );
283              
284             It has ability to convert characters (using C) from MARC
285             database before creating structures enabling character re-mapping or quick
286             fix-up of data. If you specified C both in C and C
287             only the one from C will be used.
288              
289             This function returns hash which is like this:
290              
291             '200' => [
292             {
293             'i1' => '1',
294             'i2' => ' '
295             'a' => 'Goa',
296             'f' => 'Valdo D\'Arienzo',
297             'e' => 'tipografie e tipografi nel XVI secolo',
298             }
299             ],
300              
301             This method will also create additional field C<000> with MFN.
302              
303             =cut
304              
305             sub to_hash {
306 24     24 1 60 my $self = shift;
307              
308 24   33     74 my $mfn = shift || confess "need mfn!";
309              
310 24         270 my $args = {@_};
311 24   100     118 my $filter_coderef = $args->{'hash_filter'} || $self->{'hash_filter'};
312              
313             # init record to include MFN as field 000
314 24         86 my $rec = { '000' => [ $mfn ] };
315              
316 24   50     66 my $row = $self->fetch($mfn) || return;
317              
318 24         30 foreach my $tag (keys %{$row}) {
  24         139  
319 395         402 foreach my $l (@{$row->{$tag}}) {
  395         675  
320              
321             # remove end marker
322 458         1481 $l =~ s/\x1E$//;
323              
324             # filter output
325 458 100       1124 $l = $filter_coderef->($l, $tag) if $filter_coderef;
326              
327 458         3884 my $val;
328              
329             # has identifiers?
330 458 100       3377 ($val->{'i1'},$val->{'i2'}) = ($1,$2) if ($l =~ s/^([01 #])([01 #])\x1F/\x1F/);
331              
332 458         629 my $sf_usage;
333             my @subfields;
334              
335             # has subfields?
336 458 100       1354 if ($l =~ m/\x1F/) {
337 373         1435 foreach my $t (split(/\x1F/,$l)) {
338 1397 100       2945 next if (! $t);
339 1028         1756 my $f = substr($t,0,1);
340 1028         1322 my $v = substr($t,1);
341              
342 1028   100     5079 push @subfields, ( $f, $sf_usage->{$f}++ || 0 );
343              
344             # repeatable subfiled -- convert it to array
345 1028 100       1823 if ( defined $val->{$f} ) {
346 1 50       4 if ( ref($val->{$f}) ne 'ARRAY' ) {
347 1         14 $val->{$f} = [ $val->{$f}, $v ];
348             } else {
349 0         0 push @{$val->{$f}}, $v;
  0         0  
350             }
351             } else {
352 1027         2872 $val->{$f} = $v;
353             }
354             }
355 373 100       1560 $val->{subfields} = [ @subfields ] if $args->{include_subfields};
356             } else {
357 85         556 $val = $l;
358             }
359              
360 458         469 push @{$rec->{$tag}}, $val;
  458         2416  
361             }
362             }
363              
364 24         329 return $rec;
365             }
366              
367             =head2 to_ascii
368              
369             print $marc->to_ascii( 42 );
370              
371             =cut
372              
373             sub to_ascii {
374 10     10 1 63 my $self = shift;
375              
376 10   33     25 my $mfn = shift || confess "need mfn";
377 10   50     31 my $row = $self->fetch($mfn) || return;
378              
379 10         13 my $out;
380              
381 10         14 foreach my $f (sort keys %{$row}) {
  10         121  
382 165         169 my $dump = join('', @{ $row->{$f} });
  165         325  
383 165         950 $dump =~ s/\x1e$//;
384 165         470 $dump =~ s/\x1f/\$/g;
385 165         345 $out .= "$f\t$dump\n";
386             }
387              
388 10         118 return $out;
389             }
390              
391             1;
392             __END__