The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package XML::Filter::Tee;

=head1 NAME

XML::Filter::Tee - Send SAX events to multiple processor, with switching

=head1 SYNOPSIS

    my $t = XML::Filter::Tee->new(
        { Handler => $h0 },
        { Handler => $h1 },
        { Handler => $h2 },
        ...
    );

    ## Altering the handlers list:
    $t->set_handlers( $h0, $h1, $h2, $h3 );

    ## Controlling flow to a handler by number and by reference:
    $t->disable_handler( 0 );
    $t->enable_handler( 0 );

    $t->disable_handler( $h0 );
    $t->enable_handler( $h0 );

    ## Use in a SAX machine (though see L<XML::SAX::Pipeline> and
    ## L<XML::SAX::Tap> for a more convenient way to build a machine
    ## like this):
    my $m = Machine(
        [ Intake => "XML::Filter::Tee" => qw( A B ) ],
        [ A      => ">>log.xml" ],
        [ B      => \*OUTPUT ],
    );

=head1 DESCRIPTION

XML::Filter::Tee is a SAX filter that passes each event it receives on to a
list of downstream handlers.

It's like L<XML::Filter::SAXT> in that the events are not buffered; each
event is sent first to the tap, and then to the branch (this is
different from L<XML::SAX::Dispatcher>, which buffers the events).
Unlike L<XML::Filter::SAXT>, it allows it's list of handlers to be
reconfigured (via L</set_handlers>) and it allows control over which
handlers are allowed to receive events.  These features are designed to
make XML::Filter::Tee instances more useful with SAX machines, but they to
add some overhead relative to XML::Filter::SAXT.

The events are not copied, since they may be data structures that are
difficult or impossibly to copy properly, like parts of a C-based DOM
implementation.  This means that the handlers must not alter the events
or later handlers will see the alterations.

=cut

$VERSION = 0.1;

use strict;
use Carp;
use XML::SAX::Base;
use XML::SAX::EventMethodMaker qw( compile_methods sax_event_names );

=head1 METHODS

=over

=item new

    my $t = XML::Filter::Tee->new(
        { Handler => $h0 },
        { Handler => $h1 },
        { Handler => $h2 },
        ...
    );

=cut

sub new {
    my $proto = shift;
    my $self = bless {}, ref $proto || $proto;

    $self->{DisabledHandlers} = [];

    $self->set_handlers( @_ );

    return $self;
}


=item set_handlers

    $t->set_handlers( $h0, $h1, $h2 );
    $t->set_handlers( {
            Handler => $h0,
        },
        {
            Handler => $h1,
        },
    );

Replaces the current list of handlers with new ones.

Can also name handlers to make enabling/disabling them by name easier:

    $m->set_handlers( {
            Handler => $validator,
            Name    => "Validator",
        },
        {
            Handler => $outputer,
        },
    );

    $m->disable_handler( "Validator" );

=cut

sub set_handlers {
    my $self = shift;

    $self->{Handlers} = [
        map XML::SAX::Base->new(
            ref $_ eq "HASH"
                ? %$_
                : { Handler => $_ }
        ), @_
    ];
}


=item disable_handler

    $t->disable_handler( 0 );            ## By location
    $t->disable_handler( "Validator" );  ## By name
    $t->disable_handler( $h0 );          ## By reference

Stops sending events to the indicated handler.

=cut

sub disable_handlers {
    my $self = shift;

    croak "Can only disable one handler" if @_ > 1;
    my ( $which ) = @_;

    my $hs = $self->{Handlers};

    if ( ref $which ) {
        for my $i ( 0..$#$hs ) {
            next unless $hs->[$i];
            if ( $hs->[$i] == $which ) {
                $self->{DisabledHandlers}->[$i] = $hs->[$i];
                $hs->[$i] = undef;
            }
        }
    }
    elsif ( $which =~ /^\d+(?!\n)$/ ) {
        $self->{DisabledHandlers}->[$which] = $hs;
        $self->{Handlers}->[$which] = undef;
    }
    else {
        for my $i ( 0..$#$hs ) {
            next unless $hs->[$i];
            if ( $hs->[$i]->{Name} eq $which ) {
                $self->{DisabledHandlers}->[$i] = $hs->[$i];
                $hs->[$i] = undef;
            }
        }
    }
}

=item enable_handler

    $t->enable_handler( 0 );            ## By location
    $t->enable_handler( "Validator" );  ## By name
    $t->enable_handler( $h0 );          ## By reference

Stops sending events to the indicated handler.

=cut

sub enable_handlers {
    my $self = shift;

    croak "Can only enable one handler" if @_ > 1;
    my ( $which ) = @_;

    my $hs = $self->{Handlers};

    if ( ref $which ) {
        for my $i ( 0..$#$hs ) {
            next unless $hs->[$i];
            if ( $hs->[$i] == $which ) {
                $hs->[$i] = $self->{DisabledHandlers}->[$i];
                $self->{DisabledHandlers}->[$i] = undef;
            }
        }
    }
    elsif ( $which =~ /^\d+(?!\n)$/ ) {
        $hs->[$which] = $self->{DisabledHandlers}->[$which];
        $self->{DisabledHandlers}->[$which] = undef;
    }
    else {
        for my $i ( 0..$#$hs ) {
            next unless $hs->[$i];
            if ( $hs->[$i]->{Name} eq $which ) {
                $hs->[$i] = $self->{DisabledHandlers}->[$i];
                $self->{DisabledHandlers}->[$i] = undef;
            }
        }
    }
}


compile_methods( __PACKAGE__, <<'FOO', sax_event_names );
sub <EVENT> {
    my $self = shift;
    for ( @{$self->{Handlers}} ) {
        next unless defined $_;
        $_-><EVENT>( @_ );
    }
}
FOO


=back

=head1 AUTHOR

    Barrie Slaymaker <barries@slaysys.com>

=head1 COPYRIGHT

    Copyright 2002, Barrie Slaymaker, All Rights Reserved

You may use this module under the terms of the Artistic, GNU Public, or
BSD licenses, as you choose.

=cut

1;