views:

623

answers:

8

Common Lisp guys have their CL-WHO, which makes HTML templating integrated with the "main" language thus making the task easier. For those who don't know CL-WHO, it looks like this (example from CL-WHO's webpage):

(with-html-output (*http-stream*)
(:table :border 0 :cellpadding 4
  (loop for i below 25 by 5
     do (htm
         (:tr :align "right"
          (loop for j from i below (+ i 5)
                do (htm
                    (:td :bgcolor (if (oddp j)
                                    "pink"
                                    "green")
                         (fmt "~@R" (1+ j))))))))))

Do you know any libraries like this for other languages? The one I know about (that mimics CL-WHO) is Brevé for Python. I'm particularly interested in Perl flavour, but it's interesting how other languages handle integrating HTML into their syntax.

+5  A: 

Perl's CGI module has support for something like this.

use CGI ':standard';
use Lisp::Fmt 

print header();

print table( { -border => 1, -cellpading => 4},
    loop({ below => 25, by=> 5}, sub {
        my $i = shift;
        tr( {-align => 'right'} ,
            loop({ from => $i, below $i + 5}, sub {
                my $j = shift;
                td({-bgcolor => ($oddp eq $j ? 'pink' : 'green')}
                    fmt("~@R", 1+$j);
            })
        )
    });

I tried to keep it lispy, so you'll have to implement a lispy loop function yourself. I don't really program Common List, so I hope I understood your code correctly.

Leon Timmermans
+3  A: 

Perl's standard CGI module can do something similar:

#!/usr/bin/perl

use strict;
use warnings;
use CGI qw/:standard/;

print 
    start_html("An example"),
    h1(
     {
      -align => "left",
      -class => "headerinfo",
     },
     'this is an example'
    ),
    "The CGI module has functions that add HTML:",
    ul( map li($_),
     ("start_html",
     "h1, h2, h3, etc.",
     "ol, ul, li",
     "ol, ul, li",
     "table, tr, th, td")
    ),
    "and many more.",
    end_html();

That produces:

<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>An example</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<h1 class="headerinfo" align="left">this is an example</h1>The CGI module has functions that add HTML:<ul><li>start_html</li> <li>h1, h2, h3, etc.</li> <li>ol, ul, li</li> <li>ol, ul, li</li> <li>table, tr, th, td</li></ul>and many more.
</body>
</html>

The li section could be rewritten like this

print ol(map li($_), @list);

if you had a list or an array.

Chas. Owens
maybe add some attributes to your example?
ysth
ysth, are you happy now?
Chas. Owens
+3  A: 

There is stan: An s-expression-like syntax for expressing xml in pure python, from Divmod's Nevow. I think it's kind of what you want. An example from the tutorial linked:

t = T.table[
           T.tr[
               T.td[ "Name:" ],
               T.td[ original.name ]
           ],
           T.tr[
               T.td[ "Email:" ],
               T.td[T.a(href='mailto:%s' % original.email)[ original.email ] ]
           ],
           T.tr[
               T.td[ "Password:" ],
               T.td[ "******" ]
           ],
       ]
Ali A
+1 you beat me to it.
Vasil
+6  A: 

One of The Perl Foundation's current grant-sponsored projects (a lightweight web framework for Perl 6) has working Perl6 code that provides a similar interface:

use Tags;
say show {
    html {
        head { title { 'Tags Demo' } }
        body {
            outs "hi";
            ul :id<numberlist> {
                outs "A list from one to ten:";
                for 1..10 {
                    li :class<number>, { $_ }
                }
            }
        }
    }
}

Browse or clone the current code on github.

Brian Phillips
This is actually a port of Template::Declare, which you can use in perl5, today :) (http://search.cpan.org/perldoc?Template::Declare)
8jean
+10  A: 

For CPAN offerings have a look at the following (in alphabetical order)...

Using the table part of the CL-WHO example provided (minus Roman numerals and s/background-color/color/ to squeeze code into screen width here!)....


Builder

use Builder;
my $builder = Builder->new;
my $h = $builder->block( 'Builder::XML' );

$h->table( { border => 0, cellpadding => 4 }, sub {
   for ( my $i = 1; $i < 25; $i += 5 ) {
       $h->tr( { align => 'right' }, sub {
           for my $j (0..4) {
               $h->td( { color => $j % 2 ? 'pink' : 'green' }, $i + $j );
           }
       });
   } 
});

say $builder->render;


HTML::AsSubs

use HTML::AsSubs;

my $td = sub {
    my $i = shift;
    return map { 
        td( { color => $_ % 2 ? 'pink' : 'green' }, $i + $_ )
    } 0..4;
};

say table( { border => 0, cellpadding => 4 },
    map { 
        &tr( { align => 'right' }, $td->( $_ ) ) 
    } loop( below => 25, by => 5 )
)->as_HTML;


HTML::Tiny

use HTML::Tiny;
my $h = HTML::Tiny->new;

my $td = sub {
    my $i = shift;
    return map { 
        $h->td( { 'color' => $_ % 2 ? 'pink' : 'green' }, $i + $_ )
    } 0..4;
};

say $h->table(
    { border => 0, cellpadding => 4 },
    [
        map { 
            $h->tr( { align => 'right' }, [ $td->( $_ ) ] )  
        } loop( below => 25, by => 5 )    
    ]
);


Markapl

use Markapl;

template 'MyTable' => sub {
    table ( border => 0, cellpadding => 4 ) {
       for ( my $i = 1; $i < 25; $i += 5 ) {
           row ( align => 'right' ) {
               for my $j ( 0.. 4 ) {
                   td ( color => $j % 2 ? 'pink' : 'green' ) { $i + $j }
               }
           }
       } 
    }
};

print main->render( 'MyTable' );


Template::Declare

package MyTemplates;
use Template::Declare::Tags;
use base 'Template::Declare';

template 'MyTable' => sub {
    table {
        attr { border => 0, cellpadding => 4 };
        for ( my $i = 1; $i < 25; $i += 5 ) {
            row  {
                attr { align => 'right' };
                    for my $j ( 0..4 ) {
                        cell {
                            attr { color => $j % 2 ? 'pink' : 'green' } 
                            outs $i + $j;
                        }
                    }
            }
        } 
    }
};

package main;
use Template::Declare;
Template::Declare->init( roots => ['MyTemplates'] );
print Template::Declare->show( 'MyTable' );


XML::Generator

use XML::Generator;
my $x = XML::Generator->new( pretty => 2 );

my $td = sub {
    my $i = shift;
    return map { 
        $x->td( { 'color' => $_ % 2 ? 'pink' : 'green' }, $i + $_ )
    } 0..4;
};

say $x->table(
    { border => 0, cellpadding => 4 },
    map { 
        $x->tr( { align => 'right' }, $td->( $_ ) )  
    } loop( below => 25, by => 5 )    
);


And the following can be used to produce the "loop" in HTML::AsSubs / HTML::Tiny / XML::Generator examples....

sub loop {
    my ( %p ) = @_;
    my @list;

    for ( my $i = $p{start} || 1; $i < $p{below}; $i += $p{by} ) {
        push @list, $i;
    }

    return @list;
}

/I3az/

draegtun
+1  A: 

Here is such thing for JavaScript. It looks like the following:

T.div({ className: "content"},
      T.p("Some ", T.u("paragraph")),
      T.p("Another paragraph"))
fionbio
+2  A: 

Clojure

There are a bunch of CL-WHO-inspired HTML-generating libraries available in Clojure (as one would expect, Clojure being a Lisp). Here's how you could do it using the HTML library that comes with Compojure, and cl-format:

(use 'compojure.html
     'com.infolace.format)

(html
 [:table {:border 0 :cellpadding 4}
  (map (fn [tds] [:tr {:align "right"} tds])
       (partition 5 (map (fn [num color]
                           [:td {:bgcolor color}
                            (cl-format nil "~@R" (inc num))])
                         (range 25)
                         (cycle ["green" "pink"]))))])

Compojure's HTML library makes good use of Clojure's literal hash-maps as attribute/value pairs, and using literal vectors for tags instead of lists for everything helps the tags stand out a bit and avoids some of the need for macro magic.

partition breaks up a collection into groups of some number of elements. cycle generates an infinitely repeating list of the elements of a collection. These plus range and map help you avoid explicit loops and counter variables.

Brian Carper
+1  A: 

Haskell

Haskell has an HTML combinator library that is not all that different from CL-WHO. The lazy functional approach to programming, though, does result in a much different idiomatic iteration structure than the loop facilities in Common Lisp:

import Data.Char
import Data.List
import Text.Html
-- from http://fawcett.blogspot.com/2007/08/roman-numerals-in-haskell.html
import RomanNumerals

-- Simple roman numeral conversion; returns "" if it cannot convert.
rom :: Int -> String
rom r = let m = toRoman r
        in (map toUpper . maybe "" id) m

-- Group a list N elements at a time.
-- groupN 2 [1,2,3,4,5] == [[1,2],[3,4],[5]]
groupN n [] = []
groupN n xs = let (a, b) = splitAt n xs in a : (groupN n b)

pink = "pink" -- for convenience below; green is already covered by Text.Html

rom_table = table ! [border 0, cellpadding 4] << trs
    where
      -- a list of <tr> entries
      trs = map (rom_tr . map rom_td) rom_array

      -- generates a <tr> from a list of <td>s
      rom_tr tds = tr ! [align "right"] << tds

      -- generates a <td> given a numeral and a color
      rom_td (r, c) = td ! [bgcolor c] << r

      -- our 5 x 5 array (list x list) of numerals and colors
      rom_array = (groupN 5 . take 25) rom_colors

      -- a theoretically infinite list of pairs of roman numerals and colors
      -- (practically, though, the roman numeral library has limits!)
      rom_colors = zip (map rom [1..]) colors

      -- an infinite list of alternating green and pink colors
      colors = cycle [green, pink]

main = let s = prettyHtml rom_table 
       in putStrLn s

I should note there's also a little combinator library in Text.Html for composing tables using "above" and "beside" operators to calculate row/column spanning, but it's a little too simplistic in terms of applying attributes to duplicate this example exactly, and we don't need the fancy splitting of rows and columns.

Owen S.