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   50683 use Catmandu::Sane;
  28         669305  
  28         152  
4 28     28   5777 use Catmandu::Util;
  28         59  
  28         1039  
5 28     28   8206 use Catmandu::Exporter::MARC::XML;
  28         125  
  28         981  
6 28     28   9591 use MARC::Spec::Parser;
  28         842480  
  28         939  
7 28     28   247 use List::Util;
  28         107  
  28         1533  
8 28     28   10854 use Memoize;
  28         52057  
  28         1324  
9 28     28   180 use Carp;
  28         59  
  28         1154  
10 28     28   154 use Moo;
  28         88  
  28         164  
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.20';
19              
20             sub marc_map {
21 626     626 1 44824 my $self = $_[0];
22              
23             # $_[2] : marc_path
24 626 100       1832 my $context = ref($_[2]) ?
25             $_[2] :
26             $self->compile_marc_path($_[2], subfield_wildcard => 1);
27              
28 626 50       1551 confess "invalid marc path" unless $context;
29              
30             # $_[1] : data record
31 626         1093 my $record = $_[1]->{'record'};
32              
33 626 0 33     2119 return wantarray ? () : undef unless (defined $record && ref($record) eq 'ARRAY');
    50          
34              
35             # $_[3] : opts
36 626   50     1492 my $split = $_[3]->{'-split'} // 0;
37 626   50     1226 my $join_char = $_[3]->{'-join'} // '';
38 626   50     1148 my $pluck = $_[3]->{'-pluck'} // 0;
39 626   100     1595 my $value_set = $_[3]->{'-value'} // undef;
40 626   50     1155 my $nested_arrays = $_[3]->{'-nested_arrays'} // 0;
41 626   100     1107 my $append = $_[3]->{'-force_array'} // undef;
42              
43 626         779 my $vals;
44              
45 626         1042 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     20104 );
      0        
      33        
      66        
      0        
      33        
      33        
      66        
      33        
