X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FHTML%2FZoom.pm;h=1af6a0b9413614d16dd3f7691c0aea267f8cac7b;hb=2d0662c077bfd275682acfad25d407e0724ec34f;hp=0ae18d24d58e3da8393051c72b2206b78b2eedd8;hpb=bf5a23d02e1ab37136d70e736014a81ecfcfe15c;p=catagits%2FHTML-Zoom.git diff --git a/lib/HTML/Zoom.pm b/lib/HTML/Zoom.pm index 0ae18d2..1af6a0b 100644 --- a/lib/HTML/Zoom.pm +++ b/lib/HTML/Zoom.pm @@ -1,11 +1,16 @@ package HTML::Zoom; -use strict; -use warnings FATAL => 'all'; +use strictures 1; use HTML::Zoom::ZConfig; -use HTML::Zoom::MatchWithoutFilter; use HTML::Zoom::ReadFH; +use HTML::Zoom::Transform; +use HTML::Zoom::TransformBuilder; +use Scalar::Util (); + +our $VERSION = '0.009006'; + +$VERSION = eval $VERSION; sub new { my ($class, $args) = @_; @@ -24,13 +29,18 @@ sub _with { bless({ %{$_[0]}, %{$_[1]} }, ref($_[0])); } -sub from_html { +sub from_events { my $self = shift->_self_or_new; $self->_with({ - initial_events => $self->zconfig->parser->html_to_events($_[0]) + 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; @@ -43,9 +53,7 @@ sub to_stream { unless $self->{initial_events}; my $sutils = $self->zconfig->stream_utils; my $stream = $sutils->stream_from_array(@{$self->{initial_events}}); - foreach my $filter_spec (@{$self->{filters}||[]}) { - $stream = $sutils->wrap_with_filter($stream, @{$filter_spec}); - } + $stream = $_->apply_to_stream($stream) for @{$self->{transforms}||[]}; $stream } @@ -53,9 +61,14 @@ 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->zconfig->stream_utils->stream_to_array($self->to_stream); + $self->to_events; return } @@ -65,6 +78,17 @@ sub apply { $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); @@ -75,26 +99,43 @@ sub memoize { ref($self)->new($self)->from_html($self->to_html); } -sub with_filter { - my ($self, $selector, $filter) = @_; - my $match = $self->parse_selector($selector); +sub with_transform { + my $self = shift->_self_or_new; + my ($transform) = @_; $self->_with({ - filters => [ @{$self->{filters}||[]}, [ $match, $filter ] ] + 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, $selector) = @_; - my $match = $self->parse_selector($selector); - return HTML::Zoom::MatchWithoutFilter->construct( - $self, $match, $self->zconfig->filter_builder, - ); + 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' }) +# ->remove_attribute(class => 'foo') # ->then # ->well_anything_really # @@ -104,17 +145,37 @@ sub select { sub then { my $self = shift; - die "Can't call ->then without a previous filter" - unless $self->{filters}; - $self->select($self->{filters}->[-1][0]); + die "Can't call ->then without a previous transform" + unless $self->{transforms}; + $self->select($self->{transforms}->[-1]->selector); } -sub parse_selector { - my ($self, $selector) = @_; - return $selector if ref($selector); # already a match sub - $self->zconfig->selector_parser->parse_selector($selector); +sub AUTOLOAD { + my ($self, $selector, @args) = @_; + my $sel = $self->select($selector); + my $meth = our $AUTOLOAD; + $meth =~ s/.*:://; + if (ref($selector) eq 'HASH') { + my $ret = $self; + $ret = $ret->_do($_, $meth, @{$selector->{$_}}) for keys %$selector; + $ret; + } else { + $self->_do($selector, $meth, @args); + } } +sub _do { + my ($self, $selector, $meth, @args) = @_; + my $sel = $self->select($selector); + if( my $cr = $sel->_zconfig->filter_builder->can($meth)) { + return $sel->$meth(@args); + } else { + die "We can't do $meth on ->select('$selector')"; + } +} + +sub DESTROY {} + 1; =head1 NAME @@ -152,13 +213,14 @@ HTML::Zoom - selector based streaming template engine $_->select('.name')->replace_content('Matt') ->select('.age')->replace_content('26') }, + # alternate form sub { - $_->select('.name')->replace_content('Mark') - ->select('.age')->replace_content('0x29') + $_->replace_content({'.name' => ['Mark'],'.age' => ['0x29'] }) }, + #alternate alternate form sub { - $_->select('.name')->replace_content('Epitaph') - ->select('.age')->replace_content('') + $_->replace_content('.name' => 'Epitaph') + ->replace_content('.age' => '') }, ], { repeat_between => '.between' } @@ -206,6 +268,538 @@ will produce: =end testinfo -=head1 SOMETHING ELSE +=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: + +
+