Improving XslCompiledTransform performance.

Imagine this scenario, you have some xml files that you wish to transform to html files, this is done by using xsl files.

This you think is a bit slow (even though what “slow” is, is different from person to person J).

So, how do we do this? Again, I’ll show by example. First create a new xml file like this:

<?xml version="1.0" encoding="utf-8" ?>

<Persons>

  <Person>

    <Id>1</Id>

    <Name>John</Name>

    <Age>32</Age>

    <City>Stockholm</City>

    <Email>John@Stockholm.com</Email>

  </Person>

  <Person>

    <Id>2</Id>

    <Name>Mike</Name>

    <Age>25</Age>

    <City>New York</City>

    <Email>Mike@NewYork.com</Email>

  </Person>

  <Person>

    <Id>3</Id>

    <Name>Jean</Name>

    <Age>42</Age>

    <City>Paris</City>

    <Email>Jean@Stockholm.com</Email>

  </Person>

  <Person>

    <Id>4</Id>

    <Name>Phil</Name>

    <Age>18</Age>

    <City>London</City>

    <Email>Phil@London.com</Email>

  </Person>

  <Person>

    <Id>5</Id>

    <Name>Mark</Name>

    <Age>36</Age>

    <City>Sidney</City>

    <Email>Mark@Sidney.com</Email>

  </Person>

</Persons>

And save it as Persons_1.xml, Persons_2.xml and Persons_3.xml (in the real world the contents would be different depending on who provides them, but it doesn’t matter for this purpose).

Then create a new .xsl file, this will generate a new html file. The xsl will check if the person is older than 30, if so, that column will have one color, if not, another color. And for some reason it is also a

requirement to have all strings in uppercase letters. For this we will create C# functions in the xsl. (All this is just to have some processing to do)

Read more about scripting in xls files here:

"XSLT Stylesheet Scripting Using <msxsl:script>"

https://msdn.microsoft.com/en-us/library/533texsx.aspx

"Script Blocks Using msxsl:script"

https://msdn.microsoft.com/en-us/library/wxaw5z5e.aspx

So, create the xsl like so:

<?xml version="1.0" encoding="iso-8859-1"?>

<xsl:stylesheet version="1.0"

xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="urn:user">

  <xsl:template match="/">

    <html>

      <body>

        <h2>Persons</h2>

        <table border="1">

          <tr bgcolor="#00FFFF">

            <th>Id</th>

            <th>Name</th>

            <th>Age</th>

            <th>City</th>

            <th>Email</th>

          </tr>

          <xsl:for-each select="Persons/Person">

            <tr>

             <td><xsl:value-of select="Id"/></td>

              <td><xsl:value-of select="user:TextToUpper(Name)"/></td>

              <xsl:choose>

                <xsl:when test="user:IsAbove30(Age)">

                  <td bgcolor="#00FF00"><xsl:value-of select="Age"/></td>

                </xsl:when>

                <xsl:otherwise>

                  <td bgcolor="#FF0000"><xsl:value-of select="Age"/></td>

                </xsl:otherwise>

              </xsl:choose>

              <td><xsl:value-of select="user:TextToUpper(City)"/></td>

              <td><xsl:value-of select="user:TextToUpper(Email)"/></td>

            </tr>

          </xsl:for-each>

        </table>

      </body>

    </html>

  </xsl:template>

  <msxsl:script language="C#" implements-prefix="user">

    <![CDATA[

    public string TextToUpper(string inString)

    {

      string s = inString.ToUpper();

      return s;

    }

    public bool IsAbove30(int inAge)

    {

      return (inAge > 30);

    }

    ]]>

  </msxsl:script>

</xsl:stylesheet>

Save this as Person.xsl.

Now, time to transform these xml files to html files using the xsl as the guide on how the html should look.

So create a new console application and add this code (should be self-explanatory):

    class Program

    {

        public static string[] xmlFiles = new string[] { @"..\..\Person_1.xml", @"..\..\Person_2.xml", @"..\..\Person_3.xml" };

        public static string xslFile = @"..\..\Person.xsl";

        static void Main(string[] args)

        {

            Program p = new Program();

            Stopwatch sw = new Stopwatch();

            foreach (string xmlfile in Program.xmlFiles)

            {

                sw.Start();

                XmlDocument xmlDoc = new XmlDocument();

                xmlDoc.Load(xmlfile);

                p.Run(xmlDoc, xmlfile);

                sw.Stop();

                Console.WriteLine("{0} done, elapsed time (ms): {1}", xmlfile, sw.ElapsedMilliseconds);

                sw.Reset();

            }

        }

        private void Run(XmlDocument xmlDoc, string fileName)

        {

            XslCompiledTransform xct = new XslCompiledTransform();

            StringBuilder sb = new StringBuilder();

            XmlWriter xw = XmlWriter.Create(sb);

            XsltSettings xs = new XsltSettings(true, true);

            XmlUrlResolver xur = new XmlUrlResolver();

            try

            {

                xct.Load(Program.xslFile, xs, xur);

                xct.Transform(xmlDoc, xw);

                using (StreamWriter sw = new StreamWriter(fileName + ".html"))

                {

                    sw.Write(sb.ToString());

                }

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex);

            }

        }

    }

And run it, the output on my machine is typically something like this:

