views:

43

answers:

3

I'm trying to create a page like this via XSLT transformation.

Pages

  • page1
  • page2

Links

  • link1
  • link2

Here is the xml

<siteMenu>
  <Pages>
    <title>page1</title>
  </Pages>
  <Pages>
    <title>page2</title>
  </Pages>
  <Links>
    <title>link1</title>
  </Links>
  <Links>
    <title>link2</title>
  </Links>
</siteMenu>

I tried using

<xsl:for-each select="*"> and <xsl:for-each-group select="*" group-by="@v">

but that gives me every element, but how can i separate them out based on the parent node?

I also want to make it dynamic so that if I add another item in siteMenu, it would update the xslt appropriately.

+1  A: 

You are selecting everything (*).

Use select="Pages" to select pages, and select="Links" for links.

Learn about XPath expressions here.

Oded
The problem is that I want to make it dynamic. So that I can add another node called "Sites" and it will add it to the menu.
scott
+1  A: 

I would start with

<ul>
  <xsl:for-each select="//Pages">
    <li><xsl:value-of select="./title"/></li>
  </xsl:for-each>
</ul>

and continue from there.

ndim
see my reply to Oded about making it dynamic.
scott
+4  A: 

So you want to group by node name. Not the best idea to store more than actual structure information in the XML nodes, but here you go:

<siteMenu>
  <Pages>
    <title>page1</title>
  </Pages>
  <Pages>
    <title>page2</title>
  </Pages>
  <Links>
    <title>link1</title>
  </Links>
  <Links>
    <title>link2</title>
  </Links>
  <Sites>
    <title>site1</title>
  </Sites>
  <Sites>
    <title>site2</title>
  </Sites>
</siteMenu>

and this XSLT 2.0 transformation (relevant fragment only):

<xsl:template match="siteMenu">
  <xsl:for-each-group select="*" group-by="name()">
    <xsl:sort select="name()" />
    <div>
      <h1><xsl:value-of-select="current-grouping-key()" /></h1>
      <ul>
        <xsl:for-each select="current-group()">
          <li><xsl:value-of select="title" /></li>
        </xsl:for-each>
      </ul>
    </div>
  </xsl:for-each-group>
</xsl:template>

or this XSLT 1.0 transformation (relevant fragment only):

<xsl:key name="kMenu" match="siteMenu/*" use="name()" />

<xsl:template match="siteMenu">
  <xsl:for-each select="*[
    generate-id() = generate-id(key('kMenu', name())[1])
  ]">
    <xsl:sort select="name()" />
    <div>
      <h1><xsl:value-of-select="name()" /></h1>
      <ul>
        <xsl:for-each select="key('kMenu', name())">
          <li><xsl:value-of select="title" /></li>
        </xsl:for-each>
      </ul>
    </div>
  </xsl:for-each-group>
</xsl:template>

Hint: You might want to think about using such an XML instead, this obviously matches your site structure better and you need no grouping to output it correctly.

<siteMenu>
  <section name="Pages">
    <title>page1</title>
    <title>page2</title>
  </section>
  <section name="Links">
    <title>link1</title>
    <title>link2</title>
  </section>
  <section name="Sites">
    <title>site1</title>
    <title>site2</title>
  </section>
</siteMenu>
Tomalak
That will work for XSLT 2.0, but not for 1.0. I'll have to find another route then.
scott
@scott: But you've used `for-each-group` in your question yourself, and left no indication whatsoever that you are on XSLT 1.0. I just *had to* assume you are on 2.0… Will be adding an XSLT 1.0 solution.
Tomalak
+1 for suggesting using a reasonable XML format.
ndim
I found an ideal solution, http://www.jenitennison.com/xslt/grouping/muenchian.html . Works beautifully.
scott
@scott: What this answer does *is* Muenchian grouping.
Tomalak