File Coverage

blib/lib/SOAP/XML/Client.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package SOAP::XML::Client;
2             $SOAP::XML::Client::VERSION = '2.8';
3 2     2   15483 use strict;
  2         3  
  2         80  
4 2     2   10 use Carp;
  2         3  
  2         252  
5 2     2   518 use XML::LibXML 0.6;
  0            
  0            
6             use SOAP::Lite 0.67;
7             use SOAP::Data::Builder 0.8;
8             use File::Slurp;
9             use Encode qw( decode );
10              
11             use vars qw($DEBUG);
12              
13             use base qw(Class::Accessor::Fast);
14              
15             my @methods = qw(results results_xml uri xmlns proxy soapversion timeout error
16             strip_default_xmlns encoding header transport status);
17              
18             # wsdk
19              
20             __PACKAGE__->mk_accessors(@methods);
21              
22             $DEBUG = 0;
23              
24             # Get an XML Parser
25             my $parser = XML::LibXML->new();
26             $parser->validation(0);
27             $parser->expand_entities(0);
28              
29             # which methods should be set on object constructor
30             my @config_methods
31             = qw(uri xmlns proxy soapversion strip_default_xmlns encoding timeout);
32              
33             sub new {
34             my ( $proto, $conf ) = @_;
35             my $class = ref($proto) || $proto;
36             my $self = {};
37             bless( $self, $class );
38              
39             # Set up default soapversion and timeout
40             $conf->{soapversion} = '1.1' unless defined $conf->{soapversion};
41             $conf->{timeout} = '30' unless defined $conf->{timeout};
42             $conf->{strip_default_xmlns} = 1
43             unless defined $conf->{strip_default_xmlns};
44             $conf->{encoding} ||= 'utf-8';
45              
46             # There is a WDSL file - process it
47             if ( defined $conf->{wsdl} ) {
48             $self->wsdl( $conf->{wsdl} );
49             $self->_process_wsdl();
50             }
51              
52             if ( $conf->{disable_base64} ) {
53             *SOAP::Serializer::as_base64Binary = sub {
54             my $self = shift;
55             my ( $value, $name, $type, $attr ) = @_;
56             return [
57             $name,
58             { 'xsi:type' => 'xsd:string', %$attr },
59             SOAP::Utils::encode_data($value)
60             ];
61             };
62             }
63              
64             # Read in the required params
65             foreach my $soap_conf (@config_methods) {
66             unless ( defined $conf->{$soap_conf} ) {
67             croak "$soap_conf is required";
68             } else {
69             $self->$soap_conf( $conf->{$soap_conf} );
70             }
71             }
72              
73             $self->header( $conf->{header} ) if $conf->{header};
74              
75             # Set up the SOAP object
76             $self->{soap} = SOAP::Lite->new;
77              
78             # We want the raw XML back
79             $self->{soap}->outputxml(1);
80              
81             return $self;
82              
83             }
84              
85             #sub _process_wsdl {
86             # my $self = shift;
87             # my $services = SOAP::Schema->schema_url($self->wsdl())->parse()->services();
88             # use Data::Dumper; #print STDERR Dumper($services);
89             #
90             # foreach my $class (values %$services) {
91             # print "C: $class\n";
92             # foreach my $method (keys %$class) {
93             # print "M: $method\n";
94             # print Dumper($class->{$method});
95             # $self->{proxies}->{$method} = $class->{$method}->{endpoint}->value();
96             # $self->{uris}->{$method} = $class->{$method}->{uri}->value();
97             # }
98             # }
99             #}
100              
101             sub fetch {
102             my ( $self, $conf ) = @_;
103              
104             # Reset the error so that the object ca be reused
105             $self->error(undef);
106              
107             # Got to have a method!
108             if ( !defined $conf->{method} or $conf->{method} eq '' ) {
109             $self->error('You must supply a method name');
110             return undef;
111             }
112              
113             # Got to get xml from somewhere!
114             if ( !defined $conf->{xml} && !defined $conf->{filename} ) {
115             $self->error(
116             "You must supply either the 'xml' or the 'filename' to use");
117             return undef;
118             }
119              
120             # Check the filename if supplied
121             if ( defined $conf->{filename} ) {
122              
123             # Got a filename, see if it is readable
124             unless ( -r $conf->{filename} ) {
125             $self->error( "Unable to read: " . $conf->{filename} );
126             return undef;
127             } else {
128              
129             # Ok, read it in
130             my $file_xml = read_file( $conf->{filename} );
131             $conf->{xml} = $file_xml;
132             }
133             }
134              
135             # create a builder
136             $self->{sdb} = SOAP::Data::Builder->new();
137              
138             unless ( $conf->{xml} eq '' ) {
139              
140             # add some wrapping paper so XML::LibXML likes it with no top level
141             my $xml_data
142             = '' . $conf->{xml} . '';
143             my $xml;
144             eval { $xml = $parser->parse_string($xml_data) };
145             if ($@) {
146             $self->error( 'Error parsing your XML: ' . $@ );
147             return undef;
148             }
149              
150             # Create the SOAP data from the XML
151             my $nodes = $xml->childNodes;
152             my $top = $nodes->get_node(1); # our wrapper
153             if ( my $nodes = $top->childNodes ) {
154             foreach my $node ( @{$nodes} ) {
155             $self->_process_node( { node => $node } );
156             }
157             }
158             }
159              
160             ################
161             ## Execute the call and get the result back
162             ################
163              
164             carp "About to run _call()" if $DEBUG;
165              
166             #use Data::Dumper;
167             #print Dumper($self->{sdb}->to_soap_data());
168             #my $serialized_xml = SOAP::Serializer->autotype(0)->serialize( $self->{sdb}->to_soap_data() );
169             #carp "IF WE GET HERE IT WORKED!!!!!!!";
170             #print Dumper($self->{sdb}->elems());
171              
172             # execute the call in the relevant style done by the child object
173             my ( $res, $transport ) = $self->_call( $conf->{method} );
174              
175             $self->transport( $transport );
176             $self->status( $transport->status );
177              
178             # TODO: actually need to specify encoding expected in return (or parse from response?)
179             $res = decode( $self->{encoding}, $res );
180              
181             carp "After run _call()" if $DEBUG;
182              
183             if ( !defined $res or $res =~ /^\d/ or !$transport->is_success ) {
184              
185             # Got a web error - if it was XML it wouldn't start with a digit!
186             $self->error($res);
187             return undef;
188             } else {
189              
190             # Strip out default name space stuff as it makes it hard
191             # to parse and there's no reason for it I can see!
192             $res =~ s/xmlns=".*?"//g if $self->strip_default_xmlns();
193              
194             # Generate xml object from the responce
195             my $res_xml;
196             eval { $res_xml = $parser->parse_string($res) };
197             if ($@) {
198              
199             # Not valid xml
200             $self->error( 'Unable to parse returned data as XML: ' . $@ );
201             return undef;
202             } else {
203              
204             # Now look for faults
205             if ( my $nodes = $res_xml->findnodes("//faultstring") ) {
206              
207             # loop through faultstrings - checking it's parent is 'Fault'
208             # We do not care about namespaces
209             foreach my $node ( $nodes->get_nodelist() ) {
210             my $parentnode = $node->parentNode();
211             if ( $parentnode->nodeName() =~ /Fault/ ) {
212              
213             # There is a "(*:)Fault/faultstring"
214             # get the human readable string
215             $self->error(
216             $nodes->get_node(1)->findvalue( '.', $nodes ) );
217             last;
218             }
219             }
220             }
221              
222             # See if there was a fault
223             return undef if $self->error();
224              
225             # All looking good
226             $self->results_xml($res_xml);
227             $self->results($res);
228              
229             # I tried just return; but it didn't like it!
230             return 1;
231             }
232             }
233             }
234              
235             ### Private methods
236              
237             # Convert the XML to SOAP::Data::Builder
238             sub _process_node {
239             my ( $self, $conf ) = @_;
240              
241             # We never access text nodes directly, only via the parent node
242             return if $conf->{node}->nodeType == 3;
243              
244             carp "PROCESSING: " . $conf->{node}->nodeName() if $DEBUG;
245              
246             # Set up the parent if there was one
247             my $parent = undef;
248             $parent = $conf->{parent} if defined $conf->{parent};
249              
250             if ( $DEBUG && defined $parent ) {
251             carp "PARENT NAME:" . $parent->{fullname};
252             }
253              
254             my $type = undef;
255              
256             # Extract the attributes from the node
257             my %attribs;
258             foreach my $att ( $conf->{node}->attributes() ) {
259              
260             # skip anything which isn't defined!
261             next unless defined $att;
262              
263             # Check if it's our 'special' value
264             if ( $att->name() eq '_value_type' ) {
265             $type = $att->value();
266             } else {
267             $attribs{ $att->name() } = $att->value();
268             }
269             }
270              
271             my @t = $conf->{node}->childNodes();
272              
273             # If we have 1 child and that child is text then use the content
274             # of the child as our value we must also be at the end of the tree
275             if ( scalar(@t) == 1
276             && $conf->{node}->childNodes()->get_node(1)->nodeType() == 3 )
277             {
278              
279             #return;
280             my $value = $conf->{node}->childNodes()->get_node(1)->textContent();
281             carp "ADDING : " . $conf->{node}->nodeName . " Value: $value"
282             if $DEBUG;
283             $self->{sdb}->add_elem(
284             name => $conf->{node}->nodeName,
285             attributes => \%attribs,
286             parent => $parent,
287             value => $value,
288             type => $type,
289             );
290              
291             carp "END OF THE LINE BUDDY!" if $DEBUG;
292             } else {
293             carp "- FOUND CHILD NODES" if $DEBUG;
294              
295             # Add it - it's a node without a value, but has child nodes
296             my $obj;
297             if ( defined $parent ) {
298             carp "ADDING ELEMENT WITH PARENT: " . $conf->{node}->nodeName
299             if $DEBUG;
300              
301             # Add with the parent
302             $obj = $self->{sdb}->add_elem(
303             name => $conf->{node}->nodeName,
304             attributes => \%attribs,
305             parent => $parent,
306             );
307             } else {
308             carp "ADDING ELEMENT WITH NO PARENT: " . $conf->{node}->nodeName
309             if $DEBUG;
310              
311             # Add with the parent
312             # Add without parent
313             $obj = $self->{sdb}->add_elem(
314             name => $conf->{node}->nodeName,
315             attributes => \%attribs,
316             );
317             }
318              
319             foreach my $node ( $conf->{node}->childNodes() ) {
320              
321             # process each child node as long as it's not
322             # a text node (type 3)
323             $self->_process_node(
324             { 'node' => $node,
325             'parent' => $obj,
326             }
327             );
328             }
329             }
330             }
331              
332             1;
333              
334             __END__