File Coverage

blib/lib/eBay/API/XML/BaseDataType.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             ###############################################################################
4             #
5             # Module: ............... eBay/API/XML
6             # File: ................. BaseDataType.pm
7             # Original Author: ...... Milenko Milanovic
8             # Last Modified By: ..... Robert Bradley / Jeff Nokes
9             # Last Modified: ........ 03/06/2007 @ 16:47
10             #
11             ###############################################################################
12              
13             package eBay::API::XML::BaseDataType;
14              
15             #
16             # BUG FIXES:
17             # 1. XML:Simple parses empty tags into a hash reference?!?!
18             # converted into 'Location' => {}
19             # Changed _formatScalarIfNeeded to convert such tags into
20             # an empty string
21             #
22              
23 5     5   27 use strict;
  5         12  
  5         157  
24              
25 5     5   29 use Exporter;
  5         8  
  5         9357  
26             our @ISA = ('Exporter');
27              
28 5     5   55 use Data::Dumper;
  5         8  
  5         390  
29 5     5   55 use Scalar::Util 'blessed';
  5         9  
  5         472  
30 5     5   4699 use XML::Writer;
  5         75273  
  5         145  
31 5     5   2343 use XML::Simple;
  0            
  0            
32             use Encode;
33              
34             # During deserialization, convert boolean string values 'true' and 'false'
35             # to 1 and 0, respectively
36             use constant DESERIALIZE_BOOLEAN_STRING_TO_NUMBER => 1;
37              
38             # During deserialization, print to stdout object instantiantiation tree
39             use constant DISPLAY_RECURSION => 0;
40              
41             # $] variable returns perl version.
42             # For further description of $gsTurnOffUtf8OnSerializedString variable
43             # see comments at the place the variable is being used.
44             my $gsTurnOffUtf8OnSerializedString = ($] eq '5.008001');
45              
46             =head1 Subroutines:
47              
48             =cut
49              
50             =head2 new()
51              
52             =cut
53              
54             sub new {
55             my $classname = shift;
56             my $self = {};
57             bless ($self, $classname);
58             $self->_init( @_ );
59             return $self;
60             }
61              
62             sub _init {
63             my $self = shift;
64            
65             if ( @_ ) {
66             my %extra = @_;
67             @$self{ keys %extra } = values %extra;
68             }
69             }
70              
71              
72             =head2 serialize()
73              
74             =cut
75              
76             sub serialize {
77             my $self = shift;
78             my $tagName = shift;
79              
80             if (! defined $tagName ) {
81             $tagName = $self;
82             }
83              
84             my $strOutput = '';
85            
86             my $pXmlWriter = XML::Writer->new( OUTPUT => \$strOutput
87             # , DATA_MODE => 'true'
88             # , DATA_INDENT => 2
89             );
90             # add ''
91             # at the top of document, otherwise API call throws
92             # 'soapenv:Body must be terminated by the matching end-tag
93             # ""' error!!!
94             $pXmlWriter->xmlDecl("UTF-8");
95             my $isTopLevel = 1;
96             $self->_serializeInner( $tagName, $pXmlWriter, $isTopLevel);
97             $pXmlWriter->end();
98              
99             # I had to add this Encode stuff.
100             # Without this when I serialize datatypes containing Chinese signs
101             # I get '500 Wide character in syswrite' error on production machines
102             # running Perl 5.8.1 on RedHat 7.2
103             # This is not needed when running on Perl 5.8.7
104             # That is why I dynamicly determine whether to use 'Encode::_utf8_off'
105             # or not.
106             # mmilanovic, 06/11/2006, 20:00
107             if ($gsTurnOffUtf8OnSerializedString) {
108             Encode::_utf8_off($strOutput);
109             }
110              
111             return $strOutput;
112             }
113              
114             #
115             # protected
116             #
117             sub _serializeInner {
118              
119             my $self = shift;
120             my $tagName = shift;
121             my $pXmlWriter = shift;
122             my $isTopLevel = shift || 0;
123              
124             my $raProperties = $self->getPropertiesList();
125             my $raAttributes = $self->getAttributesList();
126            
127             # Do not serialize DataType property that has no
128             # properties (keys in its hash).
129             # We should not serialize a data type object whose tree is
130             # completely empty (we should not have empty tags in generated
131             # XML document.
132             # Well, I think verifing that an object has no properties is a
133             # good enough verification.
134             if ( ! $isTopLevel ) {
135             if ( (scalar (keys %$self)) == 0 ) {
136             return;
137             }
138             }
139              
140             # 1. serialize attributes
141             my %hAttr = ();
142             foreach my $raAttr (@$raAttributes) {
143             my $key = $raAttr->[0];
144             my $value = $self->{$key};
145             if ( defined $value ) {
146             $hAttr{$key} = $value;
147             }
148             }
149             $pXmlWriter->startTag($tagName, %hAttr);
150              
151             # 2. serialize properties
152             foreach my $prop (@$raProperties) {
153              
154             my $key = $prop->[0];
155             my $value = $self->{$key};
156            
157             if ( $key ne 'content' ) {
158             if ( defined $value ) {
159             if ( ref($value) eq 'ARRAY' ) {
160             foreach my $elem (@$value) {
161             _serializeValue( $key, $elem, $pXmlWriter);
162             }
163             } else {
164             _serializeValue( $key, $value, $pXmlWriter);
165             }
166             }
167             }
168             }
169              
170             # 3. serialize content
171             # It is possible that element has some primitive value and attributes
172             # In our case this happens to types like AmountType
173             my $content = $self->{'content'};
174             if ( defined $content ) {
175             $pXmlWriter->characters( $content );
176             }
177              
178             $pXmlWriter->endTag();
179             }
180              
181             #
182             # protected
183             #
184             sub _serializeValue {
185             my $key = shift;
186             my $value = shift;
187             my $pXmlWriter = shift;
188            
189             my $isRef = ref($value);
190             if ( $isRef ) {
191             $value->_serializeInner($key, $pXmlWriter);
192             } else {
193             $pXmlWriter->startTag( $key );
194             $pXmlWriter->characters( $value );
195             $pXmlWriter->endTag( );
196             }
197             }
198              
199              
200             =head2 deserialize()
201              
202             parameters:
203              
204             1. rhXmlSimple - Data structure created by parsing an XML str with
205             XML::Simple
206              
207             2. recursionLevel - Level of recursion, this is an optional argument
208             and it is used for debuging purposes only.
209             If constant DISPLAY_RECURSION is set to 1,
210             recursionLevel is used to pretty print the output
211             tracing the recursion.
212              
213             3. sRawXmlString - XML string used to set objects properties. The string is
214             first parsed by XML::Simple. Data structure that is
215             received after parsing is used to populate object's
216             properties (it overrides 'rhXmlSimple' parameter).
217             'sRawXmlString' parameter should be used for test
218             purposes only!!
219              
220             =cut
221              
222             sub deserialize {
223              
224             my $self = shift;
225             my %args = @_;
226              
227             my $raAttributes = $self->getAttributesList();
228             my $raProperties = $self->getPropertiesList();
229              
230             my $rhXmlSimple = $args { 'rhXmlSimple' };
231              
232             # 'sRawXmlStr' parameter overrides 'rhXmlSimple' parameter and
233             # it should be used for test purposes only
234             my $sRawXmlStr = $args { 'sRawXmlString' };
235             if ( defined $sRawXmlStr ) {
236             eval {
237             $rhXmlSimple = XMLin( $sRawXmlStr,
238             ,forcearray => []
239             ,keyattr => [] );
240             };
241             if ( $@ ) {
242             print $@ . "\n";
243             print "error during XML parsing, object "
244             . blessed ($self)
245             . " not properly deserilized\n";
246             # This piece of code is should be used for testing purposes ONLY!!
247             #
248             return;
249             }
250             }
251             ### print recursion is used only for debug purpose
252             my $recursionLevel = $args {'recursionLevel'};
253             if ( ! defined $recursionLevel ) {
254             $recursionLevel = 1;
255             }
256            
257              
258             #print 'Deser: ' . Dumper($self);
259             if ( DISPLAY_RECURSION == 1 ) {
260             my $ident = ($recursionLevel-1) * 2;
261             my $tmpStr = pack("c$ident", 32);
262             print $tmpStr . "Deserializing {$recursionLevel}-> "
263             . blessed($self) . "\n";
264             }
265             # 1. deserialize all properties
266              
267             foreach my $prop (@$raProperties) {
268              
269             my $key = $prop->[0];
270             my $typeNS = $prop->[1];
271             my $isArrayInMetaData = $prop->[2];
272             my $sPropertyPackageName = $prop->[3];
273             my $isComplexDataType = $prop->[4];
274              
275             my $value = $rhXmlSimple->{$key};
276              
277             if ( defined $value ) {
278              
279             my $isArrayInXml = (ref($value) eq 'ARRAY');
280             my $isArray = ($isArrayInXml || $isArrayInMetaData);
281              
282             ## AmountType might be both, scalar and DataType
283             # if it is a scalar process it like a scalar
284              
285             my $isScalar = isScalar ( $value, $isComplexDataType );
286              
287             if ( $isArray ) { ### 1. array
288              
289             my @inputArray = undef;
290            
291             if ( $isArrayInXml ) {
292            
293             @inputArray = @$value;
294             } elsif ($isArrayInMetaData ) { ### property is an array
295             ### but there is only
296             ### one element in that
297             ### array
298             @inputArray = ( $value );
299             }
300            
301             my @arr = ();
302             foreach my $elem ( @inputArray ) {
303              
304             if ( isScalar ($elem, $isComplexDataType ) ) {
305             ### 1.1 array of scalars
306             push @arr, _formatScalarIfNeeded ($elem, $typeNS);
307             } else {
308             ### 1.2 array of objects
309             my $pTmpType = $self->deserializeObject(
310             $sPropertyPackageName
311             , $elem
312             , $recursionLevel);
313              
314             push @arr, $pTmpType;
315             }
316             }
317             $self->{$key} = \@arr;
318              
319             } elsif ( $isScalar ) { ### 2. scalar
320              
321             $self->{$key} = _formatScalarIfNeeded( $value, $typeNS) ;
322             } else { ### 3. object
323              
324             my $pTmpType = $self->deserializeObject (
325             $sPropertyPackageName, $value
326             ,$recursionLevel);
327             $self->{$key} = $pTmpType;
328             }
329             }
330             }
331              
332              
333             # 2. get attributes
334             foreach my $raAttr (@$raAttributes) {
335             my $key = $raAttr->[0];
336             my $value = $rhXmlSimple->{$key};
337             if ( defined $value ) {
338             $self->{ $key } = $value;
339             }
340             }
341              
342             # 3. get content
343             # not needed, it will be read within 1.
344             #my $content = $rhXmlSimple->{'content'};
345             #if ( defined $content ) {
346             # $self->setValue($content);
347             #}
348             }
349              
350             =pod
351              
352             =head2 _formatScalarIfNeeded
353              
354             Access level: private
355              
356             1. 'xs:boolean'
357              
358             XML schema API calls for boolean values return 'true' and 'false'. During
359             deserilization we convert API boolean values to perl's boolean values:
360              
361             1 (true) and 0 (false).
362              
363             2. XML:Simple parses empty tags into a hash reference?!?!
364             converted into 'Location' => {}
365              
366             =cut
367              
368              
369             sub _formatScalarIfNeeded {
370             my $value = shift;
371             my $typeNS = shift;
372              
373             my $ret = $value;
374              
375             #
376             # XML:Simple parses empty tags into a hash reference?!?!
377             # examples:
378             #
379             #
380             #
381             # are parsed into 'Location' => {}
382             # test_value
383             # is parsed into 'Location' => 'test_value'
384             # That is why - if we have a scalar property
385             # and if XML::Simple parsed that property into a hash ref
386             # I am converting that property into an empty value.
387             #
388             if ( ref($value) eq 'HASH' ) {
389             if ( _isEmptyHash($value) ) {
390             $ret = '';
391             }
392             }
393             ## special handling for 'boolean' values
394             if ( DESERIALIZE_BOOLEAN_STRING_TO_NUMBER ) {
395             if ( $typeNS eq 'xs:boolean' ) {
396              
397             if ( $ret eq 'true' ) {
398             $ret = 1;
399             } else {
400             $ret = 0;
401             }
402             }
403             }
404            
405             return $ret;
406             }
407              
408              
409             sub deserializeObject {
410              
411             my $self = shift;
412             my $sObjectPackageName = shift;
413             my $rhInnerXmlSimple = shift;
414             my $recursionLevel = shift || 1;
415              
416             $recursionLevel++;
417              
418             # Instantiate a property that is an object
419             my $pTmpType = $sObjectPackageName->new();
420              
421             if ( ref($rhInnerXmlSimple) ne 'HASH' ) {
422              
423             # This is a HACK to support shorthand SimpleType data type 'value'
424             # initialization !!!!
425             # I consider it to be a dangerous hack but it seems that is working!!
426             # mmilanovic, 02/20/2006
427              
428             # This code is used by SimpleTypes
429             # (those data types have setValue property).
430             # Examples of such data types are: AmountType, UserIDType, ItemIDType.
431             # The code covers a case when a setter receives a scalar value
432             # instead of a real SimpleType data type object.
433             # In that case we assume that the scalar represents SimpleType
434             # data type value!!
435              
436             # This is covered in Unit test:
437             # testBaseDataType.pl
438             # section: Test Simple type deserilization
439             # test: 'partial OO test'
440             #
441            
442             if ( $pTmpType->can('setValue') ) {
443             my $value = $rhInnerXmlSimple;
444             $pTmpType->setValue($value);
445             }
446             } else {
447              
448             $pTmpType->deserialize('rhXmlSimple' => $rhInnerXmlSimple
449             ,'recursionLevel' => $recursionLevel);
450             }
451             return $pTmpType;
452             }
453              
454             sub isScalar {
455             my $value = shift;
456             my $isComplexDataType = shift;
457              
458             # Complex data is any DataType which is being generated
459             # and does not contain "::Enum::" in its full package name
460              
461             if ( $isComplexDataType ) {
462             return 0;
463             }
464              
465             return 1;
466             }
467              
468              
469             sub getPropertiesList {
470             return []; # reference to an array
471             }
472              
473             sub getAttributesList {
474             return []; # reference to an array
475             }
476              
477             =head2 convertArray_To_RefToArrayIfNeeded()
478              
479             Some DataType setters set reference to an array and this function is used in
480             such setters to convert passed paremeter to 'a reference to an array' if one
481             is not passed.
482              
483             Example:
484              
485             DataType: FeesType.pm has 'setFee' setter. This setter expects
486             a reference to an array to be passed.
487              
488             Still, we will support 3 types of parameters:
489              
490             1. parameter is a reference to an array, no conversion (just as should be)
491              
492             2. parameter is an array, convert it to a reference to an array
493              
494             3. parameter is a scalar, create an array with one element and
495             then create a reference to that array
496              
497             This method is used in setters that expect a parameter of
498             'a reference to an array' type
499              
500             The generated setters look like the following one:
501              
502             sub setProperty {
503             my $self = shift;
504             $self->{'property'} = $self->convertArray_To_RefToArrayIfNeeded(@_);
505             }
506              
507             =cut
508              
509             sub convertArray_To_RefToArrayIfNeeded {
510             my $self = shift;
511             my @arr = @_;
512             my $testElem = $_[0];
513              
514             my $ra = undef;
515             if ( defined $testElem ) { # if there is at least one parameter
516            
517             my $length = scalar @arr;
518             if ( $length == 1 ) { # there is only one parameter
519             my $elem = $arr[0];
520             if ( ref($elem) eq 'ARRAY' ) { # the parameter is an array ref
521             $ra = $elem;
522             } else {
523             $ra = [ $elem ]; # the parameter is a scalar
524             }
525             } else { # there are more than one parameter
526             $ra = \@arr; # consider that an array has been passed
527             }
528             }
529             return $ra;
530             }
531              
532             =pod
533              
534             =head2 _getDataTypeInstance()
535              
536             Used in getters that return a BaseDataType object.
537             If the object is not defined, it instantiate it.
538              
539             This allows the following syntax:
540              
541             my $sSellerId = $pItem->getSeller()->getUserID();
542              
543             Otherwise we would have to write something like this:
544              
545             my $pSeller = $pItem->getSeller();
546             if ( defined $pSeller ) {
547             $SellerId = $pSeller->getUserID();
548             }
549              
550             =cut
551              
552              
553              
554             sub _getDataTypeInstance {
555             my $self = shift;
556              
557             my $propertyName = shift;
558             my $propertyFullPackageName = shift;
559            
560             my $pObj = $self->{$propertyName};
561             if ( ! defined $pObj ) {
562             $pObj = $propertyFullPackageName->new();
563             $self->{$propertyName} = $pObj;
564             }
565             return $pObj;
566             }
567              
568             =head2 _getDataTypeArray()
569              
570             Used in getters that return an array.
571              
572             If the array is not defined instantiate it.
573              
574             Internally all arrays are stored as references to an array.
575             Depending on calling context, this method returns either an array or
576             a reference to an array, which means we can use both of the following syntaxes:
577              
578             my $ra = $pType->_getDataTypeArray(); # returns a ref to an array
579             my @a = $pType->_getDataTypeArray(); # returns an array
580              
581             =cut
582              
583             sub _getDataTypeArray {
584             my $self = shift;
585              
586             my $propertyName = shift;
587            
588             my $ra = $self->{$propertyName};
589             if ( ! defined $ra) {
590             $ra = [];
591             $self->{$propertyName} = $ra;
592             }
593             return wantarray ? @$ra : $ra;
594             }
595              
596             =head2 isEmpty()
597              
598             Returns:
599              
600             1 - If hash containing object properties is empty.
601              
602             0 - If hash conatining object properties is not empty
603              
604             Basically this means that:
605              
606             "scalar (keys @$self )" returns 0
607              
608             or "scalar %$self" returns 1
609              
610             =cut
611              
612             sub isEmpty {
613             my $self = shift;
614             return _isEmptyHash( $self);
615             }
616              
617             sub _isEmptyHash {
618             my $rh = shift;
619            
620             # scalar %hash returns a true value if hash has elements defined
621             my $ret = scalar %$rh;
622              
623             return ! $ret;
624             }
625             1;