Tech Blog

Using Microsoft’s Extended XPath functions in BizTalk

It’s not that well documented, but Microsoft have a bunch of
extra functions you can use from XPath expressions within XSLT.
You can find the documentation on them here.

What isn’t made apparent is that none of them work with XslTransform:
a subset of them will work with XslCompiledTransform,
and the others only work with MSXML 4.0 onwards.

This becomes obvious if you try and use these functions, or if you read this
blog post from the Microsoft XML team
on the XslCompiledTransform class.

Being the curious cat I am, I wondered if there was any way to use the functions from
within XslTransform
if there was, I figured you could use the functions in a BizTalk Map.

Short answer: No, but you can use them via Extension Objects (I’ll show you how later
in this post).
Long Answer:
If you look at the source code for the System.Xml.dll assembly
(either via .NET Reflector,
or the SSCLI, or
(even better) downloading the official source code using NetMassDownloader)
you can look at how XSLT containing these functions is handled.

[Note: Unfortunately, a lot of the “good stuff” is actually implemented
in the System.Data.SqlXml.dll assembly:
this assembly contains the System.Xml.Xsl.Runtime namespace
in which most of the logic is defined.
The assembly source is not included in the source you can download using NetMassDownloader,
so the only way of viewing it is via .NET reflector, or the SSCLI. Interestingly,
the System.Data.SqlXml.dll assembly
no longer contains any SqlXml related code – it was all migrated to System.Data.dll and Microsoft.Data.SqlXml.dll with
the release of .NET 2.0 from what I can see – all that’s left is internal XSLT code,
so the assembly would now be better called System.Xml.Xslt.dll]

As an example, we’ll look at ms:format-date:
this will format an XSD-style date/time using a given format string – pretty much
the same as using DateTime.ToString(string) in
.NET.
If you do a search for format-date in
the source code, you find all of the implementation in the System.Data.SqlXml.dll assembly.

Firstly, you find a method called ResolveFunction in
the QilGenerator class
(in the System.Xml.Xsl.Xslt namespace):
(Note: Qil refers to the process of compiling XSLT to MSIL and thence
into a managed assembly via an Abstract
Syntax Tree (AST)
– it’s an interesting topic and worthy of a post in it’s own
right! Give me time…)


// NOTE: DO NOT call QilNode.Clone()
while executing this method since fixup nodes cannot be cloned


