File Coverage

blib/lib/AnyEvent/XMPP/Parser.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 AnyEvent::XMPP::Parser;
2 21     21   33083 no warnings;
  21         37  
  21         1595  
3 21     21   110 use strict;
  21         42  
  21         838  
4 21     21   13231 use AnyEvent::XMPP::Node;
  21         54  
  21         648  
5             # OMFG!!!111 THANK YOU FOR THIS MODULE TO HANDLE THE XMPP INSANITY:
6 21     21   45346 use XML::Parser::Expat;
  0            
  0            
7              
8             =head1 NAME
9              
10             AnyEvent::XMPP::Parser - Parser for XML streams (helper for AnyEvent::XMPP)
11              
12             =head1 SYNOPSIS
13              
14             use AnyEvent::XMPP::Parser;
15             ...
16              
17             =head1 DESCRIPTION
18              
19             This is a XMPP XML parser helper class, which helps me to cope with the XMPP XML.
20              
21             See also L for a discussion of the issues with XML in XMPP.
22              
23             =head1 METHODS
24              
25             =over 4
26              
27             =item B
28              
29             This creates a new AnyEvent::XMPP::Parser and calls C.
30              
31             =cut
32              
33             sub new {
34             my $this = shift;
35             my $class = ref($this) || $this;
36             my $self = {
37             stanza_cb => sub { die "No stanza callback provided!" },
38             error_cb => sub { warn "No error callback provided: $_[0]: $_[1]!" },
39             stream_cb => sub { },
40             @_
41             };
42             bless $self, $class;
43             $self->init;
44             $self
45             }
46              
47             =item B
48              
49             Sets the 'XML stanza' callback.
50              
51             C<$cb> must be a code reference. The first argument to
52             the callback will be this AnyEvent::XMPP::Parser instance and
53             the second will be the stanzas root AnyEvent::XMPP::Node as first argument.
54              
55             If the second argument is undefined the end of the stream has been found.
56              
57             =cut
58              
59             sub set_stanza_cb {
60             my ($self, $cb) = @_;
61             $self->{stanza_cb} = $cb;
62             }
63              
64             =item B
65              
66             This sets the error callback that will be called when
67             the parser encounters an syntax error. The first argument
68             is the exception and the second is the data which caused the error.
69              
70             =cut
71              
72             sub set_error_cb {
73             my ($self, $cb) = @_;
74             $self->{error_cb} = $cb;
75             }
76              
77             =item B
78              
79             This method sets the stream tag callback. It is called
80             when the tag from the server has been encountered.
81             The first argument to the callback is the L
82             of the opening stream tag.
83              
84             =cut
85              
86             sub set_stream_cb {
87             my ($self, $cb) = @_;
88             $self->{stream_cb} = $cb;
89             }
90              
91             =item B
92              
93             This methods (re)initializes the parser.
94              
95             =cut
96              
97             sub init {
98             my ($self) = @_;
99             $self->{parser} = XML::Parser::ExpatNB->new (
100             Namespaces => 1,
101             ProtocolEncoding => 'UTF-8'
102             );
103             $self->{parser}->setHandlers (
104             Start => sub { $self->cb_start_tag (@_) },
105             End => sub { $self->cb_end_tag (@_) },
106             Char => sub { $self->cb_char_data (@_) },
107             Default => sub { $self->cb_default (@_) },
108             );
109             $self->{nso} = {};
110             $self->{nodestack} = [];
111             }
112              
113             =item B
114              
115             This methods removes all handlers. Use it to avoid circular references.
116              
117             =cut
118              
119             sub cleanup {
120             my ($self) = @_;
121              
122             for (qw(stanza_cb error_cb stream_cb parser)) {
123             delete $self->{$_};
124             }
125              
126             return;
127             }
128              
129             =item B
130              
131             This method checks whether the C<$cmptag> matches the C<$tagname>
132             in the C<$namespace>.
133              
134             C<$cmptag> needs to come from the XML::Parser::Expat as it has
135             some magic attached that stores the namespace.
136              
137             =cut
138              
139             sub nseq {
140             my ($self, $ns, $name, $tag) = @_;
141              
142             unless (exists $self->{nso}->{$ns}->{$name}) {
143             $self->{nso}->{$ns}->{$name} =
144             $self->{parser}->generate_ns_name ($name, $ns);
145             }
146              
147             return $self->{parser}->eq_name ($self->{nso}->{$ns}->{$name}, $tag);
148             }
149              
150             =item B
151              
152             This method feeds a chunk of unparsed data to the parser.
153              
154             =cut
155              
156             sub feed {
157             my ($self, $data) = @_;
158             eval {
159             $self->{parser}->parse_more ($data);
160             };
161             if ($@) {
162             if ($self->{error_cb}) {
163             $self->{error_cb}->($@, $data, 'xml');
164             } else {
165             warn "parser error: $@ on [$data]\n";
166             }
167             }
168             }
169              
170             sub cb_start_tag {
171             my ($self, $p, $el, %attrs) = @_;
172             my $node = AnyEvent::XMPP::Node->new ($p->namespace ($el), $el, \%attrs, $self);
173             $node->append_raw ($p->recognized_string);
174             if (not @{$self->{nodestack}}) {
175             $self->{stream_cb}->($node);
176             }
177             push @{$self->{nodestack}}, $node;
178             }
179              
180             sub cb_char_data {
181             my ($self, $p, $str) = @_;
182             unless (@{$self->{nodestack}}) {
183             warn "characters outside of tag: [$str]!\n";
184             return;
185             }
186              
187             return if @{$self->{nodestack}} < 2; # don't append anything to the stream element
188              
189             my $node = $self->{nodestack}->[-1];
190             $node->add_text ($str);
191             $node->append_raw ($p->recognized_string);
192             }
193              
194             sub cb_end_tag {
195             my ($self, $p, $el) = @_;
196              
197             unless (@{$self->{nodestack}}) {
198             warn "end tag read without any starting tag!\n";
199             return;
200             }
201              
202             if (!$p->eq_name ($self->{nodestack}->[-1]->name, $el)) {
203             warn "end tag doesn't match start tags ($self->{tags}->[-1]->[0])!\n";
204             return;
205             }
206              
207             my $node = pop @{$self->{nodestack}};
208             $node->append_raw ($p->recognized_string);
209              
210             # > 1 because we don't want the stream tag to save all our children...
211             if (@{$self->{nodestack}} > 1) {
212             $self->{nodestack}->[-1]->add_node ($node);
213             }
214              
215             eval {
216             if (@{$self->{nodestack}} == 1) {
217             $self->{stanza_cb}->($self, $node);
218             } elsif (@{$self->{nodestack}} == 0) {
219             $self->{stanza_cb}->($self, undef);
220             }
221             };
222             if ($@) {
223             $self->{error_cb}->($@, undef, 'exception');
224             }
225             }
226              
227             sub cb_default {
228             my ($self, $p, $str) = @_;
229             $self->{nodestack}->[-1]->append_raw ($str)
230             if @{$self->{nodestack}} > 1; # don't append to the stream element
231             }
232              
233             =back
234              
235             =head1 AUTHOR
236              
237             Robin Redeker, C<< >>, JID: C<< >>
238              
239             =head1 COPYRIGHT & LICENSE
240              
241             Copyright 2007, 2008 Robin Redeker, all rights reserved.
242              
243             This program is free software; you can redistribute it and/or modify it
244             under the same terms as Perl itself.
245              
246             =cut
247              
248             sub DESTROY {
249             my ($self) = @_;
250             $self->{parser}->release if defined($self->{parser});
251             }
252              
253             1; # End of AnyEvent::XMPP