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