tags:

views:

27

answers:

2

I need to merge certain xml nodes based on an attribute value, change that attribute value on the merged node and sum another attribute.

I am able to change the value of the attributes, but I couldn't figure out how to sum(@count) and assign it to @count on the resulting xml

Source xml

<xml>
 <books category="X" count="2">
  <book name="bookx1"/>
  <book name="bookx2"/>
 </books>
 <books category="Y" count="3">
  <book name="booky1"/>
  <book name="booky2"/>
  <book name="booky3"/>
 </books>
 <books category="Z" count="2">
  <book name="bookz1"/>
  <book name="bookz2"/>
 </books></xml>

After xslt transform it needs to be like this

<xml>
 <books category="A" count="5">
  <book name="bookx1"/>
  <book name="bookx2"/>
  <book name="booky1"/>
  <book name="booky2"/>
  <book name="booky3"/>
 </books>
 <books category="Z" count="2">
  <book name="bookz1"/>
  <book name="bookz2"/>
 </books></xml>

This is my partial xslt

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="xml"/>
 <xsl:template match="*">
  <xsl:element name="{local-name()}">
   <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
 </xsl:template>
 <xsl:template match="@*">
  <xsl:copy-of select="."/>
 </xsl:template>

 <xsl:template match="@category">
  <xsl:attribute name="category">
   <xsl:choose>
    <xsl:when test=".='X'">
     <xsl:text>A</xsl:text>
    </xsl:when>
    <xsl:when test=".='Y'">
     <xsl:text>A</xsl:text>
    </xsl:when>
    <xsl:when test=".='Z'">
     <xsl:text>B</xsl:text>
    </xsl:when>
    <xsl:otherwise>
     <xsl:value-of select="."/>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:attribute>
 </xsl:template>

 <xsl:template match="books[@category='X']"/>
 <xsl:template match="books[@category='Y']"/></xsl:transform>
A: 

This transformation:

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

    <xsl:key name="kBooksByCat" match="books"
    use="@category = 'Z'"/>

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

 <xsl:template match="/*">
  <xml>
    <xsl:variable name="vNonZ"
      select="key('kBooksByCat', 'false')"/>

    <xsl:variable name="vCatZ"
      select="key('kBooksByCat', 'true')"/>

    <xsl:if test="$vNonZ">
     <books category="A" count="{sum($vNonZ/@count)}">
       <xsl:apply-templates select="$vNonZ/node()"/>
     </books>
    </xsl:if>
    <xsl:if test="$vCatZ">
     <books category="B" count="{sum($vCatZ/@count)}">
       <xsl:apply-templates select="$vCatZ/node()"/>
     </books>
    </xsl:if>
  </xml>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<xml>
 <books category="X" count="2">
  <book name="bookx1"/>
  <book name="bookx2"/>
 </books>
 <books category="Y" count="3">
  <book name="booky1"/>
  <book name="booky2"/>
  <book name="booky3"/>
 </books>
 <books category="Z" count="2">
  <book name="bookz1"/>
  <book name="bookz2"/>
 </books>
 </xml>

produces the wanted, correct result:

<xml>
   <books category="A" count="5">
      <book name="bookx1"/>
      <book name="bookx2"/>
      <book name="booky1"/>
      <book name="booky2"/>
      <book name="booky3"/>
   </books>
   <books category="B" count="2">
      <book name="bookz1"/>
      <book name="bookz2"/>
   </books>
</xml>
Dimitre Novatchev
Wow, awesome! that's exactly what I needed. Thank you so much
gangt
@gangt: Glad my answer was useful. In SO gratitude is expressed by accepting the correct answer and/or upvoting useful answers.
Dimitre Novatchev
A: 

Another stylesheet without key:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="books[@category='Z'][1]">
        <xsl:variable name="us" select="../books[@category='Z']"/>
        <books category="B" count="{sum($us/@count)}">
            <xsl:apply-templates select="$us/node()"/>
        </books>
    </xsl:template>
    <xsl:template match="books[@category!='Z'][1]">
        <xsl:variable name="us" select="../books[@category!='Z']"/>
        <books category="A" count="{sum($us/@count)}">
            <xsl:apply-templates select="$us/node()"/>
        </books>
    </xsl:template>
    <xsl:template match="books"/>
</xsl:stylesheet>

Output:

<xml>
    <books category="A" count="5">
        <book name="bookx1"></book>
        <book name="bookx2"></book>
        <book name="booky1"></book>
        <book name="booky2"></book>
        <book name="booky3"></book>
    </books>
    <books category="B" count="2">
        <book name="bookz1"></book>
        <book name="bookz2"></book>
    </books>
</xml>

Note: Just for fun. The two books templates are so close. Maybe there is a way to express those in one template. Some complex translate?

Alejandro
Thanks to you both. Both xslt works like a charm
gangt