Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using XSL to move from one node to another

Tags:

dom

xml

xslt

xpath

So I want to convert the following using XSL

<doc>
    <data id="priority" level="2" include="true">
        <name>Priority</name>
    </data>
    <data id="cost" level="1" leveltype="number">
        <name>Cost</name>
    </data>
    <data id="date" level="3" include="true">
        <name>Date</name>
    </data>
</doc>

To this

<doc>
    <data id="priority">
        <name>Priority</name>
    </data>
    <data id="cost">
        <name>Cost</name>
    </data>
    <data id="date">
        <name>Date</name>
    </data>

    <!-- ordering matters, though if necessary I can reorder this manually via the DOM instead of XSL -->
    <levels>   
        <level id="cost" include="false" type="number"/>
        <level id="priority" include="true"/>
        <level id="date" include="true"/>
    </level>
</doc>

Basically I want to take the level attributes and make them their own thing. A huge bonus would be if there were some way to remove the level number and use the order of the node instead to represent that.

like image 319
yoS Avatar asked May 16 '11 19:05

yoS


2 Answers

This is a shorter and simpler solution using only templates (no <xsl:for-each>):

<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="/*">
  <doc>
   <xsl:apply-templates select="*"/>
   <levels>
    <xsl:apply-templates select="data" mode="level">
     <xsl:sort select="@level" data-type="number"/>
    </xsl:apply-templates>
   </levels>
  </doc>
 </xsl:template>
 <xsl:template match="data/@*[not(name()='id')]"/>

 <xsl:template match="data" mode="level">
  <level id="{@id}" include="{boolean(@include)}">
   <xsl:if test="@leveltype">
    <xsl:attribute name="type"><xsl:value-of select="@leveltype"/></xsl:attribute>
   </xsl:if>
  </level>
 </xsl:template>
</xsl:stylesheet>

When applied on the provided XML document:

<doc>
    <data id="priority" level="2" include="true">
        <name>Priority</name>
    </data>
    <data id="cost" level="1" leveltype="number">
        <name>Cost</name>
    </data>
    <data id="date" level="3" include="true">
        <name>Date</name>
    </data>
</doc>

the wanted, correct result is produced:

<doc>
   <data id="priority">
      <name>Priority</name>
   </data>
   <data id="cost">
      <name>Cost</name>
   </data>
   <data id="date">
      <name>Date</name>
   </data>
   <levels>
      <level id="cost" include="false" type="number"/>
      <level id="priority" include="true"/>
      <level id="date" include="true"/>
   </levels>
</doc>

Explanation:

  1. Using and overriding the identity rule/template.

  2. Using mode="level" to generate the second part of the result-document.

like image 150
Dimitre Novatchev Avatar answered Oct 11 '22 10:10

Dimitre Novatchev


Just a variant:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="doc">

 <doc>

  <!-- build and sort data nodes -->
  <xsl:for-each select="data">
   <xsl:sort select="@id"/>
    <data id="{@id}">
     <xsl:copy-of select="name"/>
    </data>
   </xsl:for-each>

   <!-- build and sort levels -->
   <levels>
    <xsl:for-each select="data">
     <xsl:sort select="@id"/>
      <level id="{@id}" include="{boolean(@include)}">
       <xsl:if test="@leveltype">
        <xsl:attribute name="type">
         <xsl:value-of select="@leveltype"/>
        </xsl:attribute>
       </xsl:if>
      </level>
    </xsl:for-each>
   </levels>

 </doc>

 </xsl:template>
</xsl:stylesheet>
like image 37
Emiliano Poggi Avatar answered Oct 11 '22 10:10

Emiliano Poggi