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