0

Learning XSLT, and I would like to know if this transformation is possible

Input something like this

<book>
  <title>Title 1</title>
  <author>Author 1</author>
</book>
<book>
  <title>Title 2</title>
  <author>Author 2</author>
</book>
<book>
  <title>Title 3</title>
  <author>Author 3</author>
</book>

Output

<book1>
  <title1>Title 1</title1>
  <author1>Author 1</author1>
</book1>
<book2>
  <title2>Title 2</title2>
  <author2>Author 2</author2>
</book2>
<book3>
  <title3>Title 3</title3>
  <author3>Author 3</author3>
</book3>

What would the XSLT for this look like?

  • Maybe this can help? http://stackoverflow.com/questions/833118/in-xslt-how-do-i-increment-a-global-variable-from-a-different-scope – OldProgrammer Dec 18 '13 at 17:34
  • 1
    Why don't you take a look at the `` instruction that allows you to create an element with a calculated name, and at the position() function? – michael.hor257k Dec 18 '13 at 17:36
  • 6
    It's possible but it's probably a bad idea - if you have elements that represent the same kind of data then they should have the same name, you're just making more work for the downstream consumers of the XML. – Ian Roberts Dec 18 '13 at 17:51

2 Answers2

1

As stated in the comments this is quite easy, but ill advised. However, if you acknowledge that it is ill-advised, then there is no reason to show how.

Firstly, in XSLT there are two ways to create an element, by placing the element inline:

<xsl:template match="book">
    <book1>
        <!-- Some stuff -->
    </book1>
<xsl:template>

Or programatically using the xsl:element directive:

<xsl:template match="book">
    <xsl:element name="book1">
        <!-- Some stuff -->
    </xsl:element>
<xsl:template>

The @name attribute in the above code can take an XPath expression if we use an attribute value template. Which means we can insert a dynamic value in there:

<xsl:template match="book">
    <xsl:element name="book{//some/xpath/here()}">
        <!-- Some stuff -->
    </xsl:element>
<xsl:template>

Additionally, we can get the position of the current node based on the current context using the position() Xpath function:

<xsl:template match="/">
    <xsl:for-each select="//book">
        <!-- this loop will number the books in the whole document 
             because its seaching in all nodes (//). -->
        <xsl:value-of select="position()"/>
    </xsl:for-each>
<xsl:template>

<xsl:template match="library">
    <xsl:for-each select=".//book">
        <!-- this loop will number the books in the current *library*
             because its seaching in all nodes under the library node (.//)  -->
        <xsl:value-of select="position()"/>
    </xsl:for-each>
<xsl:template>

As Michael Kay showed in his answer as I was typing this (grrr), combining these is trivial.

0

I've no idea why you would want to create such a horrible XML document, but if you must, you can do it using:

<xsl:template match="book">
  <xsl:variable name="n" select="position()"/>
  <xsl:element name="book{$n}">
    <xsl:element name="title{$n}">
      <xsl:value-of select="title"/>
    </xsl:element>
    <xsl:element name="author{$n}">
      <xsl:value-of select="author"/>
    </xsl:element>
  </xsl:element>
</xsl:template>
Michael Kay
  • 156,231
  • 11
  • 92
  • 164