55              
56 400         606 my $v;
57              
58 400 100       667 if ($value_set) {
59 48         98 for (my $i = 3; $i < @{$field}; $i += 2) {
  110         274  
60 96         154 my $subfield_regex = $context->{subfield_regex};
61 96 100       492 if ($field->[$i] =~ $subfield_regex) {
62 34         126 $v = $value_set;
63 34         89 last;
64             }
65             }
66             }
67             else {
68 352         15914 $v = [];
69              
70 352 100       617 if ($pluck) {
71             # Treat the subfield as a hash index
72 16         59 my $_h = {};
73 16         58 for (my $i = $context->{start}; $i < @{$field}; $i += 2) {
  49         145  
74 33         68 push @{ $_h->{ $field->[$i] } } , $field->[$i + 1];
  33         175  
75             }
76 16         57 my $subfield = $context->{subfield};
77 16         138 $subfield =~ s{[^a-zA-Z0-9]}{}g;
78 16         90 for my $c (split('',$subfield)) {
79 33   100     113 my $val = $_h->{$c} // [undef];
80 33         65 push @$v , @{ $val } ;
  33         129  
81             }
82             }
83             else {
84 336         590 for (my $i = $context->{start}; $i < @{$field}; $i += 2) {
  896         1698  
85 560         768 my $subfield_regex = $context->{subfield_regex};
86 560 100       2164 if ($field->[$i] =~ $subfield_regex) {
87 417         1044 push(@$v, $field->[$i + 1]);
88             }
89             }
90             }
91              
92 352 100       706 if (@$v) {
93 298 100       615 if (!$split) {
94 227         442 my @defined_values = grep {defined($_)} @$v;
  258         854  
95 227         647 $v = join $join_char, @defined_values;
96             }
97              
98 298 100       821 if (defined(my $off = $context->{from})) {
99 51 100       167 if (ref $v eq 'ARRAY') {
100 11         37 my @defined_values = grep {defined($_)} @$v;
  11         49  
101 11         38 $v = join $join_char, @defined_values;
102             }
103 51         106 my $len = $context->{len};
104 51 100       144 if (length(${v}) > $off) {
105 41         125 $v = substr($v, $off, $len);
106             } else {
107 10         20 $v = undef;
108             }
109             }
110             }
111             else {
112 54         99 $v = undef;
113             }
114             }
115              
116 400 100       825 if (defined $v) {
117 322 100       582 if ($split) {
118 71 100 66     243 $v = [ $v ] unless (defined($v) && ref($v) eq 'ARRAY');
119 71 100 66     186 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       53 if ($nested_arrays == 1) {
124 40         65 push @$vals , $v;
125             }
126             else {
127 1         3 push @$vals , @$v;
128             }
129             }
130             else {
131 30 100       65 if ($nested_arrays == 1) {
132 9         20 $vals = [$v];
133             }
134             else {
135 21         78 $vals = [ @$v ];
136             }
137             }
138             }
139             else {
140 251         652 push @$vals , $v;
141             }
142             }
143             }
144              
145 626 100 66     1689 if ($split && defined $vals) {
    100          
    100          
146 30         78 $vals = [ $vals ];
147             }
148             elsif ($append) {
149             # we got a $append
150             }
151             elsif (defined $vals) {
152 134         413 $vals = join $join_char , @$vals;
153             }
154             else {
155             # no result
156             }
157              
158 626         11259 $vals;
159             }
160              
161             sub marc_add {
162 10     10 1 154 my ($self,$data,$marc_path,@subfields) = @_;
163              
164 10         33 my %subfields = @subfields;
165 10   100     35 my $marc = $data->{'record'} // [];
166              
167 10 50       42 if ($marc_path =~ /^\w{3}$/) {
168 10         21 my @field = ();
169 10         17 push @field , $marc_path;
170 10   100     39 push @field , $subfields{ind1} // ' ';
171 10   100     30 push @field , $subfields{ind2} // ' ';
172              
173              
174 10         61 for (my $i = 0 ; $i < @subfields ; $i += 2) {
175 20         30 my $code = $subfields[$i];
176 20 100       44 next unless length $code == 1;
177 14         24 my $value = $subfields[$i+1];
178              
179 14 100       43 if ($value =~ /^\$\.(\S+)$/) {
180 6         14 my $path = $1;
181 6         26 $value = Catmandu::Util::data_at($path,$data);
182             }
183              
184 14 100 33     432 if (Catmandu::Util::is_array_ref $value) {
    50          
    50          
185 3         9 for (@$value) {
186 9         15 push @field , $code;
187 9         20 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         17 push @field , $code;
198 11         32 push @field , $value;
199             }
200             }
201              
202 10 50       31 push @{ $marc } , \@field if @field > 3;
  10         24  
203             }
204              
205 10         50 $data->{'record'} = $marc;
206              
207 10         97 $data;
208             }
209              
210             sub marc_append {
211 1     1 1 14 my ($self,$data,$marc_path,$value) = @_;
212 1         3 my $record = $data->{'record'};
213              
214 1 50       4 return $data unless defined $record;
215              
216 1 50       3 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       9 confess "invalid marc path" unless $context;
235              
236 1         4 for my $field (@$record) {
237 19         47 my ($tag, $ind1, $ind2, @subfields) = @$field;
238              
239 19 50       24 if ($context->{is_regex_field}) {
240 0 0       0 next unless $tag =~ $context->{field_regex};
241             }
242             else {
243 19 100       41 next unless $tag eq $context->{field};
244             }
245              
246 1 50       3 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       3 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       3 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         4 $field->[-1] .= $value;
266             }
267             }
268              
269 1         17 $data;
270             }
271              
272             sub marc_replace_all {
273 3     3 1 40 my ($self,$data,$marc_path,$regex,$value) = @_;
274 3         7 my $record = $data->{'record'};
275              
276 3 50       8 return $data unless defined $record;
277              
278 3 50       12 if ($value =~ /^\$\.(\S+)/) {
279 0         0 my $path = $1;
280 0         0 $value = Catmandu::Util::data_at($path,$data);
281             }
282              
283 3 50       16 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         47 my $context = $self->compile_marc_path($marc_path, subfield_wildcard => 1);
295              
296 3 50       70 confess "invalid marc path" unless $context;
297              
298 3         9 for my $field (@$record) {
299 57         119 my ($tag, $ind1, $ind2, @subfields) = @$field;
300              
301 57 50       77 if ($context->{is_regex_field}) {
302 0 0       0 next unless $tag =~ $context->{field_regex};
303             }
304             else {
305 57 100       114 next unless $tag eq $context->{field};
306             }
307              
308 5 50       10 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       13 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         15 for (my $i = 0; $i < @subfields; $i += 2) {
320 6 100       43 if ($subfields[$i] =~ $context->{subfield}) {
321             # Trick to double eval the right hand side
322 5         47 $field->[$i + 4] =~ s{$regex}{"\"$value\""}eeg;
  5         232  
323             }
324             }
325             }
326              
327 3         49 $data;
328             }
329              
330             sub marc_set {
331 8     8 1 102 my ($self,$data,$marc_path,$value,%opts) = @_;
332 8         16 my $record = $data->{'record'};
333              
334 8 50       26 return $data unless defined $record;
335              
336 8 100       31 if ($value =~ /^\$\.(\S+)/) {
337 3         9 my $path = $1;
338 3         13 $value = Catmandu::Util::data_at($path,$data);
339             }
340              
341 8 50       237 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         141 my $context = $self->compile_marc_path($marc_path, subfield_default => 1);
353              
354 8 50       137 confess "invalid marc path" unless $context;
355              
356 8         23 for my $field (@$record) {
357 168         369 my ($tag, $ind1, $ind2, @subfields) = @$field;
358              
359 168 50       251 if ($context->{is_regex_field}) {
360 0 0       0 next unless $tag =~ $context->{field_regex};
361             }
362             else {
363 168 100       353 next unless $tag eq $context->{field};
364             }
365              
366 8 100       21 if (defined $context->{ind1}) {
367 2 50 33     16 if (!defined $ind1 || $ind1 ne $context->{ind1}) {
368 0         0 next;
369             }
370             }
371 8 50       21 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         26 for (my $i = 0; $i < @subfields; $i += 2) {
379 13 100       91 if ($subfields[$i] =~ $context->{subfield}) {
380 5 100       18 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         14 $found = 1;
387             }
388             }
389              
390 8 100       26 if ($found == 0) {
391 3         11 push(@$field,$context->{subfield},$value);
392             }
393             }
394              
395 8         100 $data;
396             }
397              
398             sub marc_remove {
399 7     7 1 84 my ($self,$data, $marc_path,%opts) = @_;
400 7         15 my $record = $data->{'record'};
401              
402 7         12 my $new_record;
403              
404 7         108 my $context = $self->compile_marc_path($marc_path);
405              
406 7 50       64 confess "invalid marc path" unless $context;
407              
408 7         16 for my $field (@$record) {
409 120         138 my $field_size = int(@$field);
410              
411 120 100 66     380 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         14 my $ind_match = undef;
418              
419 7 100 66     53 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     8 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         11 $ind_match = 1;
431             }
432              
433 7 100 100     45 if ($ind_match && ! defined $context->{subfield_regex}) {
434 4         12 next;
435             }
436              
437 3 50       6 if (defined $context->{subfield_regex}) {
438 3         6 my $subfield_regex = $context->{subfield_regex};
439 3         5 my $new_subf = [];
440 3         7 for (my $i = $context->{start}; $i < $field_size; $i += 2) {
441 6 100       25 unless ($field->[$i] =~ $subfield_regex) {
442 2         4 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         165 push @$new_record , $field;
452             }
453              
454 7         16 $data->{'record'} = $new_record;
455              
456 7         106 return $data;
457             }
458              
459             sub marc_spec {
460 47     47 1 5639 my $self = $_[0];
461              
462             # $_[1] : data record
463 47         62 my $data = $_[1];
464 47         66 my $record = $data->{'record'};
465              
466             # $_[2] : spec
467 47         52 my ($ms, $spec);
468 47 50       99 if( ref $_[2] ) {
469 47         51 $ms = $_[2];
470 47         100 $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         4163 my $EMPTY = q{};
477             # $_[3] : opts
478 47   50     121 my $split = $_[3]->{'-split'} // 0;
479 47   66     103 my $join_char = $_[3]->{'-join'} // $EMPTY;
480 47   100     91 my $pluck = $_[3]->{'-pluck'} // 0;
481 47   100     130 my $value_set = $_[3]->{'-value'} // undef;
482 47   100     95 my $invert = $_[3]->{'-invert'} // 0;
483 47   100     84 my $nested_arrays = $_[3]->{'-nested_arrays'} // 0;
484 47   100     81 my $append = $_[3]->{'-force_array'} // 0;
485              
486 47 50       75 if($nested_arrays) {
487 0         0 $split = 1
488             }
489              
490             # filter by tag
491 47         63 my @fields = ();
492 47         610 my $field_spec = $ms->field;
493 47         235 my $tag_spec = $field_spec->tag;
494              
495 47         62 @fields = grep { $_->[0] =~ /$tag_spec/ } @{ $record };
  956         1689  
  47         78  
496 47 100       128 return unless @fields;
497              
498             # filter by indicator
499 45         61 my ( $indicator1, $indicator2 );
500 45 100       93 if ( $field_spec->has_indicator1 ) {
501 2         6 $indicator1 = $field_spec->indicator1;
502 2         37 $indicator1 = qr/$indicator1/;
503             }
504 45 100       82 if ( $field_spec->has_indicator2 ) {
505 3         11 $indicator2 = $field_spec->indicator2;
506 3         16 $indicator2 = qr/$indicator2/;
507             }
508              
509             # calculate char start
510             my $chst = sub {
511 75     75   101 my ($sp) = @_;
512 75         79 my $char_start;
513 75 100       143 if ( $sp->has_char_start ) {
514 17 100       272 $char_start = ( '#' eq $sp->char_start )
515             ? $sp->char_length * -1
516             : $sp->char_start;
517             }
518 75         431 return $char_start;
519 45         147 };
520              
521             # vars we need only for subfields
522 45         73 my (@sf_spec, $invert_level, $codes, $invert_chars);
523 45 100       80 if ( $ms->has_subfields ) {
524             # set the order of subfields
525 18         21 @sf_spec = map { $_ } @{ $ms->subfields };
  29         44  
  18         30  
526 18 100       42 unless ( $pluck ) {
527 17         46 @sf_spec = sort { $a->code cmp $b->code } @sf_spec;
  11         38  
528             }
529              
530             # set invert level default
531 18         21 $invert_level = 4;
532 18 100       32 if ( $invert ) {
533 4         5 $codes = '[^';
534 4         7 $codes .= join $EMPTY, map { $_->code } @sf_spec;
  10         20  
535 4         8 $codes .= ']';
536             }
537              
538             $invert_chars = sub {
539 4     4   26 my ( $str, $start, $length ) = @_;
540 4         9 for ( substr $str, $start, $length ) {
541 4         10 $_ = $EMPTY;
542             }
543 4         13 return $str;
544 18         45 };
545             }
546             else {
547             # return $value_set ASAP
548 27 100       65 return $value_set if defined $value_set;
549             }
550              
551             # vars we need for fields and subfields
552 44         56 my ($referred, $char_start, $prev_tag, $index_range);
553 44         56 my $current_tag = $EMPTY;
554 44         48 my $tag_index = 0;
555 44         641 my $index_start = $field_spec->index_start;
556 44         683 my $index_end = $field_spec->index_end;
557              
558             my $to_referred = sub {
559 64     64   110 my ( @values ) = @_;
560 64 50       126 if($nested_arrays) {
    100          
561 0         0 push @{$referred}, \@values;
  0         0  
562             } elsif($split) {
563 29         31 push @{$referred}, @values;
  29         71  
564             } else {
565 35         36 push @{$referred}, join $join_char, @values;
  35         112  
566             }
567 44         301 };
568              
569 44 50       584 if( defined $field_spec->index_start ) {
570 44         689 $index_range =
571             _get_index_range( $field_spec->index_start, $field_spec->index_end, $#fields );
572             }
573              
574             # iterate over fields
575 44         1660 for my $field (@fields) {
576 114         398 $prev_tag = $current_tag;
577 114         136 $current_tag = $field->[0];
578              
579 114 100 66     245 $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       162 if( defined $indicator1 ) {
585 21 100 100     72 next unless ( defined $field->[1] && $field->[1] =~ $indicator1);
586             }
587              
588 95 100       134 if( defined $indicator2 ) {
589             #next unless $field->[2] =~ $indicator2;
590 22 100 100     74 next unless ( defined $field->[2] && $field->[2] =~ $indicator2);
591             }
592              
593             # filter by index
594 81 50       115 if ( defined $index_range ) {
595 81 100       167 next unless ( Catmandu::Util::array_includes( $index_range, $tag_index ) );
596             }
597              
598             # filter field by subspec
599 72 100       3582 if( $field_spec->has_subspecs) {
600 2         10 my $valid = $self->_it_subspecs( $data, $field_spec->tag, $field_spec->subspecs, $tag_index );
601 2 100       7 next unless $valid;
602             }
603              
604 71         90 my @subfields = ();
605              
606 71 100       127 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     95 if ( $invert && !$sf->has_subspecs) {
610 12 100 66     44 if ( -1 == $sf->index_length && !$sf->has_char_start ) {
    100          
611 6 100       10 next if ( $invert_level == 3 ); # skip subfield spec it's already covered
612 5         7 $invert_level = 3;
613             }
614             elsif ( $sf->has_char_start ) {
615 4         8 $invert_level = 1;
616             }
617             else {
618 2         4 $invert_level = 2;
619             }
620             }
621              
622 45         50 my @subfield = ();
623 45 100       90 my $code = ( $invert_level == 3 ) ? $codes : $sf->code;
624 45         272 $code = qr/$code/;
625 45         77 for ( my $i = 3 ; $i < @{$field} ; $i += 2 ) {
  187         301  
626 142 100       417 if ( $field->[$i] =~ /$code/ ) {
627 64         113 push @subfield, $field->[ $i + 1 ];
628             }
629             }
630              
631 45 100       70 if ( $invert_level == 3 ) { # no index or charpos
632 5 100       9 if (@subfield) {
633 1         2 push @subfields, @subfield;
634             }
635              
636 5 50 33     12 if ( $referred && $value_set ) { # return $value_set ASAP
637 0         0 return $value_set;
638             }
639              
640 5         13 next;
641             }
642              
643 40 100       72 next unless (@subfield);
644              
645             # filter by index
646 30 50       518 if ( defined $sf->index_start ) {
647 30         479 my $sf_range =
648             _get_index_range( $sf->index_start, $sf->index_end, $#subfield );
649              
650 30 100       1100 if ( $invert_level == 2 ) { # inverted
651             @subfield = map {
652 1 100       3 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       100 defined $subfield[$_]
661             ? $subfield[$_]
662             : ();
663 29         31 } @{$sf_range};
  29         42  
664             }
665 30 100       96 next unless (@subfield);
666             }
667              
668             # return $value_set ASAP
669 28 50       44 return $value_set if $value_set;
670              
671             # filter subfield by subspec
672 28 50       54 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         49 $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         62 substr $_, $char_start, $sf->char_length;
  3         49  
690             } @subfield;
691             }
692             }
693 28 50       69 next unless @subfield;
694 28         88 push @subfields, @subfield;
695             } # end of subfield iteration
696 29 100       68 $to_referred->(@subfields) if @subfields;
697             } # end of subfield handling
698             else { # no particular subfields requested
699 42         53 my @contents = ();
700 42         55 for ( my $i = 4 ; $i < @{$field} ; $i += 2 ) {
  89         145  
701             # get substring
702 47         72 $char_start = $chst->($field_spec);
703 47 100       212 my $content = ( defined $char_start )
704             ? substr $field->[$i], $char_start, $field_spec->char_length
705             : $field->[$i];
706 47         140 push @contents, $content;
707             }
708 42 50       66 next unless (@contents);
709 42         66 $to_referred->(@contents);
710             } # end of field handling
711             } # end of field iteration
712 44 100       241 return unless ($referred);
713              
714 42 100       79 if($append) {
    100          
715 4 100       27 return [$referred] if $split;
716 3         66 return $referred;
717             } elsif($split) {
718 15         229 return [$referred];
719             }
720              
721 23         25 return join $join_char, @{$referred};
  23         544  
722             }
723              
724             sub _it_subspecs {
725 2     2   5 my ( $self, $data, $tag, $subspecs, $tag_index, $code_index ) = @_;
726             my $set_index = sub {
727 7     7   10 my ( $subspec ) = @_;
728 7         13 foreach my $side ( ('left', 'right') ) {
729 14 100       3066 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       126 next unless ( $tag eq $subspec->$side->field->tag );
732 7         223 $subspec->$side->field->set_index_start_end( $tag_index );
733             }
734 2         8 };
735              
736 2         4 my $valid = 1;
737 2         3 foreach my $subspec ( @{$subspecs} ) {
  2         7  
738 4 100       11 if( ref $subspec eq 'ARRAY' ) { # chained subSpecs (OR)
739 1         2 foreach my $or_subspec ( @{$subspec} ) {
  1         3  
740 4         9 $set_index->( $or_subspec );
741 4         33 $valid = $self->_validate_subspec( $or_subspec, $data );
742             # at least one of them is true (OR)
743 4 100       10 last if $valid;
744             }
745             }
746             else { # repeated SubSpecs (AND)
747 3         7 $set_index->( $subspec );
748 3         25 $valid = $self->_validate_subspec( $subspec, $data );
749             # all of them have to be true (AND)
750 3 100       7 last unless $valid;
751             }
752             }
753 2         10 return $valid;
754             }
755              
756             sub _validate_subspec {
757 7     7   13 my ( $self, $subspec, $data ) = @_;
758 7         10 my ($left_subterm, $right_subterm);
759              
760 7 50 33     33 if('!' ne $subspec->operator && '?' ne $subspec->operator) {
761 7 50       98 if ( ref $subspec->left ne 'MARC::Spec::Comparisonstring' ) {
762 7         115 $left_subterm = $self->marc_spec(
763             $data,
764             $subspec->left,
765             { '-split' => 1 }
766             ); # split should result in an array ref
767 7 50       21 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       109 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         40 push @{$right_subterm}, $subspec->right->comparable;
  7         96  
786             }
787              
788 7 50       48 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         8 foreach my $v ( @{$left_subterm->[0]} ) {
  7         12  
798 7 100   7   22 return 1 if List::Util::any {$v eq $_} @{$right_subterm};
  7         29  
  7         19  
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       10 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       8 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         9 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 221 my ($self,$data) = @_;
856              
857 11         23 my $xml;
858 11         328 my $exporter = Catmandu::Exporter::MARC::XML->new(file => \$xml , xml_declaration => 0 , collection => 0);
859 11         357 $exporter->add($data);
860 11         1292 $exporter->commit;
861              
862 11         144 $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         18 my ($tag, $ind1, $ind2, @subfields) = @$field;
871              
872 6 100 66     25 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         3 shift @subfields;
878 2   100     4 push @{$data->{fields} ||= []} , { $tag => join "" , @subfields };
  2         13  
879             }
880             else {
881 3         5 my @sf;
882 3 50 33     11 my $start = !defined($subfields[0]) || $subfields[0] eq '_' ? 2 : 0;
883 3         7 for (my $i = $start; $i < @subfields; $i += 2) {
884 5         16 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         21 $data;
895             }
896              
897             sub marc_json_to_record {
898 1     1 0 12 my ($self,$data,%opts) = @_;
899              
900 1         3 my $record = [];
901              
902 1 50       6 if (Catmandu::Util::is_string($data->{leader})) {
903 1         6 push @$record , [ 'LDR', undef, undef, '_', $data->{leader} ],
904             }
905              
906 1 50       4 if (Catmandu::Util::is_array_ref($data->{fields})) {
907 1         3 for my $field (@{$data->{fields}}) {
  1         3  
908 5 50       13 next unless Catmandu::Util::is_hash_ref($field);
909              
910 5         8 my ($tag) = keys %$field;
911 5         8 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         7 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         6 my $sfs = [ '_' , ''];
922 3         4 for my $sf (@{ $val->{subfields} }) {
  3         6  
923 5 50       11 next unless Catmandu::Util::is_hash_ref($sf);
924              
925 5         7 my ($code) = keys %$sf;
926 5         9 my $sval = $sf->{$code};
927              
928 5         9 push @$sfs , [ $code , $sval];
929             }
930              
931 3         12 push @$record , [ $tag , $ind1 , $ind2 , @$sfs];
932             }
933             }
934             }
935              
936 1 50       3 if (@$record > 0) {
937 1         7 delete $data->{fields};
938 1         2 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         2 my $old_record = $data->{'record'};
948 1         2 my $new_record = [];
949              
950 1         3 for my $field (@$old_record) {
951 14         61 my ($tag,$ind1,$ind2,@subfields) = @$field;
952              
953 14         26 my $fixed_field = [$tag,$ind1,$ind2];
954              
955 14         29 for (my $i = 0 ; $i < @subfields ; $i += 2) {
956 30         35 my $code = $subfields[$i];
957 30         35 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         64 my @chunks = split( /\$([a-z])/, $value );
962              
963 30         36 my $real_value = shift @chunks;
964              
965 30         49 push @$fixed_field , ( $code, $real_value);
966              
967 30         65 while (@chunks) {
968 2         7 push @$fixed_field , ( splice @chunks, 0, 2 );
969             }
970             }
971              
972 14         26 push @$new_record , $fixed_field;
973             }
974              
975 1         2 $data->{'record'} = $new_record;
976              
977 1         25 $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