Can I get xpath count value in robot framework - xpath

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}

Related

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>

xpath expression wild-cards

I have a requirement to specify wild card in the following xpath
Field[#name="/Root/Table[i]/FirstName"]
Basically the "i" would be a variable which can have either a GUID or a running number. I would like to pick up all elements that basically have the attribute pattern
"/Root/Table[*]/FirstName"
i.e. starting with "/Root/Table[" and ending with "]/FirstName". Any ideas as to how this can be done ?
Here is a sample payload:
<Package>
<Input>
<Data id="36e9f0fe3f8d4508ac20710e07cfddd4">
<Input>
<Field name="/Root/Table[1]/FirstName">Thomas</Field>
</Input>
</Data>
</Input>
</Package>
You should be able to do this using starts-with() and a makeshift ends-with() (since XPath 1.0 doesn't actually have an ends-with() function):
//*[starts-with(#name, '/Root/Table[') and
substring(#name, string-length(#name) - 11 + 1) = ']/FirstName']
Here, 11 is the length of ]/FirstName.

Double node on Xpath for different values

How to write a Xpath for two attributes? e.g. i need to get a value of discount > 20% and also the same discount is greater than amount 200(without any link to base value)
You can combine constraints in predicates. E.g.:
from lxml import etree
doc = etree.XML("""<xml>
<items>
<item discount_perc="25" discount_value="250">Something</item>
</items>
</xml>
""")
doc.xpath('items/item[#discount_perc > 20 and #discount_value > 200]')
Will try to answer by a simple example. Imagine you have the following xml:
<?xml version="1.0"?>
<data>
<node value="10" weight="1">foo</node>
<node value="10" weight="2">bar</node>
</data>
Then use this query to select the first <node>'s text:
//node[#value="10" and #weight="1"]/text()
and this for the second:
//node[#value="10" and #weight="2"]/text()
Hope this helps.

XPath : select all following siblings until another sibling

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 '<<'.

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