XPath to get sibling node's value - xslt

I'm using a testing tool that generates XML like this
<detail>
<results>
<component>A</component>
<test>
<property>performance</property>
<result>FAIL</result>
</test>
<test>
<property>memory</property>
<result>PASS</result>
</test>
</results>
<results>
<component>B</component>
<test>
<property>files</property>
<result>PASS</result>
</test>
<test>
<property>handles</property>
<result>FAIL</result>
</test>
</results>
</detail>
Note that each results element contains 1 component element and 0-* test elements. I want to get the text of each detail>results>component and detail>results>test>property where a detail>results>test>result text is 'FAIL'.
Is there an XPath expression that would do this?
If not, is there a simple XSLT to transform the example into something I can import into a spreadsheet like
A,performance,FAIL
A,memory,PASS
B,files,PASS
B,handles,FAIL

This Xpath will give you the properties that have failed:
//detail/results/test[result = 'FAIL']/property
And this will give you the names of the components that have a partial fail:
//detail/results[test/result = 'FAIL']/component
Combining these you can find all results that have a fail:
//detail/results[test/result = 'FAIL']
And then iterate inside this to get all failed tests in that result and returns the proprty that failed:
$result/test[result = 'FAIL']/property
I'm not on a machine where I can test an XSL yet, but putting one together from the above XPaths shouldn't be difficult.

When the below transformations
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="detail">
<xsl:for-each select="results">
<xsl:for-each select="test">
<xsl:value-of select="preceding-sibling::component"/><xsl:text>,</xsl:text>
<xsl:value-of select="child::property"/><xsl:text>,</xsl:text>
<xsl:value-of select="child::result"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
runs on below XML
<detail>
<results>
<component>A</component>
<test>
<property>performance</property>
<result>FAIL</result>
</test>
<test>
<property>memory</property>
<result>PASS</result>
</test>
</results>
<results>
<component>B</component>
<test>
<property>files</property>
<result>PASS</result>
</test>
<test>
<property>handles</property>
<result>FAIL</result>
</test>
</results>
</detail>
gives the required Output:
A,performance,FAIL
A,memory,PASS
B,files,PASS
B,handles,FAIL

I would recommend to prefer 'template match' and apply-template before for-each.
Therefor try this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:strip-space elements="*" />
<xsl:output method="xml" indent="yes"/>
<xsl:template match="detail">
<xsl:apply-templates select="results[test/result='FAIL']/test" />
</xsl:template>
<xsl:template match="results/test">
<xsl:value-of select="../component"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="property"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="result"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

Related

XSLT 1.0 (xsltproc) - How to use XSLT to parse XML values and add them into an already formed XML?

Output.xml is already formed.
I have to parse Input.xml to find Feature_ID for a Test_ID from the mapping in Input.xml and add it to Output.xml.
I was thinking this can be done with XSLT. How would the XSLT look like?
Input.xml
<Mapping>
<Test>
<Test_ID>123</Test_ID>
<Feature_ID>111</Feature_ID>
</Test>
<Test>
<Test_ID>456</Test_ID>
<Feature_ID>222</Feature_ID>
</Test>
</Mapping>
Current (already formed) Output.xml
<?xml version="1.0" encoding="UTF-8"?>
<Suite>
<Test>
<Test_ID>123</Test_ID>
<Test_Name>Test_First</Test_Name>
</Test>
<Test>
<Test_ID>456</Test_ID>
<Test_Name>Test_Second</Test_Name>
</Test>
</Suite>
Desired Output.xml
<Suite>
<Test>
<Test_ID>123</Test_ID>
<Test_Name>Test_First</Test_Name>
<Feature_ID>111</Feature_ID>
</Test>
<Test>
<Test_ID>456</Test_ID>
<Test_Name>Test_Second</Test_Name>
<Feature_ID>222</Feature_ID>
</Test>
</Suite>
Also, how to pass Output.xml in the below command?
xsltproc XSLT.xsl Input.xml > Output_New.xml
To copy elements from one document to another, consider document() function in an XSLT script. Then call xsltproc on only run the main input document.
Actually, depending on your desired result, Input should be Output and vice versa since the root is Suite.
XSLT (notice Input.xml referenced inside)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Suite">
<xsl:copy>
<xsl:apply-templates select="Test"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Test">
<xsl:copy>
<xsl:variable name="curr_id" select="Test_ID"/>
<xsl:variable name="expr" select="document('FeatureID_Mapping.xml')/Mapping/Test[Test_ID = $curr_id]"/>
<xsl:copy-of select="Test_ID|Test_Name"/>
<xsl:choose>
<xsl:when test="$expr">
<xsl:copy-of select="$expr/Feature_ID"/>
</xsl:when>
<xsl:otherwise>
<Feature_ID/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
xsltproc
xsltproc myScript.xsl Output.xml > myDesiredResult.xml
Result
<Suite>
<Test>
<Test_ID>123</Test_ID>
<Test_Name>Test_First</Test_Name>
<Feature_ID>111</Feature_ID>
</Test>
<Test>
<Test_ID>456</Test_ID>
<Test_Name>Test_Second</Test_Name>
<Feature_ID>222</Feature_ID>
</Test>
</Suite>

