XPath : select all following siblings until another sibling - xpath

Here is an excerpt of my xml :
<node/>
<node/>
<node id="1">content</node>
<node/>
<node/>
<node/>
<node id="2">content</node>
<node/>
<node/>
I am positioned in the node[#id='1']. I need an Xpath to match all the <node/> elements until the next not empty node (here node[#id='2']).
Edit:
the #id attributes are only to explain my problem more clearly, but are not in my original XML. I need a solution which does not use the #id attributes.
I do not want to match the empty siblings after node[#id='2'], so I can't use a naive following-sibling::node[text()=''].
How can I achieve this ?

You could do it this way:
../node[not(text()) and preceding-sibling::node[#id][1][#id='1']]
where '1' is the id of the current node (generate the expression dynamically).
The expression says:
from the current context go to the parent
select those child nodes that
have no text and
from all "preceding sibling nodes that have an id" the first one must have an id of 1
If you are in XSLT you can select from the following-sibling axis because you can use the current() function:
<!-- the for-each is merely to switch the current node -->
<xsl:for-each select="node[#id='1']">
<xsl:copy-of select="
following-sibling::node[
not(text()) and
generate-id(preceding-sibling::node[#id][1])
=
generate-id(current())
]
" />
</xsl:for-each>
or simpler (and more efficient) with a key:
<xsl:key
name="kNode"
match="node[not(text())]"
use="generate-id(preceding-sibling::node[#id][1])"
/>
<xsl:copy-of select="key('kNode', generate-id(node[#id='1']))" />

Simpler than the accepted answer:
//node[#id='1']/following-sibling::node[following::node[#id='2']]
Find a node anywhere whose id is '1'
Now find all the following sibling node elements
...but only if those elements also have a node with id="2" somewhere after them.
Shown in action with a more clear test document (and legal id values):
xml = '<root>
<node id="a"/><node id="b"/>
<node id="c">content</node>
<node id="d"/><node id="e"/><node id="f"/>
<node id="g">content</node>
<node id="h"/><node id="i"/>
</root>'
# A Ruby library that uses libxml2; http://nokogiri.org
require 'nokogiri'; doc = Nokogiri::XML(xml)
expression = "//node[#id='c']/following-sibling::node[following::node[#id='g']]"
puts doc.xpath(expression)
#=> <node id="d"/>
#=> <node id="e"/>
#=> <node id="f"/>

XPath 2.0 has the operators '<<' and '>>' where node1 << node2 is true if node1 precedes node2 in document order.
So based on that with XPath 2.0 in an XSLT 2.0 stylesheet where the current node is the node[#id = '1'] you could use
following-sibling::node[not(text()) and . << current()/following-sibling::node[#od][1]]
That also needs the current() function from XSLT, so that is why I said "with XPath 2.0 in an XSLT 2.0 stylesheet". The syntax above is pure XPath, in an XSLT stylesheet you would need to escape '<<' as '<<'.

Related

Can I get xpath count value in robot framework

Assume the following XML:
<data>
<node id="1" />
<node id="2" />
<node id="12" />
<node id="16" />
</data>
This xpath expression should be valid:
count(//node)
.. and should produce the number 4
I'm new to robot frameworks. Is it possible to use this xpath in robot framework?
for example something like:
${value}= Get something something source=${xml} xpath=count(//node)
The one below works but I would like the xpath to produce the end value, not a list.
#{nodelist}= Get Elements ${xml} xpath=node
Length Should Be ${nodelist} 4
Edit
I know that I can count the nodes in a list of nodes. However, I would like to get the absolute value (integer or string) using xpath. Now I need to write different code depending on if the xpath result is a node, list or attribute when the xpath could theoretically produce the final value.
You can use the Get Element Count Keyword it returns the number of elements matching the locator
You can do something as simple as this
${count} = Get Element Count name:div_name
Should Be True ${count} > 2
For more info on Keywords Have a look at this Keyword Page
When working with XML it is generally best to use the XML library. In the below example you'll find a solution for counting the elements using the XML library Get Element Count.
data.xml
<data>
<node id="1" />
<node id="2" />
<node id="12" />
<node id="16" />
</data>
Testcase.robot
*** Settings ***
Library XML
Library OperatingSystem
*** Test Cases ***
TC
${xml} Get File ./data.xml
${count} Get Element Count ${xml} xpath=node
Should Be Equal As Integers ${count} ${4}

XMLStarlet: selecting nodes using less than / greater than

Does XMLStarlet let you use a less-than/greater-than operator to filter on an attribute value? For example, consider a document like this:
<xml>
<list>
<node name="a" val="x" />
<node name="b" val="y" />
<node name="c" val="z" />
etc.
</list>
{code}
Is there a way to select nodes whose value is greater than "x"? This XPath does not seem to work with XMLStarlet 1.5.0:
//node[#val > 'x']
Nor does this:
//node[#value gt 'x']
Comparing Characters like they were numbers (ASCII values/UniCode codepoints) is (unfortunately) impossible in XPath 1.0, look at this SO question if interested in more details.
So if your #val attributes are sorted in the XML, you can achieve this with a simple XPath expression selecting all nodes after an 'equal' match:
//node[#val='x']/following-sibling::node
If not, you'd have to use an XSLT-Stylesheet. Luckily, XMLStarlet has the ability to apply XSL-Stylesheets. I cite from their overview:
Apply XSLT stylesheets to XML documents (including EXSLT support, and passing parameters to stylesheets)
So you have the possibility to apply an xsl:stylesheet to achieve the desired result using xsl:sort, which is capable of sorting by characters.
<xsl:template match="/list">
<xsl:for-each select="//node"> <!-- all nodes sorted by 'val' attribute' -->
<xsl:sort select="#val" data-type="text" order="ascending" case-order="upper-first"/>
<xsl:value-of select="#name" /> <!-- or whatever output you desire -->
</xsl:for-each>
</xsl:template>

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"]]

How to get content from next node

I have an XML below -
<document>
<node name="Node 0 Text here" ID="01" >aa
</node>
<node name="Node 1 Text here" ID="11">bb
</node>
<node name="Node 2 Text here" ID="12">cc
</node>
<node name="Node 3 Text here" ID="22">dd
</node>
<node name="Node 4 Text here" ID="23">ee
</node>
</document>
I need to search content in a particular node within this XML.
If search keyword does not exist in that node, then I have to begin searching from the next node of current node, you could say sibling.
If that keyword does not exist in all the nodes after the current node then it should begin search from start..
I have to achieve this in my code behind- dotnet class. I have used -
XmlNodeList xmlNodes = xd.SelectNodes("//12/following-sibling::*");
Here, 12 refers to nodeid of the current node,which will be passed as an argument. But I am getting error.
Any help is appreciated.
I need to search content in a particular node within this XML
to get a node matching by its content, the XPath is:
node[contains(text(),'aa')]
This will return the first node for example and any other node whose content text contains aa.
If search keyword does not exist in that node, then I have to begin searching from the next node of current node, you could say sibling. If that keyword does not exist in all the nodes after the current node then it should begin search from start.
This sentence does not make much sense to XPath. The expression above will return all nodes matching the keyword. If you want the first matched node you can get it from the XmlNodeList after or directly from the XPath expression changing it to:
node[contains(text(),'aa')][1]
12 refers to nodeid of the current node,which will be passed as an argument
That's not correct. To select the node by id you should use, for instance:
node[#id=12]/text()
This will get the content of the node with id=12.
Use:
(/*/node[ID='12']/following-sibling::*[contains(.,$pattern)][1]
|
/*/node[ID='12']/preceding-sibling::*[contains(.,$pattern)][1]
)
[last()]
This expression selects the last from the two wanted selections -- the first of the following siblings that contains the value of $pattern and the first of the preceding siblings that contains the value of $pattern.
You need to substitute $pattern with the exact value you want to serch for.

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