[xsl] Looping through a tree and retaining parent values

Subject: [xsl] Looping through a tree and retaining parent values
From: Mark Peters <flickrmeister@xxxxxxxxx>
Date: Wed, 1 Apr 2009 09:58:08 -0400
Hi Everyone,

I'm puzzling over a transformation that I thought would be simple but
which becomes progressively more complex as I try to wrap my mind
around it.

Here is a series of menuItem nodes, which are nested in menuItems
nodes. Each menuItem node has a label attribute.

<menuConfig>
	<menu id="A">
		<menuItems>
			<menuItem label="1">
				<menuItems>
					<menuItem label="a"/>
						<menuItems>
							<menuItem label="1a">
								<menuItems>
									<menuItem label="1a.a"/>
									<menuItem label="1a.b"/>
								</menuItems>
							</menuItem>
						</menuItems>
					<menuItem label="b">
					</menuItem>
				</menuItems>
			</menuItem>
			<menuItem label="2">
				<menuItems>
					<menuItem label="c"/>
					<menuItem label="d">
						<menuItems>
							<menuItem label="2d">
								<menuItems>
									<menuItem label="2d.d"/>
									<menuItem label="2d.e"/>
									<menuItem label="2d.f"/>
								</menuItems>
							</menuItem>

						</menuItems>
					</menuItem>
					<menuItem label="e">
						<menuItems>
							<menuItem label="2e">
								<menuItems>
									<menuItem label="2e.g"/>
								</menuItems>
							</menuItem>
						</menuItems>
					</menuItem>
				</menuItems>
			</menuItem>
		</menuItems>
	</menu>
</menuConfig>

What I'm trying to do is create a set of p nodes which nest a series
of uicontrol nodes, one uicontrol node for each level of menuItem in
the original XML document. In other words, for each menuItem in the
original XML, there should be a uicontrol element. The first uicontrol
node in the series contains the @label value for the parent menuItem
node. The next uiconcontrol node in the series contains the @label
value for the first child menuItem node, and so on.

The p node should have an id attribute whose value is the @label value
of the last child node in each tree.

Here is an example:

<body>
	<p id="1">
		<uicontrol>1</uicontrol>
	</p>
	<p id="a">
		<uicontrol>1</uicontrol> &gt;
		<uicontrol>a</uicontrol></p>
	<p id="1a">
		<uicontrol>1</uicontrol> &gt;
		<uicontrol>a</uicontrol> &gt;
		<uicontrol>1a</uicontrol>
	</p>
	<p id="1.1a">
		<uicontrol>1</uicontrol> &gt;
		<uicontrol>a</uicontrol> &gt;
		<uicontrol>1a</uicontrol> &gt;
		<uicontrol>1a.a</uicontrol>
	</p>
	<p id="1.1b">
		<uicontrol>1</uicontrol> &gt;
		<uicontrol>a</uicontrol> &gt;
		<uicontrol>1a</uicontrol> &gt;
		<uicontrol>1b.b</uicontrol>
	</p>
	<p id="b">
		<uicontrol>1</uicontrol> &gt;
		<uicontrol>b</uicontrol>
	</p>
	<p id="2">
		<uicontrol>2</uicontrol>
	<p id="c">
		<uicontrol>2</uicontrol> &gt;
		<uicontrol>c</uicontrol>
	</p>
	<p id="d">
                <uicontrol>2</uicontrol> &gt;
		<uicontrol>d</uicontrol>
	</p>
	<p id="2d">
		<uicontrol>2</uicontrol> &gt;
		<uicontrol>d</uicontrol> &gt;
		<uicontrol>2d</uicontrol>
	</p>
	<p id="2d.d">
		<uicontrol>2</uicontrol> &gt;
		<uicontrol>d</uicontrol> &gt;
		<uicontrol>2d</uicontrol> &gt;
		<uicontrol>2d.d</uicontrol>
	</p>
	<p id="2d.e">
		<uicontrol>2</uicontrol> &gt;
		<uicontrol>d</uicontrol> &gt;
		<uicontrol>2d</uicontrol> &gt;
		<uicontrol>2d.e</uicontrol>
	</p>
	<p id="e">
		<uicontrol>2</uicontrol> &gt;
		<uicontrol>e</uicontrol>
	</p>
	<p id="2e">
		<uicontrol>2</uicontrol> &gt;
		<uicontrol>e</uicontrol> &gt;
		<uicontrol>2e</uicontrol></p>
	<p id="2e.g">
                <uicontrol>2</uicontrol> &gt;
		<uicontrol>e</uicontrol> &gt;
		<uicontrol>2e</uicontrol> &gt;
		<uicontrol>2e.g</uicontrol>
	</p>
</body>

So far, I've come up with a stylesheet that produces a single p node
for every parent menuItem. It's a start. I've thought about trying to
use variables to retain the menuItem/@label values at each level in
the tree; but, reading about variables, I'm not sure this is possible.
Likewise, I've thought about defining each loop in a separate template
and then calling each template from the "master" template. But then,
I'm not sure how I'd retrieve the final menuItem/@label value for the
p/@id value.

And I must not understand the last() function correctly. The p/@id
values inserted by the stylesheet are "1a" and "2e.g," and not the
@label value for the last menuItem in each tree.

<xsl:template match="/">
		<xsl:for-each select="//menu">
			<body>
				<xsl:for-each select="menuItems">
					<xsl:for-each select="menuItem">
						<p>
							<xsl:attribute name="id">

<xsl:value-of select="descendant::menuItem[last()]/@label"/>
                                                        </xsl:attribute>
							<uicontrol>
								<xsl:value-of select="@label"/>
							</uicontrol> &gt;
							<xsl:for-each select="descendant::menuItem[2]">
								<uicontrol>
									<xsl:value-of select="@label"/>
								</uicontrol> &gt;
									<xsl:for-each select="descendant::menuItem[3]">
									<uicontrol>
										<xsl:value-of select="@label"/>
									</uicontrol> &gt;
											<xsl:for-each select="descendant::menuItem[4]">
										<uicontrol>
											<xsl:value-of select="@label"/>
										</uicontrol> &gt;
												<xsl:for-each select="descendant::menuItem[5]">
											<uicontrol>
												<xsl:value-of select="@label"/>
											</uicontrol> &gt;
												</xsl:for-each>
									</xsl:for-each>
								</xsl:for-each>
							</xsl:for-each>
						</p>
					</xsl:for-each>
				</xsl:for-each>
			</body>
		</xsl:for-each>
	</xsl:template>

I'd appreciate any help you could offer.

Thanks,
Mark
-- 

Mark Peters
Senior Technical Writer
Saba Software

Current Thread