fill example code stubbed out, pluss more docs;
John Napiorkowski [Tue, 5 Apr 2011 16:34:34 +0000 (12:34 -0400)]
lib/HTML/Zoom.pm
lib/HTML/Zoom/FilterBuilder.pm
t/fill.t
t/wrap_into.t [new file with mode: 0644]

index ee24979..377621a 100644 (file)
@@ -157,98 +157,6 @@ sub AUTOLOAD {
   return $self = $self->select($selector)->$meth(@args);
 }
 
-## mst: I've been thinking
-## mst: $zoom->fill($selector => $text) -> $zoom->select($selector)->replace_content($text)
-## mst: same for \$html
-## mst: then if it's an object or an arrayref we assume repeat_content
-
-=head2 fill
-
-  @args = ($selector => $text||\$text) ## $zoom->select($selector)->replace_content($text)
-  @args = ($selector => \@arrayref||$coderef) ## $zoom->select($selector)->repeat_content(\@arrayref)
-  @args = [qw/a b c/] => {}, $object
-
-=cut
-
-sub fill {
-  my $self = shift;
-  while(@_) {
-    my $selector = shift;
-    my $args = shift;
-    my $type = Scalar::Util::reftype($args) ? Scalar::Util::reftype($args) : 'SCALAR';
-    if($type eq 'ARRAY' || $type eq 'CODE') {
-      $self = $self->select($selector)->repeat_content($args);
-    } elsif($type eq 'SCALAR') {
-      $self = $self->select($selector)->replace_content($args);
-    } elsif($type eq 'HASH') {
-      die "Selector must be ArrayRef"
-        unless ref($selector) eq 'ARRAY';
-      foreach my $attr (@$selector) {
-        my $val = Scalar::Util::blessed($args) ? $args->$attr : $args->{$attr};
-        next unless defined($val);
-        $self = $self->select(".$attr")->replace_content($val);
-      }
-    } else {
-      die "I don't know what to do with args of type $type";
-    }
-  }
-  return $self;
-}
-
-=head2 wrap_with
-
-    my $layout = HTML::Zoom->from_html(<<HTML);
-    <html>
-      <head>
-        <title>Default Title</title>
-        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-       <meta name="keywords" content="my special website" />
-        <link rel="shortcut icon" href="/favicon.ico" />
-           <link rel="stylesheet" href="/css/core.css" type="text/css" />
-      </head>
-      </body id="content-area">
-        <div>Default Content</div>
-      </body>
-    </html>
-    HTML
-
-    my $content = HTML::Zoom->from_html(<<HTML);
-    <html>
-      <head>
-        <title>My Awesome Page</title>
-      </head>
-      </body>
-        <h1>Special Page</h1>
-        <p>Here is some content</p>
-      </body>
-    </html>
-    HTML
-
-    $content
-      ->template($layout)  ## is a wrap object of some sort
-      ->match('title')
-      ->match('body')
-      ->apply
-
-    ## ^^ would be similar to:
-
-    my (@title, @body);
-    $content
-      ->select('title')
-      ->collect_content({into => \@title})
-      ->select('body')
-      ->collect_content({into => \@body})
-      ->run;
-
-    $layout
-      ->select('title')
-      ->replace_content(\@title);
-      ->select('body')
-      ->replace_content(\@body);
-      ->...
-
-=cut
-
 1;
 
 =head1 NAME
