Showing posts with label XSLT. Show all posts
Showing posts with label XSLT. Show all posts

Thursday, 7 August 2014

XPATH to check if a given node is present or not

To check if a node is present in a XML or not simply use boolean function

So for the XML


<person id="1"> <name>John Smith</name> <phone_home>12345</phone_home> <phone_office></phone_office> </person>





If we want to know if phone_home is present or not then simply say

boolean(/person/phone_office)

Similarly if you want to check if any phone is present or not

boolean(/person/*[contains(name(.),"phone")])

and in the same way it can be extended to more complex queries, like if any phone is empty

boolean(/person/*[contains(name(.),"phone") and .=""])

or if any or the nodes are empty:

boolean(/person/*[.=""])


Same approach can be applied based on node content.

So for XML


<jobs> <job seq="1">Job 1 Completed with status success</job> <job seq="2">Job 2 Completed with status success</job> <job seq="3">Job 3 Completed with status failure</job> <job seq="4">Job 4 Completed with status success</job> </jobs>







If we need to know if any of the jobs failed:

boolean(/jobs/job[contains(.,"status failure")])

Also, the same approach can be used based on attribute name

So for XML


<details> <detail name="John Smith"/> <detail phone_home="12345"/> <detail phone_office="54321"/> </details>






If we need to know if any of the attribute has phone in attribute name then:

boolean(/details/detail[contains(name(./@*), "phone")])

And same as with node names we can check if any of phone is empty:

boolean(/details/detail[contains(name(./@*), "phone") and ./@* = ""])

or if any of the attribute is empty

boolean(/details/detail[./@* = ""])

Hope this helps

Tuesday, 31 December 2013

Recursive XSLT: Concat Multiple Nodes

Sharing this XSLT which I used couple of years back to concat a node set. The usecase was to extract a specific node set and concatenate their values.

Sample Input:
<root>
    <node>
        <data>123</data>
        <data>456</data>
        <junk>abc</junk>
    </node>
    <junknode>
        <junk>def</junk>
    </junknode>
    <node>
        <data>789</data>
        <junk>ghi</junk>
        <data>012</data>
    </node>
</root>


Desired Output:
<out>
  <mssg>123456789012</mssg>
  <junk>abcdefghi</junk>
</out>


XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output indent="yes" />
  <xsl:template match="/">
    <out>
      <xsl:variable name="mssg_data">
        <xsl:call-template name="ConCat">
          <xsl:with-param name="List" select="/root/node/data" />
        </xsl:call-template>
      </xsl:variable>
      <xsl:variable name="junk_data">
        <xsl:call-template name="ConCat">
          <xsl:with-param name="List" select="//junk" />
        </xsl:call-template>
      </xsl:variable>
      <mssg>
        <xsl:value-of select="$mssg_data" disable-output-escaping="no" />
      </mssg>
      <junk>
        <xsl:value-of select="$junk_data"/> 
      </junk>
    </out>
  </xsl:template>
  <xsl:template name="ConCat">
    <xsl:param name="List" />
    <xsl:param name="result" select="''" />
    <xsl:choose>
      <xsl:when test="$List">
        <xsl:call-template name="ConCat">
          <xsl:with-param name="List" select="$List[position() &gt; 1]" />
          <xsl:with-param name="result" select="concat($result, $List[1])" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$result" disable-output-escaping="no" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>


Note: The similar result can be achieved by using XPATH evaluator to get text of data node set (ie /root/node/data/text()), depending on the requirement one or the other approach may be more suitable.

Monday, 30 December 2013

Looping in XSLT: Recursive Template Calls

To loop in XSLT you need to call a template recursively.

In past I have done this using two approaches:

Option-1: While Loop
Logic is similar to a while loop. So the counter is decremented till it is greater than 0.
 
 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output indent="no" />
  <xsl:template match="/">
    <LoopInput>
      <xsl:call-template name="CreateLoop">
        <xsl:with-param name="count" select="${numLoop}" />
      </xsl:call-template>
    </LoopInput>
  </xsl:template>
  <xsl:template name="CreateLoop">
    <xsl:param name="count" />
    <xsl:if test="$count &gt; 0">
      <xsl:call-template name="CreateLoop">
        <xsl:with-param name="count" select="$count - 1" />
      </xsl:call-template>
      <Line>
        <xsl:text disable-output-escaping="no">Line Data: </xsl:text>
        <xsl:value-of select="$count" disable-output-escaping="no" />
      </Line>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>


Option-2: For Loop
Here counter is incremented from a initial count till it reaches the desired loop count.

 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output indent="no" />
  <xsl:template match="/">
    <LoopInput>
      <xsl:call-template name="CreateLoop">
        <xsl:with-param name="MaxLoop" select="${numLoop}" />
        <xsl:with-param name="iLoop" select="1" />
      </xsl:call-template>
    </LoopInput>
  </xsl:template>
  <xsl:template name="CreateLoop">
    <xsl:param name="MaxLoop" />
    <xsl:param name="iLoop" />
    <Line>
      <xsl:text disable-output-escaping="no">Line Data: </xsl:text>
      <xsl:value-of select="$iLoop" disable-output-escaping="no" />
    </Line>
    <xsl:if test="$iLoop &lt; $MaxLoop">
      <xsl:call-template name="CreateLoop">
        <xsl:with-param name="MaxLoop" select="$MaxLoop" />
        <xsl:with-param name="iLoop" select="$iLoop + 1" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Thursday, 21 June 2012

Recursive XSLT to filter nodes

We all know that one can surely do a lot of cool stuff with XSL but still sometimes you'll come across some small piece of code which will impress you with its simplicity and yet effectiveness. Here is my favorite piece of  XSLT code:

 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>


All it does it create a copy of input XML. But it can be modified to do a lot of neat stuff. Here are a few examples:

----------------------------- Example-1: Convert attributes to fields -----------------------------

Source XML:

<root att1="att1">
  <child1  att2="att2">
    <child2  name="field_name1" value="field_value1"/>
    <child2  name="field_name2" value="field_value2"/>
  </child1>
</root>

Lets say my target application does not like attributes and it wants data as XML nodes. So the desired output is:
<?xml version="1.0" encoding="UTF-8"?>

<root att1="att1">
    <child1 att2="att2">
        <field_name1>field_value1</field_name1>
        <field_name2>field_value2</field_name2>
    </child1>
</root>


Here is the XSL code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="child2">
    <xsl:element name="{@name}">
      <xsl:value-of select="@value"/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>


----------------------------- Example-2 Filter Specific Data -----------------------------

Source XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <node>
    <data1>null</data1>
    <data2>data</data2>
    <data3/>
    <data4>null</data4>
    <data5></data5>
  </node>
</root>


Desired Output is:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <node>
    <data2>data</data2>
    <data3/>
    <data5/>
  </node>
</root>

This is very common in integration scenarios. Application-A gives out data will no value as 'null' values but Application-B takes this 'null' as a string. So best way forward is to filter out these 'null' nodes.

Here is the XSL code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>

  <xsl:template match="node()|@*">
          <xsl:if test=". !='null'">
            <xsl:copy>
              <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
          </xsl:if>
  </xsl:template>


</xsl:stylesheet>

Awesome isn't it? Other way would be to put a check on each node for value 'null' before it is sent to Application-B, which would be just painfully annoying and plainly a bad approach.


----------------------------- Example-3: Update Selective Nodes -----------------------------

How about the other way around. So now we have some empty nodes and we want their value set as 'null'

Source XML:
<root att1="a1">
  <child att2="a2">
    <data att3="a3"></data>
    <data att3="a3">data</data>
    <data att3="a3"/>
  </child>
</root>


Desired Output:
<?xml version="1.0" encoding="UTF-8"?>
<root att1="a1">
    <child att2="a2">
        <data>null</data>
        <data att3="a3">data</data>
        <data>null</data>
    </child>
</root>



Here is the XSL code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>

  <xsl:template match="node()|@*">
          <xsl:choose>
          <xsl:when test="string-length(.) = 0">
            <xsl:element name="{name()}">
                <xsl:text disable-output-escaping="no">null</xsl:text>
            </xsl:element>
          </xsl:when>
          <xsl:otherwise>
            <xsl:copy>
              <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
          </xsl:otherwise>
        </xsl:choose>
  </xsl:template>
</xsl:stylesheet>


These are just a few examples but we can use this XSL in many ways to reformat/restructure a XML. There is always a possibility for a better approach. So guys if you know anything better then do update me, either in comments or just shoot me a mail.