Search

While developing websites, I (and perhaps you too), come accross some nice XSLT-tricks and features that will open a whole new aspect on the approach of XSLT. In this topic, I want to share some of these tips with you. If you know any good tips yourself that are not listed (yet) in this topic, feel free to share them with us! Who knows this might result in some nice article some day ;-)

Here we go...

Using 'modulo' to create a zebra table

A zebra-table is many times done with JavaScript when the page is already rendered, but you can also do this in XSLT:

<table>
    <tr>
        <th>Name</th>
        <th>E-mail address</th>
        <th>Telephone number</th>
    </tr>
    <xsl:for-each select="contacts/entry">
        <tr>
            <xsl:attribute name="class">
                <xsl:choose>
                <!-- This is where the magic happens: -->
                    <xsl:when test="position() mod 2 = 0">even</xsl:when>
                    <xsl:when test="position() mod 2 = 1">odd</xsl:when>
                </xsl:choose>
            </xsl:attribute>
            <!-- Show the table data: -->
            <td>...</td>
            <td>...</td>
            <td>...</td>
        </tr>
    </xsl:for-each>
</table>

Using 'modulo' to determine the last item in a row

Imagine: you have a products-page where the products are shown in a grid of 4 items for each row.

The products-section is 475px wide and each product is shown in a square of 100px width and 100px height, and in css a float: left, and a margin-right: 25px; margin-bottom: 25px;. As you do the quick math, you'll see that a products bounding box will be 125 × 125 pixels. This would mean that 4 products on a row will be 500px wide. The result will be that only 3 products on a row will be show, instead of 4.

You can solve this by giving each 4th element a margin-right of 0px. Just as in our previous example, we can use a modulo for this:

<div id="products">
    <xsl:for-each select="products/entry">
        <div>
            <!-- Do the magic: -->
            <xsl:if test="position() mod 4 = 0">
                <xsl:attribute name="class">last</xsl:attribute>
            </xsl:if>
        </div>
        <!-- Show the product: -->
        ...
    </xsl:for-each>
</div>

Calculating the corresponding width or height of a JIT scaled image

As most of you know, when you enter the value 0 (zero) as a width or height for JIT, the corresponding width or height gets automaticly calculated. But sometimes you need to have those width- and height-attributes to be filled in correctly. Especially if you wish to validate your page as strict XHTML 1.0.

When you get your image from a upload-field it provides the original width- and height. Using some magical math you can calculate the corresponding counter-values with this data:

XML:

<image size="22 KB" path="/uploads/portfolio/images" type="image/jpeg">
    <filename>my_image.jpg</filename>
    <meta creation="2010-09-28T12:26:18+02:00" width="626" height="202" />
</image>

XSLT:

<!-- To calculate the correct height: -->
<img src="/image/2/100/0/5{image/@path}/{image/filename}" width="100">
    <!-- Calculate the corresponding height-attribute: -->
    <xsl:attribute name="height">
        <xsl:value-of select="round(image/meta/@height * (100 div image/meta/@width))" />
    </xsl:attribute>
</img>
<!-- To calculate the correct width: -->
<img src="/image/2/0/100/5{image/@path}/{image/filename}" height="100">
    <!-- Calculate the corresponding width-attribute: -->
    <xsl:attribute name="width">
        <xsl:value-of select="round(image/meta/@width * (100 div image/meta/@height))" />
    </xsl:attribute>
</img>

Comparing two nodesets with eachother

Sometimes you have two sections who share the same options. For example: Organisations sell different kinds of fruit, and customers like different types of fruit. So you want to show the organisations based on the fruit the customer selects as favourite. This can be done with:

<xsl:for-each select="organisation/entry[fruit/item/@value = $customer/fruit/item/@value]">
    ...
</xsl:for-each>

Great tips!!!

Just one remark:

But sometimes you need to have those width- and height-attributes to be filled in correctly. Especially if you wish to validate your page as strict XHTML 1.0.

