Choosing Lighting Parameters with XSLT

I was thinking over the SVG lighting examples that I presented over the weekend and I wanted a clean way to produce something more complex that utilizes the lighting effect that I came up with. Examples are easiest to learn from when the information can be presented in small chunks. I think that logic carries through to code analysis. If there's no good reason to make big long functions then I stick to what fits on my screen. That's very developer-centric of me, but I am a developer.

So the way that I know to keep things small when it comes to XML is XSLT. That might sound backwards since XSLT and XML can be very verbose, but what I mean is that units can be independently analyzed. Large and complicated functionality can be elegantly composed from many small blocks. Chunks of code that I've produced with XSLT tend to be quite digestable - so far.

Using XSLT lets me define my own XML variant quickly and relatively easily. I didn't write a DTD or Schema for this, so for now my source can't be validated. The shortcut I take works well for self-contained samples like this. I just write an XML file making up tags and attributes as I think of them, then I make some XSLT that will parse that file. It's worked out great for me so far.

Inception

I decided I'd just scatter some shapes around and specify lights for them. Here's the XML that I came up with. Click here for the unformatted version. A gnomon is the part of a sundial that casts a shadow. I just wanted a distinctive term, no sundial here yet. Each gnomon is specified as

<gnomon colour = "red" azimuth = "170" x = "3.5" y = "2.5"/>

All I did was make up attribute names for a colour, position (x and y) and a lighting angle (I misappropriated 'azimuth' for this). Without any kind of document definition or schema, the meaning of these attributes is totally up to the XSLT that I apply to it. Click here to read the XSLT (or here for an unformatted version).

I'd like to start by analyzing a skeleton of the XSLT:

<xsl:stylesheet version = "2.0">
    <xsl:output method = "xml" ...
/>
...
    <xsl:template match = "/gnomons">
gnomons template body
    </xsl:template>
...
    <xsl:template match = "gnomon">
gnomon template body
    </xsl:template>
</xsl:stylesheet>

I've got a template in the XSLT that matches each of two tags that I've used in the XML document. The templates don't have names, they just have a match attribute that specifies the pattern they'll be applied to. The way I've written it "gnomons" will only be matched as the document root element. That's the element that contains the rest of the document. That's what the XPath statement "/gnomons" means. The other template is the one that matches the gnomon items themselves. This simple case is analogous to many others that could be handled with XML/XSLT. I'm transforming a collection of things described in an XML document with unique output for the collection as a whole and output for each item in the collection. There's a very simple design pattern that could be distilled from this I'm sure. The output for the entire collection (gnomons) is the skeleton of the SVG image that I want to produce. Compare the template to the output.

The Collection Input and Output

Here's the body of the template for the unique gnomons entity:

 <xsl:template match = "/gnomons">
        <svg viewBox = "0 0 10 10">
            <defs>
                <g id = "shape" transform = "scale(2)">
                    <path d = "M 0,-.2 l .4,0 a .1,0.09 0 0 1 .1,.1 l -.1,.3 a 0.13,0.13 0 0 1 -.1,.1 l -.2,0 a 0.13,0.13 0 0 1 -.1,-.1 l -.1,-.3 a .1,0.09 0 0 1 .1,-.1 "/>
                </g>
            </defs>
            <g>
                <xsl:apply-templates select = "gnomon"/>
            </g>
        </svg>
    </xsl:template>

And here's the SVG that it will produce:

<svg viewBox = "0 0 10 10" preserveAspectRatio = "xMidYMid meet" zoomAndPan = "magnify" version = "1.1" contentScriptType = "text/ecmascript" contentStyleType = "text/css">
    <defs>
        <g id = "shape" transform = "scale(2)">
            <path d = "M 0,-.2 l .4,0 a .1,0.09 0 0 1 .1,.1 l -.1,.3 a 0.13,0.13 0 0 1 -.1,.1 l -.2,0 a 0.13,0.13 0 0 1 -.1,-.1 l -.1,-.3 a .1,0.09 0 0 1 .1,-.1 "/>
        </g>
    </defs>
    <g>
        <g>
...bunch of shapes and radialGradients and stuff...
        </g>
    </g>
</svg>

