Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Peter Duncanson 430 posts 1360 karma points c-trib
    Nov 22, 2009 @ 21:11
    Peter Duncanson
    0

    Documenting XSLT, any ideas?

    We've got another XSL project with alot of XSL files which I'm hoping to document but not sure how. Quick google around found this:

    http://www.pnp-software.com/XSLTdoc/index.html#DocumentingTheCode

    Which seems on the face of it to be pretty cool although all those extra elements don't really help with the readability of the actual file I was thinking about a xml comment driven system using NaturalDoc or similar.

    Anyone got any ideas/tips?

  • Chris Houston 535 posts 980 karma points MVP admin c-trib
    Nov 22, 2009 @ 23:04
    Chris Houston
    0

    Hi Peter,

    An interesting idea, I think I tend to agree with you about the potential for making the XSL even less readable!

    Currently I just use standard XML comments within my Umbraco XSL files although I have not tried to generate external documentation files from these comments. I guess it depends on the size and complexity of your XSL files. I would be interested to hear how you eventually solve this.

    Cheers,

    Chris

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 8x admin c-trib
    Nov 23, 2009 @ 01:52
    Chriztian Steinmeier
    2

    I've always used XML comments (I don't only use XSLT in Umbraco), and then I just collapse them in the editor (I use TextMate but many editors/IDEs support folding) if they're too lengthy.

    Though not quite the same idea (depends on how you want to document them) you could take a look at XSpec, which is a way to develop XSLT using a BDD-style framework, where you document the behavior of the various templates/functions before you actually code them.

    /Chriztian

  • Peter Duncanson 430 posts 1360 karma points c-trib
    Nov 23, 2009 @ 17:59
    Peter Duncanson
    0

    Xspec is a great find! Thank you Chriztian, will have a more indepth read up on that one later and a bit of a play too.

  • Chris Houston 535 posts 980 karma points MVP admin c-trib
    Nov 23, 2009 @ 18:14
    Chris Houston
    0

    I agree, looks promising. I like the fact the documentation can then be used as unit tests for the XSLT you write, I can imagine an extension for Umbraco that would allow these tests to be run from within the UI, either after each save or from a seperate Test Run button.

    ( although personally I now do all my XSLT development within Visual Studio, so being able to run the tests within VS would be useful. )

    Do keep us updated how you get on with this Peter!

    Cheers,

    Chris

  • Peter Duncanson 430 posts 1360 karma points c-trib
    Nov 25, 2009 @ 19:35
    Peter Duncanson
    0

    Will do Chris, I'm cursed with having too many ideas and toys and not enough time at the minute, oh what I'd give for a week of geeking it up with some beer!

  • Peter Duncanson 430 posts 1360 karma points c-trib
    Nov 28, 2009 @ 22:52
    Peter Duncanson
    0

    I've found XSLDoc (different to XSLTDoc) too which uses XML comments to document your files. Takes some of the noise away as we mentioned but you loose some of the extras that XSLTDoc gives you.

    http://www.bacman.net/apps/XSLdoc/

    One nice feature is that you can generate Docs on the fly via a website: http://www.bacman.net/apps/XSLdoc/XSLdoc.jsp?URL=http://www.bacman.net/apps/XSLdoc/xsl/test.xsl

    Can't say I'm overly impressed with the output though, not exactly easy to read :(

    More to follow...

  • Peter Duncanson 430 posts 1360 karma points c-trib
    Nov 29, 2009 @ 22:30
    Peter Duncanson
    2

    Ok, another update, this might be a long one. XSpec, heres an actual example and a how to.

    Installation:

    • Download XSpec (http://code.google.com/p/xspec/downloads/list) and unzip somewhere handy
    • Ensure you've got Java installed (your on your own on that one, bit beyond the scope)
    • Download Saxon 9 (http://sourceforge.net/projects/saxon/files/Saxon-HE/9.2/saxonhe9-2-0-3j.zip/download) and unzip somewhere handy
    • Open the xspec.bat file from your XSpec folder that you unzipped and change line 3 to point to the .jar file which is in your Saxon folder (eg SET CP=C:\Program Files\Java\Saxon\saxon9he.jar)
    • Open a command prompt and switch directory to your xspec directory
    • Cut and paste the two files listed below (_image.xsl and test_image.xsl) into your XSpec folder
    • Run "xspec test_thumbnail.xsl"

    Job done, this "should" open the results in your browser. Woohoo, welcome to Behaviour Driven Development for XSL!

    Right could of example files to come, first off the original XSL file from one of my Umbraco Macros.This is used to create a thumbnail of any image (using my own .net thumbnailer) pretty simple:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
    <xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
    exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">

    <xsl:output method="xml" omit-xml-declaration="yes"/>

    <xsl:param name="currentPage"/>

    <!-- MACRO PARAMS -->
    <xsl:variable name="mediaID" select="/macro/mediaID"/>
    <xsl:variable name="alt" select="/macro/alt" />
    <xsl:variable name="width" select="/macro/imageWidth" />
    <xsl:variable name="height" select="/macro/imageHeight" />
    <xsl:variable name="style" select="/macro/style" />
    <xsl:variable name="class" select="/macro/class" />
    <xsl:variable name="id" select="/macro/imageID" />

    <xsl:template match="/">
    <xsl:if test="$mediaID != ''">
    <xsl:variable name="media" select="umbraco.library:GetMedia( $mediaID, 'false' )" />
    <xsl:variable name="url" select="$media/data[@alias='umbracoFile']" />
    <img>
    <xsl:attribute name="src">/umbraco/thumbnail.aspx?image=<xsl:value-of select="$url" /><xsl:if test="$width != ''">&amp;width=<xsl:value-of select="$width" /></xsl:if><xsl:if test="$height != ''">&amp;height=<xsl:value-of select="$height" /></xsl:if></xsl:attribute>
    <xsl:if test="$style != ''">
    <xsl:attribute name="style"><xsl:value-of select="$style" /></xsl:attribute>
    </xsl:if>
    <xsl:if test="$class != ''">
    <xsl:attribute name="class"><xsl:value-of select="$class" /></xsl:attribute>
    </xsl:if>
    <xsl:if test="$id != ''">
    <xsl:attribute name="id"><xsl:value-of select="$id" /></xsl:attribute>
    </xsl:if>
    </img>
    </xsl:if>

    </xsl:template>

    Straight away its obvious that this is not really that re-usable or that easy to test. I like to have any handy functions in include files so they are easy to re-use not just between projects but also between macros. So I wanted to move this template around a little, here what I did:

    • I created a new include XSL file called "_Images.xsl" but did not create a matching marco for it. Notice the leading underscore, I've started using this for marking none macro XSL files, it makes them stand out from the others.
    • I modified my existing "RenderImage" XSL file to include my new child XSL file
    • I then cut the bulk of the code out into a original file and moved it to a new template called "Image.RenderByMediaID", this has several params on it which match the variables in the original, they are all optional except MediaID.
    • At this point I spotted that I might want to render a thumbnail from other macros that are not using a media content item, so I created another template called "Image.RenderByUrl" which again excepts all the same params, except it takes a url rather than a media id.
    • "Image.RenderByMediaID" was then changed to use "Image.RenderByUrl" under the hood and pass the url of the media item to the template. Lots of chaining of templates and passing a params. It looks a bit clumsy but its actually really reusable and simple I think

    Ok heres the code after the refactoring:

    Our new "_Image.xsl" include file:

    Save this one as "_image.xsl"

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
    <xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
    exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">

    <xsl:template name="Image.RenderByUrl">
    <xsl:param name="url" select="'noimage.gif'"/>
    <xsl:param name="alt" select="''" />
    <xsl:param name="width" select="''" />
    <xsl:param name="height" select="''" />
    <xsl:param name="style" select="''" />
    <xsl:param name="class" select="''" />
    <xsl:param name="id" select="''" />
    <img>
    <xsl:attribute name="src">/umbraco/thumbnail.aspx?image=<xsl:value-of select="$url" /><xsl:if test="$width != ''">&amp;width=<xsl:value-of select="$width" /></xsl:if><xsl:if test="$height != ''">&amp;height=<xsl:value-of select="$height" /></xsl:if></xsl:attribute>
    <xsl:if test="$style != ''">
    <xsl:attribute name="style"><xsl:value-of select="$style" /></xsl:attribute>
    </xsl:if>
    <xsl:if test="$class != ''">
    <xsl:attribute name="class"><xsl:value-of select="$class" /></xsl:attribute>
    </xsl:if>
    <xsl:if test="$id != ''">
    <xsl:attribute name="id"><xsl:value-of select="$id" /></xsl:attribute>
    </xsl:if>
    </img>
    </xsl:template>

    <xsl:template name="Image.RenderByMediaID">
    <xsl:param name="mediaID" />
    <xsl:param name="alt" />
    <xsl:param name="width" />
    <xsl:param name="height" />
    <xsl:param name="style" />
    <xsl:param name="class" />
    <xsl:param name="id" />

    <li>[<xsl:value-of select="$mediaID" />]</li>

    <xsl:if test="$mediaID != ''">
    <xsl:variable name="media" select="umbraco.library:GetMedia( $mediaID, 'false' )" />
    <xsl:variable name="url" select="$media/data[@alias='umbracoFile']" />
    <xsl:call-template name="Image.RenderByUrl">
    <xsl:with-param name="url" select="$url"/>
    <xsl:with-param name="alt" select="$alt" />
    <xsl:with-param name="width" select="$width" />
    <xsl:with-param name="height" select="$height" />
    <xsl:with-param name="style" select="$style" />
    <xsl:with-param name="class" select="$class" />
    <xsl:with-param name="id" select="$id" />
    </xsl:call-template>
    </xsl:if>
    </xsl:template>

    </xsl:stylesheet>

    And the new RenderImage.xsl file:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
    <xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
    exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">
    <xsl:output method="xml" omit-xml-declaration="yes"/>

    <xsl:include href="_image.xslt" />

    <xsl:param name="currentPage"/>

    <xsl:template match="/">
    <xsl:call-template name="Image.RenderByMediaID">
    <xsl:with-param name="mediaID" select="/macro/mediaID" />
    <xsl:with-param name="alt" select="/macro/alt" />
    <xsl:with-param name="width" select="/macro/imageWidth" />
    <xsl:with-param name="height" select="/macro/imageHeight" />
    <xsl:with-param name="style" select="/macro/style" />
    <xsl:with-param name="class" select="/macro/class" />
    <xsl:with-param name="id" select="/macro/imageID" />
    </xsl:call-template>
    </xsl:template>

    </xsl:stylesheet>

    Notice all the variables have gone, we now just get the values for those direct in the template. By removing the variables from global scope we can also use some sensible variables and param names in the rest of our code. Notice that we use alot of the same variable names, we don't do any of the old "mediaID2", "thisMediaID", "myMediaID" stuff, that just leads to confusion I think, instead we use XSL's built in scoping and stick to it, once we've made that choice we have to stick to it but it makes it easier in the long run, honest :)

    Right, now thats a bit more easy to test. Why? Well I now only really have to test "Image.RenderByUrl" as thats the one thats doing the output. Thats the one that I want to ensure is doing what it should. Everything else just passes params into this template so this is the place to start.

    So for those following along at home (and to make it easy for you) here is a blank XSpec test file (you lucky people I could not find one of these so had to build it together through trial and error:

    <!-- 

    Things you need to do:
    * Read http://code.google.com/p/xspec/wiki/WritingScenarios
    * Change stylesheet in the x:description element to the relative name of the XSL file you want to test
    * Add x:scenario elements for each test you want to do
    * The x:expect element can contain your expected XML/HTML output

    XSpec can do alot more but this will do, seriously go read that link above

    -->
    <x:description xslt-version="1.0"
    xmlns:x="http://www.jenitennison.com/xslt/xspec"
    stylesheet="thumbnail.xsl">

    <x:scenario label="When I do this">
    <x:call template="You xsl template name here">
    <x:param name="your_param" select="'your value'" />
    </x:call>
    <x:expect label="it should return this">
    <p>my XML output here</p>
    </x:expect>
    </x:scenario>

    </x:description>

    So time for some tests! Heres the first one, given a url will it render out a img tag with the right url? I created this file and saved it as "test_thumbnail.xsl"

    <x:description xslt-version="1.0" xmlns:x="http://www.jenitennison.com/xslt/xspec" stylesheet="thumbnail.xsl">
    <x:scenario label="When called with just a URL">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src and blank alt">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg" alt="" />
    </x:expect>
    </x:scenario>
    </x:description>

    One gotcha, just like XSL (as it is XSL) I noticed that I had to wrap my test string in single quotes, school boy error but still worth pointing out.

    To run this I simply type "xspec test_thumbnail.xsl" in my command prompt (remember you have to be in the folder you unzipped the xspec into for this to work or put the xspec bat file in your PATH environment variable).

    Ok that one test worked a treat, the result opened in my browser all tested and passed. I need more tests, as I have lot of params in this one:

    Save this one as "test_image.xsl"

    <x:description xslt-version="1.0" xmlns:x="http://www.jenitennison.com/xslt/xspec" stylesheet="_image.xsl">


    <x:scenario label="When called with just a URL">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src and blank alt">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg" alt="" />
    </x:expect>
    </x:scenario>


    <x:scenario label="When called with a URL and alt">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    <x:param name="alt" select="'testing, testing'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src with alt text">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg" alt="testing, testing" />
    </x:expect>
    </x:scenario>


    <x:scenario label="When called with URL and height">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    <x:param name="height" select="'100'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src with height query string param">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg&amp;height=100" alt="" />
    </x:expect>
    </x:scenario>


    <x:scenario label="When called with URL and width">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    <x:param name="width" select="'200'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src with width query string param">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg&amp;width=200" alt="" />
    </x:expect>
    </x:scenario>


    <x:scenario label="When called with URL, width and height">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    <x:param name="width" select="'200'" />
    <x:param name="height" select="'100'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src with height and width query string param">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg&amp;width=200&amp;height=100" alt="" />
    </x:expect>
    </x:scenario>


    <x:scenario label="When called with URL and css class">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    <x:param name="class" select="'test_class'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src and the css class attribute included">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg" alt="" class="test_class" />
    </x:expect>
    </x:scenario>


    <x:scenario label="When called with URL and id">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    <x:param name="id" select="'my_id'" />
    </x:call>
    <x:expect label="it should return an image tag with correct thumbnailer src and the DOM id attribute included">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg" alt="" id="my_id" />
    </x:expect>
    </x:scenario>


    <x:scenario label="When called with all params">
    <x:call template="Image.RenderByUrl">
    <x:param name="url" select="'test.jpg'" />
    <x:param name="class" select="'test_class'" />
    <x:param name="id" select="'my_id'" />
    <x:param name="alt" select="'testing, testing'" />
    <x:param name="width" select="'200'" />
    <x:param name="height" select="'100'" />
    </x:call>
    <x:expect label="it should return a perfect image tag!">
    <img src="/umbraco/thumbnail.aspx?image=test.jpg&amp;width=200&amp;height=100" alt="testing, testing" class="test_class" id="my_id" />
    </x:expect>
    </x:scenario>


    </x:description>

    An running this lot I found an error! I never render out the alt attribute! I might not have spotted that without my tests, so I added to my Image.RenderByUrl template:

           <xsl:attribute name="alt"><xsl:value-of select="$alt" /></xsl:attribute>

    So how did that feel? Well a bit long winded if honest as it was an up hill slog to get it all setup and running. But now I've been through the pain (and documented it here so you don't have to) I really liked it. Forced me to re-think my code and I'm looking forward to creating more test for more of my XSL (not just Umbraco but others too). I quite like it and with some cut and pasting creating the additional test for my image template was actually pretty swift.

    Whats next, more testing and I need away of getting this wired up into my Cruise Control build server. Also there is a doc about how to get xspec working in Oxygen XML editor, it should be possible to wire it up to Visual Studio and maybe Umbraco itself? Bit out my hands those last bits though :)

    Hope that helps and sorry for the length

    Pete

     

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 8x admin c-trib
    Nov 29, 2009 @ 22:55
    Chriztian Steinmeier
    0

    Hi Peter,

    Great writeup! I've found XSpec very good for refactoring (as you just showed here) and for times when someone needs "this XML format transformed to that format" - because you can chuck along one test at a time, until you reach "all green".

    I've had a few gotchas with the currentPage parameter that I'm hoping to document here (or in the wiki) when time permits...

    Thanks again for taking the time to write this up.

    /Chriztian

     

  • Chris Houston 535 posts 980 karma points MVP admin c-trib
    Nov 29, 2009 @ 23:05
    Chris Houston
    1

    Hi Pete,

    That was a great post, thanks for the very comprehensive update!

    I, like you, have too many things on, and not enough time to play with the toys or do the fun coding I'd like to do, some how paid for work always seems to have to take priority, roll on 2010 for a few changes of priorities :)

    It would be nice to port this to run using .NET and the MS XSLT parser rather than Saxon, then from an Umbraco point of view we can be sure it's definitely going to work within Umbraco and we don't get tempted to add XSLT 2 code that will fail if you try and run it within Umbraco.

    This would also reduce the number of dependancy's and probably make it easier to integrate with VS and maybe within Umbraco?!

    Now there's a challenge for the Christmas hols ;-)

    Cheers,

    Chris

     

  • Peter Duncanson 430 posts 1360 karma points c-trib
    Nov 30, 2009 @ 11:30
    Peter Duncanson
    0

    Are you taking that challenge on then Chris? :)

    Next step is to document my two new templates using both of the documenting tools (XSLDoc and XSLTDoc) and see which one I like the most. I'll post details of that up here too. Might convert these posts into Books as well.

    Finally, I'll try to read through my posts more before I post (TopGear was on so was in a rush!)

    Cheers

    Pete

  • Peter Duncanson 430 posts 1360 karma points c-trib
    Nov 30, 2009 @ 11:39
    Peter Duncanson
    0

    Did some quick digging into why it uses Saxon. It uses alot of XSL2 code to generate all the tests and Saxon (correct me if wrong) is the only parser that will handle XSL2 with there being nothing similar natively in .net

    The good news is there is a .net version of Saxon which you could include to remove the need for the Java maybe?

    Having XSL2 in Umbraco could be fun though :)

    You can set which version of XSL you are using in the <x:description /> element (I set it to version 1 in my demos above as it defaults to version 2) this stops it allowing you to use anything from XSL2 in your templates, so should be safe?

    Hope that helps

    Pete

Please Sign in or register to post replies

Write your reply to:

Draft