index d1e169a..4f9cf27 100644 (file)
@@ -278,7 +278,7 @@ sub repeat {
   if ($repeat_between) {
     $options->{filter} = sub {
       $_->select($repeat_between)->collect({ into => \@between })
-    };
+    }
   }
   my $repeater = sub {
     my $s = $self->_stream_from_proto($repeat_for);
@@ -313,6 +313,16 @@ sub repeat_content {
   $self->repeat($repeat_for, { %{$options||{}}, content => 1 })
 }
 
+sub select {
+  my ($self, $selector) = @_;
+  return HTML::Zoom::TransformBuilder->new({
+    zconfig => $self->_zconfig,
+    selector => $selector,
+    filters => [],
+    proto => $self,
+  });
+}
+
 1;
 
 =head1 NAME
@@ -639,34 +649,12 @@ or another L<HTML::Zoom> object.
 
 =head2 repeat
 
-    $zoom->select('.item')->repeat(sub {
-      if (my $row = $db_thing->next) {
-        return sub { $_->select('.item-name')->replace_content($row->name) }
-      } else {
-        return
-      }
-    }, { flush_before => 1 });
-
-Run I<$repeat_for>, which should be iterator (code reference) returning
-subroutines, reference to array of subroutines, or other zoom-able object
-consisting of transformations.  Those subroutines would be run with $_
-local-ized to result of L<HTML::Zoom/select> (of collected elements), and with
-said result passed as parameter to subroutine.
-
-You might want to use iterator when you don't have all elements upfront
+For a given selection, repeat over transformations, typically for the purposes
+of populating lists.  Takes either an array of anonymous subroutines or a zoom-
+able object consisting of transformation.
 
-    $zoom = $zoom->select('.contents')->repeat(sub {
-      while (my $line = $fh->getline) {
-        return sub {
-          $_->select('.lno')->replace_content($fh->input_line_number)
-            ->select('.line')->replace_content($line)
-        }
-      }
-      return
-    });
-
-You might want to use array reference if it doesn't matter that all iterations
-are pre-generated
+Example of array reference style (when it doesn't matter that all iterations are
+pre-generated)
 
     $zoom->select('table')->repeat([
       map {
@@ -676,8 +664,27 @@ are pre-generated
         }
       } @list
     ]);
+    
+Subroutines would be run with $_ localized to result of L<HTML::Zoom/select> (of
+collected elements), and with said result passed as parameter to subroutine.
+
+You might want to use CodeStream when you don't have all elements upfront
+
+    $zoom->select('.contents')->repeat(sub {
+      HTML::Zoom::CodeStream->new({
+        code => sub {
+          while (my $line = $fh->getline) {
+            return sub {
+              $_->select('.lno')->replace_content($fh->input_line_number)
+                ->select('.line')->replace_content($line)
+            }
+          }
+          return
+        },
+      })
+    });
 
-In addition to common options as in L</collect>, it also supports
+In addition to common options as in L</collect>, it also supports:
 
 =over
 
