File Coverage

blib/lib/XML/SAX/Machine.pm
Criterion Covered Total %
statement 258 377 68.4
branch 115 218 52.7
condition 42 83 50.6
subroutine 23 34 67.6
pod 9 11 81.8
total 447 723 61.8


line stmt bran cond sub pod time code
1             package XML::SAX::Machine;
2             {
3             $XML::SAX::Machine::VERSION = '0.46';
4             }
5             # ABSTRACT: Manage a collection of SAX processors
6              
7              
8              
9 14     14   7086 use strict;
  14         24  
  14         739  
10              
11 14     14   118 use constant has_named_regexp_character_classes => $] > 5.006000;
  14         26  
  14         1444  
12              
13 14     14   72 use Carp;
  14         26  
  14         902  
14 14     14   20813 use UNIVERSAL;
  14         206  
  14         71  
15 14     14   11362 use XML::SAX::EventMethodMaker qw( :all );
  14         43  
  14         9053  
16 14     14   947 use XML::SAX::Machines;
  14         30  
  14         420  
17              
18             ## Tell the config stuff what options we'll be requesting, so we
19             ## don't get typoes in this code. Very annoying, but I mispelt it
20             ## so often, that adding one statement like this seemed like a low
21             ## pain solution, since testing options like this can be long and
22             ## bothersome.
23             XML::SAX::Machines->expected_processor_class_options(qw(
24             ConstructWithHashedOptions
25             ));
26              
27              
28              
29             sub new {
30 63     63 1 17528 my $proto = shift;
31 63   33     441 my $class = ref $proto || $proto;
32              
33 63 100 100     439 my @options_if_any = @_ && ref $_[-1] eq "HASH" ? %{pop()} : ();
  17         48  
34 63         246 my $self = bless { @options_if_any }, $class;
35              
36 63         250 $self->{Parts} = [];
37 63         165 $self->{PartsByName} = {}; ## Mapping of names to parts
38              
39 63         239 $self->_compile_specs( @_ );
40              
41             ## Set this last in case any specs have handler "Exhaust"
42 47 100       165 $self->set_handler( $self->{Handler} ) if $self->{Handler};
43              
44 47         414 return $self;
45             }
46              
47              
48             sub _find_part_rec {
49 239     239   458 my $self = shift;
50 239         310 my ( $id ) = @_;
51              
52 239 50       4895 if ( ref $id ) {
53 0   0     0 return exists $self->{PartsByProcessor}->{$id}
54             && $self->{PartsByProcessor}->{$id};
55             }
56              
57 239 100       1294 if ( $id =~ /^[+-]?\d+(?!\n)$/ ) {
58             return undef
59 58         215 if $id > $#{$self->{Parts}}
  56         226  
60 58 100 66     76 || $id < - ( $#{$self->{Parts}} + 1 );
61 56         292 return $self->{Parts}->[$id];
62             }
63              
64 181 100       851 return $self->{PartsByName}->{$id}
65             if exists $self->{PartsByName}->{$id};
66              
67 22         54 return undef;
68             }
69              
70              
71             sub find_part {
72 104     104 1 1752 my $self = shift;
73 104         176 my ( $spec ) = @_;
74              
75 104 50       298 return $self->{Handler} if $spec eq "Exhaust";
76              
77 104         128 my $part_rec;
78              
79 104 100       300 if ( 0 <= index $spec, "/" ) {
80             ## Take the sloooow road...
81 6         32 require File::Spec::Unix;
82 6 50       52 croak "find_part() path not absolute: '$spec'"
83             unless File::Spec::Unix->file_name_is_absolute( $spec );
84              
85             ## Cannonical-ize it, do /foo/../ => / conversion
86 6         25 $spec = File::Spec::Unix->canonpath( $spec );
87 6         14 1 while $spec =~ s{/[^/]+/\.\.(/|(?!\n\Z))}{$1};
88              
89 6         27 my @names = File::Spec::Unix->splitdir( $spec );
90 6   33     30 pop @names while @names && ! length $names[-1];
91 6   66     48 shift @names while @names && ! length $names[0];
92              
93 6 50       31 croak "invalid find_part() specification: '$spec'"
94             unless File::Spec::Unix->file_name_is_absolute( $spec );
95              
96 6         7 my @audit_trail;
97 6         7 my $proc = $self;
98 6         8 for ( @names ) {
99 9         10 push @audit_trail, $_;
100 9         16 $part_rec = $proc->_find_part_rec( $_ );
101 9 50       15 unless ( $part_rec ) {
102 0         0 croak "find_path() could not find '",
103             join( "/", "", @audit_trail ),
104             "' in ", ref $self;
105             }
106 9         22 $proc = $part_rec->{Processor};
107             }
108             }
109             else {
110 98         265 $part_rec = $self->_find_part_rec( $spec );
111             }
112              
113 104 50       265 croak "find_path() could not find '$spec' in ", ref $self
114             unless $part_rec;
115              
116 104         232 my $proc = $part_rec->{Processor};
117              
118             ## Be paranoid here, just in case we have a bug somewhere. I prefer
119             ## getting reasonable bug reports...
120 104 50       402 confess "find_path() found an undefined Processor reference as part '$_[0]' in ",
121             ref $self
122             unless defined $proc;
123              
124 104 50       245 confess "find_path() found '$proc' instead of a Processor reference as part '$_[0]' in ",
125             ref $self
126             unless ref $proc;
127              
128 104 50       280 confess "find_path() found a ",
129             ref $proc,
130             " reference instead of a Processor reference in part '$_[0]' in ",
131             ref $self
132             unless index( "SCALAR|ARRAY|HASH|Regexp|REF|CODE", ref $proc ) <= 0;
133              
134 104         3129 return $proc;
135             }
136              
137              
138 14     14   92 use vars qw( $AUTOLOAD );
  14         46  
  14         26561  
139              
140 0     0   0 sub DESTROY {} ## Prevent AUTOLOADing of this.
141              
142             my $alpha_first_char = has_named_regexp_character_classes
143             ? "^[[:alpha:]]"
144             : "^[a-zA-Z]";
145              
146             sub AUTOLOAD {
147 4     4   38 my $self = shift;
148              
149 4         16 $AUTOLOAD =~ s/.*://;
150              
151 4         11 my $fc = substr $AUTOLOAD, 0, 1;
152             ## TODO: Find out how Perl determines "alphaness" and use that.
153 4 50 33     93 croak ref $self, " does not provide method $AUTOLOAD"
154             unless $fc eq uc $fc && $AUTOLOAD =~ /$alpha_first_char/o;
155              
156 4         15 my $found = $self->find_part( $AUTOLOAD );
157 4         27 return $found;
158             }
159              
160              
161             sub parts {
162 11     11 1 12 my $self = shift;
163 11 50       18 croak "Can't set parts for a '", ref( $self ), "'" if @_;
164 11 50       24 confess "undef Parts" unless defined $self->{Parts};
165 11         13 return map $_->{Processor}, @{$self->{Parts}};
  11         36  
166             }
167              
168              
169             ## TODO: Detect deep recursion in _all_part_recs(). In fact, detect deep
170             ## recursion when building the machine.
171              
172             sub _all_part_recs {
173 0     0   0 my $self = shift;
174 0 0       0 croak "Can't pass parms to ", ref( $self ), "->_all_part_recs" if @_;
175 0 0       0 confess "undef Parts" unless defined $self->{Parts};
176 0         0 my $proc;
177 0         0 return map {
178 0         0 $proc = $_->{Processor};
179 0 0       0 UNIVERSAL::can( $proc, "all_parts" )
180             ? ( $_, $proc->_all_part_recs )
181             : $_;
182 0         0 } @{$self->{Parts}};
183             }
184              
185              
186             sub all_parts {
187 0     0 1 0 my $self = shift;
188 0 0       0 croak "Can't pass parms to ", ref( $self ), "->_all_parts" if @_;
189 0 0       0 confess "undef Parts" unless defined $self->{Parts};
190 0         0 return map $_->{Processor}, $self->_all_part_recs;
191             }
192              
193              
194             #=item add_parts
195             #
196             # $m->add_parts( { Foo => $foo, Bar => $bar } );
197             #
198             #On linear machines:
199             #
200             # $m->add_parts( @parts );
201             #
202             #Adds one or more parts to the machine. Does not connect them, you need
203             #to do that manually (we need to add a $m->connect_parts() style API).
204             #
205             #=cut
206             #
207             #sub add_parts {
208             # my $self = shift;
209             #confess "TODO";
210             #}
211              
212             #=item remove_parts
213             #
214             # $m->remove_parts( qw( Foo Bar ) );
215             #
216             #Slower, but possible:
217             #
218             # $m->remove_parts( $m->Foo, $m->Bar );
219             #
220             #On linear machines:
221             #
222             # $m->remove_parts( 1, 3 );
223             #
224             #Removes one or more parts from the machine. Does not connect them
225             #except on linear machines. Attempts to disconnect any parts that
226             #point to them, and that they point to. This attempt will fail for any
227             #part that does not provide a handler() or handlers() method.
228             #
229             #This is breadth-first recursive, like C<$m->find_part( $id )> is. This
230             #will remove *all* parts with the given names from a complex
231             #machine (this does not apply to index numbers).
232             #
233             #Returns a list of the removed parts.
234             #
235             #If a name is not found, it is ignored.
236             #
237             #=cut
238             #
239             #sub remove_parts {
240             # my $self = shift;
241             #
242             # my %names;
243             # my @found;
244             #
245             # for my $doomed ( @_ ) {
246             # unless ( ref $doomed ) {
247             # $names{$doomed} = undef;
248             # if ( my $f = delete $self->{Parts}->{$doomed} ) {
249             # push @found, $f;
250             # }
251             # else {
252             # for my $c ( $self->parts ) {
253             # if ( $c->can( "remove_parts" )
254             # && ( my @f = $c->remove_parts( $doomed ) )
255             # ) {
256             # push @found, @f;
257             # }
258             # }
259             # }
260             # }
261             # else {
262             # ## It's a reference. Do this the slow, painful way.
263             # for my $name ( keys %{$self->{Parts}} ) {
264             # if ( $doomed == $self->{Parts}->{$name} ) {
265             # $names{$name} = undef;
266             # push @found, delete $self->{Parts}->{$name};
267             # }
268             # }
269             #
270             # for my $c ( $self->parts ) {
271             # if ( $c->can( "remove_parts" )
272             # && ( my @f = $c->remove_parts( $doomed ) )
273             # ) {
274             # push @found, @f;
275             # }
276             # }
277             # }
278             # }
279             #
280             # for my $c ( sort keys %{$self->{Connections}} ) {
281             # if ( exists $names{$self->{Connections}->{$c}} ) {
282             ###TODO: Unhook the processors if possible
283             # delete $self->{Connections}->{$c};
284             # }
285             # if ( exists $names{$c} ) {
286             ###TODO: Unhook the processors if possible
287             # delete $self->{Connections}->{$c};
288             # }
289             # }
290             #
291             # return @found;
292             #}
293              
294              
295             sub set_handler {
296 10     10 1 22 my $self = shift;
297 10         24 my ( $handler, $type ) = reverse @_;
298              
299 10   50     51 $type ||= "Handler";
300              
301 10         10 for my $part_rec ( @{$self->{Parts}} ) {
  10         124  
302 15         199 my $proc = $part_rec->{Processor};
303 15         30 my $hs = $part_rec->{Handlers};
304              
305 15 50       181 if ( grep ref $_ ? $_ == $self->{$type} : $_ eq "Exhaust", @$hs ) {
    100          
306 6 100 66     74 if ( @$hs == 1 && $proc->can( "set_handler" ) ) {
307 5 50       30 $proc->set_handler(
308             $type ne "Handler" ? $type : (),
309             $handler
310             );
311 5         88 next;
312             }
313              
314 1 50       15 unless ( $proc->can( "set_handlers" ) ) {
315 0 0       0 croak ref $proc,
316             @$hs == 1
317             ? " has no set_handler or set_handlers method"
318             : " has no set_handlers method"
319             }
320              
321             $proc->set_handlers(
322 2         3 map {
323 1         2 my $h;
324             my $t;
325 2 50       8 if ( ref $_ ) {
    100          
326 0         0 $h = $_;
327 0         0 $t = "Handler";
328             }
329             elsif ( $_ eq "Exhaust" ) {
330 1         2 $h = $handler;
331 1         2 $t = $type;
332             } else {
333 1         6 ( $h, $t ) = reverse split /=>/, $_;
334 1         8 $h = $self->find_part( $h );
335 1         2 $t = $type;
336 1 50       4 croak "Can't locate part $_ to be a handler for ",
337             $part_rec->string_description
338             unless $h;
339             }
340 2         3 { $type => $h }
  2         16  
341             } @$hs
342             );
343             }
344             }
345              
346 10         27 $self->{$type} = $handler;
347             }
348              
349              
350             my $warned_about_missing_sax_tracer;
351             sub trace_parts {
352 0     0 1 0 my $self = shift;
353              
354 0 0       0 unless ( eval "require Devel::TraceSAX; 1" ) {
355 0 0       0 warn $@ unless $warned_about_missing_sax_tracer++;
356 0         0 return;
357             }
358              
359              
360 0 0       0 for ( @_ ? map $self->_find_part_rec( $_ ), @_ : @{$self->{Parts}} ) {
  0         0  
361 0         0 Devel::TraceSAX::trace_SAX(
362             $_->{Processor},
363             $_->string_description
364             );
365             }
366              
367             ## some parts are created lazily, let's trace those, too
368 0 0 0     0 $self->{TraceAdHocParts} ||= 1 unless @_;
369             }
370              
371              
372              
373             sub trace_all_parts {
374 0     0 1 0 my $self = shift;
375              
376 0 0       0 croak "Can't pass parms to trace_all_parts" if @_;
377              
378 0 0       0 unless ( eval "require Devel::TraceSAX; 1" ) {
379 0 0       0 warn $@ unless $warned_about_missing_sax_tracer++;
380 0         0 return;
381             }
382              
383 0         0 for ( @{$self->{Parts}} ) {
  0         0  
384 0         0 Devel::TraceSAX::trace_SAX(
385             $_->{Processor},
386             $_->string_description
387             );
388 0 0       0 $_->{Processor}->trace_all_parts
389             if $_->{Processor}->can( "trace_all_parts" );
390             }
391              
392             ## some parts are created lazily, let's trace those, too
393 0         0 $self->{TraceAdHocParts} = 1;
394             }
395              
396              
397              
398             sub untracify_parts {
399 0     0 1 0 my $self = shift;
400 0 0       0 for ( @_ ? map $self->find_part( $_ ), @_ : $self->parts ) {
401 0         0 XML::SAX::TraceViaISA::remove_tracing_subclass( $_ );
402             }
403             }
404              
405              
406              
407             compile_methods __PACKAGE__, <<'EOCODE', sax_event_names "ParseMethods" ;
408             sub {
409             my $self = shift;
410             my $h = $self->find_part( "Intake" );
411             croak "SAX machine 'Intake' undefined"
412             unless $h;
413              
414             if ( $h->can( "" ) ) {
415             my ( $ok, @result ) = eval {
416             ( 1, wantarray
417             ? $h->( @_ )
418             : scalar $h->( @_ )
419             );
420             };
421            
422             ## Not sure how/where causes me to need this next line, but
423             ## in perl5.6.1 it seems necessary.
424             return wantarray ? @result : $result[0] if $ok;
425             die $@ unless $@ =~ /No .*routine defined/;
426             undef $@;
427              
428             if ( $h->isa( "XML::SAX::Base" ) ) {
429             ## Due to a bug in old versions of X::S::B, we need to reset
430             ## this so that it will pass events on.
431             ## TODO: when newer X::S::B's are common, jack up the
432             ## version in Makefile.PL's PREREQ_PM :).
433             delete $h->{ParseOptions};
434             }
435             }
436              
437             require XML::SAX::ParserFactory;
438             $self->{Parser} = XML::SAX::ParserFactory->parser(
439             Handler => $h
440             );
441              
442             Devel::TraceSAX::trace_SAX(
443             $self->{Parser},
444             "Ad hoc parser (" . ref( $self->{Parser} ) . ")"
445             ) if $self->{TraceAdHocParts};
446              
447             return $self->{Parser}->(@_);
448             }
449             EOCODE
450              
451              
452             compile_methods __PACKAGE__, <<'EOCODE', sax_event_names ;
453             sub {
454             my $self = shift;
455             my $h = $self->find_part( "Intake" );
456             croak "SAX machine 'Intake' undefined"
457             unless $h;
458              
459             return $h->( @_ ) if $h->can( "" );
460             }
461             EOCODE
462              
463              
464              
465             my %basic_types = (
466             ARRAY => undef,
467             CODE => undef,
468             GLOB => undef,
469             HASH => undef,
470             REF => undef, ## Never seen this one, but it's listed in perlfunc
471             Regexp => undef,
472             SCALAR => undef,
473             );
474              
475              
476             sub _resolve_spec {
477 107     107   156 my $self = shift;
478 107         157 my ( $spec ) = @_;
479              
480 107 100       564 croak "undef passed instead of a filter to ", ref( $self ), "->new()"
481             unless defined $spec;
482              
483 106 100       1000 croak "Empty filter name ('') passed to ", ref( $self ), "->new()"
484             unless length $spec;
485              
486 105         158 my $type = ref $spec;
487              
488 105 100 100     1657 if (
    100 66        
      66        
      66        
489             $type eq "SCALAR"
490             ## TODO: || $type eq "ARRAY" <== need XML::SAX::Writer to supt this.
491             || $type eq "GLOB"
492             || UNIVERSAL::isa( $spec, "IO::Handle" )
493             || ( ! $type && $spec =~ /^\s*([>|]|\+>)/ )
494             ) {
495             ## Cheat until XML::SAX::Writer cat grok it
496 17 50       55 if ( ! $type ) {
497 14     14   48276 use Symbol;
  14         20256  
  14         60486  
498 0         0 my $fh = gensym;
499 0 0       0 open $fh, $spec or croak "$! opening '$spec'" ;
500 0         0 $spec = $fh;
501             }
502 17         9950 require XML::SAX::Writer;
503 17         266113 $spec = XML::SAX::Writer->new( Output => $spec );
504             }
505             elsif ( !$type ) {
506 49 50       320 if ( $spec !~ /^\s*<|\|\s*(?!\n)$/ ) {
507             ## Doesn't look like the caller wants to slurp a file
508             ## Let's require it now to catch errors early, then
509             ## new() it later after all requires are done.
510             ## delaying the new()s might help us from doing things
511             ## like blowing away output files and then finding
512             ## errors, for instance.
513 49 100 100     2198 croak $@ unless $spec->can( "new" ) || eval "require $spec";
514             }
515             }
516             else {
517 39 100       646 croak "'$type' not supported in a SAX machine specification\n"
518             if exists $basic_types{$type};
519             }
520              
521 100         50268 return $spec;
522             }
523              
524             my $is_name_like = has_named_regexp_character_classes
525             ? '^[[:alpha:]]\w*(?!\n)$'
526             : '^[a-zA-Z]\w*(?!\n)$';
527              
528             sub _valid_name($) {
529 86     86   3367 my ( $prospect ) = @_;
530 86 50 33     487 return 0 unless defined $prospect && length $prospect;
531 86         211 my $fc = substr $prospect, 0, 1;
532             ## I wonder how close to valid Perl method names this is?
533 86 100 100     1892 ( $fc eq uc $fc && $prospect =~ /$is_name_like/o ) ? 1 : 0;
534             }
535              
536              
537             sub _push_spec {
538 107     107   331 my $self = shift;
539 107 100       387 my ( $name, $spec, @handlers ) =
540             ref $_[0]
541             ? ( undef, @_ ) ## Implictly unnamed: [ $obj, ... ]
542             : @_; ## Named or explicitly unnamed: [ $name, ...]
543              
544 107         518 my $part = XML::SAX::Machine::Part->new(
545             Name => $name,
546             Handlers => \@handlers,
547             );
548              
549             # if ( grep $_ eq "Exhaust", @handlers ) {
550             # $self->{OverusedNames}->{Exhaust} ||= undef
551             # if exists $self->{PartsByName}->{Exhaust};
552             #
553             # $self->{PartsByName}->{Exhaust} = $self->{Parts}->[-1];
554             #
555             # @handlers = grep $_ ne "Exhaust", @handlers;
556             # }
557              
558             ## NOTE: This may
559             ## still return a non-reference, which is the type of processor
560             ## wanted here. We construct those lazily below; see the docs
561             ## about order of construction.
562 107         543 my $proc = $self->_resolve_spec( $spec );
563 100         248 $part->{Processor} = $proc;
564 100 50       267 croak "SAX machine BUG: couldn't resolve spec '$spec'"
565             unless defined $proc;
566              
567 100         289 push @{$self->{Parts}}, $part;
  100         250  
568 100         137 $part->{Number} = $#{$self->{Parts}};
  100         256  
569              
570 100 100       259 if ( defined $name ) {
571 75 100 50     244 $self->{OverusedNames}->{$name} ||= undef
572             if exists $self->{PartsByName}->{$name};
573              
574 75 100 50     192 $self->{IllegalNames}->{$name} ||= undef
      66        
575             unless _valid_name $name && $name ne "Exhaust";
576              
577 75         1566 $self->{PartsByName}->{$name} = $self->{Parts}->[-1];
578             }
579              
580             ## This HASH is used to detect cycles even if the user uses
581             ## preconstructed references instead of named parts.
582 100 100       1025 $self->{PartsByProcessor}->{$proc} = $part
583             if ref $proc;
584             }
585              
586              
587             sub _names_err_msgs {
588 189     189   502 my ( $s, @names ) = @_ ;
589 189 100       717 @names = map ref $_ eq "HASH" ? keys %$_ : $_, @names;
590 189 100       946 return () unless @names;
591              
592 6         8 @names = keys %{ { map { ( $_ => undef ) } @names } };
  6         10  
  7         215  
593              
594 6 100       178 if ( @names == 1 ) {
595 5         24 $s =~ s/%[A-Z]+//g;
596             }
597             else {
598 1         13 $s =~ s/%([A-Z]+)/\L$1/g;
599             }
600              
601 6         44 return $s . join ", ", map "'$_'", sort @names ;
602             }
603              
604              
605             sub _build_part {
606 150     150   183 my $self = shift;
607 150         187 my ( $part ) = @_;
608              
609 150         218 my $part_num = $part->{Number};
610              
611 150 100       621 return if $self->{BuiltParts}->[$part_num];
612              
613 100 50       229 confess "SAX machine BUG: cycle found too late"
614             if $self->{SeenParts}->[$part_num];
615 100         167 ++$self->{SeenParts}->[$part_num];
616              
617             ## We retun a list of all cycles that have been discovered but
618             ## not yet completed. We don't return cycles that have been
619             ## completely discovered; those are placed in DetectedCycles.
620 100         119 my @open_cycles;
621              
622 100         113 eval {
623             ## This eval is to make sure we decrement SeenParts so that
624             ## we don't encounter spurious cycle found too late exceptions.
625              
626             ## Build any handlers, detect cycles
627 100         144 my @handler_procs;
628              
629             ## I decided not to autolink one handler to the next in order to keep
630             ## from causing hard to diagnose errors when unintended machines are
631             ## passed in. The special purpose machines, like Pipeline, have
632             ## that logic built in.
633             ## ## Link any part with no handlers to the next part.
634             ## push @{$part->{Handlers}}, $part->{Number} + 1
635             ## if ! @{$part->{Handlers}} && $part->{Number} < $#{$self->{Parts}};
636              
637 100         118 for my $handler_spec ( @{$part->{Handlers}} ) {
  100         207  
638              
639 77         99 my $handler;
640              
641 77 100       151 if ( ref $handler_spec ) {
642             ## The caller specified a handler with a real reference, so
643             ## we don't need to build it, but we do need to do
644             ## cycle detection. _build_part won't build it in this case
645             ## but it will link it and do cycle detection.
646 3 100       12 $handler = $self->{PartsByProcessor}->{$handler_spec}
647             if exists $self->{PartsByProcessor}->{$handler_spec};
648              
649 3 100       6 if ( ! defined $handler ) {
650             ## It's a processor not in this machine. Hope the
651             ## caller knows what it's doing.
652 1         2 push @handler_procs, $handler_spec;
653 1         4 next;
654             }
655             }
656             else {
657 74         197 $handler = $self->_find_part_rec( $handler_spec );
658             ## all handler specs were checked earlier, so "survive" this
659             ## failure and let the queued error message tell the user
660             ## about it.
661 74 100       254 next unless defined $handler;
662             }
663              
664 56 100       157 if ( $self->{SeenParts}->[$handler->{Number}] ) {
665             ## Oop, a cycle, and we don't want to recurse or we'll
666             ## recurse forever.
667 6 100       23 push @open_cycles, $part eq $handler
668             ? [ $handler ]
669             : [ $part, $handler ];
670 6         18 next;
671             }
672              
673 50         281 my @nested_cycles = $self->_build_part( $handler );
674              
675 50         80 my $handler_proc = $handler->{Processor};
676              
677 50 50       106 confess "SAX machine BUG: found a part with no processor: ",
678             $handler->string_description
679             unless defined $handler_proc;
680              
681 50 50       121 confess "SAX machine BUG: found a unbuilt '",
682             $handler->{Processor},
683             "' processor: ",
684             $handler->string_description
685             unless ref $handler_proc;
686              
687 50         71 push @handler_procs, $handler_proc;
688              
689 50         137 for my $nested_cycle ( @nested_cycles ) {
690 5 50       15 if ( $nested_cycle->[-1] == $part ) {
691             ## the returned cycle "ended" with our part, so
692             ## we have a complete description of the cycle, log it
693             ## and move on.
694 5         6 push @{$self->{DetectedCycles}}, $nested_cycle;
  5         19  
695             }
696             else {
697             ## This part is part of this cycle but not it's "beginning"
698 0         0 push @open_cycles, [ $part, $nested_cycle ];
699             }
700             }
701             }
702              
703             ## Create this processor if need be, otherwise just set the handlers.
704 100         392 my $proc = $part->{Processor};
705 100 50       208 confess "SAX machine BUG: undefined processor for ",
706             $part->string_description
707             unless defined $proc;
708              
709 100 100       286 unless ( ref $proc ) {
    100          
710             ## TODO: Figure a way to specify the type of handler, probably
711             ## using a DTDHandler=>Name syntax, not sure. Perhaps
712             ## using a hash would be best.
713              
714 47 50       465 if ( $proc =~ /^\s*<|\|\s*(?!\n)$/ ) {
    100          
715             ## Looks like the caller wants to slurp a file
716             ## We open it ourselves to get all of Perl's magical
717             ## "open" goodness. TODO: also check for a URL scheme
718             ## and handle that :).
719              
720             ## TODO: Move this in to a/the parse method so it can
721             ## be repeated.
722 0         0 require Symbol;
723 0         0 my $fh = Symbol::gensym;
724 0 0       0 open $fh, $proc or croak "$! opening '$proc'";
725 0         0 require XML::SAX::ParserFactory;
726 0         0 require IO::Handle;
727 0         0 $proc = XML::SAX::ParserFactory->parser(
728             Source => {
729             ByteStream => $fh,
730             },
731             map {
732 0         0 ( Handler => $_ ),
733             } @handler_procs
734             );
735              
736             }
737             elsif (
738             XML::SAX::Machines->processor_class_option(
739             $proc,
740             "ConstructWithHashedOptions"
741             )
742             ) {
743             ## This is designed to build options in a format compatible
744             ## with SAXT style constructors when multiple handlers are
745             ## defined.
746 12         67 $proc = $proc->new(
747             map {
748 15         42 { Handler => $_ }, ## Hashes
749             } @handler_procs ## 0 or more of 'em
750             );
751             }
752             else {
753             ## More common Foo->new( Handler => $h );
754 32 50       91 croak "$proc->new doesn't allow multiple handlers.\nSet ConstructWithOptionsHashes => 1 in XML::SAX::Machines::ConfigDefaults if need be"
755             if @handler_procs > 1;
756 25         152 $proc = $proc->new(
757             map {
758 32         80 ( Handler => $_ ), ## A plain list
759             } @handler_procs ## with 0 or 1 elts
760             );
761             }
762 47         4158 $self->{PartsByProcessor}->{$proc} = $part;
763             }
764             elsif ( @handler_procs ) {
765 13 50       151 if ( $proc->can( "set_handlers" ) ) {
    50          
766 0         0 $proc->set_handlers( @handler_procs );
767             }
768             elsif ( $proc->can( "set_handler" ) ) {
769 13 100       38 if ( @handler_procs == 1 ) {
770 12         44 $proc->set_handler( @handler_procs );
771             }
772             else {
773 1         3 die "SAX machine part ", $part->string_description,
774             " can only take one handler at a time\n";
775             }
776             }
777             else {
778 0         0 die "SAX machine part ", $part->string_description,
779             " does not provide a set_handler() or set_handlers() method\n"
780             }
781             }
782              
783 99         283 $part->{Processor} = $proc;
784             };
785              
786 100         414 --$self->{SeenParts}->[$part->{Number}];
787 100         176 $self->{BuiltParts}->[$part_num] = 1;
788              
789              
790 100 100       206 if ( $@ ) {
791 1         2 chomp $@;
792 1         3 $@ .= "\n ...while building " . $part->string_description . "\n";
793 1         3 die $@;
794             }
795              
796 99         196 return @open_cycles;
797             }
798              
799              
800             sub _compile_specs {
801 63     63   135 my $self = shift;
802              
803 63         80 my @errors;
804              
805             ## Init the permanent structures
806 63         127 $self->{Parts} = [];
807 63         129 $self->{PartsByName} = {};
808 63         139 $self->{PartsByProcessor} = {};
809              
810             ## And some temporary structures.
811 63         113 $self->{IllegalNames} = {};
812 63         130 $self->{OverusedNames} = {};
813              
814             ## Scan the specs and figure out the connectivity, names and load
815             ## any requirements, etc.
816 63         135 for my $spec ( @_ ) {
817 107         150 eval {
818 107 100       508 $self->_push_spec(
819             ref $spec eq "ARRAY"
820             ? @$spec
821             : ( undef, $spec )
822             );
823             };
824             ## This could be ugly if $@ contains a stack trace, but it'll have
825             ## to do.
826 107 100       586 if ( $@ ) {
827 7         17 chomp $@;
828 7         21 push @errors, $@;
829             }
830             }
831              
832 100         786 push @errors, (
833             _names_err_msgs(
834             "illegal SAX machine part name%S ",
835             $self->{IllegalNames}
836             ),
837             _names_err_msgs(
838             "undefined SAX machine part%S specified as handler%S ",
839             grep defined && ! $self->_find_part_rec( $_ ),
840             grep ! ref && $_ ne "Exhaust",
841 63         151 map @{$_->{Handlers}},
842 63   66     217 @{$self->{Parts}}
      100        
843             ),
844             _names_err_msgs(
845             "multiple SAX machine parts named ",
846             $self->{OverusedNames}
847             )
848             );
849              
850             ## Free some memory and make object dumps smaller
851 63         379 delete $self->{IllegalNames};
852 63         120 delete $self->{OverusedNames};
853              
854             ## If we made it this far, all classes have been loaded and all
855             ## non-processor refs have been converted in to processors.
856             ## Now
857             ## we need to build and that were specified by type name and do
858             ## them in reverse order so we can pass the
859             ## Handler option(s) in.
860             ## If multiple handlers are defined, then
861             ## we assume that the constructor takes a SAXT like parameter list.
862             ## TODO: figure out how to allow DocumentHandler, etc. Perhaps allow
863             ## HASH refs in ARRAY syntax decls.
864            
865             ## Some temporaries
866 63         146 $self->{BuiltParts} = [];
867 63         113 $self->{SeenParts} = [];
868 63         114 $self->{DetectedCycles} = [];
869              
870             ## _build_part is recursive and builds any downstream handlers
871             ## needed to build a part.
872 63         82 for ( @{$self->{Parts}} ) {
  63         153  
873 100         302 eval {
874 100         119 push @{$self->{DetectedCycles}}, $self->_build_part( $_ );
  100         331  
875             };
876 100 100       276 if ( $@ ) {
877 1         11 chomp $@;
878 1         3 push @errors, $@;
879             }
880             }
881              
882             # $self->{PartsByName}->{Intake} ||= $self->{Parts}->[0];
883             # $self->{PartsByName}->{Exhaust} ||= $self->{Parts}->[-1];
884              
885 63 100       102 if ( @{$self->{DetectedCycles}} ) {
  63         178  
886             ## Remove duplicate (cycles are found once for each processor in
887             ## the cycle.
888 4         5 my %unique_cycles;
889              
890 4         5 for my $cycle ( @{$self->{DetectedCycles}} ) {
  4         7  
891 6         7 my $start = 0;
892 6         14 for ( 1..$#$cycle ) {
893 5 50       22 $start = $_
894             if $cycle->[$_]->{Number} < $cycle->[$start]->{Number};
895             }
896 6         26 my $key = join(
897             ",",
898             map $_->{Number},
899 6         17 @{$cycle}[$start..($#$cycle),0..($start-1)]
900             );
901 6   66     138 $unique_cycles{$key} ||= $cycle;
902             }
903            
904 5         16 push @errors, map {
905 4         16 "Cycle detected in SAX machine: " .
906             join(
907             "->",
908             map $_->string_description, $_->[-1], @$_
909             );
910             } map $unique_cycles{$_}, sort keys %unique_cycles;
911             }
912              
913 63         138 delete $self->{SeenParts};
914 63         1073 delete $self->{BuiltParts};
915 63         108 delete $self->{DetectedCycles};
916              
917 63 100       2223 croak join "\n", @errors if @errors;
918             }
919              
920              
921             sub _SAX2_attrs {
922 0     0   0 my %a = @_;
923              
924             return {
925 0 0       0 map {
926 0         0 defined $a{$_}
927             ? ( $_ => {
928             LocalName => $_,
929             Name => $_,
930             Value => $a{$_},
931             } )
932             : () ;
933             } keys %a
934             };
935             }
936              
937              
938             my %ids;
939             sub _idify($) {
940 0 0   0   0 $ids{$_[0]} = keys %ids unless exists $ids{$_[0]};
941 0         0 return $ids{$_[0]};
942             }
943              
944              
945             sub pointer_elt {
946 0     0 0 0 my $self = shift;
947 0         0 my ( $elt_type, $h_spec, $options ) = @_;
948              
949 0         0 my $part_rec;
950              
951 0 0 0     0 $h_spec = $self->{Handler}
952             if $h_spec eq "Exhaust" && defined $self->{Handler};
953              
954             ## Look locally first in case the name is not
955             ## unique among parts in RootMachine.
956 0 0       0 $part_rec = $self->_find_part_rec( $h_spec )
957             if ! $part_rec;
958              
959             ## Don't look for indexes in RootMachine
960 0 0 0     0 $part_rec = $options->{RootMachine}->_find_part_rec(
      0        
961             $h_spec
962             ) if ! $part_rec
963             && defined $options->{RootMachine}
964             && $h_spec != /^-?\d+$/ ;
965              
966 0         0 my %attrs;
967              
968 0 0       0 if ( $part_rec ) {
969 0   0     0 %attrs = (
970             name => $part_rec->{Name} || $h_spec,
971             "handler-id" => _idify $part_rec->{Processor},
972             );
973             }
974             else {
975 0 0       0 if ( ref $h_spec ) {
976 0         0 %attrs = (
977             type => ref $h_spec,
978             "handler-id" => _idify $h_spec,
979             );
980             }
981             else {
982 0         0 %attrs = (
983             name => $h_spec,
984             );
985             }
986             }
987              
988             return {
989 0         0 Name => $elt_type,
990             LocalName => $elt_type,
991             Attributes => _SAX2_attrs( %attrs ),
992             };
993             }
994              
995              
996             sub generate_part_descriptions {
997 0     0 0 0 my $self = shift;
998 0         0 my ( $options ) = @_;
999              
1000 0         0 my $h = $options->{Handler};
1001 0 0       0 croak "No Handler passed" unless $h;
1002              
1003 0         0 for my $part_rec ( @{$self->{Parts}} ) {
  0         0  
1004 0         0 my $proc = $part_rec->{Processor};
1005              
1006 0 0       0 if ( $proc->can( "generate_description" ) ) {
1007 0         0 $proc->generate_description( {
1008             %$options,
1009             Name => $part_rec->{Name},
1010             Description => $part_rec->string_description,
1011             } );
1012             }
1013             else {
1014 0         0 my $part_elt = {
1015             LocalName => "part",
1016             Name => "part",
1017             Attributes => _SAX2_attrs(
1018             id => _idify $proc,
1019             type => ref $part_rec,
1020             name => $part_rec->{Name},
1021             description => $part_rec->string_description,
1022             ),
1023             };
1024 0         0 $h->start_element( $part_elt );
1025 0         0 for my $h_spec ( @{$part_rec->{Handlers}} ) {
  0         0  
1026 0         0 my $handler_elt = $self->pointer_elt( "handler", $h_spec );
1027              
1028 0         0 $h->start_element( $handler_elt );
1029 0         0 $h->end_element( $handler_elt );
1030             }
1031 0         0 $h->end_element( $part_elt );
1032             }
1033             }
1034             }
1035              
1036              
1037             sub generate_description {
1038 0     0 1 0 my $self = shift;
1039              
1040 0         0 my $options =
1041             @_ == 1
1042             ? ref $_[0] eq "HASH"
1043 0 0       0 ? { %{$_[0]} }
    0          
    0          
1044             : {
1045             Handler =>
1046             ref $_[0]
1047             ? $_[0]
1048             : $self->_resolve_spec( $_[0] )
1049             }
1050             : { @_ };
1051              
1052 0         0 my $h = $options->{Handler};
1053 0 0       0 croak "No Handler passed" unless $h;
1054              
1055 0 0       0 unless ( $options->{Depth} ) {
1056 0         0 %ids = ();
1057 0         0 $options->{RootMachine} = $self;
1058              
1059 0         0 $h->start_document({});
1060             }
1061              
1062 0         0 ++$options->{Depth};
1063 0         0 my $root_elt = {
1064             LocalName => "sax-machine",
1065             Name => "sax-machine",
1066             Attributes => _SAX2_attrs(
1067             id => _idify $self,
1068             type => ref $self,
1069             name => $options->{Name},
1070             description => $options->{Description},
1071             ),
1072             };
1073              
1074 0         0 $h->start_element( $root_elt );
1075              
1076             ## Listing the handler first so it doesn't look like a part's
1077             ## handler (which it kinda does if it's hanging out *after* a
1078             ## tag :). Also makes following the links by hand a tad easier.
1079 0 0       0 if ( defined $self->{Handler} ) {
1080 0         0 my $handler_elt = $self->pointer_elt( "handler", $self->{Handler} );
1081 0 0       0 $handler_elt->{Attributes}->{name} = {
1082             Name => "name",
1083             LocalName => "name",
1084             Value => "Exhaust"
1085             } unless exists $handler_elt->{Attributes}->{Name};
1086            
1087 0         0 $h->start_element( $handler_elt );
1088 0         0 $h->end_element( $handler_elt );
1089             }
1090              
1091 0         0 for ( sort keys %{$self->{PartsByName}} ) {
  0         0  
1092 0 0       0 if ( $self->{PartsByName}->{$_}->{Name} ne $_ ) {
1093 0         0 warn $self->{PartsByName}->{$_}->{Name}, " : ", $_;
1094 0         0 my $handler_elt = $self->pointer_elt( "alias", $_ );
1095 0         0 %{$handler_elt->{Attributes}} = (
  0         0  
1096 0         0 %{$handler_elt->{Attributes}},
1097 0         0 %{_SAX2_attrs( alias => $_ )},
1098             );
1099 0         0 $h->start_element( $handler_elt );
1100 0         0 $h->end_element( $handler_elt );
1101             }
1102             }
1103              
1104 0         0 $self->generate_part_descriptions( $options );
1105 0         0 $h->end_element( $root_elt );
1106              
1107 0         0 --$options->{Depth};
1108 0 0       0 $h->end_document({}) unless $options->{Depth};
1109             }
1110              
1111              
1112             ##
1113             ## This is a private class, only this class should use it directly.
1114             ##
1115             package XML::SAX::Machine::Part;
1116             {
1117             $XML::SAX::Machine::Part::VERSION = '0.46';
1118             }
1119              
1120             use fields (
1121 14         89 'Name', ## The caller-given name of the part
1122             'Number', ## Where it sits in the parts list.
1123             'Processor', ## The actual SAX processor
1124             'Handlers', ## The handlers the caller specified
1125 14     14   20200 );
  14         24895  
1126              
1127              
1128             sub new {
1129 107     107   326 my $proto = shift;
1130 107   33     408 my $class = ref $proto || $proto;
1131              
1132 107         302 my $self = bless {}, $class;
1133            
1134 107         441 my %options = @_ ;
1135 107         867 $self->{$_} = $options{$_} for keys %options;
1136              
1137 107         712 return $self;
1138             }
1139              
1140              
1141             sub string_description {
1142 16     16   18 my $self = shift;
1143              
1144 16 50 33     108 return join(
    50          
1145             "",
1146             $self->{Name}
1147             ? $self->{Name}
1148             : ( "#", $self->{Number} ),
1149             " (",
1150             $self->{Processor}
1151             ? ( ref $self->{Processor} || $self->{Processor} )
1152             : "",
1153             ")"
1154             );
1155             }
1156              
1157             1;
1158              
1159             __END__