File Coverage

blib/lib/MARC/MIR.pm
Criterion Covered Total %
statement 43 185 23.2
branch 3 50 6.0
condition 2 14 14.2
subroutine 15 56 26.7
pod 3 31 9.6
total 66 336 19.6


line stmt bran cond sub pod time code
1             package MARC::MIR;
2 1     1   14226 use parent 'Exporter';
  1         305  
  1         5  
3 1     1   1057 use autodie;
  1         19584  
  1         7  
4 1     1   15973 use Modern::Perl;
  1         14421  
  1         6  
5 1     1   878 use Perlude;
  1         12342  
  1         9926  
6             # use Perlude::Sh qw< :all >;
7              
8             # ABSTRACT: DSL to manipulate MIR records.
9             our $VERSION = '0.4';
10              
11             # our %EXPORT_TAGS =
12             # ( dsl => [qw<
13             # with_fields
14             # with_subfields
15             # map_fields
16             # map_subfields
17             # grep_fields
18             # grep_subfields
19             # any_fields
20             # any_subfields
21             # map_values
22             #
23             # tag
24             # value
25             #
26             # with_value
27             # is_control
28             # record_id
29             #
30             # for_humans
31             # >]
32             # , debug => [qw< ready_to_see >]
33             # , iso2709 => [qw< from_iso2709 to_iso2709 iso2709_records_of >]
34             # , marawk => [qw< marawk $NUM $RAW $REC $ID %FIELDS >]
35             # , all => [qw<
36              
37             our @EXPORT = qw<
38             with_fields
39             with_subfields
40             map_fields
41             map_subfields
42             grep_fields
43             grep_subfields
44             any_fields
45             any_subfields
46             map_values
47              
48             with_datafields
49             map_datafields
50             grep_datafields
51             any_datafields
52              
53             append_subfields_to
54              
55             tag
56             value
57              
58             with_value
59             is_control
60             record_id
61              
62             for_humans
63             ready_to_see
64             from_iso2709 to_iso2709 iso2709_records_of
65             marawk $NUM $RAW $REC $ID %FIELDS
66              
67             yaz_marcdump
68              
69             record_charset
70              
71             cib_handlers cib_keys cib_reader cib_writer
72              
73             indicators
74             >;
75             # );
76             # our @EXPORT_OK = $EXPORT_TAGS{all} = [map @$_, values %EXPORT_TAGS];
77              
78             our $RS = "\x1d";
79             our $FS = "\x1e";
80             our $SS = "\x1f";
81              
82             sub iso2709_records_of (_) {
83 0     0 0 0 my $fh;
84 0 0       0 if ( ref $_[0] ) { $fh = shift }
  0         0  
85 0 0       0 else { open $fh, shift or die $! }
86             sub {
87 0     0   0 local $/ = $RS;
88 0   0     0 <$fh> // ();
89             }
90 0         0 }
91              
92             sub ready_to_see (_) {
93 0     0 0 0 s/$ISO2709::FS/$ISO2709::FS\n/g;
94 0         0 $_
95             }
96              
97             sub _fold_indicators {
98 0 0   0   0 my $ind = shift or return " ";
99 0 0       0 ref $ind ? @$ind : $ind
100             }
101              
102             sub to_iso2709 (_) {
103             # adapted from Frederic Demian's MARC::Moose serializer
104 0     0 0 0 state $empty_header = 'x'x24;
105              
106 0         0 my $rec = shift;
107 0 0       0 for ( $$rec[0] ) { length or $_ = $empty_header }
  0         0  
108 0         0 my (@directory,@data);
109 0         0 my $from = 0;
110              
111             # TODO: middleware anaromy_check (control fields)
112             # TODO: is serialization a middleware ?
113 0         0 for my $field ( @{ $$rec[1] } ) { # TODO: use map_fields ? :)
  0         0  
114             # my ( $tag, $data, $indicator ) = @$field;
115 0         0 my $last;
116 0         0 my $raw = do {
117 0 0       0 if ( ref $$field[1] ) { # data field
118 0         0 $last = pop @{ $$field[1] };
  0         0  
119 0 0       0 join ''
120             , # TODO: is *this* a middleware ?
121 0         0 ( map { ref $_ ? @$_ : $_ } ($$field[2] ||= [' ',' '] ) )
122             , $SS
123 0   0     0 , map( { @$_, $SS } @{ $$field[1] } )
  0         0  
124             , @$last
125             , $FS
126             }
127             else { # control field
128 0         0 $$field[1] . $FS;
129             }
130             };
131 0 0       0 $last and push @{ $$field[1] }, $last;
  0         0  
132              
133             # my $len = bytes::length( $raw );
134 0         0 my $len = length( $raw );
135 0         0 push @data, $raw;
136 0         0 push @directory
137             , sprintf( "%03s%04d%05d", $$field[0], $len, $from );
138              
139 0         0 $from+=$len;
140             }
141              
142 0         0 my $offset = 24 + 12 * @{ $$rec[1] } + 1;
  0         0  
143 0         0 my $length = $offset + $from + 1;
144              
145             # $length > 9999 and die "$length bytes is too long for a marc record";
146              
147 0         0 for ( $$rec[0] ) {
148 0         0 substr($_, 0, 5) = sprintf("%05d", $length);
149 0         0 substr($_, 12, 5) = sprintf("%05d", $offset);
150             # Default leader various pseudo variable fields
151             # Force UNICODE MARC21: substr($$rec[0], 9, 1) = 'a';
152             # those are defaults described at http://archive.ifla.org/VI/3/p1996-1/uni.htm
153             # xxxxnAxxxxxxxxxxxxxxxx
154             # A:
155             # a printed language
156             # b manuscript language
157             # c printed scores
158             # d manuscript scores
159             # e printed carto
160             # f manuscript carto
161             # g video
162             # i sound
163             # j music
164             # k tron
165             # m multimedia
166             # r 3D
167              
168             }
169              
170 0         0 join ''
171             , $$rec[0]
172             , @directory
173             , $FS
174             , @data
175             , $RS
176             }
177              
178              
179             sub _field {
180 0     0   0 my @chunks = split /\x1f(.)/;
181 0 0       0 return @chunks if @chunks == 1;
182 0         0 my @subfields;
183 0         0 my $indicators = [split //, shift @chunks];
184 0         0 while (@chunks) {
185 0         0 push @subfields, [splice @chunks,0,2];
186             }
187 0         0 \@subfields, $indicators;
188             }
189              
190             sub from_iso2709 (_) {
191 0     0 0 0 my $raw = shift;
192 0         0 chop $raw;
193 0         0 my ( $head, @fields ) = split /\x1e/, $raw;
194 0 0       0 @fields or die "raw $raw";
195 0 0       0 $head =~ /(.{24})/cg or die;
196 0         0 my $leader = $1;
197 0         0 my @tags = $head =~ /\G(\d{3})\d{9}/cg;
198 0 0       0 unless ( $head =~ /\G$/cg ) {
199 0         0 die "head tailing ".( $head =~ /(.*)/cg );
200             }
201 0         0 [ $leader
202             , [ map [ shift(@tags), _field ], @fields ]
203             ];
204             }
205              
206             sub _control_field_for_human {
207 0 0   0   0 ref $$_[1]
208             ? ()
209             : "$$_[0] $$_[1]"
210             }
211              
212             sub _data_field_for_human {
213 0     0   0 my ($tag, $subfields, $indicators) = @$_;
214 0 0       0 ref $subfields or return (); # probably a control field
215 0         0 join ''
216             , $tag
217             , '(' , _fold_indicators( $indicators ) , ') '
218             , map {
219 0         0 ' $'
220             , $$_[0]
221             , ' '
222             , $$_[1]
223             } @$subfields
224             }
225              
226             sub for_humans (_) {
227 0     0 0 0 my $record = shift;
228 0 0 0     0 join "\n"
229             , $$record[0]
230             , map {
231 0         0 _control_field_for_human ||
232             _data_field_for_human || die YAML::Dump { "can't humanize ", $_ }
233 0         0 } @{ $$record[1]}
234             }
235              
236              
237             sub is_control (_) {
238 0     0 0 0 my $r = shift;
239 0         0 @$r == 2;
240             }
241 22     22 1 54 sub tag (_) { @{ shift() }[0] }
  22         156  
242 0     0 1 0 sub value (_) { @{ shift() }[1] }
  0         0  
243              
244 6 100   6   19 sub _use_arg { push @_, $_ unless @_ > 1 }
245             sub _one_or_array {
246 4     4   6 my $r = shift;
247 4 50       15 (ref $r) ? @$r : $r
248             }
249              
250              
251             sub _with_data {
252 2     2   10 &_use_arg;
253 2         3 my ( $code, $on ) = @_;
254 2         4 map { $code->() } $$on[1];
  2         7  
255             }
256              
257             sub _map_data {
258 0     0   0 &_use_arg;
259 0         0 my ( $code, $on ) = @_;
260 0         0 map { $code->() } _one_or_array $$on[1]
  0         0  
261             }
262              
263             sub _any_data {
264 0     0   0 &_use_arg;
265 0         0 my ( $code, $on ) = @_;
266 0         0 map {
267 0         0 my $r = $code->();
268 0 0       0 $r and return $r;
269             } _one_or_array $$on[1]
270             };
271              
272             sub _grep_data {
273 4     4   8 &_use_arg;
274 4         6 my ( $code, $on ) = @_;
275 4         11 grep { $code->() } _one_or_array $$on[1]
  15         31  
276             }
277              
278 2   66 2 0 1375 sub with_fields (&;$) { $_[1] ||= $_; &_with_data }
  2         6  
279 0   0 0 0 0 sub with_subfields (&;$) { $_[1] ||= $_; &_with_data }
  0         0  
280 0     0 0 0 sub map_fields (&;$) { &_map_data }
281 0     0 0 0 sub map_subfields (&;$) { &_map_data }
282 4     4 0 512 sub grep_fields (&;$) { &_grep_data }
283 0     0 0 0 sub grep_subfields (&;$) { &_grep_data }
284 0     0 0 0 sub any_fields (&;$) { &_any_data }
285 0     0 0 0 sub any_subfields (&;$) { &_any_data }
286              
287             sub datafields_only {
288 0     0 0 0 my $r = $_[0];
289 0 0   0   0 $_[0] = sub { (is_control) ? () : $r->() };
  0         0  
290             }
291              
292 0     0 0 0 sub map_datafields (&;$) { &datafields_only; &_map_data }
  0         0  
293             # sub with_datafields (&;$) { &datafields_only; &_with_data }
294 0     0 0 0 sub grep_datafields (&;$) { &datafields_only; &_grep_data }
  0         0  
295 0     0 0 0 sub any_datafields (&;$) { &datafields_only; &_any_data }
  0         0  
296              
297             sub with_value (&;$) {
298 0     0 0 0 my $code = shift;
299 0 0       0 my $r = @_ ? shift : $_;
300 0         0 ( map $code->(), $$r[1] )[0];
301             }
302              
303             sub record_id (_) {
304 0 0   0 0 0 any_fields { tag eq '001' and value } shift;
  0     0   0  
305             }
306              
307             sub map_values (&$;$) {
308 0     0 0 0 my $code = shift;
309 0         0 my ( $fspec, $sspec ) = map { @$_ } shift;
  0         0  
310 0 0       0 my $rec = @_ ? shift : $_;
311 0     0   0 map {
312 0         0 map { with_value {$code->()} }
313 0     0   0 grep_subfields { (tag) ~~ $sspec }
314 0     0   0 } grep_fields { (tag) ~~ $fspec }
  0         0  
315             # TODO: Benchmark: is it really faster ?
316             # map_fields {
317             # if ( (tag) ~~ $fspec ) {
318             # map_subfields {
319             # if ( (tag) ~~ $sspec ) {
320             # with_value { $code->() }
321             # } else { () }
322             # }
323             # } else { () }
324             # } $rec
325 0         0 }
326              
327             sub marawk (&$) {
328 0     0 0 0 my $code = shift;
329 0     0   0 my ($stream) = map {
330 0         0 ref $_
331             ? $_
332 0 0       0 : concatM {iso2709_records_of} ls $_
333             } $_[0];
334              
335 0         0 our ( $NUM, $RAW, $REC, $ID, %FIELDS )
336             = ( 0 );
337             now {
338 0     0   0 $NUM++;
339 0         0 $RAW = $_;
340 0         0 $_ = $REC = from_iso2709 $_;
341 0 0       0 $ID = record_id or die "no ID inthere :". for_humans;
342 0         0 %FIELDS=();
343              
344             map_fields {
345 0         0 push @{ $FIELDS{(tag)} }
  0         0  
346             , $_
347 0         0 };
348 0         0 $code->();
349 0         0 } $stream
350             }
351              
352             sub cib_reader {
353 1     1 0 2 my $fmt = shift;
354 1         6 my @fields = map @$_, shift;
355              
356             sub {
357 0     0   0 my %cib;
358 0         0 @cib{ @fields } = unpack $fmt, shift;
359 0         0 \%cib
360             }
361              
362 1         9 }
363              
364             sub cib_writer {
365 1     1 0 2 my $fmt = shift;
366 1         6 my @fields = map @$_, shift;
367             sub {
368 0     0   0 my $cib = shift;
369 0         0 pack $fmt, @$cib{@fields};
370             }
371 1         7 }
372              
373             sub cib_keys {
374 1     1 0 1 my $cib = shift;
375 1         2 my $last = @$cib;
376 1         2 my ( @fmt, @fields );
377 1         6 for ( my $i = 0; $last > $i; ) {
378 12         22 push @fmt , "A$$cib[$i++]";
379 12         26 push @fields, $$cib[$i++];
380             }
381              
382 1         7 ( (join '',@fmt)
383             , \@fields
384             );
385              
386             }
387              
388             sub cib_handlers {
389 1     1 0 4 my @spec = cib_keys shift;
390 1         4 ( cib_reader ( @spec )
391             , cib_writer ( @spec )
392             );
393             }
394              
395             our ($gdp_reader,$gpd_writer) = cib_handlers
396             [qw[
397             8 entered
398             1 date_type
399             4 pub
400             4 pub2
401             3 audience
402             1 gov
403             1 modif
404             3 lang
405             1 transliteration
406             4 charset
407             4 charset2
408             2 title ]];
409              
410             sub record_charset (_) {
411 0     0 0   my $rec = shift;
412 0     0     ''. ( map_values
413             {$MARC::MIR::gdp_parser->( $_ )->{charset}}
414 0           [qw< 100 a >], $rec
415             )[0]
416             }
417              
418             sub append_subfields_to {
419 0     0 0   my ($dest,@data) = @_;
420 0 0         @data or @data = @{$$_[1]};
  0            
421 0           push @{ $$dest[1] }
  0            
422 0           , @{ $$_[1] };
423             }
424              
425 0     0 1   sub indicators { $$_[2] }
426              
427             1;