Generating Template::Declare Code from a HTML Baseline
Dec 20, 2010 / By Yanick Champoux
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.
Leave a Reply
You must be logged in to post a comment.

Recent Comments