Reputation: 11
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
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
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
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
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