File Coverage

blib/lib/Catmandu/MARC.pm
Criterion Covered Total %
statement 455 653 69.6
branch 236 430 54.8
condition 83 205 40.4
subroutine 26 31 83.8
pod 11 13 84.6
total 811 1332 60.8


line stmt bran cond sub pod time code
1             package Catmandu::MARC;
2              
3 28     28   48576 use Catmandu::Sane;
  28         656594  
  28         156  
4 28     28   4791 use Catmandu::Util;
  28         974  
  28         1129  
5 28     28   8350 use Catmandu::Exporter::MARC::XML;
  28         127  
  28         1107  
6 28     28   10388 use MARC::Spec::Parser;
  28         894272  
  28         1106  
7 28     28   272 use List::Util;
  28         112  
  28         1725  
8 28     28   12114 use Memoize;
  28         55706  
  28         1452  
9 28     28   199 use Carp;
  28         66  
  28         1242  
10 28     28   160 use Moo;
  28         96  
  28         179  
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.21';
19              
20             sub marc_map {
21 624     624 1 39311 my $self = $_[0];
22              
23             # $_[2] : marc_path
24 624 100       1641 my $context = ref($_[2]) ?
25             $_[2] :
26             $self->compile_marc_path($_[2], subfield_wildcard => 1);
27              
28 624 50       1431 confess "invalid marc path" unless $context;
29              
30             # $_[1] : data record
31 624         961 my $record = $_[1]->{'record'};
32              
33 624 0 33     1913 return wantarray ? () : undef unless (defined $record && ref($record) eq 'ARRAY');
    50          
34              
35             # $_[3] : opts
36 624   50     1201 my $split = $_[3]->{'-split'} // 0;
37 624   50     1103 my $join_char = $_[3]->{'-join'} // '';
38 624   50     989 my $pluck = $_[3]->{'-pluck'} // 0;
39 624   100     1361 my $value_set = $_[3]->{'-value'} // undef;
40 624   50     1060 my $nested_arrays = $_[3]->{'-nested_arrays'} // 0;
41 624   100     1010 my $append = $_[3]->{'-force_array'} // undef;
42              
43 624         671 my $vals;
44              
45 624         909 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 5123 50 100     16787 );
      0        
      33        
      66        
      0        
      33        
      33        
      66        
      33        
