File Coverage

blib/lib/BSON/Decode.pm
Criterion Covered Total %
statement 36 142 25.3
branch 6 34 17.6
condition 2 5 40.0
subroutine 6 26 23.0
pod 4 5 80.0
total 54 212 25.4


line stmt bran cond sub pod time code
1             package BSON::Decode;
2              
3 2     2   31439 use 5.006;
  2         4  
4 2     2   6 use strict;
  2         2  
  2         29  
5 2     2   9 use warnings;
  2         5  
  2         41  
6 2     2   6 use Carp qw(carp croak);
  2         2  
  2         2333  
7              
8             =head1 NAME
9              
10             BSON::Decode - Decode BSON file and return a perl data structure!
11              
12             =head1 VERSION
13              
14             Version 0.03
15              
16             =cut
17              
18             our $VERSION = '0.03';
19              
20             =head1 SYNOPSIS
21              
22             A small module to parse BSON file to a perl structure
23              
24             my $file = shift;
25             open( my $INF ,$file);
26             my $bs = BSON::Decode->new($INF);
27            
28             say Dumper $bs->fetch(); # read first element
29             say Dumper $bs->fetch(); # read second element
30             $bs->rewind();# rewind the filehandle
31             say Dumper $bs->fetch_all(); # read all element in an ARRAY
32             close $INF;
33              
34              
35              
36             =cut
37              
38             =head1 DESCRIPTION
39              
40             This is a pure perl BSON decoder. It is simple and without any dependencies.
41             (It was mandatory to allow use in an embeded system)
42             The main use is to compare 2 BSON files
43            
44             Some of the BSON grammar's element are not implemented
45              
46             \x06 Undefined (value) Deprecated
47             \x0C DBPointer Deprecated
48             \x0E Deprecated
49              
50             For binary content (\x05)
51             The data is uuencoded as value of the subtype.
52              
53             =cut
54              
55             =head1 METHOD
56              
57             =head2 new
58              
59             Instanciate the parser.
60            
61             By default the parser read from STDIN (unix filter).
62             In that case no rewind is possible. Only sequential read.
63             my $bs = BSON::Decode->new();
64             It is possible to pass an argument to the constructor to chose another source.
65            
66             =head3 filehandle
67            
68             an open filehandle from where to read the BSON data:
69             open( my $INF ,$file);
70             my $bs = BSON::Decode->new($INF);
71              
72             =cut
73              
74             =head3 file name
75            
76             this a path to an existing BSON file:
77             my $bs = BSON::Decode->new($file);
78            
79             =cut
80              
81             =head3 scalar
82            
83             a perl scalar with the BSON data:
84             my $bs = BSON::Decode->new($bson);
85              
86             =cut
87              
88             sub new
89             {
90 1     1 1 61852 my $class = shift;
91              
92 1         1 my $self = {};
93 1         3 bless( $self, $class );
94 1   50     10 $self->{fh} = shift // '';
95 1         1 my $valid = 0;
96             {
97 1         1 local $@ = "";
  1         2  
98 1         1 my $fd = eval {fileno $self->{fh}};
  1         5  
99 1   33     6 $valid = !$@ && defined $fd;
100             }
101 1 50       3 if ( !$valid )
102             {
103 1 50       6 if ( -f $self->{fh} )
    50          
104             {
105 0         0 my $f_name = delete $self->{fh};
106 0         0 open $self->{fh}, '<', $f_name;
107             }
108             elsif ( length $self->{fh} )
109             {
110 0         0 $self->{buffer} = $self->{buffer_bck} = delete $self->{fh};
111             }
112             else
113             {
114 1         3 $self->{fh} = *STDIN;
115 1         8 $self->{stdin} = 1;
116             }
117             }
118 1 50       4 binmode( $self->{fh} ) if exists $self->{fh};
119              
120             $self->{func} = {
121             1 => sub { ## 64-bit float
122 0     0   0 my ( $bson, $name ) = @_;
123 0         0 my $data = substr( $$bson, 0, 8, '' );
124 0         0 return unpack( "d", $data );
125             },
126             2 => sub { ## UTF-8 string
127 0     0   0 my ( $bson, $name ) = @_;
128 0         0 my $size = unpack( "i", substr( $$bson, 0, 4, '' ) );
129 0         0 my $data = substr( $$bson, 0, $size - 1, '' );
130              
131 0         0 return unpack( "a*", $data );
132             },
133             3 => sub { ## Embedded document
134 0     0   0 my ( $bson, $name ) = @_;
135 0         0 my $size = unpack( "i", substr( $$bson, 0, 4, '' ) );
136 0         0 return $self->document( $bson, $size - 4 );
137             },
138             4 => sub { ## Array
139 0     0   0 my ( $bson, $name ) = @_;
140 0         0 my $size = unpack( "i", substr( $$bson, 0, 4, '' ) );
141 0         0 my $r = $self->document( $bson, $size - 4 );
142 0         0 my @t;
143 0         0 foreach my $k ( sort keys %$r )
144             {
145 0 0       0 push @t, $r->{$k} if $k =~ /^\d+$/;
146             }
147 0         0 return \@t;
148             },
149             5 => sub { ## Binary data
150 0     0   0 my ( $bson, $name ) = @_;
151 0         0 my $size = unpack( "i", substr( $$bson, 0, 4, '' ) );
152 0         0 my $subtype = substr( $$bson, 0, 1, '' );
153 0         0 my $binary = pack( 'u', substr( $$bson, 0, $size, '' ) );
154 0         0 return ( $subtype => $binary );
155             },
156             7 => sub { ## ObjectId
157 0     0   0 my ( $bson, $name ) = @_;
158 0         0 my $data = substr( $$bson, 0, 12, '' );
159 0         0 return unpack( "H*", $data );
160             },
161             8 => sub { ## Boolean
162 0     0   0 my ( $bson, $name ) = @_;
163 0         0 my $data = substr( $$bson, 0, 1, '' );
164 0         0 return oct( "0x" . unpack( "H*", $data ) );
165             },
166             9 => sub { ## UTC datetime
167 0     0   0 my ( $bson, $name ) = @_;
168 0         0 my $data = substr( $$bson, 0, 8, '' );
169 0         0 return unpack( "q", $data );
170             },
171             10 => sub { ## Null value
172 0     0   0 my ( $bson, $name ) = @_;
173 0         0 return 'null';
174             },
175             11 => sub { ## REGEX
176 0     0   0 my ( $bson, $name ) = @_;
177 0         0 my $regex = unpack( "Z*", $$bson );
178 0         0 substr $$bson, 0, length( $regex ) + 1, '';
179 0         0 my $regex_options = unpack( "Z*", $$bson );
180 0         0 substr $$bson, 0, length( $regex_options ) + 1, '';
181 0         0 return '/' . $regex . '/' . $regex_options;
182             },
183             13 => sub { ## javascript code
184 0     0   0 my ( $bson, $name ) = @_;
185 0         0 my $size = unpack( "i", substr( $$bson, 0, 4, '' ) );
186 0         0 my $data = substr( $$bson, 0, $size - 1, '' );
187 0         0 return unpack( "a*", $data );
188             },
189             16 => sub { ## 32-bit integer
190 0     0   0 my ( $bson, $name ) = @_;
191 0         0 my $data = substr( $$bson, 0, 4, '' );
192 0         0 return unpack( "i", $data );
193             },
194             17 => sub { ## timestamp
195 0     0   0 my ( $bson, $name ) = @_;
196 0         0 my $data = substr( $$bson, 0, 8, '' );
197 0         0 return unpack( "Q", $data );
198             },
199             18 => sub { ## 64-bit integer
200 0     0   0 my ( $bson, $name ) = @_;
201 0         0 my $data = substr( $$bson, 0, 8, '' );
202 0         0 return unpack( "Q", $data );
203              
204             },
205             19 => sub { ## 128-bit integer
206 0     0   0 my ( $bson, $name ) = @_;
207 0         0 my $data = substr( $$bson, 0, 16, '' );
208 0         0 return unpack( "Q", $data );
209             },
210             127 => sub { ## MAX key
211 0     0   0 my ( $bson, $name ) = @_;
212 0         0 return 'maxkey';
213             },
214             255 => sub { ## MIN key
215 0     0   0 my ( $bson, $name ) = @_;
216 0         0 return 'minkey';
217             }
218 1         40 };
219 1         3 return $self;
220             }
221              
222             =head2 fetch_all
223              
224             Parse the BSON data and return an ARRAY with each element in a perl structure.
225             The parsing is done from the current position.
226            
227             =cut
228              
229             sub fetch_all
230             {
231 0     0 1 0 my ( $self ) = @_;
232 0         0 my @all;
233 0 0       0 if ( $self->{fh} )
234             {
235 0         0 push @all, $self->fetch() while ( !eof $self->{fh} );
236             }
237             else
238             {
239 0         0 push @all, $self->fetch() while ( $self->{buffer} );
240             }
241 0         0 return \@all;
242             }
243              
244             =head2 fetch
245              
246             Parse the BSON data and return the next element in a perl structure.
247              
248             =cut
249              
250             sub fetch
251             {
252 1     1 1 4 my ( $self ) = @_;
253 1         2 my $res;
254             my $sizebits;
255 0         0 my $bson;
256 0         0 my $size;
257 1 50       2 if ( $self->{fh} )
258             {
259 1         12 my $n = read( $self->{fh}, $sizebits, 4 );
260 1 50       122 carp "error reading size\n" if ( $n != 4 );
261              
262 1         44 $size = unpack( "i", $sizebits );
263 1         2 $size -= 4; # -4 because the size includes itself
264 1         31 $n = read( $self->{fh}, $bson, $size );
265 0 0         carp "error reading bson string\n" if ( $n != $size );
266             }
267             else
268             {
269              
270 0           $sizebits = substr( $self->{buffer}, 0, 4, '' );
271 0           $size = unpack( "i", $sizebits );
272 0           $size -= 4;
273 0           $bson = substr( $self->{buffer}, 0, $size, '' );
274             }
275 0 0         if ( length $bson )
276             {
277 0           my $sep = substr( $bson, -1, 1 );
278 0 0         croak( "Bad record seperator '".unpack( "H*",$sep)."'" ) if ( $sep ne "\x00" );
279             }
280 0           $res = $self->document( \$bson, $size );
281 0           return $res;
282             }
283              
284             =head2 rewind
285              
286             Rewind the file descriptor ( or buffer ).
287              
288             =cut
289              
290             sub rewind
291             {
292 0     0 1   my ( $self ) = @_;
293 0 0         if ( $self->{stdin} )
294             {
295 0           carp( "No rewind for STDIN" );
296             }
297             else
298             {
299 0 0         if ( exists $self->{fh} )
300             {
301 0           seek( $self->{fh}, 0, 0 );
302             }
303             else
304             {
305 0           $self->{buffer} = $self->{buffer_bck};
306             }
307             }
308 0           return;
309             }
310              
311             sub document
312             {
313 0     0 0   my ( $self, $str, $size ) = @_;
314 0           my $res = {};
315 0           my $bson = substr( $$str, 0, $size, '' );
316 0 0         if ( length( $bson ) != $size )
317             {
318 0           die "error reading bson string " . length( $bson ) . " != $size\n";
319             }
320 0           my $sep = substr( $bson, -1, 1 );
321 0 0         if ( $sep ne "\x00" )
322             {
323 0           die( "Bad record seperator '$sep'" );
324             }
325 0           while ( length( $bson ) )
326             {
327 0           my $element = oct( "0x" . unpack( "H*", substr( $bson, 0, 1, '' ) ) );
328 0           my $name;
329 0 0         next if ( $element == 0 );
330 0           $name = unpack( "Z*", $bson );
331 0           substr $bson, 0, length( $name ) + 1, '';
332 0 0         if ( exists $self->{func}{$element} )
333             {
334 0           $res->{$name} = $self->{func}{$element}->( \$bson, $name );
335             }
336             else
337             {
338 0           warn "Type $element not implemented for $name";
339 0           last;
340             }
341             }
342 0           return $res;
343             }
344              
345             =head1 AUTHOR
346              
347             DULAUNOY Fabrice, C<< >>
348              
349             =head1 BUGS
350              
351             Please report any bugs or feature requests to C, or through
352             the web interface at L. I will be notified, and then you'll
353             automatically be notified of progress on your bug as I make changes.
354              
355              
356              
357              
358             =head1 SUPPORT
359              
360             You can find documentation for this module with the perldoc command.
361              
362             perldoc BSON::Decode
363              
364              
365             You can also look for information at:
366              
367             =over 4
368              
369             =item * RT: CPAN's request tracker (report bugs here)
370              
371             L
372              
373             =item * AnnoCPAN: Annotated CPAN documentation
374              
375             L
376              
377             =item * CPAN Ratings
378              
379             L
380              
381             =item * Search CPAN
382              
383             L
384              
385             =back
386              
387              
388             =head1 ACKNOWLEDGEMENTS
389              
390              
391             =head1 LICENSE AND COPYRIGHT
392              
393             Copyright 2016 DULAUNOY Fabrice.
394              
395             This program is free software; you can redistribute it and/or modify it
396             under the terms of the the Artistic License (2.0). You may obtain a
397             copy of the full license at:
398              
399             L
400              
401             Any use, modification, and distribution of the Standard or Modified
402             Versions is governed by this Artistic License. By using, modifying or
403             distributing the Package, you accept this license. Do not use, modify,
404             or distribute the Package, if you do not accept this license.
405              
406             If your Modified Version has been derived from a Modified Version made
407             by someone other than you, you are nevertheless required to ensure that
408             your Modified Version complies with the requirements of this license.
409              
410             This license does not grant you the right to use any trademark, service
411             mark, tradename, or logo of the Copyright Holder.
412              
413             This license includes the non-exclusive, worldwide, free-of-charge
414             patent license to make, have made, use, offer to sell, sell, import and
415             otherwise transfer the Package with respect to any patent claims
416             licensable by the Copyright Holder that are necessarily infringed by the
417             Package. If you institute patent litigation (including a cross-claim or
418             counterclaim) against any party alleging that the Package constitutes
419             direct or contributory patent infringement, then this Artistic License
420             to you shall terminate on the date that such litigation is filed.
421              
422             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
423             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
424             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
425             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
426             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
427             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
428             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
429             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
430              
431              
432             =cut
433              
434             1; # End of BSON::Decode