Wednesday, October 12, 2005

XSLT transformation in .NET

Recently I've been doing a lot of XSLT transformations (XML to HTML) in .NET, and through trial and error I've come to a number of conclusions about how best to go about this. If you are looking for a starting point to get something that "just works" (which I wish I could have found when I started), this blog entry is meant to give you just that.

The XslTransform.Transform() method comes with a lot of overloads in NET 1.0, and as of .NET 1.1 almost the entire set was deprecated and replaced with a new set that added an additional parameter, XmlResolver. You might find the number of choices overwhelming.

Before you start pouring over every possible parameter permutation, try out the following:

XslTransform.Transform(XPathNavigator, XsltArgumentList, Stream, XmlResolver)

Out of these 4 parameters, in this demo you will only need two:
a) XPathNavigator--This parameter contains the source xml in a form navigable by XPath.
b) FileStream--This parameter contains the XSL transformation file.

The other parameters will be passed in as null:
c) XsltArgumentList--this is provided to allow you to pass in values not found in the xml, in case you need to "supplement" the xml values. If you want to do that, you may, but for myself, I place all necessary values into the xml, so I'm not needing to use this parameter and am just passing in null.
d) XmlResolver--the xml that I am generating for transformation is not using namespaces. Therefore, again, I pass in null for this parameter.

Now, let's go ahead and build a working XML to HTML xsl transformation:

1. If you don't have xml yet, generate it. For this example we'll type it in a text editor, but the XmlTextWriter class is very effective at easily generating xml for you using methods such as WriteElementString().

(For example, you might want to retrieve data from a database into a SqlTextReader instance and then use a while(sqlTextReader.Read()){} loop to create your xml document.)

Go ahead and create c:\xsltdemo\xmlfiles\MyXmlFile.xml in Notepad:


<?xml version="1.0" encoding="utf-8" ?>
<customers>
<customer id="35">
<firstname>Sally</firstname>
<lastname>Chiu</lastname>
</customer>
<customer id="36">
<firstname><bold>Garth</bold></firstname>
<lastname>Wachowski</lastname>
</customer>
<customer id="37">
<firstname>Bertha</firstname>
<lastname>Boxleitner</lastname>
</customer>
</customers>


2. Next, you'll need an XSL template to determine how to display this in HTML.

The main tags to understand if you've never used XSLT before are as follows. Read them through, have a look at the template below, then come back and re-read them again.
a) <xsl:output> tag
- set the parameters as shown below. (Note: The indent attribute is only respected when using a FileStream as your 3rd parameter in the XslTransform.Transform() method, as we are doing in this demo. If you use XmlTextWriter as your 3rd parameter, indentation is managed by the XmlTextWriter settings.)
b) <xsl:template> tag
- this is used first to contain the entire HTML for the page. However, below the initial xsl:template tag you may also create many sub-templates whose purpose is to add formatting to the specific node they are assigned to handle. Notice at the bottom of our sample that <bold≷ tags are managed by a separate template.
c) select attribute
- this attribute is contained in most xsl tags including all tags following this one (<xsl:value-of>, <xsl:apply-templates>, <xsl-if>, <xsl-for-each>). Select specifies the path to the node you are referencing within the tag, and can be expressed as an absolute path "/mynode/mysubnode" or a relative path (".", "@someattribute", "./childnode").
d) <xsl:value-of> tag
- This tag simply displays the value of the selected node. NOTE: If you try to place this tag within an HTML tag attribute, it will fail. Instead, you must place the node in curly brackets, as shown here:
regular HTML:
<xsl:value-of select="/mynode"/>
within an HTML tag:
<input type="textbox" name="{/mynode}">
e) <xsl:apply-templates> tag
- This tag is the same as value-of, except that you are throwing the value to any formatting templates within the xslt file to handle nested tags within the returned node string (such as <b> or <i>, or any customized tags).
f) <xsl:if> tag
- This allows you to do if logic. If you need else or further choices, you must switch immediately to the case statement, xsl:choose. (google it for the syntax). Note that you can test simply on a node name (such as /customers/customer) which is equivalent to asking whether the length of the value returned from the node > 0.
g) <xsl:for-each> tag
- perfect for looping. To make it work, you want your xml to contain collections, such as:
<customers>
<customer name="Clark"/>
<customer name="Lois"/>
</customers>
because this corresponds to the for-each tag as follows:
<xsl:for-each select="/customers/customer">
<td><xsl:value-of select="@name"/></td>
</xsl:for-each>

OK, you now have enough knowledge to create your xsl template:

Create c:\xsltdemo\xslt\CustomersList.xslt


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" encoding="Windows-1252"
omit-xml-declaration="yes" doctype-public="-//W3C//DTD HTML 3.2 Final//EN" />
<xsl:template match="/">
<BODY>
<xsl:if test="/customers/customer">
<table cellspacing="0" class="dtTABLE">
<TR VALIGN="top">
<TH>ID</TH>
<TH>First Name</TH>
<TH>Last Name</TH>
</TR>
<xsl:for-each select="/customers/customer">
<TR VALIGN="top">
<TD><xsl:value-of select="@id"/></TD>
<TD><xsl:apply-templates select="./firstname"/></TD>
<TD><xsl:apply-templates select="./lastname"/></TD>
</TR>
</xsl:for-each>
</table>
</xsl:if>
</BODY>

</xsl:template>

<xsl:template match="bold">
<b><xsl:value-of select="." /></b>
</xsl:template>

</xsl:stylesheet>


3. Finally, you write your code to process the above files. Within a method, do the following:

a) declare the files and paths used within this method as local constants (within the method.)

const string XML_PATH = @"c:\xsltdemo\xmlfiles\MyXmlFile.xml";
const string XSL_TEMPLATE = "c:\xsltdemo\xslt\CustomersList.xslt";
const string HTML_DIRECTORY = @"c:\xsltdemo\htmloutput\";
const string HTML_PAGE = "Customers.html";


b) convert your xml into an XPathNavigator instance.
NOTE Your xml could be located in either a file, an XmlTextReader instance, or a MemoryStream. You are starting with a file or an XmlTextReader, but later on, for much greater performance, refactor your xml configuration/creation into a MemoryStream!


XPathDocument xPathDoc = new XPathDocument(XML_PATH);
XPathNavigator xPathNavigator = xPathDoc.CreateNavigator();


c) create a directory using the DirectoryInfo class, then build a FileStream instance on that directory path:


DirectoryInfo di = new DirectoryInfo(HTML_DIRECTORY;
if (di.Exists != true)
di.Create();

string absoluteFilePath = di.FullName + HTML_PAGE;

FileStream fileStream = File.Create(absoluteFilePath);


d) perform the transformation:


XslTransform xslt = new XslTransform();
xslt.Load(XSL_TEMPLATE);
xslt.Transform(xPathNavigator, null, fileStream, null);


You're done!

2 comments:

Unknown said...

I found your web site website on bing and verify many of your early posts. Maintain up the truly superior operate. I just added your Feed to my MSN News Reader. On the lookout for forward to reading a lot more from you discovering out later on!…advertising | advertisement | production houses in pakistan | pakistani matrimony

Unknown said...

Pretty nice post. I just stumbled upon your weblog and wished to say that I’ve really enjoyed browsing your blog posts. In any case I will be subscribing to your feed and I hope you write again soon!
Advertising agencies in Pakistan