Reputation:
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
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
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