Showing posts with label XSL. Show all posts
Showing posts with label XSL. Show all posts

17 September 2008

Column layouts with XSL

Marking up a set of items so that they are laid out in a fixed number of columns from left to right can be tricky if the items vary in height. You need markup something like the example below to achieve this...

<div class="row">
 <div> class="item"></div>
 <div> class="item"></div>
 <div> class="item"></div>
</div>
<div class="row">
 <div> class="item"></div>
 <div> class="item"></div>
</div>

...with CSS like this...

.item { float: left; width: 250px; }

Outputting this from XSL is fairly straight forward and requires relatively little "code" by juggling some XSL and XPath. Here is some example XML:

<?xml version="1.0"?>
<items>
 <item title="Item 1"/>
 <item title="Item 2"/>
 <item title="Item 3"/>
 <item title="Item 4"/>
 <item title="Item 5"/>
</items>

The XSL to transform this into HTML of the format shown earlier looks like this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:variable name="cols" select="3"/>
 <xsl:template match="/items">
  <xsl:apply-templates select="item[position() mod $cols = 1]" mode="row"/>
 </xsl:template>
 <xsl:template match="item" mode="row">
  <div class="row">
   <xsl:apply-templates select=".|./following-sibling::item[position() &lt; $cols]"/>
  </div>
 </xsl:template>
 <xsl:template match="item">
  <div class="item">
   <xsl:value-of select="@title"/>
  </div>
 </xsl:template>
</xsl:stylesheet>

First off, we've got a variable named "cols" for setting the number of columns to output. Next, inside the first template matching our root element, we have an apply-templates which selects all the item element with a position that when divided by the number of columns leaves a remainder of one. This should give us items 1, 4, 7, 10 etc and these are then passed to a template in mode "row".

This apply-templates will match the next template whose job is to output the div element with class row. Within this div there is another apply-templates, this time matching the current item element and a number of its following siblings. That being one less than then number of columns required so in this example we get the context item plus its two following siblings. The template that matches this apply is that last one, without the mode specified.

The last "item" matching template simply writes out the div with class item surrounding the content of the item itself.

13 May 2008

Creating Views using XSL in ASP.NET MVC

There's something about Web Forms that never felt quite right to me; it all seemed a bit too much like a bodge that created more problems than it solved. I've been having a tinker with ASP.NET MVC for the past few days now and I'm really liking the way it all fits together; giving you clean separation between the layers and a means of passing data between them.

The View layer in MVC uses a very Classic ASP-esque markup for mixing code and HTML. Seems rather dirty but by this point any major processing should have been done and all that you'll need to do is render HTML with maybe some looping.

That being said if all we'll need to do by this point is some looping at the most then why not use an XSL transform instead?

XSL is well defined and established now with a load of tools out there for giving you a WYSIWYG view of your tranform as you build it. Plus as it is purely XML based it will likely be a lot easier for designers (the folk who we really want to build Views) to get to grips with.

A quick proof of concept

Step 1 - Create a model class for your XSL ViewData

This has simply an XmlDocument which will contain our serialized object and a string which will contain a path to our XSL file.

public class XslViewData
{
 private System.Xml.XmlDocument _doc;
 private string _xslPath;

 public XslViewData(System.Xml.XmlDocument doc, string xslPath)
 {
  _doc = doc;
  _xslPath = xslPath;
 }
  
 public System.Xml.XmlDocument Doc
 {
  get 
  {
   return _doc;
  }
 }

 public string XslPath
 {
  get
  {
   return _xslPath;
  }
 }
}

Step 2 - Serialize your object to XML in your Controller

First make sure the type you want to render is ready for XML serialization. This involves adding some attributes to classes and properties so read up on that first before you carry on. It's important to get your type serializing out in a nice way as it will be easier to work with in the XSL.

