File Coverage

blib/lib/POE/Filter/XML/RPC/Value.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package POE::Filter::XML::RPC::Value;
2              
3 1     1   2443 use 5.010;
  1         4  
  1         41  
4 1     1   5 use warnings;
  1         2  
  1         27  
5 1     1   5 use strict;
  1         2  
  1         34  
6              
7 1     1   4 use base('POE::Filter::XML::Node', 'Exporter');
  1         2  
  1         878  
8             use Scalar::Util('looks_like_number', 'reftype');
9             use Regexp::Common('time');
10             use Hash::Util('fieldhash');
11              
12             our $VERSION = '0.04';
13              
14             use constant
15             {
16             'ARRAY' => 'array',
17             'BASE64' => 'base64',
18             'BOOL' => 'bool',
19             'DATETIME' => 'dateTime.iso8601',
20             'DOUBLE' => 'double',
21             'INT' => 'int',
22             'STRING' => 'string',
23             'STRUCT' => 'struct',
24             'DATA' => 'data',
25             'NAME' => 'name',
26             'VALUE' => 'value',
27             'MEMBER' => 'member',
28             };
29              
30             our @EXPORT= qw/ ARRAY BASE64 BOOL DATETIME DOUBLE INT STRING STRUCT /;
31              
32             sub new
33             {
34             my $class = shift(@_);
35             my $arg = shift(@_);
36             my $force_type = shift(@_);
37            
38             my $val = process($arg, $force_type);
39             bless($val, $class);
40              
41             $val->_type($force_type // determine_type($arg));
42             return $val;
43             }
44              
45             sub process
46             {
47             my ($arg, $force) = (shift(@_), shift(@_));
48            
49             my $val = __PACKAGE__->SUPER::new(+VALUE);
50            
51             given($force // determine_type($arg))
52             {
53             when(+ARRAY)
54             {
55             my $data = $val->appendChild(+ARRAY)->appendChild(+DATA);
56            
57             foreach(@$arg)
58             {
59             $data->appendChild(process($_));
60             }
61             }
62             when(+STRUCT)
63             {
64             my $struct = $val->appendChild(+STRUCT);
65              
66             while(my ($key, $val) = each %$arg)
67             {
68             my $member = $struct->appendChild(+MEMBER);
69             $member->appendChild(+NAME)->appendText($key);
70             $member->appendChild(process($val));
71             }
72             }
73             default
74             {
75             $val->appendChild($_)->appendText($arg);
76             }
77             }
78              
79             return $val;
80             }
81              
82             sub value()
83             {
84             my ($self, $arg, $force_type) = (shift(@_), shift(@_), shift(@_));
85            
86             if(defined($arg))
87             {
88             $self->removeChild($self->firstChild());
89             my $type = $force_type // determine_type($arg);
90             $self->appendChild($type)->appendText($arg);
91             $self->_type($type);
92             }
93             else
94             {
95             my $content = $self->findvalue('child::text()');
96             if(defined($content) && length($content))
97             {
98             return $content;
99             }
100             else
101             {
102             return node_to_value($self);
103             }
104             }
105             }
106              
107             sub node_to_value
108             {
109             my $node = shift(@_);
110            
111             my $content = $node->findvalue('child::text()');
112             return $content if defined($content) && length($content);
113              
114             my $val = $node->firstChild();
115             given($val->nodeName())
116             {
117             when(+STRUCT)
118             {
119             my $struct = {};
120             foreach($val->findnodes('child::member'))
121             {
122             $struct->{$_->findvalue('child::name/child::text()')} =
123             node_to_value(($_->findnodes('child::value'))[0]);
124             }
125              
126             return $struct;
127             }
128             when(+ARRAY)
129             {
130             my $array = [];
131              
132             foreach($val->findnodes('child::data/child::value'))
133             {
134             push(@$array, node_to_value($_));
135             }
136              
137             return $array;
138             }
139             default
140             {
141             return $val->findvalue('child::text()');
142             }
143             }
144             }
145              
146             sub type()
147             {
148             my $self = shift(@_);
149             if(!defined($self->_type()))
150             {
151             my $content = $self->findvalue('child::text()');
152            
153             if(defined($content) && length($content))
154             {
155             # string
156             $self->_type(+STRING);
157             return +STRING;
158             }
159            
160             my $determined = determine_type($self->value());
161             $self->_type($determined);
162             return $determined;
163             }
164             else
165             {
166             return $self->_type();
167             }
168             }
169              
170             sub _type()
171             {
172             my ($self, $arg) = (shift(@_), shift(@_));
173             fieldhash state %type;
174              
175             if(defined($arg))
176             {
177             $type{$self} = $arg;
178             }
179             else
180             {
181             return $type{$self};
182             }
183             }
184              
185             sub determine_type($)
186             {
187             my $arg = shift(@_);
188              
189             given($arg)
190             {
191             when(m@^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$@)
192             {
193             return +BASE64;
194             }
195             when(/^(?:1|0){1}$|^true$|^false$/i)
196             {
197             return +BOOL;
198             }
199             }
200              
201             if(looks_like_number($arg))
202             {
203             if($arg =~ /\.{1}/)
204             {
205             return +DOUBLE;
206             }
207             else
208             {
209             return +INT;
210             }
211             }
212              
213             given(reftype($arg) // '')
214             {
215             when('ARRAY')
216             {
217             return +ARRAY;
218             }
219             when('HASH')
220             {
221             return +STRUCT;
222             }
223             default
224             {
225             state $iso = "$RE{'time'}{'iso'}";
226             if($arg =~ /$iso/)
227             {
228             return +DATETIME;
229             }
230             return +STRING;
231             }
232             }
233             }
234              
235             =pod
236              
237             =head1 NAME
238              
239             POE::Filter::XML::RPC::Value - Represents XMLRPC value types
240              
241             =head1 SYNOPSIS
242              
243             use 5.010;
244             use POE::Filter::XML::RPC::Value;
245              
246             my $val1 = POE::Filter::XML::RPC::Value->new([qw/one two three/]);
247             my $val2 = POE::Filter::XML::RPC::Value->new('1A2B3C==');
248             my $val3 = POE::Filter::XML::RPC::Value->new(1);
249             my $val4 = POE::Filter::XML::RPC::Value->new('19980717T14:08:55');
250             my $val5 = POE::Filter::XML::RPC::Value->new(1.00);
251             my $val6 = POE::Filter::XML::RPC::Value->new(42);
252             my $val7 = POE::Filter::XML::RPC::Value->new('some text');
253             my $val8 = POE::Filter::XML::RPC::Value->new({'key' => 'val'});
254             my $val9 = POE::Filter::XML::RPC::Value->new(1234, +STRING);
255              
256             say $val1->type(); # array
257             say $val2->type(); # base64
258             say $val3->type(); # bool
259             say $val4->type(); # dateTime.iso8601
260             say $val5->type(); # double
261             say $val6->type(); # int
262             say $val7->type(); # string
263             say $val8->type(); # struct
264             say $val9->type(); # string
265              
266             =head1 DESCRIPTION
267              
268             POE::Filter::XML::RPC::Value does most of the automagical marshalling that is
269             expected when dealing with XMLRPC value types. Structs are converted to hashes.
270             Arrays are converted to arrays, etc. And it works both ways. So if passed a
271             complex, nested Perl data structure, it will Do The Right Thing.
272              
273             =head1 PUBLIC METHODS
274              
275             =over 4
276              
277             =item new()
278              
279             new() accepts a scalar, and an optional type argument to use to construct the
280             the value. See EXPORTED CONSTANTS for acceptable types.
281              
282             The scalar provided can contain a string, hash or array reference, or may be a
283             numerical value. Scalar::Util is put to good use to determine what kind of
284             value was passed, and some good old fashion regular expression magic thrown at
285             it to see if it is a ISO 8601 datetime, or perhaps BASE64 encoded data.
286              
287             If the type determination turns out wrong for whatever reason, a type argument
288             can also be supplied to force a particular type.
289              
290             =item type()
291              
292             type() returns what type of value is represented. For values received from some
293             where else, it will spelunk into the data and determine the type using the same
294             heuristics used for construction. Will be one of the EXPORTED CONSTANTS
295              
296             =item value()
297              
298             value() returns the data properly marshalled into whatever Perl type is valid.
299             Arrays and Structs will be marshalled into their Perl equivalent and returned
300             as a reference to that type, while all other types will be return as a scalar.
301              
302             value() can also take a new value to replace the old one. It can even be of a
303             different type. And again if the heuristics for your data don't do the right
304             thing, you can also provide a second argument of what type the data should be.
305              
306             =back
307              
308             =head1 PRIVATE METHODS
309              
310             =over 4
311              
312             =item _type()
313              
314             _type() stores the cached type of the current Value with examining the content
315             to determine if that still holds true. Use with care.
316              
317             =back
318              
319             =head1 PROTECTED FUNCTIONS
320              
321             These are not exported or available for export at all.
322              
323             =over 4
324              
325             =item determine_type
326              
327             This function contains the logic behind the type guessing heuristic. Simply
328             supply whatever scalar you want to it and it will return one of the EXPORTED
329             CONSTANTS.
330              
331             =item node_to_value
332              
333             This function takes a POE::Filter::XML::Node of the following structure:
334              
335             <value>
336             <!-- some other stuff in here, could be <array/>,<struct/>, etc -->
337             </value>
338              
339             then marshals and returns that data to you.
340              
341             =back
342              
343             =head1 EXPORTED CONSTANTS
344              
345             Here are the exported constants and their values. Note that the values for
346             these constants are the same as valid tag names for value types in XMLRPC.
347              
348             +ARRAY => 'array',
349             +BASE64 => 'base64',
350             +BOOL => 'bool',
351             +DATETIME => 'dateTime.iso8601',
352             +DOUBLE => 'double',
353             +INT => 'int',
354             +STRING => 'string',
355             +STRUCT => 'struct',
356              
357             =head1 NOTES
358              
359             Value is actually a subclass of POE::Filter::XML::Node and so all of its
360             methods, including XML::LibXML::Element's, are available for use. This could
361             ultimately be useful to avoid marshalling all of the data out of the Node and
362             instead apply an XPATH expression to target specifically what is desired deep
363             within a nested structure.
364              
365             =head1 AUTHOR
366              
367             Copyright 2009 Nicholas Perez.
368             Licensed and distributed under the GPL.
369              
370             =cut
371              
372             1;