55              
56 363         544 my $v;
57              
58 363 100       544 if ($value_set) {
59 48         77 for (my $i = 3; $i < @{$field}; $i += 2) {
  110         219  
60 96         136 my $subfield_regex = $context->{subfield_regex};
61 96 100       379 if ($field->[$i] =~ $subfield_regex) {
62 34         65 $v = $value_set;
63 34         72 last;
64             }
65             }
66             }
67             else {
68 315         469 $v = [];
69              
70 315 100       481 if ($pluck) {
71             # Treat the subfield as a hash index
72 16         30 my $_h = {};
73 16         32 for (my $i = $context->{start}; $i < @{$field}; $i += 2) {
  49         109  
74 33         44 push @{ $_h->{ $field->[$i] } } , $field->[$i + 1];
  33         102  
75             }
76 16         32 my $subfield = $context->{subfield};
77 16         84 $subfield =~ s{[^a-zA-Z0-9]}{}g;
78 16         57 for my $c (split('',$subfield)) {
79 33   100     79 my $val = $_h->{$c} // [undef];
80 33         43 push @$v , @{ $val } ;
  33         87  
81             }
82             }
83             else {
84 299         490 for (my $i = $context->{start}; $i < @{$field}; $i += 2) {
  714         1344  
85 415         536 my $subfield_regex = $context->{subfield_regex};
86 415 100       1510 if ($field->[$i] =~ $subfield_regex) {
87 274         690 push(@$v, $field->[$i + 1]);
88             }
89             }
90             }
91              
92 315 100       551 if (@$v) {
93 261 100       459 if (!$split) {
94 226         360 my @defined_values = grep {defined($_)} @$v;
  255         677  
95 226         548 $v = join $join_char, @defined_values;
96             }
97              
98 261 100       651 if (defined(my $off = $context->{from})) {
99 51 100       112 if (ref $v eq 'ARRAY') {
100 11         26 my @defined_values = grep {defined($_)} @$v;
  11         38  
101 11         29 $v = join $join_char, @defined_values;
102             }
103 51         83 my $len = $context->{len};
104 51 100       117 if (length(${v}) > $off) {
105 41         97 $v = substr($v, $off, $len);
106             } else {
107 10         22 $v = undef;
108             }
109             }
110             }
111             else {
112 54         119 $v = undef;
113             }
114             }
115              
116 363 100       646 if (defined $v) {
117 285 100       451 if ($split) {
118 35 100 66     138 $v = [ $v ] unless (defined($v) && ref($v) eq 'ARRAY');
119 35 100 66     95 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 5 100       10 if ($nested_arrays == 1) {
124 4         10 push @$vals , $v;
125             }
126             else {
127 1         2 push @$vals , @$v;
128             }
129             }
130             else {
131 30 100       54 if ($nested_arrays == 1) {
132 9         17 $vals = [$v];
133             }
134             else {
135 21         60 $vals = [ @$v ];
136             }
137             }
138             }
139             else {
140 250         552 push @$vals , $v;
141             }
142             }
143             }
144              
145 624 100 66     1788 if ($split && defined $vals) {
    100          
    100          
146 30         65 $vals = [ $vals ];
147             }
148             elsif ($append) {
149             # we got a $append
150             }
151             elsif (defined $vals) {
152 133         304 $vals = join $join_char , @$vals;
153             }
154             else {
155             # no result
156             }
157              
158 624         9498 $vals;
159             }
160              
161             sub marc_add {
162 10     10 1 156 my ($self,$data,$marc_path,@subfields) = @_;
163              
164 10         32 my %subfields = @subfields;
165 10   100     33 my $marc = $data->{'record'} // [];
166              
167 10 50       38 if ($marc_path =~ /^\w{3}$/) {
168 10         19 my @field = ();
169 10         18 push @field , $marc_path;
170 10   100     38 push @field , $subfields{ind1} // ' ';
171 10   100     33 push @field , $subfields{ind2} // ' ';
172              
173              
174 10         28 for (my $i = 0 ; $i < @subfields ; $i += 2) {
175 20         28 my $code = $subfields[$i];
176 20 100       41 next unless length $code == 1;
177 14         24 my $value = $subfields[$i+1];
178              
179 14 100       42 if ($value =~ /^\$\.(\S+)$/) {
180 6         14 my $path = $1;
181 6         22 $value = Catmandu::Util::data_at($path,$data);
182             }
183              
184 14 100 33     471 if (Catmandu::Util::is_array_ref $value) {
    50          
    50          
185 3         9 for (@$value) {
186 9         14 push @field , $code;
187 9         21 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         21 push @field , $code;
198 11         32 push @field , $value;
199             }
200             }
201              
202 10 50       29 push @{ $marc } , \@field if @field > 3;
  10         20  
203             }
204              
205 10         19 $data->{'record'} = $marc;
206              
207 10         94 $data;
208             }
209              
210             sub marc_append {
211 1     1 1 24 my ($self,$data,$marc_path,$value) = @_;
212 1         5 my $record = $data->{'record'};
213              
214 1 50       7 return $data unless defined $record;
215              
216 1 50       6 if ($value =~ /^\$\.(\S+)/) {
217 0         0 my $path = $1;
218 0         0 $value = Catmandu::Util::data_at($path,$data);
219             }
220              
221 1 50       11 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         31 my $context = $self->compile_marc_path($marc_path);
233              
234 1 50       18 confess "invalid marc path" unless $context;
235              
236 1         5 for my $field (@$record) {
237 19         74 my ($tag, $ind1, $ind2, @subfields) = @$field;
238              
239 19 50       48 if ($context->{is_regex_field}) {
240 0 0       0 next unless $tag =~ $context->{field_regex};
241             }
242             else {
243 19 100       71 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       6 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       4 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         6 $field->[-1] .= $value;
266             }
267             }
268              
269 1         31 $data;
270             }
271              
272             sub marc_replace_all {
273 3     3 1 35 my ($self,$data,$marc_path,$regex,$value) = @_;
274 3         5 my $record = $data->{'record'};
275              
276 3 50       8 return $data unless defined $record;
277              
278 3 50       11 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         46 my $context = $self->compile_marc_path($marc_path, subfield_wildcard => 1);
295              
296 3 50       91 confess "invalid marc path" unless $context;
297              
298 3         8 for my $field (@$record) {
299 57         111 my ($tag, $ind1, $ind2, @subfields) = @$field;
300              
301 57 50       83 if ($context->{is_regex_field}) {
302 0 0       0 next unless $tag =~ $context->{field_regex};
303             }
304             else {
305 57 100       106 next unless $tag eq $context->{field};
306             }
307              
308 5 50       11 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       46 if ($subfields[$i] =~ $context->{subfield}) {
321             # Trick to double eval the right hand side
322 5         45 $field->[$i + 4] =~ s{$regex}{"\"$value\""}eeg;
  5         226  
323             }
324             }
325             }
326              
327 3         50 $data;
328             }
329              
330             sub marc_set {
331 8     8 1 98 my ($self,$data,$marc_path,$value,%opts) = @_;
332 8         18 my $record = $data->{'record'};
333              
334 8 50       25 return $data unless defined $record;
335              
336 8 100       30 if ($value =~ /^\$\.(\S+)/) {
337 3         10 my $path = $1;
338 3         13 $value = Catmandu::Util::data_at($path,$data);
339             }
340              
341 8 50       208 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         130 my $context = $self->compile_marc_path($marc_path, subfield_default => 1);
353              
354 8 50       157 confess "invalid marc path" unless $context;
355              
356 8         24 for my $field (@$record) {
357 168         343 my ($tag, $ind1, $ind2, @subfields) = @$field;
358              
359 168 50       228 if ($context->{is_regex_field}) {
360 0 0       0 next unless $tag =~ $context->{field_regex};
361             }
362             else {
363 168 100       309 next unless $tag eq $context->{field};
364             }
365              
366 8 100       23 if (defined $context->{ind1}) {
367 2 50 33     14 if (!defined $ind1 || $ind1 ne $context->{ind1}) {
368 0         0 next;
369             }
370             }
371 8 50       20 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         14 my $found = 0;
378 8         25 for (my $i = 0; $i < @subfields; $i += 2) {
379 13 100       93 if ($subfields[$i] =~ $context->{subfield}) {
380 5 100       17 if (defined $context->{from}) {
381 1         8 substr($field->[$i + 4], $context->{from}, $context->{len}) = $value;
382             }
383             else {
384 4         12 $field->[$i + 4] = $value;
385             }
386 5         15 $found = 1;
387             }
388             }
389              
390 8 100       27 if ($found == 0) {
391 3         12 push(@$field,$context->{subfield},$value);
392             }
393             }
394              
395 8         92 $data;
396             }
397              
398             sub marc_remove {
399 7     7 1 88 my ($self,$data, $marc_path,%opts) = @_;
400 7         16 my $record = $data->{'record'};
401              
402 7         12 my $new_record;
403              
404 7         129 my $context = $self->compile_marc_path($marc_path);
405              
406 7 50       73 confess "invalid marc path" unless $context;
407              
408 7         21 for my $field (@$record) {
409 120         143 my $field_size = int(@$field);
410              
411 120 100 66     431 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         12 my $ind_match = undef;
418              
419 7 100 66     58 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     18 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     12 $ind_match = 1 if (defined $field->[2] && $field->[2] eq $context->{ind2});
428             }
429             else {
430 5         9 $ind_match = 1;
431             }
432              
433 7 100 100     32 if ($ind_match && ! defined $context->{subfield_regex}) {
434 4         10 next;
435             }
436              
437 3 50       5 if (defined $context->{subfield_regex}) {
438 3         5 my $subfield_regex = $context->{subfield_regex};
439 3         7 my $new_subf = [];
440 3         14 for (my $i = $context->{start}; $i < $field_size; $i += 2) {
441 6 100       32 unless ($field->[$i] =~ $subfield_regex) {
442 2         5 push @$new_subf , $field->[$i];
443 2         6 push @$new_subf , $field->[$i+1];
444             }
445             }
446              
447 3 100       10 splice @$field , $context->{start} , int(@$field), @$new_subf if $ind_match;
448             }
449             }
450              
451 116         174 push @$new_record , $field;
452             }
453              
454 7         15 $data->{'record'} = $new_record;
455              
456 7         128 return $data;
457             }
458              
459             sub marc_spec {
460 47     47 1 5756 my $self = $_[0];
461              
462             # $_[1] : data record
463 47         60 my $data = $_[1];
464 47         63 my $record = $data->{'record'};
465              
466             # $_[2] : spec
467 47         63 my ($ms, $spec);
468 47 50       103 if( ref $_[2] ) {
469 47         58 $ms = $_[2];
470 47         107 $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         4777 my $EMPTY = q{};
477             # $_[3] : opts
478 47   50     122 my $split = $_[3]->{'-split'} // 0;
479 47   66     105 my $join_char = $_[3]->{'-join'} // $EMPTY;
480 47   100     101 my $pluck = $_[3]->{'-pluck'} // 0;
481 47   100     145 my $value_set = $_[3]->{'-value'} // undef;
482 47   100     93 my $invert = $_[3]->{'-invert'} // 0;
483 47   100     83 my $nested_arrays = $_[3]->{'-nested_arrays'} // 0;
484 47   100     87 my $append = $_[3]->{'-force_array'} // 0;
485              
486 47 50       77 if($nested_arrays) {
487 0         0 $split = 1
488             }
489              
490             # filter by tag
491 47         71 my @fields = ();
492 47         663 my $field_spec = $ms->field;
493 47         259 my $tag_spec = $field_spec->tag;
494              
495 47         60 @fields = grep { $_->[0] =~ /$tag_spec/ } @{ $record };
  956         1897  
  47         81  
496 47 100       127 return unless @fields;
497              
498             # filter by indicator
499 45         62 my ( $indicator1, $indicator2 );
500 45 100       105 if ( $field_spec->has_indicator1 ) {
501 2         6 $indicator1 = $field_spec->indicator1;
502 2         16 $indicator1 = qr/$indicator1/;
503             }
504 45 100       83 if ( $field_spec->has_indicator2 ) {
505 3         7 $indicator2 = $field_spec->indicator2;
506 3         25 $indicator2 = qr/$indicator2/;
507             }
508              
509             # calculate char start
510             my $chst = sub {
511 75     75   105 my ($sp) = @_;
512 75         81 my $char_start;
513 75 100       134 if ( $sp->has_char_start ) {
514 17 100       300 $char_start = ( '#' eq $sp->char_start )
515             ? $sp->char_length * -1
516             : $sp->char_start;
517             }
518 75         481 return $char_start;
519 45         166 };
520              
521             # vars we need only for subfields
522 45         68 my (@sf_spec, $invert_level, $codes, $invert_chars);
523 45 100       89 if ( $ms->has_subfields ) {
524             # set the order of subfields
525 18         23 @sf_spec = map { $_ } @{ $ms->subfields };
  29         100  
  18         28  
526 18 100       39 unless ( $pluck ) {
527 17         50 @sf_spec = sort { $a->code cmp $b->code } @sf_spec;
  11         36  
528             }
529              
530             # set invert level default
531 18         19 $invert_level = 4;
532 18 100       31 if ( $invert ) {
533 4         7 $codes = '[^';
534 4         7 $codes .= join $EMPTY, map { $_->code } @sf_spec;
  10         21  
535 4         6 $codes .= ']';
536             }
537              
538             $invert_chars = sub {
539 4     4   28 my ( $str, $start, $length ) = @_;
540 4         9 for ( substr $str, $start, $length ) {
541 4         11 $_ = $EMPTY;
542             }
543 4         15 return $str;
544 18         50 };
545             }
546             else {
547             # return $value_set ASAP
548 27 100       66 return $value_set if defined $value_set;
549             }
550              
551             # vars we need for fields and subfields
552 44         60 my ($referred, $char_start, $prev_tag, $index_range);
553 44         57 my $current_tag = $EMPTY;
554 44         49 my $tag_index = 0;
555 44         677 my $index_start = $field_spec->index_start;
556 44         724 my $index_end = $field_spec->index_end;
557              
558             my $to_referred = sub {
559 64     64   115 my ( @values ) = @_;
560 64 50       114 if($nested_arrays) {
    100          
561 0         0 push @{$referred}, \@values;
  0         0  
562             } elsif($split) {
563 29         34 push @{$referred}, @values;
  29         74  
564             } else {
565 35         39 push @{$referred}, join $join_char, @values;
  35         115  
566             }
567 44         299 };
568              
569 44 50       606 if( defined $field_spec->index_start ) {
570 44         696 $index_range =
571             _get_index_range( $field_spec->index_start, $field_spec->index_end, $#fields );
572             }
573              
574             # iterate over fields
575 44         1856 for my $field (@fields) {
576 114         408 $prev_tag = $current_tag;
577 114         148 $current_tag = $field->[0];
578              
579 114 100 66     250 $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       171 if( defined $indicator1 ) {
585 21 100 66     90 next unless ( defined $field->[1] && $field->[1] =~ $indicator1);
586             }
587              
588 95 100       130 if( defined $indicator2 ) {
589             #next unless $field->[2] =~ $indicator2;
590 22 100 66     82 next unless ( defined $field->[2] && $field->[2] =~ $indicator2);
591             }
592              
593             # filter by index
594 81 50       117 if ( defined $index_range ) {
595 81 100       164 next unless ( Catmandu::Util::array_includes( $index_range, $tag_index ) );
596             }
597              
598             # filter field by subspec
599 72 100       4258 if( $field_spec->has_subspecs) {
600 2         11 my $valid = $self->_it_subspecs( $data, $field_spec->tag, $field_spec->subspecs, $tag_index );
601 2 100       12 next unless $valid;
602             }
603              
604 71         103 my @subfields = ();
605              
606 71 100       136 if ( $ms->has_subfields ) { # now we dealing with subfields
607 29         44 for my $sf (@sf_spec) {
608             # set invert level
609 46 100 66     117 if ( $invert && !$sf->has_subspecs) {
610 12 100 66     50 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         57 my @subfield = ();
623 45 100       108 my $code = ( $invert_level == 3 ) ? $codes : $sf->code;
624 45         326 $code = qr/$code/;
625 45         77 for ( my $i = 3 ; $i < @{$field} ; $i += 2 ) {
  187         294  
626 142 100       402 if ( $field->[$i] =~ /$code/ ) {
627 64         130 push @subfield, $field->[ $i + 1 ];
628             }
629             }
630              
631 45 100       74 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     10 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       76 next unless (@subfield);
644              
645             # filter by index
646 30 50       498 if ( defined $sf->index_start ) {
647 30         501 my $sf_range =
648             _get_index_range( $sf->index_start, $sf->index_end, $#subfield );
649              
650 30 100       1147 if ( $invert_level == 2 ) { # inverted
651             @subfield = map {
652 1 100       4 Catmandu::Util::array_includes( $sf_range, $_ )
  3         75  
653             ? ()
654             : $subfield[$_];
655             } 0 .. $#subfield;
656             }
657             else { # without invert
658             @subfield =
659             map {
660 38 100       102 defined $subfield[$_]
661             ? $subfield[$_]
662             : ();
663 29         32 } @{$sf_range};
  29         50  
664             }
665 30 100       115 next unless (@subfield);
666             }
667              
668             # return $value_set ASAP
669 28 50       53 return $value_set if $value_set;
670              
671             # filter subfield by subspec
672 28 50       60 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         46 $char_start = $chst->($sf);
679 28 100       45 if ( defined $char_start ) {
680 7 100       14 if ( $invert_level == 1 ) { # inverted
681             @subfield =
682             map {
683 4         7 $invert_chars->( $_, $char_start, $sf->char_length );
  4         51  
684             } @subfield;
685             }
686             else {
687             @subfield =
688             map {
689 3         62 substr $_, $char_start, $sf->char_length;
  3         51  
690             } @subfield;
691             }
692             }
693 28 50       72 next unless @subfield;
694 28         84 push @subfields, @subfield;
695             } # end of subfield iteration
696 29 100       66 $to_referred->(@subfields) if @subfields;
697             } # end of subfield handling
698             else { # no particular subfields requested
699 42         59 my @contents = ();
700 42         59 for ( my $i = 4 ; $i < @{$field} ; $i += 2 ) {
  89         150  
701             # get substring
702 47         119 $char_start = $chst->($field_spec);
703 47 100       217 my $content = ( defined $char_start )
704             ? substr $field->[$i], $char_start, $field_spec->char_length
705             : $field->[$i];
706 47         144 push @contents, $content;
707             }
708 42 50       68 next unless (@contents);
709 42         71 $to_referred->(@contents);
710             } # end of field handling
711             } # end of field iteration
712 44 100       259 return unless ($referred);
713              
714 42 100       85 if($append) {
    100          
715 4 100       32 return [$referred] if $split;
716 3         69 return $referred;
717             } elsif($split) {
718 15         247 return [$referred];
719             }
720              
721 23         24 return join $join_char, @{$referred};
  23         518  
722             }
723              
724             sub _it_subspecs {
725 2     2   6 my ( $self, $data, $tag, $subspecs, $tag_index, $code_index ) = @_;
726             my $set_index = sub {
727 7     7   14 my ( $subspec ) = @_;
728 7         11 foreach my $side ( ('left', 'right') ) {
729 14 100       3701 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       143 next unless ( $tag eq $subspec->$side->field->tag );
732 7         256 $subspec->$side->field->set_index_start_end( $tag_index );
733             }
734 2         10 };
735              
736 2         6 my $valid = 1;
737 2         4 foreach my $subspec ( @{$subspecs} ) {
  2         5  
738 4 100       12 if( ref $subspec eq 'ARRAY' ) { # chained subSpecs (OR)
739 1         2 foreach my $or_subspec ( @{$subspec} ) {
  1         3  
740 4         7 $set_index->( $or_subspec );
741 4         35 $valid = $self->_validate_subspec( $or_subspec, $data );
742             # at least one of them is true (OR)
743 4 100       12 last if $valid;
744             }
745             }
746             else { # repeated SubSpecs (AND)
747 3         8 $set_index->( $subspec );
748 3         33 $valid = $self->_validate_subspec( $subspec, $data );
749             # all of them have to be true (AND)
750 3 100       9 last unless $valid;
751             }
752             }
753 2         12 return $valid;
754             }
755              
756             sub _validate_subspec {
757 7     7   15 my ( $self, $subspec, $data ) = @_;
758 7         11 my ($left_subterm, $right_subterm);
759              
760 7 50 33     37 if('!' ne $subspec->operator && '?' ne $subspec->operator) {
761 7 50       128 if ( ref $subspec->left ne 'MARC::Spec::Comparisonstring' ) {
762 7         139 $left_subterm = $self->marc_spec(
763             $data,
764             $subspec->left,
765             { '-split' => 1 }
766             ); # split should result in an array ref
767 7 50       22 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       126 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         46 push @{$right_subterm}, $subspec->right->comparable;
  7         103  
786             }
787              
788 7 50       58 if($subspec->operator eq '?') {
789 0 0       0 return (@{$right_subterm}) ? 1 : 0;
  0         0  
790             }
791              
792 7 50       14 if($subspec->operator eq '!') {
793 0 0       0 return (@{$right_subterm}) ? 0 : 1;
  0         0  
794             }
795              
796 7 50       16 if($subspec->operator eq '=') {
797 7         9 foreach my $v ( @{$left_subterm->[0]} ) {
  7         14  
798 7 100   7   23 return 1 if List::Util::any {$v eq $_} @{$right_subterm};
  7         32  
  7         25  
799             }
800             }
801              
802 4 50       12 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       11 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       11 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         10 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 144 my ($self,$data) = @_;
856              
857 11         17 my $xml;
858 11         242 my $exporter = Catmandu::Exporter::MARC::XML->new(file => \$xml , xml_declaration => 0 , collection => 0);
859 11         265 $exporter->add($data);
860 11         840 $exporter->commit;
861              
862 11         96 $xml;
863             }
864              
865             sub marc_record_to_json {
866 1     1 0 57 my ($self,$data,%opts) = @_;
867              
868 1 50       5 if (my $marc = delete $data->{'record'}) {
869 1         4 for my $field (@$marc) {
870 6         21 my ($tag, $ind1, $ind2, @subfields) = @$field;
871              
872 6 100 66     24 if ($tag eq 'LDR') {
    100          
873 1         2 shift @subfields;
874 1         5 $data->{leader} = join "", @subfields;
875             }
876             elsif ($tag eq 'FMT' || substr($tag, 0, 2) eq '00') {
877 2         4 shift @subfields;
878 2   100     3 push @{$data->{fields} ||= []} , { $tag => join "" , @subfields };
  2         13  
879             }
880             else {
881 3         4 my @sf;
882 3 50 33     11 my $start = !defined($subfields[0]) || $subfields[0] eq '_' ? 2 : 0;
883 3         8 for (my $i = $start; $i < @subfields; $i += 2) {
884 5         15 push @sf, { $subfields[$i] => $subfields[$i+1] };
885             }
886 3   50     4 push @{$data->{fields} ||= []} , { $tag => {
  3         23  
887             subfields => \@sf,
888             ind1 => $ind1,
889             ind2 => $ind2 } };
890             }
891             }
892             }
893              
894 1         23 $data;
895             }
896              
897             sub marc_json_to_record {
898 1     1 0 15 my ($self,$data,%opts) = @_;
899              
900 1         3 my $record = [];
901              
902 1 50       7 if (Catmandu::Util::is_string($data->{leader})) {
903 1         5 push @$record , [ 'LDR', undef, undef, '_', $data->{leader} ],
904             }
905              
906 1 50       5 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         12 my ($tag) = keys %$field;
911 5         7 my $val = $field->{$tag};
912              
913 5 100 66     23 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         5 my $ind2 = $val->{ind2};
919 3 50       8 next unless Catmandu::Util::is_array_ref($val->{subfields});
920              
921 3         7 my $sfs = [ '_' , ''];
922 3         3 for my $sf (@{ $val->{subfields} }) {
  3         5  
923 5 50       13 next unless Catmandu::Util::is_hash_ref($sf);
924              
925 5         9 my ($code) = keys %$sf;
926 5         6 my $sval = $sf->{$code};
927              
928 5         12 push @$sfs , [ $code , $sval];
929             }
930              
931 3         10 push @$record , [ $tag , $ind1 , $ind2 , @$sfs];
932             }
933             }
934             }
935              
936 1 50       4 if (@$record > 0) {
937 1         5 delete $data->{fields};
938 1         3 delete $data->{leader};
939 1         2 $data->{'record'} = $record;
940             }
941              
942 1         18 $data;
943             }
944              
945             sub marc_decode_dollar_subfields {
946 1     1 1 13 my ($self,$data,%opts) = @_;
947 1         3 my $old_record = $data->{'record'};
948 1         3 my $new_record = [];
949              
950 1         3 for my $field (@$old_record) {
951 14         49 my ($tag,$ind1,$ind2,@subfields) = @$field;
952              
953 14         29 my $fixed_field = [$tag,$ind1,$ind2];
954              
955 14         29 for (my $i = 0 ; $i < @subfields ; $i += 2) {
956 30         41 my $code = $subfields[$i];
957 30         42 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         76 my @chunks = split( /\$([a-z])/, $value );
962              
963 30         49 my $real_value = shift @chunks;
964              
965 30         66 push @$fixed_field , ( $code, $real_value);
966              
967 30         79 while (@chunks) {
968 2         23 push @$fixed_field , ( splice @chunks, 0, 2 );
969             }
970             }
971              
972 14         31 push @$new_record , $fixed_field;
973             }
974              
975 1         3 $data->{'record'} = $new_record;
976              
977 1         31 $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