xslt grouping question

Tag: xslt , xslt-2.0 Author: liupengfei_hugy Date: 2010-08-16

I have an xml

 <Root>
  <Parent>
    <Child1><Node1>AAA</Node1><Node2>BBB</Node2></Child1>
    <Child2><NodeX>XXX</NodeX><NodeY>YYY</NodeY></Child2>
    <Child1><Node1>EEE</Node1><Node2>FFF</Node2></Child1>
    <Child2><NodeX>GGG</NodeX><NodeY>HHH</NodeY></Child2>
    <OtherChild></OtherChild>

  </Parent>
</Root>

Child2 always will be with child1. I need to know how I can loop through using xsl:foreach and create an XML output example. I may have other nodes like <OtherChild> but my concern is only Child1 and Chid2 nodes

    <TransformedXML>
  <Child attributefromNode1="AAA" attributefromNode2="BBB" attributefromNodeX="XXX" attributeFromNodeY="YYY"/>
  <Child attributefromnode1="EEE" attributefromNode2="FFF" attributefromNodeX="GGG" attributeFromNodeY="HHH"/>
</TransformedXML>

My question is how do I loop in XSLT considering Child2 node will follow a Child1 always?

Tip: When you want to edit a post, please press edit link at bottom of question, don't use browser's back button. You would otherwise be reposting the same question again. Here's your original:
I'm surprised SO didn't catch this double post.
@deceze it did. Or, rather the mechanism SO uses to catch dupe questions did. Which is the community.
Good question again (+1). See my answer for a short and efficient solution :)

Best Answer

I this something like this should do the trick:

  <xsl:stylesheet version="1.0"                 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns="http://www.w3.org/TR/xhtml1/strict">
  <xsl:output
   method="xml"
   indent="yes" />

  <xsl:template match="/">
<xsl:for-each select="Root/Parent/Child1">
<Child>
  <xsl:attribute name="attributefromNode1">
    <xsl:value-of select="Node1" />

  </xsl:attribute>
  <xsl:attribute name="attributefromNode2" >
    <xsl:value-of select="Node2" />

  </xsl:attribute>
  <xsl:attribute name="attributefromNodeX">
    <xsl:value-of select="following-sibling::Child2/NodeX" />

  </xsl:attribute>
  <xsl:attribute name="attributefromNodeY">
    <xsl:value-of select="following-sibling::Child2/NodeY" />

  </xsl:attribute>



</Child>   
    </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

comments:

if you are goint to hardcode elements and attributes names, why you won't use literal result elements and attribute value templates as DevNull answer? This is hardcode and verbose.

Other Answer1

You shouldn't have to loop...

XML INPUT

<Root>
  <Parent>
    <Child1><Node1>AAA</Node1><Node2>BBB</Node2></Child1>
    <Child2><NodeX>XXX</NodeX><NodeY>YYY</NodeY></Child2>
    <Child1><Node1>EEE</Node1><Node2>FFF</Node2></Child1>
    <Child2><NodeX>GGG</NodeX><NodeY>HHH</NodeY></Child2>
    <OtherChild></OtherChild>

  </Parent>
</Root>

XSL

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

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

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

  <xsl:template match="Child1">
    <Child attributefromNode1="{Node1}" 
      attributefromNode2="{Node2}" 
      attributefromNodeX="{following-sibling::Child2[1]/NodeX}" 
      attributefromNodeY="{following-sibling::Child2[1]/NodeY}"/>  
  </xsl:template>

</xsl:stylesheet>

XML OUTPUT

<?xml version="1.0" encoding="UTF-8"?>
<TransformedXML>
   <Child attributefromNode1="AAA" attributefromNode2="BBB" attributefromNodeX="XXX"
          attributefromNodeY="YYY"/>
   <Child attributefromNode1="EEE" attributefromNode2="FFF" attributefromNodeX="GGG"
          attributefromNodeY="HHH"/>
</TransformedXML>

Other Answer2

Here is (almost the same as before) a short solution:

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

 <xsl:key name="kFollowingChild1" match="*[not(self::Child1)]/*"
  use="generate-id(../preceding-sibling::Child1[1])"/>

 <xsl:template match="Parent">
  <TransformedXML>
   <xsl:apply-templates/>
  </TransformedXML>
 </xsl:template>

 <xsl:template match="Child1">
  <Child>
   <xsl:for-each select="*|key('kFollowingChild1', generate-id())">
    <xsl:attribute name="attribute{position()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
   </xsl:for-each>
  </Child>
 </xsl:template>

 <xsl:template match="text()"/>
</xsl:stylesheet>

when this transformation is applied to the provided XML document:

<Root>
  <Parent>
    <Child1><Node1>AAA</Node1><Node2>BBB</Node2></Child1>
    <Child2><NodeX>XXX</NodeX><NodeY>YYY</NodeY></Child2>
    <Child1><Node1>EEE</Node1><Node2>FFF</Node2></Child1>
    <Child2><NodeX>GGG</NodeX><NodeY>HHH</NodeY></Child2>
    <OtherChild></OtherChild>
  </Parent>
</Root>

the wanted, correct result is produced:

<TransformedXML>
   <Child attribute1="AAA" attribute2="BBB" attribute3="XXX" attribute4="YYY"/>
   <Child attribute1="EEE" attribute2="FFF" attribute3="GGG" attribute4="HHH"/>
</TransformedXML>

Other Answer3

This XSLT 2.0 stylesheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:template match="Parent">
        <TransformedXML>
            <xsl:apply-templates select="Child1"/>
        </TransformedXML>
    </xsl:template>
    <xsl:template match="Child1">
        <Child>
            <xsl:apply-templates select="*|following-sibling::Child2[1]/*"/>
        </Child>
    </xsl:template>
    <xsl:template match="Child1/*|Child2/*">
        <xsl:attribute name="attributefrom{name()}">
            <xsl:value-of select="."/>
        </xsl:attribute>
    </xsl:template>
</xsl:stylesheet>

Output:

<TransformedXML>
    <Child attributefromNode1="AAA" attributefromNode2="BBB" attributefromNodeX="XXX" attributefromNodeY="YYY"/>
    <Child attributefromNode1="EEE" attributefromNode2="FFF" attributefromNodeX="GGG" attributefromNodeY="HHH"/>
</TransformedXML>