In the previous post about streaming we have dropped at the point where we have XmlReader in hands, which continously gets data from IEnumerable<Person> source. Now we shall remind about ForwardXPathNavigator - a class we have built back in 2002, which adds streaming transformations to .NET's xslt processor.
XmlReader
IEnumerable<Person>
ForwardXPathNavigator
While XslCompiledTransform is desperately obsolete, and no upgrade will possibly follow; still it's among the fastest xslt 1.0 processors. With ForwardXPathNavigator we add ability to transform input data of arbitrary size to this processor.
XslCompiledTransform
We find it interesting that xslt 3.0 Working Draft defines streaming processing in a way that closely matches rules for ForwardXPathNavigator:
Streaming achieves two important objectives: it allows large documents to be transformed without requiring correspondingly large amounts of memory; and it allows the processor to start producing output before it has finished receiving its input, thus reducing latency.
The rules for streamability, which are defined in detail in 19.3 Streamability Analysis, impose two main constraints:
The only nodes reachable from the node that is currently being processed are its attributes and namespaces, its ancestors and their attributes and namespaces, and its descendants and their attributes and namespaces. The siblings of the node, and the siblings of its ancestors, are not reachable in the tree, and any attempt to use their values is a static error. However, constructs (for example, simple forms of xsl:number, and simple positional patterns) that require knowledge of the number of preceding elements by name are permitted.
xsl:number
When processing a given node in the tree, each descendant node can only be visited once. Essentially this allows two styles of processing: either visit each of the children once, and then process that child with the same restrictions applied; or process all the descendants in a single pass, in which case it is not possible while processing a descendant to make any further downward selection.
The only significant difference between ForwardXPathNavigator and xlst 3.0 streaming is in that we reported violations of rules for streamability at runtime, while xslt 3.0 attempts to perform this analysis at compile time.
Here the C# code for the xslt streamed transformation:
var transform = new XslCompiledTransform(); transform.Load("People.xslt"); // We have a streamed business data. var people = Data.CreateRandomData(10000, 0, 0, 10000); // We want to see it as streamed xml data. using(var stream = people.ToXmlStream("people", "http://www.nesterovsky-bros.com")) using(var reader = XmlReader.Create(stream)) using(var output = File.Create("people.html")) { // XPath forward navigator is used as an input source. transform.Transform( new ForwardXPathNavigator(reader), new XsltArgumentList(), output); }
Notice how XmlReader is wrapped into ForwardXPathNavigator.
To complete the picture we need xslt that follows the streaming rules:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:d="http://www.nesterovsky-bros.com" exclude-result-prefixes="msxsl d"> <xsl:output method="html" indent="yes"/> <!-- Root template processed in the streaming mode. --> <xsl:template match="/d:people"> <html> <head> <title>List of persons</title> <style type="text/css"> .even { } .odd { background: #d0d0d0; } </style> </head> <body> <table border="1"> <tr> <th>ID</th> <th>First name</th> <th>Last name</th> <th>City</th> <th>Title</th> <th>Age</th> </tr> <xsl:for-each select="d:person"> <!-- Get element snapshot. A snapshot allows arbitrary access to the element's content. --> <xsl:variable name="person"> <xsl:copy-of select="."/> </xsl:variable> <xsl:variable name="position" select="position()"/> <xsl:apply-templates mode="snapshot" select="msxsl:node-set($person)/d:person"> <xsl:with-param name="position" select="$position"/> </xsl:apply-templates> </xsl:for-each> </table> </body> </html> </xsl:template> <xsl:template mode="snapshot" match="d:person"> <xsl:param name="position"/> <tr> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="$position mod 2 = 1"> <xsl:text>odd</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>even</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:attribute> <td> <xsl:value-of select="d:Id"/> </td> <td> <xsl:value-of select="d:FirstName"/> </td> <td> <xsl:value-of select="d:LastName"/> </td> <td> <xsl:value-of select="d:City"/> </td> <td> <xsl:value-of select="d:Title"/> </td> <td> <xsl:value-of select="d:Age"/> </td> </tr> </xsl:template> </xsl:stylesheet>
So, we have started with a streamed entity data, proceeded to the streamed XmlReader and reached to the streamed xslt transformation.
But at the final post about streaming we shall remind a simple way of building WCF service returning html stream from our xslt transformation.
The sources can be found at Streaming.zip.
Remember Me
a@href@title, b, blockquote@cite, em, i, strike, strong, sub, super, u