File Coverage

blib/lib/Catmandu/Breaker.pm
Criterion Covered Total %
statement 23 72 31.9
branch 1 18 5.5
condition 3 28 10.7
subroutine 7 12 58.3
pod 0 5 0.0
total 34 135 25.1


line stmt bran cond sub pod time code
1             package Catmandu::Breaker;
2              
3             our $VERSION = '0.141';
4              
5 8     8   300881 use Moo;
  8         16  
  8         46  
6 8     8   3621 use Carp;
  8         18  
  8         544  
7 8     8   66 use Catmandu;
  8         18  
  8         46  
8 8     8   1816 use Catmandu::Util;
  8         18  
  8         321  
9 8     8   47 use Catmandu;
  8         15  
  8         31  
10 8     8   1592 use Data::Dumper;
  8         18  
  8         10697  
11              
12             has verbose => ( is => 'ro', default => sub {0} );
13             has maxscan => ( is => 'ro', default => sub {-1} );
14             has tags => ( is => 'ro' );
15             has _counter => ( is => 'ro', default => sub {0} );
16              
17             sub counter {
18 0     0 0 0 my ($self) = @_;
19 0         0 $self->{_counter} = $self->{_counter} + 1;
20 0         0 $self->{_counter};
21             }
22              
23             sub to_breaker {
24 3277     3277 0 26875 my ( $self, $identifier, $tag, $value ) = @_;
25              
26 3277   50     6334 $value //= '';
27              
28 3277 50 33     13189 croak "usage: to_breaker(idenifier,tag,value)"
      33        
29             unless defined($identifier) && defined($tag) && defined($value);
30              
31 3277         5563 $value =~ s{\n}{\\n}mg;
32              
33 3277         13384 sprintf "%s\t%s\t%s\n", $identifier, $tag, $value;
34             }
35              
36             sub from_breaker {
37 0     0 0   my ( $self, $line ) = @_;
38              
39 0           my ( $id, $tag, $value ) = split( /\s+/, $line, 3 );
40              
41 0 0 0       croak "error line not in breaker format : $line"
      0        
42             unless defined($id) && defined($tag) && defined($value);
43              
44 0           return +{ identifier => $id, tag => $tag, value => $value };
45             }
46              
47             sub parse {
48 0     0 0   my ( $self, $file, $format ) = @_;
49              
50 0   0       my $tags = $self->tags // $self->scan_tags($file);
51 0   0       $format = $format // 'Table';
52              
53 0           my $importer = Catmandu->importer( 'Text', file => $file );
54 0           my $exporter = Catmandu->exporter( 'Stat', fields => $tags, as => $format );
55              
56 0           my $rec = {};
57 0           my $prev_id = undef;
58              
59 0           my $it = $importer;
60              
61 0 0         if ( $self->verbose ) {
62 0           $it = $importer->benchmark();
63             }
64              
65             $it->each(
66             sub {
67 0     0     my $line = $_[0]->{text};
68              
69 0           my $brk = $self->from_breaker($line);
70 0           my $id = $brk->{identifier};
71 0           my $tag = $brk->{tag};
72 0           my $value = $brk->{value};
73              
74 0 0 0       if ( defined($prev_id) && $prev_id ne $id ) {
75 0           $exporter->add($rec);
76 0           $rec = {};
77             }
78              
79 0           $rec->{_id} = $id;
80              
81 0 0         if ( exists $rec->{$tag} ) {
82             my $prev
83             = ref( $rec->{$tag} ) eq 'ARRAY'
84             ? $rec->{$tag}
85 0 0         : [ $rec->{$tag} ];
86 0           $rec->{$tag} = [ @$prev, $value ];
87             }
88             else {
89 0           $rec->{$tag} = $value;
90             }
91              
92 0           $prev_id = $id;
93             }
94 0           );
95 0           $exporter->add($rec);
96              
97 0           $exporter->commit;
98             }
99              
100             sub scan_tags {
101 0     0 0   my ( $self, $file ) = @_;
102              
103 0           my $tags = {};
104 0           my $io = Catmandu::Util::io($file);
105              
106 0 0         print STDERR "Scanning:\n" if $self->verbose;
107 0           my $n = 0;
108 0           while ( my $line = $io->getline ) {
109 0           $n++;
110 0           chop($line);
111              
112 0 0 0       print STDERR "..$n\n" if ( $self->verbose && $n % 1000 == 0 );
113              
114 0           my $brk = $self->from_breaker($line);
115 0           my $tag = $brk->{tag};
116 0           $tags->{$tag} = 1;
117              
118 0 0 0       last if ( $self->maxscan > 0 && $n > $self->maxscan );
119             }
120              
121 0           $io->close;
122              
123 0           return join( ",", sort keys %$tags );
124             }
125              
126             1;
127              
128             __END__
129              
130             =pod
131              
132             =head1 NAME
133              
134             Catmandu::Breaker - Package that exports data in a Breaker format
135              
136             =head1 SYNOPSIS
137              
138             # From the command line
139              
140             # Using the default breaker
141             $ catmandu convert JSON to Breaker < data.json
142              
143             # Break a OAI-PMH harvest
144             $ catmandu convert OAI --url http://biblio.ugent.be/oai to Breaker
145              
146             # Using a MARC breaker
147             $ catmandu convert MARC to Breaker --handler marc < data.mrc
148              
149             # Using an XML breaker plus create a list of unique record fields
150             $ catmandu convert XML --path book to Breaker --handler xml --fields data.fields < t/book.xml > data.breaker
151              
152             # Find the usage statistics of fields in the XML file above
153             $ catmandu breaker data.breaker
154              
155             # Use the list of unique fields in the report
156             $ catmandu breaker --fields data.fields data.breaker
157              
158             # verbose output
159             $ catmandu breaker -v data.breaker
160              
161             # The breaker commands needs to know the unique fields in the dataset to build statistics.
162             # By default it will scan the whole file for fields. This can be a very
163             # time consuming process. With --maxscan one can limit the number of lines
164             # in the breaker file that can be scanned for unique fields
165             $ catmandu breaker -v --maxscan 1000000 data.breaker
166              
167             # Alternatively the fields option can be used to specify the unique fields
168             $ catmandu breaker -v --fields 245a,022a data.breaker
169              
170             $ cat data.breaker | cut -f 2 | sort -u > data.fields
171             $ catmandu breaker -v --fields data.fields data.breaker
172              
173             # Export statistics as CSV. See L<Catmandu::Exporter::Stat> for supported formats.
174             $ catmandu breaker --as CSV data.breaker
175              
176              
177             =head1 DESCRIPTION
178              
179             Inspired by the article "Metadata Analysis at the Command-Line" by Mark Phillips in
180             L<http://journal.code4lib.org/articles/7818> this exporter breaks metadata records
181             into the Breaker format which can be analyzed further by command line tools.
182              
183             =head1 BREAKER FORMAT
184              
185             When breaking a input using 'catmandu convert {format} to Breaker' each metadata
186             fields gets transformed into a 'breaker' format:
187              
188             <record-identifier><tab><metadata-field><tab><metadata-value><tab><metadatavalue>...
189              
190             For the default JSON breaker the input format is broken down into JSON-like Paths. E.g.
191             when give this YAML input:
192              
193             ---
194             name: John
195             colors:
196             - black
197             - yellow
198             - red
199             institution:
200             name: Acme
201             years:
202             - 1949
203             - 1950
204             - 1951
205             - 1952
206              
207             the breaker command 'catmandu convert YAML to Breaker < file.yml' will generate:
208              
209             1 colors[] black
210             1 colors[] yellow
211             1 colors[] red
212             1 institution.name Acme
213             1 institution.years[] 1949
214             1 institution.years[] 1950
215             1 institution.years[] 1951
216             1 institution.years[] 1952
217             1 name John
218              
219             The first column is a counter for each record (or the content of the _id field when present).
220             The second column provides a JSON path to the data (with the array-paths translated to []).
221             The third column is the field value.
222              
223             One can use this output in combination with Unix tools like C<grep>, C<sort>, C<cut>, etc to
224             inspect the breaker output:
225              
226             $ catmandu convert YAML to Breaker < file.yml | grep 'institution.years'
227              
228             Some input formats, like MARC, the JSON-path format doesn't provide much information
229             which fields are present in the MARC because field names are part of the data. It is
230             then possible to use a special C<handler> to create a more verbose breaker
231             output.
232              
233             For instance, without a special handler:
234              
235             $ catmandu convert MARC to Breaker < t/camel.usmarc
236             fol05731351 record[][] LDR
237             fol05731351 record[][] _
238             fol05731351 record[][] 00755cam 22002414a 4500
239             fol05731351 record[][] 001
240             fol05731351 record[][] _
241             fol05731351 record[][] fol05731351
242             fol05731351 record[][] 082
243             fol05731351 record[][] 0
244             fol05731351 record[][] 0
245             fol05731351 record[][] a
246              
247             With the special L<marc handler|Catmandu::Exporter::Breaker::Parser::marc>:
248              
249             $ catmandu convert MARC to Breaker --handler marc < t/camel.usmarc
250              
251             fol05731351 LDR 00755cam 22002414a 4500
252             fol05731351 001 fol05731351
253             fol05731351 003 IMchF
254             fol05731351 005 20000613133448.0
255             fol05731351 008 000107s2000 nyua 001 0 eng
256             fol05731351 010a 00020737
257             fol05731351 020a 0471383147 (paper/cd-rom : alk. paper)
258             fol05731351 040a DLC
259             fol05731351 040c DLC
260             fol05731351 040d DLC
261              
262             For the L<Catmandu::PICA> tools a L<pica handler|Catmandu::Exporter::Breaker::Parser::pica> is available.
263              
264             For the L<Catmandu::MAB2> tools a L<mab handler|Catmandu::Exporter::Breaker::Parser::mab> is available.
265              
266             For the L<Catmandu::XML> tools an L<xml handler|Catmandu::Exporter::Breaker::Parser::xml> is available:
267              
268             $ catmandu convert XML --path book to Breaker --handler xml < t/book.xml
269              
270             =head1 BREAKER STATISTICS
271              
272             Statistical information can be calculated from a breaker output using the
273             'catmandu breaker' command:
274              
275             $ catmandu convert MARC to Breaker --handler marc < t/camel.usmarc > data.breaker
276             $ catmandu breaker data.breaker
277              
278             | name | count | zeros | zeros% | min | max | mean | median | mode | variance | stdev | uniq%| entropy |
279             |------|-------|-------|--------|-----|-----|------|--------|--------|----------|-------|------|---------|
280             | 001 | 10 | 0 | 0.0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 100 | 3.3/3.3 |
281             | 003 | 10 | 0 | 0.0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 10 | 0.0/3.3 |
282             | 005 | 10 | 0 | 0.0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 100 | 3.3/3.3 |
283             | 008 | 10 | 0 | 0.0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 100 | 3.3/3.3 |
284             | 010a | 10 | 0 | 0.0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 100 | 3.3/3.3 |
285             | 020a | 9 | 1 | 10.0 | 0 | 1 | 0.9 | 1 | 1 | 0.09 | 0.3 | 90 | 3.3/3.3 |
286             | 040a | 10 | 0 | 0.0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 10 | 0.0/3.3 |
287             | 040c | 10 | 0 | 0.0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 10 | 0.0/3.3 |
288             | 040d | 5 | 5 | 50.0 | 0 | 1 | 0.5 | 0.5 | [0, 1] | 0.25 | 0.5 | 10 | 1.0/3.3 |
289              
290             The output table provides statistical information on the usage of fields in the
291             original format. We see that the C<001> field was counted 10 times in the data set,
292             but the C<040d> value is only present 5 times. The C<020a> is empty in 10% (zeros%)
293             of the records. The C<001> has very unique values (entropy is maximum), but all C<040c>
294             fields contain the same information (entropy is minimum).
295              
296             See L<Catmandu::Exporter::Stat> for more information about the statistical fields
297             and supported output formats.
298              
299             =head1 MODULES
300              
301             =over
302              
303             =item * L<Catmandu::Exporter::Breaker>
304              
305             =item * L<Catmandu::Cmd::breaker>
306              
307             =back
308              
309             =head1 SEE ALSO
310              
311             L<Catmandu>, L<Catmandu::MARC>, L<Catmandu::XML>, L<Catmandu::Stat>
312              
313             =head1 AUTHOR
314              
315             Patrick Hochstenbach, C<< <patrick.hochstenbach at ugent.be> >>
316              
317             =head1 CONTRIBUTORS
318              
319             Jakob Voss, C<< nichtich at cpan.org >>
320              
321             Johann Rolschewski, C<< jorol at cpan.org >>
322              
323             =cut