AutoTester999
AutoTester999

Reputation: 616

XSLT - How to split a string based on a delimiter and assign values to multiple attributes

Input XML:

<testng-results>
<suite>
    <test>
        <class>
            <test-method status="PASS" description="Test_ID:123,Test_Name:Test ABC,Product:Product ABC"></test-method>
            <test-method status="PASS" description="Test_ID:456,Test_Name:Test XYZ,Product:Product XYZ"></test-method>
        </class>
    </test>
</suite>
</testng-results>

My current XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:template match="/">
  <Suite>
     <xsl:for-each select="testng-results/suite/test/class/test-method">
        <test>
           <xsl:attribute name="status">
              <xsl:value-of select="@status" />
           </xsl:attribute>
           <xsl:attribute name="Test_ID">
              <xsl:value-of select="" />
           </xsl:attribute>
           <xsl:attribute name="Test_Name">
              <xsl:value-of select="" />
           </xsl:attribute>
           <xsl:attribute name="Product">
              <xsl:value-of select="" />
           </xsl:attribute>
        </test>
     </xsl:for-each>
  </Suite>

EXPECTED OUTPUT.XML:

<?xml version="1.0" encoding="UTF-8"?>
<Suite>
   <test status="PASS" Test_ID="123" Test_Name="Test ABC" Product="Product ABC" />
   <test status="PASS" Test_ID="456" Test_Name="Test XYZ" Product="Product XYZ" />
</Suite>

I have to get the string from the 'description' value and split the values to generate the output xml.

I have read that XSLT 2.0 is better equipped with string tokenize functions. But, I am restricted to use XSLT 1.0.

Upvotes: 1

Views: 3190

Answers (3)

michael.hor257k
michael.hor257k

Reputation: 116959

If your processor supports the EXSLT str:tokenize() extension function, you can do:

XSLT 1.0 + EXSLT

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="/testng-results">
    <Suite>
        <xsl:for-each select="suite/test/class/test-method">
            <test status="{@status}" >
                <xsl:for-each select="str:tokenize(@description, ',')">
                    <xsl:attribute name="{substring-before(., ':')}">
                        <xsl:value-of select="substring-after(., ':')" />
                    </xsl:attribute>
                </xsl:for-each>
            </test>
        </xsl:for-each>
    </Suite>
</xsl:template>

</xsl:stylesheet>

Otherwise you could just extract each of the 3 required values using string functions:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="/testng-results">
    <Suite>
        <xsl:for-each select="suite/test/class/test-method">
            <test 
                status="{@status}" 
                Test_ID="{substring-before(substring-after(@description, 'Test_ID:'), ',') }" 
                Test_Name="{substring-before(substring-after(@description, 'Test_Name:'), ',') }" 
                Product="{substring-after(@description, 'Product:')}"/>
        </xsl:for-each>
    </Suite>
</xsl:template>

</xsl:stylesheet>

Upvotes: 1

zx485
zx485

Reputation: 29022

You can achieve this with a named template retrieving the appropriate values by using xsl:call-template.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:template match="/">
      <Suite>
         <xsl:for-each select="testng-results/suite/test/class/test-method">
            <test>
               <xsl:attribute name="status">
                  <xsl:value-of select="@status" />
               </xsl:attribute>
               <xsl:attribute name="Test_ID">
                  <xsl:call-template name="subElem">
                      <xsl:with-param name="str" select="@description" />
                      <xsl:with-param name="itemName" select="'Test_ID'" />
                  </xsl:call-template>
               </xsl:attribute>
               <xsl:attribute name="Test_Name">
                  <xsl:call-template name="subElem">
                      <xsl:with-param name="str" select="@description" />
                      <xsl:with-param name="itemName" select="'Test_Name'" />
                  </xsl:call-template>
               </xsl:attribute>
               <xsl:attribute name="Product">
                  <xsl:call-template name="subElem">
                      <xsl:with-param name="str" select="@description" />
                      <xsl:with-param name="itemName" select="'Product'" />
                  </xsl:call-template>
               </xsl:attribute>
            </test>
         </xsl:for-each>
      </Suite>
  </xsl:template>

  <xsl:template name="subElem">
      <xsl:param name="str" />
      <xsl:param name="itemName" />
      <xsl:variable name="res" select="substring-after(substring-after($str,$itemName),':')" />
      <xsl:choose>
          <xsl:when test="contains($res,',')">
              <xsl:value-of select="substring-before($res,',')" />
          </xsl:when>     
          <xsl:otherwise>
              <xsl:value-of select="$res" />
          </xsl:otherwise>
      </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

The individual items are retrieved by their name.

Its output is:

<?xml version="1.0"?>
<Suite>
    <test status="PASS" Test_ID="123" Test_Name="Test ABC" Product="Product ABC"/>
    <test status="PASS" Test_ID="456" Test_Name="Test XYZ" Product="Product XYZ"/>
</Suite>

Upvotes: 1

Little Santi
Little Santi

Reputation: 8783

I propose this approach. It is quite rough, but it is sure, because it relays on standard XSLT elements and functions only:

(To tokenize, it implements a recursive algorithm.)

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method="xml" version="1.0" indent="yes"/>

<xsl:template match="/">
    <Suite>
        <xsl:apply-templates select="testng-results/suite/test/class/test-method"></xsl:apply-templates>
    </Suite>
</xsl:template>

<xsl:template match="test-method">
    <test>
        <xsl:call-template name="tokenize-to-attrs">
            <xsl:with-param name="s" select="@description"/>
        </xsl:call-template>
    </test> 
</xsl:template>

<xsl:template name="tokenize-to-attrs">
    <xsl:param name="s"/>
    <xsl:variable name="pair" select="substring-before($s, ',')"/>
    <xsl:if test="$pair">
        <xsl:call-template name="to-attr">
            <xsl:with-param name="s" select="$pair"/>
        </xsl:call-template>
        <xsl:call-template name="tokenize-to-attrs">
            <xsl:with-param name="s" select="substring-after($s, ',')"/>
        </xsl:call-template>
    </xsl:if>
    <xsl:if test="not($pair)">
        <xsl:call-template name="to-attr">
            <xsl:with-param name="s" select="$s"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

<xsl:template name="to-attr">
    <xsl:param name="s"/>
    <xsl:variable name="name" select="substring-before($s, ':')"/>
    <xsl:variable name="value" select="substring-after($s, ':')"/>
    <xsl:attribute name="{$name}">
        <xsl:value-of select="$value"/>
    </xsl:attribute>
</xsl:template>

</xsl:stylesheet>

Upvotes: 1

Related Questions