XSLT 1.0 (xsltproc) - Output only the last duplicate node

How do I output just the last duplicate node using XSLT 1.0?
I use xsltproc processor.
Input.xml
<testng-results>
<suite>
<test>
<class name="system.apps.CreateTerritory">
<test-method status="PASS" name="initTest" is-config="true"> </test-method>
<test-method status="FAIL" name="ABC"> </test-method>
</class>
<class name="system.apps.CreateAccount">
<test-method status="PASS" name="initTest" is-config="true"> </test-method>
<test-method status="SKIP" name="DEF"> </test-method>
<test-method status="PASS" name="initTest" is-config="true"> </test-method>
<test-method status="FAIL" name="DEF"> </test-method>
</class>
</test>
</suite>
</testng-results>
My Current XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings" extension-element-prefixes="str" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<Suite>
<xsl:for-each select="testng-results/suite/test/class">
<xsl:for-each select="test-method">
<xsl:if test="not(#is-config)">
<Test>
<Method_Name>
<xsl:value-of select="#name"/>
</Method_Name>
<Status>
<xsl:value-of select="#status"/>
</Status>
</Test>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</Suite>
</xsl:template>
</xsl:stylesheet>
Note: I can't change the way the nested match is done (for-each class and then for-each test-method as I need to be this way for other reasons)
Current Output.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Suite>
<Test>
<Method_Name>ABC</Method_Name>
<Status>FAIL</Status>
</Test>
<Test>
<Method_Name>DEF</Method_Name>
<Status>SKIP</Status>
</Test>
<Test>
<Method_Name>DEF</Method_Name>
<Status>FAIL</Status>
</Test>
</Suite>
Expected Output.xml (only the last node is outputted for every duplicate test-method):
<?xml version="1.0" encoding="UTF-8"?>
<Suite>
<Test>
<Method_Name>ABC</Method_Name>
<Status>FAIL</Status>
</Test>
<Test>
<Method_Name>DEF</Method_Name>
<Status>FAIL</Status>
</Test>
</Suite>
If you're using xsltproc (that is the libxslt processor), you can take advantage of the EXSLT set:distinct() extension function in order to utilize a shortened version of Muenchian grouping:
XSLT 1.0 (+EXSLT)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="set">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="test-by-name" match="test-method[not(#is-config)]" use="#name" />
<xsl:template match="/testng-results">
<xsl:variable name="tests" select="suite/test/class/test-method[not(#is-config)]" />
<Suite>
<xsl:for-each select="set:distinct($tests/#name)">
<Test>
<Method_Name>
<xsl:value-of select="."/>
</Method_Name>
<Status>
<xsl:value-of select="key('test-by-name', .)[last()]/#status"/>
</Status>
</Test>
</xsl:for-each>
</Suite>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer, you can do it in pure XSLT 1.0 as:
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:key name="test-by-name" match="test-method[not(#is-config)]" use="#name" />
<xsl:template match="/testng-results">
<Suite>
<xsl:for-each select="suite/test/class/test-method[not(#is-config)][count(. | key('test-by-name', #name)[last()]) = 1]">
<Test>
<Method_Name>
<xsl:value-of select="."/>
</Method_Name>
<Status>
<xsl:value-of select="#status"/>
</Status>
</Test>
</xsl:for-each>
</Suite>
</xsl:template>
</xsl:stylesheet>
Note the use of the [last()] predicate instead of the usual [1].

