For the templating system of my web applications, I usually go for HTML::Mason. It’s a wonderful system but, as it mixes Perl code with HTML, no matter how hard I try to be well-behaved, my templates always seem to grow into giant miss-indented smorgasborgs.
Because of that, recently I’ve also been playing around with Template::Declare. As the templates of Template::Declare
are pure Perl, not only do I have a fighting chance to keep my indentation consistent, but if I fail, Perl::Tidy is there to save my bacon.
Now, one thing with Template::Declare
is that if I already have some HTML, writing the template from scratch is a little bit daunting. On the other hand, wouldn’t that conversion be a perfect occasion to showcase the latest development on my own XML templating system, XML::XSS? Well, yes, I believe it would. :-)
In the latest development of XML::XSS
, we can not only create stylesheets as classes, but I’ve introduced a style
keyword that makes the syntax much cleaner. Follow me, I’ll show you.
First, nothing too fancy. We just create a stylesheet called XML::XSS::Stylesheet::HTML2TD
that inherits from XML::XSS
.
package XML::XSS::Stylesheet::HTML2TD; use Moose; use XML::XSS; use Perl::Tidy; extends 'XML::XSS';
And then, we begin with the fun stuff. For all HTML elements, we want to morph
<div class="[..]"> [..] </div>
into something like
div { attr { class => "[..]" }; outs "[..]"; }
Since we want all HTML elements to be transformed, we use the catchall element of the stylesheet:
style '*' => ( pre => \&pre_element, post => '};', ); sub pre_element { my ( $self, $node, $args ) = @_; my $name = $node->nodeName; return "$name {" . pre_attrs( $node ); } sub pre_attrs { my $node = shift; my @attr = $node->attributes or return ''; my $output = 'attr { '; for ( @attr ) { my $value = $_->value; $value =~ s/'/'/g; $output .= $_->nodeName . ' => ' . "'$value'" . ', '; } $output .= '};'; return $output; }
For the text, we want to wrap it with calls to outs
. Except for text nodes that are nothing but empty spaces, which we’ll gladly skip:
style '#text' => ( process => sub { $_[1]->data =~ /\S/ }, pre => "outs '", post => "';", filter => sub { s/'/\\'/g; s/^\s+|\s+$//gm; $_ }, );
For the pièce de résistance, since we’ll eventually ask Perl::Tidy
to clean up our code, why not do it directly as we are transforming the document?
style '#document' => ( content => sub { my ( $self, $node, $args ) = @_; my $raw = $self->stylesheet->render( $node->childNodes ); my $output; my $err; eval { Perl::Tidy::perltidy( source => \$raw, destination => \$output, errorfile => \$err, ) }; # send the raw output if Tidy failed return $err ? $raw : $output; }, ); 1;
And we are done. Now we can take a semi-badly formated HTML snippet like this one,
<html> <div class="entry_info"> <div style="float: right"> created: Sat, Dec 18 2010</div> </div> <div><p>Web applications typically have [..] </p> <pre class="code" > [..] </pre> </div> </html>
and pass it through XML::XSS
very new xss
command-line utility to get
the corresponding Template::Declare
code:
$ xss --stylesheet HTML2TD snippet.xml html { div { attr { class => 'entry_info', }; div { attr { style => 'float: right', }; outs 'created: Sat, Dec 18 2010'; }; }; div { p { outs 'Web applications typically have [..]'; }; pre { attr { class => 'brush: plain', }; outs '[..]'; }; }; };
There is still a lot to do, but I’m rrrreally liking the direction XML::XSS
is taking.
No comments