Apply template on preceding-sibling of following sibling of current node - xslt

Hi I have following Input
<Root>
<A rename="yes"/>
<B rename="no"/>
<C rename="no"/>
<D rename="yes"/>
<E rename="no"/>
<F all="yes"/>
</Root>
Currently i am at <A> and i want apply template on the element whose #rename="yes", that is coming before element <F>.
i am trying to doing something like:
<xsl:apply-templates select=
"following-sibling::*[#all='yes']/preceding-sibling::node()[#rename='yes'" />
But i am not getting the expected output. Please suggest.

Currently i am at <A> and i want
apply template on the element whose
#rename="yes", that is coming before
element <F>
You want this XPath expression (assuming A has only one following sibling named F):
following-sibling::F/preceding-sibling::*[#rename='yes'][1]
It selects any element whose rename attribute has value of "yes" and that is the first such preceding sibling of any following sibling (of the current node) element named F.
Here is a complete XSLT transformation:
<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="A">
<xsl:apply-templates mode="found" select=
"following-sibling::F/preceding-sibling::*[#rename='yes'][1]"/>
</xsl:template>
<xsl:template match="*" mode="found">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Root>
<A rename="yes"/>
<B rename="no"/>
<C rename="no"/>
<D rename="yes"/>
<E rename="no"/>
<F all="yes"/>
</Root>
the wanted, correct result is produced:
<D rename="yes"/>

Related

Select preceding elements until I find an element to stop on (XSLT 1.0)

I want to select the a elements that precede bird until I hit dog, but not select the bird or dog. And, I don't know what the elements are. They could be different than in the sample XML. And, I would like to do it in the select of a variable.
Input XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<a>cat</a>
<a>dog</a>
<a>dog</a>
<a>cat</a>
<a>snake</a>
<a>cat</a>
<a>cat</a>
<a>bird</a>
<a>dog</a>
<a>cat</a>
</root>
Desired Output XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<a>cat</a>
<a>snake</a>
<a>cat</a>
<a>cat</a>
</root>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="nodes">
<xsl:copy-of select="//a"/>
</xsl:variable>
<xsl:variable name="nodeList" select="msxsl:node-set($nodes)"/>
<!-- I want to select the a elements that precede bird until I hit dog, but not select the bird or dog.
And, I don't know what the elements are. They could be different than in the sample XML.
And, I want to do it in the select of the variable below.
-->
<xsl:variable name="subsetOfNodeList" select="$nodeList/a[.='bird']/preceding-sibling::a[. >> $nodeList/a[.='bird']/preceding-sibling::a[.='dog'][1]]"/>
<xsl:template match="root">
<xsl:copy>
<xsl:copy-of select="$subsetOfNodeList"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Given XSLT 1 I think you can select the preceding-siblings of the bird and intersect (remember intersection of node-sets n1 and n2 is done with $n1[count(. | $n2) = count($n2)]) them with anything following the dog, here is an example that additionally uses a key to identify what follows the dog:
<xsl:key name="fol" match="a[not(. = 'dog')]" use="generate-id(preceding-sibling::a[. = 'dog'][1])"/>
<xsl:template match="root">
<xsl:variable name="bird" select="a[. = 'bird']"/>
<xsl:variable name="prec-dog" select="$bird/preceding-sibling::a[. = 'dog'][1]"/>
<xsl:variable name="fol-dog" select="key('fol', generate-id($prec-dog))"/>
<xsl:variable name="prec-siblings" select="$bird/preceding-sibling::a[not(. = 'dog')]"/>
<xsl:variable name="intersect" select="$prec-siblings[count((. | $fol-dog)) = count($fol-dog)]"/>
<xsl:copy>
<xsl:copy-of select="$intersect"/>
</xsl:copy>
</xsl:template>
At https://xsltfiddle.liberty-development.net/eiQZDbr/2 for the input
<root>
<a id="c1">cat</a>
<a id="d1">dog</a>
<a id="d2">dog</a>
<a id="c3">cat</a>
<a id="s1">snake</a>
<a id="c4">cat</a>
<a id="c5">cat</a>
<a>bird</a>
<a>dog</a>
<a>cat</a>
</root>
I get the result
<root>
<a id="c3">cat</a>
<a id="s1">snake</a>
<a id="c4">cat</a>
<a id="c5">cat</a>
</root>
One of possible solutions is to make the "initial" call of a dedicated
template (let's call it print), passing it the last element to be
printed, i.e. the first preceding sibling of bird element (I assume
such a bird element is only one).
This template:
Checks whether the argument passed has content other than
dog (actually a condition to do anything). If this is the case then:
Make a recursive call to itself, passing it the first preceding
sibling of the argument element.
Print the element passed as the argument.
So the whole script can look like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template name="print">
<xsl:param name="xx"/>
<xsl:if test="$xx/text() != 'dog'">
<xsl:call-template name="print">
<xsl:with-param name="xx" select="$xx/preceding-sibling::*[1]"/>
</xsl:call-template>
<xsl:copy-of select="$xx"/>
</xsl:if>
</xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:call-template name="print">
<xsl:with-param name="xx" select="a[text() = 'bird']/preceding-sibling::*[1]"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
</xsl:transform>
For a working example see http://xsltfiddle.liberty-development.net/gWcDMet

I want to find the count of current element within its parent element using XSLT

<a>
<z/>
<b/>
<b/>
<b/>
<c/>
</a>
I want to find the count of 'b' within 'a' when my parsing current node is 'c' using XSLT.
Is it possible to do this using XSLT?
I am not aware of what the element name 'b' would be, i.e. for its preceding sibling.
If you are positioned on c tag, or whatever the element is actually called, then to get the count of the preceding siblings, you would do this...
<xsl:value-of select="count(preceding-sibling::*)" />
EDIT: In answer to your comment, if you don't want to count all siblings, but only the count of the immediately preceding one, and the ones with the same name before that, you could try this...
<xsl:value-of select="count(preceding-sibling::*[name() = name(current()/preceding-sibling::*[1])])" />
This would not work though in the case you had multiple c nodes under one parent...
<a>
<z/>
<b/>
<b/>
<b/>
<c/>
<z/>
<b/>
<c/>
</a>
In this case, you could define a key like this, to group elements by the unique id of the first following element with a different name:
<xsl:key name="keyc" match="*" use="generate-id(following-sibling::*[name() != name(current())][1])" />
Then you can get the count like so:
<xsl:value-of select="count(key('keyc', generate-id()))" />
Here are the three options in action....
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="keyc" match="*" use="generate-id(following-sibling::*[name() != name(current())][1])" />
<xsl:template match="c">
<c>
<example1>
<xsl:value-of select="count(preceding-sibling::*)" />
</example1>
<example2>
<xsl:value-of select="count(preceding-sibling::*[name() = name(current()/preceding-sibling::*[1])])" />
</example2>
<example3>
<xsl:value-of select="count(key('keyc', generate-id()))" />
</example3>
</c>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Use xsl:number. It prints out the numer of the current element, formatted as required.
There are various options concerning how to perform the numeration,
e.g. multi-level or alphabetic one.
Actually it is quite a powerful tool.
I want to find the count of 'b' within 'a' when my parsing current node is 'c'
Let me rephrase that:
you want to count all occurrences of <b> which are on the same level as <c>.
This XSLT does the job by calling an <xsl:template> with a parameter:
the local-name of the element to be counted (in this case 'b'):
<?xml version="1.0"?>
<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="//c"> <!-- select the desired target element -->
<xsl:call-template name="count">
<xsl:with-param name="elemName" select="'b'" /> <!-- name of the element -->
</xsl:call-template>
</xsl:template>
<xsl:template name="count"> <!-- count all matching elements before and after -->
<xsl:param name="elemName" />
<xsl:value-of select="count(preceding-sibling::*[local-name() = $elemName]) + count(following-sibling::*[local-name() = $elemName])" />
</xsl:template>
</xsl:stylesheet>
In the case of your example the output simply is:
3

How to instruct XSLT to apply template only on children?

When applyng this XSLT:
<xsl:template match="e">
<xsl:value-of select="#name"/>
</xsl:template>
To this xml:
<root>
<e name="1"/>
<la>
<e name="bla"/>
</la>
</root>
I get both "1" and "bla".
Why is this so?
How can I make sure that the XSLT is applied only to the direct children of root?
Did you try match="root/e"? If you want to match nodes in a certain context, you need to provide the context in the rule, otherwise all nodes with the matching node name apply to the rule.
You may also use something like:
<?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="child::e"/>
</xsl:template>
<xsl:template match="e">
<xsl:value-of select="#name"/>
</xsl:template>
</xsl:stylesheet>

XSL: Comparing nodes by comparing their child nodes

I would like to be able to compare two nodes based on the values of their child nodes. Testing node equality with the = operator just compares the string values of the respective nodes. I would like to compare them based on values in their child nodes.
To be a bit more specific, I would like <a> and <b> (below) to be equal, because the values of #id are the same for <c> elements that have matching #type attributes also have matching #id attributes.
<a>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
<c type="type-three" id="9088"/>
</a>
<b>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
</b>
But these would be different:
<a>
<c type="type-one" id="5675"/>
</a>
<b>
<c type="type-one" id="2342"/>
</b>
The only solution I can begin to see involves a laborious comparison with a for-each statement, which I would like to avoid if possible.
If possible I would like to stick with XSLT 1.0. I am using xsltproc.
First of all, the relation called "equals" cannot have that name.
"Equals" means that the relation is an equivalence relation. By definition any equivalence relation ~ must be:
Reflexive: x ~ x .
Symmetric: if x ~ y then y ~ x
Transitive: if x ~ y and y ~ z then x ~ z .
Here is an example, showing that the proposed "equals" relation isn't transitive:
x is:
<a>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
<c type="type-three" id="9088"/>
</a>
y is:
<b>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
<c type="type-four" id="1234"/>
</b>
z is:
<b>
<c type="type-three" id="3333"/>
<c type="type-four" id="1234"/>
</b>
Now, we can see that x ~ y and y ~ z. However, clearly this doesn't hold: x ~ z
This said, I am calling the relation "matches" and it is relaxed and not "equals".
Here is a solution to the problem, with the above adjustment:
Do note that this cannot be expressed with a single XPath expression, because XPath 1.0 (used within an XSLT 1.0 transformation) doesn't have range variables.
<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="/*">
<xsl:call-template name="matches">
<xsl:with-param name="pElem1" select="a"/>
<xsl:with-param name="pElem2" select="b"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="matches">
<xsl:param name="pElem1" select="/.."/>
<xsl:param name="pElem2" select="/.."/>
<xsl:variable name="vMisMatch">
<xsl:for-each select="$pElem1/c[#type = $pElem2/c/#type]">
<xsl:if test=
"$pElem2/c[#type = current()/#type and not(#id = current()/#id)]">1</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="not(string($vMisMatch))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<a>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
<c type="type-three" id="9088"/>
</a>
<b>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
</b>
</t>
the wanted, correct result is produced:
true
When the same transformation is applied on this XML document:
<t>
<a>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
<c type="type-three" id="9088"/>
</a>
<b>
<c type="type-one" id="5675"/>
<c type="type-two" id="9876"/>
</b>
</t>
again the correct result is produced:
false
Here is what I came up with. Given a toy data set like this:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a>
<item key="x" value="123"/>
<item key="y" value="456"/>
<item key="z" value="789"/>
</a>
<b>
<item key="x" value="123"/>
<item key="z" value="789"/>
</b>
</root>
This stylesheet shows how to test equality, as defined in the question.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:set="http://exslt.org/sets" xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="set exsl">
<xsl:output method="text" version="1.0" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:variable name="values-are-equal">
<xsl:call-template name="equal">
<xsl:with-param name="A" select="/root/a"/>
<xsl:with-param name="B" select="/root/b"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$values-are-equal = 1">Equal</xsl:when>
<xsl:otherwise>Inequal</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="equal">
<xsl:param name="A" />
<xsl:param name="B" />
<xsl:variable name="common-keys" select="$A/item/#key[ count(set:distinct( . | $B/item/#key )) = count( set:distinct( $B/item/#key ) ) ]"/>
<xsl:variable name="differences">
<xsl:for-each select="$common-keys">
<xsl:if test="$A/item[#key = current()]/#value != $B/item[#key = current()]/#value">
<different/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="count( exsl:node-set($differences)/* ) > 0">0</xsl:when>
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This uses some extensions, which are available in xsltproc and other processors.

XPath relative path in expression

I am in 'group' node. From it, I want to find such 'item' node, that has 'id' attribute equals to current's 'group' node 'ref_item_id' attribute value. So in my case, by being in 'group' node B, I want 'item' node A as output. This works:
<xsl:value-of select="preceding-sibling::item[#id='1']/#description"/>
But this doesn't (gives nothing):
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
When I type:
<xsl:value-of select="#ref_item_id"/>
I have '1' as result. So this attribute is for sure accessible, but I can't find path to it from XPath expression above. I tried many '../' combinations, but couldn't get it work.
Code to test: http://www.xmlplayground.com/7l42fo
Full XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
</root>
Full XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This has to do with context. As soon as you enter a predicate, the context becomes the node currently being filtered by the predicate, and no longer the node matched by the template.
You have two options - use a variable to cache the outer scope data and reference that variable in your predicate
<xsl:variable name='ref_item_id' select='#ref_item_id' />
<xsl:value-of select="preceding-sibling::item[#id=$ref_item_id]/#description"/>
or make use of the current() function
<xsl:value-of select="preceding-sibling::item[#id=current()/#ref_item_id]/#description"/>
Your expression searches for an item whose id attribute matches its own ref_item_id. You need to capture the current ref_item_id in an xsl:variable and refer to that xsl:variable in the expression.
One more possible solution using xsl:key
<xsl:key name="kItemId" match="item" use="#id" />
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="key('kItemId', #ref_item_id)[1]/#description"/>
</xsl:for-each>
</xsl:template>
Looking at the XML, if I assume that you have <item> and <group> as siblings and in any order.
Then a sample input XML would look like the following.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
<item description="D" id="1"/>
<group description="E" ref_item_id="2"/>
</root>
Now, if the goal is to extract the description of all the <item> nodes whose id is matching with corresponding <group> *nodes ref_item_id*. Then we can simply loop over only such <item> nodes and get their description.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[(./#id=following-sibling::group/#ref_item_id) or (./#id=preceding-sibling::group/#ref_item_id)]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Since you say that nodes are having unique id and all nodes are placed before nodes.
I would recommend you to use the following XSL and loop over specific nodes instead of nodes.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[./#id=following-sibling::group/#ref_item_id]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>