make FilterStream and MappedStream sensitive to being peeked so they peek the next...
Matt S Trout [Sun, 10 Oct 2010 18:56:56 +0000 (19:56 +0100)]
lib/HTML/Zoom/FilterStream.pm
lib/HTML/Zoom/MappedStream.pm
lib/HTML/Zoom/StreamBase.pm
t/balance.t [new file with mode: 0644]

index 4bee41c..e349faf 100644 (file)
@@ -23,7 +23,7 @@ sub new {
 }
 
 sub _next {
-  my ($self) = @_;
+  my ($self, $am_peek) = @_;
 
   # if our main stream is already gone then we can short-circuit
   # straight out - there's no way for an alternate stream to be there
@@ -35,9 +35,12 @@ sub _next {
   # it's gone - we're still effectively "in the match" but this is the
   # point at which that fact is abstracted away from downstream consumers
 
+  my $_next = $am_peek ? 'peek' : 'next';
+
   if (my $alt = $self->{_alt_stream}) {
 
-    if (my ($evt) = $alt->next) {
+    if (my ($evt) = $alt->$_next) {
+      $self->{_peeked_from} = $alt if $am_peek;
       return $evt;
     }
 
@@ -49,7 +52,9 @@ sub _next {
 
   # if there's no alternate stream currently, process the main stream
 
-  while (my ($evt) = $self->{_stream}->next) {
+  while (my ($evt) = $self->{_stream}->$_next) {
+
+    $self->{_peeked_from} = $self->{_stream} if $am_peek;
 
     # don't match this event? return it immediately
 
@@ -78,8 +83,9 @@ sub _next {
     # the filter returned a stream - if it contains something return the
     # first entry and stash it as the new alternate stream
 
-    if (my ($new_evt) = $res->next) {
+    if (my ($new_evt) = $res->$_next) {
       $self->{_alt_stream} = $res;
+      $self->{_peeked_from} = $res if $am_peek;
       return $new_evt;
     }
 
@@ -87,6 +93,18 @@ sub _next {
     # - this will happens for e.g. with an in place close (<foo />) that's
     # being removed. In that case, we fall off to loop back round and try
     # the next event from our main stream
+  } continue {
+
+    # if we fell off the bottom (empty new alternate stream or filter ate
+    # the event) then we need to advance our internal stream one so that the
+    # top of the while loop gets the right thing; also, we need to clear the
+    # _peeked_from in case our source stream is exhausted (it'll be
+    # re-assigned if the while condition gets a new event)
+
+    if ($am_peek) {
+      $self->{_stream}->next;
+      delete $self->{_peeked_from};
+    }
   }
 
   # main stream exhausted so throw it away so we hit the short circuit
index 7d0c505..e6183cd 100644 (file)
@@ -13,12 +13,14 @@ sub new {
 }
 
 sub _next {
-  return unless (my $self = shift)->{_source};
+  my ($self, $am_peek) = @_;
+  return unless $self->{_source};
   # If we were aiming for a "true" perl-like map then we should
   # elegantly handle the case where the map function returns 0 events
   # and the case where it returns >1 - if you're reading this comment
   # because you wanted it to do that, now would be the time to fix it :)
-  if (my ($next) = $self->{_source}->next) {
+  if (my ($next) = $self->{_source}->${\($am_peek ? 'peek' : 'next')}) {
+    $self->{_peeked_from} = $next if $am_peek;
     local $_ = $next;
     return $self->{_mapper}->($next);
   }
index e9bee1c..6c0b416 100644 (file)
@@ -11,7 +11,7 @@ sub peek {
   if (exists $self->{_peeked}) {
     return ($self->{_peeked});
   }
-  if (my ($peeked) = $self->_next) {
+  if (my ($peeked) = $self->_next(1)) {
     return ($self->{_peeked} = $peeked);
   }
   return;
@@ -23,6 +23,9 @@ sub next {
   # peeked entry so return that
 
   if (exists $self->{_peeked}) {
+    if (my $peeked_from = delete $self->{_peeked_from}) {
+      $peeked_from->next;
+    }
     return (delete $self->{_peeked});
   }
 
@@ -80,4 +83,9 @@ sub apply {
   $self->$code;
 }
 
+sub to_html {
+  my ($self) = @_;
+  $self->_zconfig->producer->html_from_stream($self);
+}
+
 1;
diff --git a/t/balance.t b/t/balance.t
new file mode 100644 (file)
index 0000000..5a3009d
--- /dev/null
@@ -0,0 +1,30 @@
+use strictures 1;
+use Test::More;
+use HTML::Zoom;
+
+my $z = HTML::Zoom->from_html(q{<html>
+  <body>
+    <div class="outer">
+      <div class="inner"><span /></div>
+    </div>
+  </body>
+</html>});
+
+is(
+  $z->select('.outer')
+    ->collect_content({
+       filter => sub { $_->select('.inner')->replace_content('bar!') },
+       passthrough => 1
+      })
+    ->to_html,
+  q{<html>
+  <body>
+    <div class="outer">
+      <div class="inner">bar!</div>
+    </div>
+  </body>
+</html>},
+  "filter within collect works ok"
+);
+
+done_testing;