File Coverage

blib/lib/XML/Handler/Essex.pm
Criterion Covered Total %
statement 26 28 92.8
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 36 38 94.7


line stmt bran cond sub pod time code
1             package XML::Handler::Essex;
2              
3             $VERSION = 0.000_1;
4              
5 5     5   12803 use XML::Essex::Constants;
  5         10  
  5         38  
6 5     5   29 use Scalar::Util qw( reftype );
  5         29  
  5         929  
7              
8             #use Time::HiRes qw( time );
9             #sub warn { warn sprintf( "%.2f", time ), " ", @_; }
10              
11             BEGIN {
12 5     5   125 require XML::Handler::Essex::Threaded if threaded_essex;
13             }
14              
15             =head1 NAME
16              
17             XML::Handler::Essex - Essex handler object (including XML::Filter::Essex)
18              
19             =head1 SYNOPSIS
20              
21             use XML::Handler::Essex;
22              
23             my $h = XML::Handler::Essex->new(
24             Main => sub {
25             while ( get_chars ) {
26             put uc;
27             }
28             }
29             );
30              
31             =head1 DESCRIPTION
32              
33             Defines (and exports, by default) C and C routines
34             that allow an Essex handler and filter to pull events from the SAX
35             stream.
36              
37             Pulling is handled in one of two ways: the entire input document is
38             buffered if a perl earlier than 5.8.0 is used, due to lack of
39             multithreading, and threading is used in perls later than 5.8.0.
40              
41             Note that the event constructor functions (C, C,
42             etc) are not exported by this module as they are from
43             XML::Generator::Essex and XML::Filter::Essex; handlers rarely need
44             these.
45              
46             Returns a "1" by default, use C to change.
47              
48             =for test_script XML-Filter-Essex.t
49              
50             =cut
51              
52 5     5   3787 use XML::Essex::Base (); # Don't import things.
  5         13  
  5         97  
53 5     5   4961 use XML::Essex::Model ();
  5         17  
  5         310  
54 5     5   37 use Carp ();
  5         9  
  5         111  
55              
56 5     5   28 no warnings "once";
  5         224  
  5         408  
57              
58             @ISA = qw( XML::Essex::Base );
59              
60             @EXPORT = qw(
61             isa
62             next_event
63             path
64             type
65             xeof
66              
67             get
68             on
69             );
70              
71             # get_start_document
72             # get_start_doc
73             #
74             # get_start_element
75             # get_start_elt
76             # get_end_element
77             # get_end_elt
78             # get_element
79             # get_elt
80             #
81             # get_characters
82             # get_chars
83             #);
84              
85 5     5   26 use strict;
  5         9  
  5         372  
86 5     5   7766 use NEXT;
  5         17071  
  5         146  
87 5     5   11540 use XML::SAX::EventMethodMaker qw( compile_missing_methods sax_event_names );
  0            
  0            
