For the purposes of this example, we’ll take some xml data and as part of an XPath function we will perform a calculation to get the area and one to get the perimeter using custom XPath functions. We may do this because the existing XPath functions do not meet your needs. I will change the example of how I used it to protect information for the company I was with, but we’ll just say we were getting different xml feeds of different formats and needed an easy way to set up the parsing of these and picking of string values from within the xml. This solution needed to be flexible although they could expect small code changes when adding a new xml feed. These settings for the XPath used in the select as well as the XPath statement can be stored in the database, but for the example, we’ll keep it simple. I’ll also use VB.Net since that was what the client was using and I have permission to change the code around for the example. I’ll also try to not include too much code in the sample. Below is an example XML file:
It would be helpful to first see how something like this would be used. Yes, there are alternate ways to come up with the same result. In our case we’ll load a MemoryStream with the xml data, create out XPathDocument, XPathNavigator, XPathNodeIterator, XPathExpression. Finally, what is different here than an ordinary XPathExpression is that we have a SampleContext, with which we set the namespace and URI. We then set the context for the expression. We can then iterate through the nodes. To show how passing variables to our functions work, we’ll pass a multiplier like 1000 to the function.
Dim ms As New MemoryStream()
Dim writer As New StreamWriter(ms)
writer.Write(xmlData)
writer.Flush()
ms.Position = 0
Dim XPathDoc As XPathDocument = New XPathDocument(ms)
' Create an XPathNavigator
Dim XPathNav As XPathNavigator = XPathDoc.CreateNavigator()
Dim nodes As XPathNodeIterator = XPathNav.Select(“/squares/square”)
Dim expression As XPathExpression
Expression = XPathNav.Compile(“sampleFunctions:getArea(@length, @width, $multiplier)”)
Dim arguments As New XsltArgumentList()
Arguments.AddParam(“multiplier”, “”, 1000)
Dim context As New SampleContext(XPathNav.NameTable, arguments)
context.AddNamespace("sampleFunctions", "http:\\sampleXPathFunctions")
expression.SetContext(context)
While nodes.MoveNext
data.AddLine(nodes.Current.Evaluate(expression).ToString())
End While
Return data
You’ll notice the sampleFunctions:getArea. What we are doing is compiling that XPath function. This could just as easily been a standard XPath function like concat. We’ll dig into how this all works.
First we’ll create the SampleContext class inheriting from XsltContext.
The most important of the overridden functions is ResolveFunction where we define the functions and its parameters to the executing XPath as well as return value. ResolveVariable is important as well when passing a variable to the function.
Public Overrides Function ResolveFunction(ByVal prefix As String, ByVal name As String, ByVal ArgTypes() As System.Xml.XPath.XPathResultType) As System.Xml.Xsl.IXsltContextFunction
Dim func As SampleXPathRegExExtensionFunction = Nothing
Select Case name
Case SampleXPathFunctions.area
func = New SampleXPathRegExExtensionFunction(name, 3, 3, New XPathResultType() {XPathResultType.Number, XPathResultType.Number, XPathResultType.Number}, XPathResultType.Number)
Case SampleXPathFunctions.perimeter
func = New SampleXPathRegExExtensionFunction(name, 3, 3, New XPathResultType() {XPathResultType.Number, XPathResultType.Number, XPathResultType.Number}, XPathResultType.Number)
End Select
Return func
End Function
Public Overrides Function ResolveVariable(ByVal prefix As String, ByVal name As String) As System.Xml.Xsl.IXsltContextVariable
Return New SampleXPathExtensionVariable(name, “”)
End Function
We’ll now create our SampleXPathRegExExtensionFunction, which should implement IXsltContextFunction. When implementing in 2005, you will see all the methods you need to implement. The most important method to review is Invoke. This method gets executed after it is resolved and allows you to write your XPath function in managed code. Here you could do whatever as long as you return the necessary return type.
Public Function Invoke(ByVal xsltContext As System.Xml.Xsl.XsltContext, ByVal args() As Object, ByVal docContext As System.Xml.XPath.XPathNavigator) As Object Implements System.Xml.Xsl.IXsltContextFunction.Invoke
Select Case _Name
Case SampleXPathFunctions.area
Dim length As Double = Convert.ToDouble(args(0))
Dim width As Double = Convert.ToDouble(args(1))
Dim multiplier As Double = Convert.ToDouble(args(2))
Return length * width * multiplier
Case SampleXPathFunctions.perimeter
Dim length As Double = Convert.ToDouble(args(0))
Dim width As Double = Convert.ToDouble(args(1))
Dim multiplier As Double = Convert.ToDouble(args(2))
Return (length + width) * 2 * multiplier
End Select
Return Nothing
End Function
Next you should create a SampleXPathExtensionVariable that implements IXsltContextVariable.
The only function here that may need some explaining is Evaluate. You just want to cast the xsltContext to the XsltContext created previously.
Public Function Evaluate(ByVal xsltContext As System.Xml.Xsl.XsltContext) As Object Implements System.Xml.Xsl.IXsltContextVariable.Evaluate
Dim vars As XsltArgumentList = CType(xsltContext, SampleContext).ArgList
Return vars.GetParam(_VarName, _NamespaceURI)
End Function
That’s all there is to it. The performance is very fast and it is a good solution for times when you have xml and need to extract some data and do something with it beyond just retrieving the value or simple formatting. You can make your XPath statements much more powerful.