File Coverage

lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm
Criterion Covered Total %
statement 170 179 94.9
branch 75 84 89.2
condition 18 26 69.2
subroutine 24 25 96.0
pod 4 6 66.6
total 291 320 90.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package SOAP::WSDL::XSD::Typelib::ComplexType;
3 12     12   34012 use strict;
  12         12  
  12         311  
4 12     12   36 use warnings;
  12         16  
  12         201  
5 12     12   32 use Carp;
  12         12  
  12         602  
6 12     12   3608 use SOAP::WSDL::XSD::Typelib::Builtin;
  12         22  
  12         264  
7 12     12   40 use Scalar::Util qw(blessed);
  12         10  
  12         536  
8 12     12   5938 use Data::Dumper;
  12         51289  
  12         691  
9             require Class::Std::Fast::Storable;
10 12     12   4687 use Class::Load ();
  12         220900  
  12         296  
11              
12 12     12   68 use base qw(SOAP::WSDL::XSD::Typelib::Builtin::anyType);
  12         13  
  12         4480  
13              
14             our $VERSION = 3.003;
15              
16             # remove in 2.1
17             our $AS_HASH_REF_WITHOUT_ATTRIBUTES = 0;
18              
19             my %ELEMENT_FORM_QUALIFIED_OF; # denotes whether elements are qualified
20             my %ELEMENTS_FROM; # order of elements in a class
21             my %ATTRIBUTES_OF; # references to value hashes
22             my %CLASSES_OF; # class names of elements in a class
23             my %NAMES_OF; # XML names of elements in a class
24              
25              
26             # XML Attribute handling
27             my %xml_attr_of :ATTR();
28              
29             # Namespace handling
30             my %xmlns_of :ATTR();
31              
32             # don't you ever dare to use this !
33             our $___attributes_of_ref = \%ATTRIBUTES_OF;
34             our $___xml_attribute_of_ref = \%xml_attr_of;
35              
36             # STORABLE_ methods for supporting Class::Std::Fast::Storable.
37             # We could also handle them via AUTOMETHOD,
38             # but AUTOMETHOD should always croak...
39             # Actually, AUTOMETHOD is faster (~1%) if Class::Std::Fast is loaded
40             # properly, and slower (~10%) if not.
41             # Hmmm. Trade 1% for 10?
42              
43             my %STORABLE_METHODS = (
44             STORABLE_freeze_pre => undef,
45             STORABLE_freeze_post => undef,
46             STORABLE_thaw_pre => undef,
47             STORABLE_thaw_post => undef,
48             );
49              
50             # for error reporting. Eases working with data objects...
51             sub AUTOMETHOD {
52             # return before unpacking @_ for speed reasons
53 16 50   16 0 3840 return if exists $STORABLE_METHODS{$_};
54              
55 16         19 my ($self, $ident, @args_from) = @_;
56 16 100 100     48 my $class = ref $self || $self or die "Cannot call AUTOMETHOD as function";
57              
58             # Test whether we're called from ->can()
59 15         50 my @caller = caller(1);
60              
61             # return if not called by AUTOLOAD - caller must be something like can()
62             # Unfortunately we cannot test for "UNIVERSAL::can", as it gets overwritten
63             # by both Class::Std and Class::Std::Fast, and we don't know the loading
64             # order (Class::Std::Fast should be loaded before for maximum speedup)
65 15 100       43 return if $caller[3] ne 'Class::Std::AUTOLOAD';
66              
67 3         153 confess "Can't locate object method \"$_\" via package \"$class\". \n"
68             . "Valid methods are: "
69 3         10 . join(', ', map { ("get_$_" , "set_$_") } keys %{ $ATTRIBUTES_OF{ $class } })
  3         14  
70             . "\n"
71             }
72              
73             sub attr {
74             # We're working on @_ for speed.
75             # Normally, the first line would look like this:
76             # my $self = shift;
77              
78 11 100   11 1 1102 my $class = $_[0]->__get_attr_class()
79             or return;
80              
81             # pass arguments to attributes constructor (if any);
82             # lets attr($foo) work as setter
83 10 100       40 if ($_[1]) {
84 3         10 return $xml_attr_of{ ${$_[0]} } = $class->new($_[1]);
  3         12  
85             }
86 7 100       10 return $xml_attr_of{ ${$_[0]} } if exists $xml_attr_of{ ${$_[0]} };
  5         18  
  7         148  
87 2         7 return $xml_attr_of{ ${$_[0]} } = $class->new();
  2         8  
88             }
89              
90             sub serialize_attr {
91 10 100   10 1 11 return q{} if not $xml_attr_of{ ${ $_[0] } };
  10         93  
92 3         5 return $xml_attr_of{ ${ $_[0] } }->serialize();
  3         10  
93             }
94              
95             # TODO: are complextypes are always true ?
96 12     12 1 58 sub as_bool :BOOLIFY { 1 }
  12     7   19  
  12         78  
  7         140  
