How to perform intersection in nodeset using xpath 2.0 - xslt

I have two different node-sets that contain one or more elements with the same name.
I want to select these same-named elements by using the intersect operation in XPath 2.0 and XPath 1.0 as well.
This is the sample code i have tried.
Input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child1>
<a />
<b />
<d />
</child1>
<child2>
<c />
<a />
<d />
</child2>
</root>
The code I tried
Xpath 1.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<!-- TODO: Auto-generated template -->
<xsl:variable name="ns2" select="/root/child2/child::*"/>
<xsl:copy-of select="/root/child1/child[.=$ns2]"/>
</xsl:template>
</xsl:stylesheet>
Xpath 2.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<!-- TODO: Auto-generated template -->
<xsl:variable name="ns1" select="/root/child1/child::*"/>
<xsl:variable name="ns2" select="/root/child2/child::*"/>
<xsl:copy-of select="$ns1 intersect $ns2"/>
</xsl:template>
</xsl:stylesheet>
Issue: I am getting the empty result.
Expected result:
<?xml version="1.0" encoding="UTF-8"?>
<a/>
<d/>
Please suggest what I am missing.
I have tried the set operation intersection for two different noteset.I have attached my code sample below,

You are not really doing "intersection" here, because all of the child elements of child1 are different to the child elements of child2. Just because two elements share the same name, that does not make them the same element.
What it looks like you are after is elements under child1 that have the same name as an element under child2
If you want your XSLT 1.0 solution to get results, you need to change the xsl:copy-of to this
<xsl:copy-of select="/root/child1/child::*[.=$ns2]"/>
Or this...
<xsl:copy-of select="/root/child1/*[.=$ns2]"/>
However, this will return all child1 child elements, because you are checking the value of the elements are the same, not the name. One way to do it is this...
<xsl:for-each select="/root/child1/*">
<xsl:variable name="name" select="local-name()" />
<xsl:copy-of select=".[$ns2[local-name() = $name]]" />
</xsl:for-each>
Alternatively, define a key like so:
<xsl:key name="child2" match="child2/*" use="local-name()" />
Then you could do this...
<xsl:copy-of select="/root/child1/*[key('child2', local-name())]"/>
In XSLT 2.0, you could do something like this....
<xsl:copy-of select="$ns1[some $name in $ns2/local-name() satisfies $name=local-name()]"/>

