Simply copied this from my website http://www.camco.ie/code/ikiwiki,3.20120202,20120313a/ feel free to reformat / delete

The following re-write allows for multiple definitions of the same tag value in a template definition. This, in turn, allows us to use TMPL_LOOPS in our template directives; all-be-it in a rather limited way.

I'm willing to consider such a feature, but it needs to be presented in the form of a patch that is reviewable, not a gratuitous rewrite. --Joey

Yes, my apologies for that. The two worker functions mktmpl_hash and proc_tmpl_hash are new. The preprocess function then starts by arranging the parameters into an array. This array is passed to the mktmpl_hash and it creates a hash, suitable for passing into the HTML::Template directly. The proc_tmpl_hash then walks the hash structure and processes the parameters.

I know ... you weren't looking for an explanation, just a patch ... totally understand. Point I'm trying to make, it's a 90% re-write anyway (and my style(8) will probably piss most people off).

Anyway, would love to contribute so will try to get to doing this "correctly" and post as a patch.

I would, personally, only use this feature for very basic loops and, although nested loops might be possible (with a little more tinkering) it think any attempt would be better served by Kathyrn Anderson's field et al. plugin.

It is (primarily) intended to allow insertion of organised CSS blocks (i.e. <div>) through template directives (since i can't seem to get HTML and Markup to mix the way I want).

Apologies for the re-write. I struggle reading perl code that I didn't write and (probably too often) re-format to reduce my head-aches. Anyway it didn't make sense to post the patch since everything's changed now.

NB: this should be 100% backwards compatible.

lib/perl5/IkiWiki/Plugin/template.pm

    #!/usr/bin/perl
    # Structured template plugin.
    package IkiWiki::Plugin::template ;

    use warnings ;
    use strict ;
    use IkiWiki 3.00 ;
    use Encode ;

    sub mktmpl_hash( $ ; $ ; @ ) ;
                # declare to supress warning in recursive call
    sub mktmpl_hash( $ ; $ ; @ )
                # make hash for the template, filling
                # values from the supplied params
    {
        my $template = shift( @_ )
                || error( "mktmpl_hash: no template provided" ) ;
        my $param_src = shift( @_ )
                || error( "mktmpl_hash: no parameters" ) ;

        my $path ;
        if( $#_ > 0 )
        {
            $path = [ @_ ] ;
        } else {
            $path = shift(@_) || [] ;
        } ;

        my %params ;

        my @path_vars ;
        if( $#{$path} < 0 )
        {
            @path_vars = $template->query() ;
        } else {
            @path_vars = $template->query( loop => $path ) ;
        } ;

        foreach my $var ( @path_vars )
        {
            push( @{$path}, $var ) ;
            my $param_type = $template->query( name => $path ) ;
            if( $param_type eq 'VAR' )
            {
                my @var_path = split( /_/, $var ) ;
                if( $var_path[0] ne '' )
                {
                    $path->[-1] = join( '_', @var_path[1..$#var_path] )
                        if( $var_path[0] eq 'raw' ) ;
                    $params{$var} = shift( @{$param_src->{$path->[-1]}} )
                            || return(undef) ;
                } ;
            } elsif( $param_type eq 'LOOP' )
            {
                $params{$var} = [] ;
                push( @{$params{$var}}, $_ )
                    while( $_ = mktmpl_hash($template,$param_src,$path) ) ;
            } ;
            pop( @{$path} ) ;
        } ; 
        return( \%params ) ;
    } ;

    sub proc_tmpl_hash( $ ; $ ; $ ; $ ) ;
                # declare to supress warning in recursive call
    sub proc_tmpl_hash( $ ; $ ; $ ; $ )
                # walk the hash, preprocess and
                # convert to html
    {
        my $tmpl_hash = shift( @_ ) ;
        my $page = shift( @_ ) ;
        my $destpage = shift( @_ ) ;
        my $scan = shift( @_ ) ;
        foreach my $key ( keys(%{$tmpl_hash}) )
        {
            unless( ref($tmpl_hash->{$key}) )
                        # here we assume that
                        # any reference is an
                        # array and allow it to
                        # fail if that's false
            {
                $tmpl_hash->{$key} =
                        IkiWiki::preprocess(
                                $page,
                                $destpage,
                                $tmpl_hash->{$key},
                                $scan ) ;
                my @key_path = split( /_/, $key ) ;
                $tmpl_hash->{$key} =
                        IkiWiki::htmlize(
                                $page,
                                $destpage,
                                pagetype($pagesources{$page}),
                                $tmpl_hash->{$key}, )
                    unless( $key_path[0] eq 'raw' ) ;
            } else {
                proc_tmpl_hash( $_, $page, $destpage, $scan )
                    foreach( @{$tmpl_hash->{$key}} ) ;
            } ;
        } ;
    } ;

    # "standard" ikiwiki definitions / hooks

    sub import
    {
        hook( type => "getsetup",
                id => "template",
                call => \&getsetup ) ;
        hook( type => "preprocess",
                id => "template",
                call => \&preprocess,
                scan => 1 ) ;
    } ;

    sub getsetup()
    {
        return(
                plugin => {
                    safe => 1,
                    rebuild => undef,
                    section => "widget",
                }, ) ;
    } ;

    sub preprocess( @ )
    {
    # first process arguments into arrays of values
        my %params ;

        my( $key, $value ) ;
        while( ($key,$value)=splice(@_,0,2) )
        {
            if( exists($params{$key}) )
            {
                push( @{$params{$key}}, $value ) ;
            } else {
                $params{$key} = [ $value ] ;
            } ;
        } ;

    # set context
        my $scan = ! defined( wantarray() ) ;
                    # This needs to run even in scan
                    # mode, in order to process links
                    # and other metadata included via
                    # the template.

    # check for critical values
        if( ! exists($params{id}) )
        {
            error( gettext("missing id parameter") ) ;
        } ;

    # set some convenience variables
        my $id = $params{id}->[$#{$params{id}}] ;
        my $page = $params{page}->[$#{$params{page}}] ;
        my $destpage = $params{destpage}->[$#{$params{destpage}}] ;
    # ... and an essential one for the production pass
        $params{basename} = [ IkiWiki::basename($page) ] ;

    # load the template
        my $template ;
        eval {
            $template =
                    template_depends( $id, $page,
                            blind_cache=>1 ) ;
                        # The bare id is used, so
                        # a page templates/$id can
                        # be used as the template.
        } ;
        if( $@ )
        {
            error(
                    sprintf(
                            gettext("failed to process template %s"),
                            htmllink(
                                    $page,
                                    $destpage,
                                    "/templates/$id")
                            )." $@"
                    ) ;
        } ;

    # create and process the parameters
        my $tmpl_hash = mktmpl_hash( $template, \%params ) ;
        proc_tmpl_hash( $tmpl_hash, $page, $destpage, $scan ) ;
    # ... and load the template with the values
        $template->param( $tmpl_hash ) ;

    # return the processed page chunk
        return( IkiWiki::preprocess($page,
                        $destpage,
                        $template->output(),$scan)
                ) ;
    } ;

    1 ;

sample template

# <TMPL_VAR HEADER0>

<table>
<TMPL_LOOP TEST0>
<tr>
    <td><TMPL_VAR DATA0></td>
    <td><TMPL_VAR DATA1></td>
</tr>
</TMPL_LOOP>
</table>

sample iki page

[[!meta  title="this is my loops page"]]

[[!template  id="loops"
header0="this is a table"
data0="cell0:0"
data1="cell0:1"
data0="cell1:0"
data1="cell1:1"
]]