Using Xslt along with XPath we can transform any xml document in the way we want and use it e.g. to create the html page. At this time, we have Xslt 2.0 and XPath 2, but unfortunatelly .Net Framework doesn't support them. We can only use Xslt 1.
We have two ways to work with Xslt 2. One is to use a 3rd party library, e.g. XQSharp or Saxon. The second option is to manually implement the missed in Xslt 1 functions. How to do it I want to show in this post.
First is the sample code. The goal is to create a table from this xml data:
<?xml version="1.0" encoding="utf-8" ?>
<books>
<book isAvailable="true" averageUsersRating="4.0">
<title>First title</title>
<author>First book author</author>
</book>
<book isAvailable="true" averageUsersRating="1.2">
<title>Second title</title>
<author>Second book author</author>
</book>
<book isAvailable="false" averageUsersRating="5.0">
<title>Third title</title>
<author>Third book author</author>
</book>
</books>
The books which are currently not available must be placed in red table row. And the books titles which have rating greater that 4 must be written in upper case.
Xsl to transform this data to html table looks as follow
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<head></head>
<body>
<table>
<tr>
<th>Title</th>
<th>Author</th>
<th>Average rating</th>
</tr>
<xsl:for-each select="books/book">
<xsl:choose>
<xsl:when test="@isAvailable='true'">
<tr class="bookIsAvailable">
<xsl:call-template name="bookRowTemplate"/>
</tr>
</xsl:when>
<xsl:otherwise>
<tr class="bookIsNotAvailable">
<xsl:call-template name="bookRowTemplate"/>
</tr>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<xsl:template name="bookRowTemplate" >
<td>
<xsl:choose>
<xsl:when test="@averageUsersRating > 4">
<!-- return title in upper case -->
</xsl:when>
<xsl:otherwise>
<xsl:value-of select ="./title"/>
</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:value-of select ="./author"/>
</td>
<td>
<xsl:value-of select ="@averageUsersRating"/>
</td>
</xsl:template>
</xsl:stylesheet>
This stylesheet is I think self-explanatory. Colouring the appropriate table rows is easy epending on the value of isAvailable attribute.
The second requirement is to write the appropriate books titles in upper case. Xslt/XPath doesn't have (or I don't know) function for that, so we must write it manually.
In C# function this is easy to write:
public class XslStringExtensions
{
public string ConvertToUpperCase(string source)
{
return source.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
}
}
But is it possible to use this C# method in the stylesheet? The answer is yes.
First we must add to the stylesheet new attribute: xmlns:StringExtensions="urn:StringExtensions". Now we can write in the xsl the code for executing this method:
<xsl:when test="@averageUsersRating > 4">
<xsl:value-of select ="StringExtensions:ConvertToUpperCase(./title)"/>
</xsl:when>
At the end, we must configure the XslCompiledTransform to use the custom C# function. To do that, we must create list with arguments:
XsltArgumentList args = new XsltArgumentList();
args.AddExtensionObject("urn:StringExtensions", new XslStringExtensions());'
And that list we must pass to the XslCompiledTransform.Transform object, e.g.
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(xslTemplate);
XmlDocument doc=loadedXmlDoc;
MemoryStream returnedHtml = new MemoryStream();
transform.Transform(doc.CreateNavigator(), args, returnedHtml);
NHibernate has many ways to create and executes queries. With one of them* - the Named Queries we can execute our manually created stored procedures. This example is dedicated to SQL Server. This is important to notice, because this code is database specified. That means, for each database the query can look diffrent. E.g. in SQL Server we call the stored procedure by using the syntax: exec <procedure>. In Oracle it is: call <procedure>, so we must remember it.
Let's assume we have a simple stored procedure in our database which takes two arguments:
create procedure FindFilesWithNameLike(@pattern nvarchar(max), @extension nvarchar(10)) as
select f.Id,f.Name,f.[Path],f.Size,f.Extension from [File] f
join Extension e on f.Extension=e.Id
where f.Name
like '%'+@pattern +'%'
and e.Name=@extension
To use it with NHibernate, we must create a mapping. This is done in the *.hbm.xml file (I didn't find if this can be done with Fluent NHibernate and/or mapping by code).
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<sql-query name="FindFilesWithNameLike" callable="true" >
<query-param name="pattern" type="System.String"/>
<query-param name="extension" type="System.String"/>
<return-scalar column ="Id" type ="System.Guid"/>
<return-scalar column ="Name" type ="System.String"/>
<return-scalar column ="Size" type ="System.Int64"/>
<return-scalar column ="Extension" type ="System.String"/>
<return-scalar column ="Path" type ="System.String"/>
exec FindFilesWithNameLike @pattern=:pattern, @extension=:extension
</sql-query>
</hibernate-mapping>
As you can see - nothing special. We are using the query-param to define the procedure parameters and return-scalars to define, what columns (with types) are returned. At the end there is our database-specified code to execute the stored procedure with parameters.
When we have a mapping, we can now execute this query:
public IEnumerable<File> Execute(string pattern, string extension)
{
return session.GetNamedQuery("FindFilesWithNameLike")
.SetParameter("pattern", pattern)
.SetParameter("extension", extension)
.SetResultTransformer(new ResultToFileTransformer())
.List<File>();
}
To do this, we are using the api for named queries. When we execute this query we will have a list of list of objects. Thats why I added the SetResultTransformer. When the database table and custom entity is mapped one to one, we can use the Transformers.AliasToBean<T>, otherwise we can create custom transformer. In this example it is the ResultToFileTransformer. That class must implement the IResultTransformer interface.
*probably with using the native sql support in the NHibernate this it possible either, but I didn't checked