Generating Template::Declare Code from a HTML Baseline

Posted in: Technical Track

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/'/&apos;/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.

email
Want to talk with an expert? Schedule a call with our team to get the conversation started.

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *