Sunday, March 12, 2006

XSLT... mapping from XML data to SVG

Well I've just found myself doing some more transformation work, pretty easy stuff translating a basic schema which was a series of nested "node" objects, each with a label. And using it to create an SVG tree based render of the information. First port of call was to try and cheat by using an automatic mapping tool, after all XSLT is the future of transformation isn't it?

So the first challenge was that SVG doesn't have a schema it has DTDs! A quick "generate schema" request in XMLSpy to give myself something to aim at. So now we have a source schema that looks like


<?xml version="1.0" encoding="UTF-8"?>
<node label="top" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node label="business servicse">
<node label="first service"/>
<node label="second service"/>
</node>
<node label="crm services"/>
<node label="support services">
<node label="hear no evil"/>
<node label="see no evil"/>
<node label="speak no evil"/>
<node label="monkey">
<node label="see"/>
<node label="do"/>
</node>
</node>
</node>



And we are aiming at the wonder that is SVG, so basically we need the "text" and "polyline" elements as the target, and we are going for fixed positioning. This means we have two basic challenges
  1. Recursion - Nodes have nodes etc etc
  2. Position is based on the position of the previous element - X is based on the depth in the node and Y is based on the Y of the previous element
This is noddy stuff indeed. BUT using the mapping tool (MapForce) I gave up trying to get this bit to work, the schema it was generating was huge and still wasn't doing Recursion at all unless you started writing custom functions. So I decided to bite the bullet and code directly in XSLT, it is a transformation language after-all.

Well in Java, using for example JAX-B, I know I could knock out the code in a couple of minutes as its just about recursion and have a return value from a function to give the current Y position. Now in XSLT you have the concept of variables... but they aren't actually variable you can't change them!. What is required is to declare a variable which calls a function etc etc, this then gets you a variable that is assigned based on a calculation.

Recursion is slightly easier as all you need to do is "call-template" on a previously declared template. This is coding from the good old "C" days but with the worst syntax imaginable. And here is the challenge, if XSLT really is to be a transformation language then it needs to be, in the same way as BPEL, to be an execution language rather than the definition language. So it needs a BPMN or even a standard programming language that is "compiled" down to XSLT.

Any way after a bit of work (a few hours) I got the mapping done, remember this is basically just turning a nested structure into a flat structure and connecting the nodes with lines.


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:svg="http://www.w3.org/2000/svg" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="xs fn">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:variable name="xOffset">4</xsl:variable>
<xsl:variable name="yOffset">2</xsl:variable>
<xsl:variable name="depthCurrent">0</xsl:variable>
<xsl:template name="processNode">
<xsl:param name="depth"/>
<xsl:param name="yRoot"/>
<xsl:variable name="x" select="$depth * $xOffset"/>
<!-- Count the number of previous nodes, add on the depth as the parents are truly preceding as they haven't been closed and create the Y value -->
<xsl:variable name="y" select="(count(preceding::node) + $depth) * $yOffset"/>
<!-- loop through each of the labels, there is only one however -->
<xsl:for-each select="@label">
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="{$x}" y="{$y}">
<xsl:value-of select="."/>
</svg:text>
<!-- Now draw the lines, these run from the parents down to the element, hence the use of Y root -->
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="{$x - (0.75 * $xOffset)}, {$yRoot + (0.2 * $yOffset)}, {$x - (0.75 * $xOffset)}, {$y - (0.4 * $yOffset)}, {$x}, {$y - (0.2 * $yOffset)} "/>
</xsl:for-each>
<!-- for each of the nodes in this one repeat the process passing an increased depth to the child -->
<xsl:for-each select="node">
<xsl:call-template name="processNode">
<xsl:with-param name="depth">
<xsl:value-of select="$depth + 1"/>
</xsl:with-param>
<xsl:with-param name="yRoot">
<xsl:value-of select="$y"/>
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="/node">
<svg:svg xmlns:svg="http://www.w3.org/2000/svg">
<svg:g transform="translate(10,10) scale(10)">
<xsl:call-template name="processNode">
<xsl:with-param name="depth">0</xsl:with-param>
<xsl:with-param name="yRoot">0</xsl:with-param>
</xsl:call-template>
</svg:g>
</svg:svg>
</xsl:template>
</xsl:stylesheet>



Which gives us


<?xml version="1.0" encoding="UTF-8"?>
<svg:svg xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:svg="http://www.w3.org/2000/svg">
<svg:g transform="translate(10,10) scale(10)">
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="0" y="0">top</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="-3, 0.4, -3, -0.8, 0, -0.4 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="4" y="2">business servicse</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="1, 0.4, 1, 1.2, 4, 1.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="8" y="4">first service</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="5, 2.4, 5, 3.2, 8, 3.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="8" y="6">second service</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="5, 2.4, 5, 5.2, 8, 5.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="4" y="8">crm services</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="1, 0.4, 1, 7.2, 4, 7.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="4" y="10">support services</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="1, 0.4, 1, 9.2, 4, 9.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="8" y="12">hear no evil</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="5, 10.4, 5, 11.2, 8, 11.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="8" y="14">see no evil</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="5, 10.4, 5, 13.2, 8, 13.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="8" y="16">speak no evil</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="5, 10.4, 5, 15.2, 8, 15.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="8" y="18">monkey</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="5, 10.4, 5, 17.2, 8, 17.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="12" y="20">see</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="9, 18.4, 9, 19.2, 12, 19.6 " />
<svg:text style="text-anchor: left; font-size: 1" text-anchor="start" font-size="1" x="12" y="22">do</svg:text>
<svg:polyline fill="none" stroke="blue" stroke-width="0.2" points="9, 18.4, 9, 21.2, 12, 21.6 " />
</svg:g>
</svg:svg>


Now its pretty compact and it gets the job done, but this was hand-coded as the visual tools were more complex that writing it myself... this is worrying as more people try and do XSLT for ESBs and the like in that type of visual tool, they aren't liable to be creating the most efficient, or indeed the most complete, transformations.

I wasn't too worried about these transformation engines relying on XSLT and the visual mappers, after all how often do you have to map a hierachy into a flat structure? I can see quite a few times where this would crop up, for instance creating loading information for instance when you want to bulk load into a database.

This then got me thinking about even greater complexities that occur and how will XSLT mappers help people, or are we all going to end up coding in XSLT? I hope not as XSLT is a plain RUBBISH programming language. It would be good to create a set of mapping challenges that will push the latest technologies to the max and see if they help or hinder the creation of decent mappings.

XSLT programming is like coding through mud, its not a rewarding experience.


For those interested the final image (as a png so everyone can see it) is

1 comment:

Anonymous said...

The trick is to realise XSLT is a functional language, so stop thinking in terms of procedural or OO. Think in terms of Scheme or Haskell: those languages can be very efficient. (SM)