using System.Xml;
using System.Xml.Serialization;
...
Product prod = ProductRepository.GetProduct(id);
XmlSerializer ser = new XmlSerializer(typeof(Product));  
XmlDocument doc = new XmlDocument();
System.IO.MemoryStream ms = new System.IO.MemoryStream();
XmlWriter xw = XmlWriter.Create(ms);
ser.Serialize(xw, prod);
ms.Position = 0;
doc.Load(XmlReader.Create(ms));
RenderView("Xsl", new XslViewData(doc, "/Content/prodDetail.xsl"));

Step 3 - Create a ViewPage or ViewUserControl to host your XSL

All you need in the aspx/ascx file is an Xml ASP.NET control...

<asp:Xml ID="Xml1" runat="server" onload="Xml1_Load"></asp:Xml>

...and in the code behind...

public partial class Xsl : ViewPage<XslViewData>
{
 protected void Xml1_Load(object sender, EventArgs e)
 {
  Xml1.Document = ViewData.Doc;
  Xml1.TransformSource = ViewData.XslPath;
 }
}

Step 4 - Create an XSL file

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
 
 <xsl:template match="/Product">
  <h2><xsl:value-of select="Name"/></h2>
  <xsl:apply-templates select="Image"/>
    </xsl:template>

 <xsl:template match="Image">
  <img>
   <xsl:attribute name="src">
    <xsl:value-of select="ImageUrl"/>
   </xsl:attribute>
  </img>
 </xsl:template>
 
 <xsl:template match="*">
 </xsl:template>
 
</xsl:stylesheet>

Done

Not only is this simple to implement it has a lot of scope for improvment; compiling transforms, adding caching and configuation etc. This method also has the advantage that it requires no recompile to alter the template.

Links

13 February 2007

XML namespace prefixes in MSXML

If you're working with XSL or a similar technology that makes use of XML namespace prefixes using the Microsoft XML DOM you'll likely run into problems if you try to do anything more than just load in a file.

Adding elements

The W3 DOM specification includes a createElementNS method for creating an element scoped within a namespace however MSXML doesn't. You can create an element with a prefix using createElement but this doesn't correctly register the namespace of the node and you'll get a schema error something like:

msxml3.dll: Keyword xsl:stylesheet may not contain xsl:include.

In order to create an element and register it correctly you have to use createNode instead which takes node type (1 for an element), node name and namespace URI as arguments e.g.

Set ndIncl = xslDoc.createNode(1, "xsl:include",
"http://www.w3.org/1999/XSL/Transform")

Using XPath

Similar to the createElement problem, even if you've only loaded an XSL document you won't be able to use XPath to query it because oddly the namespaces aren't automatically registered with XPath e.g.

Set nlTemps = xslDoc.documentElement.selectNodes("/xsl:stylesheet/xsl:template")

yields the following error:

msxml3.dll: Reference to undeclared namespace prefix: 'xsl'.

To get this to play ball you have to set the "SelectionNamespaces" second-level property which takes a space delimited list of namespace definitions using a setProperty call of the form:

xslDoc.setProperty "SelectionNamespaces",
"xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"

Links

15 December 2006

Working with XML - Part 3 - Formatting XML using XSL

XSL is an language for transforming XML into different formats, written in XML using specific elements and attributes. To achieve the transform you simply load you XML and XSL into DOM objects then call transformNode on the XML document passing it your XSL document and it will return the transformed output.

XSL is effectively a procedural programming language. It has conditional and looping logic structures as well as variables and callable "functions". I'd recommend reading XSL @ W3SChools as this explains the basics well.

Recap

In parts one and two we looked at loading a sample bit of XML and how to select certain nodes from it using XPath.

<?xml version="1.0" ?>
<library>

  <authors>
     <author id="12345">
        <name>Charles Dickens</name>
     </author>
     <author id="23456">  
        <name>Rudyard Kipling</name>

     </author>        
  </authors>
  <books>
     <book>
        <title>Great Expectations</title>
        <author>12345</author>

     </book>
     <book>
        <title>The Jungle Book</title>
        <author>23456</author>
     </book>

  </books>
