Monday, September 21, 2009
Filed under:
xslt,
advanced
- by Douglas Robar
I prefer to use XSLT macros whenever possible, resorting to C#
only when I absolutely have to.
Until recently I have created .net macros for even simple
functionality that I would have preferred to put in an
easily-maintained XSLT macro. Why? Because I couldn't access a
needed namespace from within XSLT.
An example: file level operations
A recent site I worked on needed to display the size of a file.
If the file were in umbraco's Media section this would be easy with
the built-in umbracoBytes property. But in this
case, the file was uploaded rather than placed in umbraco's Media
section. Thus, the usual umbraco metadata was not available.
Various solutions presented themselves:
- Add an 'umbracoBytes' property (of type label
or static text) to my document type. Umbraco will auto-populate this
property when a file is uploaded (this is VERY cool and a great tip
to remember!). In my case, however, I had four upload fields in
the docType and whatever file was uploaded last would have its
filesize placed in the 'umbracoBytes' field. I needed a solution
that would work for all the files uploaded.
- Create an event handler setting a unique
uploadFile1_umbracoBytes, uploadFile2_umbracoBytes, etc. property
when uploading the file. This seemed like too much effort and I
would be sure to forget to update the event handler should a fifth
upload field be added to the docType later on.
- Create a custom XSLT extension to get the
filesize information. This is a good solution but a whole extension
seemed rather over-kill for a few lines of C# code.
The C# function I wanted to use was very simple, taking the path
to a file and returning the filesize in kilobytes. This could
certainly be done as an XSLT extension:
public String uploadFileSize(String filePath) {
if ((filePath != null) && (filePath.Length != 0)) {
String localFile = HttpContext.Current.Server.MapPath(filePath);
if (File.Exists(localFile)) {
FileInfo fileinfo = new FileInfo(localFile);
return String.Format("{0:#,###,###.##}", (fileinfo.Length / 1024));
}
}
return null;
}
Using .NET namespaces in your XSLT macros
What I really wanted to do was augment my XSLT macro with that
bit of C# code to get the file size from within the XSLT file
itself, without needing to create a .NET control, some app_code, an
event handler, or custom XSLT extension.
Unfortunately, file level access and many other fun capabilities
are not allowed in your umbraco XSLT macros. Not by default, that
is. But it's really simple to add that ability with in-line C# (a
topic we'll cover in another post) in your XSLT macro.
<msxml:script language="CSharp" implements-prefix="ps">
<msxml:using namespace="System.IO" />
<msxml:assembly name="System.Web" />
<msxml:using namespace="System.Web" />
<![CDATA[
public String uploadFileSize(String filePath) {
if ((filePath != null) && (filePath.Length != 0)) {
String localFile = HttpContext.Current.Server.MapPath(filePath);
if (File.Exists(localFile)) {
FileInfo fileinfo = new FileInfo(localFile);
return String.Format("{0:#,###,###.##}", (fileinfo.Length / 1024));
}
}
return null;
}
]]>
</msxml:script>
The msxml:using and
msxml:assembly statements are the important bits. The msxml:assembly loads the assembly like adding a reference in a .NET project. The msxml:using makes using the functions shorter with no prefix required, just as using
System.IO; and using System.Web; in your
C# code would do.
I found System.IO was already available so I didn't need to add the assembly statement for it. System.Web was not available so I added it with the msxml:assembly statement.
Completed XSLT macro with in-line .NET code
Here is a complete (and very simple, rather hard-coded) XSLT
file. Your macros would probably have more logic in the main
template, possibly set the uploadedFile variable with a macro
parameter (using a propertyPicker data type), and include
additional checks for errors.
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp " ">
]>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:ps="urn:schemas-percipient-studios"
xmlns:umbraco.library="urn:umbraco.library"
exclude-result-prefixes="msxml ps umbraco.library" >
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:param name="currentPage"/>
<!-- =========================================================== -->
<xsl:template match="/">
<xsl:variable name="uploadedFile" select="$currentPage/data[@alias='uploadedFile']" />
<xsl:if test="string($uploadedFile) != ''">
<p>
The size of the uploaded file is:
<xsl:value-of select="ps:uploadFileSize($uploadedFile)" /> kb
</p>
</xsl:if>
</xsl:template>
<!-- =========================================================== -->
<msxml:script language="CSharp" implements-prefix="ps">
<msxml:using namespace="System.IO" />
<msxml:assembly name="System.Web" />
<msxml:using namespace="System.Web" />
<![CDATA[
public String uploadFileSize(String filePath) {
if ((filePath != null) && (filePath.Length != 0)) {
String localFile = HttpContext.Current.Server.MapPath(filePath);
if (File.Exists(localFile)) {
FileInfo fileinfo = new FileInfo(localFile);
return String.Format("{0:#,###,###.##}", (fileinfo.Length / 1024));
}
}
return null;
}
]]>
</msxml:script>
<!-- =========================================================== -->
</xsl:stylesheet>
Conclusion
As you can see, extending your XSLT macros with a bit of .NET
functionality is very simple and will help you make the most of
your macros. And because XSLT macros are compiled, performance is
not a problem, though I also cached this macro heavily as well just to be on the safe side.