File Coverage

blib/lib/Catmandu/MARC.pm
Criterion Covered Total %
statement 455 653 69.6
branch 236 430 54.8
condition 85 205 41.4
subroutine 26 31 83.8
pod 11 13 84.6
total 813 1332 61.0


line stmt bran cond sub pod time code
1             package Catmandu::MARC;
2              
3 28     28   70112 use Catmandu::Sane;
  28         838622  
  28         164  
4 28     28   5287 use Catmandu::Util;
  28         1009  
  28         1299  
5 28     28   8474 use Catmandu::Exporter::MARC::XML;
  28         147  
  28         1071  
6 28     28   10550 use MARC::Spec::Parser;
  28         958206  
  28         1100  
7 28     28   325 use List::Util;
  28         174  
  28         1993  
8 28     28   12705 use Memoize;
  28         62840  
  28         1571  
9 28     28   246 use Carp;
  28         77  
  28         1338  
10 28     28   202 use Moo;
  28         105  
  28         197  
11              
12             with 'MooX::Singleton';
13              
14             memoize('compile_marc_path');
15             memoize('parse_marc_spec');
16             memoize('_get_index_range');
17              
18             our $VERSION = '1.19';
19              
20             sub marc_map {
21 626     626 1 49571 my $self = $_[0];
22              
23             # $_[2] : marc_path
24 626 100       1849 my $context = ref($_[2]) ?
25             $_[2] :
26             $self->compile_marc_path($_[2], subfield_wildcard => 1);
27              
28 626 50       1675 confess "invalid marc path" unless $context;
29              
30             # $_[1] : data record
31 626         1104 my $record = $_[1]->{'record'};
32              
33 626 0 33     2167 return wantarray ? () : undef unless (defined $record && ref($record) eq 'ARRAY');
    50          
34              
35             # $_[3] : opts
36 626   50     1486 my $split = $_[3]->{'-split'} // 0;
37 626   50     1278 my $join_char = $_[3]->{'-join'} // '';
38 626   50     1217 my $pluck = $_[3]->{'-pluck'} // 0;
39 626   100     1591 my $value_set = $_[3]->{'-value'} // undef;
40 626   50     1122 my $nested_arrays = $_[3]->{'-nested_arrays'} // 0;
41 626   100     1116 my $append = $_[3]->{'-force_array'} // undef;
42              
43 626         824 my $vals;
44              
45 626         1047 for my $field (@$record) {
46             next if (
47             ($context->{is_regex_field} == 0 && $field->[0] ne $context->{field} )
48             ||
49             (defined $context->{ind1} && (!defined $field->[1] || $field->[1] ne $context->{ind1}))
50             ||
51             (defined $context->{ind2} && (!defined $field->[2] || $field->[2] ne $context->{ind2}))
52             ||
53             ($context->{is_regex_field} == 1 && $field->[0] !~ $context->{field_regex} )
54 5565 50 100     20998 );
      0        
      33        
      66        
      0        
      33        
      33        
      66        
      33        
55              
56 400         622 my $v;
57              
58 400 100       710 if ($value_set) {
59 48         87 for (my $i = 3; $i < @{$field}; $i += 2) {
  110         249  
60 96         138 my $subfield_regex = $context->{subfield_regex};
61 96 100       410 if ($field->[$i] =~ $subfield_regex) {
62 34         63 $v = $value_set;
63 34         66 last;
64             }
65             }
66             }
67             else {
68 352         618 $v = [];
69              
70 352 100       655 if ($pluck) {
71             # Treat the subfield as a hash index
72 16         37 my $_h = {};
73 16         42 for (my $i = $context->{start}; $i < @{$field}; $i += 2) {
  49         119  
74 33         48 push @{ $_h->{ $field->[$i] } } , $field->[$i + 1];
  33         125  
75             }
76 16         37 my $subfield = $context->{subfield};
77 16         112 $subfield =~ s{[^a-zA-Z0-9]}{}g;
78 16         68 for my $c (split('',$subfield)) {
79 33   100     98 my $val = $_h->{$c} // [undef];
80 33         53 push @$v , @{ $val } ;
  33         90  
81             }
82             }
83             else {
84 336         617 for (my $i = $context->{start}; $i < @{$field}; $i += 2) {
  896         1985  
85 560         884 my $subfield_regex = $context->{subfield_regex};
86 560 100       2589 if ($field->[$i] =~ $subfield_regex) {
87 417         1402 push(@$v, $field->[$i + 1]);
88             }
89             }
90             }
91              
92 352 100       747 if (@$v) {
93 298 100       614 if (!$split) {
94 227         408 my @defined_values = grep {defined($_)} @$v;
  258         846  
95 227         635 $v = join $join_char, @defined_values;
96             }
97              
98 298 100       855 if (defined(my $off = $context->{from})) {
99 51 100       126 if (ref $v eq 'ARRAY') {
100 11         29 my @defined_values = grep {defined($_)} @$v;
  11         44  
101 11         35 $v = join $join_char, @defined_values;
102             }
103 51         93 my $len = $context->{len};
104 51 100       118 if (length(${v}) > $off) {
105 41         106 $v = substr($v, $off, $len);
106             } else {
107 10         25 $v = undef;
108             }
109             }
110             }
111             else {
112 54         105 $v = undef;
113             }
114             }
115              
116 400 100       899 if (defined $v) {
117 322 100       579 if ($split) {
118 71 100 66     329 $v = [ $v ] unless (defined($v) && ref($v) eq 'ARRAY');
119 71 100 66     255 if (defined($vals) && ref($vals) eq 'ARRAY') {
120             # With the nested arrays option a split will
121             # always return an array of array of values.
122             # This was the old behavior of Inline marc_map functions
123 41 100       167 if ($nested_arrays == 1) {
124 40         350 push @$vals , $v;
125             }
126             else {
127 1         3 push @$vals , @$v;
128             }
129             }
130             else {
131 30 100       66 if ($nested_arrays == 1) {
132 9         29 $vals = [$v];
133             }
134             else {
135 21         71 $vals = [ @$v ];
136             }
137             }
138             }
139             else {
140 251         620 push @$vals , $v;
141             }
142             }
143             }
144              
145 626 100 66     1905 if ($split && defined $vals) {
    100          
    100          
146 30         66 $vals = [ $vals ];
147             }
148             elsif ($append) {
149             # we got a $append
150             }
151             elsif (defined $vals) {
152 134         340 $vals = join $join_char , @$vals;
153             }
154             else {
155             # no result
156             }
157              
158 626         11449 $vals;
159             }
160              
161             sub marc_add {
162 10     10 1 312 my ($self,$data,$marc_path,@subfields) = @_;
163              
164 10         55 my %subfields = @subfields;
165 10   100     46 my $marc = $data->{'record'} // [];
166              
167 10 50       60 if ($marc_path =~ /^\w{3}$/) {
168 10         25 my @field = ();
169 10         27 push @field , $marc_path;
170 10   100     50 push @field , $subfields{ind1} // ' ';
171 10   100     46 push @field , $subfields{ind2} // ' ';
172              
173              
174 10         35 for (my $i = 0 ; $i < @subfields ; $i += 2) {
175 20         34 my $code = $subfields[$i];
176 20 100       55 next unless length $code == 1;
177 14         26 my $value = $subfields[$i+1];
178              
179 14 100       78 if ($value =~ /^\$\.(\S+)$/) {
180 6         23 my $path = $1;
181 6         29 $value = Catmandu::Util::data_at($path,$data);
182             }
183              
184 14 100 33     682 if (Catmandu::Util::is_array_ref $value) {
    50          
    50          
185 3         11 for (@$value) {
186 9         21 push @field , $code;
187 9         67 push @field , $_;
188             }
189             }
190             elsif (Catmandu::Util::is_hash_ref $value) {
191 0         0 for (keys %$value) {
192 0         0 push @field , $code;
193 0         0 push @field , $value->{$_};
194             }
195             }
196             elsif (Catmandu::Util::is_value($value) && length($value) > 0) {
197 11         24 push @field , $code;
198 11         44 push @field , $value;
199             }
200             }
201              
202 10 50       30 push @{ $marc } , \@field if @field > 3;
  10         32  
203             }
204              
205 10         21 $data->{'record'} = $marc;
206              
207 10         110 $data;
208             }
209              
210             sub marc_append {
211 1     1 1 13 my ($self,$data,$marc_path,$value) = @_;
212 1         3 my $record = $data->{'record'};
213              
214 1 50       3 return $data unless defined $record;
215              
216 1 50       4 if ($value =~ /^\$\.(\S+)/) {
217 0         0 my $path = $1;
218 0         0 $value = Catmandu::Util::data_at($path,$data);
219             }
220              
221 1 50       6 if (Catmandu::Util::is_array_ref $value) {
    50          
222 0         0 $value = $value->[-1];
223             }
224             elsif (Catmandu::Util::is_hash_ref $value) {
225 0         0 my $last;
226 0         0 for (keys %$value) {
227 0         0 $last = $value->{$_};
228             }
229 0         0 $value = $last;
230             }
231              
232 1         16 my $context = $self->compile_marc_path($marc_path);
233              
234 1 50       15 confess "invalid marc path" unless $context;
235              
236 1         6 for my $field (@$record) {
237 19         66 my ($tag, $ind1, $ind2, @subfields) = @$field;
238              
239 19 50       44 if ($context->{is_regex_field}) {
240 0 0       0 next unless $tag =~ $context->{field_regex};
241             }
242             else {
243 19 100       65 next unless $tag eq $context->{field};
244             }
245              
246 1 50       6 if (defined $context->{ind1}) {
247 0 0 0     0 if (!defined $ind1 || $ind1 ne $context->{ind1}) {
248 0         0 next;
249             }
250             }
251 1 50       4 if (defined $context->{ind2}) {
252 0 0 0     0 if (!defined $ind2 || $ind2 ne $context->{ind2}) {
253 0         0 next;
254             }
255             }
256              
257 1 50       5 if ($context->{subfield}) {
258 0         0 for (my $i = 0; $i < @subfields; $i += 2) {
259 0 0       0 if ($subfields[$i] =~ $context->{subfield}) {
260 0         0 $field->[$i + 4] .= $value;
261             }
262             }
263             }
264             else {
265 1         5 $field->[-1] .= $value;
266             }
267             }
268              
269 1         23 $data;
270             }
271              
272             sub marc_replace_all {
273 3     3 1 33 my ($self,$data,$marc_path,$regex,$value) = @_;
274 3         7 my $record = $data->{'record'};
275              
276 3 50       7 return $data unless defined $record;
277              
278 3 50       9 if ($value =~ /^\$\.(\S+)/) {
279 0         0 my $path = $1;
280 0         0 $value = Catmandu::Util::data_at($path,$data);
281             }
282              
283 3 50       14 if (Catmandu::Util::is_array_ref $value) {
    50          
284 0         0 $value = $value->[-1];
285             }
286             elsif (Catmandu::Util::is_hash_ref $value) {
287 0         0 my $last;
288 0         0 for (keys %$value) {
289 0         0 $last = $value->{$_};
290             }
291 0         0 $value = $last;
292             }
293              
294 3         45 my $context = $self->compile_marc_path($marc_path, subfield_wildcard => 1);
295              
296 3 50       66 confess "invalid marc path" unless $context;
297              
298 3         7 for my $field (@$record) {
299 57         121 my ($tag, $ind1, $ind2, @subfields) = @$field;
300              
301 57 50       75 if ($context->{is_regex_field}) {
302 0 0       0 next unless $tag =~ $context->{field_regex};
303             }
304             else {
305 57 100       109 next unless $tag eq $context->{field};
306             }
307              
308 5 50       9 if (defined $context->{ind1}) {
309 0 0 0     0 if (!defined $ind1 || $ind1 ne $context->{ind1}) {
310 0         0 next;
311             }
312             }
313 5 50       10 if (defined $context->{ind2}) {
314 0 0 0     0 if (!defined $ind2 || $ind2 ne $context->{ind2}) {
315 0         0 next;
316             }
317             }
318              
319 5         11 for (my $i = 0; $i < @subfields; $i += 2) {
320 6 100       39 if ($subfields[$i] =~ $context->{subfield}) {
321             # Trick to double eval the right hand side
322 5         42 $field->[$i + 4] =~ s{$regex}{"\"$value\""}eeg;
  5         222  
323             }
324             }
325             }
326              
327 3         49 $data;
328             }
329              
330             sub marc_set {
331 8     8 1 121 my ($self,$data,$marc_path,$value,%opts) = @_;
332 8         21 my $record = $data->{'record'};
333              
334 8 50       28 return $data unless defined $record;
335              
336 8 100       36 if ($value =~ /^\$\.(\S+)/) {
337 3         13 my $path = $1;
338 3         17 $value = Catmandu::Util::data_at($path,$data);
339             }
340              
341 8 50       273 if (Catmandu::Util::is_array_ref $value) {
    50          
342 0         0 $value = $value->[-1];
343             }
344             elsif (Catmandu::Util::is_hash_ref $value) {
345 0         0 my $last;
346 0         0 for (keys %$value) {
347 0         0 $last = $value->{$_};
348             }
349 0         0 $value = $last;
350             }
351              
352 8         158 my $context = $self->compile_marc_path($marc_path, subfield_default => 1);
353              
354 8 50       166 confess "invalid marc path" unless $context;
355              
356 8         24 for my $field (@$record) {
357 168         421 my ($tag, $ind1, $ind2, @subfields) = @$field;
358              
359 168 50       272 if ($context->{is_regex_field}) {
360 0 0       0 next unless $tag =~ $context->{field_regex};
361             }
362             else {
363 168 100       383 next unless $tag eq $context->{field};
364             }
365              
366 8 100       26 if (defined $context->{ind1}) {
367 2 50 33     13 if (!defined $ind1 || $ind1 ne $context->{ind1}) {
368 0         0 next;
369             }
370             }
371 8 50       25 if (defined $context->{ind2}) {
372 0 0 0     0 if (!defined $ind2 || $ind2 ne $context->{ind2}) {
373 0         0 next;
374             }
375             }
376              
377 8         13 my $found = 0;
378 8         29 for (my $i = 0; $i < @subfields; $i += 2) {
379 13 100       106 if ($subfields[$i] =~ $context->{subfield}) {
380 5 100       19 if (defined $context->{from}) {
381 1         6 substr($field->[$i + 4], $context->{from}, $context->{len}) = $value;
382             }
383             else {
384 4         14 $field->[$i + 4] = $value;
385             }
386 5         15 $found = 1;
387             }
388             }
389              
390 8 100       31 if ($found == 0) {
391 3         18 push(@$field,$context->{subfield},$value);
392             }
393             }
394              
395 8         95 $data;
396             }
397              
398             sub marc_remove {
399 7     7 1 103 my ($self,$data, $marc_path,%opts) = @_;
400 7         20 my $record = $data->{'record'};
401              
402 7         12 my $new_record;
403              
404 7         130 my $context = $self->compile_marc_path($marc_path);
405              
406 7 50       73 confess "invalid marc path" unless $context;
407              
408 7         22 for my $field (@$record) {
409 120         159 my $field_size = int(@$field);
410              
411 120 100 66     465 if (
      33        
      66        
412             ($context->{is_regex_field} == 0 && $field->[0] eq $context->{field})
413             ||
414             ($context->{is_regex_field} == 1 && $field->[0] =~ $context->{field_regex})
415             ) {
416              
417 7         13 my $ind_match = undef;
418              
419 7 100 66     50 if (defined $context->{ind1} && defined $context->{ind2}) {
    50          
    100          
420             $ind_match = 1 if (defined $field->[1] && $field->[1] eq $context->{ind1} &&
421 1 0 33     9 defined $field->[2] && $field->[2] eq $context->{ind2});
      33        
      33        
422             }
423             elsif (defined $context->{ind1}) {
424 0 0 0     0 $ind_match = 1 if (defined $field->[1] && $field->[1] eq $context->{ind1});
425             }
426             elsif (defined $context->{ind2}) {
427 1 50 33     10 $ind_match = 1 if (defined $field->[2] && $field->[2] eq $context->{ind2});
428             }
429             else {
430 5         12 $ind_match = 1;
431             }
432              
433 7 100 100     40 if ($ind_match && ! defined $context->{subfield_regex}) {
434 4         20 next;
435             }
436              
437 3 50       7 if (defined $context->{subfield_regex}) {
438 3         5 my $subfield_regex = $context->{subfield_regex};
439 3         4 my $new_subf = [];
440 3         8 for (my $i = $context->{start}; $i < $field_size; $i += 2) {
441 6 100       33 unless ($field->[$i] =~ $subfield_regex) {
442 2         6 push @$new_subf , $field->[$i];
443 2         6 push @$new_subf , $field->[$i+1];
444             }
445             }
446              
447 3 100       12 splice @$field , $context->{start} , int(@$field), @$new_subf if $ind_match;
448             }
449             }
450              
451 116         194 push @$new_record , $field;
452             }
453              
454 7         17 $data->{'record'} = $new_record;
455              
456 7         403 return $data;
457             }
458              
459             sub marc_spec {
460 47     47 1 6593 my $self = $_[0];
461              
462             # $_[1] : data record
463 47         70 my $data = $_[1];
464 47         84 my $record = $data->{'record'};
465              
466             # $_[2] : spec
467 47         76 my ($ms, $spec);
468 47 50       119 if( ref $_[2] ) {
469 47         79 $ms = $_[2];
470 47         124 $spec = $ms->to_string()
471             } else {
472 0         0 $ms = $self->parse_marc_spec( $_[2] ); # memoized
473 0         0 $spec = $_[2];
474             }
475              
476 47         6401 my $EMPTY = q{};
477             # $_[3] : opts
478 47   50     144 my $split = $_[3]->{'-split'} // 0;
479 47   66     129 my $join_char = $_[3]->{'-join'} // $EMPTY;
480 47   100     121 my $pluck = $_[3]->{'-pluck'} // 0;
481 47   100     180 my $value_set = $_[3]->{'-value'} // undef;
482 47   100     126 my $invert = $_[3]->{'-invert'} // 0;
483 47   100     111 my $nested_arrays = $_[3]->{'-nested_arrays'} // 0;
484 47   100     113 my $append = $_[3]->{'-force_array'} // 0;
485              
486 47 50       84 if($nested_arrays) {
487 0         0 $split = 1
488             }
489              
490             # filter by tag
491 47         75 my @fields = ();
492 47         785 my $field_spec = $ms->field;
493 47         358 my $tag_spec = $field_spec->tag;
494              
495 47         71 @fields = grep { $_->[0] =~ /$tag_spec/ } @{ $record };
  956         2252  
  47         99  
496 47 100       141 return unless @fields;
497              
498             # filter by indicator
499 45         81 my ( $indicator1, $indicator2 );
500 45 100       115 if ( $field_spec->has_indicator1 ) {
501 2         8 $indicator1 = $field_spec->indicator1;
502 2         21 $indicator1 = qr/$indicator1/;
503             }
504 45 100       114 if ( $field_spec->has_indicator2 ) {
505 3         11 $indicator2 = $field_spec->indicator2;
506 3         20 $indicator2 = qr/$indicator2/;
507             }
508              
509             # calculate char start
510             my $chst = sub {
511 75     75   125 my ($sp) = @_;
512 75         95 my $char_start;
513 75 100       237 if ( $sp->has_char_start ) {
514 17 100       395 $char_start = ( '#' eq $sp->char_start )
515             ? $sp->char_length * -1
516             : $sp->char_start;
517             }
518 75         542 return $char_start;
519 45         200 };
520              
521             # vars we need only for subfields
522 45         84 my (@sf_spec, $invert_level, $codes, $invert_chars);
523 45 100       108 if ( $ms->has_subfields ) {
524             # set the order of subfields
525 18         26 @sf_spec = map { $_ } @{ $ms->subfields };
  29         52  
  18         38  
526 18 100       38 unless ( $pluck ) {
527 17         49 @sf_spec = sort { $a->code cmp $b->code } @sf_spec;
  11         31  
528             }
529              
530             # set invert level default
531 18         26 $invert_level = 4;
532 18 100       34 if ( $invert ) {
533 4         6 $codes = '[^';
534 4         7 $codes .= join $EMPTY, map { $_->code } @sf_spec;
  10         21  
535 4         8 $codes .= ']';
536             }
537              
538             $invert_chars = sub {
539 4     4   27 my ( $str, $start, $length ) = @_;
540 4         9 for ( substr $str, $start, $length ) {
541 4         11 $_ = $EMPTY;
542             }
543 4         13 return $str;
544 18         58 };
545             }
546             else {
547             # return $value_set ASAP
548 27 100       84 return $value_set if defined $value_set;
549             }
550              
551             # vars we need for fields and subfields
552 44         72 my ($referred, $char_start, $prev_tag, $index_range);
553 44         72 my $current_tag = $EMPTY;
554 44         66 my $tag_index = 0;
555 44         823 my $index_start = $field_spec->index_start;
556 44         926 my $index_end = $field_spec->index_end;
557              
558             my $to_referred = sub {
559 64     64   142 my ( @values ) = @_;
560 64 50       140 if($nested_arrays) {
    100          
561 0         0 push @{$referred}, \@values;
  0         0  
562             } elsif($split) {
563 29         52 push @{$referred}, @values;
  29         96  
564             } else {
565 35         48 push @{$referred}, join $join_char, @values;
  35         128  
566             }
567 44         365 };
568              
569 44 50       696 if( defined $field_spec->index_start ) {
570 44         912 $index_range =
571             _get_index_range( $field_spec->index_start, $field_spec->index_end, $#fields );
572             }
573              
574             # iterate over fields
575 44         2312 for my $field (@fields) {
576 114         450 $prev_tag = $current_tag;
577 114         180 $current_tag = $field->[0];
578              
579 114 100 66     291 $tag_index = ( $prev_tag eq $current_tag and defined $tag_index)
580             ? ++$tag_index
581             : 0; #: $field_spec->index_start;
582              
583             # filter by indicator
584 114 100       233 if( defined $indicator1 ) {
585 21 100 100     91 next unless ( defined $field->[1] && $field->[1] =~ $indicator1);
586             }
587              
588 95 100       157 if( defined $indicator2 ) {
589             #next unless $field->[2] =~ $indicator2;
590 22 100 100     151 next unless ( defined $field->[2] && $field->[2] =~ $indicator2);
591             }
592              
593             # filter by index
594 81 50       140 if ( defined $index_range ) {
595 81 100       229 next unless ( Catmandu::Util::array_includes( $index_range, $tag_index ) );
596             }
597              
598             # filter field by subspec
599 72 100       4508 if( $field_spec->has_subspecs) {
600 2         17 my $valid = $self->_it_subspecs( $data, $field_spec->tag, $field_spec->subspecs, $tag_index );
601 2 100       11 next unless $valid;
602             }
603              
604 71         109 my @subfields = ();
605              
606 71 100       161 if ( $ms->has_subfields ) { # now we dealing with subfields
607 29         43 for my $sf (@sf_spec) {
608             # set invert level
609 46 100 66     106 if ( $invert && !$sf->has_subspecs) {
610 12 100 66     55 if ( -1 == $sf->index_length && !$sf->has_char_start ) {
    100          
611 6 100       13 next if ( $invert_level == 3 ); # skip subfield spec it's already covered
612 5         8 $invert_level = 3;
613             }
614             elsif ( $sf->has_char_start ) {
615 4         6 $invert_level = 1;
616             }
617             else {
618 2         3 $invert_level = 2;
619             }
620             }
621              
622 45         55 my @subfield = ();
623 45 100       103 my $code = ( $invert_level == 3 ) ? $codes : $sf->code;
624 45         307 $code = qr/$code/;
625 45         82 for ( my $i = 3 ; $i < @{$field} ; $i += 2 ) {
  187         303  
626 142 100       433 if ( $field->[$i] =~ /$code/ ) {
627 64         118 push @subfield, $field->[ $i + 1 ];
628             }
629             }
630              
631 45 100       77 if ( $invert_level == 3 ) { # no index or charpos
632 5 100       10 if (@subfield) {
633 1         3 push @subfields, @subfield;
634             }
635              
636 5 50 33     13 if ( $referred && $value_set ) { # return $value_set ASAP
637 0         0 return $value_set;
638             }
639              
640 5         11 next;
641             }
642              
643 40 100       86 next unless (@subfield);
644              
645             # filter by index
646 30 50       529 if ( defined $sf->index_start ) {
647 30         554 my $sf_range =
648             _get_index_range( $sf->index_start, $sf->index_end, $#subfield );
649              
650 30 100       1282 if ( $invert_level == 2 ) { # inverted
651             @subfield = map {
652 1 100       5 Catmandu::Util::array_includes( $sf_range, $_ )
  3         76  
653             ? ()
654             : $subfield[$_];
655             } 0 .. $#subfield;
656             }
657             else { # without invert
658             @subfield =
659             map {
660 38 100       114 defined $subfield[$_]
661             ? $subfield[$_]
662             : ();
663 29         38 } @{$sf_range};
  29         56  
664             }
665 30 100       98 next unless (@subfield);
666             }
667              
668             # return $value_set ASAP
669 28 50       54 return $value_set if $value_set;
670              
671             # filter subfield by subspec
672 28 50       69 if( $sf->has_subspecs) {
673 0         0 my $valid = $self->_it_subspecs( $data, $field_spec->tag, $sf->subspecs, $tag_index);
674 0 0       0 next unless $valid;
675             }
676              
677             # get substring
678 28         54 $char_start = $chst->($sf);
679 28 100       55 if ( defined $char_start ) {
680 7 100       16 if ( $invert_level == 1 ) { # inverted
681             @subfield =
682             map {
683 4         6 $invert_chars->( $_, $char_start, $sf->char_length );
  4         51  
684             } @subfield;
685             }
686             else {
687             @subfield =
688             map {
689 3         81 substr $_, $char_start, $sf->char_length;
  3         60  
690             } @subfield;
691             }
692             }
693 28 50       75 next unless @subfield;
694 28         98 push @subfields, @subfield;
695             } # end of subfield iteration
696 29 100       85 $to_referred->(@subfields) if @subfields;
697             } # end of subfield handling
698             else { # no particular subfields requested
699 42         73 my @contents = ();
700 42         76 for ( my $i = 4 ; $i < @{$field} ; $i += 2 ) {
  89         204  
701             # get substring
702 47         98 $char_start = $chst->($field_spec);
703 47 100       330 my $content = ( defined $char_start )
704             ? substr $field->[$i], $char_start, $field_spec->char_length
705             : $field->[$i];
706 47         201 push @contents, $content;
707             }
708 42 50       87 next unless (@contents);
709 42         91 $to_referred->(@contents);
710             } # end of field handling
711             } # end of field iteration
712 44 100       284 return unless ($referred);
713              
714 42 100       100 if($append) {
    100          
715 4 100       33 return [$referred] if $split;
716 3         86 return $referred;
717             } elsif($split) {
718 15         305 return [$referred];
719             }
720              
721 23         32 return join $join_char, @{$referred};
  23         626  
722             }
723              
724             sub _it_subspecs {
725 2     2   16 my ( $self, $data, $tag, $subspecs, $tag_index, $code_index ) = @_;
726             my $set_index = sub {
727 7     7   14 my ( $subspec ) = @_;
728 7         16 foreach my $side ( ('left', 'right') ) {
729 14 100       6268 next if ( ref $subspec->$side eq 'MARC::Spec::Comparisonstring' );
730             # only set new index if subspec field tag equals spec field tag!!
731 7 50       187 next unless ( $tag eq $subspec->$side->field->tag );
732 7         360 $subspec->$side->field->set_index_start_end( $tag_index );
733             }
734 2         18 };
735              
736 2         7 my $valid = 1;
737 2         6 foreach my $subspec ( @{$subspecs} ) {
  2         7  
738 4 100       20 if( ref $subspec eq 'ARRAY' ) { # chained subSpecs (OR)
739 1         4 foreach my $or_subspec ( @{$subspec} ) {
  1         4  
740 4         11 $set_index->( $or_subspec );
741 4         52 $valid = $self->_validate_subspec( $or_subspec, $data );
742             # at least one of them is true (OR)
743 4 100       13 last if $valid;
744             }
745             }
746             else { # repeated SubSpecs (AND)
747 3         14 $set_index->( $subspec );
748 3         63 $valid = $self->_validate_subspec( $subspec, $data );
749             # all of them have to be true (AND)
750 3 100       14 last unless $valid;
751             }
752             }
753 2         18 return $valid;
754             }
755              
756             sub _validate_subspec {
757 7     7   22 my ( $self, $subspec, $data ) = @_;
758 7         15 my ($left_subterm, $right_subterm);
759              
760 7 50 33     50 if('!' ne $subspec->operator && '?' ne $subspec->operator) {
761 7 50       238 if ( ref $subspec->left ne 'MARC::Spec::Comparisonstring' ) {
762 7         204 $left_subterm = $self->marc_spec(
763             $data,
764             $subspec->left,
765             { '-split' => 1 }
766             ); # split should result in an array ref
767 7 50       28 return 0 unless defined $left_subterm;
768             }
769             else {
770 0         0 push @{$left_subterm}, $subspec->left->comparable;
  0         0  
771             }
772             }
773              
774 7 50       170 if ( ref $subspec->right ne 'MARC::Spec::Comparisonstring' ) {
775 0         0 $right_subterm = $self->marc_spec(
776             $data,
777             $subspec->right,
778             { '-split' => 1 }
779             ); # split should result in an array ref
780 0 0       0 unless( defined $right_subterm ) {
781 0         0 $right_subterm = [];
782             }
783             }
784             else {
785 7         51 push @{$right_subterm}, $subspec->right->comparable;
  7         118  
786             }
787              
788 7 50       75 if($subspec->operator eq '?') {
789 0 0       0 return (@{$right_subterm}) ? 1 : 0;
  0         0  
790             }
791              
792 7 50       28 if($subspec->operator eq '!') {
793 0 0       0 return (@{$right_subterm}) ? 0 : 1;
  0         0  
794             }
795              
796 7 50       24 if($subspec->operator eq '=') {
797 7         11 foreach my $v ( @{$left_subterm->[0]} ) {
  7         21  
798 7 100   7   30 return 1 if List::Util::any {$v eq $_} @{$right_subterm};
  7         41  
  7         28  
799             }
800             }
801              
802 4 50       15 if($subspec->operator eq '!=') {
803 0         0 foreach my $v ( @{$left_subterm->[0]} ) {
  0         0  
804 0 0   0   0 return 0 if List::Util::any {$v eq $_} @{$right_subterm};
  0         0  
  0         0  
805             }
806 0         0 return 1;
807             }
808              
809 4 50       13 if($subspec->operator eq '~') {
810 0         0 foreach my $v ( @{$left_subterm->[0]} ) {
  0         0  
811 0 0   0   0 return 1 if List::Util::any {$v =~ /$_/} @{$right_subterm};
  0         0  
  0         0  
812             }
813             }
814              
815 4 50       13 if($subspec->operator eq '!~') {
816 0         0 foreach my $v ( @{$left_subterm->[0]} ) {
  0         0  
817 0 0   0   0 return 0 if List::Util::any {$v =~ /$_/} @{$right_subterm};
  0         0  
  0         0  
818             }
819 0         0 return 1;
820             }
821              
822 4         13 return 0;
823             }
824              
825             sub parse_marc_spec {
826             my ( $self, $marc_spec ) = @_;
827             return MARC::Spec::Parser->new( $marc_spec )->marcspec;
828             }
829              
830             sub _get_index_range {
831             my ( $index_start, $index_end, $last_index ) = @_;
832              
833             if ( '#' eq $index_start ) {
834             if ( '#' eq $index_end or 0 == $index_end ) { return [$last_index]; }
835             $index_start = $last_index;
836             $index_end = $last_index - $index_end;
837             if ( 0 > $index_end ) { $index_end = 0; }
838             }
839             else {
840             if ( $last_index < $index_start ) {
841             return [$index_start];
842             } # this will result to no hits
843             }
844              
845             if ( '#' eq $index_end or $index_end > $last_index ) {
846             $index_end = $last_index;
847             }
848              
849             return ( $index_start <= $index_end )
850             ? [ $index_start .. $index_end ]
851             : [ $index_end .. $index_start ];
852             }
853              
854             sub marc_xml {
855 11     11 1 173 my ($self,$data) = @_;
856              
857 11         22 my $xml;
858 11         225 my $exporter = Catmandu::Exporter::MARC::XML->new(file => \$xml , xml_declaration => 0 , collection => 0);
859 11         276 $exporter->add($data);
860 11         878 $exporter->commit;
861              
862 11         102 $xml;
863             }
864              
865             sub marc_record_to_json {
866 1     1 0 47 my ($self,$data,%opts) = @_;
867              
868 1 50       5 if (my $marc = delete $data->{'record'}) {
869 1         3 for my $field (@$marc) {
870 6         16 my ($tag, $ind1, $ind2, @subfields) = @$field;
871              
872 6 100 66     23 if ($tag eq 'LDR') {
    100          
873 1         2 shift @subfields;
874 1         4 $data->{leader} = join "", @subfields;
875             }
876             elsif ($tag eq 'FMT' || substr($tag, 0, 2) eq '00') {
877 2         3 shift @subfields;
878 2   100     4 push @{$data->{fields} ||= []} , { $tag => join "" , @subfields };
  2         10  
879             }
880             else {
881 3         4 my @sf;
882 3 50 33     10 my $start = !defined($subfields[0]) || $subfields[0] eq '_' ? 2 : 0;
883 3         7 for (my $i = $start; $i < @subfields; $i += 2) {
884 5         17 push @sf, { $subfields[$i] => $subfields[$i+1] };
885             }
886 3   50     3 push @{$data->{fields} ||= []} , { $tag => {
  3         15  
887             subfields => \@sf,
888             ind1 => $ind1,
889             ind2 => $ind2 } };
890             }
891             }
892             }
893              
894 1         22 $data;
895             }
896              
897             sub marc_json_to_record {
898 1     1 0 13 my ($self,$data,%opts) = @_;
899              
900 1         2 my $record = [];
901              
902 1 50       7 if (Catmandu::Util::is_string($data->{leader})) {
903 1         4 push @$record , [ 'LDR', undef, undef, '_', $data->{leader} ],
904             }
905              
906 1 50       4 if (Catmandu::Util::is_array_ref($data->{fields})) {
907 1         2 for my $field (@{$data->{fields}}) {
  1         3  
908 5 50       11 next unless Catmandu::Util::is_hash_ref($field);
909              
910 5         10 my ($tag) = keys %$field;
911 5         6 my $val = $field->{$tag};
912              
913 5 100 66     22 if ($tag eq 'FMT' || substr($tag, 0, 2) eq '00') {
    50          
914 2         6 push @$record , [ $tag, undef, undef, '_', $val ],
915             }
916             elsif (Catmandu::Util::is_hash_ref($val)) {
917 3         5 my $ind1 = $val->{ind1};
918 3         4 my $ind2 = $val->{ind2};
919 3 50       7 next unless Catmandu::Util::is_array_ref($val->{subfields});
920              
921 3         5 my $sfs = [ '_' , ''];
922 3         4 for my $sf (@{ $val->{subfields} }) {
  3         6  
923 5 50       10 next unless Catmandu::Util::is_hash_ref($sf);
924              
925 5         9 my ($code) = keys %$sf;
926 5         7 my $sval = $sf->{$code};
927              
928 5         10 push @$sfs , [ $code , $sval];
929             }
930              
931 3         8 push @$record , [ $tag , $ind1 , $ind2 , @$sfs];
932             }
933             }
934             }
935              
936 1 50       3 if (@$record > 0) {
937 1         6 delete $data->{fields};
938 1         2 delete $data->{leader};
939 1         2 $data->{'record'} = $record;
940             }
941              
942 1         19 $data;
943             }
944              
945             sub marc_decode_dollar_subfields {
946 1     1 1 13 my ($self,$data,%opts) = @_;
947 1         2 my $old_record = $data->{'record'};
948 1         3 my $new_record = [];
949              
950 1         2 for my $field (@$old_record) {
951 14         57 my ($tag,$ind1,$ind2,@subfields) = @$field;
952              
953 14         38 my $fixed_field = [$tag,$ind1,$ind2];
954              
955 14         34 for (my $i = 0 ; $i < @subfields ; $i += 2) {
956 30         55 my $code = $subfields[$i];
957 30         49 my $value = $subfields[$i+1];
958              
959             # If a subfield contains fields coded like: data$xmore$yevenmore
960             # chunks = (data,x,y,evenmore)
961 30         94 my @chunks = split( /\$([a-z])/, $value );
962              
963 30         53 my $real_value = shift @chunks;
964              
965 30         76 push @$fixed_field , ( $code, $real_value);
966              
967 30         90 while (@chunks) {
968 2         11 push @$fixed_field , ( splice @chunks, 0, 2 );
969             }
970             }
971              
972 14         38 push @$new_record , $fixed_field;
973             }
974              
975 1         4 $data->{'record'} = $new_record;
976              
977 1         46 $data;
978             }
979              
980             sub compile_marc_path {
981             my ($self,$marc_path,%opts) = @_;
982              
983             my ($field,$field_regex,$ind1,$ind2,
984             $subfield,$subfield_regex,$from,$to,$len,$is_regex_field);
985              
986             my $MARC_PATH_REGEX = qr/(\S{1,3})(\[([^,])?,?([^,])?\])?([\$_a-z0-9^]+)?(\/([0-9]+)(-([0-9]+))?)?/;
987             if ($marc_path =~ $MARC_PATH_REGEX) {
988             $field = $1;
989             $ind1 = $3;
990             $ind2 = $4;
991             $subfield = $5;
992             $field = "0" x (3 - length($field)) . $field; # fixing 020 treated as 20 bug
993             if (defined($subfield)) {
994             $subfield =~ s{\$}{}g;
995             unless ($subfield =~ /^[a-zA-Z0-9]$/) {
996             $subfield = "[$subfield]";
997             }
998             }
999             elsif ($opts{subfield_default}) {
1000             $subfield = $field =~ /^0|LDR|FMT/ ? '_' : 'a';
1001             }
1002             elsif ($opts{subfield_wildcard}) {
1003             $subfield = '[a-z0-9_]';
1004             }
1005             if (defined($subfield)) {
1006             $subfield_regex = qr/^(?:${subfield})$/;
1007             }
1008             $from = $7;
1009             $to = $9;
1010             $len = defined $to ? $to - $from + 1 : 1;
1011             }
1012             else {
1013             return undef;
1014             }
1015              
1016             if ($field =~ /[\*\.]/) {
1017             $field_regex = $field;
1018             $field_regex =~ s/[\*\.]/(?:[A-Z0-9])/g;
1019             $is_regex_field = 1;
1020             $field_regex = qr/^$field_regex$/;
1021             }
1022             else {
1023             $is_regex_field = 0;
1024             }
1025              
1026             return {
1027             field => $field ,
1028             field_regex => $field_regex ,
1029             is_regex_field => $is_regex_field ,
1030             subfield => $subfield ,
1031             subfield_regex => $subfield_regex ,
1032             ind1 => $ind1 ,
1033             ind2 => $ind2 ,
1034             start => 3,
1035             from => $from ,
1036             to => $to ,
1037             len => $len
1038             };
1039             }
1040              
1041             sub marc_copy {
1042 0     0 1   my $self = $_[0];
1043 0           my $data = $_[1];
1044 0           my $marc_path = $_[2];
1045 0           my $marc_value = $_[3];
1046 0           my $is_cut = $_[4];
1047              
1048             # $_[2] : marc_path
1049 0 0         my $context = ref($marc_path) ? $marc_path : $self->compile_marc_path($_[2], subfield_wildcard => 0);
1050              
1051 0 0         confess "invalid marc path" unless $context;
1052              
1053             # $_[1] : data record
1054 0           my $record = $data->{'record'};
1055              
1056 0 0 0       return wantarray ? () : undef unless (defined $record && ref($record) eq 'ARRAY');
    0          
1057              
1058             # When is_cut is on, we need to create a new record containing the remaining fields
1059 0           my @new_record = ();
1060              
1061 0           my $fields = [];
1062              
1063 0           for my $field (@$record) {
1064 0           my ($tag, $ind1, $ind2, @subfields) = @$field;
1065              
1066 0 0 0       if (
      0        
      0        
1067             ($context->{is_regex_field} == 0 && $tag ne $context->{field} )
1068             ||
1069             ($context->{is_regex_field} == 1 && $tag !~ $context->{field_regex} )
1070             ) {
1071 0 0         push @new_record , $field if $is_cut;
1072 0           next;
1073             }
1074              
1075 0 0         if (defined $context->{ind1}) {
1076 0 0 0       if (!defined $ind1 || $ind1 ne $context->{ind1}) {
1077 0 0         push @new_record , $field if $is_cut;
1078 0           next;
1079             }
1080             }
1081 0 0         if (defined $context->{ind2}) {
1082 0 0 0       if (!defined $ind2 || $ind2 ne $context->{ind2}) {
1083 0 0         push @new_record , $field if $is_cut;
1084 0           next;
1085             }
1086             }
1087              
1088 0 0         if ($context->{subfield}) {
1089 0           my $found = 0;
1090 0           for (my $i = 0; $i < @subfields; $i += 2) {
1091 0 0         if ($subfields[$i] =~ $context->{subfield}) {
1092 0 0         if (defined($marc_value)) {
1093 0 0         $found = 1 if $subfields[$i+1] =~ /$marc_value/;
1094             }
1095             else {
1096 0           $found = 1;
1097             }
1098             }
1099             }
1100              
1101 0 0         unless ($found) {
1102 0 0         push @new_record , $field if $is_cut;
1103 0           next;
1104             }
1105             }
1106             else {
1107 0 0         if (defined($marc_value)) {
1108 0           my @sf = ();
1109 0           for (my $i = 0; $i < @subfields; $i += 2) {
1110 0           push @sf , $subfields[$i+1];
1111             }
1112              
1113 0           my $string = join "", @sf;
1114              
1115 0 0         unless ($string =~ /$marc_value/) {
1116 0 0         push @new_record , $field if $is_cut;
1117 0           next;
1118             }
1119             }
1120             }
1121              
1122 0           my $f = {};
1123 0           $f->{tag} = $field->[0];
1124              
1125             # indicator 1
1126 0 0         if(defined $field->[1]) {
1127 0           $f->{ind1} = $field->[1];
1128             } else {
1129 0           $f->{ind1} = undef;
1130             }
1131              
1132             # indicator 2
1133 0 0         if(defined $field->[2]) {
1134 0           $f->{ind2} = $field->[2];
1135             } else {
1136 0           $f->{ind2} = undef;
1137             }
1138              
1139             # fixed fields
1140 0 0         if($field->[3] eq '_') {
1141 0           $f->{content} = $field->[4];
1142 0           push(@$fields, $f);
1143 0           next;
1144             }
1145              
1146             # subfields
1147 0           for (my $i = $context->{start}; $i < @{$field}; $i += 2) {
  0            
1148 0           push(@{$f->{subfields}}, { $field->[$i] => $field->[$i + 1] });
  0            
1149             }
1150              
1151 0           push(@$fields, $f);
1152             }
1153              
1154 0 0         if ($is_cut) {
1155 0           $data->{record} = \@new_record;
1156             }
1157              
1158 0           [$fields];
1159             }
1160              
1161             sub marc_paste {
1162 0     0 1   my $self = $_[0];
1163 0           my $data = $_[1];
1164 0           my $json_path = $_[2];
1165 0           my $marc_path = $_[3];
1166 0           my $marc_value = $_[4];
1167              
1168 0           my $value = Catmandu::Util::data_at($json_path,$data);
1169              
1170 0 0         return $data unless Catmandu::Util::is_array_ref($value);
1171              
1172 0           my @new_parts;
1173              
1174 0           for my $part (@$value) {
1175             return $data unless
1176             Catmandu::Util::is_hash_ref($part) &&
1177             exists $part->{tag} &&
1178             exists $part->{ind1} &&
1179             exists $part->{ind2} &&
1180 0 0 0       ( exists $part->{content} || exists $part->{subfields} );
      0        
      0        
      0        
      0        
1181              
1182 0           my $tag = $part->{tag};
1183 0   0       my $ind1 = $part->{ind1} // ' ';
1184 0   0       my $ind2 = $part->{ind2} // ' ';
1185 0           my $content = $part->{content};
1186 0           my $subfields = $part->{subfields};
1187              
1188 0 0 0       if (defined($content)) {
    0          
1189 0           push @new_parts , [ $tag , $ind1 , $ind2 , '_' , $content ];
1190             }
1191             elsif (defined($subfields) && Catmandu::Util::is_array_ref($subfields)) {
1192 0           my @tmp = ( $tag , $ind1 , $ind2 );
1193              
1194 0           for my $sf (@$subfields) {
1195 0           while (my ($key, $value) = each %$sf) {
1196 0           push @tmp, $key , $value;
1197             }
1198             }
1199              
1200 0           push @new_parts , [ @tmp ];
1201             }
1202             else {
1203             # Illegal input
1204 0           return $data;
1205             }
1206             }
1207              
1208 0 0         if (defined($marc_path)) {
1209 0           my $context = $self->compile_marc_path($marc_path, subfield_wildcard => 0);
1210              
1211 0 0         confess "invalid marc path" unless $context;
1212              
1213 0           my @record = @{$data->{record}};
  0            
1214 0           my $found_match = undef;
1215              
1216 0           my $field_position = -1;
1217              
1218 0           for my $field (@record) {
1219 0           $field_position++;
1220 0           my ($tag, $ind1, $ind2, @subfields) = @$field;
1221              
1222 0 0         if ($context->{is_regex_field}) {
1223 0 0         next unless $tag =~ $context->{field_regex};
1224             }
1225             else {
1226 0 0         next unless $tag eq $context->{field};
1227             }
1228              
1229 0 0         if (defined $context->{ind1}) {
1230 0 0 0       if (!defined $ind1 || $ind1 ne $context->{ind1}) {
1231 0           next;
1232             }
1233             }
1234 0 0         if (defined $context->{ind2}) {
1235 0 0 0       if (!defined $ind2 || $ind2 ne $context->{ind2}) {
1236 0           next;
1237             }
1238             }
1239              
1240 0 0         if ($context->{subfield}) {
1241 0           for (my $i = 0; $i < @subfields; $i += 2) {
1242 0 0         if ($subfields[$i] =~ $context->{subfield}) {
1243 0 0         if (defined($marc_value)) {
1244 0 0         $found_match = $field_position if $subfields[$i+1] =~ /$marc_value/;
1245             }
1246             else {
1247 0           $found_match = $field_position;
1248             }
1249             }
1250             }
1251             } else {
1252 0 0         if (defined($marc_value)) {
1253 0           my @sf = ();
1254 0           for (my $i = 0; $i < @subfields; $i += 2) {
1255 0           push @sf , $subfields[$i+1];
1256             }
1257              
1258 0           my $string = join "", @sf;
1259              
1260 0 0         if ($string =~ /$marc_value/) {
1261 0           $found_match = $field_position;
1262             }
1263             else {
1264             # don't match anything
1265             }
1266             }
1267             else {
1268 0           $found_match = $field_position;
1269             }
1270             }
1271             }
1272              
1273 0 0         if (defined $found_match) {
1274 0           my @new_record = (
1275             @record[0..$found_match] ,
1276             @new_parts ,
1277             @record[$found_match+1..$#record]
1278             );
1279 0           $data->{record} = \@new_record;
1280             }
1281             }
1282             else {
1283 0           push @{$data->{record}} , @new_parts;
  0            
1284             }
1285              
1286 0           $data;
1287             }
1288              
1289             1;
1290              
1291             __END__
1292              
1293             =head1 NAME
1294              
1295             Catmandu::MARC - Catmandu modules for working with MARC data
1296              
1297             =begin markdown
1298              
1299             # STATUS
1300              
1301             [![Build Status](https://travis-ci.org/LibreCat/Catmandu-MARC.svg?branch=master)](https://travis-ci.org/LibreCat/Catmandu-MARC)
1302             [![Coverage](https://coveralls.io/repos/LibreCat/Catmandu-MARC/badge.png?branch=master)](https://coveralls.io/r/LibreCat/Catmandu-MARC)
1303             [![CPANTS kwalitee](http://cpants.cpanauthors.org/dist/Catmandu-MARC.png)](http://cpants.cpanauthors.org/dist/Catmandu-MARC)
1304              
1305             =end markdown
1306              
1307             =head1 SYNOPSIS
1308              
1309             # On the command line
1310              
1311             $ catmandu convert MARC to JSON < data.mrc
1312              
1313             $ catmandu convert MARC --type MiJ to YAML < data.marc_in_json
1314              
1315             $ catmandu convert MARC --fix "marc_map(245,title)" < data.mrc
1316              
1317             $ catmandu convert MARC --fix myfixes.txt < data.mrc
1318              
1319             myfixes:
1320              
1321             marc_map("245a", title)
1322             marc_map("5**", note.$append)
1323             marc_map('710','my.authors.$append')
1324             marc_map('008_/35-35','my.language')
1325             remove_field(record)
1326             add_field(my.funny.field,'test123')
1327              
1328             $ catmandu import MARC --fix myfixes.txt to ElasticSearch --index_name 'catmandu' < data.marc
1329              
1330             # In perl
1331             use Catmandu;
1332              
1333             my $importer = Catmandu->importer('MARC', file => 'data.mrc' );
1334             my $fixer = Catmandu->fixer('myfixes.txt');
1335             my $store = Catmandu->store('ElasticSearch', index_name => 'catmandu');
1336              
1337             $store->add_many(
1338             $fixer->fix($importer)
1339             );
1340              
1341             =head1 MODULES
1342              
1343             =over
1344              
1345             =item * L<Catmandu::MARC::Tutorial>
1346              
1347             =item * L<Catmandu::Importer::MARC>
1348              
1349             =item * L<Catmandu::Exporter::MARC>
1350              
1351             =item * L<Catmandu::Fix::marc_map>
1352              
1353             =item * L<Catmandu::Fix::marc_spec>
1354              
1355             =item * L<Catmandu::Fix::marc_add>
1356              
1357             =item * L<Catmandu::Fix::marc_append>
1358              
1359             =item * L<Catmandu::Fix::marc_replace_all>
1360              
1361             =item * L<Catmandu::Fix::marc_remove>
1362              
1363             =item * L<Catmandu::Fix::marc_xml>
1364              
1365             =item * L<Catmandu::Fix::marc_in_json>
1366              
1367             =item * L<Catmandu::Fix::marc_decode_dollar_subfields>
1368              
1369             =item * L<Catmandu::Fix::marc_set>
1370              
1371             =item * L<Catmandu::Fix::marc_copy>
1372              
1373             =item * L<Catmandu::Fix::marc_cut>
1374              
1375             =item * L<Catmandu::Fix::marc_paste>
1376              
1377             =item * L<Catmandu::Fix::Bind::marc_each>
1378              
1379             =item * L<Catmandu::Fix::Condition::marc_match>
1380              
1381             =item * L<Catmandu::Fix::Condition::marc_has>
1382              
1383             =item * L<Catmandu::Fix::Condition::marc_has_many>
1384              
1385             =item * L<Catmandu::Fix::Condition::marc_spec_has>
1386              
1387             =item * L<Catmandu::Fix::Inline::marc_map>
1388              
1389             =item * L<Catmandu::Fix::Inline::marc_add>
1390              
1391             =item * L<Catmandu::Fix::Inline::marc_remove>
1392              
1393             =back
1394              
1395             =head1 DESCRIPTION
1396              
1397             With Catmandu, LibreCat tools abstract digital library and research services as data
1398             warehouse processes. As stores we reuse MongoDB or ElasticSearch providing us with
1399             developer friendly APIs. Catmandu works with international library standards such as
1400             MARC, MODS and Dublin Core, protocols such as OAI-PMH, SRU and open repositories such
1401             as DSpace and Fedora. And, of course, we speak the evolving Semantic Web.
1402              
1403             Follow us on L<http://librecat.org> and read an introduction into Catmandu data
1404             processing at L<https://github.com/LibreCat/Catmandu/wiki>.
1405              
1406             =head1 SEE ALSO
1407              
1408             L<Catmandu>,
1409             L<Catmandu::Importer>,
1410             L<Catmandu::Fix>,
1411             L<Catmandu::Store>,
1412             L<MARC::Spec>
1413              
1414             =head1 AUTHOR
1415              
1416             Patrick Hochstenbach, C<< <patrick.hochstenbach at ugent.be> >>
1417              
1418             =head1 CONTRIBUTORS
1419              
1420             =over
1421              
1422             =item * Nicolas Steenlant, C<< <nicolas.steenlant at ugent.be> >>
1423              
1424             =item * Nicolas Franck, C<< <nicolas.franck at ugent.be> >>
1425              
1426             =item * Johann Rolschewski, C<< jorol at cpan.org >>
1427              
1428             =item * Chris Cormack
1429              
1430             =item * Robin Sheat
1431              
1432             =item * Carsten Klee, C<< klee at cpan.org >>
1433              
1434             =back
1435              
1436             =head1 LICENSE AND COPYRIGHT
1437              
1438             This program is free software; you can redistribute it and/or modify it
1439             under the terms of either: the GNU General Public License as published
1440             by the Free Software Foundation; or the Artistic License.
1441              
1442             See http://dev.perl.org/licenses/ for more information.
1443              
1444             =cut