</library>

Using XSL we can convert this XML document into, for example, HTML to send to a web browser. We could also transform it into SQL INSERT statements for adding the data to a database.

HTML Output - Listing Data

Here's an example of how you could output the records from the sample XML:

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- more stuff to go here -->

</xsl:stylesheet>

We start with the stylesheet element which specifies the XML namespace "xsl" which is used as the prefix for all the special XSL elements.

<xsl:template match="/">
   <html>
      <head>
         <title>Library</title>
      </head>
      <body>
         <h1>Library</h1>
         <xsl:apply-templates select="authors/author" />
      </body>
   </html>
</xsl:template>

Next we have a template element with a match attribute equal to "/". This template matches the root of the XML document so the transform starts here outputting the contents of the element. The apply-templates element then tells the transform to apply the appropriate templates to the nodes returned by the value of the select attribute which you'll notice is XPath.

<xsl:template match="author">
   <h2><xsl:value-of select="name" /> (<xsl:value-of select="@id" />)</h2>
   <p>Books by this author:</p>
   <table>
      <tr>
        <th>Title</th>
      </tr>
      <xsl:apply-templates select="/library/books/book[author = current()/@id]" />
   </table>
</xsl:template>

This template will match the author nodes selected so output will pass here at this point - similar to a function call in a normal programming language. Here value-of elements output the value of the nodes identified in their select attributes, XPath again.

The next apply-templates element selects all the book elements with an author child element whose value is equal to the current author element's id attribute. It uses the current() XSL function to get at the element being tranformed by the template.

<xsl:template match="book">
   <tr>
      <td><xsl:value-of select="title" /></td>
   </tr>
</xsl:template>

Finally the titles of the books are writen out on table rows so what you end up with after calling transformNode is this:

<html>
  <head>
    <title>Library</title>
  </head>
  <body>
    <h1>Library</h2>
    <h2>Charles Dickens (12345)</h2>
    <p>Books by this author:</p>
    <table>
      <tr>
        <th>Title</th>
      </tr>
      <tr>
        <td>Great Expectations</td>
      </tr>
    </table>
    <h2>Rudyard Kipling (23456)</h2>
    <p>Books by this author:</p>
    <table>
      <tr>
        <th>Title</th>
      </tr>
      <tr>
        <td>The Jungle Book</td>
      </tr>
    </table>
  </body>
</html>

Using the output element

XHTML compliant output

XSL has an HTML output mode that you can specify by adding this to the top of your stylesheet, before any template elements:

<xsl:output mode="html" />

However the Microsoft.XMLDOM will mess around with your tags if you use this so if you want your output to be XHTML compliant you need to use the XML output mode instead thusly:

<xsl:output method="xml" omit-xml-declaration="yes" />

Outputting a DOCTYPE

If you want to output a DOCTYPE (which you need to get IE to obey the CSS box model properly) you add a few attributes to your output element:

<xsl:output 
   method="xml" 
   omit-xml-declaration="yes"
   doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
   doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
/>

which will produce:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

For more info on the output element visit output @ W3Schools.

Useful XSL snipets

Alternating row classes on tables

<xsl:template match="book">
   <tr>
      <xsl:attribute name="class">
         <xsl:choose>
            <xsl:when test="(position() mod 2) = 0">even</xsl:when>
            <xsl:otherwise>odd</xsl:otherwise>
         </xsl:choose>
      </xsl:attribute>
      <td><xsl:value-of select="title" /></td>
   </tr>
</xsl:template>

Template applicability

You can make nodes of the same type product different output by changing the match attribute of your template:

<xsl:template match="book[author = 12345]">
   <tr>
      <td class="highlight"><xsl:value-of select="title" /></td>
   </tr>
</xsl:template>

<xsl:template match="book">
   <tr>
      <td><xsl:value-of select="title" /></td>
   </tr>
</xsl:template>

Here we're applying a highlight class to book rows by author 12345.

Links