Not repeating Tim's correct remark, but just to the problem:
XSLT 1.0 (Not possible with a single XPath expression, unless an XSLT standard function, such as current() is used):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="child1/*">
<xsl:copy-of select="self::*[../../child2/*[name() = name(current())]]"/>
</xsl:template>
</xsl:stylesheet>
XPath 2.0:
Use:
/*/child1/*[name() = /*/child2/*/name()]
This can be verified with the below XSLT 2.0 transformation which evaluates the expression and outputs the selected nodes:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:sequence select=
"/*/child1/*[name() = /*/child2/*/name()]"/>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document:
<root>
<child1>
<a />
<b />
<d />
</child1>
<child2>
<c />
<a />
<d />
</child2>
</root>
both transformations produce the wanted, correct result:
<a/>
<d/>

Related

How to use one cycle for different tags

I have XML with different same tags:
<?xml version="1.0" encoding="UTF-8"?>
<main>
<ROUTES>
<A1_NE>LSN/EMS_XDM_12/1021</A1_NE>
<A2_NE>LSN/EMS_XDM_12/1022</A2_NE>
<Z1_NE>LSN/EMS_XDM_12/1023</Z1_NE>
<Z2_NE>LSN/EMS_XDM_12/1024</Z2_NE>
</ROUTES>
<ROUTES>
<A1_NE>LSN/EMS_XDM_12/1001</A1_NE>
<A2_NE>LSN/EMS_XDM_12/1002</A2_NE>
<A3_NE>LSN/EMS_XDM_12/1003</A3_NE>
<A4_NE>LSN/EMS_XDM_12/1004</A4_NE>
<Z1_NE>LSN/EMS_XDM_12/1005</Z1_NE>
<Z2_NE>LSN/EMS_XDM_12/1006</Z2_NE>
</ROUTES>
</main>
XSLT:
<?xml version="1.1" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no"/>
<xsl:template match="/">
<MAIN>
<xsl:for-each select="main/ROUTES">
<xsl:element name="ROUTES">
<A_NE><xsl:value-of select="A1_NE"/></A_NE>
<A_NE><xsl:value-of select="A2_NE"/></A_NE>
<A_NE><xsl:value-of select="A3_NE"/></A_NE>
<A_NE><xsl:value-of select="A4_NE"/></A_NE>
<Z_NE><xsl:value-of select="Z1_NE"/></Z_NE>
<Z_NE><xsl:value-of select="Z2_NE"/></Z_NE>
</xsl:element>
</xsl:for-each>
</MAIN>
</xsl:template>
</xsl:stylesheet>
How I can use for-each command to transform A1_NE, A2_NE etc to A_NE column?
And also I've not understand how I can know number of the row in the source xml.
Perhabs xslt version 1.0 couldn't do this transformation.
<?xml version="1.0" encoding="UTF-8"?>
<main>
<ROUTES>
<A_NE>LSN/EMS_XDM_12/1021</A_NE>
<A_NE>LSN/EMS_XDM_12/1022</A_NE>
<Z_NE>LSN/EMS_XDM_12/1023</Z_NE>
<Z_NE>LSN/EMS_XDM_12/1024</Z_NE>
<A_NE>LSN/EMS_XDM_12/1001</A_NE>
<A_NE>LSN/EMS_XDM_12/1002</A_NE>
<A_NE>LSN/EMS_XDM_12/1003</A_NE>
<A_NE>LSN/EMS_XDM_12/1004</A_NE>
<Z_NE>LSN/EMS_XDM_12/1005</Z_NE>
<Z_NE>LSN/EMS_XDM_12/1006</Z_NE>
</ROUTES>
</main>
You should make use of template matching, to change the node names.
First select the child nodes of all ROUTES like so:
<xsl:apply-templates select="main/ROUTES/*" />
Then, have templates like this, for example, to do the renaming
<xsl:template match="A1_NE|A2_NE|A3_NE|A4_NE">
<A_NE><xsl:value-of select="."/></A_NE>
</xsl:template>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<main>
<ROUTES>
<xsl:apply-templates select="main/ROUTES/*" />
</ROUTES>
</main>
</xsl:template>
<xsl:template match="A1_NE|A2_NE|A3_NE|A4_NE">
<A_NE><xsl:value-of select="."/></A_NE>
</xsl:template>
<xsl:template match="Z1_NE|Z2_NE|Z3_NE|Z4_NE">
<Z_NE><xsl:value-of select="."/></Z_NE>
</xsl:template>
</xsl:stylesheet>
Alternatively, if those are indeed your real element names, you could try and make it generic
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<main>
<ROUTES>
<xsl:apply-templates select="main/ROUTES/*" />
</ROUTES>
</main>
</xsl:template>
<xsl:template match="ROUTES/*">
<xsl:element name="{substring(local-name(), 1, 1)}_{substring-after(local-name(), '_')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The logic that needs to be applied is not apparent from the example given. Perhaps all you need to do is:
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="/main">
<main>
<ROUTES>
<xsl:for-each select="ROUTES/*">
<xsl:element name="{translate(name(), '1234567890', '')}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</ROUTES>
</main>
</xsl:template>
</xsl:stylesheet>

XSL - Make all node values with namespace available in CDATA sections

I am looking for XSL to transform provided input to expected output.I have just provided sample but actual input xml had more than 1000 nodes and as too many nodes not able to use CDATA section in XSL, could you please help.
Input:
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
Output:
<note>
<to><![CDATA[Tove]]></to>
<from><![CDATA[Jani]]></from>
<heading><![CDATA[Reminder]]></heading>
<body><![CDATA[Don't forget me this weekend!]]></body>
</note>
You can achieve this by using the cdata-section-elements attribute of the xsl:output element which specifies all the elements that should be output in CDATA sections.
So use the following XSLT-1.0:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" cdata-section-elements="to from heading body" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See that the cdata-section-elements denotes the elements to from heading body to enclose their content in a CDATA section. The identity template just copies all of the file with regard to this.
If your elements are in a namespace, you have to prefix the element's names in the cdata-section-elements with the appropriate namespace-prefix.
For example, if you have the following XML with a namespace on the root element, all children nodes are in that namespace, too.
<?xml version="1.0" encoding="utf-8"?>
<Bank xmlns="http://xxyy.x.com" Operation="Create">
<Customer type="random">
<CustomerId>Id10</CustomerId>
<CountryCode>CountryCode19</CountryCode>
<LanguageCode>LanguageCode20</LanguageCode>
<AddressArray>
<Address type="primary">
<StreetAddress>179 Alfred St</StreetAddress>
<City>Fortitude Valley</City>
<County>GR</County>
<Country>India</Country>
</Address>
</AddressArray>
</Customer>
</Bank>
Use this XSLT (pay attention to the namespace declaration on the xsl:stylesheet element):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:ns0="http://xxyy.x.com">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" cdata-section-elements="ns0:CustomerId ns0:CountryCode ns0:LanguageCode ns0:StreetAddress ns0:City ns0:County ns0:Country" omit-xml-declaration="yes" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Make sure that the namespace of the XSLT matches the namespace of the XML, here both are http://xxyy.x.com, but in your sample XML it is xxyy.x.com.
EDIT 2:
If you have a large amount of elements you can either add them all to cdata-section-elements (maybe by constructing it by another stylesheet) or use the solution I found here: Wrapping all elements in CDATA. It is kind of a hack.
<?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" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:variable name="CDATABegin" select="'<![CDATA['" />
<xsl:variable name="CDATAEnd" select="']]>'" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="text()[normalize-space()]">
<xsl:value-of select="$CDATABegin" disable-output-escaping="yes"/>
<xsl:value-of select="." disable-output-escaping="yes"/>
<xsl:value-of select="$CDATAEnd" disable-output-escaping="yes"/>
</xsl:template>
</xsl:stylesheet>
This wraps all non-empty text() nodes in CDATA sections. But here, too, you'd have to mention all elements in template matching rules containing the CDATA wrapping code.
Which variant would be easier to apply depends on the greater scenario.

XSLT for-each-group from variable does not work

for-each-group from XSLT 2.0 works as expected from a file but not from a variable.
Have this file:
~$ cat test.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
Using stylesheet for grouping this file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0"
encoding="UTF-8" indent="yes" omit-xml-declaration="no" />
<xsl:template match="*">
<!-- variable not used for file test -->
<xsl:variable name="fields">
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
</xsl:variable>
<xsl:for-each-group select="*" group-starting-with="delimiter">
<field>
<xsl:for-each select="current-group()">
<xsl:value-of select="self::c"/>
</xsl:for-each>
</field>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I get the result I want:
<?xml version="1.0" encoding="UTF-8"?>
<field>AB</field>
<field>C</field>
Trying to group the variable name="fields" with:
<xsl:for-each-group select="$fields/*" group-starting-with="delimiter">
I get the result:
<?xml version="1.0" encoding="UTF-8"?>
<field/>
Why does for-each-group works on a file but not from a variable?
The variable fields is a document-node(), you can define the type of the variable to be element()
<xsl:variable name="fields" as="element()">
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
</xsl:variable>

Grouping of same tags under an element in xslt

I am working XSLT where the source looks like this.
Source:
<Data>
<AB>all</AB>
<AB>all2</AB>
<CD>hhhhhh</CD>
<DE>hhhshhh</DE>
</Data>
Need to write XSLT to get output as
<Info>
<XXX>
<TTT value="all"/>
<TTT value="all2"/>
</XXX>
<!-- ....-->
<!-- ..to het all the elements.. -->
</Info>
I have to write xslt to match tag.
<xsl:template match="AB">
</xsl:template>
I can do it by matching Data tag.
<?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:template match="Data">
<info>
<XXX>
<xsl:for-each select="AB">
<TTT>
<xsl:attribute name="value">
<xsl:value-of select="."/>
</xsl:attribute>
</TTT>
</xsl:for-each>
</XXX>
</info>
</xsl:template>
</xsl:stylesheet>
Can any one help me out how to do it by matching AB tag
<xsl:template match="AB">
</xsl:template>
Thank you.
I think you are asking how do you use xsl:apply-templates. If so, your XSLT would look like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Data">
<info>
<XXX>
<xsl:apply-templates select="AB"/>
</XXX>
</info>
</xsl:template>
<xsl:template match="AB">
<TTT value="{.}"/>
</xsl:template>
</xsl:stylesheet>
Do also note the use of Attribute Value Templates in the AB template to simplify the XSLT.
If you also require the other non-AB elements to be output unchanged, you would make use of the identity transform in your XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Data">
<info>
<XXX>
<xsl:apply-templates select="AB"/>
</XXX>
<xsl:apply-templates select="node()[not(self::AB)]" />
</info>
</xsl:template>
<xsl:template match="AB">
<TTT value="{.}"/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This would also match all non-AB elements, outputing following the XXX element. In your case, it would output the following:
<info>
<XXX>
<TTT value="all" />
<TTT value="all2" />
</XXX>
<CD>hhhhhh</CD>
<DE>hhhshhh</DE>
</info>
Of course, there is no reason you couldn't have other templates matching elements like CD or DE to transform those too.

Using a Value Of a Variable As a Name For a New Variable

I would like to format a name of a variable dynamically (with help of another variables/parameters) and then use it. In the code below I try to use the value of a variable cur as a name of a variable. But it does not work:
<!-- xml -->
<root>
<subroot param='1'/>
<subroot param='2'/>
</root>
<!-- xslt -->
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match='/'>
<xsl:variable name='var1'>Tom</xsl:variable>
<xsl:variable name='var2'>Simone</xsl:variable>
<xsl:for-each select='/root/subroot'>
<xsl:value-of select='#param'/>
<xsl:variable name='cur'>var<xsl:value-of select='#param'/></xsl:variable>
<input value='{${$cur}}'/>
</xsl:for-each>
root found
</xsl:template>
</xsl:stylesheet>
There must be a result:
<input value='Tom'/>
<input value='Simone'/>
Any suggestions how to make it working? Thank you very much for help.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default">
<xsl:variable name='var1'>Tom</xsl:variable>
<xsl:variable name='var2'>Simone</xsl:variable>
<xsl:template match='/'>
<root>
<xsl:for-each select='/root/subroot'>
<xsl:variable name='cur' select='#param'/>
<xsl:variable name="curVar" select="document('')/*/xsl:variable[#name= concat('var', $cur)]"/>
<input value='{$curVar}'/>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
This should do the trick.
Output :
<?xml version="1.0" encoding="UTF-8"?>
<root>
<input value="Tom"/>
<input value="Simone"/>
</root>
<input value='{${$cur}}'/>
This is illegal syntax in all current and known future versions of XSLT (1.0, 2.0 and 3.0).
And you don't need such capability.
Simply use:
<input value="{$vmyVar[position() = $cur]}"/>
The complete transformation becomes:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:params>
<p>Tom</p>
<p>Simone</p>
</my:params>
<xsl:variable name="vmyVar" select=
"document('')/*/my:params/*"/>
<xsl:template match='/'>
<root>
<xsl:for-each select='/root/subroot'>
<xsl:variable name='cur' select='#param'/>
<input value="{$vmyVar[position() = $cur]}"/>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<root>
<subroot param="2"/>
<subroot param="1"/>
</root>
the wanted, correct result is produced:
<root>
<input value="Simone"/>
<input value="Tom"/>
</root>