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.
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>
I am looking for a simple split of an xml file just based on tags; say 3 tags always repeat and need split as depicted below:
Input
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<tag1>A</tag1>
<tag2>B</tag2>
<tag3>C</tag3>
<tag1>1</tag1>
<tag2>2</tag2>
<tag3>3</tag3>
<tag1>apple</tag1>
<tag2>orange</tag2>
<tag3>mango</tag3>
</Test>
Expected Output
<Root>
<Test>
<tag1>A</tag1>
<tag2>B</tag2>
<tag3>C</tag3>
</Test>
<Test>
<tag1>1</tag1>
<tag2>2</tag2>
<tag3>3</tag3>
</Test>
<Test>
<tag1>apple</tag1>
<tag2>orange</tag2>
<tag3>mango</tag3>
</Test>
</Root>
Any help is appreciated
Thanks
If the structure is regular, you could do simply:
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:template match="/Test">
<Root>
<xsl:for-each select="tag1">
<Test>
<xsl:copy-of select=". | following-sibling::tag2[1] | following-sibling::tag3[1] "/>
</Test>
</xsl:for-each>
</Root>
</xsl:template>
</xsl:stylesheet>
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.
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>
I am again providing you the requirement like this.
Could you please look into this?
<xml>
<test>
<BookID>
<BookID1>
<BookID2>
0061AB
</BookID2>
</BookID1>
</BookID>
<amount>
16
</amount>
</test>
<test>
<BookID>
<BookID1>
<BookID2>
0062CD
</BookID2>
</BookID1>
</BookID>
<amount>
2
</amount>
</test>
<test>
<BookID>
<BookID1>
<BookID2>
0061AB
</BookID2>
</BookID1>
</BookID>
<amount>
2
</amount>
</test>
</xml>
here According to the equal value of BookID, I want to add the amount value.....like for above example, if value of BookID is 0061AB, then the value of amount should be 18.
Edit (Pasted to Answer)
Output should be like this
<xml>
<test>
<BookID>
<BookID1>
<BookID2>
0061AB
</BookID2>
<BookID1>
</BookID>
<amount>
18
</amount>
</test>
<test>
<BookID>
<BookID1>
<BookID2>
0062CD
</BookID2>
<BookID1>
</BookID>
<amount>
2
</amount>
</test>
</xml>
I'm assuming you mean the sum of the amounts. You can do with with xpath like the below
<xsl:template match="/xml">
<xsl:value-of select="sum(test[normalize-space(BookID)='0061AB']/amount)"/>
</xsl:template>
Edit
You can parameterize the BookID by using a variable, or by using a call-template as per my answer to your previous question
The following stylesheet
<xsl:template match="/xml">
<xsl:variable name="bookID" select="'0061AB'"/>
<xsl:value-of select="sum(test[normalize-space(BookID)=$bookID]/amount)"/>
</xsl:template>
Edit #2
It seems that you are now after grouping distinct elements and aggregating the totals.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- the identity template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="test">
<xsl:variable name="bookID" select="normalize-space(BookID/text())" />
<xsl:if test="not(preceding-sibling::test[normalize-space(BookID/text())=$bookID])">
<test>
<xsl:apply-templates />
</test>
</xsl:if>
<!--Else `eat` the duplicate test node-->
</xsl:template>
<xsl:template match="amount">
<amount>
<xsl:variable name="bookID" select="normalize-space(../BookID/text())" />
<xsl:value-of select="sum(//test[normalize-space(BookID/text())=$bookID]/amount)"/>
</amount>
</xsl:template>
</xsl:stylesheet>
Produces the output
<xml>
<test>
<BookID>
0061AB
</BookID>
<amount>18</amount>
</test>
<test>
<BookID>
0062CD
</BookID>
<amount>2</amount>
</test>
</xml>
Please can you take the time to read through the FAQ
Edit 3
This stylesheet will meet your latest requirement.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<!-- the identity template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="test">
<xsl:variable name="bookID" select="normalize-space(BookID/BookID1/BookID2/text())" />
<xsl:if test="not(preceding-sibling::test[normalize-space(BookID/BookID1/BookID2/text())=$bookID])">
<test>
<xsl:apply-templates />
</test>
</xsl:if>
<!--Else `eat` the duplicate test node-->
</xsl:template>
<xsl:template match="amount">
<amount>
<xsl:variable name="bookID" select="normalize-space(../BookID/BookID1/BookID2/text())" />
<xsl:value-of select="sum(//test[normalize-space(BookID/BookID1/BookID2/text())=$bookID]/amount)"/>
</amount>
</xsl:template>
</xsl:stylesheet>