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