Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT: Move node one level up

Tags:

xml

xslt

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

like image 264
Jens Avatar asked Jan 24 '11 08:01

Jens


3 Answers

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>
like image 176
Michael Kay Avatar answered Sep 18 '22 20:09

Michael Kay


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>
like image 38
dogbane Avatar answered Sep 17 '22 20:09

dogbane


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>
like image 22
Dimitre Novatchev Avatar answered Sep 21 '22 20:09

Dimitre Novatchev