..\..\Person_1.xml done, elapsed time (ms): 224

..\..\Person_2.xml done, elapsed time (ms): 117

..\..\Person_3.xml done, elapsed time (ms): 119

This may not look like a long time, but if you have hundreds or even thousand files to process, it will soon add up. These files may also be more complex than this example, adding even more time when processing.

What takes time is the compilation of the xsl, ie. the call to xct.Load.

So how to improve this? One way is to ‘cache’ the XslCompiledTransform since in this case we know that we have several xml files that will be used in conjunction with the same xsl file.

So this means that we really only need to load it once, then transform it many times. So, as an example, replace the code above with the code below:

    class Program

    {

        public static string[] xmlFiles = new string[] { @"..\..\Person_1.xml", @"..\..\Person_2.xml", @"..\..\Person_3.xml" };

        public static string xslFile = @"..\..\Person.xsl";

        bool isInitialized = false;

        XslCompiledTransform xct = null;

        XsltSettings xs = null;

        XmlUrlResolver xur = null;

        static void Main(string[] args)

        {

            Program p = new Program();

            Stopwatch sw = new Stopwatch();

            foreach (string xmlfile in Program.xmlFiles)

            {

                sw.Start();

                XmlDocument xmlDoc = new XmlDocument();

                xmlDoc.Load(xmlfile);

                p.Run(xmlDoc, xmlfile);

                sw.Stop();

                Console.WriteLine("{0} done, elapsed time (ms): {1}", xmlfile, sw.ElapsedMilliseconds);

                sw.Reset();

            }

     }

        private void Initialize()

        {

            xct = new XslCompiledTransform();

            xs = new XsltSettings(true, true);

            xur = new XmlUrlResolver();

            try

            {

                xct.Load(Program.xslFile, xs, xur);

                isInitialized = true;

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex);

            }

        }

        private void Run(XmlDocument xmlDoc, string fileName)

        {

            if (!isInitialized)

            {

                Initialize();

            }

            try

            {

                StringBuilder sb = new StringBuilder();

                XmlWriter xw = XmlWriter.Create(sb);

                xct.Transform(xmlDoc, xw);

                using (StreamWriter sw = new StreamWriter(fileName + ".html"))

                {

                    sw.Write(sb.ToString());

                }

            }

            catch (Exception ex)

      {

                Console.WriteLine(ex);

            }

        }

    }

And rerun it, typical output for me:

..\..\Person_1.xml done, elapsed time (ms): 211

..\..\Person_2.xml done, elapsed time (ms): 1

..\..\Person_3.xml done, elapsed time (ms): 1

That is some improvement. From ~450 ms to ~215, this is because we only compile the xsl once rather than on every iteration.

But as we can see, the first iteration is still taking the same amount of time (since this when the compilation happens), and you may only have a need to perform this operation once.

If you’re only doing it once, then there is no benefit of ‘caching’ the complied xsl since you will not use it after the first and only use. So, how do we go on about that.

Well, luckily there is a tool called XSLTC that is shipped with Visual Studio 2008 that does just that.

So, let’s do that. Start a command prompt and navigate to the directory where you have saved your Person.xsl file.

Then simply execute the following:

xsltc /settings:script+ Person.xsl

Note that you need to add the script+ setting since we are calling our own functions in the xsl.

Once you have executed this command, you should have two new dlls in your directory, once for the xsl (Person.dll) and one for the script in the xsl file (Person.script.dll).

(You could merge them into one dll if you wish using ILMerge, but that is another story).

More on the XSLTC tool here:

.NET Framework Developer's Guide - XSLT Compiler (xsltc.exe)

https://msdn.microsoft.com/en-us/library/bb399405.aspx

XSLTC — Compile XSLT to .NET Assembly

https://blogs.msdn.com/antosha/archive/2007/05/28/xsltc-compile-xslt-to-.net-assembly.aspx

ILMerge

https://www.microsoft.com/downloads/details.aspx?FamilyID=22914587-b4ad-4eae-87cf-b14ae6a939b0&DisplayLang=en

So, simply reference both dlls in the project, then change the code to look like below (you only need to change the Load method):

       private void Initialize()

        {

            xct = new XslCompiledTransform();

            xs = new XsltSettings(true, true);

            xur = new XmlUrlResolver();

            try

            {

                xct.Load(typeof(Person)); // <-- Use precompiled xsl

                isInitialized = true;

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex);

            }

        }

And rerun it, typical output for me:

..\..\Person_1.xml done, elapsed time (ms): 38

..\..\Person_2.xml done, elapsed time (ms): 1

..\..\Person_3.xml done, elapsed time (ms): 1

So now the first load only takes 40 ms since we are using the precompiled xsl as well as ‘caching’ the XslCompiledTransform.

This is obviously a fictional scenario, but it still shows how you can improve the transformation from xml into html using XslCompiledTransform.

In this case we started with an execution time of 460 ms and managed to get it down to 40, that is well below a 10th of the initial run time.

HTH

I’ve attached a project with all three solutions to this post.

Additional information here.

.NET Framework Class Library - XslCompiledTransform Class

https://msdn.microsoft.com/en-us/library/system.xml.xsl.xslcompiledtransform.aspx

PrecompileXsl.zip