File Coverage

blib/lib/XML/Stream/Parser.pm
Criterion Covered Total %
statement 210 282 74.4
branch 65 108 60.1
condition 19 30 63.3
subroutine 29 32 90.6
pod 0 15 0.0
total 323 467 69.1


line stmt bran cond sub pod time code
1             ##############################################################################
2             #
3             # This library is free software; you can redistribute it and/or
4             # modify it under the terms of the GNU Library General Public
5             # License as published by the Free Software Foundation; either
6             # version 2 of the License, or (at your option) any later version.
7             #
8             # This library is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11             # Library General Public License for more details.
12             #
13             # You should have received a copy of the GNU Library General Public
14             # License along with this library; if not, write to the
15             # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16             # Boston, MA 02111-1307, USA.
17             #
18             # Jabber
19             # Copyright (C) 1998-2004 Jabber Software Foundation http://jabber.org/
20             #
21             ##############################################################################
22              
23             package XML::Stream::Parser;
24              
25             =head1 NAME
26              
27             XML::Stream::Parser - SAX XML Parser for XML Streams
28              
29             =head1 SYNOPSIS
30              
31             Light weight XML parser that builds XML::Parser::Tree objects from the
32             incoming stream and passes them to a function to tell whoever is using
33             it that there are new packets.
34              
35             =head1 DESCRIPTION
36              
37             This module provides a very light weight parser
38              
39             =head1 METHODS
40              
41             =head1 EXAMPLES
42              
43             =head1 AUTHOR
44              
45             By Ryan Eatmon in January of 2001 for http://jabber.org/
46              
47             Currently maintained by Darian Anthony Patrick.
48              
49             =head1 COPYRIGHT
50              
51             Copyright (C) 1998-2004 Jabber Software Foundation http://jabber.org/
52              
53             This module licensed under the LGPL, version 2.1.
54              
55             =cut
56              
57 12     12   60 use strict;
  12         19  
  12         490  
58 12     12   59 use warnings;
  12         21  
  12         364  
59 12     12   55 use vars qw( $VERSION );
  12         18  
  12         683  
60              
61             $VERSION = "1.24";
62              
63 12     12   61 use Scalar::Util qw(weaken);
  12         16  
  12         735  
64              
65 12     12   5857 use XML::Stream::Tree;
  12         31  
  12         440  
66 12     12   6894 use XML::Stream::Node;
  12         26  
  12         409  
67 12     12   106 use XML::Stream::Tools;
  12         17  
  12         33330  
