fixed add/before pre/app pend content stuff, added docs and test, also docs and test...
[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;
b9c27a5a 9use Scalar::Util ();
d80786d0 10
f107bef7 11our $VERSION = '0.009004';
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
c877066a 153sub AUTOLOAD {
b9c27a5a 154 my ($self, $selector, @args) = @_;
c877066a 155 my $meth = our $AUTOLOAD;
156 $meth =~ s/.*:://;
b9c27a5a 157 return $self = $self->select($selector)->$meth(@args);
c877066a 158}
c2d0669e 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)
b9c27a5a 168 @args = ($selector => \@arrayref||$coderef) ## $zoom->select($selector)->repeat_content(\@arrayref)
169 @args = [qw/a b c/] => {}, $object
c2d0669e 170
171=cut
172
173sub fill {
174 my $self = shift;
b9c27a5a 175 while(@_) {
176 my $selector = shift;
177 my $args = shift;
178 my $type = Scalar::Util::reftype($args) ? Scalar::Util::reftype($args) : 'SCALAR';
c2d0669e 179 if($type eq 'ARRAY' || $type eq 'CODE') {
180 $self = $self->select($selector)->repeat_content($args);
181 } elsif($type eq 'SCALAR') {
c2d0669e 182 $self = $self->select($selector)->replace_content($args);
b9c27a5a 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 }
c2d0669e 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')
3f03e15a 231 ->apply
c2d0669e 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
d80786d0 2521;
253
254=head1 NAME
255
256HTML::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
302will 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
1c4455ae 343=head1 DANGER WILL ROBINSON
344
345This is a 0.9 release. That means that I'm fairly happy the API isn't going
346to change in surprising and upsetting ways before 1.0 and a real compatibility
347freeze. But it also means that if it turns out there's a mistake the size of
348a politician's ego in the API design that I haven't spotted yet there may be
349a bit of breakage between here and 1.0. Hopefully not though. Appendages
350crossed and all that.
351
352Worse still, the rest of the distribution isn't documented yet. I'm sorry.
353I suck. But lots of people have been asking me to ship this, docs or no, so
354having got this class itself at least somewhat documented I figured now was
355a good time to cut a first real release.
356
357=head1 DESCRIPTION
358
359HTML::Zoom is a lazy, stream oriented, streaming capable, mostly functional,
360CSS selector based semantic templating engine for HTML and HTML-like
361document formats.
362
363Which is, on the whole, a bit of a mouthful. So let me step back a moment
364and explain why you care enough to understand what I mean:
365
366=head2 JQUERY ENVY
367
368HTML::Zoom is the cure for JQuery envy. When your javascript guy pushes a
369piece of data into a document by doing:
370
371 $('.username').replaceAll(username);
372
373In HTML::Zoom one can write
374
375 $zoom->select('.username')->replace_content($username);
376
377which is, I hope, almost as clear, hampered only by the fact that Zoom can't
378assume a global document and therefore has nothing quite so simple as the
379$() function to get the initial selection.
380
381L<HTML::Zoom::SelectorParser> implements a subset of the JQuery selector
382specification, and will continue to track that rather than the W3C standards
383for the forseeable future on grounds of pragmatism. Also on grounds of their
384spec is written in EN_US rather than EN_W3C, and I read the former much better.
385
386I am happy to admit that it's very, very much a subset at the moment - see the
387L<HTML::Zoom::SelectorParser> POD for what's currently there, and expect more
388and more to be supported over time as we need it and patch it in.
389
390=head2 CLEAN TEMPLATES
391
392HTML::Zoom is the cure for messy templates. How many times have you looked at
393templates 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
402and despaired of the fact that neither the HTML structure nor the logic are
403remotely easy to read? Fortunately, with HTML::Zoom we can separate the two
404cleanly:
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')
2daa653a 415 ->add_to_attribute( for => $field->{id} )
1c4455ae 416 ->then
417 ->replace_content( $field->{label} )
418
419 ->select('input')
2daa653a 420 ->add_to_attribute( name => $field->{name} )
1c4455ae 421 ->then
2daa653a 422 ->add_to_attribute( type => $field->{type} )
1c4455ae 423 ->then
2daa653a 424 ->add_to_attribute( value => $field->{value} )
1c4455ae 425
426 } } @fields
427 ]);
428
429This is, admittedly, very much not shorter. However, it makes it extremely
430clear what's happening and therefore less hassle to maintain. Especially
431because it allows the designer to fiddle with the HTML without cutting
432himself on sharp ELSE clauses, and the developer to add available data to
433the template without getting angle bracket cuts on sensitive parts.
434
435Better still, HTML::Zoom knows that it's inserting content into HTML and
436can 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
445and 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
448it's the template system I hate the least - for text templating, I don't
449honestly think I'll ever like anything except the next version of Template
450Toolkit better - but HTML isn't text. Zoom knows that. Do you?)
451
452=head2 PUTTING THE FUN INTO FUNCTIONAL
453
454The principle of HTML::Zoom is to provide a reusable, functional container
455object that lets you build up a set of transforms to be applied; every method
456call you make on a zoom object returns a new object, so it's safe to do so
457on one somebody else gave you without worrying about altering state (with
458the notable exception of ->next for stream objects, which I'll come to later).
459
460So:
461
462 my $z2 = $z1->select('.name')->replace_content($name);
463
464 my $z3 = $z2->select('.title')->replace_content('Ms.');
465
466each time produces a new Zoom object. If you want to package up a set of
467transforms 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
475HTML::Zoom does its best to defer doing anything until it's absolutely
476required. The only point at which it descends into state is when you force
477it to create a stream, directly by:
478
c9e76777 479 my $stream = $zoom->to_stream;
1c4455ae 480
481 while (my $evt = $stream->next) {
482 # handle zoom event here
483 }
484
485or 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
495Better still, the $fh returned doesn't create its stream until the first
496call to getline, which means that until you call that and force it to be
497stateful you can get back to the original stateless Zoom object via:
498
499 my $zoom = $fh->to_zoom;
500
501which is exceedingly handy for filtering L<Plack> PSGI responses, among other
502things.
503
504Because HTML::Zoom doesn't try and evaluate everything up front, you can
505generally put things together in whatever order is most appropriate. This
506means that:
507
508 my $start = HTML::Zoom->from_html($html);
509
510 my $zoom = $start->select('div')->replace_content('THIS IS A DIV!');
511
512and:
513
514 my $start = HTML::Zoom->select('div')->replace_content('THIS IS A DIV!');
515
516 my $zoom = $start->from_html($html);
517
518will produce equivalent final $zoom objects, thus proving that there can be
519more than one way to do it without one of them being a
520L<bait and switch|Switch>.
521
522=head2 STOCKTON TO DARLINGTON UNDER STREAM POWER
523
524HTML::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
533More importantly, all selectors and filters are also built as stream
534operations, 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
546Internally, things are marginally more complicated than that, but not enough
547that you as a user should normally need to care.
548
549In fact, an HTML::Zoom object is mostly just a container for the relevant
550information from which to build the final stream that does the real work. A
551stream built from a Zoom object is a stream of events from parsing the
552initial HTML, wrapped in a filter stream per selector/filter pair provided
553as described above.
554
555The upshot of this is that the application of filters works just as well on
556streams as on the original Zoom object - in fact, when you run a
557L</repeat_content> operation your subroutines are applied to the stream for
558that element of the repeat, rather than constructing a new zoom per repeat
559element as well.
560
561More concretely:
562
563 $_->select('div')->replace_content('I AM A DIV!');
564
565works on both HTML::Zoom objects themselves and HTML::Zoom stream objects and
566shares sufficient of the implementation that you can generally forget the
567difference - barring the fact that a stream already has state attached so
568things like to_fh are no longer available.
569
570=head2 POP! GOES THE WEASEL
571
572... and by Weasel, I mean layout.
573
574HTML::Zoom's filehandle object supports an additional event key, 'flush',
575that is transparent to the rest of the system but indicates to the filehandle
576object to end a getline operation at that point and return the HTML so far.
577
578This means that in an environment where streaming output is available, such
579as a number of the L<Plack> PSGI handlers, you can add the flush key to an
580event in order to ensure that the HTML generated so far is flushed through
581to the browser right now. This can be especially useful if you know you're
582about to call a web service or a potentially slow database query or similar
583to ensure that at least the header/layout of your page renders now, improving
584perceived user responsiveness while your application waits around for the
585data it needs.
586
587This is currently exposed by the 'flush_before' option to the collect filter,
588which incidentally also underlies the replace and repeat filters, so to
589indicate we want this behaviour to happen before a query is executed we can
590write 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
600which should have the desired effect given a sufficiently lazy $db_thing (for
601example a L<DBIx::Class::ResultSet> object).
602
603=head2 A FISTFUL OF OBJECTS
604
605At the core of an HTML::Zoom system lurks an L<HTML::Zoom::ZConfig> object,
606whose purpose is to hang on to the various bits and pieces that things need
607so that there's a common way of accessing shared functionality.
608
609Were I a computer scientist I would probably call this an "Inversion of
610Control" object - which you'd be welcome to google to learn more about, or
611you can just imagine a computer scientist being suspended upside down over
612a pit. Either way works for me, I'm a pure maths grad.
613
614The 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
630In theory you could replace any of these with anything you like, but in
631practice you're probably best restricting yourself to subclasses, or at
632least things that manage to look like the original if you squint a bit.
633
634If you do something more clever than that, or find yourself overriding things
635in your ZConfig a lot, please please tell us about it via one of the means
636mentioned under L</SUPPORT>.
637
638=head2 SEMANTIC DIDACTIC
639
640Some will argue that overloading CSS selectors to do data stuff is a terrible
641idea, 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
643where it keeps reminding me of the late, great Tony Hart's plasticine friend).
644
645To which I say, "eh", "meh", and possibly also "feh". If it really upsets
646you, either use extra classes for this (and remove them afterwards) or
647use special fake elements or, well, honestly, just use something different.
648L<Template::Semantic> provides a similar idea to zoom except using XPath
649and XML::LibXML transforms rather than a lightweight streaming approach -
650maybe you'd like that better. Or maybe you really did want
651L<Template Toolkit|Template> after all. It is still damn good at what it does,
652after all.
653
654So far, however, I've found that for new sites the designers I'm working with
655generally want to produce nice semantic HTML with classes that represent the
656nature of the data rather than the structure of the layout, so sharing them
657as a common interface works really well for us.
658
659In the absence of any evidence that overloading CSS selectors has killed
660children or unexpectedly set fire to grandmothers - and given microformats
661have been around for a while there's been plenty of opportunity for
662octagenarian combustion - I'd suggest you give it a try and see if you like it.
663
664=head2 GET THEE TO A SUMMARY!
665
666Erm. Well.
667
668HTML::Zoom is a lazy, stream oriented, streaming capable, mostly functional,
669CSS selector based semantic templating engine for HTML and HTML-like
670document formats.
671
672But I said that already. Although hopefully by now you have some idea what I
673meant when I said it. If you didn't have any idea the first time. I mean, I'm
674not trying to call you stupid or anything. Just saying that maybe it wasn't
675totally obvious without the explanation. Or something.
676
677Er.
678
679Maybe 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
689Create a new empty Zoom object. You can optionally pass an
690L<HTML::Zoom::ZConfig> instance if you're trying to override one or more of
691the default components.
692
693This method isn't often used directly since several other methods can also
694act as constructors, notable L</select> and L</from_html>
695
696=head2 zconfig
697
698 my $zconfig = $zoom->zconfig;
699
700Retrieve the L<HTML::Zoom::ZConfig> instance used by this Zoom object. You
701shouldn'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
709Parses the HTML using the current zconfig's parser object and returns a new
710zoom 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
718Convenience 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
727Creates a stream, starting with a stream of the events from the HTML supplied
728via L</from_html> and then wrapping it in turn with each selector+filter pair
729that 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
737Returns an L<HTML::Zoom::ReadFH> instance that will create a stream the first
738time its getline method is called and then return all HTML up to the next
739event with 'flush' set.
740
741You can pass this filehandle to compliant PSGI handlers (and probably most
742web frameworks).
743
744=head2 run
745
746 $zoom->run;
747
748Runs the zoom object's transforms without doing anything with the results.
749
750Normally used to get side effects of a zoom run - for example when using
751L<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
759Sets $_ to the zoom object and then runs the provided code. Basically syntax
760sugar, 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
772Runs the zoom processing and returns the resulting HTML.
773
774=head2 memoize
775
776 my $z2 = $z1->memoize;
777
778Creates a new zoom whose source HTML is the results of the original zoom's
779processing. Effectively syntax sugar for:
780
781 my $z2 = HTML::Zoom->from_html($z1->to_html);
782
783but 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
795Lower level interface than L</select> to adding filters to your zoom object.
796
797In 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
97192b02 805Returns an intermediary object of the class L<HTML::Zoom::TransformBuilder>
1c4455ae 806on which methods of your L<HTML::Zoom::FilterBuilder> object can be called.
807
808In normal usage you should generally always put the pair of method calls
809together; the intermediary object isn't designed or expected to stick around.
810
811=head2 then
812
2daa653a 813 my $z2 = $z1->select('div')->add_to_attribute(class => 'spoon')
1c4455ae 814 ->then
815 ->replace_content('I AM A DIV!');
816
817Re-runs the previous select to allow you to chain actions together on the
818same selector.
819
b9c27a5a 820=head1 AUTOLOAD METHODS
821
822L<HTML::Zoom> AUTOLOADS methods against L</select> so that you can reduce a
823certain amount of boilerplate typing. This allows you to replace:
824
825 $z->select('div')->replace_content("Hello World");
826
827With:
828
829 $z->replace_content(div => "Hello World");
830
831Besides saving a few keys per invocations, you may feel this looks neater
832in your code and increases understanding.
833
f107bef7 834=head1 AUTHOR
45b4cea1 835
f107bef7 836mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
45b4cea1 837
f107bef7 838=head1 CONTRIBUTORS
45b4cea1 839
f107bef7 840Oliver Charles
841
842Jakub Nareski
843
844Simon Elliot
845
846Joe Highton
847
848John Napiorkowski
849
850=head1 COPYRIGHT
851
852Copyright (c) 2010-2011 the HTML::Zoom L</AUTHOR> and L</CONTRIBUTORS>
853as listed above.
45b4cea1 854
855=head1 LICENSE
856
857This library is free software, you can redistribute it and/or modify
858it under the same terms as Perl itself.
859
d80786d0 860=cut
45b4cea1 861