File Coverage

blib/lib/XML/Saxtract.pm
Criterion Covered Total %
statement 127 139 91.3
branch 41 54 75.9
condition 14 18 77.7
subroutine 16 16 100.0
pod 2 2 100.0
total 200 229 87.3


line stmt bran cond sub pod time code
1 2     2   144920 use strict;
  2         18  
  2         44  
2 2     2   8 use warnings;
  2         3  
  2         73  
3              
4             package XML::Saxtract;
5             $XML::Saxtract::VERSION = '1.04';
6             # ABSTRACT: Streaming parse XML data into a result hash based upon a specification hash
7             # PODNAME: XML::Saxtract
8              
9 2     2   7 use Exporter qw(import);
  2         3  
  2         70  
10             our @EXPORT_OK = qw(saxtract_string saxtract_url);
11              
12 2     2   1035 use LWP::UserAgent;
  2         71234  
  2         55  
13 2     2   770 use XML::SAX;
  2         6312  
  2         378  
14              
15             sub saxtract_string {
16 12     12 1 7158 my $xml_string = shift;
17 12         19 my $spec = shift;
18 12         24 my %options = @_;
19              
20 12         69 my $handler = XML::Saxtract::ContentHandler->new( $spec, $options{object} );
21 12         77 my $parser = XML::SAX::ParserFactory->parser( Handler => $handler );
22 12         91529 $parser->parse_string($xml_string);
23              
24 12         1094 return $handler->get_result();
25             }
26              
27             sub saxtract_url {
28 1     1 1 1811 my $uri = shift;
29 1         2 my $spec = shift;
30 1         3 my %options = @_;
31              
32 1   33     11 my $agent = $options{agent} || LWP::UserAgent->new();
33              
34 1         2263 my $response = $agent->get($uri);
35 1 50       25364 if ( !$response->is_success() ) {
36 0 0       0 if ( $options{die_on_failure} ) {
37 0         0 die($response);
38             }
39             else {
40 0         0 return;
41             }
42             }
43              
44 1         15 return saxtract_string( $response->content(), $spec, %options );
45             }
46              
47             package XML::Saxtract::ContentHandler;
48             $XML::Saxtract::ContentHandler::VERSION = '1.04';
49 2     2   14 use parent qw(Class::Accessor);
  2         4  
  2         15  
50             __PACKAGE__->follow_best_practice;
51             __PACKAGE__->mk_ro_accessors(qw(result));
52              
53 2     2   4796 use Data::Dumper;
  2         10606  
  2         1833  
