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].
Related
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>
How do I output just the last duplicate node WITHIN the upper/parent element 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>
<class name="system.apps.CreateProposal">
<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:key name="test-by-name" match="test-method[not(#is-config)]" use="#name" />
<xsl:template match="/">
<Suite>
<xsl:for-each select="testng-results/suite/test/class">
<xsl:for-each select="test-method[not(#is-config)][count(. | key('test-by-name', #name)[last()]) = 1]">
<Test>
<Class_Name>
<xsl:value-of select="../#name"/>
</Class_Name>
<Method_Name>
<xsl:value-of select="#name"/>
</Method_Name>
<Status>
<xsl:value-of select="#status"/>
</Status>
</Test>
</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 (the problem is that the duplicity is being checked ACROSS classes instead of WITHIN that class):
<?xml version="1.0" encoding="UTF-8"?>
<Suite>
<Test>
<Class_Name>system.apps.CreateTerritory</Class_Name>
<Method_Name>ABC</Method_Name>
<Status>FAIL</Status>
</Test>
<Test>
<Class_Name>system.apps.CreateProposal</Class_Name>
<Method_Name>DEF</Method_Name>
<Status>FAIL</Status>
</Test>
</Suite>
Expected Output.xml (only the last node is outputted for every duplicate test-method WITHIN THAT CLASS):
<?xml version="1.0" encoding="UTF-8"?>
<Suite>
<Test>
<Class_Name>system.apps.CreateTerritory</Class_Name>
<Method_Name>ABC</Method_Name>
<Status>FAIL</Status>
</Test>
<Test>
<Class_Name>system.apps.CreateAccount</Class_Name>
<Method_Name>DEF</Method_Name>
<Status>FAIL</Status>
</Test>
<Test>
<Class_Name>system.apps.CreateProposal</Class_Name>
<Method_Name>DEF</Method_Name>
<Status>FAIL</Status>
</Test>
</Suite>
As I mentioned in a comment on your previous question, you need to add a class identifier to the key. Assuming that each class has a unique name, this could be:
<xsl:key name="test-by-name" match="test-method[not(#is-config)]" use="concat(#name, '|', ../#name)" />
Then:
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="concat(#name, '|', ../#name)" />
<xsl:template match="/testng-results">
<Suite>
<xsl:for-each select="suite/test/class/test-method[not(#is-config)][count(. | key('test-by-name', concat(#name, '|', ../#name))[last()]) = 1]">
<Test>
<Class_Name>
<xsl:value-of select="../#name"/>
</Class_Name>
<Method_Name>
<xsl:value-of select="#name"/>
</Method_Name>
<Status>
<xsl:value-of select="#status"/>
</Status>
</Test>
</xsl:for-each>
</Suite>
</xsl:template>
</xsl:stylesheet>
will return:
Result
<?xml version="1.0" encoding="UTF-8"?>
<Suite>
<Test>
<Class_Name>system.apps.CreateTerritory</Class_Name>
<Method_Name>ABC</Method_Name>
<Status>FAIL</Status>
</Test>
<Test>
<Class_Name>system.apps.CreateAccount</Class_Name>
<Method_Name>DEF</Method_Name>
<Status>FAIL</Status>
</Test>
<Test>
<Class_Name>system.apps.CreateProposal</Class_Name>
<Method_Name>DEF</Method_Name>
<Status>FAIL</Status>
</Test>
</Suite>
I am using xslt 1.0 My input xml is as below
<?xml version="1.0" encoding="UTF-8"?>
<Employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<FirstName>John</FirstName>
<LastName>Peter</LastName>
<Initial>T</Initial>
<Spouse>
<FirstName>Rita</FirstName>
<LastName>Hudson</LastName>
</Spouse>
</Employee>
I an trying to write a xsl to produce below output...
<?xml version="1.0" encoding="UTF-8"?>
<ArrayOfstringVariable xmlns="http://schemas.abc.org/2004/07/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<stringVariable>
<name>ServerName</name>
<value>tmn.eu.com</value>
</stringVariable>
<stringVariable>
<name>EmpFirstName</name>
<value>John</value>
</stringVariable>
<stringVariable>
<name>EmpLastName</name>
<value>Peter</value>
</stringVariable>
<stringVariable>
<name>SpouseFirstName</name>
<value>Rita</value>
</stringVariable>
<stringVariable>
<name>SpouseLastName</name>
<value>Hudson</value>
</stringVariable>
</ArrayOfstringVariable>
The output xml contains ArrayOfstringVariable stringVariable name value pair..
The name is hardcoded and the value is from input xml..
Name value "ServerName" is hardcoded.
I tried with xsl code below but it create name value pair with all the elements from input xml
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="no"
encoding="UTF-8" indent="yes" />
<xsl:template match="Employee">
<ArrayOfstringVariable>
<xsl:apply-templates select="*"/>
</ArrayOfstringVariable>
</xsl:template>
<xsl:template match="*">
<stringVariable>
<name>
<xsl:value-of select="local-name()"/>
</name>
<value>
<xsl:value-of select="."/>
</value>
</stringVariable>
</xsl:template>
</xsl:stylesheet>
Can anyone help me to write xsl to produce above output?
Thanks in advance
The XSLT will be straight forward as most of your elements are hardcoded:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:ns="http://schemas.abc.org/2004/07/">
<xsl:output method="xml" omit-xml-declaration="no" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Employee">
<ns:ArrayOfstringVariable>
<ns:stringVariable>
<ns:name>ServerName</ns:name>
<ns:value>tmn.eu.com</ns:value>
</ns:stringVariable>
<ns:stringVariable>
<ns:name>EmpFirstName</ns:name>
<ns:value>
<xsl:value-of select="FirstName"/>
</ns:value>
</ns:stringVariable>
<ns:stringVariable>
<ns:name>EmpLastName</ns:name>
<ns:value>
<xsl:value-of select="LastName"/>
</ns:value>
</ns:stringVariable>
<ns:stringVariable>
<ns:name>SpouseFirstName</ns:name>
<ns:value>
<xsl:value-of select="Spouse/FirstName"/>
</ns:value>
</ns:stringVariable>
<ns:stringVariable>
<ns:name>SpouseLastName</ns:name>
<ns:value>
<xsl:value-of select="Spouse/LastName"/>
</ns:value>
</ns:stringVariable>
</ns:ArrayOfstringVariable>
</xsl:template>
</xsl:stylesheet>
Please look at example:
xslt:
<?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:strip-space elements="*"/>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="name()" case-order="upper-first"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
source xml:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<Z>Z</Z>
<a>a</a>
<A>A</A>
<B>B</B>
<k>k</k>
</Test>
Result is. This is case insensitive order. How to force upper-case letters be first?
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<A>A</A>
<a>a</a>
<B>B</B>
<k>k</k>
<Z>Z</Z>
</Test>
But, i need something like this:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<A>A</A>
<B>B</B>
<Z>Z</Z>
<a>a</a>
<k>k</k>
</Test>
What I'm doing wrong?
You may need to sort by the case in a separate sort instruction:
<xsl:apply-templates>
<xsl:sort select="translate(name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '00000000000000000000000000')" />
<xsl:sort select="name()"/>
</xsl:apply-templates>
This solution might need some testing though.
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>