QilNode IXPathEnvironment.ResolveFunction(string prefix, string name, IList<QilNode>
args, IFocus env) {




else if (name
== “format-date” || name == “format-time”)


{

    FunctionInfo.CheckArity(/*minArg:*/1, /*maxArg:*/3,
name, args.Count);


    bool fwdCompat
= (xslVersion == XslVersion.ForwardsCompatible);


    return f.InvokeMsFormatDateTime

    (

        /*datetime:*/f.ConvertToString(args[0]),

        /*format:
*/
1 < args.Count ? f.ConvertToString(args[1]) : f.String(string.Empty),


        /*lang:    */2
< args.Count ? f.ConvertToString(args[2]) : f.String(string.Empty),


        /*isDate:
*/
f.Boolean(name == “format-date”)


    );

}

What the ResolveFunction method
does is attempt to map a call to an external function (defined in the urn:schemas-microsoft-com:xslt namespace)
to a method defined in the XsltFunctions class.

In this case, the method InvokeMsFormatDateTime in
the XsltQilFactory class
(again in the System.Xml.Xsl.Xslt namespace)
is called to create an early-bound method link (note that XsltMethods.MSFormatDateTime returns
a System.Refelection.MethodInfo object
which can be used to call the method via reflection):


public QilNode InvokeMsFormatDateTime(QilNode datetime, QilNode format, QilNode lang, QilNode isDate)
{


CheckString(datetime);

CheckString(format);

CheckString(lang);

CheckBool(isDate);

    return XsltInvokeEarlyBound(QName(“ms:format-date-time”),

        XsltMethods.MSFormatDateTime, T.StringX, new QilNode[]
{ datetime, format, lang, isDate }


    );

}

The XsltMethods class
contains this entry for MSFormatDateTime:


internal static class XsltMethods

{

    …

    public static readonly MethodInfo MSFormatDateTime
= GetMethod(typeof(XsltFunctions), “MSFormatDateTime”);

Which in turn points to the MSFormatDateTime method
in the XsltFunctions class
(defined in the System.Xml.Xsl.Runtime namespace):


namespace System.Xml.Xsl.Runtime
{


    using Res =
System.Xml.Utils.Res;


 

[EditorBrowsable(EditorBrowsableState.Never)]

public static class XsltFunctions {

private static readonly CompareInfo compareInfo
= CultureInfo.InvariantCulture.CompareInfo;




 

// string ms:format-date(string
datetime[, string format[, string language]])


// string ms:format-time(string
datetime[, string format[, string language]])


//

// Format xsd:dateTime
as a date/time string for a given language using a given format string.


// * Datetime contains
a lexical representation of xsd:dateTime. If datetime is not valid, the


// empty string
is returned.


// * Format specifies
a format string in the same way as for GetDateFormat/GetTimeFormat system


// functions. If
format is the empty string or not passed, the default date/time format for the


// given culture
is used.


// * Language specifies
a culture used for formatting. If language is the empty string or not


// passed, the current
culture is used. If language is not recognized, a runtime error happens.


public static string MSFormatDateTime(string dateTime, string format, string lang, bool isDate)

{

    try {

         int locale
= GetCultureInfo(lang).LCID;

         XsdDateTime xdt;

         if (!XsdDateTime.TryParse(dateTime, XsdDateTimeFlags.AllXsd
| XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrTimeNoTz, out xdt))
{


            return string.Empty;

         }

         SystemTime st
= new SystemTime(xdt.ToZulu());

         StringBuilder sb
= new StringBuilder(format.Length
+ 16);



(interestingly, there’s all sorts of good stuff in the XsltFunctions class,
including the implementation of the XSLT translate function,
and other functions performed by managed code when the XSLT is compiled to MSIL).

Phew! So it turns out that all the good stuff is defined in the XsltFunctions class.
More importantly, the call to ResolveFunction only happens
in XslCompiledTransform.
So you can’t use any of these functions from XslTransform.
Which means there is no direct way to use them from the BizTalk Mapper.

Calling extended XPath functions via Extension Objects

To call the extended functions from a BizTalk Map, in theory all you need to do is
reference them from a Script functoid (using a Script Type of “External
Assembly”). Unfortunately if you try this, you’ll see that the XsltFunctions class
does not appear:

Those of you who were paying attention to the definition of the XsltFunctions class
may already understand why: you can’t use Static Classes with Extension Objects in
a BizTalk Map.
The reason for this is that the extension objects are loaded by BizTalk using this
code:


object extension
= Assembly.Load(assemblyName).CreateInstance(typeName);

CreateInstance will
throw a MissingMethodException if
the type passed in does not have a default constructor – which our static class does
not.

The BizTalk Mapper knows this, and so does not display static classes in the list
of classes in the script functoid.

As far as I am aware (and please correct me if you know of another way) the only way
to get around this would be to create a non-static wrapper class round the methods
you wanted.
For example if you did this:


namespace System.Data.SqlXmlHelper

{

    public class Helper

    {

        public static string MSFormatDateTime(string dateTime, string format, string lang, bool isDate)

        {

            return XsltFunctions.MSFormatDateTime(dateTime,
format, lang, isDate);


        }

    }

}

Then you could use the above in a script functoid like this:

The other issue is that some of the functions aren’t defined in the System.Data.SqlXml.dll assembly
– they’re in the unmanaged MSXML dll (from v4.0 onwards) and are designed to be used
only when you’re using MSXML to process the XSLT.

In order to save you the trouble of creating a wrapper assembly for calling these
functions, I’ve done it for you.
You’ll find it (and the source code) in my next post.

Back to Tech Blog