- - add transform_attribute method (rbuels)
+TBA
+ - New Feature: HTML::Zoom will autoload FilterBuilder functions so that
+ you can avoid a bit of boilerplate in method calls. Now you can replace:
+
+ $z->select('div')->replace_content("Hello World");
+
+ With:
+
+ $z->replace_content(div => "Hello World");
+
+ - Lots of changes to FilterBuilder so that functionality matched the docs
+ better, improved the docs and added tests for documented functions to
+ avoid future regressions.
+
- add / to excluded characters in attribute names to correctly parse <br/>
+ - add transform_attribute method (rbuels)
0.009004 2011-02-14
use HTML::Zoom::ReadFH;
use HTML::Zoom::Transform;
use HTML::Zoom::TransformBuilder;
+use Scalar::Util ();
our $VERSION = '0.009004';
$self->select($self->{transforms}->[-1]->selector);
}
+sub AUTOLOAD {
+ my ($self, $selector, @args) = @_;
+ my $sel = $self->select($selector);
+ my $meth = our $AUTOLOAD;
+ $meth =~ s/.*:://;
+ if(my $cr = $sel->_zconfig->filter_builder->can($meth)) {
+ return $sel->$meth(@args);
+ } else {
+ die "We can't do $meth on ->select('$selector')";
+ }
+}
+
1;
=head1 NAME
Re-runs the previous select to allow you to chain actions together on the
same selector.
+=head1 AUTOLOAD METHODS
+
+L<HTML::Zoom> AUTOLOADS methods against L</select> so that you can reduce a
+certain amount of boilerplate typing. This allows you to replace:
+
+ $z->select('div')->replace_content("Hello World");
+
+With:
+
+ $z->replace_content(div => "Hello World");
+
+Besides saving a few keys per invocations, you may feel this looks neater
+in your code and increases understanding.
+
=head1 AUTHOR
mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
sub add_before {
my ($self, $events) = @_;
- sub { return $self->_stream_from_array(@$events, $_[0]) };
+ my $coll_proto = $self->collect({ passthrough => 1 });
+ sub {
+ my $emit = $self->_stream_from_proto($events);
+ my $coll = &$coll_proto;
+ if($coll) {
+ if(ref $coll eq 'ARRAY') {
+ my $firstbit = $self->_stream_from_proto([$coll->[0]]);
+ return $self->_stream_concat($emit, $firstbit, $coll->[1]);
+ } elsif(ref $coll eq 'HASH') {
+ return [$emit, $coll];
+ } else {
+ return $self->_stream_concat($emit, $coll);
+ }
+ } else { return $emit }
+ }
}
sub add_after {
my $coll_proto = $self->collect({ passthrough => 1 });
sub {
my ($evt) = @_;
- my $emit = $self->_stream_from_array(@$events);
+ my $emit = $self->_stream_from_proto($events);
my $coll = &$coll_proto;
return ref($coll) eq 'HASH' # single event, no collect
? [ $coll, $emit ]
sub prepend_content {
my ($self, $events) = @_;
+ my $coll_proto = $self->collect({ passthrough => 1, content => 1 });
sub {
my ($evt) = @_;
+ my $emit = $self->_stream_from_proto($events);
if ($evt->{is_in_place_close}) {
$evt = { %$evt }; delete @{$evt}{qw(raw is_in_place_close)};
return [ $evt, $self->_stream_from_array(
- @$events, { type => 'CLOSE', name => $evt->{name} }
+ $emit->next, { type => 'CLOSE', name => $evt->{name} }
) ];
}
- return $self->_stream_from_array($evt, @$events);
+ my $coll = &$coll_proto;
+ return [ $coll->[0], $self->_stream_concat($emit, $coll->[1]) ];
};
}
my $coll_proto = $self->collect({ passthrough => 1, content => 1 });
sub {
my ($evt) = @_;
+ my $emit = $self->_stream_from_proto($events);
if ($evt->{is_in_place_close}) {
$evt = { %$evt }; delete @{$evt}{qw(raw is_in_place_close)};
return [ $evt, $self->_stream_from_array(
- @$events, { type => 'CLOSE', name => $evt->{name} }
+ $emit->next, { type => 'CLOSE', name => $evt->{name} }
) ];
}
my $coll = &$coll_proto;
- my $emit = $self->_stream_from_array(@$events);
return [ $coll->[0], $self->_stream_concat($coll->[1], $emit) ];
};
}
if ($repeat_between) {
$options->{filter} = sub {
$_->select($repeat_between)->collect({ into => \@between })
- };
+ }
}
my $repeater = sub {
my $s = $self->_stream_from_proto($repeat_for);
->select('p')
->set_attribute(class=>'paragraph')
->select('div')
- ->set_attribute(name=>'class', value=>'divider');
+ ->set_attribute({name=>'class', value=>'divider'});
Overrides existing values, if such exist. When multiple L</set_attribute>
=head2 add_to_attribute
Adds a value to an existing attribute, or creates one if the attribute does not
-yet exist.
+yet exist. You may call this method with either an Array or HashRef of Args.
+
+Here's the 'long form' HashRef:
+
+ $html_zoom
+ ->select('p')
+ ->set_attribute(class=>'paragraph')
+ ->then
+ ->add_to_attribute({name=>'class', value=>'divider'});
+
+And the exact same effect using the 'short form' Array:
$html_zoom
->select('p')
->set_attribute(class=>'paragraph')
->then
- ->add_to_attribute(name=>'class', value=>'divider');
+ ->add_to_attribute(class=>'divider');
Attributes with more than one value will have a dividing space.
=head2 prepend_content
- TBD
+Similar to add_before, but adds the content to the match.
+
+ HTML::Zoom
+ ->from_html(q[<p>World</p>])
+ ->select('p')
+ ->prepend_content("Hello ")
+ ->to_html
+
+ ## <p>Hello World</p>
+
+Acceptable values are strings, scalar refs and L<HTML::Zoom> objects
=head2 append_content
- TBD
+Similar to add_after, but adds the content to the match.
+
+ HTML::Zoom
+ ->from_html(q[<p>Hello </p>])
+ ->select('p')
+ ->prepend_content("World")
+ ->to_html
+
+ ## <p>Hello World</p>
+
+Acceptable values are strings, scalar refs and L<HTML::Zoom> objects
=head2 replace
=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.
+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.
-You might want to use iterator when you don't have all elements upfront
-
- $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
$self->_zconfig->producer->html_from_stream($self);
}
+sub AUTOLOAD {
+ my ($self, $selector, @args) = @_;
+ my $meth = our $AUTOLOAD;
+ $meth =~ s/.*:://;
+ return $self = $self->select($selector)->$meth(@args);
+}
+
1;
use strictures 1;
use base qw(HTML::Zoom::SubObject);
-
use Scalar::Util ();
use HTML::Zoom::CodeStream;
--- /dev/null
+use strict;
+use warnings FATAL => 'all';
+
+use HTML::Zoom;
+use Test::More;
+
+ok my $zoom = HTML::Zoom->from_html(<<HTML);
+<html>
+ <head>
+ <title>Hi!</title>
+ </head>
+ <body id="content-area">
+ <h1>Test</h1>
+ <div>
+ <p class="first-para">Some Stuff</p>
+ <p class="body-para">More Stuff</p>
+ <p class="body-para">Even More Stuff</p>
+ <ol>
+ <li class="first-item odd">First</li>
+ <li class="body-items even">Stuff A</li>
+ <li class="body-items odd">Stuff B</li>
+ <li class="body-items even">Stuff C</li>
+ <li class="last-item odd">Last</li>
+ </ol>
+ <ul class="items">
+ <li class="first">space</li>
+ <li class="second">space</li>
+ </ul>
+ <p class="body-para">Even More stuff</p>
+ <p class="last-para">Some Stuff</p>
+ </div>
+ <div id="blocks">
+ <h2 class="h2-item">Sub Item</h2>
+ </div>
+ <ul id="people">
+ <li class="name">name</li>
+ <li class="email">email</li>
+ </ul>
+ <ul id="people2">
+ <li class="name">name</li>
+ <li class="email">email</li>
+ </ul>
+ <ul id="object">
+ <li class="aa">AA</li>
+ <li class="bb">BB</li>
+ </ul>
+ <p id="footer">Copyright 2222</p>
+ </body>
+</html>
+HTML
+
+ok ! eval { $zoom->non_method('.first-param' => 'First!'); 1},
+ 'Properly die on bad method';
+
+ok my $title = sub {
+ my ($z, $content) = @_;
+ $z = $z->replace_content(title =>$content);
+ return $z;
+};
+
+ok my $dwim = $zoom
+ ->$title('Hello NYC')
+ ->replace_content('.first-para' => 'First!')
+ ->replace_content('.last-para' => 'Last!')
+ ->add_to_attribute(p => class => 'para')
+ ->prepend_content('.first-item' => [{type=>'TEXT', raw=>'FIRST: '}])
+ ->prepend_content('.last-item' => [{type=>'TEXT', raw=>'LAST: '}])
+ ->replace_content('title' => 'Hello World')
+ ->repeat_content(
+ '.items' => [
+ map {
+ my $v = $_;
+ sub {
+ $_->replace_content('.first' => $v)
+ ->replace_content('.second' => $v);
+ },
+ } (111,222)
+ ]
+ )
+ ->to_html;
+
+like(
+ HTML::Zoom
+ ->from_html(q[<title>Hi!</title>])
+ ->$title('Hello NYC')
+ ->to_html,
+ qr/Hello NYC/,
+ 'Got correct title(custom subref)'
+);
+
+like(
+ HTML::Zoom
+ ->from_html(q[<p>Hi!</p>])
+ ->replace_content(p=>'Ask not what your country can do for you...')
+ ->to_html,
+ qr/Ask not what your country can do for you\.\.\./,
+ 'Got correct from replace_content'
+);
+
+like(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->add_to_attribute('p', {name => 'class', value => 'para'})
+ ->to_html,
+ qr/first para/,
+ 'Got correct from add_to_attribute'
+);
+
+like(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->add_to_attribute('p', class => 'para')
+ ->to_html,
+ qr/first para/,
+ 'Got correct from add_to_attribute'
+);
+
+like(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->set_attribute('p', class => 'para')
+ ->to_html,
+ qr/class="para"/,
+ 'Got correct from set_attribute'
+);
+
+like(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->set_attribute('p', {name => 'class', value => 'para'})
+ ->to_html,
+ qr/class="para"/,
+ 'Got correct from set_attribute'
+);
+
+is(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->add_before(p=>[{type=>'TEXT', raw=>'added before'}])
+ ->to_html,
+ 'added before<p class="first">Hi!</p>',
+ 'Got correct from add_before'
+);
+
+is(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->add_after(p=>[{type=>'TEXT', raw=>'added after'}])
+ ->to_html,
+ '<p class="first">Hi!</p>added after',
+ 'Got correct from add_after'
+);
+
+is(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->prepend_content(p=>[{type=>'TEXT', raw=>'prepend_content'}])
+ ->to_html,
+ '<p class="first">prepend_contentHi!</p>',
+ 'Got correct from prepend_content'
+);
+
+is(
+ HTML::Zoom
+ ->from_html(q[<p class="first">Hi!</p>])
+ ->append_content(p=>[{type=>'TEXT', raw=>'append_content'}])
+ ->to_html,
+ '<p class="first">Hi!append_content</p>',
+ 'Got correct from append_content'
+);
+
+{
+ my @body;
+ ok my $body = HTML::Zoom
+ ->from_html(q[<div>My Stuff Is Here</div>])
+ ->collect_content(div => { into => \@body, passthrough => 1})
+ ->to_html, 'Collected Content';
+
+ is $body, "<div>My Stuff Is Here</div>", "Got expected";
+
+ is(
+ HTML::Zoom->from_events(\@body)->to_html,
+ 'My Stuff Is Here',
+ 'Collected the right stuff',
+ );
+}
+
+like(
+ HTML::Zoom
+ ->from_html(q[<ul class="items"><li class="first">first</li><li class="second">second</li></ul>])
+ ->repeat_content(
+ '.items' => [
+ map {
+ my $v = $_;
+ sub {
+ $_->replace_content('.first' => $v)
+ ->replace_content('.second' => $v);
+ },
+ } (111,222)
+ ]
+ )
+ ->to_html,
+ qr[<ul class="items"><li class="first">111</li><li class="second">111</li><li class="first">222</li><li class="second">222</li></ul>],
+ 'Got correct list'
+);
+
+{
+ ok my $z = HTML::Zoom
+ ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+ ->add_before(p=>'added before');
+
+ is $z->to_html, '<div>Life, whatever</div>added before<p class="first">Hi!</p>',
+ 'Got correct from add_before';
+}
+
+{
+ ok my $z = HTML::Zoom
+ ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+ ->add_after(p=>'added after');
+
+ is $z->to_html, '<div>Life, whatever</div><p class="first">Hi!</p>added after',
+ 'Got correct from add_after';
+}
+
+{
+ ok my $z = HTML::Zoom
+ ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+ ->append_content(p=>'appended');
+
+ is $z->to_html, '<div>Life, whatever</div><p class="first">Hi!appended</p>',
+ 'Got correct from append_content';
+}
+
+{
+ ok my $z = HTML::Zoom
+ ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+ ->prepend_content(p=>'prepended');
+
+
+ is $z->to_html, '<div>Life, whatever</div><p class="first">prependedHi!</p>',
+ 'Got correct from prepend_content';
+}
+
+{
+ 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';
+}
+
+done_testing;
--- /dev/null
+use strict;
+use warnings FATAL => 'all';
+
+use HTML::Zoom;
+use Test::More;
+
+ok my $zoom = HTML::Zoom->from_html(<<HTML);
+<html>
+ <head>
+ <title>Hi!</title>
+ </head>
+ <body id="content-area">
+ <h1>Test</h1>
+ <div>
+ <p class="first-para">Some Stuff</p>
+ <p class="body-para">More Stuff</p>
+ <p class="body-para">Even More Stuff</p>
+ <ol>
+ <li class="first-item odd">First</li>
+ <li class="body-items even">Stuff A</li>
+ <li class="body-items odd">Stuff B</li>
+ <li class="body-items even">Stuff C</li>
+ <li class="last-item odd">Last</li>
+ </ol>
+ <ul class="items">
+ <li class="first">space</li>
+ <li class="second">space</li>
+ </ul>
+ <p class="body-para">Even More stuff</p>
+ <p class="last-para">Some Stuff</p>
+ </div>
+ <div id="blocks">
+ <h2 class="h2-item">Sub Item</h2>
+ </div>
+ <ul id="people">
+ <li class="name">name</li>
+ <li class="email">email</li>
+ </ul>
+ <ul id="people2">
+ <li class="name">name</li>
+ <li class="email">email</li>
+ </ul>
+ <ul id="object">
+ <li class="aa">AA</li>
+ <li class="bb">BB</li>
+ </ul>
+ <p id="footer">Copyright 2222</p>
+ </body>
+</html>
+HTML
+
+## Stub for testing the fill method to be
+
+ok my $title = sub {
+ my ($z, $content) = @_;
+ $z = $z->select('title')->replace_content($content);
+ 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';
+}
+
+done_testing;