Search

So I have a bit of a problem that has kept me stumped for some time. I know how I would handle this in PHP or Javascript, but the whole not-being-able-to-change-a-variable thing in XSLT is rather confusing.

I’m writing an application that will allow the people at my company to see how tasked a particular team member is. This is determined by finding the tasks that are assigned to that person and the complexity of those tasks then displaying them visually in their status bar (kind of like a video game).

If it’s a simple task, display one div, moderate task = 2 divs, complex = 3 divs.

What I would like is for the first 6 divs to have one class, 6-8 to have another class and 8-10 to have a third class. How can I accomplish that?

Feel free to correct any inefficiencies in my code. I’m still learning and this is my first real Symphony project.

<xsl:for-each select="/data/tasks/entry">
    <xsl:if test="$name = assigned-to/item">
        <xsl:variable name="dif" select="difficulty/item"/>
        <xsl:choose>
            <xsl:when test="$dif = 'Simple'">
                <xsl:call-template name="blocks">
                  <xsl:with-param name="count" select="1"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="$dif = 'Moderate'">
                <xsl:call-template name="blocks">
                  <xsl:with-param name="count" select="2"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="$dif = 'Complex'">
                <xsl:call-template name="blocks">
                  <xsl:with-param name="count" select="3"/>
                </xsl:call-template>
            </xsl:when>
        </xsl:choose>
    </xsl:if>
</xsl:for-each>


<xsl:template name="blocks">

    <xsl:param name="count" select="1"/>

    <xsl:if test="$count > 0">
      <div class="block"></div>
      <xsl:call-template name="blocks">
        <xsl:with-param name="count" select="$count - 1"/>
      </xsl:call-template>
    </xsl:if>

</xsl:template>

Firstly on refactoring your existing code. Below is a version that utilises rule-based templating rather than a computational one:

<xsl:template match="data">
    <xsl:apply-templates select="tasks/entry[$name = assigned-to/item]"/>
</xsl:template>

<xsl:template match="tasks/entry">
    <xsl:apply-templates select="difficulty/item"/>
</xsl:template>

<xsl:template match="difficulty/item[. = 'Simple']">
    <div class="block"></div>   
</xsl:template>

<xsl:template match="difficulty/item[. = 'Moderate']">
    <div class="block"></div>   
    <div class="block"></div>
</xsl:template>

<xsl:template match="difficulty/item[. = 'Complex']">
    <div class="block"></div>   
    <div class="block"></div>
    <div class="block"></div>
</xsl:template>

The above code functions similarly to CSS styling rules. i.e. “if a template matches a node-set of difficulty/item where the item value is ‘Complex’, output 3 div blocks”.

Concerning your requirement of needing to output different classes based on the grouping of your div elements, while it’s possible to do this without using exsl:node-set(), to simplify the transition from the re-factored code across, it’s simpler to use it:

<xsl:template match="data">
    <xsl:apply-templates select="tasks/entry[$name = assigned-to/item]"/>
</xsl:template>

<xsl:template match="tasks/entry">
    <xsl:variable name="tree">
        <tree>
            <xsl:apply-templates select="difficulty/item"/>
        </tree>
    </xsl:variable>

    <xsl:for-each select="exsl:node-set($tree)/tree/div">
        <xsl:variable name="style">
            <xsl:choose>
                <xsl:when test="position() &lt; 6">green</xsl:when>
                <xsl:when test="position() &gt; 5 and position() &lt; 9">orange</xsl:when>
                <xsl:when test="position() &gt; 8 and position() &lt; 11">red</xsl:when>
            </xsl:choose>
        </xsl:variable>
        <div class="{$style}"></div>
    </xsl:for-each>
</xsl:template>

<xsl:template match="difficulty/item[. = 'Simple']">
    <div class="block"></div>   
</xsl:template>

<xsl:template match="difficulty/item[. = 'Moderate']">
    <div class="block"></div>   
    <div class="block"></div>
</xsl:template>

<xsl:template match="difficulty/item[. = 'Complex']">
    <div class="block"></div>   
    <div class="block"></div>
    <div class="block"></div>
</xsl:template>

Notice how the only template that is changed is for the tasks/entry match. This stores the output of the original code into a temporary tree. This way, you can go iterate through div elements and apply further classes.

Since I do not know what your rules are with applying the classes and the XML structure of your difficulty/item (i.e. are there multiple items under difficulty? Should the position() logic count across all div elements or just within the difficulty/item node-set?), the internal logic with applying the style may be incorrect.

Thanks so much for your help Allen. I have a lot I need to learn about best coding practices for XSLT.

Although, I’m not sure how to implement the EXSLT extension. I’ve followed the directions to include the namespace but I can’t seem to find which download contains the exsl.node-set.xsl file it’s asking for. What am I doing wrong here?

You don’t need to include any xslt for supported EXSLT extensions as the XSLT processor itself adds the functionality based on the declared namespace. You simply need to add the namespace to the stylesheet and utilise it as described on the EXSLT website. My code example above should work as long as you defined your stylesheet with:

<xsl:stylesheet version="1.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:exsl="http://exslt.org/common"
            extension-element-prefixes="exsl">

This is described here: http://www.exslt.org/exsl/index.html

Thanks, I found the answer to my question shortly before your reply. I’ll give your suggestions a try and see if I can get them working. I appreciate all the help.

I wish I had your brain for this stuff Allen… I’m quite envious at times!

@designermonkey Agreed.

@Allen, I tried implementing your solution but there seems to be one minor problem. It seems that putting the exslt processing within the for-each statement resets the position on each task. Is there a way to have it retain the position throughout all the tasks?