xslt - combining subelements matching on their element text

I'm trying to come up with an XSLT transform template that can combine sub-elements matched by their element text (which can be anything)
Example input XML
<test>
<name>Alpha</name>
<value>11</value>
</test>
<test>
<name>Beta</name>
<value>14</value>
</test>
<test>
<name>Alpha</name>
<value>22</value>
</test>
I would like the transform to combine the subelements with matching elements, so any subelements with name Alpha would be grouped in the output.
Example result XML
<test>
<name>Alpha</name>
<valuelist>11,22</valuelist>
</test>
<test>
<name>Beta</name>
<valuelist>14</valuelist>
<test>
The values of the text for name can be anything, so cannot be specifically noted in the template match.
Using XSLT 2.0:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="test" group-by="name">
<test>
<xsl:copy-of select="name"/>
<valuelist>
<xsl:value-of select="current-group()/value" separator=","/>
</valuelist>
</test>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
Online sample at http://xsltransform.net/ej9EGbV.

Match node of called template

lets say I have a random template in my xsl:
<xsl:template name="keywords">
<test>
<foo>bar</foo>
<bar>foo</bar>
</test>
<test>
<foo>foobar</foo>
<bar>barfoo</bar>
</test>
<xsl:template>
I want to output let's say only the first node set. Is there an elegant way to do this?
How can I match the node if it is not in the source xml, but the result of a called template?
Thanks!
If you save the result of calling the template in a variable then you can extract parts of it using XPath
<xsl:variable name="result">
<xsl:call-template name="keywords"/>
</xsl:variable>
<xsl:sequence select="$keywords/test[1]" />
You can access the nodes inside a named template using an Xpath expression like:
document('')/xsl:stylesheet/xsl:template[#name='keywords']/test[1]
Added:
For example, the following stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<output>
<xsl:copy-of select="document('')/xsl:stylesheet/xsl:template[#name='keywords']/test[1]"/>
</output>
</xsl:template>
<xsl:template name="keywords">
<test>
<foo>bar</foo>
<bar>foo</bar>
</test>
<test>
<foo>foobar</foo>
<bar>barfoo</bar>
</test>
</xsl:template>
</xsl:stylesheet>
when applied to any well-formed XML input, will return:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<test xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<foo>bar</foo>
<bar>foo</bar>
</test>
</output>
Note: you can get rid of the (harmless) redundant namespace declaration by applying templates instead of deep-copying.

Accessing value of param in xsl

I am trying to get the value of the parameter of template named as is Round from the following code but it returns me the blank, can anybody tell me that how to get value of parameter in template
<xsl:apply-templates select="//solution">
<xsl:with-param name="isRound">N</xsl:with-param>
</xsl:apply-templates>
<xsl:template match="solution">
<xsl:param name="isRound" />
<test>
<xsl:value-of select="$isRound"/>
</test>
</xsl:template>
The Output of this is:
<test></test>
Actually this works. I try it as following. Adding other necessary things such as stylesheet root element.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:apply-templates select="//solution">
<xsl:with-param name="isRound">N</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="solution">
<xsl:param name="isRound" />
<test>
<xsl:value-of select="$isRound"/>
</test>
</xsl:template>
</xsl:stylesheet>
Input for example.
<root>
<solution/>
</root>
Result
<?xml version="1.0"?>
<test>N</test>