RE: [xsl] Multiple matches against keys?

Subject: RE: [xsl] Multiple matches against keys?
From: "Andrew Welch" <ajwelch@xxxxxxxxxxxxxxx>
Date: Tue, 31 Aug 2004 10:32:51 +0100
> I'm trying to group and sort a list of tasks by category, where one
> <task> may have multiple categories identified by <context>
> tags. I find
> that I get different results depending on the ordering in the
> source XML:
>
> <tasks>
>     <task>
>         <subject>This is to be done at home</subject>
>         <context>home</context>
>     </task>
>     <task>
>         <subject>No context so skip this one.</subject>
>     </task>
>     <task>
>         <subject>Make a phone call</subject>
>         <context>call</context>
>     </task>
>     <task>
>         <subject>This is to be done at home, and is a phone
> call</subject>
>         <context>home</context>
>         <context>call</context>
>     </task>
>     <task>
>         <subject>This is a call to be made from work</subject>
>         <context>work</context>
>         <context>call</context>
>     </task>
> </tasks>
>
> Using the following XSLT, I get what I want:
>
> <xsl:stylesheet version="1.0"
> xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
>     <xsl:output method="xml" version="1.0" encoding="UTF-8"
> indent="yes"/>
>     <xsl:key name="tasks-by-context" match="task" use="context"/>
>     <xsl:template match="/">
>         <ul>
>             <xsl:for-each select="//task[count(. |
> key('tasks-by-context', context)[1]) = 1][count(context)>0]">
>                 <xsl:sort select="subject"/>
>                 <xsl:variable name="c" select="context"/>
>                 <li>
>                     CONTEXT: <xsl:value-of select="$c"/>:
> (<xsl:value-of
> select="count(key('tasks-by-context',$c))"/> tasks)
>                     </li>
>                 <ul>
>                     <xsl:for-each
> select="key('tasks-by-context', $c)">
>                             <li>
>                                     Task : <xsl:value-of
> select="subject"/>
>                             </li>
>                     </xsl:for-each>
>                 </ul>
>             </xsl:for-each>
>         </ul>
>     </xsl:template>
> </xsl:stylesheet>
>
> As I'm skipping uncategorized tasks (i.e. with no <context>
> children), I
> get the following output:
>
> - CONTEXT: call: (3 tasks)
>     - Task : Make a phone call
>     - Task : This is to be done at home, and is a phone call
>     - Task : This is a call to be made from work
> - CONTEXT: home: (2 tasks)
>     - Task : This is to be done at home
>     - Task : This is to be done at home, and is a phone call
>
> However, this behavior differs if the original <task>
> elements are in a
> different order. For example, moving the "Make a phone call"
> task to the
> end of the <tasks> list reduces the output to the following:
>
> - CONTEXT: home: (2 tasks)
>     - Task : This is to be done at home
>     - Task : This is to be done at home, and is a phone call
>
> So it looks like my key definition is no longer picking up
> "call" as a
> valid key item. I'm actually not sure whether I'm using the <xsl:key>
> tag correctly here -- will it match and index a single <task> node
> multiple times if it contains multiple tags?



Here's an XSLT 2.0 solution, although I'm not sure your desired output
is correct:

<xsl:template match="/">
    <xsl:for-each-group select="/tasks/task" group-by="context">
        <xsl:text>- CONTEXT: </xsl:text>
        <xsl:value-of select="context"/>: (<xsl:text/>
        <xsl:value-of select="count(current-group())"/>
tasks)&#xa;<xsl:text/>
        <xsl:for-each select="current-group()/subject">
            <xsl:text>    - Task: </xsl:text>
            <xsl:value-of select="."/><xsl:text>&#xa;</xsl:text>
        </xsl:for-each>
    </xsl:for-each-group>
</xsl:template>

This will produce:

- CONTEXT: call: (3 tasks)
    - Task: Make a phone call
    - Task: This is to be done at home, and is a phone call
    - Task: This is a call to be made from work
- CONTEXT: home: (2 tasks)
    - Task: This is to be done at home
    - Task: This is to be done at home, and is a phone call
- CONTEXT: work call: (1 tasks)
    - Task: This is a call to be made from work

Whereas your desired output is:

- CONTEXT: call: (3 tasks)
    - Task : Make a phone call
    - Task : This is to be done at home, and is a phone call
    - Task : This is a call to be made from work
- CONTEXT: home: (2 tasks)
    - Task : This is to be done at home
    - Task : This is to be done at home, and is a phone call

Wouldn't you want to output the 'work' context as well?

cheers
andrew

Current Thread