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