package HTML::Zoom; use strictures 1; use HTML::Zoom::ZConfig; use HTML::Zoom::ReadFH; use HTML::Zoom::Transform; use HTML::Zoom::TransformBuilder; our $VERSION = '0.009004'; $VERSION = eval $VERSION; sub new { my ($class, $args) = @_; my $new = {}; $new->{zconfig} = HTML::Zoom::ZConfig->new($args->{zconfig}||{}); bless($new, $class); } sub zconfig { shift->_self_or_new->{zconfig} } sub _self_or_new { ref($_[0]) ? $_[0] : $_[0]->new } sub _with { bless({ %{$_[0]}, %{$_[1]} }, ref($_[0])); } sub from_events { my $self = shift->_self_or_new; $self->_with({ initial_events => shift, }); } sub from_html { my $self = shift->_self_or_new; $self->from_events($self->zconfig->parser->html_to_events($_[0])) } sub from_file { my $self = shift->_self_or_new; my $filename = shift; $self->from_html(do { local (@ARGV, $/) = ($filename); <> }); } sub to_stream { my $self = shift; die "No events to build from - forgot to call from_html?" unless $self->{initial_events}; my $sutils = $self->zconfig->stream_utils; my $stream = $sutils->stream_from_array(@{$self->{initial_events}}); $stream = $_->apply_to_stream($stream) for @{$self->{transforms}||[]}; $stream } sub to_fh { HTML::Zoom::ReadFH->from_zoom(shift); } sub to_events { my $self = shift; [ $self->zconfig->stream_utils->stream_to_array($self->to_stream) ]; } sub run { my $self = shift; $self->to_events; return } sub apply { my ($self, $code) = @_; local $_ = $self; $self->$code; } sub apply_if { my ($self, $predicate, $code) = @_; if($predicate) { local $_ = $self; $self->$code; } else { $self; } } sub to_html { my $self = shift; $self->zconfig->producer->html_from_stream($self->to_stream); } sub memoize { my $self = shift; ref($self)->new($self)->from_html($self->to_html); } sub with_transform { my $self = shift->_self_or_new; my ($transform) = @_; $self->_with({ transforms => [ @{$self->{transforms}||[]}, $transform ] }); } sub with_filter { my $self = shift->_self_or_new; my ($selector, $filter) = @_; $self->with_transform( HTML::Zoom::Transform->new({ zconfig => $self->zconfig, selector => $selector, filters => [ $filter ] }) ); } sub select { my $self = shift->_self_or_new; my ($selector) = @_; return HTML::Zoom::TransformBuilder->new({ zconfig => $self->zconfig, selector => $selector, proto => $self }); } # There's a bug waiting to happen here: if you do something like # # $zoom->select('.foo') # ->remove_attribute(class => 'foo') # ->then # ->well_anything_really # # the second action won't execute because it doesn't match anymore. # Ideally instead we'd merge the match subs but that's more complex to # implement so I'm deferring it for the moment. sub then { my $self = shift; die "Can't call ->then without a previous transform" unless $self->{transforms}; $self->select($self->{transforms}->[-1]->selector); } 1; =head1 NAME HTML::Zoom - selector based streaming template engine =head1 SYNOPSIS use HTML::Zoom; my $template = < Hello people


Name: Bob

Age: 23

HTML my $output = HTML::Zoom ->from_html($template) ->select('title, #greeting')->replace_content('Hello world & dog!') ->select('#list')->repeat_content( [ sub { $_->select('.name')->replace_content('Matt') ->select('.age')->replace_content('26') }, sub { $_->select('.name')->replace_content('Mark') ->select('.age')->replace_content('0x29') }, sub { $_->select('.name')->replace_content('Epitaph') ->select('.age')->replace_content('') }, ], { repeat_between => '.between' } ) ->to_html; will produce: =begin testinfo my $expect = < Hello world & dog!

Hello world & dog!

Name: Matt

Age: 26

Name: Mark

Age: 0x29

Name: Epitaph

Age: <redacted>

=begin testinfo HTML is($output, $expect, 'Synopsis code works ok'); =end testinfo =head1 DANGER WILL ROBINSON This is a 0.9 release. That means that I'm fairly happy the API isn't going to change in surprising and upsetting ways before 1.0 and a real compatibility freeze. But it also means that if it turns out there's a mistake the size of a politician's ego in the API design that I haven't spotted yet there may be a bit of breakage between here and 1.0. Hopefully not though. Appendages crossed and all that. Worse still, the rest of the distribution isn't documented yet. I'm sorry. I suck. But lots of people have been asking me to ship this, docs or no, so having got this class itself at least somewhat documented I figured now was a good time to cut a first real release. =head1 DESCRIPTION HTML::Zoom is a lazy, stream oriented, streaming capable, mostly functional, CSS selector based semantic templating engine for HTML and HTML-like document formats. Which is, on the whole, a bit of a mouthful. So let me step back a moment and explain why you care enough to understand what I mean: =head2 JQUERY ENVY HTML::Zoom is the cure for JQuery envy. When your javascript guy pushes a piece of data into a document by doing: $('.username').replaceAll(username); In HTML::Zoom one can write $zoom->select('.username')->replace_content($username); which is, I hope, almost as clear, hampered only by the fact that Zoom can't assume a global document and therefore has nothing quite so simple as the $() function to get the initial selection. L implements a subset of the JQuery selector specification, and will continue to track that rather than the W3C standards for the forseeable future on grounds of pragmatism. Also on grounds of their spec is written in EN_US rather than EN_W3C, and I read the former much better. I am happy to admit that it's very, very much a subset at the moment - see the L POD for what's currently there, and expect more and more to be supported over time as we need it and patch it in. =head2 CLEAN TEMPLATES HTML::Zoom is the cure for messy templates. How many times have you looked at templates like this:
[% FOREACH field IN fields %] [% END %]
and despaired of the fact that neither the HTML structure nor the logic are remotely easy to read? Fortunately, with HTML::Zoom we can separate the two cleanly: