Search

Hi,

I’ve ran into a spot of bother with my XML and XPath. I’m trying to display my list of services, and with that my projects that relate to that particular service.

This is my site:

Now as you can see it’s just displaying Recent Projects and nothing within them.

Here is my XSLT that I have written.

 <xsl:key name="service-handle" match="entry" use="service/@handle" />

<!-- Template Matches for Projects & Services main page -->
<xsl:template match="services">
        <xsl:apply-templates select="entry[../section/@handle = 'services'][premium = 'Yes']" />
            <h3>Other Services</h3>
            <xsl:choose>
                <xsl:when test="$service = ''">
                <ul class="stars">
                    <xsl:for-each select="entry[../section/@handle = 'services'][premium = 'No']">
                        <li><a href="{$root}/{$root-page}/{service/@handle}"><xsl:value-of select="service" /></a></li>
                    </xsl:for-each>
                </ul>
                </xsl:when>
                <xsl:otherwise>
                <ul class="stars">
                    <xsl:for-each select="entry[../section/@handle = 'services'][service/@handle != $service]">
                        <li><a href="{$root}/{$root-page}/{service/@handle}"><xsl:value-of select="service" /></a></li>
                    </xsl:for-each>
                </ul>
                </xsl:otherwise>
            </xsl:choose>
</xsl:template>

<xsl:template match="entry[../section/@handle = 'services'][premium = 'Yes']">
    <xsl:choose>
        <xsl:when test="$service = ''">
                <div class="service" id="{service/@handle}">
                    <h2><a href="{$root}/{$root-page}/{service/@handle}"><xsl:value-of select="service" /></a></h2>
                    <div class="grid_4 alpha">
                        <xsl:apply-templates select="shot" />
                    </div>
                    <div class="grid_4">
                        <h3>Recent Projects</h3>

                        <ul class="stars">
                            <xsl:for-each select="/data/projects/entry[service/item/@handle = key('service-handle', service/@handle)]">
                                <li>
                                  <a href="{$root}/projects-services/{project/@handle}/">
                                    <xsl:value-of select="project"/>
                                  </a>
                                </li>
                            </xsl:for-each>
                        </ul>
                    </div>
                    <div class="grid_4 omega description">
                        <xsl:copy-of select="description/*" />
                        <xsl:apply-templates select="partner-logo" />
                    </div>
                </div>
        </xsl:when>
        <xsl:otherwise>
            <xsl:for-each select="service[@handle = $service]">
            <h2><xsl:value-of select="." /></h2>
            <xsl:copy-of select="../description/*" />
            <xsl:apply-templates select="../partner-logo" />
            <xsl:apply-templates select="../shot" />
            </xsl:for-each>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Now I have tried to use the method and try something new and advanced so that I can learn different techniques to display my data, but it seems it’s not displaying what I want it to.

I’m thinking my problem is to do with the context of the node in the key function, but then again I could be doing this the completely wrong way. In my head I know what I want to happen, I want to display the project that’s service matches the service the list is being displayed in.

Anybody who can lend a hand I would be very appreciative and hopefully I can return the favour.

I also tried this as an alternative:

<xsl:for-each select="../../projects/entry[service/item/@handle = ../../service/@handle]">

that didn’t work, but hardcoding in ‘ecommerce’ like so:

<xsl:for-each select="../../projects/entry[service/item/@handle = 'ecommerce']">

does work.

Any ideas what I am missing here with my XPath?

Can you post your full XML please (maybe to gist or pastie).

Sure thing Nick, it’s a bit of a beast. :)

XML

I’m only guessing here since the logic is a little off-base, but I think this is what you’re looking for:

Firstly, the best way to think about xsl:key is to think of them as a way to get an XML snippet (or formally, node-set) of the items that you want, based on a value.

So the question you want to ask is:

Question: What do I want as my result set?

Answer: I want to get a list of projects. /data/projects/entry

Question: What value should I use to pluck the results I want out from my original XML?

Answer: I want to only get projects with a particular type of service. service/item/@handle

With the above answered, you can now construct your xsl:key instruction:

<xsl:key name="service-handle" match="/data/projects/entry" use="service/item/@handle"/>

Next, you need to use the key to iterate through the node-set. It’s best to think of the XML snippet you get back from the xsl:key as any regular XML:

<xsl:for-each select="key('service-handle', service/@handle)">
    ...
</xsl:for-each>

So to clarify, something like: key('service-handle', 'ecommerce') will result in a node-set of your e-commerce project entries.

Putting it together:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="html" version="1.0" encoding="utf-8" indent="no"/>
    <xsl:key name="service-handle" match="/data/projects/entry" use="service/item/@handle"/>
    <xsl:variable name="service" select="''"/>
    <xsl:variable name="root" select="''"/>
    <xsl:variable name="root-page" select="''"/>
    <!-- Template Matches for Projects & Services main page -->
    <xsl:template match="services">
        <xsl:apply-templates select="entry[../section/@handle = 'services'][premium = 'Yes']"/>
        <h3>Other Services</h3>
        <xsl:choose>
            <xsl:when test="$service = ''">
                <ul class="stars">
                    <xsl:for-each select="entry[../section/@handle = 'services'][premium = 'No']">
                        <li>
                            <a href="{$root}/{$root-page}/{service/@handle}">
                                <xsl:value-of select="service"/>
                            </a>
                        </li>
                    </xsl:for-each>
                </ul>
            </xsl:when>
            <xsl:otherwise>
                <ul class="stars">
                    <xsl:for-each select="entry[../section/@handle = 'services'][service/@handle != $service]">
                        <li>
                            <a href="{$root}/{$root-page}/{service/@handle}">
                                <xsl:value-of select="service"/>
                            </a>
                        </li>
                    </xsl:for-each>
                </ul>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="entry[../section/@handle = 'services'][premium = 'Yes']">
        <xsl:choose>
            <xsl:when test="$service = ''">
                <div class="service" id="{service/@handle}">
                    <h2>
                        <a href="{$root}/{$root-page}/{service/@handle}">
                            <xsl:value-of select="service"/>
                        </a>
                    </h2>
                    <div class="grid_4 alpha">
                        <xsl:apply-templates select="shot"/>
                    </div>
                    <div class="grid_4">
                        <h3>Recent Projects</h3>
                        <ul class="stars">
                            <xsl:for-each select="key('service-handle', service/@handle)">
                                <li>
                                    <a href="{$root}/projects-services/{project/@handle}/">
                                        <xsl:value-of select="project"/>
                                    </a>
                                </li>
                            </xsl:for-each>
                        </ul>
                    </div>
                    <div class="grid_4 omega description">
                        <xsl:copy-of select="description/*"/>
                        <xsl:apply-templates select="partner-logo"/>
                    </div>
                </div>
            </xsl:when>
            <xsl:otherwise>
                <xsl:for-each select="service[@handle = $service]">
                    <h2>
                        <xsl:value-of select="."/>
                    </h2>
                    <xsl:copy-of select="../description/*"/>
                    <xsl:apply-templates select="../partner-logo"/>
                    <xsl:apply-templates select="../shot"/>
                </xsl:for-each>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

Note that there are many places where the XSLT code can be more elegantly handled.

Allen,

I see what your doing there. Ok, I will give all that a try.

Hopefully I can understand and learn. I suppose there are many ways to achieve something, it’s just how you approach the problem.

Thanks very much,

Can I ask why you set the Key use as:

service/item/@handle

but when you use the key function you use:

service/@handle

For reference, here’s the repasted XML source. I’ve removed elements that was irrelevant to this discussion: http://pastie.org/1114409.

This is because, xsl:key is referencing the projects element. You can see that the path needs to be (in full): /data/projects/entry/service/item/@handle. This is setting up the project’s “identifier”.

With the key function, it is grabbing the value that sits inside the services entry, the path being (in ful): /data/services/entry/service/@handle. This is telling xsl:key what the identifier’s value should be.

Ok sorry about the delay in replying, been out of the office and unable to sit down and read this. Doing so now.

Right I’m kind of still a little lost here.

if the xsl:key uses /data/projects/entry as it’s match how does that link to the key function if that key function is referencing data/services/entry/service/@handle

I think I need to understand this in the simplest terms. I have been over to Jeni’s XSLT Pages, and tried to understand.

From what I understand the key() function takes 2 arguments, the name which I presume is merely a label and could be anything, the 2nd being the value which in this case is:

service/@handle

So I don’t understand what the match argument does in the xsl:key declaration? Because I am now presuming that service/@handle is in relation to the template match which is:

<xsl:template match="entry[../section/@handle = 'services'][premium = 'Yes']">

Which in turn is in:

<xsl:template match="services">

which obviously sits inside data

So I think basically my question is, what is the purpose of match argument in the initial declaration?

Actually I think the penny has dropped finally.

In the original key declaration we are setting up the matched element /data/projects/entry and then when we call the key() function we are using /data/projects/entry as the context.

So if I am in the services/entry node my key is saying go to /data/projects/entry and then by using the service/@handle as the identifier, which say in my case may be eCommerce and then because my for-each is in /data/projects/entry it returns all the data that is in that node but also matches eCommerce too.

Is that right? Please tell me it is. :)

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