tags:

views:

196

answers:

3

Hi! I'm trying to learn XSLT (for some holiday coding fun). I think I now have a pretty good understanding of the basics (grabbing subtrees, filtering-out elements, and renaming elements). Where I'm having trouble is when it comes to drastically reorganizing XML structures. If you had a deeply nested structure and wanted to flatten it, how would you go about doing so?

For example, let's say I'm trying to transform a docbook snippet into html...

input (docbook):

<section>
  <title>Title A</title>
  <para>foo</para>
  <para>bar</para>
  <section>
    <title>Title B</title>
    <para>baz</para>
    <para>biz</para>
    <section>
      <title>Title C</title>
      <para>bing</para>
    </section>
  </section>
  <section>
    <title>Title D</title>
    <para>fizzle</para>
  </section>
</section>

output (html):

<h1>Title A</h1>
<p>foo</p>
<p>bar</p>
<h2>Title B</h2>
<p>baz</p>
<p>biz</p>
<h3>Title C</h3>
<p>bing</p>
<h2>Title D</h2>
<p>fizzle</p>

Is this where xsl:param and xsl:call-template come into play?

Thanks!

+2  A: 

See this question

santiiiii
+1  A: 

Hi,

this should do the Job:

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

    <xsl:template match="/">
            <html><header>
              <xsl:apply-templates/>
            </header></html>
    </xsl:template>

    <xsl:template match="title">
        <h2><xsl:value-of select="."></h2>
    </xsl:template>

    <xsl:template match="para">
        <p><xsl:value-of select="."></p>
    </xsl:template>
  </xsl:stylesheet>

For flattening you don't need call-template. If you use call-template you hand over some attribs, see here

Carsten C.
+4  A: 

Carsten's test case works (with minor adjustments, you need terminate xsl:value-of with a /) , but always uses <h2> as the heading. If you want to use different heading elemenents according to the nesting level of the title, then you need something in addition to it:

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

  <xsl:template match="/">
    <html>
      <body>
        <xsl:apply-templates />
      </body>
    </html>
  </xsl:template>

  <xsl:template match="title">
    <xsl:choose>
      <xsl:when test="count(ancestor::section) = 1">
        <h1><xsl:value-of select="." /></h1>
      </xsl:when>
      <xsl:when test="count(ancestor::section) = 2">
        <h2><xsl:value-of select="." /></h2>
      </xsl:when>
      <xsl:otherwise>
        <h3><xsl:value-of select="." /></h3>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="para">
    <p><xsl:value-of select="." /></p>
  </xsl:template>

</xsl:stylesheet>

The XPath function count(ancestor::section) will return a count of all <section> elements that are parents of the current element. In the example I have used <h1> and <h2> for the two outermost levels and <h3> for anything deeper nested, but of course you can use other differentiations at your disgression.

It would even be possible to generate the number after the heading on the fly, using this expression:

  <xsl:template match="title">
    <xsl:variable name="heading">h<xsl:value-of select="count(ancestor::section)" /></xsl:variable>
    <xsl:element name="{$heading}">
      <xsl:value-of select="." />
    </xsl:element>
  </xsl:template>

The xsl:variable section in there creates a variable with a value of h + nesting level. The variable then can be used as a parameter for the xsl:element element that allows you to dynamically define the name of the element you want to create.

Followup: If you want to use only the h1-h6 as suggested, you could do it like this:

<xsl:template match="title">
  <xsl:variable name="hierarchy" select="count(ancestor::section)"/>
  <xsl:variable name="heading">h<xsl:value-of select="$hierarchy" /></xsl:variable>

  <xsl:choose>
    <xsl:when test="$hierarchy > 6">
      <h6 class="{$heading}"><xsl:value-of select="." /></h6>
    </xsl:when>
    <xsl:otherwise>
      <xsl:element name="{$heading}">
        <xsl:value-of select="." />
      </xsl:element>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

This expression uses <h6 class="h..."> for anything that has a nesting deeper than 6. It uses <h1> through <h6> for all other hierarchy levels.

nd
Awesome! Thanks for the full blown lesson!
splicer
Nice work. :) I would add a check for `count(ancestor::section) < 7` to the second template to prevent the creation of `<h7>` elements.
Tomalak