index 4c2901e..715fc7d 100644 (file)
--- a/t/fill.t
+++ b/t/fill.t
@@ -57,4 +57,447 @@ ok my $title = sub {
   return $z;
 };
 
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(
+      [
+        sub { $_->select('li')->replace_content('Real Life1') },
+        sub { $_->select('li')->replace_content('Real Life2') },
+        sub { $_->select('li')->replace_content('Real Life3') },
+      ],
+    )
+    ->to_html;
+
+  is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>',
+    'Got correct from repeat_content';
+}
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content([
+      map { my $i = $_; +sub {$_->select('li')->replace_content("Real Life$i")} } (1,2,3)
+    ])
+    ->to_html;
+
+  is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>',
+    'Got correct from repeat_content';
+}
+
+
+use HTML::Zoom::CodeStream;
+sub code_stream (&) {
+  my $code = shift;
+  return sub {
+    HTML::Zoom::CodeStream->new({
+      code => $code,
+    });
+  }
+}
+
+{
+  my @list = qw(foo bar baz);
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(code_stream {
+      if (my $name = shift @list) {
+        return sub { $_->select('li')->replace_content($name) };
+      } else {
+        return
+      }
+    })
+    ->to_html;
+  
+  is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>',
+    'Got correct from repeat_content';
+}
+
+{
+  my @list = qw(foo bar baz);
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(sub {
+      HTML::Zoom::CodeStream->new({
+        code => sub {
+          if (my $name = shift @list) {
+            return sub { $_->select('li')->replace_content($name) };
+          } else {
+            return
+          }
+        }
+      });
+    })
+    ->to_html;
+  
+  is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>',
+    'Got correct from repeat_content';
+}
+
+{
+  use Scalar::Util ();
+  my $next_item_from_array = sub {
+    my @items = @_;
+    return sub { shift @items };      
+  };
+  
+  my $next_item_from_proto = sub {
+    my $proto = shift;
+    if (
+      ref $proto eq 'ARRAY' ||
+      ref $proto eq 'HASH' ||
+      Scalar::Util::blessed($proto)
+    ) {
+      return $next_item_from_array->($proto, @_);
+    } elsif(ref $proto eq 'CODE' ) {
+      return $proto;
+    } else {
+      die "Don't know what to do with $proto, it's a ". ref($proto);
+    }
+  };
+  
+  my $normalize_targets = sub {
+    my $targets = shift;
+    my $targets_type = ref $targets;
+    return $targets_type eq 'ARRAY' ? $targets
+      : $targets_type eq 'HASH' ? [ map { +{$_=>$targets->{$_}} } keys %$targets ]
+        : die "targets data structure ". ref($targets). " not understood";
+  };
+  
+  my $replace_from_hash_or_object = sub {
+    my ($datum, $value) = @_;
+    return ref($datum) eq 'HASH' ?
+      $datum->{$value} : $datum->$value; 
+  };
+  
+  my $fill = sub {
+    my ($zoom, $targets, @rest) = @_;
+    $zoom->repeat_content(sub {
+      my $itr = $next_item_from_proto->(@rest);
+      $targets = $normalize_targets->($targets);
+      HTML::Zoom::CodeStream->new({
+        code => sub {
+          my $cnt = 0;
+          if(my $datum = $itr->($zoom, $cnt)) {
+            $cnt++;
+            return sub {
+              for my $idx(0..$#{$targets}) {
+                my $target = $targets->[$idx];
+                my ($match, $replace) = do {
+                  my $type = ref $target;
+                  $type ? ($type eq 'HASH' ? do { 
+                      my ($match, $value) = %$target;
+                      ref $value eq 'CODE' ?
+                        ($match, $value->($datum, $idx, $_))
+                        : ($match, $replace_from_hash_or_object->($datum, $value));
+                    } : die "What?")
+                    : do {
+                        ref($datum) eq 'ARRAY' ?
+                        ($target, $datum->[$idx]) :
+                        ( '.'.$target, $replace_from_hash_or_object->($datum, $target));
+                      };
+                };
+                $_ = $_->select($match)
+                  ->replace_content($replace);                
+              } $_;
+            };
+          } else {
+            return;
+          }
+        },
+      });
+    });
+  };
+  
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        ['.even','.odd'],
+        [0,1],
+        [2,3],
+      ), 'Made Zoom object from array';
+      
+    is(
+      $z->to_html,
+      '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+      'Got correct from repeat_content'
+    );  
+  }
+  
+  {
+    my @items = ( [0,1], [2,3] );
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        ['.even','.odd'],
+        sub { shift @items }
+      ), 'Made Zoom object from itr';
+      
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+      ok(
+         (@items = ( [0,1], [2,3] )),
+         'Reset the items array'
+      );
+    }
+  }
+  
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [
+          {'.even' => sub { my ($i,$cnt,$z) = @_; return $i->[0]; } },
+          {'.odd' => sub { my ($i,$cnt,$z) = @_; return $i->[1]; } },         
+        ],
+        [0,1],
+        [2,3],
+      ), 'Made Zoom object from code style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+  
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [
+          {'.even' => sub { my ($i,$cnt,$z) = @_; return $i->{even}; } },
+          {'.odd' => sub { my ($i,$cnt,$z) = @_; return $i->{odd}; } },         
+        ],
+        { even => 0, odd => 1},
+        { even => 2, odd => 3},        
+      ), 'Made Zoom object from code style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+  
+  {
+    ok my $even_or_odd = sub {
+      my ($i,$cnt,$z) = @_;
+      return $cnt % 2 ? $i->{odd} : $i->{even};
+    };
+    
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [
+          {'.even' => $even_or_odd },
+          {'.odd' => $even_or_odd },         
+        ],
+        { even => 0, odd => 1},
+        { even => 2, odd => 3},        
+      ), 'Made Zoom object from code style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+  
+  {
+    ok my $even_or_odd = sub {
+      my ($i,$cnt,$z) = @_;
+      return $cnt % 2 ? $i->{odd} : $i->{even};
+    };
+    
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [
+          map { +{$_ => $even_or_odd} } qw(.even .odd),
+        ],
+        { even => 0, odd => 1},
+        { even => 2, odd => 3},        
+      ), 'Made Zoom object from code style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+  
+  {
+    {
+      package Test::HTML::Zoom::EvenOdd;
+        
+      sub new {
+        my $class = shift;
+        bless { _e=>$_[0], _o=>$_[1] }, $class; 
+      }
+        
+      sub even { shift->{_e} }
+      sub odd { shift->{_o} }
+    }
+    
+    ok my $even_or_odd = sub {
+      my ($i,$cnt,$z) = @_;
+      return $cnt % 2 ? $i->odd : $i->even;
+    };
+    
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [
+          map { +{$_ => $even_or_odd} } qw(.even .odd),
+        ],
+        Test::HTML::Zoom::EvenOdd->new(0,1),
+        Test::HTML::Zoom::EvenOdd->new(2,3),
+      ), 'Made Zoom object from code style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [
+          { '.even' => 'even'},
+          { '.odd' => 'odd'},
+        ],
+        { even => 0, odd => 1},
+        { even => 2, odd => 3},        
+      ), 'Made Zoom object from declare accessors style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [
+          { '.even' => 'even'},
+          { '.odd' => 'odd'},
+        ],
+        Test::HTML::Zoom::EvenOdd->new(0,1),
+        Test::HTML::Zoom::EvenOdd->new(2,3),        
+      ), 'Made Zoom object from declare accessors style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        {
+          '.even' => 'even',
+          '.odd' => 'odd'
+        },
+        Test::HTML::Zoom::EvenOdd->new(0,1),
+        Test::HTML::Zoom::EvenOdd->new(2,3),        
+      ), 'Made Zoom object from declare accessors style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+  
+  ## Helper for when you have a list of object or hashs and you just want to map
+  ## target classes to accessors
+  
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        { map { ".".$_ => $_ } qw(even odd) },
+        Test::HTML::Zoom::EvenOdd->new(0,1),
+        Test::HTML::Zoom::EvenOdd->new(2,3),        
+      ), 'Made Zoom object from declare accessors style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+
+  ## if the target is arrayref but the data is a list of objects or hashes, we
+  ## guess the target is a class form of the target items, but use the value of
+  ## the item as the hash or object accessor
+  
+  {
+    ok my $z = HTML::Zoom
+      ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>])
+      ->select('ul')
+      ->$fill(
+        [qw(even odd)],
+        Test::HTML::Zoom::EvenOdd->new(0,1),
+        Test::HTML::Zoom::EvenOdd->new(2,3),        
+      ), 'Made Zoom object from declare accessors style';
+
+    for my $cnt (1..2) {
+      is(
+        $z->to_html,
+        '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>',
+        'Got correct from repeat_content: '.$cnt
+      );
+    }
+  }
+}
+
 done_testing;
