Match node of called template - xslt

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.

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>

Difference between 'where' and 'if' in an XSLT script

I am new to XML/XSLT, and I'm a bit confused about the difference between the <xsl:if test="x"> and adding a where="x" at the end of a statement.
Below is some example data and two XSLT versions of code. I tried running it both ways here: https://www.w3schools.com/xml/tryxslt.asp?xmlfile=cdcatalog&xsltfile=cdcatalog_ex1 but nothing appears, so I may be doing something wrong. Is anyone able to clarify this for me?
<?xml version="1.0"?>
<Tests xmlns="http://www.adatum.com">
<Test TestId="0001" TestType="CMD">
<Name>Convert number to string</Name>
<CommandLine>Examp1.EXE</CommandLine>
<Input>1</Input>
<Output>One</Output>
</Test>
<Test TestId="0002" TestType="CMD">
<Name>Find succeeding characters</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>abc</Input>
<Output>def</Output>
</Test>
<Test TestId="0003" TestType="GUI">
<Name>Convert multiple numbers to strings</Name>
<CommandLine>Examp2.EXE /Verbose</CommandLine>
<Input>123</Input>
<Output>One Two Three</Output>
</Test>
<Test TestId="0004" TestType="GUI">
<Name>Find correlated key</Name>
<CommandLine>Examp3.EXE</CommandLine>
<Input>a1</Input>
<Output>b1</Output>
</Test>
<Test TestId="0005" TestType="GUI">
<Name>Count characters</Name>
<CommandLine>FinalExamp.EXE</CommandLine>
<Input>This is a test</Input>
<Output>14</Output>
</Test>
</Tests>
Using where my XSLT is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="Tests/Test" where="#TestType='CMD'">
<xsl:value-of select="current()">
</xsl:for-each>
</xsl:template>
</xsl:stylesheet
Code using the if statemtent
<?xml version="1.0" encoding="UTF-8"?>
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="Tests/Test">
<xsl:if test="#TestType='CMD'">
<xsl:value-of select="current()">
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet
The is no where attribute for xsl:for-each.
What you mean is called a predicate which is enclosed by Double brackets.
So change your xsl:for-each from
<xsl:for-each select="Tests/Test" where="#TestType='CMD'">
<xsl:value-of select="current()">
</xsl:for-each>
to
<xsl:for-each select="Tests/Test[#TestType='CMD']">
<xsl:value-of select="current()">
</xsl:for-each>
That should do the trick.

Split based on just tags

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>

XSLT sort case sensitive

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.

XPath to get sibling node's value

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>