Brian
Brian

Reputation: 11

XSL Embedded for-each

I know this is covered somewhat on here, but I'm new to XSLT and I'm trying to do a nested for-each loop on nodes that are in different places in the input XML but at the same node level.

So...for example:

    <Level1>
    <Level2>
        <Level3a>
          <Item1>Clothes Washed</Item1>
          <Item2>08/02/2011 06:54</Item2>
          <DoneBy>Ingrid</DoneBy>
        </Level3a>
        <Level3a>
          <Item1>Car Washed</Item1>
          <Item2>08/02/2011 08:25</Item2>
          <DoneBy>Jeanette</DoneBy>
        </Level3a>
        <Level3a>
          <Item1>Dog Walked</Item1>
          <Item2>08/02/2011 10:30</Item2>
          <DoneBy>Ingrid</DoneBy>
        </Level3a>
        <Level3b>
          <DoneWho>Ingrid</DoneWho>
          <JobTitle>Main Asst</JobTitle>
        </Level3b>
        <Level3b>
          <DoneWho>Jeanette</DoneWho>
          <JonTitle>2nd Asst</JobTitle>
        </Level3b>
      </Level2>
    </Level1>

I need the output to be

    <Jobs>
      <CompletedJob>
        <JobTitle>Main Asst</JobTitle>
        <Job>Clothes Washed</Job>
        <CompOn>08/02/2011</CompOn>
        <CompAt>06:54<CompAt>
      </CompletedJob>
      <CompletedJob>
        <JobTitle>Main Asst</JobTitle>
        <Job>Dog Walked</Job>
        <CompOn>08/02/2011</CompOn>
        <CompAt>10:30</CompAt>
      </CompletedJob>
      <CompletedJob>
        <JobTitle>2nd Asst</JobTitle>
        <Job>Car Washed</Job>
        <CompOn>08/02/2011</CompOn>
        <CompAt>08:25</CompAt>
      </CompletedJob>
    </Jobs>

EDIT: please see the change in the above output...I guess this is really what I'm trying to do. Replacing in output with ...thanks again.

I've tried a neste for-each loop, but I can't refer to a different node from inside the main for-each loop.

Any help is greatly appreciated.

Upvotes: 1

Views: 467

Answers (4)

Petr Kozelka
Petr Kozelka

Reputation: 7980

No need for nested loops - XSLT is resursive enough by nature.

Following stylesheet does the job:

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

    <xsl:output method="xml"/>
    <xsl:template match="/">
        <Jobs>
            <xsl:for-each select="*/*/Level3a">
                <xsl:sort select="DoneBy"/>

                <CompletedJob>
                    <JobTitle>
                        <xsl:value-of select="../Level3b[DoneWho=current()/DoneBy]/JobTitle"/>
                    </JobTitle>
                    <Job>
                        <xsl:value-of select="Item1"/>
                    </Job>
                    <CompOn>
                        <xsl:value-of select="substring-before(Item2,' ')"/>
                    </CompOn>
                    <CompAt>
                        <xsl:value-of select="substring-after(Item2,' ')"/>
                    </CompAt>
                </CompletedJob>

            </xsl:for-each>
         </Jobs>
    </xsl:template>

</xsl:stylesheet>

Level3b elements are now used for resolving DoneBy to JobTitle.

Upvotes: 1

Daniel Haley
Daniel Haley

Reputation: 52848

You shouldn't have to use nested loops (or loops at all).

Using a fixed version of the input XML:

<Level1>
  <Level2>
    <Level3a>
      <Item1>Clothes Washed</Item1>
      <Item2>08/02/2011 06:54</Item2>
      <DoneBy>Ingrid</DoneBy>
    </Level3a>
    <Level3a>
      <Item1>Car Washed</Item1>
      <Item2>08/02/2011 08:25</Item2>
      <DoneBy>Jeanette</DoneBy>
    </Level3a>
    <Level3a>
      <Item1>Dog Walked</Item1>
      <Item2>08/02/2011 10:30</Item2>
      <DoneBy>Ingrid</DoneBy>
    </Level3a>
    <Level3b>
      <DoneWho>Ingrid</DoneWho>
      <JobTitle>Main Asst</JobTitle>
    </Level3b>
    <Level3b>
      <DoneWho>Jeanette</DoneWho>
      <JobTitle>2nd Asst</JobTitle>
    </Level3b>
  </Level2>
