File Coverage

lib/XML/RPC/Enc/LibXML.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package XML::RPC::Enc::LibXML;
2              
3 2     2   7714 use strict;
  2         5  
  2         70  
4 2     2   10 use warnings;
  2         3  
  2         56  
5 2     2   9 use base 'XML::RPC::Enc';
  2         5  
  2         843  
6 2     2   2323 use XML::LibXML;
  0            
  0            
7             use XML::Hash::LX;
8             use Carp;
9             #use Encode ();
10              
11             use XML::RPC::Fast ();
12             our $VERSION = $XML::RPC::Fast::VERSION;
13             BEGIN {
14             if (eval { my $x = pack 'q', -1; 1 }) {
15             *_HAVE_BIGINT = sub () { 1 };
16             my $maxint = eval q{ 0+"9223372036854775807" };
17             *_MAX_BIGINT = sub () { $maxint };
18             } else {
19             require Math::BigInt;
20             *_HAVE_BIGINT = sub () { 0 };
21             my $maxint = Math::BigInt->new("0x7fffffffffffffff");
22             *_MAX_BIGINT = sub () { $maxint };
23             }
24             }
25              
26              
27             =head1 NAME
28              
29             XML::RPC::Enc::LibXML - Encode/decode XML-RPC using LibXML
30              
31             =head1 SYNOPSIS
32              
33             use XML::RPC::Fast;
34             use XML::RPC::Enc::LibXML;
35            
36             my $rpc = XML::RPC::Fast->new(
37             $uri,
38             encoder => XML::RPC::Enc::LibXML->new(
39             # internal_encoding currently not implemented, always want wide chars
40             internal_encoding => undef,
41             external_encoding => 'windows-1251',
42             )
43             );
44              
45             $rpc->registerType( base64 => sub {
46             my $node = shift;
47             return MIME::Base64::decode($node->textContent);
48             });
49              
50             $rpc->registerType( 'dateTime.iso8601' => sub {
51             my $node = shift;
52             return DateTime::Format::ISO8601->parse_datetime($node->textContent);
53             });
54              
55             $rpc->registerClass( DateTime => sub {
56             return ( 'dateTime.iso8601' => $_[0]->strftime('%Y%m%dT%H%M%S.%3N%z') );
57             });
58              
59             $rpc->registerClass( DateTime => sub {
60             my $node = XML::LibXML::Element->new('dateTime.iso8601');
61             $node->appendText($_[0]->strftime('%Y%m%dT%H%M%S.%3N%z'));
62             return $node;
63             });
64              
65             =head1 DESCRIPTION
66              
67             Default encoder/decoder for L
68              
69             If MIME::Base64 is installed, decoder for C type C will be setup
70              
71             If DateTime::Format::ISO8601 is installed, decoder for C type C will be setup
72              
73             Also will be setup by default encoders for L and L (will be encoded as C)
74              
75             Ty avoid default decoders setup:
76              
77             BEGIN {
78             $XML::RPC::Enc::LibXML::TYPES{base64} = 0;
79             $XML::RPC::Enc::LibXML::TYPES{'dateTime.iso8601'} = 0;
80             }
81             use XML::RPC::Enc::LibXML;
82              
83             =head1 IMPLEMENTED METHODS
84              
85             =head2 new
86              
87             =head2 request
88              
89             =head2 response
90              
91             =head2 fault
92              
93             =head2 decode
94              
95             =head2 registerType
96              
97             =head2 registerClass
98              
99             =head1 SEE ALSO
100              
101             =over 4
102              
103             =item * L
104              
105             Base class (also contains documentation)
106              
107             =back
108              
109             =cut
110              
111             # xml => perl
112             # args: xml-nodes (children of <$type> ... )
113             # retv: any scalar
114             our %TYPES;
115              
116             # perl => xml
117             # args: object
118             # retv: ( type => string ) || xml-node
119             our %CLASS;
120              
121             our $E;
122              
123             BEGIN {
124             if ( !exists $TYPES{base64} and eval{ require MIME::Base64;1 } ) {
125             $TYPES{base64} = sub {
126             #defined $E ? $E->encode(
127             MIME::Base64::decode(shift->textContent);
128             };
129             }
130             # DateTime is the most "standart" datetime object in perl, try to use it
131             if ( !exists $TYPES{'dateTime.iso8601'} and eval{ require DateTime::Format::ISO8601;1 } ) {
132             $TYPES{'dateTime.iso8601'} = sub {
133             DateTime::Format::ISO8601->parse_datetime(shift->textContent)
134             };
135             }
136             }
137              
138             #%TYPES = (
139             # custom => sub { ... },
140             # %TYPES,
141             #);
142              
143             # We need no modules to predefine encoders for dates
144             %CLASS = (
145             DateTime => sub {
146             'dateTime.iso8601',$_[0]->strftime('%Y%m%dT%H%M%S.%3N%z');
147             },
148             'Class::Date' => sub {
149             'dateTime.iso8601',$_[0]->strftime('%Y%m%dT%H%M%S').sprintf( '%+03d%02d', $_[0]->tzoffset / 3600, ( $_[0]->tzoffset % 3600 ) / 60 );
150             },
151             %CLASS,
152             );
153              
154             sub new {
155             my $pkg = shift;
156             my $self = bless {
157             @_,
158             parser => XML::LibXML->new(),
159             types => { },
160             class => { },
161             #internal_encoding => undef,
162             }, $pkg;
163             $self->{external_encoding} = 'utf-8' unless defined $self->{external_encoding};
164             return $self;
165             }
166              
167              
168             sub registerType {
169             my ( $self,$type,$decode ) = @_;
170             my $old;
171             if (ref $self) {
172             $old = $self->{types}{$type};
173             $self->{types}{$type} = $decode;
174             } else {
175             $old = $TYPES{$type};
176             $TYPES{$type} = $decode;
177             }
178             $old;
179             }
180              
181             sub registerClass {
182             my ( $self,$class,$encode ) = @_;
183             my $old;
184             if (ref $self) {
185             $old = $self->{class}{$class};
186             $self->{class}{$class} = $encode;
187             } else {
188             $old = $CLASS{$class};
189             $CLASS{$class} = $encode;
190             }
191             $old;
192             }
193              
194             # Encoder part
195              
196             sub _unparse_param {
197             my $p = shift;
198             my $r = XML::LibXML::Element->new('value');
199              
200             if ( ref($p) eq 'HASH' ) {
201             # struct -> ( member -> { name, value } )*
202             my $s = XML::LibXML::Element->new('struct');
203             $r->appendChild($s);
204             for ( keys %$p ) {
205             my $m = XML::LibXML::Element->new('member');
206             my $n = XML::LibXML::Element->new('name');
207             $n->appendText(defined $E ? $E->decode($_) : $_);
208             $m->appendChild($n);
209             $m->appendChild(_unparse_param($p->{$_}));
210             $s->appendChild($m);
211             }
212             }
213             elsif ( ref($p) eq 'ARRAY' ) {
214             my $a = XML::LibXML::Element->new('array');
215             my $d = XML::LibXML::Element->new('data');
216             $a->appendChild($d);
217             $r->appendChild($a);
218             for (@$p) {
219             $d->appendChild( _unparse_param($_) )
220             }
221             }
222             elsif ( ref($p) eq 'CODE' ) {
223             $r->appendChild(hash2xml($p->(), doc => 1)->documentElement);
224             }
225             elsif (ref $p) {
226             if (exists $CLASS{ ref $p }) {
227             my ($t,$x) = $CLASS{ ref $p }->($p);
228             if (ref $t and eval{ $t->isa('XML::LibXML::Node') }) {
229             $r->appendChild($t);
230             } else {
231             my $v = XML::LibXML::Element->new($t);
232             $v->appendText(defined $E ? $E->decode($x) : $x);
233             $r->appendChild($v);
234             }
235             }
236             elsif ( UNIVERSAL::isa($p,'SCALAR') ) {
237             my $v = XML::LibXML::Element->new(ref $p);
238             $v->appendText(defined $E ? $E->decode($$p) : $$p) if defined $$p;
239             $r->appendChild($v);
240             }
241             elsif ( UNIVERSAL::isa($p,'REF') ) {
242             my $v = XML::LibXML::Element->new(ref $p);
243             $v->appendChild(hash2xml($$p, doc => 1)->documentElement);
244             $r->appendChild($v);
245             }
246             else {
247             warn "Bad reference: $p";
248             #$result = undef;
249             }
250             }
251             else {
252             #no warnings;
253             if (!defined $p) {
254             my $v = XML::LibXML::Element->new('string');
255             $r->appendChild($v);
256             }
257              
258             =for rem
259              
260             Q: What is the legal syntax (and range) for integers?
261             How to deal with leading zeros?
262             Is a leading plus sign allowed?
263             How to deal with whitespace?
264              
265             A: An integer is a 32-bit signed number.
266             You can include a plus or minus at the beginning of a string of numeric characters.
267             Leading zeros are collapsed.
268             Whitespace is not permitted.
269             Just numeric characters preceeded by a plus or minus.
270              
271             Q: What is the legal syntax (and range) for floating point values (doubles)?
272             How is the exponent represented?
273             How to deal with whitespace?
274             Can infinity and "not a number" be represented?
275              
276             A: There is no representation for infinity or negative infinity or "not a number".
277             At this time, only decimal point notation is allowed, a plus or a minus,
278             followed by any number of numeric characters,
279             followed by a period and any number of numeric characters.
280             Whitespace is not allowed.
281             The range of allowable values is implementation-dependent, is not specified.
282              
283             # int
284             '+0' => 0
285             '-0' => 0
286             '+1234567' => 1234567
287             '0777' => 777
288             '0000000000000' => 0
289             '0000000000000000000000000000000000000000000000000' => 0
290             # not int
291             '999999999999999999999999999999999999';
292              
293             =cut
294             elsif ($p =~ m/^([\-+]?)\d+(\.\d+|)$/) {
295             my ($have_sign,$is_double) = ($1,$2);
296             if ( $is_double ) {
297             my $v = XML::LibXML::Element->new('double');
298             $v->appendText( $p );
299             $r->appendChild($v);
300             }
301             else {
302             my $v;
303             # TODO: should we pass sign "+"?
304             if( $p == unpack "l", pack "l", $p ) {
305             # i4
306             $v = XML::LibXML::Element->new('i4');
307             $v->appendText(int $p);
308             }
309             elsif ( _HAVE_BIGINT and $p == unpack "q", pack "q", $p ) {
310             # i8
311             $v = XML::LibXML::Element->new('i8');
312             $v->appendText(int $p);
313             }
314             elsif ( !_HAVE_BIGINT and abs( my $bi = Math::BigInt->new($p) ) < _MAX_BIGINT ) {
315             $v = XML::LibXML::Element->new('i8');
316             $v->appendText($bi->bstr);
317             }
318             else {
319             # string
320             $v = XML::LibXML::Element->new('string');
321             $v->appendText($p);
322             }
323             $r->appendChild($v);
324             }
325             }
326             else {
327             my $v = XML::LibXML::Element->new('string');
328             $v->appendText(defined $E ? $E->decode($p) : $p);
329             $r->appendChild($v);
330             }
331             }
332             return $r;
333             }
334              
335             sub request {
336             my $self = shift;
337             local @CLASS{keys %{ $self->{class} }} = values %{ $self->{class} };
338             local $E = Encode::find_encoding($self->{internal_encoding})
339             or croak "Could not find encoding $self->{internal_encoding}"
340             if defined $self->{internal_encoding};
341             my $method = shift;
342             my $doc = XML::LibXML::Document->new('1.0',$self->{external_encoding});
343             my $root = XML::LibXML::Element->new('methodCall');
344             $doc->setDocumentElement($root);
345             my $n = XML::LibXML::Element->new('methodName');
346             $n->appendText(defined $E ? $E->decode($method) : $method);
347             $root->appendChild($n);
348             my $prms = XML::LibXML::Element->new('params');
349             $root->appendChild($prms);
350             for my $v (@_) {
351             my $p = XML::LibXML::Element->new('param');
352             $p->appendChild( _unparse_param($v) );
353             $prms->appendChild($p);
354             }
355             my $x = $doc->toString;
356             utf8::encode($x) if utf8::is_utf8($x);
357             return $x;
358             }
359              
360             sub response {
361             my $self = shift;
362             local @CLASS{keys %{ $self->{class} }} = values %{ $self->{class} };
363             local $E = Encode::find_encoding($self->{internal_encoding})
364             or croak "Could not find encoding $self->{internal_encoding}"
365             if defined $self->{internal_encoding};
366             my $doc = XML::LibXML::Document->new('1.0',$self->{external_encoding});
367             my $root = XML::LibXML::Element->new('methodResponse');
368             $doc->setDocumentElement($root);
369             my $prms = XML::LibXML::Element->new('params');
370             $root->appendChild($prms);
371             for my $v (@_) {
372             my $p = XML::LibXML::Element->new('param');
373             $p->appendChild( _unparse_param($v) );
374             $prms->appendChild($p);
375             }
376             my $x = $doc->toString;
377             utf8::encode($x) if utf8::is_utf8($x);
378             return $x;
379             }
380              
381             sub fault {
382             my $self = shift;
383             local @CLASS{keys %{ $self->{class} }} = values %{ $self->{class} };
384             local $E = Encode::find_encoding($self->{internal_encoding})
385             or croak "Could not find encoding $self->{internal_encoding}"
386             if defined $self->{internal_encoding};
387             my ($code,$err) = @_;
388             my $doc = XML::LibXML::Document->new('1.0',$self->{external_encoding});
389             my $root = XML::LibXML::Element->new('methodResponse');
390             $doc->setDocumentElement($root);
391             my $f = XML::LibXML::Element->new('fault');
392             my $v = XML::LibXML::Element->new('value');
393             my $s = XML::LibXML::Element->new('struct');
394             for (qw(faultCode faultString)){
395             my $m = XML::LibXML::Element->new('member');
396             my $n = XML::LibXML::Element->new('name');
397             $n->appendText(defined $E ? $E->decode($_) : $_);
398             $m->appendChild($n);
399             $m->appendChild(_unparse_param(shift));
400             $s->appendChild($m);
401             }
402             $v->appendChild($s);
403             $f->appendChild($v);
404             $root->appendChild($f);
405             my $x = $doc->toString;
406             utf8::encode($x) if utf8::is_utf8($x);
407             return $x;
408             }
409              
410             # Decoder part
411             our $src;
412             sub decode {
413             my $self = shift;
414             my $string = shift;
415             #utf8::encode $string if utf8::is_utf8($string);
416             local $src = $string;
417             $self->_parse( $self->{parser}->parse_string($string) )
418             }
419              
420             sub _parse_param {
421             my $v = shift;
422             for my $t ($v->childNodes) {
423             next if ref $t eq 'XML::LibXML::Text';
424             my $type = $t->nodeName;
425             #print $t->nodeName,"\n";
426             if ($type eq 'string') {
427             return defined $E ? $E->encode(''.$t->textContent) : ''.$t->textContent;
428             }
429             elsif ($type eq 'i4' or $type eq 'int') {
430             return int $t->textContent;
431             }
432             elsif ($type eq 'double') {
433             return 0+$t->textContent;
434             }
435             elsif ($type eq 'bool') {
436             $v = $t->textContent;
437             return $v eq 'false' ? 0 : !!$v ? 1 : 0;
438             }
439             elsif ($type eq 'struct') {
440             my $r = {};
441             for my $m ($t->childNodes) {
442             my ($mn,$mv);
443             if ($m->nodeName eq 'member') {
444             for my $x ($m->childNodes) {
445             #print "\tmember:".$x->nodeName,"\n";
446             if ($x->nodeName eq 'name') {
447             $mn = $x->textContent;
448             #last;
449             }
450             elsif ($x->nodeName eq 'value') {
451             $mv = _parse_param ($x);
452             $mn and last;
453             }
454             }
455             if (defined $E) {
456             $mn = $E->encode($mn);
457             $mv = $E->encode($mv);
458             }
459             $r->{$mn} = $mv;
460             }
461             }
462             return $r;
463             }
464             elsif ($type eq 'array') {
465             my $r = [];
466             for my $d ($t->childNodes) {
467             #print "\tdata:".$d->nodeName,"\n";
468             unless (defined $d) {
469             warn "!!! Internal bug: childNodes return undef. XML=\n$src";
470             next;
471             }
472             if ($d->nodeName eq 'data') {
473             for my $x ($d->childNodes) {
474             #print "\tdata:".$x->nodeName,"\n";
475             if ($x->nodeName eq 'value') {
476             push @$r, _parse_param ($x);
477             }
478             }
479             }
480             }
481             return $r;
482             }
483             # elsif ($type eq 'base64') {
484             # return decode_base64($t->textContent);
485             # }
486             # elsif ($type eq 'dateTime.iso8601') {
487             # return $t->textContent;
488             # }
489             else {
490             if (exists $TYPES{$type} and $TYPES{$type}) {
491             return $TYPES{$type}( $t->childNodes );
492             } else {
493             my @children = $t->childNodes;
494             @children or return bless( \do{ my $o }, $type );
495             if (( @children > 1 ) xor ( ref $children[0] ne 'XML::LibXML::Text' )) {
496             #print STDERR + (0+@children)."; $type => ",ref $children[0], ' ', $children[0]->nodeName, "\n";
497             return bless \(xml2hash($t)->{$type}),$type;
498             } else {
499             #print STDERR + "*** ".(0+@children)."; $type => ",ref $children[0], ' ', $children[0]->nodeName, "\n";
500             return bless \(
501             defined $E ? $E->encode($children[0]->textContent) : $children[0]->textContent
502             ),$type;
503             }
504             }
505             }
506             last;
507             }
508             return defined $E ? $E->encode($v->textContent) : $v->textContent
509             }
510              
511             sub _parse {
512             my $self = shift;
513             my $doc = shift;
514             my @r;
515             my $root = $doc->documentElement;
516             local @TYPES{keys %{ $self->{types} }} = values %{ $self->{types} };
517             local $E = Encode::find_encoding($self->{internal_encoding})
518             or croak "Could not find encoding $self->{internal_encoding}"
519             if defined $self->{internal_encoding};
520             for my $p ($doc->findnodes('//param')) {
521             #for my $ps ($root->childNodes) {
522             # if ($ps->nodeName eq 'params') {
523             # for my $p ($ps->childNodes) {
524             # if ($p->nodeName eq 'param') {
525             #print $p->nodeName,"\n";
526             for my $v ($p->childNodes) {
527             if ($v->nodeName eq 'value') {
528             #print $p->nodeName,'=',_parse_param($v),"\n";
529             push @r, _parse_param ($v);
530             }
531             }
532             # }
533             # }
534             # }
535             }
536             for my $m ($doc->findnodes('//methodName')) {
537             unshift @r, defined $E ? $E->encode($m->textContent) : $m->textContent;
538             last;
539             }
540             unless(@r) {
541             for my $f ($doc->findnodes('//fault')) {
542             my ($c,$e);
543            
544             for ($f->childNodes) {
545             if ( $_->nodeName eq 'value' ) {
546             my $flt = _parse_param ( $_ );
547             $c = $flt->{faultCode};
548             $e = $flt->{faultString};
549             last;
550             } else {
551             $c = defined $E ? $E->encode($_->textContent) : $_->textContent if $_->nodeName eq 'faultCode';
552             $e = defined $E ? $E->encode($_->textContent) : $_->textContent if $_->nodeName eq 'faultString';
553             }
554             }
555             return { fault => { faultCode => $c, faultString => $e } };
556             }
557             }
558             #warn "@r";
559             return @r;
560             }
561              
562             =head1 COPYRIGHT & LICENSE
563              
564             Copyright (c) 2008-2009 Mons Anderson.
565              
566             This program is free software; you can redistribute it and/or modify it
567             under the same terms as Perl itself.
568              
569             =head1 AUTHOR
570              
571             Mons Anderson, C<< >>
572              
573             =cut
574              
575             1;