File Coverage

blib/lib/XML/Reader.pm
Criterion Covered Total %
statement 17 539 3.1
branch 1 248 0.4
condition 0 42 0.0
subroutine 5 57 8.7
pod 25 37 67.5
total 48 923 5.2


line stmt bran cond sub pod time code
1             package XML::Reader;
2             $XML::Reader::VERSION = '0.65';
3 1     1   895 use strict;
  1         2  
  1         34  
4 1     1   4 use warnings;
  1         1  
  1         27  
5 1     1   11 use Carp;
  1         1  
  1         58  
6              
7 1     1   469 use Acme::HTTP;
  1         2694  
  1         4944  
8              
9             set_timeout(10);
10             set_redir_max(5);
11              
12             require Exporter;
13              
14             our @ISA = qw(Exporter);
15             our %EXPORT_TAGS = ( all => [ qw(slurp_xml) ] );
16             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
17             our @EXPORT = qw();
18              
19             my $use_module;
20              
21             sub import {
22 1     1   9 my $calling_module = shift;
23              
24 1         1 my @plist;
25              
26             my $act_module;
27              
28 1         3 for my $sub (@_) {
29 0 0 0     0 if ($sub eq 'XML::Parser' or $sub eq 'XML::Parsepp') {
30 0 0       0 if (defined $act_module) {
31 0         0 die "Duplicate module ('$act_module' and '$sub')";
32             }
33 0         0 $act_module = $sub;
34             }
35             else {
36 0         0 push @plist, $sub;
37             }
38             }
39              
40 1 50       3 if (defined $act_module) {
41 0         0 activate($act_module);
42             }
43              
44 1         98 XML::Reader->export_to_level(1, $calling_module, @plist);
45             }
46              
47             sub activate {
48 0     0 0   my ($mod) = @_;
49              
50 0 0         if ($mod eq 'XML::Parser') {
    0          
51 0           require XML::Parser;
52             }
53             elsif ($mod eq 'XML::Parsepp') {
54 0           require XML::Parsepp;
55             }
56             else {
57 0           die "Can't identify module = '$mod'";
58             }
59              
60 0           $use_module = $mod;
61             }
62              
63             # deprecated functions (Klaus EICHNER, 28 Apr 2010, ver. 0.35):
64             # only for backward compatibility
65              
66             # Klaus EICHNER, 28 Oct 2011, ver 0.39):
67             # remove deprecated functions newhd() and rstem()
68              
69             # sub newhd { new(@_); } # newhd() is now deprecated, use new() instead
70             # sub rstem { path(@_); } # rstem() is now deprecated, use path() instead
71              
72             sub new {
73 0     0 0   my $class = shift;
74 0           my $self = {};
75              
76 0           my %opt;
77 0 0         %opt = %{$_[1]} if defined $_[1];
  0            
78              
79 0 0         if (defined $opt{mode}) {
80 0           my $flt;
81 0 0         if ($opt{mode} eq 'attr-bef-start') { $flt = 2; } # attributes appear on seperate lines * before * .
  0 0          
    0          
    0          
82 0           elsif ($opt{mode} eq 'attr-in-hash') { $flt = 3; } # no seperate lines for attributes, they appear in the hash %{$rdr->att_hash}.
83 0           elsif ($opt{mode} eq 'pyx') { $flt = 4; } # pyx compatible way: delivers attributes, , characters, on individual lines.
84 0           elsif ($opt{mode} eq 'branches') { $flt = 5; } # reads roots and branches: $rdr->rx, $rdr->rvalue and $rdr->rval
85             else {
86 0           croak "Failed assertion #0010 in XML::Reader->new: invalid mode = '$opt{mode}', expected 'attr-bef-start', 'attr-in-hash', 'pyx' or 'branches'";
87             }
88              
89 0 0         if (defined $opt{filter}) {
90 0 0         unless ($opt{filter} eq $flt) {
91 0           croak "Failed assertion #0020 in XML::Reader->new: filter = '$opt{filter}' does not match mode = '$opt{mode}' (which corresponds to filter = '$flt')";
92             }
93             }
94             else {
95 0           $opt{filter} = $flt;
96             }
97             }
98              
99 0 0         $opt{strip} = 1 unless defined $opt{strip};
100 0 0         $opt{filter} = 2 unless defined $opt{filter};
101 0 0         $opt{parse_pi} = 0 unless defined $opt{parse_pi};
102 0 0         $opt{parse_ct} = 0 unless defined $opt{parse_ct};
103              
104 0 0 0       unless ($opt{filter} == 2 or $opt{filter} == 3 or $opt{filter} == 4 or $opt{filter} == 5) {
      0        
      0        
105 0           croak "Failed assertion #0030 in XML::Reader->new: filter is set to '$opt{filter}', but must be 2, 3, 4 or 5";
106             }
107              
108 0           my @parser_opt;
109              
110 0 0 0       if (defined $opt{dupatt} and $opt{dupatt} ne '') {
111 0 0         unless ($use_module eq 'XML::Parsepp') {
112 0           croak "Failed assertion #0035 in XML::Reader->new: expected use qw(XML::Parsepp), but found use qw($use_module)";
113             }
114 0           @parser_opt = (dupatt => $opt{dupatt});
115             }
116              
117 0 0         my $XmlParser = $use_module->new(@parser_opt)
118             or croak "Failed assertion #0040 in XML::Reader->new: Can't create $use_module -> new(@parser_opt)";
119              
120             # The following references to the handler-functions from the XML::Parser/XML::Parsepp object will be
121             # copied into the ExpatNB object during the later call to XML::Parser/XML::Parsepp->parse_start.
122              
123 0           $XmlParser->setHandlers(
124             Start => \&handle_start,
125             End => \&handle_end,
126             Proc => \&handle_procinst,
127             XMLDecl => \&handle_decl,
128             Char => \&handle_char,
129             Comment => \&handle_comment,
130             );
131              
132             # We are trying to open the file (the filename is held in in $_[0]). If the filename
133             # happens to be a reference to a scalar, then it is opened quite naturally as an
134             # 'in-memory-file'. If the open fails, then we return failure from XML::Reader->new
135             # and the calling program has to check $! to handle the failed call.
136             # If, however, the filename is already a filehandle (i.e. ref($_[0]) eq 'GLOB'), then
137             # we use that filehandle directly
138              
139 0           my $fh;
140              
141 0 0         if (ref($_[0]) eq 'GLOB') {
142 0           $fh = $_[0];
143             }
144             else {
145 0 0         if ($_[0] =~ m{\A https?:}xms) {
146 0 0         $fh = Acme::HTTP->new($_[0])
147             or croak "Failed assertion #0042 in XML::Reader->new: Can't Acme::HTTP->new('$_[0]') because $@";
148             }
149             else {
150 0 0         open $fh, '<', $_[0] or croak "Failed assertion #0045 in XML::Reader->new: Can't open < '$_[0]' because $!";
151             }
152             }
153              
154             # Now we bless into XML::Reader, and we bless *before* creating the ExpatNB-object.
155             # Thereby, to avoid a memory leak, we ensure that for each ExpatNB-object we call
156             # XML::Reader->DESTROY when the object goes away. (-- by the way, we create that
157             # ExpatNB-object by calling the XML::Parser/XML::Parsepp->parse_start method --)
158              
159 0           bless $self, $class;
160              
161             # Now we are ready to call XML::Parser/XML::Parsepp->parse_start -- XML::Parser/XML::Parsepp->parse_start()
162             # returns an object of type XML::Parser/XML::Parsepp::ExpatNB. The XML::Parser/XML::Parsepp::ExpatNB object
163             # is where all the heavy lifting happens.
164              
165             # By calling the XML::Parser/XML::Parsepp::Expat->new method (-- XML::Parser::Expat is a super-class
166             # of XML::Parser::ExpatNB --) we will have created a circular reference in
167             # $self->{ExpatNB}{parser}.
168             #
169             # (-- unfortunately, the circular reference does not show up in Data::Dumper, there
170             # is just an integer in $self->{ExpatNB}{parser} that represents a data-structure
171             # within the C-function ParserCreate() --).
172             #
173             # See also the following line of code taken from XML::Parser::Expat->new:
174             #
175             # $args{Parser} = ParserCreate($self, $args{ProtocolEncoding}, $args{Namespaces});
176            
177             # This means that, in order to avoid a memory leak, we have to break this circular
178             # reference when we are done with the processing. The breaking of the circular reference
179             # will be performed in XML::Reader->DESTROY, which calls XML::Parser::Expat->release.
180              
181             # This is an important moment (-- in terms of memory management, at least --).
182             # XML::Parser/XML::Parsepp->parse_start creates an XML::Parser/XML::Parsepp::ExpatNB-object, which in turn generates
183             # a circular reference (invisible with Data::Dumper). That circular reference will have to
184             # be cleaned up when the XML::Reader-object goes away (see XML::Reader->DESTROY).
185              
186 0 0 0       $self->{ExpatNB} = $XmlParser->parse_start(
    0          
    0          
187             XR_Data => [],
188             XR_Text => '',
189             XR_Comment => '',
190             XR_fh => $fh,
191             XR_Att => [],
192             XR_ProcInst => [],
193             XR_Decl => {},
194             XR_Prv_SPECD => '',
195             XR_Emit_attr => ($opt{filter} == 3 ? 0 : 1),
196             XR_Split_up => ($opt{filter} == 4 || $opt{filter} == 5 ? 1 : 0),
197             XR_Strip => $opt{strip},
198             XR_ParseInst => $opt{parse_pi},
199             XR_ParseComm => $opt{parse_ct},
200             ) or croak "Failed assertion #0050 in subroutine XML::Reader->new: Can't create $use_module -> parse_start";
201              
202             # for XML::Reader, version 0.21 (12-Sep-2009):
203             # inject an {XR_debug} into $self->{ExpatNB}, if so requested by $opt{debug}
204              
205 0 0         if (exists $opt{debug}) { $self->{ExpatNB}{XR_debug} = $opt{debug}; }
  0            
206              
207             # The instruction "XR_Data => []" (-- the 'XR_...' prefix stands for 'Xml::Reader...' --)
208             # inside XML::Parser/XML::Parsepp->parse_start() creates an empty array $ExpatNB{XR_Data} = []
209             # inside the ExpatNB object. This array is the place where the handlers put their data.
210             #
211             # Likewise, the instructions "XR_Text => ''", "XR_Comment => ''", and "XR_fh => $fh" , etc...
212             # create corresponding elements inside the $ExpatNB-object.
213              
214 0 0         $self->{sepchar} = defined $opt{sepchar} ? $opt{sepchar} : '';
215 0           $self->{filter} = $opt{filter};
216 0 0         $self->{using} = !defined($opt{using}) ? [] : ref($opt{using}) ? $opt{using} : [$opt{using}];
    0          
217              
218             # ********************************************************************************************
219             # The following lines have been disabled by Klaus Eichner, 30 Oct 2009 (for version 0.29)
220             # ********************************************************************************************
221             # remove all spaces and then all leading and trailing '/', then put back a single leading '/'
222             # for my $check (@{$self->{using}}) {
223             # $check =~ s{\s}''xmsg;
224             # $check =~ s{\A /+}''xms;
225             # $check =~ s{/+ \z}''xms;
226             # $check = '/'.$check;
227             # }
228             # ********************************************************************************************
229              
230 0           $self->{bush} = [];
231 0           $self->{rlist} = [];
232              
233 0 0         if ($self->{filter} == 5) {
234 0           for my $object (@_[2..$#_]) {
235 0           $object->{brna} = [];
236              
237 0 0         if (ref($object->{branch}) eq 'ARRAY') {
238 0           for my $j (0..$#{$object->{branch}}) {
  0            
239 0           $object->{branch}[$j] =~ s{\A ([^/\s])}{/$1}xms;
240              
241 0           $object->{brna}[$j] = [];
242              
243 0           my $b_level = 0;
244 0           my $b_branch = $object->{branch}[$j];
245 0           $object->{branch}[$j] =~ s{\[ [^/\]]* \]}''xmsg;
246              
247 0           $b_branch =~ s{\A /+}''xms;
248              
249 0           for my $ele (split(m{/}xms, $b_branch)) {
250 0           $b_level++;
251              
252 0 0         if ($ele =~ m{\[ \@ ([^\[\]=\s]+) = ['"] ([^'"]*) ['"] \]}xms) {
253 0           push @{$object->{brna}[$j]}, [ $b_level - 1, $1, $2 ];
  0            
254             }
255             }
256             }
257             }
258              
259 0           $object->{rota} = [];
260              
261 0           my $a_level = 0;
262 0           my $a_root = $object->{root};
263 0           $object->{root} =~ s{\[ [^/\]]* \]}''xmsg;
264 0           $a_root =~ s{\A /+}''xms;
265              
266 0           for my $ele (split(m{/}xms, $a_root)) {
267 0           $a_level++;
268              
269 0 0         if ($ele =~ m{\[ \@ ([^\[\]=\s]+) = ['"] ([^'"]*) ['"] \]}xms) {
270 0           push @{$object->{rota}}, [ $a_level - 1, $1, $2 ];
  0            
271             }
272             }
273              
274 0 0 0       if ($object->{root} =~ m{\A // ([^/] .*) \z}xms
275             or $object->{root} =~ m{\A ([^/] .*) \z}xms) {
276 0           my $chunk = $1;
277 0           push @{$self->{rlist}}, {
  0            
278             root => undef,
279             qr1 => qr{\A (.*) / \Q$chunk\E \z}xms,
280             rota => $object->{rota},
281             qrfix => undef,
282             branch => $object->{branch},
283             brna => $object->{brna},
284             };
285             }
286             else {
287 0           push @{$self->{rlist}}, {
  0            
288             root => $object->{root},
289             rota => $object->{rota},
290             qr1 => undef,
291             qrfix => undef,
292             branch => $object->{branch},
293             brna => $object->{brna},
294             };
295             }
296             }
297              
298             #~ use Data::Dump;
299             #~ dd \@_;
300             }
301              
302 0           $self->{plist} = [];
303 0           $self->{alist} = [];
304 0           $self->{path} = '/';
305 0           $self->{prefix} = '';
306 0           $self->{tag} = '';
307 0           $self->{value} = '';
308 0           $self->{att_hash} = {};
309 0           $self->{dec_hash} = {};
310 0           $self->{comment} = '';
311 0           $self->{pyx} = '';
312 0           $self->{rx} = 0;
313 0           $self->{rvalue} = [];
314 0           $self->{rresult} = [];
315 0           $self->{proc} = '';
316 0           $self->{type} = '?';
317 0           $self->{is_start} = 0;
318 0           $self->{is_end} = 0;
319 0           $self->{is_decl} = 0;
320 0           $self->{is_proc} = 0;
321 0           $self->{is_comment} = 0;
322 0           $self->{is_text} = 0;
323 0           $self->{is_attr} = 0;
324 0           $self->{is_value} = 0;
325 0           $self->{level} = 0;
326 0           $self->{item} = '';
327              
328 0           return $self;
329             }
330              
331             # path() and value() are the two main functions:
332             # **********************************************
333              
334 0     0 1   sub path { $_[0]{path}; }
335              
336             sub value {
337 0 0   0 1   if ($_[0]{filter} == 5) {
338 0 0         ref $_[0]{rvalue} eq 'ARRAY' ? @{$_[0]{rvalue}} : $_[0]{rvalue};
  0            
339             }
340             else {
341 0           $_[0]{value};
342             }
343             }
344              
345 0     0 1   sub tag { $_[0]{tag}; }
346 0     0 1   sub attr { $_[0]{attr}; }
347 0     0 1   sub att_hash { $_[0]{att_hash}; }
348 0     0 1   sub dec_hash { $_[0]{dec_hash}; }
349 0     0 1   sub type { $_[0]{type}; }
350 0     0 1   sub level { $_[0]{level}; }
351 0     0 1   sub prefix { $_[0]{prefix}; }
352 0     0 1   sub comment { $_[0]{comment}; }
353 0     0 1   sub pyx { $_[0]{pyx}; }
354 0     0 1   sub rx { $_[0]{rx}; }
355 0     0 1   sub rvalue { $_[0]{rvalue}; }
356 0     0 1   sub proc_tgt { $_[0]{proc_tgt}; }
357 0     0 1   sub proc_data { $_[0]{proc_data}; }
358 0     0 1   sub is_decl { $_[0]{is_decl}; }
359 0     0 1   sub is_start { $_[0]{is_start}; }
360 0     0 1   sub is_proc { $_[0]{is_proc}; }
361 0     0 1   sub is_comment { $_[0]{is_comment}; }
362 0     0 1   sub is_text { $_[0]{is_text}; }
363 0     0 1   sub is_attr { $_[0]{is_attr}; }
364 0     0 1   sub is_value { $_[0]{is_value}; }
365 0     0 1   sub is_end { $_[0]{is_end}; }
366              
367 0     0 0   sub NB_data { $_[0]{ExpatNB}{XR_Data}; }
368 0     0 0   sub NB_fh { $_[0]{ExpatNB}{XR_fh}; }
369              
370             sub iterate {
371 0     0 1   my $self = shift;
372              
373             {
374 0 0         if ($self->{filter} == 5) {
  0            
375 0           my $res = shift @{$self->{rresult}};
  0            
376 0 0         if ($res) {
377 0           $self->{rx} = $res->[0];
378 0           $self->{rvalue} = $res->[1];
379 0           return 1;
380             }
381             }
382              
383 0           my $token = $self->get_token;
384 0 0         unless (defined $token) {
385 0           return;
386             }
387              
388 0 0         if ($token->found_start_tag) {
389 0           push @{$self->{plist}}, $token->extract_tag;
  0            
390 0           push @{$self->{alist}}, {};
  0            
391 0           redo;
392             }
393              
394 0 0         if ($token->found_end_tag) {
395 0           pop @{$self->{plist}};
  0            
396 0           pop @{$self->{alist}};
  0            
397 0           redo;
398             }
399              
400 0           my $prv_SPECD = $token->extract_prv_SPECD;
401 0           my $nxt_SPECD = $token->extract_nxt_SPECD;
402              
403 0           $self->{rx} = 0;
404 0           $self->{rvalue} = [];
405              
406 0 0         if ($token->found_text) {
    0          
407 0           my $text = $token->extract_text;
408 0           my $comment = $token->extract_comment;
409              
410 0           my $proc_tgt = '';
411 0           my $proc_data = '';
412 0 0         if (@{$token->extract_proc} == 2) {
  0            
413 0           $proc_tgt = ${$token->extract_proc}[0];
  0            
414 0           $proc_data = ${$token->extract_proc}[1];
  0            
415             }
416              
417 0 0         $self->{is_decl} = $prv_SPECD eq 'D' ? 1 : 0;
418 0 0         $self->{is_start} = $prv_SPECD eq 'S' ? 1 : 0;
419 0 0         $self->{is_proc} = $prv_SPECD eq 'P' ? 1 : 0;
420 0 0         $self->{is_comment} = $prv_SPECD eq 'C' ? 1 : 0;
421 0 0         $self->{is_end} = $nxt_SPECD eq 'E' ? 1 : 0;
422              
423 0           $self->{is_text} = 1;
424 0           $self->{is_attr} = 0;
425              
426 0           $self->{path} = '/'.join('/', @{$self->{plist}});
  0            
427 0           $self->{attr} = '';
428 0           $self->{value} = $text;
429 0           $self->{comment} = $comment;
430 0           $self->{proc_tgt} = $proc_tgt;
431 0           $self->{proc_data} = $proc_data;
432 0           $self->{level} = @{$self->{plist}};
  0            
433 0 0         $self->{tag} = @{$self->{plist}} ? ${$self->{plist}}[-1] : '';
  0            
  0            
434 0           $self->{type} = 'T';
435 0           $self->{att_hash} = {@{$token->extract_attr}};
  0            
436 0           $self->{dec_hash} = {@{$token->extract_decl}};
  0            
437              
438 0           for (keys %{$self->{att_hash}}) {
  0            
439 0           $self->{alist}[-1]{$_} = $self->{att_hash}{$_};
440             }
441             }
442             elsif ($token->found_attr) {
443 0           my $key = $token->extract_attkey;
444 0           my $val = $token->extract_attval;
445              
446 0           $self->{is_decl} = 0;
447 0           $self->{is_start} = 0;
448 0           $self->{is_proc} = 0;
449 0           $self->{is_comment} = 0;
450 0           $self->{is_end} = 0;
451              
452 0           $self->{is_text} = 0;
453 0           $self->{is_attr} = 1;
454              
455 0           $self->{path} = '/'.join('/', @{$self->{plist}}).'/@'.$key;
  0            
456 0           $self->{attr} = $key;
457 0           $self->{value} = $val;
458 0           $self->{comment} = '';
459 0           $self->{proc_tgt} = '';
460 0           $self->{proc_data} = '';
461 0           $self->{level} = @{$self->{plist}} + 1;
  0            
462 0           $self->{tag} = '@'.$key;
463 0           $self->{type} = '@';
464 0           $self->{att_hash} = {};
465 0           $self->{dec_hash} = {};
466              
467 0           $self->{alist}[-1]{$key} = $val;
468             }
469             else {
470 0           croak "Failed assertion #0060 in subroutine XML::Reader->iterate: Found data type '".$token->[0]."'";
471             }
472              
473             # for {filter => 4 or 5}
474             # - promote $self->{type} -- from 'T'/'@' to any of the following codes: 'D', '?', 'S', 'E', '#', 'T', '@'
475             # - update $self->{is_text}
476             # - setup $self->{pyx}
477              
478 0 0 0       if ($self->{filter} == 4 or $self->{filter} == 5) {
479 0 0         if ($self->{type} eq '@') { $self->{pyx} = 'A'.$self->{attr}.' '.$self->{value}; }
  0 0          
    0          
    0          
    0          
    0          
480 0           elsif ($self->{is_decl}) { my $dc = $self->{dec_hash};
481 0           $self->{type} = 'D'; $self->{pyx} = '?xml'.join('', map {" $_='$dc->{$_}'"} sort {$b cmp $a} keys %$dc); }
  0            
  0            
  0            
482 0           elsif ($self->{is_proc}) { $self->{type} = '?'; $self->{pyx} = '?'.$self->{proc_tgt}.' '.$self->{proc_data}; }
  0            
483 0           elsif ($self->{is_start}) { $self->{type} = 'S'; $self->{pyx} = '('.$self->{tag}; }
  0            
484 0           elsif ($self->{is_end}) { $self->{type} = 'E'; $self->{pyx} = ')'.$self->{tag}; }
  0            
485 0           elsif ($self->{is_comment}) { $self->{type} = '#'; $self->{pyx} = '#'.$self->{comment}; }
  0            
486 0           else { $self->{type} = 'T'; $self->{pyx} = '-'.$self->{value}; }
  0            
487 0           $self->{pyx} =~ s{\\}'\\\\'xmsg; # replace each backslash by a double-backslash
488 0           $self->{pyx} =~ s{\t}'\\t'xmsg; # replace tabs by a literal "\\t"
489 0           $self->{pyx} =~ s{\n}'\\n'xmsg; # replace newlines by a literal "\\n"
490              
491             # update $self->{is_text}
492 0 0         $self->{is_text} = $self->{type} eq 'T' ? 1 : 0;
493             }
494             else {
495 0           $self->{pyx} = undef;
496             }
497              
498 0 0 0       $self->{is_value} = ($self->{is_text} || $self->{is_attr}) ? 1 : 0;
499              
500             # for {filter => 5} check roots
501 0 0         if ($self->{filter} == 5) {
502 0           for my $r (0..$#{$self->{rlist}}) {
  0            
503 0           my $param = $self->{rlist}[$r];
504              
505 0           my $twig;
506             my $border;
507              
508 0           my $root;
509 0           my $rotn = 0;
510              
511 0 0         if (defined $param->{root}) {
    0          
    0          
512 0           $root = $param->{root};
513             }
514             elsif (defined $param->{qrfix}) {
515 0           $root = $param->{qrfix};
516             }
517             elsif (defined $param->{qr1}) {
518 0 0         if ($self->{path} =~ $param->{qr1}) { my $prf = $1;
  0            
519 0           $rotn = () = $prf =~ m{/}xmsg;
520 0           $root = $self->{path};
521 0           $param->{qrfix} = $root;
522             }
523             }
524              
525 0 0         if (defined $root) {
526 0 0         if ($root eq '/') {
527 0 0         if (@{$self->{plist}} == 1) {
  0 0          
  0            
528 0           $twig = $self->{path};
529 0           $border = 1;
530             }
531             elsif (@{$self->{plist}} > 1) {
532 0           $twig = $self->{path};
533 0           $border = 0;
534             }
535             }
536             else {
537 0 0         if ($self->{path} eq $root) {
    0          
538 0           $twig = '/';
539 0           $border = 1;
540             }
541             elsif (substr($self->{path}, 0, length($root) + 1) eq $root.'/') {
542 0           $twig = substr($self->{path}, length($root));
543 0           $border = 0;
544             }
545             }
546             }
547              
548 0 0         next unless defined $twig;
549              
550 0           my $block = 0;
551              
552             #~ if (@{$param->{rota}}) {
553             #~ use Data::Dump;
554             #~ print "\nDeb-0010: param->{rota}:\n";
555             #~ dd $param->{rota};
556             #~ print "\nDeb-0020: self->{alist}:\n";
557             #~ dd $self->{alist};
558             #~ }
559              
560 0           for (@{$param->{rota}}) {
  0            
561 0           my ($offset, $attr, $val) = ($_->[0] + $rotn, $_->[1], $_->[2]);
562              
563             #~ print "Deb-0030: offset = $offset ($_->[0] + $rotn), attr = '$attr', val = '$val'\n";
564              
565 0           my $e = $self->{alist}[$offset];
566              
567 0 0         unless ($e) {
568             #~ print "Deb-0100: Block-01\n";
569 0           $block++;
570 0           next;
571             }
572              
573 0           my $v = $e->{$attr};
574              
575 0 0         unless (defined $v) {
576             #~ print "Deb-0110: Block-02\n";
577 0           $block++;
578 0           next;
579             }
580              
581 0 0         unless ($v eq $val) {
582             #~ print "Deb-0120: Block-03\n";
583 0           $block++;
584 0           next;
585             }
586              
587             #~ print "Deb-0150: Good...\n";
588             }
589              
590 0 0         next if $block;
591              
592 0           my $bran;
593              
594 0 0         if ($root eq '/') {
595 0           $bran = 0;
596             }
597             else {
598 0           $bran = () = $root =~ m{/}xmsg;
599             }
600              
601 0 0         if (ref $param->{branch}) { # here we have an array of branches...
    0          
    0          
602 0 0 0       if ($border and $self->{is_start}) {
603 0           $self->{bush}[$r] = [];
604             }
605              
606 0 0         if ($self->{is_value}) {
607 0           for my $i (0..$#{$param->{branch}}) {
  0            
608 0 0         if ($param->{branch}[$i] eq $twig) {
609              
610 0           my $block = 0;
611              
612             #~ if (@{$param->{brna}[$i]}) {
613             #~ use Data::Dump;
614             #~ print "\nDeb-0010: param->{brna}[$i]:\n";
615             #~ dd $param->{brna}[$i];
616             #~ print "\nDeb-0020: self->{alist}:\n";
617             #~ dd $self->{alist};
618             #~ }
619              
620 0           for (@{$param->{brna}[$i]}) {
  0            
621 0           my ($offset, $attr, $val) = ($_->[0] + $bran, $_->[1], $_->[2]);
622              
623             #~ print "Deb-0030: offset = $offset ($_->[0] + $bran), attr = '$attr', val = '$val'\n";
624              
625 0           my $e = $self->{alist}[$offset];
626              
627 0 0         unless ($e) {
628             #~ print "Deb-0100: Block-01\n";
629 0           $block++;
630 0           next;
631             }
632              
633 0           my $v = $e->{$attr};
634              
635 0 0         unless (defined $v) {
636             #~ print "Deb-0110: Block-02\n";
637 0           $block++;
638 0           next;
639             }
640              
641 0 0         unless ($v eq $val) {
642             #~ print "Deb-0120: Block-03\n";
643 0           $block++;
644 0           next;
645             }
646              
647             #~ print "Deb-0150: Good...\n";
648             }
649              
650 0 0         unless ($block) {
651 0           my $ref = \$self->{bush}[$r][$i];
652 0 0         $$ref .= (defined $$ref ? $self->{sepchar} : '').$self->{value};
653             }
654             }
655             }
656             }
657             }
658             elsif ($param->{branch} eq '+') { # collect PYX array, addition for ver 0.39 (Klaus Eichner, 28th Oct 2011)
659 0 0 0       if ($border and $self->{is_start}) {
660 0           $self->{bush}[$r] = [];
661             }
662 0           push @{$self->{bush}[$r]}, $self->{pyx};
  0            
663             }
664             elsif ($param->{branch} eq '*') { # collect pure XML data, addition for ver 0.34 (Klaus Eichner, 26th Apr 2010)
665 0 0 0       if ($border and $self->{is_start}) {
666 0           $self->{bush}[$r] = '';
667             }
668              
669 0           my $element = '';
670 0 0         if ($self->{is_decl}) {
671 0           $element .= '
672 0           for my $key (sort keys %{$self->{dec_hash}}) {
  0            
673 0           my $kval = $self->{dec_hash}{$key};
674 0           $kval =~ s{&}'&'xmsg;
675 0           $kval =~ s{'}'''xmsg;
676 0           $kval =~ s{<}'<'xmsg;
677 0           $kval =~ s{>}'>'xmsg;
678 0           $element .= qq{ $key='$kval'};
679             }
680 0           $element .= '?>';
681             }
682 0 0         if ($self->{is_start}) {
683 0           $element .= '<'.$self->{tag};
684 0           for my $key (sort keys %{$self->{att_hash}}) {
  0            
685 0           my $kval = $self->{att_hash}{$key};
686 0           $kval =~ s{&}'&'xmsg;
687 0           $kval =~ s{'}'''xmsg;
688 0           $kval =~ s{<}'<'xmsg;
689 0           $kval =~ s{>}'>'xmsg;
690 0           $element .= qq{ $key='$kval'};
691             }
692 0           $element .= '>';
693             }
694 0 0         if ($self->{is_proc}) {
695 0           my $tgt = $self->{proc_tgt};
696 0           my $dat = $self->{proc_data};
697 0           for ($tgt, $dat) {
698 0           s{&}'&'xmsg;
699 0           s{'}'''xmsg;
700 0           s{<}'<'xmsg;
701 0           s{>}'>'xmsg;
702             }
703 0           $element .= "";
704             }
705 0 0         if ($self->{is_text}) {
706 0           my $tval = $self->{value};
707 0 0         if ($tval ne '') {
708 0           $tval =~ s{&}'&'xmsg;
709 0           $tval =~ s{<}'<'xmsg;
710 0           $tval =~ s{>}'>'xmsg;
711 0           $element .= $tval;
712             }
713             }
714 0 0         if ($self->{is_comment}) {
715 0           my $tval = $self->{comment};
716 0           $tval =~ s{&}'&'xmsg;
717 0           $tval =~ s{<}'<'xmsg;
718 0           $tval =~ s{>}'>'xmsg;
719 0           $element .= "";
720             }
721 0 0         if ($self->{is_end}) {
722 0           $element .= '{tag}.'>';
723             }
724              
725 0           $self->{bush}[$r] .= $element;
726             }
727              
728 0 0 0       if ($border and $self->{is_end}) {
729 0           push @{$self->{rresult}}, [$r, $self->{bush}[$r]];
  0            
730 0           $param->{qrfix} = undef;
731             }
732             }
733 0           redo;
734             }
735              
736             # Here we check for the {using => ...} option
737 0           $self->{prefix} = '';
738              
739 0           for my $check (@{$self->{using}}) {
  0            
740 0 0         if ($check eq $self->{path}) {
741 0           $self->{prefix} = $check;
742 0           $self->{path} = '/';
743 0           $self->{level} = 0;
744 0           $self->{tag} = ''; # unfortunately we have to nullify the tag here...
745 0           last;
746             }
747 0 0         if ($check.'/' eq substr($self->{path}, 0, length($check) + 1)) { my @temp = split m{/}xms, $check;
  0            
748 0           $self->{prefix} = $check;
749 0           $self->{path} = substr($self->{path}, length($check));
750 0           $self->{level} -= @temp - 1;
751 0           last;
752             }
753             }
754              
755             # check if option {using => ...} has been requested, and if so, then skip all
756             # lines that don't have a prefix...
757 0 0 0       if (@{$self->{using}} and $self->{prefix} eq '') {
  0            
758 0           redo;
759             }
760             }
761              
762 0           return 1;
763             }
764              
765             sub get_token {
766 0     0 0   my $self = shift;
767              
768 0           until (@{$self->NB_data}) {
  0            
769             # Here is the all important reading of a chunk of XML-data from the filehandle...
770              
771 0           my $buf;
772              
773 0 0         if (ref($self->NB_fh) eq 'Acme::HTTP') {
774 0           my $ct = $self->NB_fh->read_entity_body($buf, 4096); # returns number of bytes read, or undef if IO-Error
775 0 0         last unless $ct;
776             }
777             else {
778 0           read($self->NB_fh, $buf, 4096);
779             }
780              
781             # We leave immediately as soon as there is no more data left (EOF)
782 0 0         last if $buf eq '';
783              
784             # and here is the all important parsing of that chunk:
785             # and we could get exceptions thrown here if the XML is invalid...
786            
787 0           $self->{ExpatNB}->parse_more($buf);
788              
789             # ...the recommended way to catch those exceptions is not here, but by wrapping
790             # eval{} around $rdr->iterate like follows
791             #
792             # while (eval{$rdr->iterate}) {
793             # my $text = $rdr->value;
794             # # ...
795             # }
796             # if ($@) {
797             # print "found an error: $@\n";
798             # }
799             }
800              
801             # return failure if end-of-file...
802 0 0         unless (@{$self->NB_data}) {
  0            
803 0           return;
804             }
805              
806 0           my $token = shift @{$self->NB_data};
  0            
807 0           bless $token, 'XML::Reader::Token';
808             }
809             sub handle_decl {
810 0     0 0   my ($ExpatNB, $ver, $encoding, $standalone) = @_;
811              
812 0 0         return unless $ExpatNB->{XR_ParseInst};
813              
814 0           convert_structure($ExpatNB, 'D');
815 0 0         $ExpatNB->{XR_Decl} = [(defined $ver ? (version => $ver) : ()),
    0          
    0          
    0          
816             (defined $encoding ? (encoding => $encoding) : ()),
817             (defined $standalone ? (standalone => ($standalone ? 'yes' : 'no')) : ()),
818             ];
819             }
820              
821             sub handle_procinst {
822 0     0 0   my ($ExpatNB, $target, $data) = @_;
823              
824 0 0         return unless $ExpatNB->{XR_ParseInst};
825              
826 0           convert_structure($ExpatNB, 'P');
827 0           $ExpatNB->{XR_ProcInst} = [$target, $data];
828             }
829              
830             sub handle_comment {
831 0     0 0   my ($ExpatNB, $comment) = @_;
832              
833 0 0         return unless $ExpatNB->{XR_ParseComm};
834              
835 0           convert_structure($ExpatNB, 'C');
836 0           $ExpatNB->{XR_Comment} = $comment;
837             }
838              
839             sub handle_start {
840 0     0 0   my ($ExpatNB, $element, @attr) = @_;
841              
842 0           convert_structure($ExpatNB, 'S');
843 0           $ExpatNB->{XR_Att} = \@attr;
844 0           push @{$ExpatNB->{XR_Data}}, ['<', $element];
  0            
845             }
846              
847             sub handle_end {
848 0     0 0   my ($ExpatNB, $element) = @_;
849              
850 0           convert_structure($ExpatNB, 'E');
851 0           push @{$ExpatNB->{XR_Data}}, ['>', $element];
  0            
852             }
853              
854             sub handle_char {
855 0     0 0   my ($ExpatNB, $text) = @_;
856              
857 0           $ExpatNB->{XR_Text} .= $text;
858             }
859              
860             sub convert_structure {
861 0     0 0   my ($ExpatNB, $Param_SPECD) = @_; # $Param_SPECD can be either 'S', 'P', 'E', 'C' or 'D' (or even '*')
862              
863             # These are the text and comment that may be stripped
864 0           my $text = $ExpatNB->{XR_Text};
865 0           my $comment = $ExpatNB->{XR_Comment};
866              
867             # strip spaces if requested...
868 0 0         if ($ExpatNB->{XR_Strip}) {
869 0           for my $item ($text, $comment) {
870 0           $item =~ s{\A \s+}''xms;
871 0           $item =~ s{\s+ \z}''xms;
872 0           $item =~ s{\s+}' 'xmsg;
873             }
874             }
875              
876             # Don't do anything for the first tag...
877 0 0         unless ($ExpatNB->{XR_Prv_SPECD} eq '') {
878             # Here we save the previous 'SPECD' and the current (i.e. next) 'SPECD' into lexicals
879             # so that we can manipulate them
880 0           my $prev_SPECD = $ExpatNB->{XR_Prv_SPECD};
881 0           my $next_SPECD = $Param_SPECD;
882              
883             # Do we want , , and split up into separate lines ?
884 0 0         if ($ExpatNB->{XR_Split_up}) {
885 0 0         if ($prev_SPECD ne 'E') {
886             # emit the opening tag with empty text
887 0           push @{$ExpatNB->{XR_Data}},
  0            
888             ['T', '', $comment, $prev_SPECD, '*', $ExpatNB->{XR_Att}, $ExpatNB->{XR_ProcInst}, $ExpatNB->{XR_Decl}];
889             }
890              
891 0 0         if ($ExpatNB->{XR_Emit_attr}) {
892             # Here we emit attributes on their proper lines -- *after* the start-line (see above) ...
893 0           my %at = @{$ExpatNB->{XR_Att}};
  0            
894 0           for my $key (sort keys %at) {
895 0           push @{$ExpatNB->{XR_Data}}, ['A', $key, $at{$key}];
  0            
896             }
897             }
898              
899             # emit text (only if it is not empty)
900 0 0         unless ($text eq '') {
901 0           push @{$ExpatNB->{XR_Data}},
  0            
902             ['T', $text, '', '-', '*', [], [], []];
903             }
904              
905 0 0         if ($next_SPECD eq 'E') {
906             # emit the closing tag with empty text
907 0           push @{$ExpatNB->{XR_Data}},
  0            
908             ['T', '', '', '*', $next_SPECD, [], [], []];
909             }
910             }
911             # Here we don't want , , and split up into separate lines !
912             else {
913             # Do we really want to emit attributes on their proper lines ? -- or do we just
914             # want to publish the attributes on element ${$ExpatNB->{XR_Data}}[5] ?
915 0 0         if ($ExpatNB->{XR_Emit_attr}) {
916              
917 0           my %at = @{$ExpatNB->{XR_Att}};
  0            
918              
919             # Here we emit attributes on their proper lines -- *before* the start line (see below) ...
920 0           for my $key (sort keys %at) {
921 0           push @{$ExpatNB->{XR_Data}}, ['A', $key, $at{$key}];
  0            
922             }
923             }
924              
925             # And here we emit the text
926 0           push @{$ExpatNB->{XR_Data}},
  0            
927             ['T', $text, $comment, $prev_SPECD, $next_SPECD, $ExpatNB->{XR_Att}, $ExpatNB->{XR_ProcInst}, $ExpatNB->{XR_Decl}];
928             }
929             }
930              
931             # Initialise values:
932 0           $ExpatNB->{XR_Text} = '';
933 0           $ExpatNB->{XR_Comment} = '';
934 0           $ExpatNB->{XR_Att} = [];
935 0           $ExpatNB->{XR_ProcInst} = [];
936 0           $ExpatNB->{XR_Decl} = [];
937              
938 0           $ExpatNB->{XR_Prv_SPECD} = $Param_SPECD;
939             }
940              
941             sub DESTROY {
942 0     0     my $self = shift;
943              
944             # There are circular references inside an XML::Parser::ExpatNB-object
945             # which need to be cleaned up by calling XML::Parser::Expat->release.
946              
947             # I quote from the documentation of 'XML::Parser::Expat' (-- XML::Parser::Expat
948             # is a super-class of XML::Parser::ExpatNB --)
949             #
950             # >> ------------------------------------------------------------------------
951             # >> =item release
952             # >>
953             # >> There are data structures used by XML::Parser::Expat that have circular
954             # >> references. This means that these structures will never be garbage
955             # >> collected unless these references are explicitly broken. Calling this
956             # >> method breaks those references (and makes the instance unusable.)
957             # >>
958             # >> Normally, higher level calls handle this for you, but if you are using
959             # >> XML::Parser::Expat directly, then it's your responsibility to call it.
960             # >> ------------------------------------------------------------------------
961              
962             # There is a possibility that the XML::Parser::ExpatNB-object did not get
963             # created, while still blessing the XML::Reader object. Therefore we have to
964             # test for this case before calling XML::Parser::ExpatNB->release.
965              
966 0 0         if ($self->{ExpatNB}) {
967 0           $self->{ExpatNB}->release; # ...and not $self->{ExpatNB}->parse_done;
968             }
969             }
970              
971             sub slurp_xml {
972 0     0 1   my $data = shift;
973              
974 0           my @roots;
975 0           my $filter = { filter => 5 };
976              
977 0           for my $r (@_) {
978 0 0         if (defined $r->{dupatt}) {
979 0           $filter->{dupatt} = $r->{dupatt};
980             }
981             else {
982 0           push @roots, $r;
983             }
984             }
985              
986 0           my @tree = map {[]} @roots; # start with as many empty lists as there are roots
  0            
987              
988 0           my $rdr = XML::Reader->new($data, $filter, @roots);
989              
990 0           while ($rdr->iterate) {
991 0           push @{$tree[$rdr->rx]}, $rdr->rvalue;
  0            
992             }
993              
994 0           return \@tree;
995             }
996              
997             # The package used here - XML::Reader::Token
998             # has been inspired by XML::TokeParser::Token
999              
1000             package XML::Reader::Token;
1001             $XML::Reader::Token::VERSION = '0.65';
1002 0     0     sub found_start_tag { $_[0][0] eq '<'; }
1003 0     0     sub found_end_tag { $_[0][0] eq '>'; }
1004 0     0     sub found_attr { $_[0][0] eq 'A'; }
1005 0     0     sub found_text { $_[0][0] eq 'T'; }
1006              
1007 0     0     sub extract_tag { $_[0][1]; } # type eq '<' or '>'
1008              
1009 0     0     sub extract_attkey { $_[0][1]; } # type eq 'A'
1010 0     0     sub extract_attval { $_[0][2]; } # type eq 'A'
1011              
1012 0     0     sub extract_text { $_[0][1]; } # type eq 'T'
1013 0     0     sub extract_comment { $_[0][2]; } # type eq 'T'
1014              
1015 0     0     sub extract_prv_SPECD { $_[0][3]; } # type eq 'T'
1016 0     0     sub extract_nxt_SPECD { $_[0][4]; } # type eq 'T'
1017 0     0     sub extract_attr { $_[0][5]; } # type eq 'T'
1018 0     0     sub extract_proc { $_[0][6]; } # type eq 'T'
1019 0     0     sub extract_decl { $_[0][7]; } # type eq 'T'
1020              
1021             1;
1022              
1023             __END__