54              
55             sub new {
56 12     12   37 my ( $class, @args ) = @_;
57 12         24 my $self = bless( {}, $class );
58              
59 12         32 return $self->_init(@args);
60             }
61              
62             sub _add_value {
63 110     110   117 my $object = shift;
64 110         111 my $spec = shift;
65 110         108 my $value = shift;
66              
67 110         123 my $type = ref($spec);
68 110 100       186 if ( !$type ) {
    50          
    100          
69 86         147 $object->{$spec} = $value;
70             }
71             elsif ( $type eq 'SCALAR' ) {
72 0         0 $object->{$$spec} = $value;
73             }
74             elsif ( $type eq 'CODE' ) {
75 1         4 &$spec( $object, $value );
76             }
77             else {
78 23         28 my $name = $spec->{name};
79 23         31 my $subspec_type = ref( $spec->{type} );
80 23 100       79 if ($subspec_type) {
    100          
    100          
    50          
81 7 50       15 if ( $subspec_type eq 'CODE' ) {
82 7         9 my $subspec_object = $object->{$name};
83 7 100       40 unless ($subspec_object) {
84 2         4 $subspec_object = {};
85 2         6 $object->{$name} = $subspec_object;
86             }
87 7         9 &{ $spec->{type} }( $subspec_object, $value );
  7         14  
88             }
89             }
90             elsif ( $spec->{type} eq 'array' ) {
91 2 100       6 if ( !defined( $object->{$name} ) ) {
92 1         2 $object->{$name} = [];
93             }
94 2         3 push( @{ $object->{$name} }, $value );
  2         5  
95             }
96             elsif ( $spec->{type} eq 'map' ) {
97 9 100       21 if ( !defined( $object->{$name} ) ) {
98 3         8 $object->{$name} = {};
99             }
100 9         28 $object->{$name}{ $value->{ $spec->{key} } } = $value;
101             }
102             elsif ( $spec->{type} eq 'first' ) {
103 5 100       17 if ( !defined( $object->{$name} ) ) {
104 3         9 $object->{$name} = $value;
105             }
106             }
107             else {
108             # type 'last' or default
109 0         0 $object->{$name} = $value;
110             }
111             }
112             }
113              
114             sub characters {
115 54     54   2683 my ( $self, $characters ) = @_;
116 54 50       105 return if ( $self->{skip} > 0 );
117              
118 54 50       94 if ( defined($characters) ) {
119 54         55 push( @{ $self->{buffer} }, $characters->{Data} );
  54         160  
120             }
121             }
122              
123             sub end_element {
124 46     46   3847 my ( $self, $element ) = @_;
125              
126 46 50       95 if ( $self->{skip} > 0 ) {
127 0         0 $self->{skip}--;
128 0         0 return;
129             }
130              
131 46         47 my $stack_element = pop( @{ $self->{element_stack} } );
  46         69  
132 46         64 my $name = $stack_element->{name};
133 46         60 my $attrs = $stack_element->{attrs};
134 46         61 my $spec = $stack_element->{spec};
135 46         61 my $path = $stack_element->{spec_path};
136 46         53 my $result = $stack_element->{result};
137              
138 46 50 100     86 if ( defined( $spec->{$path} ) && scalar( @{ $self->{buffer} } ) ) {
  20         61  
139 20         21 my $buffer_data = join( '', @{ $self->{buffer} } );
  20         35  
140 20         74 $buffer_data =~ s/^\s*//;
141 20         84 $buffer_data =~ s/\s*$//;
142 20         46 _add_value( $result, $spec->{$path}, $buffer_data );
143             }
144              
145 46         97 foreach my $attr ( values(%$attrs) ) {
146 89         105 my $ns_uri = $attr->{NamespaceURI};
147             my $attr_path = join( '',
148             $path, '/@', ( $ns_uri && $spec->{$ns_uri} ? "$spec->{$ns_uri}:" : '' ),
149 89 100 100     206 $attr->{LocalName} );
150              
151 89 100       165 if ( $spec->{$attr_path} ) {
152 67         107 _add_value( $result, $spec->{$attr_path}, $attr->{Value} );
153             }
154             }
155              
156 46 50 100     95 if ( !$path && scalar( @{ $self->{element_stack} } ) ) {
  23         55  
157 23         33 my $parent_element = $self->{element_stack}[-1];
158 23         56 my $path_in_parent = "$parent_element->{spec_path}/$name";
159 23         47 _add_value( $parent_element->{result}, $parent_element->{spec}{$path_in_parent},
160             $result );
161             }
162              
163 46         222 $self->{buffer} = [];
164             }
165              
166             sub _init {
167 12     12   24 my ( $self, $spec, $result ) = @_;
168              
169 12   50     59 $self->{result} = $result || {};
170             $self->{element_stack} = [
171             { spec => $spec,
172             spec_path => '',
173             result => $self->{result}
174             }
175 12         60 ];
176 12         31 $self->{buffer} = [];
177 12         19 $self->{skip} = 0;
178              
179 12         25 return $self;
180             }
181              
182             sub _spec_prefix {
183 35     35   51 my ( $self, $uri ) = @_;
184              
185 35         34 for ( my $i = scalar( @{ $self->{element_stack} } ) - 1; $i >= 0; $i-- ) {
  35         88  
186 39         76 my $spec_prefix = $self->{element_stack}[$i]->{spec}{$uri};
187 39 100       90 return $spec_prefix if ( defined($spec_prefix) );
188             }
189              
190 0         0 return;
191             }
192              
193             sub start_element {
194 46     46   36942 my ( $self, $element ) = @_;
195              
196 46 50       107 if ( $self->{skip} ) {
197 0         0 $self->{skip}++;
198 0         0 return;
199             }
200              
201 46         61 my $stack_top = $self->{element_stack}[-1];
202 46         54 my $spec = $stack_top->{spec};
203 46         57 my $result = $stack_top->{result};
204 46         57 my $uri = $element->{NamespaceURI};
205              
206 46         45 my $qname;
207 46 100       68 if ($uri) {
208 35         58 my $spec_prefix = $self->_spec_prefix($uri);
209 35 50       79 if ( !defined($spec_prefix) ) {
    100          
210              
211             # uri is not in spec, so nothing could possibly match
212 0         0 $self->{skip} = 1;
213 0         0 return;
214             }
215             elsif ( $spec_prefix eq '' ) {
216 27         39 $qname = $element->{LocalName};
217             }
218             else {
219 8         18 $qname = "$spec_prefix:$element->{LocalName}";
220             }
221             }
222             else {
223 11         19 $qname = $element->{LocalName};
224             }
225              
226 46         77 my $spec_path = "$stack_top->{spec_path}/$qname";
227 46 100 100     217 if ( defined( $spec->{$spec_path} )
      66        
228             && ref( $spec->{$spec_path} ) eq 'HASH'
229             && defined( $spec->{$spec_path}{spec} ) )
230             {
231 23         35 $spec = $spec->{$spec_path}{spec};
232 23         26 $spec_path = '';
233 23         31 $result = {};
234             }
235              
236             push(
237 46         188 @{ $self->{element_stack} },
238             { name => $qname,
239             attrs => $element->{Attributes},
240 46         50 spec => $spec,
241             spec_path => $spec_path,
242             result => $result
243             }
244             );
245             }
246              
247             1;
248              
249             __END__