File Coverage

blib/lib/XML/Atom/Stream.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             package XML::Atom::Stream;
2              
3 1     1   26196 use strict;
  1         2  
  1         47  
4             our $VERSION = '0.11';
5              
6 1     1   6 use Carp;
  1         2  
  1         84  
7 1     1   1999035 use LWP::UserAgent;
  1         2484684  
  1         42  
8 1     1   10 use HTTP::Request;
  1         2  
  1         23  
9 1     1   1304 use XML::SAX::ParserFactory;
  1         10156  
  1         31  
10 1     1   454 use XML::LibXML::SAX;
  0            
  0            
11              
12             sub new {
13             my($class, %param) = @_;
14             my $self = bless \%param, $class;
15             $self->init();
16             $self;
17             }
18              
19             sub init {
20             my $self = shift;
21             $self->{ua} ||= LWP::UserAgent->new(
22             agent => "XML::Atom::Stream/$VERSION",
23             timeout => $self->{timeout} || 15,
24             );
25             $self->{callback} or Carp::croak("no callback specified.");
26             }
27              
28             sub _setup_parser {
29             my $self = shift;
30             my $handler = XML::Atom::Stream::SAXHandler->new;
31             $handler->{callback} = $self->{callback};
32             $handler->{debug} = $self->{debug};
33             local $XML::SAX::ParserPackage = "XML::LibXML::SAX::Better";
34             my $factory = XML::SAX::ParserFactory->new;
35             my $parser = $factory->parser(Handler => $handler);
36             return $parser;
37             }
38              
39             sub connect {
40             my($self, $url) = @_;
41             $url or Carp::croak("URL needed for connect()");
42              
43             $self->{parser} = $self->_setup_parser;
44             $self->{ua}->get($url, ':content_cb' => sub { $self->on_content_cb(@_) });
45              
46             if ($self->{debug}) {
47             warn "Disconnected.", $@ ? " ($@)" : "";
48             }
49              
50             if ($self->{reconnect} && (!$self->{__exception} || $self->{__exception} =~ /xmlParse/)) {
51             warn "Trying to reconnect" if $self->{debug};
52             delete $self->{__exception};
53             $self->connect($url);
54             }
55             }
56              
57             sub on_content_cb {
58             my($self, $data, $res, $proto) = @_;
59             warn ".\n" if $data =~ /
60             eval { $self->{parser}->parse_chunk($data) };
61             if ($@) {
62             $self->{__exception} = $@;
63             die $@;
64             }
65             }
66              
67             package XML::Atom::Stream::SAXHandler;
68             use XML::Atom::Feed;
69             use XML::Handler::Trees;
70             use base qw( XML::Handler::Tree );
71              
72             sub start_element {
73             my $self = shift;
74             my($ref) = @_;
75             return if $ref->{LocalName} eq 'time' || $ref->{LocalName} eq 'atomStream';
76              
77             if ($ref->{LocalName} eq 'sorryTooSlow') {
78             warn "You're too slow and missed ", $ref->{Attributes}->{'{}youMissed'}->{Value}, " entries"
79             if $self->{debug};
80             return;
81             }
82              
83             if ($ref->{LocalName} eq 'feed') {
84             $self->{Curlist} = [];
85             }
86             $self->SUPER::start_element(@_);
87             }
88              
89             sub end_element {
90             my $self = shift;
91             my($ref) = @_;
92              
93             $self->SUPER::end_element(@_);
94             if ($ref->{LocalName} eq 'feed') {
95             my $element = $self->{Curlist};
96             my $xml = qq(\n);
97             my %ns;
98             my $dumper;
99             $dumper = sub {
100             my($ref) = @_;
101             my($elem, $stuff) = splice @$ref, 0, 2;
102             if ($elem eq '0') {
103             $xml .= encode_xml($stuff);
104             }
105             elsif ($elem =~ /^\{(.*?)\}([\w\-]+)$/) {
106             my($xmlns, $tag) = ($1, $2);
107             my $attr = shift @$stuff;
108             $xml .= qq(<$tag);
109              
110             my $has_xmlns;
111              
112             # extract and replace xmlns declarations
113             for my $key (keys %$attr) {
114             if ($key =~ m!^\{http://www\.w3\.org/2000/xmlns/\}([\w\-]+)$!) {
115             my $uri = delete $attr->{$key};
116             $ns{$uri} = $1;
117             $attr->{"xmlns:$1"} = $uri;
118             }
119             }
120              
121             for my $key (keys %$attr) {
122             my $attr_key;
123             if ($key =~ /^\{(.*?)\}(\w+)$/) {
124             my($xmlns, $prefix) = ($1, $2);
125             my $ns = $ns{$xmlns} || 'xml'; # xml:lang
126             $attr_key = "$ns:$prefix";
127             } else {
128             $attr_key = $key;
129             $has_xmlns = 1 if $key eq 'xmlns';
130             }
131              
132             $xml .= qq( $attr_key=") . encode_xml($attr->{$key}) . qq(");
133             }
134              
135             $xml .= qq( xmlns="$xmlns") if $xmlns ne 'http://www.w3.org/2005/Atom' && !$has_xmlns;
136              
137             if (@$stuff) {
138             $xml .= ">";
139             $dumper->($stuff);
140             $xml .= "";
141             } else {
142             $xml .= "/>";
143             }
144             }
145             $dumper->($ref) if @$ref;
146             };
147             $dumper->($element);
148             my $feed = eval { XML::Atom::Feed->new(Stream => \$xml) };
149             $dumper = 0; # to avoid memory leak
150             if ($@) {
151             warn "Feed parse error: $@" if $self->{debug};
152             return;
153             }
154              
155             $self->{callback}->($feed);
156             }
157             }
158              
159             my %Map = ('&' => '&', '"' => '"', '<' => '<', '>' => '>',
160             '\'' => ''');
161             my $RE = join '|', keys %Map;
162              
163             sub encode_xml {
164             my($str, $no_cdata) = @_;
165             if (!$no_cdata && $str =~ m/
166             <[^>]+> ## HTML markup
167             | ## or
168             &(?:(?!(\#([0-9]+)|\#x([0-9a-fA-F]+))).*?);
169             ## something that looks like an HTML entity.
170             /x) {
171             ## If ]]> exists in the string, encode the > to >.
172             $str =~ s/]]>/]]>/g;
173             $str = '';
174             } else {
175             $str =~ s!($RE)!$Map{$1}!g;
176             }
177             $str;
178             }
179              
180             # from http://code.sixapart.com/svn/djabberd/trunk/dev/xml-test.pl
181             package XML::LibXML::SAX::Better;
182             use strict;
183             use XML::LibXML;
184             use XML::SAX::Base;
185             use base qw(XML::SAX::Base);
186              
187             sub new {
188             my ($class, @params) = @_;
189             my $inst = $class->SUPER::new(@params);
190              
191             my $libxml = XML::LibXML->new;
192             $libxml->set_handler( $inst );
193             $inst->{LibParser} = $libxml;
194              
195             # setup SAX. 1 means "with SAX"
196             $libxml->_start_push(1);
197             $libxml->init_push;
198              
199             return $inst;
200             }
201              
202             sub parse_chunk {
203             my ( $self, $chunk ) = @_;
204             my $libxml = $self->{LibParser};
205             my $rv = $libxml->push($chunk);
206             }
207              
208             sub finish_push {
209             my $self = shift;
210             return 1 unless $self->{LibParser};
211             my $parser = delete $self->{LibParser};
212             return eval { $parser->finish_push };
213             }
214              
215              
216             # compat for test:
217              
218             sub _parse_string {
219             my ( $self, $string ) = @_;
220             # $self->{ParserOptions}{LibParser} = XML::LibXML->new;
221             $self->{ParserOptions}{LibParser} = XML::LibXML->new() unless defined $self->{ParserOptions}{LibParser};
222             $self->{ParserOptions}{ParseFunc} = \&XML::LibXML::parse_string;
223             $self->{ParserOptions}{ParseFuncParam} = $string;
224             return $self->_parse;
225             }
226              
227             sub _parse {
228             my $self = shift;
229             my $args = bless $self->{ParserOptions}, ref($self);
230              
231             $args->{LibParser}->set_handler( $self );
232             $args->{ParseFunc}->($args->{LibParser}, $args->{ParseFuncParam});
233              
234             if ( $args->{LibParser}->{SAX}->{State} == 1 ) {
235             croak( "SAX Exception not implemented, yet; Data ended before document ended\n" );
236             }
237              
238             return $self->end_document({});
239             }
240              
241             package XML::Atom::Stream;
242              
243             1;
244             __END__