</Level1>

and this stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>    
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/Level1">
    <Jobs>
      <xsl:apply-templates select="*/Level3a">
        <xsl:sort select="DoneBy"/>
      </xsl:apply-templates>
    </Jobs>
  </xsl:template>

  <xsl:template match="Level3a">
    <CompletedJob>
      <Who><xsl:apply-templates select="DoneBy"/></Who>
      <Job><xsl:apply-templates select="Item1"/></Job>
      <!-- XSLT 2.0 option
      <CompOn><xsl:value-of select="tokenize(Item2,' ')[1]"></xsl:value-of></CompOn>
      <CompAt><xsl:value-of select="tokenize(Item2,' ')[2]"></xsl:value-of></CompAt>
      -->
      <CompOn><xsl:value-of select="substring-before(Item2, ' ')"></xsl:value-of></CompOn>
      <CompAt><xsl:value-of select="substring-after(Item2, ' ')"></xsl:value-of></CompAt>
    </CompletedJob>
  </xsl:template>

  <xsl:template match="DoneBy|Item1|Item2">
    <xsl:apply-templates/>
  </xsl:template>

</xsl:stylesheet>

produces the wanted output:

<Jobs>
   <CompletedJob>
      <Who>Ingrid</Who>
      <Job>Clothes Washed</Job>
      <CompOn>08/02/2011</CompOn>
      <CompAt>06:54</CompAt>
   </CompletedJob>
   <CompletedJob>
      <Who>Ingrid</Who>
      <Job>Dog Walked</Job>
      <CompOn>08/02/2011</CompOn>
      <CompAt>10:30</CompAt>
   </CompletedJob>
   <CompletedJob>
      <Who>Jeanette</Who>
      <Job>Car Washed</Job>
      <CompOn>08/02/2011</CompOn>
      <CompAt>08:25</CompAt>
   </CompletedJob>
</Jobs>

NOTE: So far, my answer is the only one that sorts the output and produces the exact result (minus the well-formedness error) you wanted.

Upvotes: 1

Alex Pertsev
Alex Pertsev

Reputation: 948

your xml input is realy terrible.

    <xsl:template match="/">
    <xsl:apply-templates select="//Item1"/>
</xsl:template>

<xsl:template match="Item1">
    <CompletedJob>
        <Who><xsl:value-of select="../DoneBy"/></Who>
        <Job><xsl:value-of select="."/></Job>
        <xsl:apply-templates select="../Item2"/>
    </CompletedJob>
</xsl:template>

<xsl:template match="Item2">
    <CompOn><xsl:value-of select="substring-before(text(), ' ')"/></CompOn>
    <CompAt><xsl:value-of select="substring-after(text(), ' ')"/></CompAt>
</xsl:template>

this generates your output

Upvotes: 0

feathj
feathj

Reputation: 3069

Darn, beat me to it. Oh well, might as well give my solution too:

<Level1>
    <Level2>
        <Level3>
            <Item1>Clothes Washed</Item1>
            <Item2>08/02/2011 06:54</Item2>
            <DoneBy>Ingrid</DoneBy>
        </Level3>
        <Level3>
            <Item1>Car Washed</Item1>
            <Item2>08/02/2011 08:25</Item2>
            <DoneBy>Jeanette</DoneBy>
        </Level3>
        <Level3>
            <Item1>Dog Walked</Item1>
            <Item2>08/02/2011 10:30</Item2>
            <DoneBy>Ingrid</DoneBy>
        </Level3>
    </Level2>
</Level1>


<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <Jobs>
        <xsl:for-each select="/Level1/Level2/Level3">
            <CompletedJob>
                <Who><xsl:value-of select="./DoneBy"/></Who>
                <Job><xsl:value-of select="./Item1"/></Job>
                <CompOn><xsl:value-of select="substring(./Item2, 1, 10)"/></CompOn>
                <CompAt><xsl:value-of select="substring(./Item2, 12, 5)"/></CompAt>
            </CompletedJob>
        </xsl:for-each>
        </Jobs>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Related Questions