68              
69             sub new
70             {
71 13     13 0 1886 my $class = shift;
72              
73 13         34 my $self = { };
74              
75 13         46 bless($self, $class);
76              
77 13         26 my %args;
78 13         67 while($#_ >= 0) { $args{ lc pop(@_) } = pop(@_); }
  37         128  
79              
80 13         77 $self->{PARSING} = 0;
81 13         34 $self->{DOC} = 0;
82 13         37 $self->{XML} = "";
83 13         28 $self->{CNAME} = ();
84 13         28 $self->{CURR} = 0;
85              
86 13 100       74 $args{nonblocking} = 0 unless exists($args{nonblocking});
87              
88 13         122 $self->{NONBLOCKING} = delete($args{nonblocking});
89 13         78 XML::Stream::Tools::setup_debug($self, %args);
90              
91 13 100       102 $self->{SID} = exists($args{sid}) ? $args{sid} : "__xmlstream__:sid";
92              
93 13 100       84 $self->{STYLE} = (exists($args{style}) ? lc($args{style}) : "tree");
94 13 50       50 $self->{DTD} = (exists($args{dtd}) ? lc($args{dtd}) : 0);
95              
96 13         28 my $weak = $self;
97 13         90 weaken $weak;
98 13 100       76 if ($self->{STYLE} eq "tree")
    50          
99             {
100 6     6   43 $self->{HANDLER}->{startDocument} = sub{ $weak->startDocument(@_); };
  6         29  
101 6     6   39 $self->{HANDLER}->{endDocument} = sub{ $weak->endDocument(@_); };
  6         24  
102 6     124   29 $self->{HANDLER}->{startElement} = sub{ &XML::Stream::Tree::_handle_element(@_); };
  124         250  
103 6     124   29 $self->{HANDLER}->{endElement} = sub{ &XML::Stream::Tree::_handle_close(@_); };
  124         231  
104 6     176   26 $self->{HANDLER}->{characters} = sub{ &XML::Stream::Tree::_handle_cdata(@_); };
  176         326  
105             }
106             elsif ($self->{STYLE} eq "node")
107             {
108 7     7   53 $self->{HANDLER}->{startDocument} = sub{ $weak->startDocument(@_); };
  7         38  
109 7     4   48 $self->{HANDLER}->{endDocument} = sub{ $weak->endDocument(@_); };
  4         20  
110 7     118   31 $self->{HANDLER}->{startElement} = sub{ &XML::Stream::Node::_handle_element(@_); };
  118         258  
111 7     118   32 $self->{HANDLER}->{endElement} = sub{ &XML::Stream::Node::_handle_close(@_); };
  118         206  
112 7     172   32 $self->{HANDLER}->{characters} = sub{ &XML::Stream::Node::_handle_cdata(@_); };
  172         347  
113             }
114 13         22 $self->setHandlers(%{$args{handlers}});
  13         70  
115              
116 13         31 $self->{XMLONHOLD} = "";
117              
118 13         64 return $self;
119             }
120              
121              
122             ###########################################################################
123             #
124             # debug - prints the arguments to the debug log if debug is turned on.
125             #
126             ###########################################################################
127             sub debug
128             {
129 2010 50   2010 0 4205 return if ($_[1] > $_[0]->{DEBUGLEVEL});
130 0         0 my $self = shift;
131 0         0 my ($limit,@args) = @_;
132 0 0       0 return if ($self->{DEBUGFILE} eq "");
133 0         0 my $fh = $self->{DEBUGFILE};
134 0 0       0 if ($self->{DEBUGTIME} == 1)
135             {
136 0         0 my ($sec,$min,$hour) = localtime(time);
137 0         0 print $fh sprintf("[%02d:%02d:%02d] ",$hour,$min,$sec);
138             }
139 0         0 print $fh "XML::Stream::Parser: $self->{STYLE}: @args\n";
140             }
141              
142              
143             sub setSID
144             {
145 7     7 0 14 my $self = shift;
146 7         13 my $sid = shift;
147 7         19 $self->{SID} = $sid;
148             }
149              
150              
151             sub getSID
152             {
153 1695     1695 0 1620 my $self = shift;
154 1695         3243 return $self->{SID};
155             }
156              
157              
158             sub setHandlers
159             {
160 20     20 0 37 my $self = shift;
161 20         58 my (%handlers) = @_;
162              
163 20         78 foreach my $handler (keys(%handlers))
164             {
165 42         204 $self->{HANDLER}->{$handler} = $handlers{$handler};
166             }
167             }
168              
169              
170             sub parse
171             {
172 17     17 0 105 my $self = shift;
173 17         36 my $xml = shift;
174              
175 17 50       55 return unless defined($xml);
176 17 100       61 return if ($xml eq "");
177              
178 13 50       51 if ($self->{XMLONHOLD} ne "")
179             {
180 0         0 $self->{XML} = $self->{XMLONHOLD};
181 0         0 $self->{XMLONHOLD} = "";
182             }
183              
184             # XXX change this to not use regex?
185 13         298 while($xml =~ s/<\!--.*?-->//gs) {}
186              
187 13         46 $self->{XML} .= $xml;
188              
189 13 50       56 return if ($self->{PARSING} == 1);
190              
191 13         25 $self->{PARSING} = 1;
192              
193 13 50       55 if(!$self->{DOC} == 1)
194             {
195 13         47 my $start = index($self->{XML},"<");
196              
197 13 100 66     177 if ((substr($self->{XML},$start,3) eq "
198             (substr($self->{XML},$start,3) eq "
199             {
200 2         10 my $close = index($self->{XML},"?>");
201 2 50       7 if ($close == -1)
202             {
203 0         0 $self->{PARSING} = 0;
204 0         0 return;
205             }
206 2         16 $self->{XML} = substr($self->{XML},$close+2,length($self->{XML})-$close-2);
207             }
208              
209 13         28 &{$self->{HANDLER}->{startDocument}}($self);
  13         66  
210 13         29 $self->{DOC} = 1;
211             }
212              
213 13         20 while(1)
214             {
215 1656 100       3229 if (length($self->{XML}) == 0)
216             {
217 3         8 $self->{PARSING} = 0;
218 3         17 return $self->returnData(0);
219             }
220 1653         1429 my $eclose = -1;
221 1653         6094 $eclose = index($self->{XML},"{CNAME}->[$self->{CURR}].">")
222 1653 100       1339 if ($#{$self->{CNAME}} > -1);
223              
224 1653 100       2676 if ($eclose == 0)
225             {
226 454         1501 $self->{XML} = substr($self->{XML},length($self->{CNAME}->[$self->{CURR}])+3,length($self->{XML})-length($self->{CNAME}->[$self->{CURR}])-3);
227              
228 454 50       776 $self->{PARSING} = 0 if ($self->{NONBLOCKING} == 1);
229 454         407 my $weak = $self;
230 454         745 weaken $weak;
231 454         642 &{$self->{HANDLER}->{endElement}}($weak, $weak->{CNAME}->[$weak->{CURR}]);
  454         974  
232 454 50       25829 $self->{PARSING} = 1 if ($self->{NONBLOCKING} == 1);
233              
234 454         570 $self->{CURR}--;
235 454 100       775 if ($self->{CURR} == 0)
236             {
237 10         20 $self->{DOC} = 0;
238 10         45 $self->{PARSING} = 0;
239 10         18 &{$self->{HANDLER}->{endDocument}}($self);
  10         41  
240 10         39 return $self->returnData(0);
241             }
242 444         597 next;
243             }
244              
245 1199         1465 my $estart = index($self->{XML},"<");
246 1199         1868 my $cdatastart = index($self->{XML},"
247 1199 100 100     3186 if (($estart == 0) && ($cdatastart != 0))
248             {
249 499         586 my $close = index($self->{XML},">");
250 499 50       883 if ($close == -1)
251             {
252 0         0 $self->{PARSING} = 0;
253 0         0 return $self->returnData(0);
254             }
255 499         959 my $empty = (substr($self->{XML},$close-1,1) eq "/");
256 499 100       936 my $starttag = substr($self->{XML},1,$close-($empty ? 2 : 1));
257 499         526 my $nextspace = index($starttag," ");
258 499         488 my $attribs;
259             my $name;
260 499 100       735 if ($nextspace != -1)
261             {
262 138         185 $name = substr($starttag,0,$nextspace);
263 138         269 $attribs = substr($starttag,$nextspace+1,length($starttag)-$nextspace-1);
264             }
265             else
266             {
267 361         380 $name = $starttag;
268             }
269              
270 499         935 my %attribs = $self->attribution($attribs);
271 499 50 33     1183 if (($self->{DTD} == 1) && (exists($attribs{xmlns})))
272             {
273             }
274              
275 499         466 my $weak = $self;
276 499         882 weaken $weak;
277 499         517 &{$self->{HANDLER}->{startElement}}($weak, $name,%attribs);
  499         1212  
278              
279 499 100       871 if($empty == 1)
280             {
281 42         49 &{$self->{HANDLER}->{endElement}}($weak, $name);
  42         112  
282             }
283             else
284             {
285 457         484 $self->{CURR}++;
286 457         724 $self->{CNAME}->[$self->{CURR}] = $name;
287             }
288            
289 499         3515 $self->{XML} = substr($self->{XML},$close+1,length($self->{XML})-$close-1);
290 499         888 next;
291             }
292              
293 700 100       1095 if ($cdatastart == 0)
294             {
295 8         25 my $cdataclose = index($self->{XML},"]]>");
296 8 50       29 if ($cdataclose == -1)
297             {
298 0         0 $self->{PARSING} = 0;
299 0         0 return $self->returnData(0);
300             }
301            
302 8         29 &{$self->{HANDLER}->{characters}}($self,substr($self->{XML},9,$cdataclose-9));
  8         34  
303            
304 8         46 $self->{XML} = substr($self->{XML},$cdataclose+3,length($self->{XML})-$cdataclose-3);
305 8         16 next;
306             }
307              
308 692 50 66     2998 if ($estart == -1)
    50          
309             {
310 0         0 $self->{XMLONHOLD} = $self->{XML};
311 0         0 $self->{XML} = "";
312             }
313             elsif (($cdatastart == -1) || ($cdatastart > $estart))
314             {
315 692         1969 &{$self->{HANDLER}->{characters}}($self,$self->entityCheck(substr($self->{XML},0,$estart)));
  692         1721  
316 692         2748 $self->{XML} = substr($self->{XML},$estart,length($self->{XML})-$estart);
317             }
318             }
319             }
320              
321              
322             sub attribution
323             {
324 499     499 0 458 my $self = shift;
325 499         449 my $str = shift;
326              
327 499 100       918 $str = "" unless defined($str);
328              
329 499         432 my %attribs;
330              
331 499         411 while(1)
332             {
333 671         698 my $eq = index($str,"=");
334 671 100 66     1679 if((length($str) == 0) || ($eq == -1))
335             {
336 499         1439 return %attribs;
337             }
338              
339 172         173 my $ids;
340             my $id;
341 172         216 my $id1 = index($str,"\'");
342 172         212 my $id2 = index($str,"\"");
343 172 100 100     805 if((($id1 < $id2) && ($id1 != -1)) || ($id2 == -1))
      100        
344             {
345 162         156 $ids = $id1;
346 162         218 $id = "\'";
347             }
348             else {
349 10 50 66     44 if((($id2 < $id1) && ($id1 == -1)) || ($id2 != -1))
      33        
350             {
351 10         8 $ids = $id2;
352 10         12 $id = "\"";
353             }
354             }
355              
356 172         245 my $nextid = index($str,$id,$ids+1);
357 172         286 my $val = substr($str,$ids+1,$nextid-$ids-1);
358 172         247 my $key = substr($str,0,$eq);
359              
360 172         661 while($key =~ s/\s//) {}
361              
362 172         312 $attribs{$key} = $self->entityCheck($val);
363 172         657 $str = substr($str,$nextid+1,length($str)-$nextid-1);
364             }
365              
366 0         0 return %attribs;
367             }
368              
369              
370             sub entityCheck
371             {
372 864     864 0 821 my $self = shift;
373 864         1313 my $str = shift;
374              
375 864         1996 while($str =~ s/\<\;/\
376 864         1497 while($str =~ s/\>\;/\>/) {}
377 864         1478 while($str =~ s/\"\;/\"/) {}
378 864         1511 while($str =~ s/\&apos\;/\'/) {}
379 864         1486 while($str =~ s/\&\;/\&/) {}
380              
381 864         1560 return $str;
382             }
383              
384              
385             sub parsefile
386             {
387 4     4 0 1096 my $self = shift;
388 4         8 my $fileName = shift;
389              
390 4         183 open(FILE,"<",$fileName);
391 4         6 my $file;
392 4         95 while() { $file .= $_; }
  268         383  
393 4         16 $self->parse($file);
394 4         111 close(FILE);
395              
396 4         13 return $self->returnData();
397             }
398              
399              
400             sub returnData
401             {
402 17     17 0 22 my $self = shift;
403 17         23 my $clearData = shift;
404 17 100       47 $clearData = 1 unless defined($clearData);
405              
406 17         33 my $sid = $self->{SID};
407              
408 17 100       61 if ($self->{STYLE} eq "tree")
409             {
410 8 100       36 return unless exists($self->{SIDS}->{$sid}->{tree});
411 6         6 my @tree = @{$self->{SIDS}->{$sid}->{tree}};
  6         19  
412 6 100       21 delete($self->{SIDS}->{$sid}->{tree}) if ($clearData == 1);
413 6         42 return ( \@tree );
414             }
415 9 50       34 if ($self->{STYLE} eq "node")
416             {
417 9 100       58 return unless exists($self->{SIDS}->{$sid}->{node});
418 4         8 my $node = $self->{SIDS}->{$sid}->{node}->[0];
419 4 100       15 delete($self->{SIDS}->{$sid}->{node}) if ($clearData == 1);
420 4         15 return $node;
421             }
422             }
423              
424              
425             sub startDocument
426             {
427 13     13 0 33 my $self = shift;
428             }
429              
430              
431             sub endDocument
432             {
433 10     10 0 30 my $self = shift;
434             }
435              
436              
437             sub startElement
438             {
439 0     0 0   my $self = shift;
440 0           my ($sax, $tag, %att) = @_;
441              
442 0 0         return unless ($self->{DOC} == 1);
443              
444 0 0         if ($self->{STYLE} eq "debug")
445             {
446 0           print "$self->{DEBUGHEADER} \\\\ (",join(" ",%att),")\n";
447 0           $self->{DEBUGHEADER} .= $tag." ";
448             }
449             else
450             {
451 0           my @NEW;
452 0 0         if($#{$self->{TREE}} < 0)
  0            
453             {
454 0           push @{$self->{TREE}}, $tag;
  0            
455             }
456             else
457             {
458 0           push @{ $self->{TREE}[ $#{$self->{TREE}}]}, $tag;
  0            
  0            
459             }
460 0           push @NEW, \%att;
461 0           push @{$self->{TREE}}, \@NEW;
  0            
462             }
463             }
464              
465              
466             sub characters
467             {
468 0     0 0   my $self = shift;
469 0           my ($sax, $cdata) = @_;
470              
471 0 0         return unless ($self->{DOC} == 1);
472              
473 0 0         if ($self->{STYLE} eq "debug")
474             {
475 0           my $str = $cdata;
476 0           $str =~ s/\n/\#10\;/g;
477 0           print "$self->{DEBUGHEADER} || $str\n";
478             }
479             else
480             {
481 0 0         return if ($#{$self->{TREE}} == -1);
  0            
482              
483 0           my $pos = $#{$self->{TREE}};
  0            
484              
485 0 0 0       if ($pos > 0 && $self->{TREE}[$pos - 1] eq "0")
486             {
487 0           $self->{TREE}[$pos - 1] .= $cdata;
488             }
489             else
490             {
491 0           push @{$self->{TREE}[$#{$self->{TREE}}]}, 0;
  0            
  0            
492 0           push @{$self->{TREE}[$#{$self->{TREE}}]}, $cdata;
  0            
  0            
493             }
494             }
495             }
496              
497              
498             sub endElement
499             {
500 0     0 0   my $self = shift;
501 0           my ($sax, $tag) = @_;
502              
503 0 0         return unless ($self->{DOC} == 1);
504              
505 0 0         if ($self->{STYLE} eq "debug")
506             {
507 0           $self->{DEBUGHEADER} =~ s/\S+\ $//;
508 0           print "$self->{DEBUGHEADER} //\n";
509             }
510             else
511             {
512 0           my $CLOSED = pop @{$self->{TREE}};
  0            
513              
514 0 0         if($#{$self->{TREE}} < 1)
  0            
515             {
516 0           push @{$self->{TREE}}, $CLOSED;
  0            
517              
518 0 0         if($self->{TREE}->[0] eq "stream:error")
519             {
520 0           $self->{STREAMERROR} = $self->{TREE}[1]->[2];
521             }
522             }
523             else
524             {
525 0           push @{$self->{TREE}[$#{$self->{TREE}}]}, $CLOSED;
  0            
  0            
526             }
527             }
528             }
529              
530              
531             1;