File Coverage

blib/lib/XML/Stream/Parser.pm
Criterion Covered Total %
statement 210 282 74.4
branch 64 108 59.2
condition 16 30 53.3
subroutine 29 32 90.6
pod 0 15 0.0
total 319 467 68.3


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 11     11   298 use strict;
  11         24  
  11         469  
58 11     11   58 use warnings;
  11         23  
  11         409  
59 11     11   89 use vars qw( $VERSION );
  11         17  
  11         561  
60              
61             $VERSION = "1.23_06";
62              
63 11     11   55 use Scalar::Util qw(weaken);
  11         21  
  11         683  
64              
65 11     11   9821 use XML::Stream::Tree;
  11         32  
  11         387  
66 11     11   21130 use XML::Stream::Node;
  11         40  
  11         472  
67 11     11   222 use XML::Stream::Tools;
  11         23  
  11         55107  
68              
69             sub new
70             {
71 11     11 0 1716 my $class = shift;
72              
73 11         52 my $self = { };
74              
75 11         47 bless($self, $class);
76              
77 11         25 my %args;
78 11         64 while($#_ >= 0) { $args{ lc pop(@_) } = pop(@_); }
  37         164  
79              
80 11         77 $self->{PARSING} = 0;
81 11         41 $self->{DOC} = 0;
82 11         31 $self->{XML} = "";
83 11         32 $self->{CNAME} = ();
84 11         37 $self->{CURR} = 0;
85              
86 11 100       68 $args{nonblocking} = 0 unless exists($args{nonblocking});
87              
88 11         120 $self->{NONBLOCKING} = delete($args{nonblocking});
89 11         93 XML::Stream::Tools::setup_debug($self, %args);
90              
91 11 100       94 $self->{SID} = exists($args{sid}) ? $args{sid} : "__xmlstream__:sid";
92              
93 11 50       86 $self->{STYLE} = (exists($args{style}) ? lc($args{style}) : "tree");
94 11 50       55 $self->{DTD} = (exists($args{dtd}) ? lc($args{dtd}) : 0);
95              
96 11         23 my $weak = $self;
97 11         63 weaken $weak;
98 11 100       75 if ($self->{STYLE} eq "tree")
    50          
99             {
100 4     4   32 $self->{HANDLER}->{startDocument} = sub{ $weak->startDocument(@_); };
  4         21  
101 4     4   28 $self->{HANDLER}->{endDocument} = sub{ $weak->endDocument(@_); };
  4         20  
102 4     118   19 $self->{HANDLER}->{startElement} = sub{ &XML::Stream::Tree::_handle_element(@_); };
  118         410  
103 4     118   37 $self->{HANDLER}->{endElement} = sub{ &XML::Stream::Tree::_handle_close(@_); };
  118         305  
104 4     172   24 $self->{HANDLER}->{characters} = sub{ &XML::Stream::Tree::_handle_cdata(@_); };
  172         440  
105             }
106             elsif ($self->{STYLE} eq "node")
107             {
108 7     7   73 $self->{HANDLER}->{startDocument} = sub{ $weak->startDocument(@_); };
  7         143  
109 7     4   47 $self->{HANDLER}->{endDocument} = sub{ $weak->endDocument(@_); };
  4         21  
110 7     118   33 $self->{HANDLER}->{startElement} = sub{ &XML::Stream::Node::_handle_element(@_); };
  118         335  
111 7     118   45 $self->{HANDLER}->{endElement} = sub{ &XML::Stream::Node::_handle_close(@_); };
  118         313  
112 7     172   37 $self->{HANDLER}->{characters} = sub{ &XML::Stream::Node::_handle_cdata(@_); };
  172         478  
113             }
114 11         23 $self->setHandlers(%{$args{handlers}});
  11         312  
115              
116 11         37 $self->{XMLONHOLD} = "";
117              
118 11         63 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 1976 50   1976 0 7068 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 17 my $self = shift;
146 7         16 my $sid = shift;
147 7         31 $self->{SID} = $sid;
148             }
149              
150              
151             sub getSID
152             {
153 1677     1677 0 2098 my $self = shift;
154 1677         4476 return $self->{SID};
155             }
156              
157              
158             sub setHandlers
159             {
160 18     18 0 34 my $self = shift;
161 18         70 my (%handlers) = @_;
162              
163 18         72 foreach my $handler (keys(%handlers))
164             {
165 42         243 $self->{HANDLER}->{$handler} = $handlers{$handler};
166             }
167             }
168              
169              
170             sub parse
171             {
172 15     15 0 68 my $self = shift;
173 15         50 my $xml = shift;
174              
175 15 50       49 return unless defined($xml);
176 15 100       63 return if ($xml eq "");
177              
178 11 50       85 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 11         309 while($xml =~ s/<\!--.*?-->//gs) {}
186              
187 11         51 $self->{XML} .= $xml;
188              
189 11 50       151 return if ($self->{PARSING} == 1);
190              
191 11         27 $self->{PARSING} = 1;
192              
193 11 50       53 if(!$self->{DOC} == 1)
194             {
195 11         45 my $start = index($self->{XML},"<");
196              
197 11 100 66     152 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       9 if ($close == -1)
202             {
203 0         0 $self->{PARSING} = 0;
204 0         0 return;
205             }
206 2         13 $self->{XML} = substr($self->{XML},$close+2,length($self->{XML})-$close-2);
207             }
208              
209 11         31 &{$self->{HANDLER}->{startDocument}}($self);
  11         61  
210 11         38 $self->{DOC} = 1;
211             }
212              
213 11         24 while(1)
214             {
215 1638 100       3980 if (length($self->{XML}) == 0)
216             {
217 3         11 $self->{PARSING} = 0;
218 3         136 return $self->returnData(0);
219             }
220 1635         2256 my $eclose = -1;
221 1635         8188 $eclose = index($self->{XML},"{CNAME}->[$self->{CURR}].">")
222 1635 100       2091 if ($#{$self->{CNAME}} > -1);
223              
224 1635 100       3504 if ($eclose == 0)
225             {
226 447         2288 $self->{XML} = substr($self->{XML},length($self->{CNAME}->[$self->{CURR}])+3,length($self->{XML})-length($self->{CNAME}->[$self->{CURR}])-3);
227              
228 447 50       1152 $self->{PARSING} = 0 if ($self->{NONBLOCKING} == 1);
229 447         566 my $weak = $self;
230 447         841 weaken $weak;
231 447         913 &{$self->{HANDLER}->{endElement}}($weak, $weak->{CNAME}->[$weak->{CURR}]);
  447         1179  
232 447 50       28732 $self->{PARSING} = 1 if ($self->{NONBLOCKING} == 1);
233              
234 447         670 $self->{CURR}--;
235 447 100       925 if ($self->{CURR} == 0)
236             {
237 8         23 $self->{DOC} = 0;
238 8         53 $self->{PARSING} = 0;
239 8         16 &{$self->{HANDLER}->{endDocument}}($self);
  8         41  
240 8         42 return $self->returnData(0);
241             }
242 439         812 next;
243             }
244              
245 1188         1847 my $estart = index($self->{XML},"<");
246 1188         2531 my $cdatastart = index($self->{XML},"
247 1188 100 100     3761 if (($estart == 0) && ($cdatastart != 0))
248             {
249 492         897 my $close = index($self->{XML},">");
250 492 50       1010 if ($close == -1)
251             {
252 0         0 $self->{PARSING} = 0;
253 0         0 return $self->returnData(0);
254             }
255 492         1039 my $empty = (substr($self->{XML},$close-1,1) eq "/");
256 492 100       1301 my $starttag = substr($self->{XML},1,$close-($empty ? 2 : 1));
257 492         799 my $nextspace = index($starttag," ");
258 492         628 my $attribs;
259             my $name;
260 492 100       899 if ($nextspace != -1)
261             {
262 136         219 $name = substr($starttag,0,$nextspace);
263 136         386 $attribs = substr($starttag,$nextspace+1,length($starttag)-$nextspace-1);
264             }
265             else
266             {
267 356         494 $name = $starttag;
268             }
269              
270 492         1136 my %attribs = $self->attribution($attribs);
271 492 50 33     1513 if (($self->{DTD} == 1) && (exists($attribs{xmlns})))
272             {
273             }
274              
275 492         565 my $weak = $self;
276 492         1077 weaken $weak;
277 492         702 &{$self->{HANDLER}->{startElement}}($weak, $name,%attribs);
  492         1521  
278              
279 492 100       1082 if($empty == 1)
280             {
281 42         58 &{$self->{HANDLER}->{endElement}}($weak, $name);
  42         138  
282             }
283             else
284             {
285 450         701 $self->{CURR}++;
286 450         978 $self->{CNAME}->[$self->{CURR}] = $name;
287             }
288            
289 492         3072 $self->{XML} = substr($self->{XML},$close+1,length($self->{XML})-$close-1);
290 492         11673 next;
291             }
292              
293 696 100       1517 if ($cdatastart == 0)
294             {
295 8         26 my $cdataclose = index($self->{XML},"]]>");
296 8 50       37 if ($cdataclose == -1)
297             {
298 0         0 $self->{PARSING} = 0;
299 0         0 return $self->returnData(0);
300             }
301            
302 8         28 &{$self->{HANDLER}->{characters}}($self,substr($self->{XML},9,$cdataclose-9));
  8         32  
303            
304 8         47 $self->{XML} = substr($self->{XML},$cdataclose+3,length($self->{XML})-$cdataclose-3);
305 8         32 next;
306             }
307              
308 688 50 66     3571 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 688         2337 &{$self->{HANDLER}->{characters}}($self,$self->entityCheck(substr($self->{XML},0,$estart)));
  688         2224  
316 688         3513 $self->{XML} = substr($self->{XML},$estart,length($self->{XML})-$estart);
317             }
318             }
319             }
320              
321              
322             sub attribution
323             {
324 492     492 0 606 my $self = shift;
325 492         626 my $str = shift;
326              
327 492 100       1154 $str = "" unless defined($str);
328              
329 492         509 my %attribs;
330              
331 492         509 while(1)
332             {
333 658         929 my $eq = index($str,"=");
334 658 100 66     2230 if((length($str) == 0) || ($eq == -1))
335             {
336 492         1745 return %attribs;
337             }
338              
339 166         264 my $ids;
340             my $id;
341 166         300 my $id1 = index($str,"\'");
342 166         243 my $id2 = index($str,"\"");
343 166 100 66     965 if((($id1 < $id2) && ($id1 != -1)) || ($id2 == -1))
      66        
344             {
345 159         189 $ids = $id1;
346 159         234 $id = "\'";
347             }
348             else {
349 7 50 33     52 if((($id2 < $id1) && ($id1 == -1)) || ($id2 != -1))
      33        
350             {
351 7         25 $ids = $id2;
352 7         18 $id = "\"";
353             }
354             }
355              
356 166         277 my $nextid = index($str,$id,$ids+1);
357 166         350 my $val = substr($str,$ids+1,$nextid-$ids-1);
358 166         243 my $key = substr($str,0,$eq);
359              
360 166         720 while($key =~ s/\s//) {}
361              
362 166         390 $attribs{$key} = $self->entityCheck($val);
363 166         659 $str = substr($str,$nextid+1,length($str)-$nextid-1);
364             }
365              
366 0         0 return %attribs;
367             }
368              
369              
370             sub entityCheck
371             {
372 854     854 0 1102 my $self = shift;
373 854         1614 my $str = shift;
374              
375 854         2599 while($str =~ s/\<\;/\
376 854         2343 while($str =~ s/\>\;/\>/) {}
377 854         1663 while($str =~ s/\"\;/\"/) {}
378 854         1675 while($str =~ s/\&apos\;/\'/) {}
379 854         28740 while($str =~ s/\&\;/\&/) {}
380              
381 854         2395 return $str;
382             }
383              
384              
385             sub parsefile
386             {
387 4     4 0 1413 my $self = shift;
388 4         10 my $fileName = shift;
389              
390 4         250 open(FILE,"<",$fileName);
391 4         11 my $file;
392 4         144 while() { $file .= $_; }
  268         537  
393 4         21 $self->parse($file);
394 4         146 close(FILE);
395              
396 4         18 return $self->returnData();
397             }
398              
399              
400             sub returnData
401             {
402 15     15 0 32 my $self = shift;
403 15         25 my $clearData = shift;
404 15 100       51 $clearData = 1 unless defined($clearData);
405              
406 15         38 my $sid = $self->{SID};
407              
408 15 100       59 if ($self->{STYLE} eq "tree")
409             {
410 6 100       37 return unless exists($self->{SIDS}->{$sid}->{tree});
411 4         7 my @tree = @{$self->{SIDS}->{$sid}->{tree}};
  4         17  
412 4 100       35 delete($self->{SIDS}->{$sid}->{tree}) if ($clearData == 1);
413 4         20 return ( \@tree );
414             }
415 9 50       48 if ($self->{STYLE} eq "node")
416             {
417 9 100       190 return unless exists($self->{SIDS}->{$sid}->{node});
418 4         10 my $node = $self->{SIDS}->{$sid}->{node}->[0];
419 4 100       18 delete($self->{SIDS}->{$sid}->{node}) if ($clearData == 1);
420 4         20 return $node;
421             }
422             }
423              
424              
425             sub startDocument
426             {
427 11     11 0 27 my $self = shift;
428             }
429              
430              
431             sub endDocument
432             {
433 8     8 0 38 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;