site site-navbranch.xsl site

Skip to main content.

Looking for PowerPoint and/or Google Analytics solutions? Please follow the above links to ShufflePoint.com.

section no_callout.xsl no_callout

page static_html.xsl b2004_12_17

XPath Patterns for XML/A and Performance Results with COM and .NET

If you want to do anything useful with SOAP and XML, you need to learn to use XSLT. And to use XSLT properly, you will have to learn XPath. In this article I will be providing some XPath recipies for XML/A. This is not going to be an XPath tutorial - you can find plenty of those by Googling. I particularly like

http://www.zvon.org/xxl/XPathTutorial/General/examples.html

and

http://aspn.activestate.com/ASPN/Cookbook/XSLT

and

http://www.w3schools.com/xsl/xsl_languages.asp

While the xamd.xsl stylesheet provided by Microsoft as a sample can certainly be a good learning tool (and starting point for your work), it was designed for a narrow purpose and doesn't provide answers to "How do I do X?" types of questions.

One of the challenges I ran into with XML/A was getting a list of distinct members in an axis with two crossjoined dimensions. The nature of the XML/A result schema makes this sort of tricky.

One of my xslt stylesheets is used to convert XML/A into a ChartFX automation script. I needed make a timeseries graph. In the XML/A result document, the time dimension was a crossjoin with the measures on the column axis. The row axis had some other dimension. The desired display was series of charts - one per measure. Each chart was a line graph with one line for each member of the row axis. The x-axis of the charts was time.

I had to tell the chart how many ticks of time were present. This can be done for the XML/A result set I've described using this XSLT expression:

var timeticks = <xsl:value-of select="count(//xa:Axes/xa:Axis[@name='Axis0']/xa:Tuples/xa:Tuple/xa:Member[2]/xa:Caption[not(.=../../preceding-sibling::*/xa:Member[2]/xa:Caption/.)])" />;

Remember that this is an XSLT which is generating a big block of JavaScript - ChartFX has no usable XML import format.

You can also use this XPath design pattern to iterate the unique members in a crossjoin axis:

<!--
Create a series array - one for each timeperiod member
-->
<xsl:for-each select="//xa:Axes/xa:Axis[@name='Axis0']/xa:Tuples/xa:Tuple/xa:Member[2]/xa:Caption[not(.=../../preceding-sibling::*/xa:Member[2]/xa:Caption/.)]">
	scount++;
	Chart.series[scount] = new Object();
	Chart.series[scount].un = "<xsl:value-of select="../xa:UName" />";
	Chart.series[scount].name = "<xsl:value-of select="." />";
</xsl:for-each>

Note that the value of the variable scount will end the loop with the same value as we assigned to timeticks. This XPath pattern can be used wherever you need to get either a count or a list of unique members in a crossjoin.

While this is one ugly XPath expression, it has the advantage of being a single XPath expression. That means that it can be used in procedural code against an XML/A DOM. A useful example of this I provide in the file CsvOutDom.aspx. This ASP.NET script uses the above XPath design pattern to procedurally transform a XML/A result document into a comma-separated file which can be loaded, for example, into Excel.

The other common approach to obtaining unique XML nodes is the Muenchian Method. Oracle's lead XML Technical Evangelist Steve Muench developed this approach using the xsl:key element - it became so popular that it bears his name. The Muenchian Method solution to the above problem would be expressed as:

<xsl:key name="kDistinctTime" match="//xa:Axes/xa:Axis[@name='Axis0']/xa:Tuples/xa:Tuple/xa:Member[2]/xa:Caption" use="."/>

<xsl:variable name="svDistinctTimeVals" select="//xa:Axes/xa:Axis[@name='Axis0']/xa:Tuples/xa:Tuple/xa:Member[2]/xa:Caption[generate-id() = generate-id(key('kDistinctTime',.))]"/>

<xsl:for-each select=" svDistinctTimeVals ">
  ...
</xsl:for-each>

The use of keys makes this approach more efficient than the preceding-sibling approach - but at the cost of no longer being usable in procedural code such as the CSV generator sample. In any significant XML/A project you will probably find both approaches invaluable to have in your developer's toolkit.

I decided to put these patterns through their paces on using both the COM and .NET XML libraries from Microsoft. Here, in short, are the results. My machine is a 2.8GHz P4 Prescott with 2GB RAM. The XMLA results and scripts used for these tests are included in the download.

Small XMLA result:
transform.wsf: 30 msec
transform.aspx: 450 msec
CsvOutDom.aspx: 31 msec
Large XMLA result:
transform.wsf: 1031 msec
transform.aspx: 19,390 msec
CsvOutDom.aspx: 2843 msec

As is readily apparent, XSLT in .NET is a total dog. Microsoft has promised to get XSLT performance in .NET 2.0 on par with the COM implementation. In a future article I'll have a look at the beta version and share my findings.

I then decided to specifically compare the preceding-sibling method with the Muenchian method for getting the members of a dimension within a crossjoined axis. The scripts xmla-xpath.xsl and xmla-xpath2.xsl were used for this comparison. And here are the results.

Large XMLA result:
preceding-sibling method: 140281 msec
Muenchian method: 500 msec

That makes the Muenchian method like 300 times faster!

The script CsvOutDom.aspx uses the preceding-sibling method. After seeing the performance difference, I am wishing there was a way to build keys in procedural script. Perhaps then I could get a .NET script to match the performance of my MSXML script.

downloads: b2004_12_17.zip (1160K)