Search code examples
xsltgroupingmuenchian-grouping

XSLT 1.0 grouping to reformat element defined by date into element defined by task


I have a tricky XSLT transformation and I'd like your advise My xml is formatted as below:

<Person>
<name>John</name>
<date>June12</date>
<workTime taskID=1>34</workTime>
<workTime taskID=2>12</workTime>
</Person>
<Person>
<name>John</name>
<date>June12</date>
<workTime taskID=1>21</workTime>
<workTime taskID=2>11</workTime>
</Person>

The output xml should be:

<Person>
<name>John</name>
<taskID>1</taskID>
<workTime>
    <date>June12</date>
    <time>34</time>
</worTime>
<workTime>
    <date>June13</date>
    <time>21</time>
</worTime>
</Person>
<Person>
<name>John</name>
<taskID>2</taskID>
<workTime>
    <date>June12</date>
    <time>12</time>
</worTime>
<workTime>
    <date>June13</date>
    <time>11</time>
</worTime>
</Person>

Essentially, as an input, a "Person" object gathers all the task/workTime for a specific date. As an output, I want the "Person" object to gather the date/workTime for a specific task.

I need to use XLST 1.0. I've been trying to use grouping with key but get very puzzled.

Appreciate your help. Daniel


Solution

  • I believe one way to solve it is to use muenchian grouping, but in this case you are grouping by task and by person, and so you need an aggregated key

    <xsl:key name="PersonTasks" match="workTime" use="concat(@taskID, ../name)"/>
    

    This key is used to look up all distinct 'person tasks'. You would iterate over this by doing the following

    <xsl:apply-templates select="//workTime[generate-id() = generate-id(key('PersonTasks',concat(@taskID, ../name))[1])]"/>
    

    Then, for each distinct Person and TaskID, you just need to find all the work times. Try this stylesheet (Note I have added a 'People' element, to give the output a single root element to ensure it is valid XML)

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
       <xsl:key name="PersonTasks" match="workTime" use="concat(@taskID, ../name)"/>
    
       <xsl:template match="/">
          <People>
             <xsl:apply-templates select="//workTime[generate-id() = generate-id(key('PersonTasks',concat(@taskID, ../name))[1])]"/>
          </People>
       </xsl:template>
    
       <xsl:template match="workTime">
          <xsl:variable name="taskID">
             <xsl:value-of select="@taskID"/>
          </xsl:variable>
          <xsl:variable name="name">
             <xsl:value-of select="../name"/>
          </xsl:variable>
          <Person>
             <name>
                <xsl:value-of select="$name"/>
             </name>
             <taskID>
                <xsl:value-of select="$taskID"/>
             </taskID>
             <xsl:for-each select="//workTime[../name = $name][@taskID = $taskID]">
                <workTime>
                   <date>
                      <xsl:value-of select="../date"/>
                   </date>
                   <time>
                      <xsl:value-of select="."/>
                   </time>
                </workTime>
             </xsl:for-each>
          </Person>
       </xsl:template>
    
    </xsl:stylesheet>
    

    When you apply this to the following input XML

    <People>
    <Person>             
    <name>John</name>             
    <date>June12</date>             
    <workTime taskID="1">34</workTime>             
    <workTime taskID="2">12</workTime>             
    </Person>             
    <Person>             
    <name>John</name>             
    <date>June13</date>             
    <workTime taskID="1">21</workTime>             
    <workTime taskID="2">11</workTime>             
    </Person>   
    </People>
    

    You should get the following output

    <People>
       <Person>
          <name>John</name>
          <taskID>1</taskID>
          <workTime>
             <date>June12</date>
             <time>34</time>
          </workTime>
          <workTime>
             <date>June13</date>
             <time>21</time>
          </workTime>
       </Person>
       <Person>
          <name>John</name>
          <taskID>2</taskID>
          <workTime>
             <date>June12</date>
             <time>12</time>
          </workTime>
          <workTime>
             <date>June13</date>
             <time>11</time>
          </workTime>
       </Person>
    </People>