That is not true. In XHTML those width and height attributes are optional.

XHTML those width and height attributes are optional.

because XHTML expects you to try and do sizing and styling with CSS instead.

With the columns/grid boxes, I rather use this method

div#products{
    margin:0 0 0 -25px;
    width:475px;
}
div#products div{
    float:left;
    margin:0 0 25px 25px;
    width:100px;
    height:100px;
}

It saves having to add classes dependent on their position.

because XHTML expects you to try and do sizing and styling with CSS instead.

:-) That is not entirely true. XHTML does not care for styling. All it expects in this case is that a browser can render an image without knowing its size beforehand.

There's one downside to not specifying width and height in the HTML — some browsers will need to relayout the page after they download enough data from the image to determine the width and height. This can lead to a rather unsightly 'flash' effect.

Funny, just did some math xsl today to set a git image height with some variables.. must confess your solution is slight more elegant.... so thanks!

So awesome ! Thank you !
Was using jQuery for image sizes on http://www.kimberlywarner.com/

Some other goodies (require the HTML Ninja utility):

Convert a[target=_blank] to a[rel=external] with the HTML Ninja utility

<xsl:template match="a[@target='_blank']" mode="html">
    <a>
        <xsl:attribute name="rel">external</xsl:attribute>
        <xsl:apply-templates select="* | @*[not(name()='target')] | text()" mode="html"/>
    </a>
</xsl:template>

Provide an alt-tag for images with no alt-tag:

<xsl:template match="img[not(@alt) or @alt = '']" mode="html">
    <xsl:element name="{name()}">
        <xsl:apply-templates select="* | @* | text()" mode="html"/>
        <xsl:attribute name="alt">
            <xsl:value-of select="concat('Image of ', $website-name)" />
        </xsl:attribute>
    </xsl:element>
</xsl:template>

can also do it this way

<td class="item{position() mod 2}">...

keeps code cleaner and you get .item0 / .item1 for your css styling

just used that treemonkey great tip.

an (almost) complete subnav

i recently had a project where nearly every page had a sidebar containing the parent page's ($root-page's) hierarchy and thought i might share what i found to be a simple way to create it on-the-fly:

<xsl:template name="subnav">
    <nav id="subnav">
        <ul>
            <xsl:apply-templates select="/data/navigation/page[@handle = $root-page]/page" />
        </ul>
    </nav>
</xsl:template>


<xsl:template match="page">
    <li>
        <xsl:attribute name="class">
            <xsl:if test="page">parent</xsl:if>
            <xsl:if test="@handle = $current-page"> active</xsl:if>
        </xsl:attribute>
        <a>
            <xsl:attribute name="href">/<xsl:for-each select="/data/navigation//page[@id=current()/@id]/ancestor::page"><xsl:value-of select="@handle" />/</xsl:for-each><xsl:value-of select="@handle" />/</xsl:attribute>
            <xsl:value-of select="name" />
        </a>
        <xsl:if test="page">
            <ul>
                <xsl:apply-templates select="page" />
            </ul>
        </xsl:if>
    </li>
</xsl:template>

the reason this is in this thread and not presented as a complete xslt utility is because it simply makes an example of the ancestor:: axis as a way to create a dynamic nav.

Using the FOR loop iterator

When using the iterator from @nickdunn :

<xsl:template match="/">
    <xsl:call-template name="iterator">
        <xsl:with-param name="iterations" select="'5'"/>
        <xsl:with-param name="callback" select="'render-checkbox'"/>
    </xsl:call-template>
</xsl:template>

<xsl:template match="render-checkbox" mode="iterator-callback">
    <xsl:param name="position"/>
    <input type="checkbox" name="fields[checkbox-{$position}]"/>
</xsl:template>

