File Coverage

blib/lib/Text/FixedWidth.pm
Criterion Covered Total %
statement 148 164 90.2
branch 48 66 72.7
condition 19 30 63.3
subroutine 20 21 95.2
pod 7 7 100.0
total 242 288 84.0


line stmt bran cond sub pod time code
1             package Text::FixedWidth;
2              
3 8     8   310947 use warnings;
  8         42  
  8         290  
4 8     8   49 use strict;
  8         17  
  8         279  
5 8     8   48 use Carp;
  8         19  
  8         925  
6 8     8   48 use vars ('$AUTOLOAD');
  8         23  
  8         975  
7 8     8   14575 use Storable ();
  8         51799  
  8         17415  
8              
9             =head1 NAME
10              
11             Text::FixedWidth - Easy OO manipulation of fixed width text files
12              
13             =cut
14              
15             our $VERSION = '0.13';
16              
17             =head1 SYNOPSIS
18              
19             use Text::FixedWidth;
20              
21             my $fw = new Text::FixedWidth;
22             $fw->set_attributes(qw(
23             fname undef %10s
24             lname undef %-10s
25             points 0 %04d
26             ));
27              
28             $fw->parse(string => ' JayHannah 0003');
29             $fw->get_fname; # Jay
30             $fw->get_lname; # Hannah
31             $fw->get_points; # 0003
32              
33             $fw->set_fname('Chuck');
34             $fw->set_lname('Norris');
35             $fw->set_points(17);
36             $fw->string; # ' ChuckNorris 0017'
37              
38             If you're familiar with printf formats, then this class should make processing
39             fixed width files trivial.
40             Just define your attributes and then you can get_* and set_* all day long. When
41             you're happy w/ your values envoke string() to spit out your object in your
42             defined fixed width format.
43              
44             When reading a fixed width file, simply pass each line of the file into parse(), and
45             then you can use the get_ methods to retrieve the value of whatever attributes you
46             care about.
47              
48             =head1 METHODS
49              
50             =head2 new
51              
52             Constructor. Does nothing fancy.
53              
54             =cut
55              
56             sub new {
57 7     7 1 94 my ($caller,%args) = (@_);
58              
59 7         21 my $caller_is_obj = ref($caller);
60 7   33     58 my $class = $caller_is_obj || $caller;
61 7   33     55 my $self = bless {}, ref($class) || $class;
62 7         42 return $self;
63             }
64              
65              
66             =head2 set_attributes
67              
68             Pass in arguments in sets of 3 and we'll set up attributes for you.
69              
70             The first argument is the attribute name. The second argument is the default
71             value we should use until told otherwise. The third is the printf format we should
72             use to read and write this attribute from/to a string.
73              
74             $fw->set_attributes(qw(
75             fname undef %10s
76             lname undef %-10s
77             points 0 %04d
78             );
79              
80             =cut
81              
82             sub set_attributes {
83 5     5 1 33 my ($self, @att) = @_;
84              
85 5 50       32 unless (@att % 3 == 0) { die "set_attributes() requires sets of 3 parameters"; }
  0         0  
86 5         21 while (@att) {
87 14         36 my ($att, $value, $sprintf) = splice @att, 0, 3;
88 14 50       61 if (exists $self->{_attributes}{$att}) {
89 0         0 die "You already set attribute name '$att'! You can't set it again! All your attribute names must be unique";
90             }
91 14 100 100     97 if ($value && $value eq "undef") { $value = undef; }
  8         16  
92 14         64 $self->{_attributes}{$att}{sprintf} = $sprintf;
93 14         38 $self->{_attributes}{$att}{value} = $value;
94 14         70 my ($length) = ($sprintf =~ /(\d+)/g);
95 14         45 $self->{_attributes}{$att}{length} = $length;
96 14         15 push @{$self->{_attribute_order}}, $att;
  14         58  
97             }
98              
99 5         26 return 1;
100             }
101              
102              
103             =head2 set_attribute
104              
105             Like set_attributes, but only sets one attribute at a time, via named parameters:
106              
107             $fw->set_attribute(
108             name => 'lname',
109             default => undef,
110             format => '%10s',
111             );
112              
113             If an sprintf 'format' is insufficiently flexible, you can set 'reader' to a code reference
114             and also define 'length'. For example, if you need a money format without a period:
115              
116             $fw->set_attribute(
117             name => 'points2',
118             reader => sub { sprintf("%07.0f", $_[0]->get_points2 * 100) },
119             length => 7,
120             );
121             $fw->set_points2(13.2);
122             $fw->get_points2; # 13.2
123             $fw->getf_points2; # 0001320
124              
125             Similarly, you can set 'writer' to a code reference for arbitrary manipulations when
126             setting attributes:
127              
128             $fw->set_attribute(
129             name => 'points3',
130             writer => sub { $_[1] / 2 },
131             format => '%-6s',
132             );
133             $fw->set_points3(3);
134             $fw->get_points3; # 1.5
135             $fw->getf_points3; # '1.5 '
136              
137             =cut
138              
139             sub set_attribute {
140 7     7 1 71 my ($self, %args) = @_;
141 7         14 my $att = $args{name};
142 7         14 my $value = $args{default};
143 7         13 my $sprintf = $args{format};
144 7         11 my $reader = $args{reader};
145 7         16 my $writer = $args{writer};
146 7         10 my $length = $args{length};
147              
148 7 50       30 unless ($att) {
149 0         0 die "set_attribute() requires a 'name' argument";
150             }
151 7 50 66     34 unless ($sprintf || $reader) {
152 0         0 die "set_attribute() requires a 'format' or a 'reader' argument";
153             }
154 7 50 66     36 if ($reader && not defined $length) {
155 0         0 die "set_attribute() requires a 'length' when a 'reader' argument is provided";
156             }
157 7 50       43 if (exists $self->{_attributes}{$att}) {
158 0         0 die "You already set attribute name '$att'! You can't set it again! All your attribute names must be unique";
159             }
160              
161 7 50 33     23 if ($value && $value eq "undef") { $value = undef; }
  0         0  
162 7         29 $self->{_attributes}{$att}{value} = $value;
163 7 100       22 if ($sprintf) {
164 4         10 $self->{_attributes}{$att}{sprintf} = $sprintf;
165 4         25 ($length) = ($sprintf =~ /(\d+)/g);
166             } else {
167 3         9 $self->{_attributes}{$att}{reader} = $reader;
168             }
169 7         21 $self->{_attributes}{$att}{length} = $length;
170 7         32 $self->{_attributes}{$att}{writer} = $writer;
171 7         11 push @{$self->{_attribute_order}}, $att;
  7         25  
172              
173 7         44 return 1;
174             }
175              
176              
177             =head2 parse
178              
179             Parses the string you hand in. Sets each attribute to the value it finds in the string.
180              
181             $fw->parse(string => ' JayHannah 0003');
182              
183             =cut
184              
185             sub parse {
186 11     11 1 3439 my ($self, %args) = @_;
187              
188 11 50       32 die ref($self).":Please provide a string argument" if (!$args{string});
189 11         17 my $string = $args{string};
190              
191 11 100       39 $self = $self->clone if $args{clone};
192              
193 11         18 my $offset = 0;
194 11         16 foreach (@{$self->{_attribute_order}}) {
  11         33  
195 44         84 my $length = $self->{_attributes}{$_}{length};
196 44         104 $self->{_attributes}{$_}{value} = substr $string, $offset, $length;
197 44         71 $offset += $length;
198             }
199              
200 11 100       53 return $args{clone}? $self : 1;
201             }
202              
203              
204             =head2 string
205              
206             Dump the object to a string. Walks each attribute in order and outputs each in the
207             format that was specified during set_attributes().
208              
209             print $fw->string; # ' ChuckNorris 0017'
210              
211             =cut
212              
213             sub string {
214 7     7 1 141 my ($self) = @_;
215 7         11 my $rval;
216 7         10 foreach my $att (@{$self->{_attribute_order}}) {
  7         20  
217 28         62 $rval .= $self->_getf($att);
218             }
219 7         34 return $rval;
220             }
221              
222              
223             =head2 getf_*
224              
225             For the 'foo' attribute, we provide the getter get_foo() per the SYNOPSIS above.
226             But we also provide getf_foo(). get_* returns the current value in no particular format,
227             while getf_* returns the fixed-width formatted value.
228              
229             $fw->get_fname; # Jay (no particular format)
230             $fw->getf_fname; # ' Jay' (the format you specified)
231              
232             =cut
233              
234             sub _getf {
235 41     41   69 my ($self, $att) = @_;
236              
237 41         149 my $value = $self->{_attributes}{$att}{value};
238 41         88 my $length = $self->{_attributes}{$att}{length};
239 41         86 my $sprintf = $self->{_attributes}{$att}{sprintf};
240 41         78 my $reader = $self->{_attributes}{$att}{reader};
241 41 100       89 if ($reader) {
242 3         11 my $rval = $reader->($self);
243 3 50       15 if (length($rval) != $length) {
244 0         0 die "string() error: " . ref($self) . " is loaded with a 'reader' which returned a string of length " . length($rval) . ", but 'length' was set to $length. Please correct the class. The error occured on attribute '$att' converting value '$value' to '$rval'";
245             }
246 3         17 return $rval;
247             }
248              
249 38 50 66     248 if (defined ($value) and length($value) > $length) {
250 0         0 warn "string() error! " . ref($self) . " length of attribute '$att' cannot exceed '$length', but it does. Please shorten the value '$value'";
251 0         0 return 0;
252             }
253 38 100       87 if (not defined $value) {
254 10         17 $value = '';
255             }
256 38 50       77 unless ($sprintf) {
257 0         0 warn "string() error! " . ref($self) . " sprintf not set on attribute $att. Using '%s'";
258 0         0 $sprintf = '%s';
259             }
260              
261 38         42 my $rval;
262 38 100 66     227 if (
      66        
263             $sprintf =~ /\%\d*[duoxefgXEGbB]/ && ( # perldoc -f sprintf
264             (not defined $value) ||
265             $value eq "" ||
266             $value !~ /^(\d+\.?\d*|\.\d+)$/ # match valid number
267             )
268             ) {
269 3 50       9 $value = '' if (not defined $value);
270 3         34 warn "string() warning: " . ref($self) . " attribute '$att' contains '$value' which is not numeric, yet the sprintf '$sprintf' appears to be numeric. Using 0";
271 3         168 $value = 0;
272             }
273 38 50       170 $rval = sprintf($sprintf, (defined $value ? $value : ""));
274              
275 38 50       99 if (length($rval) != $length) {
276 0         0 die "string() error: " . ref($self) . " is loaded with an sprintf format which returns a string that is NOT the correct length. Please correct the class. The error occured on attribute '$att' converting value '$value' via sprintf '$sprintf', which is '$rval', which is not '$length' characters long";
277             }
278              
279 38         158 return $rval;
280             }
281              
282              
283             =head2 auto_truncate
284              
285             Text::FixedWidth can automatically truncate long values for you. Use this method to tell your $fw
286             object which attributes should behave this way.
287              
288             $fw->auto_truncate("fname", "lname");
289              
290             (The default behavior if you pass in a value that is too long is to carp out a warning,
291             ignore your set(), and return undef.)
292              
293             =cut
294              
295             sub auto_truncate {
296 2     2 1 50 my ($self, @attrs) = @_;
297 2         6 $self->{_auto_truncate} = {};
298 2         6 foreach my $attr (@attrs) {
299 3 100       9 unless ($self->{_attributes}{$attr}) {
300 1         14 carp "Can't auto_truncate attribute '$attr' because that attribute does not exist";
301 1         620 next;
302             }
303 2         7 $self->{_auto_truncate}->{$attr} = 1;
304             }
305 2         17 return 1;
306             }
307              
308             =head2 clone
309              
310             Provides a clone of a Text::FixedWidth object. If available it will attempt
311             to use L or L falling back on L.
312              
313             my $fw_copy = $fw->clone;
314              
315             This method is most useful when being called from with in the L method.
316              
317             while( my $row = $fw->parse( clone => 1, string => $str ) ) {
318             print $row->foobar;
319             }
320              
321             See L for further information.
322              
323             =cut
324              
325             sub clone {
326 11     11 1 16 my $self = shift;
327 11         795 return Storable::dclone($self);
328             }
329              
330              
331              
332              
333 0     0   0 sub DESTROY { }
334              
335             # Using Damian methodology so I don't need to require Moose.
336             # Object Oriented Perl (1st edition)
337             # Damian Conway
338             # Release date 15 Aug 1999
339             # Publisher Manning Publications
340             sub AUTOLOAD {
341 8     8   117 no strict "refs";
  8         23  
  8         6904  
342 40 100   40   207 if ($AUTOLOAD =~ /.*::get_(\w+)/) {
343 17         110 my $att = $1;
344 17         70 *{$AUTOLOAD} = sub {
345 55     55   2631 $_[0]->_get($att);
346 17         70 };
347 17         35 return &{$AUTOLOAD};
  17         54  
348             }
349              
350 23 100       113 if ($AUTOLOAD =~ /.*::getf_(\w+)/) {
351 13         35 my $att = $1;
352 13         50 *{$AUTOLOAD} = sub {
353 13     13   52 $_[0]->_getf($att);
354 13         48 };
355 13         40 return &{$AUTOLOAD};
  13         45  
356             }
357              
358 10 50       102 if ($AUTOLOAD =~ /.*::set_(\w+)/) {
359 10         65 my $att = $1;
360 10         40 *{$AUTOLOAD} = sub {
361 17     17   120 $_[0]->_set($att, $_[1]);
362 10         46 };
363 10         19 return &{$AUTOLOAD};
  10         33  
364             }
365              
366 0         0 confess ref($_[0]).":No such method: $AUTOLOAD";
367             }
368              
369              
370             sub _get {
371 55     55   90 my ($self, $att) = @_;
372 55 50       223 croak "Can't get_$att(). No such attribute: $att" unless (defined $self->{_attributes}{$att});
373 55         155 my $ret = $self->{_attributes}{$att}{value};
374 55 100       268 $ret =~ s/\s+$// if $ret;
375 55 100       203 $ret =~ s/^\s+// if $ret;
376 55         384 return $ret;
377             }
378              
379              
380             sub _set {
381 17     17   37 my ($self, $att, $val) = @_;
382              
383 17         145 my $length = $self->{_attributes}{$att}{length};
384 17         40 my $writer = $self->{_attributes}{$att}{writer};
385              
386 17 50       54 croak "Can't set_$att(). No such attribute: $att" unless (defined $self->{_attributes}{$att});
387 17 50       51 if (defined $self->{_attributes}{$att}) {
388 17 100 100     124 if ($writer) {
    100          
389 3         10 $val = $writer->($self, $val);
390             } elsif (defined $val && length($val) > $length) {
391 3 100       11 if ($self->{_auto_truncate}{$att}) {
392 2         6 $val = substr($val, 0, $length);
393 2         5 $self->{_attributes}{$att}{value} = $val;
394             } else {
395 1         31 carp "Can't set_$att('$val'). Value must be $length characters or shorter";
396 1         1072 return undef;
397             }
398             }
399 16         58 $self->{_attributes}{$att}{value} = $val;
400 16         85 return 1;
401             } else {
402 0           return 0;
403             }
404             }
405              
406              
407             =head1 ALTERNATIVES
408              
409             Other modules that may do similar things:
410             L,
411             L,
412             L,
413             L
414              
415             =head1 AUTHOR
416              
417             Jay Hannah, C<< >>, http://jays.net
418              
419             =head1 BUGS
420              
421             Please report any bugs or feature requests to C, or through
422             the web interface at L. I will be notified, and then you'll
423             automatically be notified of progress on your bug as I make changes.
424              
425             =head1 SUPPORT
426              
427             You can find documentation for this module with the perldoc command.
428              
429             perldoc Text::FixedWidth
430              
431             You can also look for information at:
432              
433             =over 4
434              
435             =item * MetaCPAN
436              
437             L
438              
439             =item * RT: CPAN's request tracker
440              
441             L
442              
443             =item * Source code
444              
445             L
446              
447             =item * AnnoCPAN: Annotated CPAN documentation
448              
449             L
450              
451             =back
452              
453              
454             =head1 COPYRIGHT & LICENSE
455              
456             Copyright 2008-2013 Jay Hannah, all rights reserved.
457              
458             This program is free software; you can redistribute it and/or modify it
459             under the same terms as Perl itself.
460              
461              
462             =cut
463              
464             1; # End of Text::FixedWidth