tags:

views:

71

answers:

2

Hi,

I am looking to transform a input xml given below

<profile name="default">
  <color id="forecolor" value="blue"></color>
  <color id="backcolor" value="white"></color>
  <color id="bordercolor" value="black"></color>
</profile>

<profile name="error" parent="default">
  <color id="forecolor" value="red"></color>
</profile>

<profile name="criticalerror" parent="error">
  <color id="bordercolor" value="red"></color>
</profile>

to be transformed like below:

<profile name="default">
  <color id="forecolor" value="blue"></color>
  <color id="backcolor" value="white"></color>
  <color id="bordercolor" value="black"></color>
</profile>
<profile name="error">
  <!--Below node is getting inherited from parent="default"-->
  <color id="backcolor" value="white"></color>
  <color id="bordercolor" value="black"></color>
  <!--Below node is overriden by this profile-->
  <color id="forecolor" value="red"></color>
</profile>
<profile name="criticalerror">
  <!--Below node is getting inherited from it's parent's parent="default"-->
  <color id="backcolor" value="white"></color>
  <!--Below node is overriden by its parent profile-->
  <color id="forecolor" value="red"></color>
  <color id="bordercolor" value="Red"></color>
</profile>

I am new to xslt world.. already spent hours on this.. please help me to get through this.

+1  A: 
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

<!-- Recursive function, tracing to parent profiles.
     $profile is the profile just visited, and $seenColors
     is the set of nodes that have been seen as overridden.-->
<xsl:template name="OutputColors">
  <xsl:param name="profile"/>
  <xsl:param name="seenColors"/>
  <xsl:if test="$profile/@parent">
    <xsl:call-template name="OutputColors">
      <xsl:with-param name="profile" select="//profile[@name=$profile/@parent]"/>
      <xsl:with-param name="seenColors" select="$seenColors | $profile/color"/>
    </xsl:call-template>
  </xsl:if>
  <xsl:for-each select="$profile/color">
    <xsl:if test="not(@id = $seenColors/@id)">
      <xsl:apply-templates select="."/>
    </xsl:if>
  </xsl:for-each>
</xsl:template>

<!-- template for profiles. Copy element and attributes;
     call recursive function for outputting colors. -->
<xsl:template match="profile">
  <xsl:variable name="currentProfile" select="."/>
  <profile name="@name">
   <xsl:copy-of select="@*"/>
   <xsl:call-template name="OutputColors">
     <xsl:with-param name="profile" select="current()"/>
     <xsl:with-param name="seenColors" select="empty"/>
   </xsl:call-template>
  </profile>
</xsl:template>

<!-- default rule. Copy everything not dealt with otherwise. -->
<xsl:template match="*|@*">
  <xsl:copy><xsl:copy-of select="@*"/><xsl:apply-templates/></xsl:copy>
</xsl:template>

</xsl:stylesheet>

as the output, I get, for your input (wrapping it with a data root node)

<?xml version="1.0"?>
<data>
<profile name="default"><color id="forecolor" value="blue"/><color id="backcolor" value="white"/><color id="bordercolor" value="black"/></profile>

<profile name="error"><color id="backcolor" value="white"/><color id="bordercolor" value="black"/><color id="forecolor" value="red"/></profile>

<profile name="criticalerror"><color id="backcolor" value="white"/><color id="forecolor" value="red"/><color id="bordercolor" value="red"/></profile>
</data>
Martin v. Löwis
I looked into doing it this way also but was turned off using the | operator because I read somewhere it may not always return the items in the expected order. That's why I went with a string. I like yours better though :)
Daniel
Notice that I don't iterate over the result of the union. I merely use it to test whether a value was overridden - so the order of nodes is irrelevant (it's a set, anyway).
Martin v. Löwis
Ah yes. Very nice :)
Daniel
Hi Daniel/Martin, are you getting below xml node in output after transform?<profile name="criticalerror"> <color id="backcolor" value="white"></color> <color id="forecolor" value="red"></color> <color id="bordercolor" value="Red"></color></profile>
See my edit for the output I get.
Martin v. Löwis
Thanks Martin it is working fine.
+1  A: 

I had a bit of a play this afternoon and came up with the following. It seems to work as you describe, although it probably isn't the most efficient way of doing it. I had fun :)

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="xml"/>

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

 <xsl:template match="profile">
  <profile name="{@name}">
   <xsl:call-template name="profile-colors">
    <xsl:with-param name="node" select="."/>
    <xsl:with-param name="seen-colors" select="''"/>
   </xsl:call-template>
  </profile>
 </xsl:template>

 <xsl:template name="profile-colors">
  <xsl:param name="node"/>
  <xsl:param name="seen-colors"/>

  <xsl:variable name="this-colors">
   <xsl:for-each select="$node/color">*<xsl:value-of select="@id"/>*</xsl:for-each>
  </xsl:variable>

  <xsl:for-each select="$node/color">
   <xsl:if test="not(contains($seen-colors, @id))">
    <xsl:copy-of select="."/>
   </xsl:if>
  </xsl:for-each>

  <xsl:variable name="parent" select="preceding-sibling::profile[@name=$node/@parent]"/>
  <xsl:if test="$parent">
   <xsl:call-template name="profile-colors">
    <xsl:with-param name="node" select="$parent"/>
    <xsl:with-param name="seen-colors" select="concat($seen-colors, $this-colors)"/>
   </xsl:call-template>
  </xsl:if>
 </xsl:template>

</xsl:stylesheet>

this gives the following output:

<?xml version="1.0"?>
<profiles>
<profile name="default"><color id="forecolor" value="blue"/><color id="backcolor" value="white"/><color id="bordercolor" value="black"/></profile>

<profile name="error"><color id="forecolor" value="red"/><color id="backcolor" value="white"/><color id="bordercolor" value="black"/></profile>

<profile name="criticalerror"><color id="bordercolor" value="red"/><color id="forecolor" value="red"/><color id="backcolor" value="white"/></profile>
</profiles>
Daniel
Thanks Daniel.. I tried your xslt, but it is not populating the last xml nodes <profile name ="CriticalError">.. can you please review it again. it is giving me output given below <profile name="criticalerror"> <color id="bordercolor" value="red" /> </profile>
I tried it, and it works fine for me.
Martin v. Löwis
Hi Daniel/Martin, are you getting below xml node in output after transform? <profile name="criticalerror"> <color id="backcolor" value="white"></color> <color id="forecolor" value="red"></color> <color id="bordercolor" value="Red"></color> </profile>
Hi Daneil/Martin.. thanks for the solution.. both of your transform is correct.. but it is not allowing me to select both answers.. thanks again.