File Coverage

blib/lib/HTML/Zoom.pm
Criterion Covered Total %
statement 66 83 79.5
branch 11 14 78.5
condition 1 2 50.0
subroutine 21 27 77.7
pod 15 17 88.2
total 114 143 79.7


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