Jon Kra
Jon Kra

Reputation:

XSLT templates nodes inheritance?

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>

Upvotes: 0

Views: 532

Answers (2)

Daniel
Daniel

Reputation: 4977

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">
 <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>

Upvotes: 1

Martin v. L&#246;wis
Martin v. L&#246;wis

Reputation: 127537

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- 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>

Upvotes: 1

Related Questions