88              
89             sub new {
90             my $proto = shift;
91              
92             return $proto->SUPER::new( @_ ) if ref $proto;
93              
94             my $class = $proto;
95              
96             if ( threaded_essex ) {
97             require XML::Handler::Essex::Threaded;
98             $class .= "::Threaded";
99             }
100              
101             return $class->SUPER::new( @_ );
102             }
103              
104              
105             sub _init { ## Called by new()
106             my $self = shift;
107              
108             $self->{PendingEvents} = [];
109             $self->{Events} = [];
110              
111             $self->NEXT::_init( @_ );
112             }
113              
114              
115             sub reset { ## called before main() by execute()
116             my $self = shift;
117             $self->{Result} = 1;
118             ## Hmmm, should we clear Events here? Can't clear
119             ## events in non-threaded mode.
120             undef $self->{Dispatchers};
121             $self->NEXT::reset( @_ );
122             }
123              
124              
125             sub finish { ## called after main() by execute()
126             my $self = shift;
127            
128             my ( $ok, $x ) = @_;
129              
130             # die ref( $self ) . "::main() exited before end_document seen\n"
131             # if $ok && $self->{InDocument};
132              
133             # In case we're also an XML::Generator::Essex, let it have
134             # first crack at the result value. This sort of encodes
135             # knowledge of the inheritance hierarchy for XML::Filter::Essex
136             # in this code; it would be better to have an arbitration
137             # scheme where there is a default result set, then a
138             # downstream result, then a manually set result, with the
139             # highest ranking one set winning (ie last in that list).
140             # The current scheme, however, is BALGE.
141             $DB::single=1;
142             my ( $result_set, $result ) = $self->NEXT::finish( @_ );
143              
144             return ( $result_set, $result ) if $result_set;
145              
146             unless ( $ok ) {
147             if ( $x eq EOD . "\n" ) {
148             return ( 1, $self->{Result} );
149             }
150             die $x;
151             }
152              
153             return ( 1, $self->{Result} );
154             }
155              
156              
157             sub _send_event_to_child {
158             my $self = shift;
159              
160             warn "Essex $self: queuing $_[0] for child\n" if debugging;
161             push @{$self->{Events}}, @{$self->{PendingEvents}}, [ @_ ];
162             @{$self->{PendingEvents}} = ();
163             # force scalar context to be consistent with the threaded case.
164             }
165              
166              
167             ## There's a DESTROY in XML::Handler::Essex::Threaded
168              
169             # NOTE: returns \@event, whereas _send_event_to_child takes @event.
170             # This is to speed the queue fudging that threaded_execute does on
171             # start_document.
172             sub _recv_event_from_parent {
173             my $self = shift;
174              
175             my $event;
176              
177             die EOD . "\n"
178             if $self->{PendingResultType} eq "end_document";
179              
180             unless ( @{$self->{Events}} ) {
181             if ( $self->{Reader} ) {
182             do {
183             $self->{Reader}->();
184             } until @{$self->{Events}};
185             }
186             else {
187             Carp::croak "No XML events to process";
188             }
189             }
190              
191             $event = $self->{Events}->[0];
192             my $event_type = $event->[0];
193             warn "Essex $self: got $event_type $event->[1] from parent\n"
194             if debugging;
195              
196             shift @{$self->{Events}};
197              
198             die $event_type . "\n"
199             if $event_type eq BOD || $event_type eq EOD || $event_type eq SEPPUKU;
200              
201             if ( threaded_essex ) {
202             ## Set the default result for this event.
203             @$self{ "PendingResultType", "PendingResult" } =
204             ( $event_type, "Essex: default result for $event_type" );
205             }
206              
207             return $event;
208             }
209              
210             # Hopefully, this handles inline set_document_locator events relatively
211             # gracefully, by queueing them up until the next event arrives. This is
212             # necessary because set_document_locator events can arrive *before* the
213             # start_document, and we need to wait for the next event to see whether
214             # to insert the BOD before the set_document_locator. This is all so that
215             # the initial set_document_locator event(s) will arrive before the
216             # start_document event in the main() routine, given that we need to
217             # send the BOD psuedo event in case the main() routine is still running.
218             sub set_document_locator {
219             push @{shift->{PendingEvents}}, [ "set_document_locator", @_ ];
220             return "Essex: document locator queued";
221             }
222              
223              
224             sub end_document {
225             my $self = shift;
226             ## Must send EOD after the end_document so that we get the end_document
227             ## result back first otherwise it would be lost because
228             ## _recv_event_from_parent does not send results back if there are any
229             ## other events in the queue. If this were not so, we could add a hack
230             ## here to queue up both end_document and EOD at once.
231             my $r = $self->_send_event_to_child( "end_document", @_ );
232              
233             push @{$self->{Events}}, [ EOD ];
234             return $self->execute;
235              
236             return $r;
237             }
238              
239             compile_missing_methods __PACKAGE__, <<'END_CODE', sax_event_names;
240             #line 1 XML::Handler::Essex::()
241             sub {
242             shift->_send_event_to_child( "", @_ );
243             }
244              
245             END_CODE
246              
247             =head1 Exported Functions
248              
249             These are exported by default, use the C syntax to suppress
250             exporting these. All of these act on $_ by default.
251              
252             =head2 Miscellaneous
253              
254             =over
255              
256             =item isa
257              
258             get until isa "start_elt" and $_->name eq "foo";
259             $r = get until isa $r, "start_elt" and $_->name eq "foo";
260              
261             Returns true if the parameter is of the indicated object type. Tests $_
262             unless more than one parameter is passed.
263              
264             Note the use of C instead of C<&&> to get paren-less C to
265             behave as expected (this is a typical Perl idiom).
266              
267             =cut
268              
269             sub isa {
270             local $_ = shift if @_ >= 2;
271             UNIVERSAL::isa( $_, "XML::Essex::Event" )
272             ? $_->isa( @_ )
273             : UNIVERSAL::isa( $_, @_ );
274             }
275              
276             =item path
277              
278             get_start_elt until path eq "/path/to/foo:bar"
279              
280             Returns the path to the current element as a string.
281              
282             =cut
283              
284             sub path {
285             my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
286             ? shift
287             : $XML::Essex::Base::self;
288             return join "/", "", map $_->name, @{$self->{Stack}};
289             }
290              
291             =for import XML::Generator::Essex/put
292              
293             =item type
294              
295             get until type eq "start_document";
296             $r = get until type $r eq "start_document";
297              
298              
299             Return the type name of the object. This is the class name with a
300             leading XML::Essex:: stripped off. This is a wrapper around the
301             event's C method.
302              
303             Dies C if the parameter is not an object with a C method.
304              
305             =cut
306              
307             sub type {
308             local $_ = shift if @_;
309              
310             Carp::croak
311             ref $_ || "a scalar",
312             " is not an Essex event, cannot type() it\n"
313             unless UNIVERSAL::can( $_, "type" );
314              
315             return $_->type( @_ )
316             }
317              
318             =item xeof
319              
320             Return TRUE if the last event read was an end_document event.
321              
322             =cut
323              
324             sub xeof {
325             my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
326             ? shift
327             : $XML::Essex::Base::self;
328              
329             lock @{$self->{Events}} if threaded_essex;
330             return @{$self->{Events}} && $self->{Events}->[0] eq EOD;
331             }
332              
333             =item get
334              
335             Gets an event or element from the incoming SAX input stream, puts it in
336             C<$_> and returns it. Throws an exception when reading past the last
337             event in a document. This exception is caught by XML::Essex and
338             causes it to wait until the beginning of the next document and reenter
339             the main routine.
340              
341             Code Action
342             ======================= =======================================
343             get; Get the next SAX event, whatever it is.
344             get "node()"; Get the next SAX event, whatever it is.
345             get "*"; Get the next element, whatever its name.
346             get "start-document::*"; Get the next start document event.
347             get "end-document::*"; Get the next end document event.
348             get "start-element::*"; Get the next start element event.
349             get "end-element::*"; Get the next end element event.
350             get "text()"; Get the next characters event.
351              
352             Right now, only the expressions shown are supported. This is a
353             limitation that will be lifted. There may be multiple characters
354             events in a row, unlike xpath's text() matching expression.
355              
356             See C and C functions and method (in
357             L) for how to test what was just gotten.
358              
359             =cut
360              
361             sub _get {
362             my $self = shift;
363              
364             my ( $type, $data ) = @{$self->_recv_event_from_parent};
365              
366             my $event = bless \$data, "XML::Essex::Event::$type";
367              
368             unless ( $event->isa( "XML::Essex::Event" ) ) {
369             no strict 'refs';
370             @{"XML::Essex::Event::${type}::ISA"} = qw( XML::Essex::Event );
371             }
372              
373             pop @{$self->{Stack}} if $self->{PopNext};
374              
375             if ( $event->isa( "XML::Essex::Event::start_document" ) ) {
376             $self->{Stack} = [];
377             $self->{PopNext} = 0;
378             }
379             elsif ( $event->isa( "XML::Essex::Event::start_element" ) ) {
380             push @{$self->{Stack}}, $event;
381             }
382             elsif ( $event->isa( "XML::Essex::Event::end_element" ) ) {
383             # Delay popping so caller can see the end_element on the
384             # stack if need be.
385             $self->{PopNext} = 1;
386             }
387             else {
388             $self->{PopNext} = 0;
389             }
390              
391             if ( $self->{Dispatchers} ) {
392             $data->{__EssexEvent} = $event;
393             for my $d ( @{$self->{Dispatchers}} ) {
394             local $_;
395             $d->$type( $data );
396             }
397             # TODO: figure out a way to clean these up.
398             # delete $data->{__EssexEvent};
399             }
400              
401             return $event;
402             }
403              
404              
405             sub get {
406             my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
407             ? shift
408             : $XML::Essex::Base::self;
409              
410             my ( $xpathlet ) = @_;
411              
412             my $event_type;
413             if ( ! defined $xpathlet || $xpathlet eq "node()" ) {
414             return $_ = $self->_get;
415             }
416             elsif ( $xpathlet eq "*" ) {
417             return $self->get_element;
418             }
419             elsif ( $xpathlet eq "start-document::*" ) {
420             $event_type = "start_document";
421             }
422             elsif ( $xpathlet eq "end-document::*" ) {
423             $event_type = "end_document";
424             }
425             elsif ( $xpathlet eq "start-element::*" ) {
426             $event_type = "start_element";
427             }
428             elsif ( $xpathlet eq "end-element::*" ) {
429             $event_type = "end_element";
430             }
431             elsif ( $xpathlet eq "text()" ) {
432             $event_type = "characters";
433             }
434             elsif ( $xpathlet eq "comment()" ) {
435             $event_type = "comment";
436             }
437             elsif ( $xpathlet eq "processing-instruction()" ) {
438             $event_type = "processing_instruction";
439             }
440             else {
441             Carp::croak "Unsupported or invalid expression '$xpathlet'";
442             }
443              
444             my $event;
445             while (1) {
446             $event = $self->_get;
447             last if $event->isa( $event_type );
448             $self->_skip_event( $event );
449             }
450              
451             $_ = $event;
452             }
453              
454             =item skip
455              
456             Skips one event. This is what happens to events that are not returned
457             from get(). For a handler, skip() does nothing (the event is ignored).
458             For a Filter, the event is passed on the the handler.
459              
460             =cut
461              
462             sub _skip_event {
463             ## Ignore it by default. XML::Filter::Essex overloads this.
464             }
465              
466             sub skip {
467             my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
468             ? shift
469             : $XML::Essex::Base::self;
470             $self->_skip_event( $self->_get );
471             }
472              
473             =item next_event
474              
475             Returns the event that the next call to get() will return. Dies if
476             at xeof. Does not set $_.
477              
478             NOTE: NOT YET IMPLEMENTED IN THREADED MODE.
479              
480             =cut
481              
482             sub next_event {
483             my $self = shift;
484              
485             my ( $type, $data ) = do {
486             Carp::croak "Essex: next_event() not yet implemented in threaded mode"
487             if threaded_essex;
488             lock @{$self->{Events}} if threaded_essex;
489             @{$self->{Events}->[0]};
490             };
491              
492             my $e = bless \$data, "XML::Essex::Event::$type";
493              
494             unless ( $e->isa( "XML::Essex::Event" ) ) {
495             no strict 'refs';
496             @{"XML::Essex::Event::${type}::ISA"} = qw( XML::Essex::Event );
497             }
498              
499             return $e;
500             }
501              
502             #=item get_start_document
503             #
504             #aka: get_start_doc
505             #
506             #Skips all events until the next start_document event. Perhaps only
507             #useful in multi-document streams.
508             #
509             #=cut
510             #
511             #sub get_start_document {
512             # my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
513             # ? shift
514             # : $XML::Essex::Base::self;
515             #
516             # my $event;
517             # do {
518             # $event = $self->get;
519             # } until $_->isa( "start_document" );
520             #
521             # $_ = $event;
522             #}
523             #
524             #*get_start_doc = \&get_start_document;
525             #
526             #=item get_end_document
527             #
528             #aka: get_end_doc
529             #
530             #Skips all events until the next end_document event.
531             #
532             #=cut
533             #
534             #sub get_end_document {
535             # my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
536             # ? shift
537             # : $XML::Essex::Base::self;
538             #
539             # my $event;
540             # do {
541             # $event = $self->get;
542             # } until $_->isa( "end_document" );
543             #
544             # $_ = $event;
545             #}
546             #
547             #*get_end_doc = \&get_end_document;
548             #
549             #=item get_start_element
550             #
551             #aka: get_start_elt
552             #
553             #Skips all events until the next start_element event.
554             #
555             #=cut
556             #
557             #sub get_start_element{
558             # my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
559             # ? shift
560             # : $XML::Essex::Base::self;
561             #
562             # my $event;
563             # do {
564             # $event = $self->_get;
565             # } until $event->isa( "start_element" );
566             #
567             # return $_ = $event;
568             #}
569             #
570             #*get_start_elt = \&get_start_element;
571             #
572             #=item get_end_element
573             #
574             #aka: get_end_elt
575             #
576             #Skips all events until the next end_element event. Returns an
577             #end_element object.
578             #
579             #=cut
580             #
581             #sub get_end_element {
582             # my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
583             # ? shift
584             # : $XML::Essex::Base::self;
585             #
586             # my $event;
587             # do {
588             # $event = $self->get;
589             # } until $_->isa( "end_element" );
590             #
591             # return $_ = $event;
592             #}
593             #
594             #*get_end_elt = \&get_end_element;
595             #
596             #=item get_element
597             #
598             #aka: get_elt
599             #
600             # my $elt = get_elt;
601             #
602             #Skips all events until the next start_element event, then consumes it
603             #and all events up to and including the matching eld_element event.
604             #Returns an L object.
605             #
606             # my $start_element = get_start_elt;
607             # my $elt = get_elt $start_element;
608             #
609             #Skips nothing; takes a start_element and uses it to create an element
610             #object by reading all content and then matching end_element event
611             #from the input stream.
612             #
613             #=cut
614             #
615             #
616             sub get_element {
617             my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
618             ? shift
619             : $XML::Essex::Base::self;
620              
621             my $start_elt;
622             if ( @_ ) {
623             $start_elt = shift;
624             }
625             else {
626             do {
627             $start_elt = $self->_get;
628             } until $start_elt->isa( "start_element" );
629             }
630             my $elt = XML::Essex::Event::element->new( $start_elt );
631             while (1) {
632             my $event = $self->_get;
633             if ( $event->isa( "XML::Essex::Event::start_element" ) ) {
634             $elt->_add_content( get_element $event );
635             }
636             elsif ( $event->isa( "XML::Essex::Event::end_element" ) ) {
637             $elt->_end_element( $event );
638             last;
639             }
640             else {
641             $elt->_add_content( $event );
642             }
643             }
644              
645             return $_ = $elt;
646             }
647              
648             #*get_elt = \&get_element;
649             #
650             #=item get_characters
651             #
652             #aka: get_chars
653             #
654             #Skips to the next characters event and returns it.
655             #
656             #=cut
657             #
658             #sub get_characters {
659             # my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
660             # ? shift
661             # : $XML::Essex::Base::self;
662             #
663             # my $event;
664             # do {
665             # $event = $self->get;
666             # } until $_->isa( "characters" );
667             #
668             # return $_ = $event;
669             #}
670             #
671             #*get_chars = \&get_characters;
672              
673             =item on
674              
675             on(
676             "start_document::*" => sub { warn "start of document reached" },
677             "end_document::*" => sub { warn "end of document reached" },
678             );
679              
680             =for TODO
681             my $rule = on $pat1 => sub { ... }, ...;
682             ...time passes with rules in effect...
683             disable_rule $rule;
684             ...time passes with rules I in effect...
685             enable_rule $rule;
686             ...time passes with rules in effect again...
687              
688             This declares that a rule should be in effect until the end of the
689             document is reached. Each rule is a ( $pattern => $action ) pair where
690             $pattern is an EventPath pattern (see
691             L) and $action is a
692             subroutine reference.
693              
694             The Essex event object matched is passed in $_[1]. A reference to
695             the current Essex handler is passed in $_[0]. This allows you to
696             write libraries of functions that access the current Essex
697             handler/filter/whatever.
698              
699             Do not call get() in the actions, you'll confuse everything. That's
700             a limitation that should be lifted one day.
701              
702             =for TODO or it is disabled.
703              
704             =for TODO Returns a handle that may be used to enable or disable all
705             rules passed in.
706              
707             For now, this must be called before the first get() for predictable
708             results.
709              
710             Rules remain in effect after the main() routine has exited to facilitate
711             pure rule based processing.
712              
713             =cut
714              
715             ## TODO: parse but don't compile rules; allow them to be compiled as
716             ## one large rule and added to a single X::F::D when the Reader
717             ## sub is run.
718             sub _wrap_action {
719             my ( $self, $action ) = @_;
720             sub {
721             local $XML::Essex::dispatcher = shift;
722             $action->( $self, $_[0]->{__EssexEvent} );
723             };
724             }
725              
726              
727             sub on {
728             my $self = @_ && UNIVERSAL::isa( $_[0], __PACKAGE__ )
729             ? shift
730             : $XML::Essex::Base::self;
731              
732             return undef unless @_;
733            
734             require XML::Filter::Dispatcher;
735              
736             my @rules;
737              
738             while ( @_ ) {
739             my ( $pattern, $action ) = ( shift, shift );
740              
741             if ( ref $action eq "ARRAY" ) {
742             ## TODO: make this recursive
743             my @actions = map {
744             ref $_ eq "CODE"
745             ? _wrap_action( $self, $_ )
746             : $_;
747             } @$action;
748              
749             $action = \@actions;
750             }
751             else {
752             $action = _wrap_action( $self, $action );
753             }
754              
755             push @rules, ( $pattern => $action );
756             }
757              
758             push @{$self->{Dispatchers}}, XML::Filter::Dispatcher->new(
759             Rules => \@rules,
760             );
761              
762             return undef;
763             }
764              
765             sub xvalue { $XML::Essex::dispatcher->xvalue( @_ ) }
766              
767             sub xpush { XML::Filter::Dispatcher::xpush( @_ ) }
768             sub xpop { XML::Filter::Dispatcher::xpop( @_ ) }
769             sub xadd { XML::Filter::Dispatcher::xadd( @_ ) }
770             sub xset { XML::Filter::Dispatcher::xset( @_ ) }
771              
772              
773             =back
774              
775             =head1 LIMITATIONS
776              
777             =head1 COPYRIGHT
778              
779             Copyright 2002, R. Barrie Slaymaker, Jr., All Rights Reserved
780              
781             =head1 LICENSE
782              
783             You may use this module under the terms of the BSD, Artistic, oir GPL licenses,
784             any version.
785              
786             =head1 AUTHOR
787              
788             Barrie Slaymaker
789              
790             =cut
791              
792             1;