File Coverage

lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package SOAP::WSDL::XSD::Typelib::ComplexType;
3 12     12   370229 use strict;
  12         31  
  12         609  
4 12     12   66 use warnings;
  12         23  
  12         404  
5 12     12   61 use Carp;
  12         18  
  12         864  
6 12     12   13296 use SOAP::WSDL::XSD::Typelib::Builtin;
  12         39  
  12         424  
7 12     12   71 use Scalar::Util qw(blessed);
  12         24  
  12         1028  
8 12     12   15492 use Data::Dumper;
  12         673260  
  12         1189  
9             require Class::Std::Fast::Storable;
10 12     12   47114 use Class::Load ();
  0            
  0            
11              
12             use base qw(SOAP::WSDL::XSD::Typelib::Builtin::anyType);
13              
14             use version; our $VERSION = qv('3.001');
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             return if exists $STORABLE_METHODS{$_};
54              
55             my ($self, $ident, @args_from) = @_;
56             my $class = ref $self || $self or die "Cannot call AUTOMETHOD as function";
57              
58             # Test whether we're called from ->can()
59             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             return if $caller[3] ne 'Class::Std::AUTOLOAD';
66              
67             confess "Can't locate object method \"$_\" via package \"$class\". \n"
68             . "Valid methods are: "
69             . join(', ', map { ("get_$_" , "set_$_") } keys %{ $ATTRIBUTES_OF{ $class } })
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             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             if ($_[1]) {
84             return $xml_attr_of{ ${$_[0]} } = $class->new($_[1]);
85             }
86             return $xml_attr_of{ ${$_[0]} } if exists $xml_attr_of{ ${$_[0]} };
87             return $xml_attr_of{ ${$_[0]} } = $class->new();
88             }
89              
90             sub serialize_attr {
91             return q{} if not $xml_attr_of{ ${ $_[0] } };
92             return $xml_attr_of{ ${ $_[0] } }->serialize();
93             }
94              
95             # TODO: are complextypes are always true ?
96             sub as_bool :BOOLIFY { 1 }
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             my $attributes_ref = $ATTRIBUTES_OF{ ref $_[0] };
105              
106             my $hash_of_ref = {};
107             if ($_[0]->isa('SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType')) {
108             $hash_of_ref->{ value } = $_[0]->get_value();
109             }
110             else {
111             foreach my $attribute (keys %{ $attributes_ref }) {
112             next if not defined $attributes_ref->{ $attribute }->{ ${ $_[0] } };
113             my $value = $attributes_ref->{ $attribute }->{ ${ $_[0] } };
114              
115             $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             $_->isa('SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType')
123             ? $_->get_value()
124             : $_->as_hash_ref($_[1])
125             } @{ $value }
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             return $hash_of_ref if $_[1] or $AS_HASH_REF_WITHOUT_ATTRIBUTES;
134              
135              
136             if (exists $xml_attr_of{ ${ $_[0] } }) {
137             $hash_of_ref->{ xmlattr } = $xml_attr_of{ ${ $_[0] } }->as_hash_ref();
138             }
139              
140             return $hash_of_ref;
141             }
142              
143             # we store per-class elements.
144             # call as __PACKAGE__->_factory
145             sub _factory {
146             my $class = shift;
147             $ELEMENTS_FROM{ $class } = shift;
148             $ATTRIBUTES_OF{ $class } = shift;
149             $CLASSES_OF{ $class } = shift;
150             $NAMES_OF{ $class } = shift;
151              
152             no strict qw(refs);
153             no warnings qw(redefine);
154              
155             while (my ($name, $attribute_ref) = each %{ $ATTRIBUTES_OF{ $class } } ) {
156             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             or eval { Class::Load::load_class $type }
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             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             my $method_name = $name;
207             $method_name =~s{[\.\-]}{_}xmsg;
208             *{ "$class\::set_$method_name" } = sub {
209             if (not $#_) {
210             delete $attribute_ref->{ ${ $_[0] } };
211             return;
212             };
213             my $is_ref = ref $_[1];
214             $attribute_ref->{ ${ $_[0] } } = ($is_ref)
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             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             } @{ $_[1] }
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             return;
239             };
240              
241             *{ "$class\::add_$method_name" } = sub {
242             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             if (not exists $attribute_ref->{ ${ $_[0]} }) {
248             $attribute_ref->{ ${ $_[0]} } = $_[1];
249             return;
250             }
251              
252             if (not ref $attribute_ref->{ ${ $_[0]} } eq 'ARRAY') {
253             # second call: listify previous value if it's no list and add current
254             $attribute_ref->{ ${ $_[0]} } = [ $attribute_ref->{ ${ $_[0]} }, $_[1] ];
255             return;
256             }
257              
258             # second and following: add to list
259             push @{ $attribute_ref->{ ${ $_[0]} } }, $_[1];
260             return;
261             };
262             }
263              
264             # TODO Could be moved as normal method into base class, e.g. here.
265             # Hmm. let's see...
266             *{ "$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             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             if (exists $_[1]->{xmlattr}) { # $args_of->{xmlattr}
288             $self->attr(delete $_[1]->{xmlattr});
289             }
290              
291             # iterate over keys of arguments
292             # and call set appropriate field in clase
293             map { ($ATTRIBUTES_OF{ $class }->{ $_ })
294             ? do {
295             my $method = "set_$_";
296              
297             # keep in sync with Generator::Template::Plugin::XSD - maybe use
298             # function to allow substituting via symbol table...
299             $method =~s{[\.\-]}{_}xmsg;
300              
301             $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             : do {
308             croak "unknown field $_ in $class. Valid fields are:\n"
309             . join(', ', @{ $ELEMENTS_FROM{ $class } }) . "\n"
310             . "Structure given:\n" . Dumper @_ };
311             } keys %{ $_[1] }; # %$args_of;
312             return $self;
313             };
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             *{ "$class\::_serialize" } = sub {
324             my $ident = ${ $_[0] };
325             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             return \join q{} , map {
330             my $element = $ATTRIBUTES_OF{ $class }->{ $_ }->{ $ident };
331              
332             # do we have some content
333             if (defined $element) {
334             $element = [ $element ] if not ref $element eq 'ARRAY';
335             # use || $_; for backward compatibility
336             my $name = $NAMES_OF{$class}->{$_} || $_;
337             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             if ( $_->isa( 'SOAP::WSDL::XSD::Typelib::Element' ) ) {
343             # serialize elements of different namespaces
344             # with namespace declaration
345             ($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             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             if (
363             exists $option_ref->{ xmlns_stack }
364             && (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             xmlns => $option_ref->{ xmlns_stack }->[-1],
369             %{ $option_ref } })
370             , $_->serialize($option_ref)
371             , $_->end_tag({ name => $name , %{ $option_ref } });
372             }
373             else {
374             join q{}, $_->start_tag({ name => $name , %{ $option_ref } })
375             , $_->serialize($option_ref)
376             , $_->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             my $set_xmlns = delete $option_ref->{xmlns};
390            
391             # serialize start tag with xmlns="" if out parent
392             # did not do that
393             join q{}, $_->start_tag({
394             name => $name,
395             %{ $option_ref },
396             (! defined $set_xmlns)
397             ? (xmlns => "")
398             : ()
399             })
400             # add xmlns = "" to child serialize options
401             # to avoid putting xmlns="" everywhere
402             , $_->serialize({ %{$option_ref}, xmlns => "" })
403             , $_->end_tag({ name => $name , %{ $option_ref } });
404             }
405             }
406             } @{ $element }
407             }
408             else {
409             q{};
410             }
411             } (@{ $ELEMENTS_FROM{ $class } });
412             };
413              
414             # put hidden complex serializer into class
415             # ... but not for AttributeSet classes
416             if ( ! $class->isa('SOAP::WSDL::XSD::Typelib::AttributeSet')) {
417             *{ "$class\::serialize" } = \&__serialize_complex;
418             };
419             }
420              
421             sub _set_element_form_qualified {
422             $ELEMENT_FORM_QUALIFIED_OF{ $_[0] } = $_[1];
423             }
424              
425             # Just as fallback: return no attribute set class as default.
426             # Subclasses may override
427             sub __get_attr_class {};
428              
429             # hidden complex serializer
430             sub __serialize_complex {
431             # we work on @_ for performance.
432             $_[1] ||= {}; # $option_ref
433            
434             push @{ $_[1]->{ xmlns_stack } }, $_[0]->get_xmlns();
435            
436             # get content first (pass by reference to avoid copying)
437             my $content_ref = $_[0]->_serialize($_[1]); # option_ref
438              
439             pop @{ $_[1]->{ xmlns_stack } };
440              
441             # do we have a empty element ?
442             return $_[0]->start_tag({ %{ $_[1] }, empty => 1 })
443             if not length ${ $content_ref };
444              
445             return join q{}, $_[0]->start_tag($_[1]), ${ $content_ref }, $_[0]->end_tag();
446             }
447              
448             sub get_xmlns {
449             return q{}
450             }
451              
452             1;
453              
454             __END__