The most obvious difference is the part where the XSLT template says <xsl:apply-templates select = "gnomon"/>, the SVG output document has all the code for the individual gnomon items. That's because the apply-templates line says to go out and look for gnomon elements and apply any templates that make sense to them. The other item of note has to do with namespaces. Namespaces can be a major source of confusion in XML. The xmlns attributes at the top of any XML document are generally the way namespaces are made accessible to the document. There are a number of other ways to handle scoping as well. In this XSLT document I've made SVG the default namespace by declaring it with no prefix. That's why every element in the XSLT document that refers to SVG has no prefix but the XSLT elements have the prefix xsl. I've bound xsl to the XSLT namespace.

A Single Input and Output

This is pretty basic XSLT stuff so far. The other template is prety basic too, but a little more interesting. Here's what it looks like:

<xsl:template match = "gnomon">
        <xsl:variable name = "shape-colour" select = "@colour"/>
        <xsl:variable name = "shape-x" select = "@x"/>
        <xsl:variable name = "shape-y" select = "@y"/>
        <xsl:variable name = "light-rotation" select = "@azimuth"/>
        <g>
            <radialGradient id = "lightingBase{position()}" cx = "50%" cy = "50%" fx = "50%" fy = "15%" r = "75%"/>
            <radialGradient id = "light{position()}" xlink:href = "#lightingBase{position()}" gradientTransform = "rotate({$light-rotation+180},0.5,0.5)">
                <stop stop-color = "rgb(255,255,255)" stop-opacity = ".7" offset = "0%"/>
                <stop stop-color = "rgb(255,255,255)" stop-opacity = ".3" offset = "85%"/>
                <stop stop-color = "rgb(255,255,255)" stop-opacity = "0" offset = "100%"/>
            </radialGradient>
            <radialGradient id = "shade{position()}" xlink:href = "#lightingBase{position()}" gradientTransform = "rotate({$light-rotation},0.5,0.5)">
                <stop stop-color = "rgb(0,0,0)" stop-opacity = "1" offset = "0%"/>
                <stop stop-color = "rgb(0,0,0)" stop-opacity = ".8" offset = "85%"/>
                <stop stop-color = "rgb(0,0,0)" stop-opacity = "0" offset = "100%"/>
            </radialGradient>
            <use xlink:href = "#shape" x = "{$shape-x}" y = "{$shape-y}" fill = "{$shape-colour}"/>
            <use xlink:href = "#shape" x = "{$shape-x}" y = "{$shape-y}" fill = "url(#shade{position()})" opacity = "0.3"/>
            <use xlink:href = "#shape" x = "{$shape-x}" y = "{$shape-y}" fill = "url(#light{position()})"/>
        </g>
    </xsl:template>

For the gnomon defined by

    <gnomon colour = "violet" azimuth = "100" x = "1.5" y = "3.5"/>

Here's the corresponding output in the SVG document:

<radialGradient id = "lightingBase10" cx = "50%" cy = "50%" fx = "50%" fy = "15%" r = "75%" xlink:type = "simple" xlink:show = "other" xlink:actuate = "onLoad"/>
            <radialGradient id = "light10" xlink:href = "#lightingBase10" gradientTransform = "rotate(280,0.5,0.5)" xlink:type = "simple" xlink:show = "other" xlink:actuate = "onLoad">
                <stop stop-color = "rgb(255,255,255)" stop-opacity = ".7" offset = "0%"/>
                <stop stop-color = "rgb(255,255,255)" stop-opacity = ".3" offset = "85%"/>
                <stop stop-color = "rgb(255,255,255)" stop-opacity = "0" offset = "100%"/>
            </radialGradient>
            <radialGradient id = "shade10" xlink:href = "#lightingBase10" gradientTransform = "rotate(100,0.5,0.5)" xlink:type = "simple" xlink:show = "other" xlink:actuate = "onLoad">
                <stop stop-color = "rgb(0,0,0)" stop-opacity = "1" offset = "0%"/>
                <stop stop-color = "rgb(0,0,0)" stop-opacity = ".8" offset = "85%"/>
                <stop stop-color = "rgb(0,0,0)" stop-opacity = "0" offset = "100%"/>
            </radialGradient>
            <use xlink:href = "#shape" x = "1.5" y = "3.5" fill = "violet" xlink:type = "simple" xlink:show = "embed" xlink:actuate = "onLoad"/>
            <use xlink:href = "#shape" x = "1.5" y = "3.5" fill = "url(#shade10)" opacity = "0.3" xlink:type = "simple" xlink:show = "embed" xlink:actuate = "onLoad"/>
            <use xlink:href = "#shape" x = "1.5" y = "3.5" fill = "url(#light10)" xlink:type = "simple" xlink:show = "embed" xlink:actuate = "onLoad"/>

