introduce superclass for streams
[catagits/HTML-Zoom.git] / lib / HTML / Zoom / FilterStream.pm
CommitLineData
456a815d 1package HTML::Zoom::FilterStream;
2
3use strict;
4use warnings FATAL => 'all';
5f74b883 5use base qw(HTML::Zoom::StreamBase);
456a815d 6
7sub new {
8 my ($class, $args) = @_;
9 bless(
10 {
11 _stream => $args->{stream},
12 _match => $args->{match},
13 _filter => $args->{filter},
14 },
15 $class
16 );
17}
18
8f962884 19sub peek {
20 my ($self) = @_;
21 if (exists $self->{_peeked}) {
22 return ($self->{_peeked});
23 }
24 if (my ($peeked) = $self->next) {
25 return ($self->{_peeked} = $peeked);
26 }
27 return;
28}
29
456a815d 30sub next {
31 my ($self) = @_;
32
8f962884 33 # peeked entry so return that
34
35 if (exists $self->{_peeked}) {
36 return (delete $self->{_peeked});
37 }
38
456a815d 39 # if our main stream is already gone then we can short-circuit
40 # straight out - there's no way for an alternate stream to be there
41
42 return unless $self->{_stream};
43
44 # if we have an alternate stream (provided by a filter call resulting
45 # from a match on the main stream) then we want to read from that until
46 # it's gone - we're still effectively "in the match" but this is the
47 # point at which that fact is abstracted away from downstream consumers
48
49 if (my $alt = $self->{_alt_stream}) {
50
51 if (my ($evt) = $alt->next) {
52 return $evt;
53 }
54
55 # once the alternate stream is exhausted we can throw it away so future
56 # requests fall straight through to the main stream
57
58 delete $self->{_alt_stream};
59 }
60
61 # if there's no alternate stream currently, process the main stream
62
63 while (my ($evt) = $self->{_stream}->next) {
64
65 # don't match this event? return it immediately
66
67 return $evt unless $evt->{type} eq 'OPEN' and $self->{_match}->($evt);
68
69 # run our filter routine against the current event
70
71 my ($res) = $self->{_filter}->($evt, $self->{_stream});
72
73 # if the result is just an event, we can return that now
74
75 return $res if ref($res) eq 'HASH';
76
77 # if no result at all, jump back to the top of the loop to get the
78 # next event and try again - the filter has eaten this one
79
80 next unless defined $res;
81
82 # ARRAY means a pair of [ $evt, $new_stream ]
83
84 if (ref($res) eq 'ARRAY') {
85 $self->{_alt_stream} = $res->[1];
86 return $res->[0];
87 }
88
89 # the filter returned a stream - if it contains something return the
90 # first entry and stash it as the new alternate stream
91
92 if (my ($new_evt) = $res->next) {
93 $self->{_alt_stream} = $res;
94 return $new_evt;
95 }
96
97 # we got a new alternate stream but it turned out to be empty
98 # - this will happens for e.g. with an in place close (<foo />) that's
99 # being removed. In that case, we fall off to loop back round and try
100 # the next event from our main stream
101 }
102
103 # main stream exhausted so throw it away so we hit the short circuit
104 # at the top and return nothing to indicate to our caller we're done
105
106 delete $self->{_stream};
107 return;
108}
109
1101;