File Coverage

blib/lib/Data/File/Map.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Data::File::Map;
2             $Data::File::Map::VERSION = '0.08';
3 7     7   363247 use Moose;
  7         2077203  
  7         54  
4 7     7   42244 use MooseX::StrictConstructor;
  7         124068  
  7         37  
5 7     7   51599 use MooseX::SemiAffordanceAccessor;
  7         40646  
  7         36  
6              
7 7     7   39541 use Moose::Util::TypeConstraints;
  7         15  
  7         58  
8 7     7   14601 use MooseX::Types::Moose qw( ArrayRef Str HashRef );
  0            
  0            
9              
10             use Data::File::Map::Field;
11              
12             use XML::LibXML;
13              
14              
15             class_type 'Data::File::Map';
16              
17             coerce 'Data::File::Map',
18             from 'Str',
19             via { Data::File::Map->new_from_file( $_ ) };
20              
21              
22             has 'format' => ( # can be text or csv
23             is => 'rw',
24             isa => subtype ( 'Str' => where { $_ eq 'csv' || $_ eq 'text' } ),
25             default => 'csv',
26             );
27              
28             has 'separator' => ( # only for csv files
29             is => 'rw',
30             isa => 'Str',
31             default => '|',
32             );
33              
34             has 'fields' => (
35             is => 'bare',
36             isa => 'ArrayRef',
37             traits => [qw( Array )],
38             default => sub { [ ] },
39             handles => {
40             '_add_field' => 'push',
41             '_fields' => 'elements',
42             },
43             lazy => 1,
44             );
45              
46             has '_xfields' => (
47             is => 'bare',
48             isa => 'ArrayRef',
49             traits => [qw( Array )],
50             handles => {
51             '_xfields' => 'elements',
52             },
53             default => sub {
54             [
55             map {
56             my %field_data;
57             @field_data{qw[name position width label]} = ( @$_ );
58             Data::File::Map::Field->new( %field_data );
59             } $_[0]->fields
60             ]
61             },
62             lazy => 1,
63             );
64              
65             sub fields {
66              
67             my ( $self, $want_objects ) = @_;
68            
69             if ( $want_objects ) {
70             return $self->_xfields;
71             }
72             else {
73             return $self->_fields;
74             }
75            
76             }
77              
78             sub add_field {
79            
80             my ( $self, @args ) = @_;
81            
82             # if an ArrayRef is given
83             if ( @args == 1 && is_ArrayRef($args[0]) ) {
84             $self->_add_field( $args[0] );
85             }
86             # if is a String and a HashRef
87             elsif ( @args == 2 && is_Str($args[0]) && is_HashRef( $args[1] ) ) {
88            
89             # create array to store fields
90             my @values = map { exists $args[1]{$_} ? $args[1]{$_} : undef } qw/position width label/;
91            
92             $self->_add_field( [ $args[0], @values ] );
93             }
94             # if just a string
95             elsif ( @args == 1 && is_Str($args[0]) ) {
96            
97             $self->_add_field( [ $args[0] ] );
98            
99             }
100              
101              
102             else {
103             die "Could not add field, unknown argument format.";
104             }
105            
106             return
107            
108             }
109              
110              
111              
112              
113             sub field_names {
114             map { $_->[0] } $_[0]->fields;
115             }
116              
117             sub get_field {
118            
119             my ( $self, $name) = @_;
120            
121             for my $field ( $self->fields(1) ) {
122            
123             return $field if $field->name eq $name;
124            
125             }
126            
127             }
128              
129              
130             sub new_from_file {
131             my ( $class, $file ) = @_;
132             my $self = $class->new;
133             $self->parse_file( $file );
134             return $self;
135             }
136              
137             sub new_from_string {
138             my ( $class, $str ) = @_;
139             my $self = $class->new;
140             $self->parse_string( $str );
141             return $self;
142             }
143              
144             sub parse_file {
145             my ( $self, $path ) = @_;
146            
147             die "You must specify a path\n" if ! $path;
148            
149             die "Could not find file $path\n" if !-e $path || ! -f $path;
150            
151             my $doc = XML::LibXML->load_xml( location => $path );
152            
153             $self->_parse_document( $doc );
154             }
155              
156             sub parse_string {
157             my ( $self, $str ) = @_;
158            
159             die "You must provide a string to parse\n" if ! $str;
160            
161             my $doc = XML::LibXML->load_xml( string => $str );
162            
163             $self->_parse_document( $doc );
164             }
165              
166             sub _parse_document {
167             my ( $self, $doc ) = @_;
168            
169             my $root = $doc->documentElement;
170            
171             # determine format
172             {
173             my ( $node ) = $root->getChildrenByTagName( 'format' );
174             if ( $node ) {
175             my $value = $node->textContent;
176             $self->set_format( $value );
177             }
178             }
179            
180             # determine separator
181             {
182             my ( $node ) = $root->getChildrenByTagName( 'separator' );
183             if ( $node ) {
184             my $value = $node->textContent;
185             $self->set_separator( $value );
186             }
187             }
188            
189             # determine fields
190             {
191             my ( $node ) = $root->getChildrenByTagName( 'fields' );
192            
193             if ( $node ) {
194            
195             if ( $self->format eq 'csv' ) {
196            
197             for my $field ( $node->getChildrenByTagName( 'field' ) ) {
198            
199             my $name = $field->textContent;
200            
201             my $item = [ $name || '' ];
202             $self->add_field( $item );
203             }
204            
205             }
206             elsif ( $self->format eq 'text' ) {
207            
208             for my $field ( $node->getChildrenByTagName( 'field' ) ) {
209            
210             my $name = $field->getAttribute('name') || $field->textContent;
211            
212             my $label = $field->getAttribute('label') || $field->textContent;
213            
214             my $position = $field->getAttribute('position');
215            
216             my $width = $field->getAttribute('width');
217            
218             my ( $pos, $w );
219             if ( $position ) {
220             ( $pos, $w ) = split /\./, $position;
221             }
222            
223             $w = $width if $width;
224            
225             if ( $pos && $w ) {
226             my $item = [ $name, $pos, $w, $label];
227             $self->add_field( $item );
228             }
229             else {
230             die "No position/width specified for field ($name)\n";
231             }
232            
233            
234             }
235            
236             }
237             }
238             }
239             }
240              
241              
242              
243              
244              
245             ## DEPRECREATE THESE FUNCTIONS
246             sub read {
247             my ( $self, $line ) = @_;
248            
249             chomp $line;
250            
251             my %rec;
252            
253             if ( $self->format eq 'csv' ) {
254            
255             my $sep = $self->separator;
256            
257             @rec{$self->field_names} = split /$sep/, $line;
258             delete $rec{''};
259             }
260             elsif ( $self->format eq 'text' ) {
261             no warnings;
262             @rec{ $self->field_names } = map {
263             my $val = substr( $line, $_->[1] - 1, $_->[2] );
264             $val ||= '';
265             $val =~ s/^\s+//;
266             $val =~ s/\s+$//;
267             $val;
268             } $self->fields;
269            
270             }
271            
272             return \%rec;
273             }
274              
275             sub read_file {
276             my ( $self, $path ) = @_;
277            
278             if ( ! -e $path ) {
279             die "File ($path) does not exist.\n";
280             }
281            
282             my @records;
283            
284             open my $file, '<', $path or die "Could not open file ($path) for reading.\n";
285             flock $file, 2;
286            
287             while ( <$file> ) {
288             push @records, $self->read( $_ );
289             }
290            
291             close $file;
292            
293             return @records;
294             }
295              
296             1;
297              
298              
299             __END__
300              
301             =pod
302              
303             =head1 NAME
304              
305             Data::File::Map - Read data file definitions stored as XML
306              
307             =head1 SYNOPSIS
308              
309             use Data::File::Map;
310              
311             # load data file definition
312              
313             $map = Data::File::Map->new_from_file( 'path/to/map.xml' );
314              
315             # read records from a data file using the map
316              
317             open FILE, 'data.txt' or die "Could not open file.";
318            
319             while( <FILE> ) {
320            
321             $record = $map->read( $_ );
322            
323             }
324            
325             close FILE;
326              
327             =head1 DESCRIPTION
328              
329             Data::File::Map will allow you to read in a data file definition stored as XML. The map
330             can then be used to parse records in a data file. Handles delimited and formatted text
331             data.
332              
333             =head1 ATTRIBUTES
334              
335             =over 4
336              
337             =item format
338              
339             The format of the data file. Can be either C<csv> for delimited files or 'text'
340             for formatted ascii files.
341              
342             =over 4
343              
344             =item isa: Str['text'|'csv']
345              
346             =item default: csv
347              
348             =back
349              
350             =item separator
351              
352             Used to separate variables in a csv file. This is a regular expression.
353              
354             =over 4
355              
356             =item isa: String['text'|'csv']
357              
358             =item default: \|
359              
360             =back
361              
362             =back
363              
364             =head1 METHODS
365              
366             =over 4
367              
368             =item add_field \@attributes
369              
370             Attribute order is name, position, width, label;
371              
372             =item add_field $name, [\%attributes]
373              
374             Add a field to the map. If C<\%attributes> is supplied, the position, width, and label
375             attributes will be stored.
376              
377             =item fields [$want_objects]
378              
379             Returns a list of ArrayRefs containing information about the fields in the definition.
380             The format off the ArrayRefs is C<[$field_name, $position, $width]>. Position and width
381             will only be defined in C<text> files. If C<$want_objects> will return a list of
382             C<Data::File::Map::Field> objects.
383              
384              
385             =item field_names
386              
387             Returns a list of field names in the order defined in the definition file.
388              
389             =item get_field $name
390              
391             Returns the L<Data::File::Map::Field> object with the given name.
392              
393             =item new
394              
395             Create a new L<Data::File::Map> instance.
396              
397             =item new_from_file $path
398              
399             Create a new L<Data::File::Map> instance and load definition from a file.
400              
401             =item new_from_string $string
402              
403             Create a new L<Data::File::Map> instance and load definition from a string.
404              
405             =item parse_file $path
406              
407             Load definition from a file.
408              
409             =item parse_string $string
410              
411             Load definition from a string.
412              
413             =item read $line
414              
415             Takes a line from a data file and uses the definition to extaract the variables.
416             Returns a HashRef with field names as keys.
417              
418             =item read_file $path
419              
420             Calls C<read> on each line in the given file and returns an array of records.
421              
422             =back
423              
424             =head1 AUTHORS
425              
426             Jeffrey Ray Hallock E<lt>jeffrey.hallock at gmail dot comE<gt>
427              
428             =head1 COPYRIGHT
429              
430             Copyright (c) 2013 Jeffrey Ray Hallock.
431            
432             =head1 LICENSE
433              
434             This is free software, licensed under:
435              
436             The Artistic License 2.0 (GPL Compatible)
437              
438             =cut
439              
440