I would like to know how to move nodes up one level using XSLT if certain conditions are complied. To give you an example have a look at the following XML source:
<Settings>
<String [...]>
<Boolean [...]/>
</String>
</Settings>
That is the XML I have as starting situation. To be clear, the node names "Settings", "String", "Boolean" are special nodes we defined.
The problem is that "Boolean" nodes are not allowed inside of "String" nodes. That's why I have to move those "Boolean" nodes on level up. The desired XML would look like this:
<Settings>
<String [...]></String>
<Boolean [...]/>
</Settings>
The XSLT also has to work with every String node which has a sibling Boolean node, regardless of the position in the XML-tree.
So far I learned that you have to first copy all of your XML using a "identity rule" and then apply some special rules for the desired transformations:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
<!-- Identity rule -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<!-- special rules ... -->
</xsl:stylesheet>
The thing I am struggling with is the rule to move all "Boolean" nodes which are siblings of "String" nodes one level up. How can I do that?!?
My interpretation of the requirement gives the solution as
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="String">
<xsl:copy>
<xsl:apply-templates select="child::node()[not(self::Boolean)]"/>
</xsl:copy>
<xsl:apply-templates select="Boolean"/>
</xsl:template>
</xsl:stylesheet>
Try the following:
<!-- copy all -->
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<!-- ignore booleans-inside-strings -->
<xsl:template match="String/Boolean" />
<!-- place the booleans-inside-strings into the settings element -->
<xsl:template match="Settings">
<xsl:copy>
<xsl:copy-of select="//String/Boolean" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
This solution is quite similar to that of @Michae-Kay. However, the overriding of the identity rule is a little bit more precise -- only String
elements that really have a Boolean
child are matched:
<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:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="String[Boolean]">
<xsl:copy>
<xsl:apply-templates select="node()[not(self::Boolean)]|@*"/>
</xsl:copy>
<xsl:apply-templates select="Boolean"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied against the following XML document:
<Settings>
<String>
<Boolean />
</String>
<String/>
</Settings>
the wanted, correct result is produced:
<Settings>
<String/>
<Boolean/>
<String/>
</Settings>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With