Most developers I talk to will cringe if they hear the acronym XSLT. I suspect that reaction is derived from some past experience where they have seen some horrendously complex XML/XSLT combination. There is certainly lots of that around. However, for certain types of document transformations, XSLT can be a very handy tool and with the right approach, and as long as you avoid edge cases, it can be fairly easy.
When I start building an XSLT transform, I always start with the “identity” transform,
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:template match="/ | @@* | node()"> <xsl:copy> <xsl:apply-templates select="* | @@* | node()" /> </xsl:copy> </xsl:template>
</xsl:stylesheet>
The identity transform simply traverses the nodes in the input document and copies them into the output document.
Make some changes
In order to make changes to the output document you need to add templates that will do something other than simply copy the existing node.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:template match="/ | @@* | node()"> <xsl:copy> <xsl:apply-templates select="* | @@* | node()" /> </xsl:copy> </xsl:template>
<!-- Change something --> <xsl:template match="foo"> <xsl:element name="bar"> <xsl:apply-templates select="* | @@* | node()" /> </xsl:element> </xsl:template>
</xsl:stylesheet>
This template matches on any element named foo
and in it’s place creates an element named bar
that contains a copy of everything that foo
contained. XSLT will always use the template that matches most specifically.
Given the above XSLT, an input XML document like this,
<baz> <foo value=”10” text=”Hello World”/> </baz>
would be transformed into
<baz> <bar value=”10” text=”Hello World”/> </baz>
And add more changes…
The advantage of XSLT over doing transformations in imperative code, is that adding more transformations to the document doesn’t make the XSLT more complex, just longer. For example, I could move the foo
element inside another element by adding a new matching template. The original parts of the template stay unchanged.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <xsl:output method="xml" indent="yes" /><span class="kwrd"><</span><span class="html">xsl:template</span> <span class="attr">match</span><span class="kwrd">="/ | @@* | node()"</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:copy</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:apply-templates</span> <span class="attr">select</span><span class="kwrd">="* | @@* | node()"</span> <span class="kwrd">/></span> <span class="kwrd"></</span><span class="html">xsl:copy</span><span class="kwrd">></span> <span class="kwrd"></</span><span class="html">xsl:template</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:template</span> <span class="attr">match</span><span class="kwrd">="foo"</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:element</span> <span class="attr">name</span><span class="kwrd">="bar"</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:apply-templates</span> <span class="attr">select</span><span class="kwrd">="* | @@* | node()"</span> <span class="kwrd">/></span> <span class="kwrd"></</span><span class="html">xsl:element</span><span class="kwrd">></span> <span class="kwrd"></</span><span class="html">xsl:template</span><span class="kwrd">></span> <span class="rem"><!-- Additional template that does not change previous --></span> <span class="kwrd"><</span><span class="html">xsl:template</span> <span class="attr">match</span><span class="kwrd">="baz"</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:element</span> <span class="attr">name</span><span class="kwrd">="splitz"</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:copy</span><span class="kwrd">></span> <span class="kwrd"><</span><span class="html">xsl:apply-templates</span> <span class="attr">select</span><span class="kwrd">="* | @@* | node()"</span> <span class="kwrd">/></span> <span class="kwrd"></</span><span class="html">xsl:copy</span><span class="kwrd">></span> <span class="kwrd"></</span><span class="html">xsl:element</span><span class="kwrd">></span> <span class="kwrd"></</span><span class="html">xsl:template</span><span class="kwrd">></span>
</xsl:stylesheet>
With imperative code, adding additional transformations often requires doing refactoring of existing code. Due to XSLT being from a more functional/declarative heritage, it tends to stay cleaner when you add more to it.
Just because I can…
And for those of you love JSON too much to ever go near XSLT, below is some sample code that takes a JSON version of my sample document and applies the XSLT transform to it using the XML support in JSON.NET.
Starting with this JSON
{ "baz": { "foo": { "value": "10", "text": "HelloWorld" } } }
we end up with
{ "splitz": { "baz": { "bar": { "value": "10", "text": "Hello World" } } } }
Here’s the code I used to do this. Please don’t take this suggestion too seriously. I suspect the performance of this approach would be pretty horrific. However, if you have a big chunk of JSON that you need to do a complex, offline transformation on, it might just prove useful.
var jdoc = JObject.Parse("{ 'baz' : { 'foo' : { 'value' : '10', 'text' : 'Hello World' } }}"); // Convert Json to XML var doc = (XmlDocument)JsonConvert.DeserializeXmlNode(jdoc.ToString());// Create XML document containing Xslt Transform var transform = new XmlDocument(); transform.LoadXml(@@"<?xml version='1.0' encoding='utf-8'?> <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' > <xsl:output method='xml' indent='yes' omit-xml-declaration='yes' />
<xsl:template match='/ | @@* | node()'> <xsl:copy> <xsl:apply-templates select='* | @@* | node()' /> </xsl:copy> </xsl:template> <xsl:template match='foo'> <xsl:element name='bar'> <xsl:apply-templates select='* | @@* | node()' /> </xsl:element> </xsl:template> <xsl:template match='baz'> <xsl:element name='splitz'> <xsl:copy> <xsl:apply-templates select='* | @@* | node()' /> </xsl:copy> </xsl:element></xsl:template> </xsl:stylesheet>"</span>);
//Create compiled transform object that will actually do the transform. var xslt = new XslCompiledTransform(); xslt.Load(transform.CreateNavigator());
// Transform our Xml-ified JSON var outputDocument = new XmlDocument(); var stream = new MemoryStream(); xslt.Transform(doc, null, stream); stream.Position = 0; outputDocument.Load(stream);
// Convert back to JSON :-) string jsonText = JsonConvert.SerializeXmlNode(outputDocument);
Image credit: Transformer https://flic.kr/p/2LK2ph
Image credit: Platypus https://flic.kr/p/8tYptD