Re: [xsl] Restrict Key to specific parent

Subject: Re: [xsl] Restrict Key to specific parent
From: Wendell Piez <wapiez@xxxxxxxxxxxxxxxx>
Date: Wed, 29 Nov 2006 16:03:16 -0500
Ty,

You don't want and can't use keys here, as keys by definition work across document-wide scope.

What you do want looks fairly baroque, given your contingencies, but is actually simpler than trying to construct a key-based retrieval. For the name, just go grab the nodes you want using XPath, and write out their values.

<xsl:template match="Name">
<xsl:for-each select="(ancestor::Profile|ancestor::Section|ancestor::Info)[last()]">
<!-- change context to the closest ancestor Profile, Section or Info -->


    <xsl:variable name="Prod-names" select="ProfileInfo/@name | Prod/@name"/>
    <!-- collect the ProfileInfo and Prod names (whichever you have here) -->

<xsl:for-each select="$Prod-names[. = not(../preceding-sibling::*/@name)]">
<!-- iterate over this set, excluding duplicates (awkwardly,
but better in this case than using a compound key to do it -->
<xsl:value-of select="."/>
<xsl:if test="not(position()=last())">
<!-- punctuate all but the last with a comma -->
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:template>


This does rely quite heavily on your input being just as you describe. Both the node collection and the logic to remove duplicates (which you may not actually need, and should toss if not) are quite sensitive to your document structure being as you have presented it here.

Cheers,
Wendell

At 02:44 PM 11/29/2006, you wrote:
Hello,
This is as simple of an example, hitting the main scenarios, I could
come up with.

I will try and be as clear and unambiguous as possible. I did research
throughout the xsl-list archive and found posts that were close, but
did not really hit my issue here.
I will start with an input xml, show the output expected and then show
you what I tried and it's short comings. So here goes.

Input XML:
<Root>
<Info>
<Prod name="Red"/>
<Prod name="Blue"/>
<Prod name="Green"/>
</Info>
<Title>Test Document</Title>
<Section>
<Paragraph>
 This is regular text for these product: <Name/>
 <Profile>
  <ProfileInfo Name="Blue"/>
   <!-- The issue here is that we cannot be sure how deep the
<Name/> element is from the <Profile> and <ProfileInfo> element -->
  This is text associated with Prod Profile: <bold><Name/></bold>
 </Profile>
 This is text after the profile that is for all Prods: <Name/>
 <Profile>
  <ProfileInfo Name="Red"/>
  <ProfileInfo Name="Green"/>
  This is text associated with Product Profile for <Name/>
 </Profile>
</Paragraph>
<Paragraph>
 This is some more text.
 <Profile>
  <ProfileInfo Name="Blue"/>
  More text associated with <Name/>
 </Profile>
</Paragraph>
</Section>
<Section>
<ProfileInfo Name="Green"/>
<Paragraph>
 This is text that goes along with this section for <Name/>
</Paragraph>
</Section>
</Root>


Expected Output:


<Title>Test Document</Title>

<Section>
<Paragraph>
This is regular text for these products: Red, Blue, Green

<Profile>
 This is text associated with Product Profile for <bold>Blue</bold>
</Profile>

This is text after the profile that is for all products: Red, Blue, Green
<Profile>
 This is text associated with Product Profile for Red, Green
</Profile>

</Paragraph>

<Paragraph>
This is some more text.

<Profile>
  More text associated with Blue
</Profile>

</Paragraph>
</Section>

<Section>
<Profile>
This is text that goes along with this section for Green
</Profile>
</Section>

Ok, so the rules that we want to abide by are as follows: (The only
template that I am having trouble with is the <Name> tag, I can
correctly I identify and resolve all other tags)

1) If there is a <Name/> Element within a <Profile> element, then
take all the ProfileInfo elements that are children of the <Profile>
element and display their @Name attribute uniquely and separated by
commas.
2) If there is a <Name/> Element within a <Section> element, and the
<Section> has a <ProfileInfo> in it (the ProfileInfo will have to come
directly after the <Section> element defined by the schema.) then
output only the @Name of the <ProfileInfo>(s) associated with that
particular <Section>
3) If there is a <Name/> Element that does not reside in a <Profile>
element (which has a profile element) or within a <Section> element
that is profiled (has a <ProfileInfo> tag) then we want to output all
Prod/@name separated by commas and non repeating.


The main question is: How to scope the <xsl:key>'s when I'm in the <xsl:for-each> such that only the profiles of that specific section are looked at, not the entire document which is happening here.

Here's what I have: (My namespace is _ for convenience)

<xsl:key name="distinct-name" match="_:Prod" use="@name" />
<xsl:key name="distinct-name-profiled" match="_:ProfileInfo" use="@Name" />

<xsl:template match="_:Name">
<xsl:choose>
 <xsl:when test="ancestor::_:ProductProfile">

   <xsl:for-each
select="preceding::_:ProfileInfo[generate-id(.)=generate-id(key('distinct-name-profiled',@Name)[1])]">
    <span>
     <xsl:value-of select="@Name"/>
     <xsl:if test="position() != last()">, </xsl:if>
    </span>
    </xsl:for-each>

 </xsl:when>
 <xsl:when test="not(ancestor::_:ProductProfile) and preceding::_:Section">

   <xsl:for-each
select="preceding::_:ProfileInfo[generate-id()=generate-id(key('distinct-name-profiled',@Name)[1])]">
    <span>
     <xsl:value-of select="@Name"/>
     <xsl:if test="position() != last()">, </xsl:if>
    </span>
    </xsl:for-each>

 </xsl:when>
 <xsl:otherwise>

  <xsl:for-each
select="//_:Prod[generate-id()=generate-id(key('distinct-name',@name))]">
   <span>
    <xsl:value-of select="@name"/>
    <xsl:if test="position() != last()">, </xsl:if>
   </span>
   </xsl:for-each>

 </xsl:otherwise>
</xsl:choose>

</xsl:template>

Please let me know if there is anything else that you man need.

Thanks again for all your time,
-Ty


======================================================================
Wendell Piez                            mailto:wapiez@xxxxxxxxxxxxxxxx
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
  Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================

Current Thread