diff --git a/t/wrap_into.t b/t/wrap_into.t
new file mode 100644 (file)
index 0000000..f5ae75c
--- /dev/null
@@ -0,0 +1,62 @@
+use strict;
+use warnings FATAL => 'all';
+
+use HTML::Zoom;
+use Test::More;
+
+ok 1, 'stub for wrapping or layout helpers'
+done_testing;
+
+=head2 wrap_with
+
+    my $layout = HTML::Zoom->from_html(<<HTML);
+    <html>
+      <head>
+        <title>Default Title</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+       <meta name="keywords" content="my special website" />
+        <link rel="shortcut icon" href="/favicon.ico" />
+           <link rel="stylesheet" href="/css/core.css" type="text/css" />
+      </head>
+      </body id="content-area">
+        <div>Default Content</div>
+      </body>
+    </html>
+    HTML
+
+    my $content = HTML::Zoom->from_html(<<HTML);
+    <html>
+      <head>
+        <title>My Awesome Page</title>
+      </head>
+      </body>
+        <h1>Special Page</h1>
+        <p>Here is some content</p>
+      </body>
+    </html>
+    HTML
+
+    $content
+      ->template($layout)  ## is a wrap object of some sort
+      ->match('title')
+      ->match('body')
+      ->apply
+
+    ## ^^ would be similar to:
+
+    my (@title, @body);
+    $content
+      ->select('title')
+      ->collect_content({into => \@title})
+      ->select('body')
+      ->collect_content({into => \@body})
+      ->run;
+
+    $layout
+      ->select('title')
+      ->replace_content(\@title);
+      ->select('body')
+      ->replace_content(\@body);
+      ->...
+
+=cut
\ No newline at end of file