I was looking for a solution on how to execute an XSL transform on a C# project file and convert it to a Nant build file. Nant provides the xsl file to do the conversion under the open source license agreement ( courtesy of Gordon Weakliem), but Nant does not provide away of producing the transformation.In my search I found some expensive solutions and decided that I didn't want to spend the $100-$1000 dollars on a tool to do this since XSLT transformation is built right into the .NET framework.The tool took all of 15 minutes to write in .NET so I think it was worth the time spent.
Building with Nant
NAnt is an Open Source product that allows you to perform some powerful build, documentation and deployment solutions for .NET. NAnt is like NMake except about 1000 times easier to use and easier to understand because it is written in XML. NAnts files are divided into Projects, Properties, Targets and Tasks. Tasks allow you to do many things such as compile C# code, file management, call other builds, or launch other applications. NAnt works with other Open Source Solutions as well such as NDoc. NDoc generates documentation from assemblies and Xml document files (Xml Document files are generated from the csc compile by setting the /doc option. This /doc option reads the summary (triple-slash) comments out of the source code and generates an Xml file from it.) NDoc can generate beautiful MSDN style chm help files from the code. If you document all of your code properly, you will produce some amazing class documentation with this tool.
NAnt also has a whole Nant-Contrib Open Source Project which creates auxiliary tasks to NAnt to do things like create MSI files for deployment. It really is an extremely powerful tool and I encourage you to download and try it. For performing production builds where you may need to accomplish things outside the capabilities of Visual Studio, it may prove to be a great solution.
Transforming through XSLT
XSLT (Xml Style Sheet Language Transform) allows you to create an active template that will act on an XML file and convert it to another XML file format. XSLT uses XPath to match elements in the source document and applies template rules to alter the results in the destination document. XPath is a sort of language that allows you to filter nodes inside an XML document. Below is a sample XSL Transform that will convert a list of books and authors in an xml data file and convert the results to an HTML table of title and author.
Listing 1 - XSLT to transform to an HTML table
<?xml version="1.0" encoding="ASCII"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform>"
<xsl:template match="/">
<html>
<body>
<h2>My Book Collection</h2>
<table border="1">
<tr bgcolor="#9ace34">
<th align="left">Title</th>
<th align="left">Author</th>
</tr>
<xsl:for-each select="catalog/library">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="author"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Listing 2 - XML Data To Transform
<?xml version="1.0" encoding="ASCII"?>
<?xml-stylesheet type="text/xsl" href="booklibrary.xsl"?>
<catalog>
<library>
<title>Digital Fortress</title>
<author>Dan Brown</author>
<country>USA</country>
<publisher>Columbia</publisher>
<price>7.99</price>
<year>2003</year>
</library>
.
.
.
</catalog>
Transforming XML in .NET
.NET provides us with the System.Xml.Xsl namespace to activate our Xsl Transforms. At the heart of this namespace is the XslTransform class. The XslTransform class makes transforming Xml a fairly simple task.Below (Listing 3) is the code from our XslTransformer that allows us to read an input file, perform an Xsl Transform on the file and produce an output file using the XslTransform class. The XPathDocument class is used to load the VS.NET project file which serves as our input into the Xsl Transform. The outputted Nant build file is produced using the XmlTextWriter class.The transform is carried out using the Transform method of the XslTransform class.Note it only takes a few lines of code to perform this whole process using the powerful classes provided by .NET.
Listing 3 - Using the XslTransform class to transform the csproj file
// read the input file to be transformed
XPathDocument xpathdocument = new XPathDocument(browserProject.FileName);
// read the xslt transform to perform on the input file
XslTransform xslt = new XslTransform();
xslt.Load(browserXSLT.FileName);
// create a file for output.
XmlTextWriter writer = new XmlTextWriter(browserNantOutput.FileName, Encoding.Unicode);
writer.Formatting=Formatting.Indented;
// perform the transform on the input file and write out the result transformation
xslt.Transform(xpathdocument, null, writer, null);
writer.Close();
// display the output in the edit box
StreamReader sr = new StreamReader(browserNantOutput.FileName);
txtOutput.Text = sr.ReadToEnd();
sr.Close();
Persisting Data in the App.Config File
One thing I wanted to try to do in this project was to persist the fields in the dialog by writing them to the App.Config File. Upon finding the System.Configuration.ConfigurationSettings.AppSettings class, I thought, Great! .NET provides a nice convenient way of persisting data to the configuration file. Then I ran into the snag of actually trying to use this class to persist data to the config file. I found that the class reads the data fine out of the file. The problem is that the class cannot write the data to the keys. What is even more confusing is that a Set method is even provided in the AppSettings class, but the class doesn't actually allow you to use this method. It was time to search and see if anyone else was having the same problem. In my research and with a little coding, I ended up creating the following class in Listing 5 to solve the problem. Basically the class ConfigHandler uses the convenient System.Xml namespace and XPath to read and write the keys and values in a configuration file. We can use the XmlDocument class to Load the configuration file and then the XPath query allows us the select the desired node out of the XmlDocument. Once we have the XmlNode of interest we can either read or write to it.The XPathquery,
/configuration/appSettings/add[@key = <key> allows us to select a single node with the particular <key> out of the Xml Hierarchy configuration->appsettings->add.
Listing 4 - App.Config file containing persistent form values
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!-- User application and configured property settings go here.-->
<!-- Example: <add key="settingName" value="settingValue"/> -->
<add key="browserProject.FileName" value="C:\NAnts\tests\NAnt.Tests.csproj" />
<add key="browserNantOutput.FileName" value="C:\temp0" />
<add key="browserXSLT.FileName" value="C:\NAnts\vsconvert.xsl" />
</appSettings>
</configuration>
Listing 5 - Class to handle persistence in the App.Config file
public class ConfigHandler
{
public ConfigHandler()
{
}
/// <summary>
/// This is the configuration file path property. Set this before using this class
/// </summary>
static string _configurationPath = "App.Config";
static public string ConfigurationPath
{
get
{
return _configurationPath ;
}
set
{
_configurationPath = value ;
}
}
/// <summary>
/// Replace the key and value pair in the configuration file
/// </summary>
/// <param name="key"></param>
/// <param name="val"></param>
static public void replaceConfigSettings(string key, string val)
{
XmlDocument xDoc = new XmlDocument();
string sFileName = _configurationPath;
try
{
// load the configuration file
xDoc.Load(sFileName);
// find the node of interest containing the key using XPATH
XmlNode theNode = xDoc.SelectSingleNode(@"/configuration/appSettings/add[@key = '" + key + "\']");
// Set the new value for the node
if (theNode != null)
theNode.Attributes["value"].Value = val;
// lop off file prefix if it exists
if(sFileName.StartsWith(file:///))
sFileName = sFileName.Remove(0,8);
// save the new configuration settings
xDoc.Save(sFileName);
xDoc = null;
}
catch(Exception ex)
{
#if DEBUG
System.Windows.Forms.MessageBox.Show("replaceConfigSettings()"+ex.Message);
#else
System.Windows.Forms.MessageBox.Show("Unable to save changes");
#endif
xDoc = null;
}
}
/// <summary>
/// Retrieve the configuration value given the key
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
static public string GetConfigSetting(string key)
{
XmlDocument xDoc = new XmlDocument();
string sFileName = _configurationPath;
// make sure the file exists, if not return
if (File.Exists(sFileName) == false)
{
return "";
}
try
{
// load the configuration file
xDoc.Load(sFileName);
// find the node of interest from the key
XmlNode theNode = xDoc.SelectSingleNode(@"/configuration/appSettings/add[@key = '" + key + "\']");
// retrieve the nodes value if it exists
if (theNode != null)
return theNode.Attributes["value"].Value;
xDoc = null;
}
catch(Exception ex)
{
#if DEBUG
System.Windows.Forms.MessageBox.Show("replaceConfigSettings()"+ex.Message);
#else
System.Windows.Forms.MessageBox.Show("Unable to load changes");
#endif
xDoc = null;
}
return "";
}
}
Conclusion
.NET gives us a lot of power over XML technologies with the System.Xml, System.Xml.Xsl, and System.Xml.XPath namespaces. With these namespaces and the classes contained within, we can manipulate Xml by running Xsl Transforms on files and performing XPath queries. Because both Nant and VS.NET projects are XML files, we can utilize the power of Xsl to convert between the two.
NAnt and NDoc are two very powerful Open Source tools that you can add to your .NET arsenal. If you are going to begin using NAnt, I encourage you to look at the simple tutorial provided on sourceforge to begin your NAnt journey. In the meantime, if you run into a situation where think you may not be able to do it with Visual Studio.NET,never say CAn't, when you may be able to do it with NAnt.