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!

Thursday, October 06, 2005

Logic for determining method inheritance type

Topics:
.NET Framework 1.x, 1.1
Reflection
MethodInfo
IsHideBySig
GetBaseDefinition()
Type.GetMethod Method (String, Type[])
overrides keyword
new keyword

*****

Using reflection I can easily determine all kinds of member information, such as whether a method is abstract, static, virtual, final. (MethodInfo.IsVirtual and so on)

However, how to easily determine a method's inheritance type? (corollary: and the inheritance type of a property's accessor methods?)

In other words, how does one determine where a method has originated:

in the current type (i.e. MethodInfo.ReflectedType),
inherited from a base class,
as overridden in the current type (from a virtual method in a base class)
as 'new' keyword in the current type (to hide this method in the current type from a method in the base class of the same name--whether or not the base class method has been declared as virtual.)

To do this, I created a method to parse these four possibilities. To store these values, I created an enumeration, InheritanceTypeEnum:


public enum InheritanceTypeEnum
{
DeclaredLocally,
Inherited,
OverridesKeyword,
NewKeyword
}


I then created two helper methods to parse each inheritnace type.


Note: I encountered two unexpected behaviours with Reflection API that required workarounds:

1. MethodInfo.IsHideBySig returned true in many scenarios other than a method with a 'new' keyword.
Workaround: I created my own method, IsMethodOfSameSignatureFoundInBaseClass(), to determine this.)

2. Type.GetMethod Method (String, Type[]) returned non-null in scenarios with the parameters in the method matched with false positives (when a parameter type was compared against a parameter of type System.Object).
Workaround: I wrote verification code on the returned method.

Here's the code:

/// 2005Oct06 David Gadd
/// new logic to determine the member's inheritance type
/// (local, inherited, overridden or new keyword)
private InheritanceTypeEnum GetInheritanceType(MethodInfo currentMethod)
{
// 1. If the method's current type != method's declaring type then the method is inherited.
// (It does not exist locally at all, it is entirely owned by the base/declaring class.
if(currentMethod.ReflectedType.FullName != currentMethod.DeclaringType.FullName)
{
return InheritanceTypeEnum.Inherited;
}
else
{
// 2. If the class that owns the base(first) definition of this method != the class
// that owns the current definition of this method, then the method is overridden
if(currentMethod.GetBaseDefinition().ReflectedType.FullName != currentMethod.ReflectedType.FullName)
{
return InheritanceTypeEnum.OverridesKeyword;
}
// else the method is defined locally within the current type
else
{
// 3. If the local method has a method of the same name in the base class
// of the current type, then this method must be qualified with the "new" keyword.
// (Originally I tried to do this with MethodInfo.IsHideBySig, but I found that this
// property returned true in many cases other than the 'new' keyword...)
bool hasSameNamedBaseMethod = IsMethodOfSameSignatureFoundInBaseClass(currentMethod);

if(hasSameNamedBaseMethod)
{
return InheritanceTypeEnum.NewKeyword;
}
else
{
// 4. If the local method's signature does not use the 'new' keyword,
// then the method is completely local in origin.
return InheritanceTypeEnum.DeclaredLocally;
}
}
}
}

/// This is meant to be a more reliable alternative to the MethodInfo.IsHideBySig property
/// which seems to render true for many situations other than the application of the new keyword
/// to a method
private bool IsMethodOfSameSignatureFoundInBaseClass(MethodInfo currentMethod)
{
bool matchFound = false;

try
{
MethodInfo baseMethod;

ParameterInfo[] currentMethodParameters = currentMethod.GetParameters();
Type[] currentMethodParameterTypes = new Type[currentMethodParameters.Length];
for(int i = 0; i < currentMethodParameters.Length; i++)
{
currentMethodParameterTypes[i] = currentMethodParameters[i].ParameterType;
}

baseMethod = currentMethod.ReflectedType.BaseType.GetMethod(currentMethod.Name, currentMethodParameterTypes);

if(baseMethod != null)
{
// At this point, if the baseMethod is not null, a succesful match SHOULD have been found.
// However, it appears that Type.GetMethod(name, parameterTypes) may have a bug:
// in testing I have found that in a 1-parameter method where the base class has a
// type of System.Object, it will be perceived as a match to an overload method in the derived
// class of 1-parameter whose type is NOT System.Object. Therefore, the following workaround:
// retrieve the parameter from baseMethod and do an explicit Type comparison.
ParameterInfo[] baseMethodParameters = baseMethod.GetParameters();

for(int i = 0; i < baseMethodParameters.Length; i++)
{
if(currentMethodParameterTypes[i] == baseMethodParameters[i].ParameterType)
{
matchFound = true;
}
else
{
matchFound = false;
// as soon as any false matches are found, exit the loop.
break;
}
}

}
}
catch(AmbiguousMatchException aex)
{
// the code above thoroughly handles method signatures
// so an ambiguous match should never be found.
throw new AmbiguousMatchException(aex.Message);
}
catch(ArgumentNullException)
{
matchFound = false;
}
catch(Exception)
{
matchFound = false;
}

return matchFound;
}