if ($repeat_between) {
$options->{filter} = sub {
$_->select($repeat_between)->collect({ into => \@between })
- };
+ }
}
my $repeater = sub {
my $s = $self->_stream_from_proto($repeat_for);
$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
=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 {
}
} @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
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;