Some More Explanation

I start out the preceding template by defining four variables: shape-colour, shape-x, shape-y and light-rotation. All I do for each is assign it the value of an attribute on the gnomon. I could have just as easily used the attributes directly throughout the template, but I like the ability to decouple the attributes from the places they're referenced. A benefit of this is the ability to modify the template later to perform pre-processing on the attributes. For example, I could do a coordinate transform on x and y or change the azimuth attribute so that it makes more sense.

The rest of this template looks a lot like the SVG code from my earlier fake lighting examples. I've rearranged that code a little bit and parameterized it so that I can substitute the values from the gnomon tag in different places in the SVG code. This will still make a big SVG document for the end user to parse, but I've already done what I can to simplify the SVG that I made the template out of. If I want to try out different arrangements of the code, this smaller template is much easier to maintain than the large output SVG document.

I've used {position()} to add a unique identifier to all the id attributes in the output. The position() function produces a cardinal number that indicates the order of the node being processed relative to its siblings. The value is just tacked on as text at the end of whatever text entity I call it in. The variables I defined earlier are used in attribute values like "rotate({$light-rotation+180},0.5,0.5)". With XSLT the value of light-rotation will be substituted and the math (the +180) will be evaluated to yield the final output for the SVG document. This can be confusing since XSLT doesn't know anything about the rotate() function but it does know to work out the math given. I can't describe the exact semantics of this yet, but the curly braces tell XSLT to evaluate their contents in a text literal. This is remeniscent of a feature that I don't like in PHP. If you have trouble, just experiment and curse frequently - that works for me. Well, that and a CDATA element might help you out.

The concepts I've explained are used repeatedly through the transformation document, so the rest should be straightforward to analyze. To perform the transform with saxon, I use the command:

java -jar "C:\Program Files\saxon\saxon8.jar" -o gnomons1.svg gnomons1.xml gnomons1.xslt

Epilogue

While working on this example, I hoped that I'd be able to do something interesting with the azimuth argument in order to come up with new focal points (fx,fy) for each gnomon. I found that unfortunately XSLT is sorely lacking in support for the trigonometry that I'd need. There are scant few math functions available to someone that would like to do a little number crunching for visualization. There is an extension mechanism for XSLT and there are some extensions that have been built to meet this need. That seems like more work and a more complex development environment to me, but I suppose that I'll eventually go that route.

I wonder if E4X is going to help the extension situation. If I could use a little script in my XSLT, I could easily get the values that I need. That seems to be the approach taken with the EXSLT extensions. I did try these out, but the functions that I wanted to use from the library aren't supported by Saxon. Other options seem to depend on the parser as well, though I haven't done any huge research into it.

The Files

In case you missed it earlier, here are the files for this example:

The Output

Raster version of the SVG output

0
Your rating: None

Nice article. I breezed through the XSLT because it scares me and my attentions are elsewhere lately. My preference: Do transformations in JavaScript. For anything interactive you'll need to anyway (unless I totally missed something here). Download Sarissa and walk the XML DOM, mangling into SVG DOM elements as you need. Set up event handlers that change the SVG for whatever events you want, etc. It may not be as cool-sounding or as readable (ha!) as XSLT, but it can do anything you want, including complex math.

The problem with that is that it's client-side. I want to generate a static document here and that should be possible without leaving any of the work until the client end. I've done the XML transforms with Javascript before (without Sarissa), and I want to explain that in dynamic examples in the future, but that's not the intent here.

[...] I want to make another simple example of XSLT and SVG today. So I&#8217;m going to do another XML document and transformation. The mechanism is similar to my last example, but I&#8217;m not using any of the same SVG. Today, instead, I&#8217;m going to show an example with a textPath for making some wavy prose. It also happens that the Mozilla team just landed support for textPath last night for Firefox trunk builds. I developed today&#8217;s example in Internet Explorer with Adobe SVG Viewer 6 preview, but I&#8217;ll be testing later with Firefox. If you want to use the latest trunk build of Firefox for testing, get it here. [...]

I must admit that XSLT is this what i need to learn. Here is nice tutorial http://www.w3schools.com/xsl/. Have you ever tryed Xalan for transformations?

No, I haven't gotten around to Xalan, but I've really been meaning to. I think it might be more suitable than Saxon for some of the projects that I want to work on now.