fill method
[catagits/HTML-Zoom.git] / lib / HTML / Zoom.pm
CommitLineData
d80786d0 1package HTML::Zoom;
2
1cf03540 3use strictures 1;
d80786d0 4
5use HTML::Zoom::ZConfig;
bf5a23d0 6use HTML::Zoom::ReadFH;
655965b3 7use HTML::Zoom::Transform;
eeeb0921 8use HTML::Zoom::TransformBuilder;
d80786d0 9
f107bef7 10our $VERSION = '0.009004';
7af7362d 11
12$VERSION = eval $VERSION;
13
d80786d0 14sub new {
15 my ($class, $args) = @_;
16 my $new = {};
17 $new->{zconfig} = HTML::Zoom::ZConfig->new($args->{zconfig}||{});
18 bless($new, $class);
19}
20
21sub zconfig { shift->_self_or_new->{zconfig} }
22
23sub _self_or_new {
24 ref($_[0]) ? $_[0] : $_[0]->new
25}
26
27sub _with {
28 bless({ %{$_[0]}, %{$_[1]} }, ref($_[0]));
29}
30
7567494d 31sub from_events {
d80786d0 32 my $self = shift->_self_or_new;
33 $self->_with({
7567494d 34 initial_events => shift,
d80786d0 35 });
36}
37
7567494d 38sub from_html {
39 my $self = shift->_self_or_new;
40 $self->from_events($self->zconfig->parser->html_to_events($_[0]))
41}
42
bf5a23d0 43sub from_file {
44 my $self = shift->_self_or_new;
45 my $filename = shift;
46 $self->from_html(do { local (@ARGV, $/) = ($filename); <> });
47}
48
d80786d0 49sub to_stream {
50 my $self = shift;
51 die "No events to build from - forgot to call from_html?"
52 unless $self->{initial_events};
53 my $sutils = $self->zconfig->stream_utils;
54 my $stream = $sutils->stream_from_array(@{$self->{initial_events}});
2f0c6a86 55 $stream = $_->apply_to_stream($stream) for @{$self->{transforms}||[]};
d80786d0 56 $stream
57}
58
bf5a23d0 59sub to_fh {
60 HTML::Zoom::ReadFH->from_zoom(shift);
61}
62
7567494d 63sub to_events {
64 my $self = shift;
65 [ $self->zconfig->stream_utils->stream_to_array($self->to_stream) ];
66}
67
bf5a23d0 68sub run {
69 my $self = shift;
7567494d 70 $self->to_events;
bf5a23d0 71 return
72}
73
74sub apply {
75 my ($self, $code) = @_;
76 local $_ = $self;
77 $self->$code;
78}
79
fdb039c6 80sub apply_if {
81 my ($self, $predicate, $code) = @_;
82 if($predicate) {
83 local $_ = $self;
84 $self->$code;
85 }
86 else {
87 $self;
88 }
89}
90
d80786d0 91sub to_html {
92 my $self = shift;
93 $self->zconfig->producer->html_from_stream($self->to_stream);
94}
95
96sub memoize {
97 my $self = shift;
98 ref($self)->new($self)->from_html($self->to_html);
99}
100
eeeb0921 101sub with_transform {
1c4455ae 102 my $self = shift->_self_or_new;
eeeb0921 103 my ($transform) = @_;
d80786d0 104 $self->_with({
2f0c6a86 105 transforms => [
106 @{$self->{transforms}||[]},
eeeb0921 107 $transform
2f0c6a86 108 ]
d80786d0 109 });
110}
eeeb0921 111
112sub with_filter {
113 my $self = shift->_self_or_new;
114 my ($selector, $filter) = @_;
115 $self->with_transform(
116 HTML::Zoom::Transform->new({
117 zconfig => $self->zconfig,
118 selector => $selector,
119 filters => [ $filter ]
120 })
121 );
122}
d80786d0 123
124sub select {
1c4455ae 125 my $self = shift->_self_or_new;
126 my ($selector) = @_;
eeeb0921 127 return HTML::Zoom::TransformBuilder->new({
128 zconfig => $self->zconfig,
129 selector => $selector,
130 proto => $self
131 });
d80786d0 132}
133
134# There's a bug waiting to happen here: if you do something like
135#
136# $zoom->select('.foo')
1c4455ae 137# ->remove_attribute(class => 'foo')
d80786d0 138# ->then
139# ->well_anything_really
140#
141# the second action won't execute because it doesn't match anymore.
142# Ideally instead we'd merge the match subs but that's more complex to
143# implement so I'm deferring it for the moment.
144
145sub then {
146 my $self = shift;
2f0c6a86 147 die "Can't call ->then without a previous transform"
148 unless $self->{transforms};
149 $self->select($self->{transforms}->[-1]->selector);
d80786d0 150}
151
c877066a 152## mst: well I'm thinking if basically
153## mst: $zoom->$whatever($selector => @args)
154## mst: becomes $zoom->select($selector)->$whatever(@args)
155
156sub AUTOLOAD {
157 my $self = shift;
158 my %selection_args = @_;
159 my $meth = our $AUTOLOAD;
160 $meth =~ s/.*:://;
161 while( my($selector, $args) = each %selection_args) {
162 $self = $self->select($selector)->$meth($args);
163 }
164 return $self
165}
c2d0669e 166
167## mst: I've been thinking
168## mst: $zoom->fill($selector => $text) -> $zoom->select($selector)->replace_content($text)
169## mst: same for \$html
170## mst: then if it's an object or an arrayref we assume repeat_content
171
172=head2 fill
173
174 @args = ($selector => $text||\$text) ## $zoom->select($selector)->replace_content($text)
175 @args =>($selector => \@arrayref||$coderef) ## $zoom->select($selector)->repeat_content(\@arrayref)
176
177=cut
178
179sub fill {
180 my $self = shift;
181 my %selection_args = @_;
182 while( my($selector, $args) = each %selection_args) {
183 my $type = ref($args) ? ref($args) : 'SCALAR';
184 if($type eq 'ARRAY' || $type eq 'CODE') {
185 $self = $self->select($selector)->repeat_content($args);
186 } elsif($type eq 'SCALAR') {
187 warn "$selector, $args";
188 $self = $self->select($selector)->replace_content($args);
189 } else {
190 die "I don't know what to do with args of type $type";
191 }
192 }
193 return $self;
194}
195
196=head2 wrap_with
197
198 my $layout = HTML::Zoom->from_html(<<HTML);
199 <html>
200 <head>
201 <title>Default Title</title>
202 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
203 <meta name="keywords" content="my special website" />
204 <link rel="shortcut icon" href="/favicon.ico" />
205 <link rel="stylesheet" href="/css/core.css" type="text/css" />
206 </head>
207 </body id="content-area">
208 <div>Default Content</div>
209 </body>
210 </html>
211 HTML
212
213 my $content = HTML::Zoom->from_html(<<HTML);
214 <html>
215 <head>
216 <title>My Awesome Page</title>
217 </head>
218 </body>
219 <h1>Special Page</h1>
220 <p>Here is some content</p>
221 </body>
222 </html>
223 HTML
224
225 $content
226 ->template($layout) ## is a wrap object of some sort
227 ->match('title')
228 ->match('body')
3f03e15a 229 ->apply
c2d0669e 230
231 ## ^^ would be similar to:
232
233 my (@title, @body);
234 $content
235 ->select('title')
236 ->collect_content({into => \@title})
237 ->select('body')
238 ->collect_content({into => \@body})
239 ->run;
240
241 $layout
242 ->select('title')
243 ->replace_content(\@title);
244 ->select('body')
245 ->replace_content(\@body);
246 ->...
247
248=cut
249
d80786d0 2501;
251
252=head1 NAME
253
254HTML::Zoom - selector based streaming template engine
255
256=head1 SYNOPSIS
257
258 use HTML::Zoom;
259
260 my $template = <<HTML;
261 <html>
262 <head>
263 <title>Hello people</title>
264 </head>
265 <body>
266 <h1 id="greeting">Placeholder</h1>
267 <div id="list">
268 <span>
269 <p>Name: <span class="name">Bob</span></p>
270 <p>Age: <span class="age">23</span></p>
271 </span>
272 <hr class="between" />
273 </div>
274 </body>
275 </html>
276 HTML
277
278 my $output = HTML::Zoom
279 ->from_html($template)
280 ->select('title, #greeting')->replace_content('Hello world & dog!')
281 ->select('#list')->repeat_content(
282 [
283 sub {
284 $_->select('.name')->replace_content('Matt')
285 ->select('.age')->replace_content('26')
286 },
287 sub {
288 $_->select('.name')->replace_content('Mark')
289 ->select('.age')->replace_content('0x29')
290 },
291 sub {
292 $_->select('.name')->replace_content('Epitaph')
293 ->select('.age')->replace_content('<redacted>')
294 },
295 ],
296 { repeat_between => '.between' }
297 )
298 ->to_html;
299
300will produce:
301
302=begin testinfo
303
304 my $expect = <<HTML;
305
306=end testinfo
307
308 <html>
309 <head>
310 <title>Hello world &amp; dog!</title>
311 </head>
312 <body>
313 <h1 id="greeting">Hello world &amp; dog!</h1>
314 <div id="list">
315 <span>
316 <p>Name: <span class="name">Matt</span></p>
317 <p>Age: <span class="age">26</span></p>
318 </span>
319 <hr class="between" />
320 <span>
321 <p>Name: <span class="name">Mark</span></p>
322 <p>Age: <span class="age">0x29</span></p>
323 </span>
324 <hr class="between" />
325 <span>
326 <p>Name: <span class="name">Epitaph</span></p>
327 <p>Age: <span class="age">&lt;redacted&gt;</span></p>
328 </span>
329
330 </div>
331 </body>
332 </html>
333
334=begin testinfo
335
336 HTML
337 is($output, $expect, 'Synopsis code works ok');
338
339=end testinfo
340
1c4455ae 341=head1 DANGER WILL ROBINSON
342
343This is a 0.9 release. That means that I'm fairly happy the API isn't going
344to change in surprising and upsetting ways before 1.0 and a real compatibility
345freeze. But it also means that if it turns out there's a mistake the size of
346a politician's ego in the API design that I haven't spotted yet there may be
347a bit of breakage between here and 1.0. Hopefully not though. Appendages
348crossed and all that.
349
350Worse still, the rest of the distribution isn't documented yet. I'm sorry.
351I suck. But lots of people have been asking me to ship this, docs or no, so
352having got this class itself at least somewhat documented I figured now was
353a good time to cut a first real release.
354
355=head1 DESCRIPTION
356
357HTML::Zoom is a lazy, stream oriented, streaming capable, mostly functional,
358CSS selector based semantic templating engine for HTML and HTML-like
359document formats.
360
361Which is, on the whole, a bit of a mouthful. So let me step back a moment
362and explain why you care enough to understand what I mean:
363
364=head2 JQUERY ENVY
365
366HTML::Zoom is the cure for JQuery envy. When your javascript guy pushes a
367piece of data into a document by doing:
368
369 $('.username').replaceAll(username);
370
371In HTML::Zoom one can write
372
373 $zoom->select('.username')->replace_content($username);
374
375which is, I hope, almost as clear, hampered only by the fact that Zoom can't
376assume a global document and therefore has nothing quite so simple as the
377$() function to get the initial selection.
378
379L<HTML::Zoom::SelectorParser> implements a subset of the JQuery selector
380specification, and will continue to track that rather than the W3C standards
381for the forseeable future on grounds of pragmatism. Also on grounds of their
382spec is written in EN_US rather than EN_W3C, and I read the former much better.
383
384I am happy to admit that it's very, very much a subset at the moment - see the
385L<HTML::Zoom::SelectorParser> POD for what's currently there, and expect more
386and more to be supported over time as we need it and patch it in.
387
388=head2 CLEAN TEMPLATES
389
390HTML::Zoom is the cure for messy templates. How many times have you looked at
391templates like this:
392
393 <form action="/somewhere">
394 [% FOREACH field IN fields %]
395 <label for="[% field.id %]">[% field.label %]</label>
396 <input name="[% field.name %]" type="[% field.type %]" value="[% field.value %]" />
397 [% END %]
398 </form>
399
400and despaired of the fact that neither the HTML structure nor the logic are
401remotely easy to read? Fortunately, with HTML::Zoom we can separate the two
402cleanly:
403
404 <form class="myform" action="/somewhere">
405 <label />
406 <input />
407 </form>
408
409 $zoom->select('.myform')->repeat_content([
410 map { my $field = $_; sub {
411
412 $_->select('label')
2daa653a 413 ->add_to_attribute( for => $field->{id} )
1c4455ae 414 ->then
415 ->replace_content( $field->{label} )
416
417 ->select('input')
2daa653a 418 ->add_to_attribute( name => $field->{name} )
1c4455ae 419 ->then
2daa653a 420 ->add_to_attribute( type => $field->{type} )
1c4455ae 421 ->then
2daa653a 422 ->add_to_attribute( value => $field->{value} )
1c4455ae 423
424 } } @fields
425 ]);
426
427This is, admittedly, very much not shorter. However, it makes it extremely
428clear what's happening and therefore less hassle to maintain. Especially
429because it allows the designer to fiddle with the HTML without cutting
430himself on sharp ELSE clauses, and the developer to add available data to
431the template without getting angle bracket cuts on sensitive parts.
432
433Better still, HTML::Zoom knows that it's inserting content into HTML and
434can escape it for you - the example template should really have been:
435
436 <form action="/somewhere">
437 [% FOREACH field IN fields %]
438 <label for="[% field.id | html %]">[% field.label | html %]</label>
439 <input name="[% field.name | html %]" type="[% field.type | html %]" value="[% field.value | html %]" />
440 [% END %]
441 </form>
442
443and frankly I'll take slightly more code any day over *that* crawling horror.
444
445(addendum: I pick on L<Template Toolkit|Template> here specifically because
446it's the template system I hate the least - for text templating, I don't
447honestly think I'll ever like anything except the next version of Template
448Toolkit better - but HTML isn't text. Zoom knows that. Do you?)
449
450=head2 PUTTING THE FUN INTO FUNCTIONAL
451
452The principle of HTML::Zoom is to provide a reusable, functional container
453object that lets you build up a set of transforms to be applied; every method
454call you make on a zoom object returns a new object, so it's safe to do so
455on one somebody else gave you without worrying about altering state (with
456the notable exception of ->next for stream objects, which I'll come to later).
457
458So:
459
460 my $z2 = $z1->select('.name')->replace_content($name);
461
462 my $z3 = $z2->select('.title')->replace_content('Ms.');
463
464each time produces a new Zoom object. If you want to package up a set of
465transforms to re-use, HTML::Zoom provides an 'apply' method:
466
467 my $add_name = sub { $_->select('.name')->replace_content($name) };
468
469 my $same_as_z2 = $z1->apply($add_name);
470
471=head2 LAZINESS IS A VIRTUE
472
473HTML::Zoom does its best to defer doing anything until it's absolutely
474required. The only point at which it descends into state is when you force
475it to create a stream, directly by:
476
c9e76777 477 my $stream = $zoom->to_stream;
1c4455ae 478
479 while (my $evt = $stream->next) {
480 # handle zoom event here
481 }
482
483or indirectly via:
484
485 my $final_html = $zoom->to_html;
486
487 my $fh = $zoom->to_fh;
488
489 while (my $chunk = $fh->getline) {
490 ...
491 }
492
493Better still, the $fh returned doesn't create its stream until the first
494call to getline, which means that until you call that and force it to be
495stateful you can get back to the original stateless Zoom object via:
496
497 my $zoom = $fh->to_zoom;
498
499which is exceedingly handy for filtering L<Plack> PSGI responses, among other
500things.
501
502Because HTML::Zoom doesn't try and evaluate everything up front, you can
503generally put things together in whatever order is most appropriate. This
504means that:
505
506 my $start = HTML::Zoom->from_html($html);
507
508 my $zoom = $start->select('div')->replace_content('THIS IS A DIV!');
509
510and:
511
512 my $start = HTML::Zoom->select('div')->replace_content('THIS IS A DIV!');
513
514 my $zoom = $start->from_html($html);
515
516will produce equivalent final $zoom objects, thus proving that there can be
517more than one way to do it without one of them being a
518L<bait and switch|Switch>.
519
520=head2 STOCKTON TO DARLINGTON UNDER STREAM POWER
521
522HTML::Zoom's execution always happens in terms of streams under the hood
523- that is, the basic pattern for doing anything is -
524
525 my $stream = get_stream_from_somewhere
526
527 while (my ($evt) = $stream->next) {
528 # do something with the event
529 }
530
531More importantly, all selectors and filters are also built as stream
532operations, so a selector and filter pair is effectively:
533
534 sub next {
535 my ($self) = @_;
536 my $next_evt = $self->parent_stream->next;
537 if ($self->selector_matches($next_evt)) {
538 return $self->apply_filter_to($next_evt);
539 } else {
540 return $next_evt;
541 }
542 }
543
544Internally, things are marginally more complicated than that, but not enough
545that you as a user should normally need to care.
546
547In fact, an HTML::Zoom object is mostly just a container for the relevant
548information from which to build the final stream that does the real work. A
549stream built from a Zoom object is a stream of events from parsing the
550initial HTML, wrapped in a filter stream per selector/filter pair provided
551as described above.
552
553The upshot of this is that the application of filters works just as well on
554streams as on the original Zoom object - in fact, when you run a
555L</repeat_content> operation your subroutines are applied to the stream for
556that element of the repeat, rather than constructing a new zoom per repeat
557element as well.
558
559More concretely:
560
561 $_->select('div')->replace_content('I AM A DIV!');
562
563works on both HTML::Zoom objects themselves and HTML::Zoom stream objects and
564shares sufficient of the implementation that you can generally forget the
565difference - barring the fact that a stream already has state attached so
566things like to_fh are no longer available.
567
568=head2 POP! GOES THE WEASEL
569
570... and by Weasel, I mean layout.
571
572HTML::Zoom's filehandle object supports an additional event key, 'flush',
573that is transparent to the rest of the system but indicates to the filehandle
574object to end a getline operation at that point and return the HTML so far.
575
576This means that in an environment where streaming output is available, such
577as a number of the L<Plack> PSGI handlers, you can add the flush key to an
578event in order to ensure that the HTML generated so far is flushed through
579to the browser right now. This can be especially useful if you know you're
580about to call a web service or a potentially slow database query or similar
581to ensure that at least the header/layout of your page renders now, improving
582perceived user responsiveness while your application waits around for the
583data it needs.
584
585This is currently exposed by the 'flush_before' option to the collect filter,
586which incidentally also underlies the replace and repeat filters, so to
587indicate we want this behaviour to happen before a query is executed we can
588write something like:
589
590 $zoom->select('.item')->repeat(sub {
591 if (my $row = $db_thing->next) {
592 return sub { $_->select('.item-name')->replace_content($row->name) }
593 } else {
594 return
595 }
596 }, { flush_before => 1 });
597
598which should have the desired effect given a sufficiently lazy $db_thing (for
599example a L<DBIx::Class::ResultSet> object).
600
601=head2 A FISTFUL OF OBJECTS
602
603At the core of an HTML::Zoom system lurks an L<HTML::Zoom::ZConfig> object,
604whose purpose is to hang on to the various bits and pieces that things need
605so that there's a common way of accessing shared functionality.
606
607Were I a computer scientist I would probably call this an "Inversion of
608Control" object - which you'd be welcome to google to learn more about, or
609you can just imagine a computer scientist being suspended upside down over
610a pit. Either way works for me, I'm a pure maths grad.
611
612The ZConfig object hangs on to one each of the following for you:
613
614=over 4
615
616=item * An HTML parser, normally L<HTML::Zoom::Parser::BuiltIn>
617
618=item * An HTML producer (emitter), normally L<HTML::Zoom::Producer::BuiltIn>
619
620=item * An object to build event filters, normally L<HTML::Zoom::FilterBuilder>
621
622=item * An object to parse CSS selectors, normally L<HTML::Zoom::SelectorParser>
623
624=item * An object to build streams, normally L<HTML::Zoom::StreamUtils>
625
626=back
627
628In theory you could replace any of these with anything you like, but in
629practice you're probably best restricting yourself to subclasses, or at
630least things that manage to look like the original if you squint a bit.
631
632If you do something more clever than that, or find yourself overriding things
633in your ZConfig a lot, please please tell us about it via one of the means
634mentioned under L</SUPPORT>.
635
636=head2 SEMANTIC DIDACTIC
637
638Some will argue that overloading CSS selectors to do data stuff is a terrible
639idea, and possibly even a step towards the "Concrete Javascript" pattern
640(which I abhor) or Smalltalk's Morphic (which I ignore, except for the part
641where it keeps reminding me of the late, great Tony Hart's plasticine friend).
642
643To which I say, "eh", "meh", and possibly also "feh". If it really upsets
644you, either use extra classes for this (and remove them afterwards) or
645use special fake elements or, well, honestly, just use something different.
646L<Template::Semantic> provides a similar idea to zoom except using XPath
647and XML::LibXML transforms rather than a lightweight streaming approach -
648maybe you'd like that better. Or maybe you really did want
649L<Template Toolkit|Template> after all. It is still damn good at what it does,
650after all.
651
652So far, however, I've found that for new sites the designers I'm working with
653generally want to produce nice semantic HTML with classes that represent the
654nature of the data rather than the structure of the layout, so sharing them
655as a common interface works really well for us.
656
657In the absence of any evidence that overloading CSS selectors has killed
658children or unexpectedly set fire to grandmothers - and given microformats
659have been around for a while there's been plenty of opportunity for
660octagenarian combustion - I'd suggest you give it a try and see if you like it.
661
662=head2 GET THEE TO A SUMMARY!
663
664Erm. Well.
665
666HTML::Zoom is a lazy, stream oriented, streaming capable, mostly functional,
667CSS selector based semantic templating engine for HTML and HTML-like
668document formats.
669
670But I said that already. Although hopefully by now you have some idea what I
671meant when I said it. If you didn't have any idea the first time. I mean, I'm
672not trying to call you stupid or anything. Just saying that maybe it wasn't
673totally obvious without the explanation. Or something.
674
675Er.
676
677Maybe we should just move on to the method docs.
678
679=head1 METHODS
680
681=head2 new
682
683 my $zoom = HTML::Zoom->new;
684
685 my $zoom = HTML::Zoom->new({ zconfig => $zconfig });
686
687Create a new empty Zoom object. You can optionally pass an
688L<HTML::Zoom::ZConfig> instance if you're trying to override one or more of
689the default components.
690
691This method isn't often used directly since several other methods can also
692act as constructors, notable L</select> and L</from_html>
693
694=head2 zconfig
695
696 my $zconfig = $zoom->zconfig;
697
698Retrieve the L<HTML::Zoom::ZConfig> instance used by this Zoom object. You
699shouldn't usually need to call this yourself.
700
701=head2 from_html
702
703 my $zoom = HTML::Zoom->from_html($html);
704
705 my $z2 = $z1->from_html($html);
706
707Parses the HTML using the current zconfig's parser object and returns a new
708zoom instance with that as the source HTML to be transformed.
709
710=head2 from_file
711
712 my $zoom = HTML::Zoom->from_file($file);
713
714 my $z2 = $z1->from_file($file);
715
716Convenience method - slurps the contents of $file and calls from_html with it.
717
718=head2 to_stream
719
720 my $stream = $zoom->to_stream;
721
722 while (my ($evt) = $stream->next) {
723 ...
724
725Creates a stream, starting with a stream of the events from the HTML supplied
726via L</from_html> and then wrapping it in turn with each selector+filter pair
727that have been applied to the zoom object.
728
729=head2 to_fh
730
731 my $fh = $zoom->to_fh;
732
733 call_something_expecting_a_filehandle($fh);
734
735Returns an L<HTML::Zoom::ReadFH> instance that will create a stream the first
736time its getline method is called and then return all HTML up to the next
737event with 'flush' set.
738
739You can pass this filehandle to compliant PSGI handlers (and probably most
740web frameworks).
741
742=head2 run
743
744 $zoom->run;
745
746Runs the zoom object's transforms without doing anything with the results.
747
748Normally used to get side effects of a zoom run - for example when using
749L<HTML::Zoom::FilterBuilder/collect> to slurp events for scraping or layout.
750
751=head2 apply
752
753 my $z2 = $z1->apply(sub {
754 $_->select('div')->replace_content('I AM A DIV!') })
755 });
756
757Sets $_ to the zoom object and then runs the provided code. Basically syntax
758sugar, the following is entirely equivalent:
759
760 my $sub = sub {
761 shift->select('div')->replace_content('I AM A DIV!') })
762 };
763
764 my $z2 = $sub->($z1);
765
766=head2 to_html
767
768 my $html = $zoom->to_html;
769
770Runs the zoom processing and returns the resulting HTML.
771
772=head2 memoize
773
774 my $z2 = $z1->memoize;
775
776Creates a new zoom whose source HTML is the results of the original zoom's
777processing. Effectively syntax sugar for:
778
779 my $z2 = HTML::Zoom->from_html($z1->to_html);
780
781but preserves your L<HTML::Zoom::ZConfig> object.
782
783=head2 with_filter
784
785 my $zoom = HTML::Zoom->with_filter(
786 'div', $filter_builder->replace_content('I AM A DIV!')
787 );
788
789 my $z2 = $z1->with_filter(
790 'div', $filter_builder->replace_content('I AM A DIV!')
791 );
792
793Lower level interface than L</select> to adding filters to your zoom object.
794
795In normal usage, you probably don't need to call this yourself.
796
797=head2 select
798
799 my $zoom = HTML::Zoom->select('div')->replace_content('I AM A DIV!');
800
801 my $z2 = $z1->select('div')->replace_content('I AM A DIV!');
802
97192b02 803Returns an intermediary object of the class L<HTML::Zoom::TransformBuilder>
1c4455ae 804on which methods of your L<HTML::Zoom::FilterBuilder> object can be called.
805
806In normal usage you should generally always put the pair of method calls
807together; the intermediary object isn't designed or expected to stick around.
808
809=head2 then
810
2daa653a 811 my $z2 = $z1->select('div')->add_to_attribute(class => 'spoon')
1c4455ae 812 ->then
813 ->replace_content('I AM A DIV!');
814
815Re-runs the previous select to allow you to chain actions together on the
816same selector.
817
f107bef7 818=head1 AUTHOR
45b4cea1 819
f107bef7 820mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
45b4cea1 821
f107bef7 822=head1 CONTRIBUTORS
45b4cea1 823
f107bef7 824Oliver Charles
825
826Jakub Nareski
827
828Simon Elliot
829
830Joe Highton
831
832John Napiorkowski
833
834=head1 COPYRIGHT
835
836Copyright (c) 2010-2011 the HTML::Zoom L</AUTHOR> and L</CONTRIBUTORS>
837as listed above.
45b4cea1 838
839=head1 LICENSE
840
841This library is free software, you can redistribute it and/or modify
842it under the same terms as Perl itself.
843
d80786d0 844=cut
45b4cea1 845