Self axis in xslt - xpath

<element>
<bye>do not delete me</bye>
<hello>do not delete me</hello>
<hello>delete me</hello>
<hello>delete me</hello>
</element>
Applied to the above xml, this deletes all the nodes except the first hello child of /element:
<xsl:template match="hello[not(current() = parent::element/hello[1])]" />
Why these ones doesn't work? (assuming the first node is not a text node)
<xsl:template match="hello[not(self::hello/position() = 1)]" />
<xsl:template match="hello[not(./position() = 1)]" />
Or this one?
<xsl:template match="hello[not(self::hello[1])]" />
What is the self axis selecting? Why isn't this last example equivalent to not(hello[1])?

First, you are wrong when you say that:
This deletes all the nodes except the first hello child of /element
The truth is that it deletes (if that's the correct word) any hello child of /element whose value is not the same as the value of the first one of these. For example, given:
XML
<element>
<hello>a</hello>
<hello>b</hello>
<hello>c</hello>
<hello>a</hello>
</element>
the template:
<xsl:template match="hello[not(current() = parent::element/hello[1])]" />
will match the second and the third hello nodes - but not the first or the fourth.
Now, with regard to your question: in XSLT 1.0, position() is not a valid location step - so this:
<xsl:template match="hello[not(self::hello/position() = 1)]" />
should return an error.
In XSLT 2.0, the pattern hello[not(self::hello/position() = 1)] will not match any hello element - because there is only one node on the self axis, and therefore its position is always 1.
Similarly:
<xsl:template match="hello[not(./position() = 1)]" />
is invalid in XSLT 1.0.
In XSLT 2.0, ./position() will always return 1 for the same reason as before: . is short for self::node() and there is only one such node.
Finally, this template:
<xsl:template match="hello[not(self::hello[1])]" />
is looking for a node that doesn't have (the first instance of) itself. Of course, no such node can exist.

Using position() on the RHS of the "/" operator is never useful -- and in XSLT 1.0, which is the tag on your question, it's not actually permitted.
In XSLT 2.0, the result of the expression X/position() is a sequence of integers 1..count(X). If the LHS is a singleton, like self::E, then count(X) is one so the result is a single integer 1.

Related

Xpath 1.0 select first node back to ancestor

My XML as below :
<Query>
<Comp>
<Pers>
<Emp>
<Job>
<Code>Not selected</Code>
</Job>
</Emp>
<Emp>
<Job>
<Code>selected</Code>
</Job>
</Emp>
</Pers>
</Comp>
</Query>
I have an XPath : /Query/Comp/Pers/Emp/Job[Code='selected']/../../../..
The result should only have one < Emp > that meet condition
<Query>
<Comp>
<Pers>
<Emp>
<Job>
<Code>selected</Code>
</Job>
</Emp>
</Pers>
</Comp>
</Query>
How could I get the result?
The system doesn't work with ancestor::*. I have to use '/..' to populate the ancestor.
You shouldn't have to use ancestor here to get the <emp> tag, the following expath should select any <emp> tag that meets your criteria:
/Query/Comp/Pers/Emp[Job[Code='selected']]
Note: You say your result should be one, which will be correct in this case but this expression will return all nodes that match your criteria
Edit:
You've stated you're using XSLT and you've given me a bit of a snippet below, but I'm still not 100% sure of your actual structure. You can use the XPath to identify all the nodes that are not equal to selected, and then use XSLT to copy everything except those.
// Copy's all nodes in the input to the output
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
// Matches specifically the Emp records that are not equal to selected and
// applies no action to them to they do not appear in the output
<xsl:template match="/Query/Comp/Pers/Emp[Job[Code!='selected']]" />
The two templates above would transform your input to your desired output!

using preceding-sibling with with xsl:sort

I'm trying to use preceding-sibling and following-sibling with a subset of records with a sort on them. The problem that the preceding / following brings back values from the original xml order:
<Salaries>
<Salary>
<Base>1000</Base>
<CreatedDate xmlns:d7p1="http://schemas.datacontract.org/2004/07/System">
<d7p1:DateTime>2016-01-09T14:38:54.8440764Z</d7p1:DateTime>
<d7p1:OffsetMinutes>0</d7p1:OffsetMinutes>
</CreatedDate>
</Salary>
<Salary>
<Base>2000</Base>
<CreatedDate xmlns:d7p1="http://schemas.datacontract.org/2004/07/System">
<d7p1:DateTime>2015-01-09T14:38:54.8440764Z</d7p1:DateTime>
<d7p1:OffsetMinutes>0</d7p1:OffsetMinutes>
</CreatedDate>
</Salary>
<Salary>
<Base>3000</Base>
<CreatedDate xmlns:d7p1="http://schemas.datacontract.org/2004/07/System">
<d7p1:DateTime>2017-01-09T14:38:54.8440764Z</d7p1:DateTime>
<d7p1:OffsetMinutes>0</d7p1:OffsetMinutes>
</CreatedDate>
</Salary>
</Salaries>
When I use a sort under a for-each (Salaries/Salary) with a c# function to add offset minutes into a date and convert to a long number 201701010000 for example(to make manipulation in xslt easier).
<xsl:sort select="number(cs:Convertdatetolong(cs:AddOffsetMinutes(substring(p:CreatedDate/d5p1:DateTime,1,19),p:CreatedDate/d5p1:OffsetMinutes)))" order="ascending"/>
The sort works perfectly and I get the records out in the following order:
2000
1000
3000
The problem comes if I use preceding-sibling / preceding (and following). I would expect the first record (2000) to have no preceding record and the last record (3000) to have no following.
However when I use the preceding / following I get the previous record and the next record from the original XML:
2000 (preceding - 1000 / following - 3000)
1000 (preceding - / following - 2000)
3000 (preceding - 2000 / following - )
I would like to be able to compare against the previous record (in the sorted order) and the current record (in the sorted order):
2000 (preceding - / following - 1000)
1000 (preceding - 2000 / following 3000)
3000 (preceding - 1000 / following - )
I've tried preceding-sibling and preceding
<xsl:value-of select="preceding::p:Salary[1]/p:Base"/>
<xsl:value-of select="preceding-sibling::p:Salary[1]/p:Base"/>
<xsl:value-of select="preceding::p:Salary[position()=1]/p:Base"/>
(the salary is in a different namespace (p)
Is this actually possible or do I have to use variables to save the previous record's data to compare against?
Any ideas gratefully received. I'm using xslt 1.0
Although XSLT/XPath often talks of a "sequence of nodes", it's actually more accurate to think of it as a "sequence of node references" - because, for example, the same node can appear more than once in the sequence. When you sort a sequence of node references, you don't change the individual nodes in any way, you only change the sequence. That means the nodes still exist in their original tree exactly where they were before, and their parents, siblings, and descendants are exactly as they were before.
What you want is not the preceding and following siblings of the node, but the nodes that come before and after it in the sorted sequence, which is a quite different thing.
One way to do this is to construct a new tree containing copies of the original nodes, which you get, for example, if you do
<xsl:variable name="x">
<xsl:for-each ...>
<xsl:sort ...>
<xsl:copy-of select="."/>
The sibling relationships of the copied nodes will then reflect the sorted order. There's the minor problem that in XSLT 1.0, $x is a result tree fragment so you have to convert it to a node-set using the exslt:node-set() function.
In fact in XSLT 1.0 that's probably the only way of doing it, because the XSLT 1.0 data model only has node sets, not sequences, which means there is no way of capturing and processing a sequence of nodes in anything other than document order. The 2.0 model has much more flexibility and power. Upgrade if you can - XSLT 1.0 is approaching 20 years old.
Thanks to Michael for the answer. Posted here for completeness. Complicated because of the name spaces in use in the xml:
<!-- Puts the whole of the Salary Node into a variable-->
<xsl:variable name="SALARY" >
<xsl:copy-of select="p:Salaries" />
</xsl:variable>
<!-- Puts the the required key data into a node-set with the correct sort applied-->
<xsl:variable name="SAL">
<xsl:for-each select="msxsl:node-set($SALARY)//p:Salary">
<xsl:sort select="number(cs:Convertdatetolong(cs:AddOffsetMinutes(substring(p:CreatedDate/d5p1:DateTime,1,19),p:CreatedDate/d5p1:OffsetMinutes)))" order="ascending"/>
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<!-- Quick Output-->
<xsl:for-each select="msxsl:node-set($SAL)//p:Salary">
<xsl:text>Sa:</xsl:text>
<xsl:value-of select="position()" />
<xsl:text>Preceding:</xsl:text>
<xsl:value-of select="preceding-sibling::p:Salary[1]/p:Base"/>
<xsl:value-of select="$newline" />
<xsl:text>Current:</xsl:text>
<xsl:value-of select="p:Base"/>
<xsl:value-of select="$newline" />
<xsl:text>Following:</xsl:text>
<xsl:value-of select="following-sibling::p:Salary[1]/p:Base"/>
<xsl:value-of select="$newline"/>
</xsl:for-each>
The preceding-sibling axis gets the preceding siblings of the context node in document order.
To refer to the preceding siblings of a node after sorting, you will need to store the sorted nodes in a variable first - and, in XSLT 1.0, convert the variable into a node-set.

Is there any method to get any type of sibling of a particular node in Xpath 2.0

Is there any method to get any type of sibling of a particular node in Xpath 2.0
The axes "following-sibling" only supports for the same type of siblings.
Ex:
<node>
<b name="bold">abc</b>
<div>gef</div>
</node>
I want to select all the sibling of the <b name="bold">.
Is there any method to get any type of sibling of a particular node in Xpath 2.0
The axes following-sibling only supports for the same type of siblings.
Use:
following-sibling::node()
this select all siblings nodes of any type -- elements, text-nodes, processing-instruction nodes and comment nodes.
Here is a complete XSLT - based verification:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/*/b[#name='bold']/following-sibling::node()">
"<xsl:copy-of select="."/>"
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<node>
<b name="bold">abc</b>
<div>gef</div>
</node>
the XPath expression is applied (off the wanted element) and all selected three nodes are copied to the output:
"
"
"<div>gef</div>"
"
"
As we can see, all sibling nodes are selected -- a whitespace-only text node, a div element and another whitespace-only text node.
Do note: This is an XPath 1.0 expression and I don't believe XPath 2.0 adds any new feature for selecting siblings than what is already in XPath 1.0.
In case by "sibling" you mean something different than the meaning of "sibling" in XPath, then you must define precisely what you mean.
Not sure I understand the question, but how about:
//*[preceding-sibling::b]
That will get all previous siblings of the <b name="bold">abc</b> element. The * selects any type of element.
If you want all siblings:
//*[preceding-sibling::b or following-sibling::b]
And if you want to be more specific in how you select the b element:
//*[preceding-sibling::b[#name="bold"]]

Sorting XPath results in the same order as multiple select parameters

I have an XML document as follows:
<objects>
<object uid="0" />
<object uid="1" />
<object uid="2" />
</objects>
I can select multiple elements using the following query:
doc.xpath("//object[#uid=2 or #uid=0 or #uid=1]")
But this returns the elements in the same order they're declared in the XML document (uid=0, uid=1, uid=2) and I want the results in the same order as I perform the XPath query (uid=2, uid=0, uid=1).
I'm unsure if this is possible with XPath alone, and have looked into XSLT sorting, but I haven't found an example that explains how I could achieve this.
I'm working in Ruby with the Nokogiri library.
There is no way in XPath 1.0 to specify the order of the selected nodes.
XPath 2.0 allows a sequence of nodes with any specific order:
//object[#uid=2], //object[#uid=1]
evaluates to a sequence in which all object items with #uid=2 precede all object items with #uid=1
If one doesn't have anXPath 2.0 engine available, it is still possible to use XSLT in order to output nodes in any desired order.
In this specific case the sequence of the following XSLT instructions:
<xsl:copy-of select="//object[#uid=2]"/>
<xsl:copy-of select="//object[#uid=1]"/>
produces the desired output:
<object uid="2" /><object uid="1" />
I am assuming you are using XPath 1.0. The W3C spec says:
The primary syntactic construct in XPath is the expression. An expression matches the production Expr. An expression is evaluated to yield an object, which has one of the following four basic types:
* node-set (an unordered collection of nodes without duplicates)
* boolean (true or false)
* number (a floating-point number)
* string (a sequence of UCS characters)
So I don't think you can re-order simply using XPath. (The rest of the spec defines document order and reverse document order, so if the latter does what you want you can get it using the appropriate axis (e.g. preceding).
In XSLT you can use <xsl:sort> using the name() of the attribute. The XSLT FAQ is very good and you should find an answer there.
An XSLT example:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pSequence" select="'2 1'"/>
<xsl:template match="objects">
<xsl:for-each select="object[contains(concat(' ',$pSequence,' '),
concat(' ',#uid,' '))]">
<xsl:sort select="substring-before(concat(' ',$pSequence,' '),
concat(' ',#uid,' '))"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
<object uid="2" /><object uid="1" />
I don't think there is a way to do it in xpath but if you wish to switch to XSLT you can use the xsl:sort tag:
<xsl:for-each select="//object[#uid=1 or #uid=2]">
<xsl:sort: select="#uid" data-type="number" />
{insert new logic here}
</xsl:for-each>
more complete info here:
http://www.w3schools.com/xsl/el_sort.asp
This is how I'd do it in Nokogiri:
require 'nokogiri'
xml = '<objects><object uid="0" /><object uid="1" /><object uid="2" /></objects>'
doc = Nokogiri::XML(xml)
objects_by_uid = doc.search('//object[#uid="2" or #uid="1"]').sort_by { |n| n['uid'].to_i }.reverse
puts objects_by_uid
Running that outputs:
<object uid="2"/>
<object uid="1"/>
An alternative to the search would be:
objects_by_uid = doc.search('//object[#uid="2" or #uid="1"]').sort { |a,b| b['uid'].to_i <=> a['uid'].to_i }
if you don't like using sort_by with the reverse.
XPath is useful for locating and retrieving the nodes but often the filtering we want to do gets too convoluted in the accessor so I let the language do it, whether it's Ruby, Perl or Python. Where I put the filtering logic is based on how big the XML data set is and whether there are a lot of different uid values I'll want to grab. Sometimes letting the XPath engine do the heavy lifting makes sense, other times its easier to let XPath grab all the object nodes and filter in the calling language.

XPath 1 query and attributes name

First question: is there any way to get the name of a node's attributes?
<node attribute1="value1" attribute2="value2" />
Second question: is there a way to get attributes and values as value pairs? The situation is the following:
<node attribute1="10" attribute2="0" />
I want to get all attributes where value>0 and this way: "attribute1=10".
First question: is there any way to
get the name of a node's attributes?
<node attribute1="value1"
attribute2="value2" />
Yes:
This XPath expression (when node is the context (current) node)):
name(#*[1])
produces the name of the first attribute (the ordering may be implementation - dependent)
and this XPath expression (when node is the context (current) node)):
name(#*[2])
produces the name of the second attribute (the ordering may be implementation - dependent).
Second question: is there a way to get
attributes and values as value pairs?
The situation is the following:
<node attribute1="10" attribute2="0"
/>
I want to get all attributes where
value>0 and this way: "attribute1=10".
This XPath expression (when the attribute named "attribute1" is the context (current) node)):
concat(name(), '=', .)
produces the string:
attribute1=value1
and this XPath expression (when the node node is the context (current) node)):
#*[. > 0]
selects all attributes of the context node, whose value is a number, greater than 0.
In XPath 2.0 one can combine them in a single XPath expression:
#*[number(.) > 0]/concat(name(.),'=',.)
to get (in this particular case) this result:
attribute1=10
If you are using XPath 1.0, which is less powerful, you'll need to embed the XPath expression in a hosting language, such as XSLT. The following XSLT 1.0 thransformation :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:for-each select="#*[number(.) > 0]">
<xsl:value-of select="concat(name(.),'=',.)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<node attribute1="10" attribute2="0" />
Produces exactly the same result:
attribute1=10
It depends a little bit on the context, I believe. In most cases, I expect you'd have to query "#*", enumerate over the items, and call "name()" - but it may work in some tests.
Re the edit - you can do:
#*[number(.)>0]
to find attributes matching your criteria, and:
concat(name(),'=',.)
to display the output. I don't think you can do both at once, though. What is the context here? xslt? what?

Resources