Generating Template::Declare Code from a HTML Baseline

Dec 20, 2010 / By Yanick Champoux

Tags: ,

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.

Leave a Reply

  • (will not be published)

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>