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.7';
3 2     2   31655 use strict;
  2         4  
  2         79  
4 2     2   10 use Carp;
  2         5  
  2         211  
5 2     2   1139 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 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->status( $transport->status );
176              
177             # TODO: actually need to specify encoding expected in return (or parse from response?)
178             $res = decode( $self->{encoding}, $res );
179              
180             carp "After run _call()" if $DEBUG;
181              
182             if ( !defined $res or $res =~ /^\d/ or !$transport->is_success ) {
183              
184             # Got a web error - if it was XML it wouldn't start with a digit!
185             $self->error($res);
186             return undef;
187             } else {
188              
189             # Strip out default name space stuff as it makes it hard
190             # to parse and there's no reason for it I can see!
191             $res =~ s/xmlns=".*?"//g if $self->strip_default_xmlns();
192              
193             # Generate xml object from the responce
194             my $res_xml;
195             eval { $res_xml = $parser->parse_string($res) };
196             if ($@) {
197              
198             # Not valid xml
199             $self->error( 'Unable to parse returned data as XML: ' . $@ );
200             return undef;
201             } else {
202              
203             # Now look for faults
204             if ( my $nodes = $res_xml->findnodes("//faultstring") ) {
205              
206             # loop through faultstrings - checking it's parent is 'Fault'
207             # We do not care about namespaces
208             foreach my $node ( $nodes->get_nodelist() ) {
209             my $parentnode = $node->parentNode();
210             if ( $parentnode->nodeName() =~ /Fault/ ) {
211              
212             # There is a "(*:)Fault/faultstring"
213             # get the human readable string
214             $self->error(
215             $nodes->get_node(1)->findvalue( '.', $nodes ) );
216             last;
217             }
218             }
219             }
220              
221             # See if there was a fault
222             return undef if $self->error();
223              
224             # All looking good
225             $self->results_xml($res_xml);
226             $self->results($res);
227              
228             # I tried just return; but it didn't like it!
229             return 1;
230             }
231             }
232             }
233              
234             ### Private methods
235              
236             # Convert the XML to SOAP::Data::Builder
237             sub _process_node {
238             my ( $self, $conf ) = @_;
239              
240             # We never access text nodes directly, only via the parent node
241             return if $conf->{node}->nodeType == 3;
242              
243             carp "PROCESSING: " . $conf->{node}->nodeName() if $DEBUG;
244              
245             # Set up the parent if there was one
246             my $parent = undef;
247             $parent = $conf->{parent} if defined $conf->{parent};
248              
249             if ( $DEBUG && defined $parent ) {
250             carp "PARENT NAME:" . $parent->{fullname};
251             }
252              
253             my $type = undef;
254              
255             # Extract the attributes from the node
256             my %attribs;
257             foreach my $att ( $conf->{node}->attributes() ) {
258              
259             # skip anything which isn't defined!
260             next unless defined $att;
261              
262             # Check if it's our 'special' value
263             if ( $att->name() eq '_value_type' ) {
264             $type = $att->value();
265             } else {
266             $attribs{ $att->name() } = $att->value();
267             }
268             }
269              
270             my @t = $conf->{node}->childNodes();
271              
272             # If we have 1 child and that child is text then use the content
273             # of the child as our value we must also be at the end of the tree
274             if ( scalar(@t) == 1
275             && $conf->{node}->childNodes()->get_node(1)->nodeType() == 3 )
276             {
277              
278             #return;
279             my $value = $conf->{node}->childNodes()->get_node(1)->textContent();
280             carp "ADDING : " . $conf->{node}->nodeName . " Value: $value"
281             if $DEBUG;
282             $self->{sdb}->add_elem(
283             name => $conf->{node}->nodeName,
284             attributes => \%attribs,
285             parent => $parent,
286             value => $value,
287             type => $type,
288             );
289              
290             carp "END OF THE LINE BUDDY!" if $DEBUG;
291             } else {
292             carp "- FOUND CHILD NODES" if $DEBUG;
293              
294             # Add it - it's a node without a value, but has child nodes
295             my $obj;
296             if ( defined $parent ) {
297             carp "ADDING ELEMENT WITH PARENT: " . $conf->{node}->nodeName
298             if $DEBUG;
299              
300             # Add with the parent
301             $obj = $self->{sdb}->add_elem(
302             name => $conf->{node}->nodeName,
303             attributes => \%attribs,
304             parent => $parent,
305             );
306             } else {
307             carp "ADDING ELEMENT WITH NO PARENT: " . $conf->{node}->nodeName
308             if $DEBUG;
309              
310             # Add with the parent
311             # Add without parent
312             $obj = $self->{sdb}->add_elem(
313             name => $conf->{node}->nodeName,
314             attributes => \%attribs,
315             );
316             }
317              
318             foreach my $node ( $conf->{node}->childNodes() ) {
319              
320             # process each child node as long as it's not
321             # a text node (type 3)
322             $self->_process_node(
323             { 'node' => $node,
324             'parent' => $obj,
325             }
326             );
327             }
328             }
329             }
330              
331             1;
332              
333             __END__