Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Adding custom XPath functions to XSL

0.00/5 (No votes)
2 Mar 2003 1  
This article shows you how to add context aware XPath functions to an XSL transformation. This is done by a hack using reflection.

Introduction

The XSL transformation is a powerful way of visualizing XML. The presence of extension objects makes it even more powerful. However it turns out that extension objects are very limited. They have no way of retrieving the current context of the transformation or the current document. Furthermore their return type is limited to only numbers and strings (returning an IXPathNavigable is restricted. See here for more details). This article presents a work around for the first problem.

Why use context aware XPath functions

A simple but very powerful application of such is the eval function. It evaluates XPath queries contained in a string or variable. For example imagine you have the following:

<xsl:variable name='vTest'>123456</xsl:variable>
<xsl:variable name= 'vQuery'>$vTest</xsl:variable>

If you take the value of vQuery using xsl:value-of you would get $vTest as a result. On the other hand <xsl:value-of select='eval($vTest)'/> will evaluated to 123456. There is no such function built-in so we will write it our selves.

Background

To read this article you should have good knowledge in C# and the Microsoft's .NET XML parser.

How it works

It looks like Microsoft have had the idea of allowing the user to write context aware functions. Have a look at the 2 classes IXsltContextFunction and XsltContext in the System.Xml.Xsl namespace. You can make your own context and function classes derived from the above 2, but you can use them only in programmatic XPath queries. An article I found in Microsoft's KB "recomends" that you use extension objects instead of IXsltContextFunction derived classes for writing your own XPath functions. Well actually it turns out that this not a recommendation - it is the only way to do it. There is no way of adding a context aware XPath extension function to the transformation. I decompiled the code that resolves functions in an XSLT (System.Xml.Xsl.XsltCompileContext.ResolveFunction) an here is what I got:

public virtual IXsltContextFunction ResolveFunction(string prefix, 
              string name, System.Xml.XPath.XPathResultType[] argTypes){
    IXsltContextFunction local0;
    string local1;
    object local2;
    MethodInfo local3;

    local0 = null;
    if (prefix == String.Empty)
        local0 = XsltCompileContext.s_FunctionTable.get_Item(name) 
                                           as IXsltContextFunction;
    else {
        local1 = this.LookupNamespace(prefix);
        if (local1 == "urn:schemas-microsoft-com:xslt" && name == "node-set")
            local0 = XsltCompileContext.s_FuncNodeSet;
        else {
            local3 = this.GetExtentionMethod(local1, name, argTypes, local2);
            if (local2 == null)
                throw new XsltException("Xslt_ScriptInvalidPrefix", prefix);
            if (local3 != null)
                local0 = new FuncExtension(local2, local3);
        }
    }
    if (local0 == null)
        throw new XsltException("Xslt_UnknownXsltFunction", name);
    if ((int) argTypes.Length < local0.Minargs || 
             local0.Maxargs < (int) argTypes.Length)
        throw new XsltException("Xslt_WrongNumberArgs", name, 
                       Convert.ToString((int) argTypes.Length));
    return local0;
}

As you can see there are 3 types of functions that the XSLT can resolve:

  • Ones that are in the root XML namespace
  • The msxsl:node-set function
  • Functions on extension objects

From the code above, it is visible that the only way of inserting a context aware function is to put it in the static hashtable s_FunctionTable field. As you have already guessed the System.Xml.Xsl.XsltCompileContext is a protected class and so is the s_FunctionTable. To access it, we will use reflection:

static Hashtable XslFunctionTable
{
    get
    {
        Assembly xmlAssembly = Assembly.LoadWithPartialName("System.Xml");
        Type tXsltCompileContext = xmlAssembly.GetType
                    ("System.Xml.Xsl.XsltCompileContext");
        FieldInfo finf = tXsltCompileContext.GetField("s_FunctionTable", 
                           BindingFlags.NonPublic | BindingFlags.Static);
        return finf.GetValue(null) as Hashtable;
    }
}

Once we have the hash table we can easily add or remove functions from it. The hashtable is initialized in the static constructor of System.Xml.Xsl.XsltCompileContext so we should avoid adding and removing functions in static constructors

Drawbacks

This is actually a hack. It works fine on .NET framework 1.0 but it may not work in future releases of the framework. I hope Microsoft fixes this problem in future. Also you can add functions only in the root XML namespace.

Possible improvements

The System.Xml assembly is located using it's partial name. This is not good because future versions of the .NET framework will use newer XML parsers and the hack wont work. The full name should be used instead.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here