File Coverage

blib/lib/Parse/Binary/FixedFormat.pm
Criterion Covered Total %
statement 12 114 10.5
branch 0 50 0.0
condition 0 26 0.0
subroutine 4 12 33.3
pod 4 6 66.6
total 20 208 9.6


line stmt bran cond sub pod time code
1             package Parse::Binary::FixedFormat;
2              
3 1     1   7 use bytes;
  1         2  
  1         6  
4 1     1   23 use strict;
  1         2  
  1         27  
5 1     1   5 use integer;
  1         1  
  1         5  
6             our $VERSION = '0.05';
7              
8             sub new {
9 0     0 1   my ($class, $layout) = @_;
10 0           my $self;
11 0 0         if (UNIVERSAL::isa($layout, 'HASH')) {
12 0           require Parse::Binary::FixedFormat::Variants;
13 0           $self = Parse::Binary::FixedFormat::Variants->new($layout);
14             } else {
15 0           $self = { Names=>[], Count=>[], Format=>"" };
16 0           bless $self, $class;
17 0 0         $self->parse_fields($layout) if $layout;
18             }
19 0           return $self;
20             }
21              
22             sub parse_fields {
23 0     0 0   my ($self,$fmt) = @_;
24 0           foreach my $fld (@$fmt) {
25 0           my ($name, $format, $count, $group) = split /\s*:\s*/,$fld;
26 0           push @{$self->{Names}}, $name;
  0            
27 0           push @{$self->{Count}}, $count;
  0            
28 0           push @{$self->{Group}}, $group;
  0            
29 0 0         if (defined $count) {
30 0   0       push @{$self->{Format}||=[]}, "($format)$count";
  0            
31             }
32             else {
33 0   0       push @{$self->{Format}||=[]}, $format;
  0            
34             }
35             }
36             }
37              
38             my %_format_cache;
39             sub _format {
40 0     0     my ($self, $lazy) = @_;
41 0   0       $self->{_format} ||= do {
42 0           my $format = join('', @{$self->{Format}});
  0            
43 0   0       $_format_cache{$format} ||= do {
44 0 0         $format =~ s/\((.*?)\)\*$/a*/ if $lazy; # tail iteration
45 0 0         $format =~ s/\((.*?)\)(?:(\d+)|(\*))/$1 x ($3 ? 1 : $2)/eg if ($] < 5.008);
  0 0          
46 0           $format;
47             };
48             };
49             }
50              
51             my %_parent_format;
52             sub unformat {
53 0     0 1   my $self = shift;
54 0           my @flds = shift;
55 0           my $lazy = shift;
56 0           my $parent = shift;
57              
58 0           my $format = $self->_format($lazy);
59 0 0         @flds = unpack($format, $flds[0]) unless $format eq 'a*';
60              
61 0           my $rec = {};
62 0           foreach my $i (0 .. $#{$self->{Names}}) {
  0            
63 0           my $name = $self->{Names}[$i];
64 0 0         if (defined(my $count = $self->{Count}[$i])) {
65 0 0         next unless $count;
66              
67 0           my $group = $self->{Group}[$i];
68 0 0         if ($count eq '*') {
69 0           $count = @flds;
70 0   0       $group ||= 1;
71             }
72              
73 0 0         if ($group) {
74 0           my $pad = 0;
75 0 0         $pad = length($1) if $self->{Format}[$i] =~ /(X+)/;
76              
77 0 0 0       if ($lazy and $i == $#{$self->{Names}}) {
  0            
78 0 0         my $format = $self->{Format}[$i] or die "No format found";
79 0 0         $format =~ s/^\((.*?)\)\*$/$1/ or die "Not a count=* field";
80              
81 0   0       my $record = ($rec->{$name} ||= []);
82 0 0 0       push @$record, $self->lazy_unformat(
83             $parent, $record, $pad, $format, \($flds[0])
84             ) if @flds and length($flds[0]);
85              
86 0           next;
87             }
88              
89 0           my $count_idx = 0;
90 0           while (my @content = splice(@flds, 0, $group)) {
91 0 0         substr($content[-1], -$pad, $pad, '') if $pad;
92 0           push @{$rec->{$name}}, \@content;
  0            
93 0           $count_idx += $group;
94 0 0         last if $count_idx >= $count;
95             }
96             }
97             else {
98 0           @{$rec->{$name}} = splice @flds, 0, $count;
  0            
99             }
100             } else {
101 0           $rec->{$name} = shift @flds;
102             }
103             }
104 0           return $rec;
105             }
106              
107             sub lazy_unformat {
108 0     0 0   my ($self, $parent, $record, $pad, $format, $data) = @_;
109              
110             # for each request of a member data, we:
111 0 0         my $valid_sub = ($parent->can('valid_unformat') ? 1 : 0);
112             return sub { {
113             # grab one chunk of data
114 0     0     my @content = unpack($format, $$data);
  0            
115 0           my $length = length(pack($format, @content));
116              
117             # eliminate it from the source string
118 0           my $chunk = substr($$data, 0, $length, '');
119 0           my $done = (length($$data) <= $pad);
120              
121 0 0 0       if ($valid_sub and !$done and !$_[0]->valid_unformat(\@content, \$chunk, $done)) {
      0        
122             # weed out invalid data immediately
123 0           redo;
124             }
125              
126             # remove extra padding
127 0 0         substr($content[-1], -$pad, $pad, '') if $pad;
128              
129             # and prepend (or replace if there are no more data) with it
130 0           splice(@{$_[1]}, -1, $done, \@content);
  0            
131 0           return \@content;
132 0           } };
133             }
134              
135             sub format {
136 0     0 1   my ($self,$rec) = @_;
137 0           my @flds;
138 0           my $i = 0;
139 0           foreach my $name (@{$self->{Names}}) {
  0            
140 0 0         if ($self->{Count}[$i]) {
141 0 0         push @flds,map {ref($_) ? @$_ : $_} @{$rec->{$name}};
  0            
  0            
142             } else {
143 0 0         if (ref($rec->{$name}) eq "ARRAY") {
144 0 0         if (@{$rec->{$name}}) {
  0            
145 0           push @flds,$rec->{$name};
146             }
147             } else {
148 0           push @flds,$rec->{$name};
149             }
150             }
151 0           $i++;
152             }
153 1     1   1519 no warnings 'uninitialized';
  1         2  
  1         184  
154 0           return pack($self->_format, @flds);
155             }
156              
157             sub blank {
158 0     0 1   my $self = shift;
159 0           my $rec = $self->unformat(pack($self->_format,
160             unpack($self->_format,
161             '')));
162 0           return $rec;
163             }
164              
165             1;
166              
167             =head1 NAME
168              
169             Parse::Binary::FixedFormat - Convert between fixed-length fields and hashes
170              
171             =head1 SYNOPSIS
172              
173             use Parse::Binary::FixedFormat;
174              
175             my $tarhdr =
176             new Parse::Binary::FixedFormat [ qw(name:a100 mode:a8 uid:a8 gid:a8 size:a12
177             mtime:a12 chksum:a8 typeflag:a1 linkname:a100
178             magic:a6 version:a2 uname:a32 gname:a32
179             devmajor:a8 devminor:a8 prefix:a155) ];
180             my $buf;
181             read TARFILE, $buf, 512;
182              
183             # create a hash from the buffer read from the file
184             my $hdr = $tarhdr->unformat($buf); # $hdr gets a hash ref
185              
186             # create a flat record from a hash reference
187             my $buf = $tarhdr->format($hdr); # $hdr is a hash ref
188              
189             # create a hash for a new record
190             my $newrec = $tarhdr->blank();
191              
192             =head1 DESCRIPTION
193              
194             B can be used to convert between a buffer with
195             fixed-length field definitions and a hash with named entries for each
196             field. The perl C and C functions are used to perform
197             the conversions. B builds the format string by
198             concatenating the field descriptions and converts between the lists
199             used by C and C and a hash that can be reference by
200             field name.
201              
202             =head1 METHODS
203              
204             B provides the following methods.
205              
206             =head2 new
207              
208             To create a converter, invoke the B method with a reference to a
209             list of field specifications.
210              
211             my $cvt =
212             new Parse::Binary::FixedFormat [ 'field-name:descriptor:count', ... ];
213              
214             Field specifications contain the following information.
215              
216             =over 4
217              
218             =item field-name
219              
220             This is the name of the field and will be used as the hash index.
221              
222             =item descriptor
223              
224             This describes the content and size of the field. All of the
225             descriptors get strung together and passed to B and B as
226             part of the template argument. See B for information
227             on what can be specified here.
228              
229             Don't use repeat counts in the descriptor except for string types
230             ("a", "A", "h, "H", and "Z"). If you want to get an array out of the
231             buffer, use the C argument.
232              
233             =item count
234              
235             This specifies a repeat count for the field. If specified as a
236             non-zero value, this field's entry in the resultant hash will be an
237             array reference instead of a scalar.
238              
239             =back
240              
241             =head2 unformat
242              
243             To convert a buffer of data into a hash, pass the buffer to the
244             B method.
245              
246             $hashref = $cvt->unformat($buf);
247              
248             Parse::Binary::FixedFormat applies the constructed format to the buffer with
249             C and maps the returned list of elements to hash entries.
250             Fields can now be accessed by name though the hash:
251              
252             print $hashref->{field-name};
253             print $hashref->{array-field}[3];
254              
255             =head2 format
256              
257             To convert the hash back into a fixed-format buffer, pass the hash
258             reference to the B method.
259              
260             $buf = $cvt->format($hashref);
261              
262             =head2 blank
263              
264              
265             To get a hash that can be used to create a new record, call the
266             B method.
267              
268             $newrec = $cvt->blank();
269              
270             =head1 ATTRIBUTES
271              
272             Each Parse::Binary::FixedFormat instance contains the following attributes.
273              
274             =over 4
275              
276             =item Names
277              
278             Names contains a list of the field names for this variant.
279              
280             =item Count
281              
282             Count contains a list of occurrence counts. This is used to indicate
283             which fields contain arrays.
284              
285             =item Format
286              
287             Format contains the template string for the Perl B and B
288             functions.
289              
290             =back
291              
292             =head1 AUTHORS
293              
294             Audrey Tang Ecpan@audreyt.orgE
295              
296             Based on Data::FixedFormat, written by Thomas Pfau
297             http://nbpfaus.net/~pfau/.
298              
299             =head1 COPYRIGHT
300              
301             Copyright 2004-2009 by Audrey Tang Ecpan@audreyt.orgE.
302              
303             Copyright (C) 2000,2002 Thomas Pfau. All rights reserved.
304              
305             This module is free software; you can redistribute it and/or modify it
306             under the terms of the GNU General Public License as published by the
307             Free Software Foundation; either version 2 of the License, or (at your
308             option) any later version.
309              
310             This library is distributed in the hope that it will be useful, but
311             WITHOUT ANY WARRANTY; without even the implied warranty of
312             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
313             Library General Public License for more details.
314              
315             You should have received a copy of the GNU General Public License
316             along with this program; if not, write to the Free Software Foundation,
317             Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
318              
319             =cut