File Coverage

blib/lib/Finance/OFX/Parse.pm
Criterion Covered Total %
statement 12 66 18.1
branch 0 22 0.0
condition 0 10 0.0
subroutine 4 9 44.4
pod 2 5 40.0
total 18 112 16.0


line stmt bran cond sub pod time code
1             # Filename: Parse.pm
2             # Parse the Open Financial Exchange format
3             # http://www.ofx.net/
4             #
5             # Created January 30, 2008 Brandon Fosdick
6             #
7             # Copyright 2008 Brandon Fosdick (BSD License)
8             #
9             # $Id: Parse.pm,v 1.2 2008/03/04 04:22:27 bfoz Exp $
10              
11             package Finance::OFX::Parse;
12              
13 1     1   6 use strict;
  1         2  
  1         29  
14 1     1   5 use warnings;
  1         2  
  1         33  
15              
16             our $VERSION = '2';
17              
18 1     1   827 use Finance::OFX::Tree;
  1         3  
  1         39  
19 1     1   1560 use HTTP::Date;
  1         8152  
  1         1224  
20              
21             sub is_unique
22             {
23 0     0 0   my $a = shift;
24 0 0         return undef unless ref($a) eq 'ARRAY';
25 0           my %saw;
26 0   0       $saw{$_->{name}}++ || return 0 for @{$a};
  0            
27 0           1;
28             }
29              
30             sub collapse
31             {
32 0     0 0   my $tree = shift;
33 0 0         return $tree unless ref($tree) eq 'ARRAY';
34              
35             # Recurse on any elements that have arrays for content
36 0           $_->{content} = collapse($_->{content}) for( @{$tree} );
  0            
37              
38             # The passed array can be converted to a hash if all of it's nodes have
39             # unique names
40 0           my %a;
41 0 0         if( is_unique($tree) )
42             {
43 0           $a{$_->{name}} = $_->{content} for ( @{$tree} );
  0            
44             }
45             else # Duplicate names can be converted to an array
46             {
47 0           my %b;
48 0           $b{$_->{name}}++ for @{$tree};
  0            
49             # grep(!$b{$_->{name}}++, @{$tree});
50 0   0       ($b{$_} > 1) && ($a{$_} = []) for keys %b;
51 0           for( @{$tree} )
  0            
52             {
53 0 0         push(@{$a{$_->{name}}}, $_->{content}), next if $b{$_->{name}} > 1;
  0            
54 0           $a{$_->{name}} = $_->{content};
55             # ($b{$_->{name}} > 1) ? push(@{$a{$_->{name}}}, $_->{content}) :
56             # ($a{$_->{name}} = $_->{content});
57             }
58             }
59 0           return \%a;
60             }
61              
62             sub parse_dates
63             {
64 0     0 0   my $tree = shift;
65              
66 0 0         if( ref($tree) eq 'ARRAY' )
    0          
67             {
68 0           parse_dates($_) for @{$tree};
  0            
69             }
70             elsif( ref($tree) eq 'HASH' )
71             {
72 0           for( keys %{$tree} )
  0            
73             {
74 0 0         if( /^dt/ )
75             {
76             # Add seconds spacer
77 0           $tree->{$_} =~ s/^([0-9]{12})([0-9]{2})/$1:$2/;
78             # Add minutes spacer
79 0           $tree->{$_} =~ s/^([0-9]{10})([0-9]{2})/$1:$2/;
80             # Add date spacers
81 0           $tree->{$_} =~ s/^([0-9]{4})([0-9]{2})([0-9]{2})/$1-$2\-$3 /;
82             # Add a leading zero to the timezone offset, if needed
83 0           $tree->{$_} =~ s/\[([-+]?)([0-9]):[A-Z]{3}\]/ $1\x30$2\x30\x30/;
84             # Handle timezone offsets that were already 2 digits
85 0           $tree->{$_} =~ s/\[([-+]?)([0-9]{1,2}):[A-Z]{3}\]/ $1$2\x30\x30/;
86             # Do the conversion
87 0           $tree->{$_} = str2time($tree->{$_}, 'GMT');
88             }
89             else
90             {
91 0           parse_dates($tree->{$_});
92             }
93             }
94             }
95             }
96              
97             sub parse
98             {
99 0     0 1   $_[0] =~ s/\x0D//g; # Un-networkify newlines
100              
101 0           my ($header, $body) = split /\n\n/, shift, 2;
102              
103             # Parse the OFX header block
104 0           $header =~ s/^\s//; # Strip leading whitespace
105 0           my %header = split /[:\n]/, $header; # Convert to a hash
106              
107 0 0 0       return undef unless ($header{OFXHEADER} == '100') and ($header{DATA} eq 'OFXSGML');
108              
109 0           my $tree = Finance::OFX::Tree::parse($body);
110 0 0 0       return undef unless $tree and ($tree->[0]{name} eq 'ofx');
111              
112 0           $tree = collapse($tree); # Collapse the parse tree into a hash
113 0           parse_dates($tree); # Convert date elements to Unix time
114              
115             # Merge the header hash into the parse tree
116 0           $tree->{header} = \%header;
117              
118 0           return $tree;
119             }
120              
121             sub parse_file
122             {
123 0     0 1   my $file = shift;
124 0 0         return undef unless $file;
125             # my $text = do { local(@ARGV, $/) = $file; <> };
126 0           my $text = read_file($file);
127 0 0         return undef unless $text;
128 0           return parse($text);
129             }
130              
131             1;
132              
133             __END__