97              
98             sub as_hash_ref {
99             # we're working on $_[0] for speed (as always...)
100             #
101             # Normally the first line would read:
102             # my ($self, $ignore_attributes) = @_;
103             #
104 21     21 1 2070 my $attributes_ref = $ATTRIBUTES_OF{ ref $_[0] };
105              
106 21         26 my $hash_of_ref = {};
107 21 100       289 if ($_[0]->isa('SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType')) {
108 4         9 $hash_of_ref->{ value } = $_[0]->get_value();
109             }
110             else {
111 17         17 foreach my $attribute (keys %{ $attributes_ref }) {
  17         41  
112 18 100       89 next if not defined $attributes_ref->{ $attribute }->{ ${ $_[0] } };
  18         37  
113 17         20 my $value = $attributes_ref->{ $attribute }->{ ${ $_[0] } };
  17         16  
114              
115 10 100       43 $hash_of_ref->{ $attribute } = blessed $value
116             ? $value->isa('SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType')
117             ? $value->get_value()
118             : $value->as_hash_ref($_[1])
119             : ref $value eq 'ARRAY'
120             ? [
121             map {
122 5         8 $_->isa('SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType')
123             ? $_->get_value()
124             : $_->as_hash_ref($_[1])
125 17 100       80 } @{ $value }
    50          
    100          
126             ]
127             : die "Neither blessed obj nor list ref";
128             };
129             }
130              
131             # $AS_HASH_REF_WITHOUT_ATTRIBUTES is deprecated by NOW and will be removed
132             # in 2.1
133 21 100 66     117 return $hash_of_ref if $_[1] or $AS_HASH_REF_WITHOUT_ATTRIBUTES;
134              
135              
136 18 100       17 if (exists $xml_attr_of{ ${ $_[0] } }) {
  18         38  
137 5         5 $hash_of_ref->{ xmlattr } = $xml_attr_of{ ${ $_[0] } }->as_hash_ref();
  5         15  
138             }
139              
140 18         45 return $hash_of_ref;
141             }
142              
143             # we store per-class elements.
144             # call as __PACKAGE__->_factory
145             sub _factory {
146 32     32   9138 my $class = shift;
147 32         90 $ELEMENTS_FROM{ $class } = shift;
148 32         38 $ATTRIBUTES_OF{ $class } = shift;
149 32         39 $CLASSES_OF{ $class } = shift;
150 32         37 $NAMES_OF{ $class } = shift;
151              
152 12     12   5014 no strict qw(refs);
  12         18  
  12         279  
153 12     12   41 no warnings qw(redefine);
  12         12  
  12         12242  
154              
155 32         32 while (my ($name, $attribute_ref) = each %{ $ATTRIBUTES_OF{ $class } } ) {
  95         265  
156 65 100       135 my $type = $CLASSES_OF{ $class }->{ $name }
157             or croak "No class given for $name";
158              
159             # require all types here
160             Class::Load::is_class_loaded($type)
161 64 100 100     134 or eval { Class::Load::load_class $type }
  2         83  
162             or croak $@;
163              
164             # check now, so we don't need to do it later.
165             # $is_list is used in the methods created. Filling it now means
166             # we don't have to check it every time the method is called, but
167             # can just use $is_list, which will hold the value assigned to
168             # it when the method was created.
169 63         2472 my $is_list = $type->isa('SOAP::WSDL::XSD::Typelib::Builtin::list');
170              
171             # The set_$name method below looks rather weird,
172             # but is optimized for performance.
173             #
174             # We could use sub calls for sure, but these are much slower. And
175             # the logic is not that easy:
176             #
177             # we accept:
178             # a) objects
179             # b) scalars
180             # c) list refs
181             # d) hash refs
182             # e) mixed stuff of all of the above, so we have to set our child to
183             # a) value if it's an object
184             # b) New object of expected class with value for simple values
185             # c 1) New object with value for list values and list type
186             # c 2) List ref of new objects with value for list values and
187             # non-list type
188             # c + e 1) List ref of objects for list values (list of objects)
189             # and non-list type
190             # c + e 2) List ref of new objects for list values (list of hashes)
191             # and non-list type where the hash ref is passed to new as
192             # argument
193             # d) New object with values passed to new for HASH references
194             #
195             # We throw an error on
196             # a) list refs of list refs - don't know what to do with this (maybe
197             # use for lists of list types ?)
198             # b) wrong object types
199             # c) non-blessed non-ARRAY/HASH references - if you can define semantics
200             # for GLOB or SCALAR references, feel free to add them.
201             # d) we should also die for non-blessed non-ARRAY/HASH references in
202             # lists but don't do yet - oh my !
203              
204             # keep in sync with Generator::Template::Plugin::XSD - maybe use
205             # function to allow substituting via symbol table...
206 63         57 my $method_name = $name;
207 63         286 $method_name =~s{[\.\-]}{_}xmsg;
208 63         225 *{ "$class\::set_$method_name" } = sub {
209 31 100   31   399 if (not $#_) {
210 1         2 delete $attribute_ref->{ ${ $_[0] } };
  1         7  
211 1         122 return;
212             };
213 30         43 my $is_ref = ref $_[1];
214 28 100       1073 $attribute_ref->{ ${ $_[0] } } = ($is_ref)
  14 50       76  
    100          
215             ? ($is_ref eq 'ARRAY')
216             ? $is_list # remembered from outside closure
217             ? $type->new({ value => $_[1] }) # it's a list element - can take list ref as value
218             : [ map { # it's not a list element - set value to list of objects
219 7         13 ref $_
220             ? ref $_ eq 'HASH'
221             ? $type->new($_)
222             : ref $_ eq $type
223             ? $_
224             : croak "cannot use " . ref($_) . " reference as value for $name - $type required"
225             : $type->new({ value => $_ })
226 30 50 66     241 } @{ $_[1] }
    100          
    100          
    100          
    50          
    100          
227             ]
228             : $is_ref eq 'HASH'
229             ? $type->new( $_[1] )
230             # neither ARRAY nor HASH - probably an object... -
231             # do we need to test for it being blessed?
232             : blessed $_[1] && $_[1]->isa($type) # of required type ?
233             ? $_[1] # use it
234             : die croak "cannot use $is_ref reference as value for $name - $type required"
235              
236             # not $is_ref
237             : defined $_[1] ? $type->new({ value => $_[1] }) : () ;
238 28         63 return;
239 63         246 };
240              
241 63         221 *{ "$class\::add_$method_name" } = sub {
242 12 100   12   436 warn "attempting to add empty value to " . ref $_[0]
243             if not defined $_[1];
244              
245             # first call
246             # test for existance, not for definedness
247 12 100       54 if (not exists $attribute_ref->{ ${ $_[0]} }) {
  12         110  
248 2         4 $attribute_ref->{ ${ $_[0]} } = $_[1];
  2         4  
249 2         5 return;
250             }
251              
252 10 100       10 if (not ref $attribute_ref->{ ${ $_[0]} } eq 'ARRAY') {
  10         24  
253             # second call: listify previous value if it's no list and add current
254 2         2 $attribute_ref->{ ${ $_[0]} } = [ $attribute_ref->{ ${ $_[0]} }, $_[1] ];
  2         4  
  2         4  
255 2         3 return;
256             }
257              
258             # second and following: add to list
259 8         9 push @{ $attribute_ref->{ ${ $_[0]} } }, $_[1];
  8         5  
  8         19  
260 8         10 return;
261 63         137 };
262             }
263              
264             # TODO Could be moved as normal method into base class, e.g. here.
265             # Hmm. let's see...
266 30         88 *{ "$class\::new" } = sub {
267             # We're working on @_ for speed.
268             # Normally, the first line would look like this:
269             # my ($class, $args_of) = @_;
270             #
271             # The hanging side comment show you what would be there, then.
272              
273             # Read as:
274             # my $self = bless \(my $o = Class::Std::Fast::ID()), $class;
275 34     34   10377 my $self = bless \(my $o = Class::Std::Fast::ID()), $_[0];
276              
277             # Set attributes if passed via { xmlattr => \%attributes }
278             #
279             # This works just because
280             # a) xmlattr cannot be used as valid XML identifier (it starts
281             # with "xml" which is banned by the XML schema standard)
282             # b) $o->attr($attribute_ref) passes $attribute_ref to the
283             # attribute object's constructor
284             # c) we are in the object's constructor here (which means that)
285             # no attributes object can have been legally constructed
286             # before.
287 34 100       186 if (exists $_[1]->{xmlattr}) { # $args_of->{xmlattr}
288 1         5 $self->attr(delete $_[1]->{xmlattr});
289             }
290              
291             # iterate over keys of arguments
292             # and call set appropriate field in clase
293 34         80 map { ($ATTRIBUTES_OF{ $class }->{ $_ })
294             ? do {
295 28         44 my $method = "set_$_";
296              
297             # keep in sync with Generator::Template::Plugin::XSD - maybe use
298             # function to allow substituting via symbol table...
299 28         48 $method =~s{[\.\-]}{_}xmsg;
300              
301 28         243 $self->$method( $_[1]->{ $_ } ); # ( $args_of->{ $_ } );
302             }
303             : $_ =~ m{ \A # beginning of string
304             xmlns # xmlns
305             }xms # get_elements is inlined for performance.
306             ? ()
307 31 100       89 : do {
    100          
308 2         11 croak "unknown field $_ in $class. Valid fields are:\n"
309 2         5 . join(', ', @{ $ELEMENTS_FROM{ $class } }) . "\n"
310             . "Structure given:\n" . Dumper @_ };
311 34         34 } keys %{ $_[1] }; # %$args_of;
312 30         64 return $self;
313 30         117 };
314              
315             # this _serialize method works fine for and
316             # complextypes, as well as for or
317             # , and attribute sets.
318             #
319             # But what about choice, extension ?
320             #
321             # Triggers XML attribute serialization if the options hash ref contains
322             # a attr element with a true value.
323 30         84 *{ "$class\::_serialize" } = sub {
324 27     27   26 my $ident = ${ $_[0] };
  27         218  
325 27         35 my $option_ref = $_[1];
326            
327             # return concatenated return value of serialize call of all
328             # elements retrieved from get_elements expanding list refs.
329 33         57 return \join q{} , map {
330 27         54 my $element = $ATTRIBUTES_OF{ $class }->{ $_ }->{ $ident };
331              
332             # do we have some content
333 33 100       59 if (defined $element) {
334 29 100       78 $element = [ $element ] if not ref $element eq 'ARRAY';
335             # use || $_; for backward compatibility
336 29   66     71 my $name = $NAMES_OF{$class}->{$_} || $_;
337 29         56 my $target_namespace = $_[0]->get_xmlns();
338             map {
339             # serialize element elements with their own serializer
340             # but name them like they're named here.
341             # TODO: check. element ref="" has a name???
342 51 100       191 if ( $_->isa( 'SOAP::WSDL::XSD::Typelib::Element' ) ) {
  29         32  
343             # serialize elements of different namespaces
344             # with namespace declaration
345 13 100       32 ($target_namespace ne $_->get_xmlns())
346             ? $_->serialize({ name => $name, qualified => 1 })
347             : $_->serialize({ name => $name });
348             }
349             # serialize complextype elments (of other types) with their
350             # serializer, but add element tags around.
351             else {
352             # default for undef is true
353 38 50 33     83 if (! defined $ELEMENT_FORM_QUALIFIED_OF{ $class }
354             or $ELEMENT_FORM_QUALIFIED_OF{ $class }
355             ) {
356             # handle types from different namespaces
357             #
358             # serialize with last namespace put on stack
359             # if the last namespace is a change from the
360             # before-last
361             #
362 38 50 66     71 if (
      33        
363             exists $option_ref->{ xmlns_stack }
364 34         104 && (scalar @{ $option_ref->{ xmlns_stack } } >= 2)
365             && ($option_ref->{ xmlns_stack }->[-1] ne $option_ref->{ xmlns_stack }->[-2])) {
366             # warn "New namespace: ", $option_ref->{ xmlns_stack }->[-1];
367             join q{}, $_->start_tag({ name => $name ,
368 0         0 xmlns => $option_ref->{ xmlns_stack }->[-1],
369 0         0 %{ $option_ref } })
370             , $_->serialize($option_ref)
371 0         0 , $_->end_tag({ name => $name , %{ $option_ref } });
372             }
373             else {
374 38         139 join q{}, $_->start_tag({ name => $name , %{ $option_ref } })
  38         87  
375             , $_->serialize($option_ref)
376 38         36 , $_->end_tag({ name => $name , %{ $option_ref } });
377             }
378             }
379             else {
380             # in elementFormDefault="unqualified" mode,
381             # the serialize method has to set
382             # xmnlns="" on all elements inside a ComplexType
383             #
384             # Other serializers usually use prefixes
385             # for "unqualified" and just omit all prefixes
386             # for inner elements
387            
388             # check whether we "had" a xmlns around
389 0         0 my $set_xmlns = delete $option_ref->{xmlns};
390            
391             # serialize start tag with xmlns="" if out parent
392             # did not do that
393 0         0 join q{}, $_->start_tag({
394             name => $name,
395 0         0 %{ $option_ref },
396             (! defined $set_xmlns)
397             ? (xmlns => "")
398             : ()
399             })
400             # add xmlns = "" to child serialize options
401             # to avoid putting xmlns="" everywhere
402 0         0 , $_->serialize({ %{$option_ref}, xmlns => "" })
403 0 0       0 , $_->end_tag({ name => $name , %{ $option_ref } });
404             }
405             }
406 29         28 } @{ $element }
407             }
408             else {
409 4         10 q{};
410             }
411 27         33 } (@{ $ELEMENTS_FROM{ $class } });
412 30         130 };
413              
414             # put hidden complex serializer into class
415             # ... but not for AttributeSet classes
416 30 100       256 if ( ! $class->isa('SOAP::WSDL::XSD::Typelib::AttributeSet')) {
417 25         131 *{ "$class\::serialize" } = \&__serialize_complex;
  25         109  
418             };
419             }
420              
421             sub _set_element_form_qualified {
422 0     0   0 $ELEMENT_FORM_QUALIFIED_OF{ $_[0] } = $_[1];
423             }
424              
425             # Just as fallback: return no attribute set class as default.
426             # Subclasses may override
427 1     1   6 sub __get_attr_class {};
428              
429             # hidden complex serializer
430             sub __serialize_complex {
431             # we work on @_ for performance.
432 23   100 23   1619 $_[1] ||= {}; # $option_ref
433            
434 23         22 push @{ $_[1]->{ xmlns_stack } }, $_[0]->get_xmlns();
  23         99  
435            
436             # get content first (pass by reference to avoid copying)
437 23         66 my $content_ref = $_[0]->_serialize($_[1]); # option_ref
438              
439 23         25 pop @{ $_[1]->{ xmlns_stack } };
  23         39  
440              
441             # do we have a empty element ?
442 6         31 return $_[0]->start_tag({ %{ $_[1] }, empty => 1 })
  23         54  
443 23 100       20 if not length ${ $content_ref };
444              
445 17         67 return join q{}, $_[0]->start_tag($_[1]), ${ $content_ref }, $_[0]->end_tag();
  17         53  
446             }
447              
448             sub get_xmlns {
449 35     35 0 557 return q{}
450             }
451              
452             1;
453              
454             __END__