I'll add another vote for using xslt for server-side and client-side templating.
Though I recognize that it's not for everyone. To that end, I've been working on generating xsl templates using a ZPT like syntax.
I'll say "ZPT like" because there are some extensions that aren't available in ZPT.
I don't have anything to release yet, but it's an extension of the bitflux stuff
I have metal working, but it's a two step process to "compile" a set of .zpt files into an .xsl file.
The resulting .xsl file is then used on the server-side to render the html, or sent to the client so it can do it's own rendering. I use a Paste filter to generate .xsl files in-the-fly and cache them. And, another Paste filter to sniff client capabilities and determine if .xsl should be sent back to the client, or wether transformation should be done on the server.
I look at client-side XSLT rendering as very important. In a way, it can be a form of edge-side includes (ESI) http://www.esi.org/
This is done through the use of the document() xslt function on the client. For example, this fragment uses a relative path to retrieve an XML document from the server and set it's root element in a variable:
<div tal:define="order document(concat('/orders/', $order_number, '.xml'))/ROOT)">
Regarding context.. XSL expects XML as input, so the context is passed to the template as XML. So, it must be pre-rendered that way by the web request handler. The downside of course is that the templating engine cannot call python objects for dynamic rendering. The upside to that is that anything rendered dynamically this way probably should be exposed using a REST style interface. Then that data can be retrieved by the client directly, not just rendered on the server (as the above example shows).
Here's an example fragment of a tal:repeat traversing the first 24 elements of an invoice:
<tr tal:repeat="li $order/OE0500[24 > position()]" tal:sort="PICSEQ ascending" tal:attributes="class concat('R',position() mod 2)"> <td tal:content="$li/ITEM" /> <td tal:content="$li/LINENUM" /> <td tal:content="$li/DESC"/> <td tal:content="$li/QTYORDERED" /> <td><div class="button">BO</div></td> </tr>
And here's the example fragment of resulting .xsl (copied out of IE so not well formatted):
<xslout:for-each select="$order/OE0500[24 > position()]"> <xslout:sort select="PICSEQ" order="ascending" /> <xslout:variable name="li" select="." /> <tr> <xslout:attribute name="class"> <xslout:value-of select="concat('R',position() mod 2)" /> </xslout:attribute> <td> <xslout:value-of select="$li/ITEM" /> </td> <td> <xslout:value-of select="$li/LINENUM" /> </td> <td> <xslout:value-of select="$li/DESC" /> </td> <td> <xslout:value-of select="$li/QTYORDERED" /> </td> <td> <div class="button">BO</div> </td> </tr> </xslout:for-each>
The 'li' placeholder variable is not necessary, but that's what the bitflux guys came up with, so I stuck with it.
This thing diverges from ZPT in a number of ways (like a sort option for tal:repeat).
Also, there's a way of creating "macros" that can receive arguments. That is, something like metal:define, but it doesn't repeat the macro contents in the output file. It's more for defining xsl templates.
So, I guess it's a frankenstein templating language. I still need to integrate with existing .xsl files (via xsl:includes or imports), but I like the ease of ZPT.
I should spend more time working on it so I can get some feedback. It's definately a niche thing.
However I still think for my clients, XSLT and XML is just too handy to pass up.
I can take "client-side" .xsl scraps and re-use them in Apache FOP.. its great.