Chris Jones
Chris Jones

Reputation: 2746

XML flaten repeating nodes

I have the following XML sturture in my project. I need to write somthing that transposes repeating nodes to make a flat stuture

<bookstore>
  <book >
    <title lang="en">Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book >
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book>
    <title lang="en">Learning XML</title>
    <author>Erik T. Ray</author>
    <year>2003</year>
    <price>39.95</price>
  </book>
</bookstore>

I want to flaten the sturture like this

<bookstore>

    <bookonetitle lang="en">Everyday Italian</bookonetitle>
    <bookoneauthor>Giada De Laurentiis</bookoneauthor>
    <bookoneyear>2005</bookoneyear>
    <bookoneprice>30.00</bookoneprice>


    <booktwotitle lang="en">Harry Potter</booktwotitle>
    <booktwoauthor>J K. Rowling</booktwoauthor>
    <booktwoyear>2005</booktwoyear>
    <booktwoprice>29.99</booktwoprice>

    <bookthreetitle lang="en">Learning XML</bookthreetitle>
    <bookthreetitle>Erik T. Ray</bookthreetitle>
    <bookthreetitle>2003</bookthreetitle>
    <bookthreetitle>39.95</bookthreetitle>



</bookstore>

Upvotes: 1

Views: 128

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243599

As per the comment by Michael Kay, your best option is to use the XSLT 2.0 instruction:

<xsl:number value="$n" format="w"/>

However, in case you can't use XSLT 2.0 and the count of books is limited and the limit known in advance, then a solution like this can be used:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my" exclude-result-prefixes="my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <my:numbers>
   <num>one</num>
   <num>two</num>
   <num>three</num>
   <num>four</num>
   <num>five</num>
   <num>six</num>
   <num>seven</num>
   <num>eight</num>
   <num>nine</num>
   <num>ten</num>
   <num>eleven</num>
   <num>twelve</num>
 </my:numbers>

 <xsl:variable name="vNumbers" select="document('')/*/my:numbers/*"/>

 <xsl:template match="/*">
  <bookstore><xsl:apply-templates/></bookstore>
 </xsl:template>
 <xsl:template match="book">
  <xsl:apply-templates select="*">
   <xsl:with-param name="pPos" select="position()"/>
  </xsl:apply-templates>
  <xsl:text>&#xA;</xsl:text>
 </xsl:template>

 <xsl:template match="book/*">
   <xsl:param name="pPos"/>

   <xsl:element name="book{$vNumbers[$pPos]}{name()}">
     <xsl:copy-of select="@*|node()"/>
   </xsl:element>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<bookstore>
  <book >
    <title lang="en">Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book >
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book>
    <title lang="en">Learning XML</title>
    <author>Erik T. Ray</author>
    <year>2003</year>
    <price>39.95</price>
  </book>
</bookstore>

the wanted, correct result is produced:

<bookstore>
   <bookonetitle lang="en">Everyday Italian</bookonetitle>
   <bookoneauthor>Giada De Laurentiis</bookoneauthor>
   <bookoneyear>2005</bookoneyear>
   <bookoneprice>30.00</bookoneprice>

   <booktwotitle lang="en">Harry Potter</booktwotitle>
   <booktwoauthor>J K. Rowling</booktwoauthor>
   <booktwoyear>2005</booktwoyear>
   <booktwoprice>29.99</booktwoprice>

   <bookthreetitle lang="en">Learning XML</bookthreetitle>
   <bookthreeauthor>Erik T. Ray</bookthreeauthor>
   <bookthreeyear>2003</bookthreeyear>
   <bookthreeprice>39.95</bookthreeprice>

</bookstore>

Upvotes: 1

Sean B. Durkin
Sean B. Durkin

Reputation: 12729

Your output structure strikes me as being very awkward. You might like to reconsider it. But in any case, this XSLT 1.0 style-sheet...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="book">
  <xsl:apply-templates select="node()"/>
</xsl:template>

<xsl:template match="book/*">
  <xsl:element name="book-{count(../preceding-sibling::book)+1}-{local-name()}">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

</xsl:stylesheet>

...will transform your sample input into...

<bookstore>
  <book-1-title lang="en">Everyday Italian</book-1-title>
  <book-1-author>Giada De Laurentiis</book-1-author>
  <book-1-year>2005</book-1-year>
  <book-1-price>30.00</book-1-price>
  <book-2-title lang="en">Harry Potter</book-2-title>
  <book-2-author>J K. Rowling</book-2-author>
  <book-2-year>2005</book-2-year>
  <book-2-price>29.99</book-2-price>
  <book-3-title lang="en">Learning XML</book-3-title>
  <book-3-author>Erik T. Ray</book-3-author>
  <book-3-year>2003</book-3-year>
  <book-3-price>39.95</book-3-price>
</bookstore> 

Upvotes: 3

Related Questions