Using /data/params/* inside the matched template won't work.

<xsl:template match="render-checkbox" mode="iterator-callback">
    <xsl:param name="position"/>

    <xsl:value-of select="/data/params/workspace" />
    <input type="checkbox" name="fields[checkbox-{$position}]"/>
</xsl:template>

I don't really have any idea why this happends, I hope someone else can explain this.

Temporary solution is to create a global variable and use that instead:

<xsl:variable name="params" select="/data/params" />

<xsl:template match="render-checkbox" mode="iterator-callback">
    <xsl:param name="position"/>

    <xsl:value-of select="$params/workspace" />
    <input type="checkbox" name="fields[checkbox-{$position}]"/>
</xsl:template>

+10 points if you come with a solution to use /data/params/workspace instead :)

Because I'm applying templates on a new nodeset when I call the callback template:

<xsl:apply-templates select="exsl:node-set($callback-node)" mode="iterator-callback">

The original root context (from where you originally call the named iterator template is lost.

Caching your context in a variable does the trick — I do it all the time. It's not a sin ;-)

The original root context (from where you originally call the named iterator template is lost.

Good to know.

Don't know if this is known already but i found out that using a string replace xslt function (like this)
You can search for &#xa;
and replace it by &lt;br/&gt;
and then set disable-output-escaping="yes"
This will change all linebreaks in a textfield to html break tags. This will allow users in the front end to preserve their line breaks without using a text formatter or using the double space with markdown...
Thought i'dd share this with you guys

Regarding search and replace, a way to highlight various portions of text.

Given XML output of a classic text field:

<p>Lorem ipsum dolor sit amet.</p>
<h2>Lorem ipsum</h2>
<p>Curabitur sodales ligula in libero.</p>

To wrap each a letter in a <span class="search-result">a</span>, use this:

<xsl:template match="*" mode="replace">
    <xsl:param name="needle" />

    <xsl:element name="{name()}">
        <xsl:apply-templates select="* | @* | text()" mode="replace">
            <xsl:with-param name="needle" select="$needle" />
        </xsl:apply-templates>
    </xsl:element>
</xsl:template>

<xsl:template match="@*" mode="replace">
    <xsl:attribute name="{name()}">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>

<xsl:template match="text()" name="replace" mode="replace">
    <xsl:param name="in" select="." />
    <xsl:param name="needle" />

    <xsl:choose>
        <xsl:when test="contains($in, $needle)">
            <xsl:value-of select="substring-before($in, $needle)" />

            <span class="search-result">
                <xsl:value-of select="$needle" />
            </span>

            <xsl:call-template name="replace">
                <xsl:with-param name="in" select="substring-after($in, $needle)" />
                <xsl:with-param name="needle" select="$needle" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$in" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Called with this:

<xsl:apply-templates select="path/to/node/*" mode="replace">
    <xsl:with-param name="needle" select="'a'" />
</xsl:template>

The original root context (from where you originally call the named iterator template) is lost.

If only XSLT 2.0 was used more often, which natively supports looping over result node-sets, this behavior would be more known to the general public.

Thanks to Michael Kay and the people from Saxonica, the JAVA community has been using XSLT 2.0 as a standard for many years now. It is such a shame PHP and .Net haven't followed with their own implementations of this improved standard.

It would make our lives much easier because it allows you to do far more complex operations.

Prepending (or appending) to an elements attribute

In this case appending {$root} to the image source attribute, so I can transfer from production to live environment without breaking the links. Using the HTML Ninja utility.

<xsl:template match="img" mode="html">
    <xsl:element name="{name()}">
        <xsl:apply-templates select="* | @* | text()" mode="html"/>
        <xsl:attribute name="src">
            <xsl:value-of select="concat($root,'/', @*)" />
        </xsl:attribute>
    </xsl:element>
</xsl:template>

@Forest: I'd go a level deeper:

<xsl:template match="img/@src" mode="html">
    <xsl:attribute name="{name()}">
        <xsl:value-of select="concat($root,'/', .)" />
    </xsl:attribute>
</xsl:template>

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details