We all know and love the Ninja Technique with XSLT, thanks to Allen Chang. It's saved me loads of time and effort over the years when outputting html content, applying classes to nodes etc.

With Nils' and Johanna's xslt kit on github, the Ninja Tehnique became a little cooler, as there was an actual useful template available which allowed us to adjust headline depth etc, in case our clients got a little headline happy with the content.

In this post, I want to take the Ninja technique to another level, and explain how I've got around outputting images in content without having to deal with a WYSIWYG editor.

To do this, we will be creating a couple of sections in Symphony, and adding to our xslt skills.

Wax On, Wax Off

To start we need to think about how we attach images to our entries in Symphony. I always advocate doing it the way the core expects, which is to have an entries section, like Posts for example, and another section for images, called Post Images for example. The Post Images will have a Select Box Link field pointing to the Posts section.

In the Post Images section we will need a field for the Image, a field for the Alternative Text (you should always have one and make it required), this will be the field that is our default in the entries index table, so should be first in the list. We will also need a field for an Image Tag. The image tag is how we will reference the image in our content within the Posts section.

Images uploaded will need tagging with image-1 or image-2 etc.

In our Posts section, we can now (in our main content field, using markdown syntax) add a new paragraph for each image we want to output. A you can imagine, we will just need the paragrah content to be image-1 or whatever number we have tagged each image with.

For a more advanced approach, which I am explaining later in the xslt, I also allow the editor to apply classes to each image, so they can align images left or right for example. I do this by using the tag of {image-1:left}, and if they really want, they can add more classes; {image-1:left more classes}.

Crouching Tiger, Hidden Dragon

Now comes the fun bit, we get into our xslt templates. Download Nils' and Johanna's xslt template ninja.xsl, and open it in your editor. We're going to add a new template that matches paragraphs that contain image tags, so we can deal with them separaltey to other normal paragraph tags. To do this we need to make use of xslt functions, specifically starts-with. We need to match our image tag, so we can do the following in our template's match statement starts-with(., '{image'). We mustn't forget tthe ninja mode either.

<xsl:template match="starts-with(., '{image')" mode="ninja">
    
</xsl:template>

Once we're insode our template, we need to break our tag apart to get the image tag, and any classes we need. Using functions again makes this very easy to do. We can make variables of the two separations to allow us to easily use them in references and output.

The tag variable needs to check whether there is a class being appended, as the tag can have classes or not; {image-1} or {image-1:left}. To do that we choose what the tag contains, by checking it with the contains function, and all we're looking for is the : by simply doing contains(., ':'). Then within a when or otherwise we do our parsing of the string to pull out the bits we want using substring-before and substring-after. Lets look at athat variable...

<xsl:variable name="tag">
    <xsl:choose>
        <xsl:when test="contains(., ':')">
            <xsl:value-of select="substring-before(substring-after(., '{'), ':')"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="substring-before(substring-after(., '{'), '}')"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>

For our class variable, we will be using any passed in classes, and also the tag. We again want to check for the : in out string so we will do something very similar to the above.

<xsl:variable name="class">
    <xsl:choose>
        <xsl:when test="contains(./text(), ':')">
            <xsl:value-of select="concat($tag, ' ', substring-before(substring-after(./text(), ':'), '}'))"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$tag"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>

Once we have these, we can match our actual image node from a datasource we output.

<xsl:variable name="media" select="/data/*/entry[contains(image-tag, $tag)]"/>

The above variable looks for any datasource that contains an entry which in turn contains a node called image-tag whose content matches our $tag variable. Couldn't be simpler.

We can now move on to outputting an image. I always opt for wrapping images in figure elements for semantics, but each to their own. I also use another intermediary template here to allow me to do image source swapping for responsive and retina layouts, but that's another article all together. For now I'll just show you the basics.

<figure>
    <xsl:attribute name="class">
        <xsl:value-of select="$class"/>
    </xsl:attribute>
    
    <img src="{$workspace}/{$media/image/@path}{$media/image/filename}" alt="{$media/alternative-text}"/>
</figure>

Now that's all done, you can simply call the content in your output the same way you always have with the Ninja Technique;

<xsl:apply-templates match="your-datasource-entry/content-node/*" mode="ninja"/>

Enter the Ninja

Putting the above together looks like this:

<xsl:template match="starts-with(., '{image')" mode="ninja">

    <xsl:variable name="tag">
        <xsl:choose>
            <xsl:when test="contains(., ':')">
                <xsl:value-of select="substring-before(substring-after(., '{'), ':')"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="substring-before(substring-after(., '{'), '}')"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>

    <xsl:variable name="class">
        <xsl:choose>
            <xsl:when test="contains(./text(), ':')">
                <xsl:value-of select="concat($tag, ' ', substring-before(substring-after(./text(), ':'), '}'))"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$tag"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>

    <xsl:variable name="media" select="/data/*/entry[contains(image-tag, $tag)]"/>

    <figure>
        <xsl:attribute name="class">
            <xsl:value-of select="$class"/>
        </xsl:attribute>
        
        <img src="{$workspace}/{$media/image/@path}{$media/image/filename}" alt="{$media/alternative-text}"/>
    </figure>

</xsl:template>

Now you've earned some xslt chops, go buy a throwing star or something.


Thanks to Nils for helping come up with this technique.

Pro Tip: Extend this even more with videos and files!