What you need to do is to change the xpath selection for tasks/entry to incorporate a larger set. This is what I meant by not knowing how your rules apply with task items. Essentially, you need to first make sure that the node-set you are storing in your $tree variable contains all the items you need to iterate over. That way, inside your for-each, the context of the position() will be intact.

I’m not sure if I explained that very well.

If you still have trouble, it’s best to provide a sample of the XML and the intended HTML result. That way I can make sure the code I produce will suit the logic you need.

Allen, here is a link to the XML: http://pastie.org/1076322

And an example of the html that should be generated: http://pastie.org/1076385

The first list has the members and how tasked they are. The second list has tasks and who they are assigned to.

Also, what do you think is the best resource for learning more about advanced XSLT? Would you still recommend the resources on this thread or are there other resources you prefer to point beginners to?

Thanks for the source and output. Now seeing your source and how you want the result to look, it made it easier for me to understand the requirement.

Word of warning, the logic is fairly advanced. The problem is more a mathematical challenge than a programming one though.

<?xml version='1.0' encoding='utf-8'?>

<xsl:stylesheet version='1.0'
    xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

<xsl:output method="xml"
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
    omit-xml-declaration="yes"
    encoding="UTF-8"
    indent="yes" />

<xsl:template match="data">
    <ul id="members">
        <xsl:apply-templates select="members/entry"/>
    </ul>
    <ul id="tasks">
        <xsl:apply-templates select="tasks/entry"/>
    </ul>
</xsl:template>

<xsl:template match="members/entry">
    <xsl:variable name="path" select="/data/tasks/entry[assigned-to/item/@id = current()/@id]/difficulty/item"/>

    <!-- Calculation: 1 unit for Simple, 2 units for Medium, 3 units for complex -->
    <xsl:variable name="iterations" select="count($path[. = 'Simple']) + (count($path[. = 'Medium']) * 2) + (count($path[. = 'Complex']) * 3)"/>

    <li class="member">
        <span class="name cell"><xsl:value-of select="name"/></span>
        <div class="tasked cell">
            <xsl:call-template name="complexity-matrix">
                <xsl:with-param name="iterations" select="$iterations"/>
                <xsl:with-param name="days-a-week" select="days-a-week/item"/>
                <xsl:with-param name="format" select="'styled'"/>
            </xsl:call-template>
        </div>
    </li>
</xsl:template>

<xsl:template match="tasks/entry">
    <xsl:variable name="iterations">
        <xsl:choose>
            <xsl:when test="difficulty/item = 'Simple'">1</xsl:when>
            <xsl:when test="difficulty/item = 'Medium'">2</xsl:when>
            <xsl:when test="difficulty/item = 'Complex'">3</xsl:when>
        </xsl:choose>
    </xsl:variable>

    <li class="task">
        <span class="project cell"><xsl:value-of select="project/item"/></span>
        <span class="task-name cell"><xsl:value-of select="name"/></span>
        <span class="priority cell"><xsl:value-of select="priority/item"/></span>
        <span class="assigned-to cell"><xsl:value-of select="assigned-to/item"/></span>
        <span class="complexity cell">
            <xsl:call-template name="complexity-matrix">
                <xsl:with-param name="iterations" select="$iterations"/>
            </xsl:call-template>
        </span>
    </li>
</xsl:template>

<!-- The complexity matrix template can generate blocks with or without 'workload' styling -->
<xsl:template name="complexity-matrix">
    <xsl:param name="iterations" select="0"/>
    <xsl:param name="days-a-week" select="5"/>
    <xsl:param name="inactive" select="(5 - $days-a-week) * 2"/>
    <xsl:param name="format" select="'no-style'"/>
    <xsl:param name="count" select="1"/>
    <xsl:param name="inactive-count" select="$inactive"/>

    <xsl:variable name="style">
        <xsl:choose>
            <xsl:when test="$inactive-count &gt; 0">inactive</xsl:when>
            <xsl:when test="$count &lt; 7">under</xsl:when>
            <xsl:when test="$count &gt; 6 and $count &lt; 9">sweet</xsl:when>
            <xsl:when test="$count &gt; 8">over</xsl:when>
        </xsl:choose>
    </xsl:variable>

    <xsl:choose>
        <xsl:when test="$format = 'styled'">
            <div class="block {$style}"></div>
        </xsl:when>
        <xsl:otherwise>
            <div class="block"></div>
        </xsl:otherwise>
    </xsl:choose>

    <xsl:if test="$count &lt; ($iterations + $inactive)">
        <xsl:call-template name="complexity-matrix">
            <xsl:with-param name="iterations" select="$iterations"/>
            <xsl:with-param name="days-a-week" select="$days-a-week"/>
            <xsl:with-param name="format" select="$format"/>
            <xsl:with-param name="count" select="$count + 1"/>
            <xsl:with-param name="inactive-count" select="$inactive-count - 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

I ended up going with a different method of implementation. Mostly because this approach does not require exslt’s node-set() custom function and as hard as it may seem, it’s actually easier to follow.

Here’s the pastie version

The code can be cleaned up in a few places. Namely, the conversion for difficulty could be combined so that members/entry and tasks/entry don’t calculate the complexity units in separate places.

Also, what do you think is the best resource for learning more about advanced XSLT? Would you still recommend the resources on this thread or are there other resources you prefer to point beginners to?

What has already been suggested is pretty comprehensive. Unfortunately in your case, you stumbled on a rather difficult challenge. If you look at the syntax used in my XSLT code, you won’t see anything that is new in terms of syntax. The logic for generating the div elements however can be a bit mind-bending.

As I mentioned previously, your particular problem has more math than XSLT!

Thank you so much for your help Allen. I rewrote a lot of my code based on your suggestions and got everything working. It figures that the first project I chose to use Symphony on would be way over my head :)

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