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   59 use strict;
  12         35  
  12         475  
58 12     12   57 use warnings;
  12         14  
  12         330  
59 12     12   46 use vars qw( $VERSION );
  12         19  
  12         537  
60              
61             $VERSION = "1.23_07";
62              
63 12     12   52 use Scalar::Util qw(weaken);
  12         24  
  12         603  
64              
65 12     12   5778 use XML::Stream::Tree;
  12         27  
  12         460  
66 12     12   6244 use XML::Stream::Node;
  12         26  
  12         393  
67 12     12   93 use XML::Stream::Tools;
  12         15  
  12         28852  
68              
69             sub new
70             {
71 13     13 0 1722 my $class = shift;
72              
73 13         33 my $self = { };
74              
75 13         44 bless($self, $class);
76              
77 13         21 my %args;
78 13         63 while($#_ >= 0) { $args{ lc pop(@_) } = pop(@_); }
  37         119  
79              
80 13         78 $self->{PARSING} = 0;
81 13         39 $self->{DOC} = 0;
82 13         35 $self->{XML} = "";
83 13         32 $self->{CNAME} = ();
84 13         30 $self->{CURR} = 0;
85              
86 13 100       65 $args{nonblocking} = 0 unless exists($args{nonblocking});
87              
88 13         100 $self->{NONBLOCKING} = delete($args{nonblocking});
89 13         80 XML::Stream::Tools::setup_debug($self, %args);
90              
91 13 100       78 $self->{SID} = exists($args{sid}) ? $args{sid} : "__xmlstream__:sid";
92              
93 13 100       62 $self->{STYLE} = (exists($args{style}) ? lc($args{style}) : "tree");
94 13 50       39 $self->{DTD} = (exists($args{dtd}) ? lc($args{dtd}) : 0);
95              
96 13         23 my $weak = $self;
97 13         65 weaken $weak;
98 13 100       68 if ($self->{STYLE} eq "tree")
    50          
99             {
100 6     6   42 $self->{HANDLER}->{startDocument} = sub{ $weak->startDocument(@_); };
  6         21  
101 6     6   33 $self->{HANDLER}->{endDocument} = sub{ $weak->endDocument(@_); };
  6         23  
102 6     124   26 $self->{HANDLER}->{startElement} = sub{ &XML::Stream::Tree::_handle_element(@_); };
  124         229  
103 6     124   27 $self->{HANDLER}->{endElement} = sub{ &XML::Stream::Tree::_handle_close(@_); };
  124         217  
104 6     176   23 $self->{HANDLER}->{characters} = sub{ &XML::Stream::Tree::_handle_cdata(@_); };
  176         309  
105             }
106             elsif ($self->{STYLE} eq "node")
107             {
108 7     7   50 $self->{HANDLER}->{startDocument} = sub{ $weak->startDocument(@_); };
  7         36  
109 7     4   37 $self->{HANDLER}->{endDocument} = sub{ $weak->endDocument(@_); };
  4         19  
110 7     118   25 $self->{HANDLER}->{startElement} = sub{ &XML::Stream::Node::_handle_element(@_); };
  118         246  
111 7     118   23 $self->{HANDLER}->{endElement} = sub{ &XML::Stream::Node::_handle_close(@_); };
  118         203  
112 7     172   26 $self->{HANDLER}->{characters} = sub{ &XML::Stream::Node::_handle_cdata(@_); };
  172         334  
113             }
114 13         21 $self->setHandlers(%{$args{handlers}});
  13         65  
115              
116 13         29 $self->{XMLONHOLD} = "";
117              
118 13         53 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 4024 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 13 my $self = shift;
146 7         10 my $sid = shift;
147 7         20 $self->{SID} = $sid;
148             }
149              
150              
151             sub getSID
152             {
153 1695     1695 0 1425 my $self = shift;
154 1695         2975 return $self->{SID};
155             }
156              
157              
158             sub setHandlers
159             {
160 20     20 0 32 my $self = shift;
161 20         49 my (%handlers) = @_;
162              
163 20         62 foreach my $handler (keys(%handlers))
164             {
165 42         217 $self->{HANDLER}->{$handler} = $handlers{$handler};
166             }
167             }
168              
169              
170             sub parse
171             {
172 17     17 0 113 my $self = shift;
173 17         33 my $xml = shift;
174              
175 17 50       53 return unless defined($xml);
176 17 100       62 return if ($xml eq "");
177              
178 13 50       50 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         310 while($xml =~ s/<\!--.*?-->//gs) {}
186              
187 13         43 $self->{XML} .= $xml;
188              
189 13 50       53 return if ($self->{PARSING} == 1);
190              
191 13         26 $self->{PARSING} = 1;
192              
193 13 50       53 if(!$self->{DOC} == 1)
194             {
195 13         53 my $start = index($self->{XML},"<");
196              
197 13 100 66     168 if ((substr($self->{XML},$start,3) eq "
198             (substr($self->{XML},$start,3) eq "
199             {
200 2         8 my $close = index($self->{XML},"?>");
201 2 50       6 if ($close == -1)
202             {
203 0         0 $self->{PARSING} = 0;
204 0         0 return;
205             }
206 2         11 $self->{XML} = substr($self->{XML},$close+2,length($self->{XML})-$close-2);
207             }
208              
209 13         24 &{$self->{HANDLER}->{startDocument}}($self);
  13         53  
210 13         25 $self->{DOC} = 1;
211             }
212              
213 13         19 while(1)
214             {
215 1656 100       2889 if (length($self->{XML}) == 0)
216             {
217 3         9 $self->{PARSING} = 0;
218 3         25 return $self->returnData(0);
219             }
220 1653         1465 my $eclose = -1;
221 1653         5437 $eclose = index($self->{XML},"{CNAME}->[$self->{CURR}].">")
222 1653 100       1141 if ($#{$self->{CNAME}} > -1);
223              
224 1653 100       2482 if ($eclose == 0)
225             {
226 454         1308 $self->{XML} = substr($self->{XML},length($self->{CNAME}->[$self->{CURR}])+3,length($self->{XML})-length($self->{CNAME}->[$self->{CURR}])-3);
227              
228 454 50       728 $self->{PARSING} = 0 if ($self->{NONBLOCKING} == 1);
229 454         424 my $weak = $self;
230 454         666 weaken $weak;
231 454         530 &{$self->{HANDLER}->{endElement}}($weak, $weak->{CNAME}->[$weak->{CURR}]);
  454         942  
232 454 50       19310 $self->{PARSING} = 1 if ($self->{NONBLOCKING} == 1);
233              
234 454         454 $self->{CURR}--;
235 454 100       685 if ($self->{CURR} == 0)
236             {
237 10         31 $self->{DOC} = 0;
238 10         36 $self->{PARSING} = 0;
239 10         13 &{$self->{HANDLER}->{endDocument}}($self);
  10         36  
240 10         36 return $self->returnData(0);
241             }
242 444         557 next;
243             }
244              
245 1199         1272 my $estart = index($self->{XML},"<");
246 1199         1610 my $cdatastart = index($self->{XML},"
247 1199 100 100     2754 if (($estart == 0) && ($cdatastart != 0))
248             {
249 499         524 my $close = index($self->{XML},">");
250 499 50       826 if ($close == -1)
251             {
252 0         0 $self->{PARSING} = 0;
253 0         0 return $self->returnData(0);
254             }
255 499         822 my $empty = (substr($self->{XML},$close-1,1) eq "/");
256 499 100       872 my $starttag = substr($self->{XML},1,$close-($empty ? 2 : 1));
257 499         494 my $nextspace = index($starttag," ");
258 499         478 my $attribs;
259             my $name;
260 499 100       716 if ($nextspace != -1)
261             {
262 138         182 $name = substr($starttag,0,$nextspace);
263 138         246 $attribs = substr($starttag,$nextspace+1,length($starttag)-$nextspace-1);
264             }
265             else
266             {
267 361         348 $name = $starttag;
268             }
269              
270 499         844 my %attribs = $self->attribution($attribs);
271 499 50 33     1107 if (($self->{DTD} == 1) && (exists($attribs{xmlns})))
272             {
273             }
274              
275 499         467 my $weak = $self;
276 499         892 weaken $weak;
277 499         463 &{$self->{HANDLER}->{startElement}}($weak, $name,%attribs);
  499         1060  
278              
279 499 100       854 if($empty == 1)
280             {
281 42         41 &{$self->{HANDLER}->{endElement}}($weak, $name);
  42         109  
282             }
283             else
284             {
285 457         431 $self->{CURR}++;
286 457         646 $self->{CNAME}->[$self->{CURR}] = $name;
287             }
288            
289 499         2794 $self->{XML} = substr($self->{XML},$close+1,length($self->{XML})-$close-1);
290 499         793 next;
291             }
292              
293 700 100       1002 if ($cdatastart == 0)
294             {
295 8         23 my $cdataclose = index($self->{XML},"]]>");
296 8 50       42 if ($cdataclose == -1)
297             {
298 0         0 $self->{PARSING} = 0;
299 0         0 return $self->returnData(0);
300             }
301            
302 8         37 &{$self->{HANDLER}->{characters}}($self,substr($self->{XML},9,$cdataclose-9));
  8         27  
303            
304 8         39 $self->{XML} = substr($self->{XML},$cdataclose+3,length($self->{XML})-$cdataclose-3);
305 8         16 next;
306             }
307              
308 692 50 66     2605 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         1629 &{$self->{HANDLER}->{characters}}($self,$self->entityCheck(substr($self->{XML},0,$estart)));
  692         1461  
316 692         2324 $self->{XML} = substr($self->{XML},$estart,length($self->{XML})-$estart);
317             }
318             }
319             }
320              
321              
322             sub attribution
323             {
324 499     499 0 461 my $self = shift;
325 499         417 my $str = shift;
326              
327 499 100       858 $str = "" unless defined($str);
328              
329 499         407 my %attribs;
330              
331 499         397 while(1)
332             {
333 671         655 my $eq = index($str,"=");
334 671 100 66     1486 if((length($str) == 0) || ($eq == -1))
335             {
336 499         1296 return %attribs;
337             }
338              
339 172         168 my $ids;
340             my $id;
341 172         214 my $id1 = index($str,"\'");
342 172         190 my $id2 = index($str,"\"");
343 172 100 100     715 if((($id1 < $id2) && ($id1 != -1)) || ($id2 == -1))
      100        
344             {
345 162         143 $ids = $id1;
346 162         197 $id = "\'";
347             }
348             else {
349 10 50 66     43 if((($id2 < $id1) && ($id1 == -1)) || ($id2 != -1))
      33        
350             {
351 10         10 $ids = $id2;
352 10         11 $id = "\"";
353             }
354             }
355              
356 172         222 my $nextid = index($str,$id,$ids+1);
357 172         255 my $val = substr($str,$ids+1,$nextid-$ids-1);
358 172         203 my $key = substr($str,0,$eq);
359              
360 172         714 while($key =~ s/\s//) {}
361              
362 172         293 $attribs{$key} = $self->entityCheck($val);
363 172         503 $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 704 my $self = shift;
373 864         1224 my $str = shift;
374              
375 864         1864 while($str =~ s/\<\;/\
376 864         1493 while($str =~ s/\>\;/\>/) {}
377 864         1360 while($str =~ s/\"\;/\"/) {}
378 864         1308 while($str =~ s/\&apos\;/\'/) {}
379 864         1349 while($str =~ s/\&\;/\&/) {}
380              
381 864         1387 return $str;
382             }
383              
384              
385             sub parsefile
386             {
387 4     4 0 1063 my $self = shift;
388 4         7 my $fileName = shift;
389              
390 4         227 open(FILE,"<",$fileName);
391 4         7 my $file;
392 4         100 while() { $file .= $_; }
  268         407  
393 4         16 $self->parse($file);
394 4         282 close(FILE);
395              
396 4         15 return $self->returnData();
397             }
398              
399              
400             sub returnData
401             {
402 17     17 0 25 my $self = shift;
403 17         22 my $clearData = shift;
404 17 100       53 $clearData = 1 unless defined($clearData);
405              
406 17         34 my $sid = $self->{SID};
407              
408 17 100       62 if ($self->{STYLE} eq "tree")
409             {
410 8 100       30 return unless exists($self->{SIDS}->{$sid}->{tree});
411 6         8 my @tree = @{$self->{SIDS}->{$sid}->{tree}};
  6         18  
412 6 100       57 delete($self->{SIDS}->{$sid}->{tree}) if ($clearData == 1);
413 6         52 return ( \@tree );
414             }
415 9 50       37 if ($self->{STYLE} eq "node")
416             {
417 9 100       62 return unless exists($self->{SIDS}->{$sid}->{node});
418 4         8 my $node = $self->{SIDS}->{$sid}->{node}->[0];
419 4 100       22 delete($self->{SIDS}->{$sid}->{node}) if ($clearData == 1);
420 4         18 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 43 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;