Can anyone help with the following XPath question? Given the node-set:
<table>
<rows>
<row>
<value column="Product">Coal</value>
<value column="Quantity">10000</value>
</row>
<row>
<value column="Product">Iron</value>
<value column="Quantity">5000</value>
</row>
<row>
<value column="Product">Ore</value>
<value column="Quantity">4000</value>
</row>
</rows>
</table>
I want to query to find the node sub-set with a given product name. Note that the product name is being supplied by an attribute of the current node being processed (i.e. "#name"). So when the #name attribute has the value of "Coal" I would expect this to be returned:
<row>
<value column="Product">Coal</value>
<value column="Quantity">10000</value>
</row>
This is what I've come up with; I know it's wrong, because I don't get anything back.
$table/rows/row[value[#column='Product'][text()=#name]]
</code>
You are obviously missing the current() function
$table/rows/row[value[#column='Product'] = current()/#name]
Within an XPath predicate (i.e. within square brackets) the context node is the node the predicate is applied to.
In your case, when you say $table/rows/row[x=#name], then #name refers to the #name attribute of row. Which has no #name attribute, so the predicate always evaluates to false for all nodes.
current() returns the current XSLT context node to help in exactly this case.
Related
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}
I have an XML document,
<resultsets>
<row>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
<row>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
<row>
<first_name>Bezalel</first_name>
<last_name>Hass</last_name>
</row>
</resultsets>
I want to sort first names and remove duplicated first names to produce this:
<resultsets>
<row>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
<row>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
</resultsets>
Following are the code I wrote:
for $last_name at $count1 in doc("employees.xml")//last_name,
$first_name at $count2 in doc("employees.xml")//first_name
let $f := $first_name
where ( $count1=$count2 )
group by $f
order by $f
return
<row>
{$f}
{$last_name}
</row>
However, this code sort the XML document by first names, but failed to remove the duplicated first name ('Bezalel'), it returns:
<resultsets>
<row>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
<row>
<first_name>Bezalel</first_name>
<last_name>Hass</last_name>
</row>
<row>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
</resultsets>
I know how to solve this using two FLOWR statements. group by behavior is weird, could you please explain why it does not remove the duplicates?
Is there any way we can solve this problem using ONE FLOWR loop and ONLY use $first_name and $last_name two variables? Thanks,
I would simply group the row elements by the first_name child and then output the first item in each group to ensure you don't get duplicates:
<resultssets>
{
for $row in resultsets/row
group by $fname := $row/first_name
order by $fname
return
$row[1]
}
</resultssets>
http://xqueryfiddle.liberty-development.net/jyyiVhf
As to how the group by clause works, see https://www.w3.org/TR/xquery-31/#id-group-by which says:
The group by clause assigns each pre-grouping tuple to a group, and
generates one post-grouping tuple for each group. In the post-grouping
tuple for a group, each grouping key is represented by a variable that
was specified in a GroupingSpec, and every variable that appears in
the pre-grouping tuples that were assigned to that group is
represented by a variable of the same name, bound to a sequence of all
values bound to the variable in any of these pre-grouping tuples.
<Item id="item0">
<Links>
<FirstLink id="link1" target="one"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content</String>
</Data>
</Item>
<Item id="item1">
<Links>
<FirstLink id="link1" target="two"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content</String>
</Data>
</Item>
I have created a Nokogiri-NodeSet with this structure, i.e. a list of items with links and data children.
How can I filter any items that don't match a certain value in the 'target'-attribute of <FirstLink>?
Actually, what I want in the end is to extract the <Data><String>-Content of every <Item> that matches a certain value in it's <FirstLink> "Target"-Attribute.
I've tried several approaches already but I'm at a loss as to how to identify an element by an attribute of it's grandchild, then extracting the content of this grandchild's parent's sibling, X(.
We can build up an XPath expression to do this. Assuming we are starting from the whole XML document, rather than the node-set you already have, something like
//Item
will select all <Item> elements (I’m guessing you already have something like that to get this node-set).
Next, to select only those <Item> elements which have <Links><FirstLink> where FirstLink has a target attribute value of one:
//Item[Links/FirstLink[#target='one']]
and finally to select the Data/String children of those nodes:
//Item[Links/FirstLink[#target='one']]/Data/String
So with Nokogiri you could use something like this (where doc is your parsed document):
doc.xpath("//Item[Links/FirstLink[#target='one']]/Data/String")
or if you want to use the node-set you already have you can use a relative expression:
nodeset.xpath("self::Item[Links/FirstLink[#target='one']]/Data/String")
I completely didn't understand what your goal is. But using a guess, I am trying to show you, how to proceed in this case :
require 'nokogiri'
doc = Nokogiri::XML <<-xml
<Item id="item0">
<Links>
<FirstLink id="link1" target="one"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content1</String>
</Data>
</Item>
<Item id="item1">
<Links>
<FirstLink id="link1" target="two"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content2</String>
</Data>
</Item>
xml
#xpath method with the expression "//Item", will select all the Item nodes. Then those Item nodes will be passed to the #reject method to select only those nodes, that has a node called Links having the target attribute value is "one". If any of the links, either FirstLink or SecondLink has the target attribute value "one", for that nodes grandparent node Item will be selected.
node.at("//Links/FirstLink")['target'] will give you the string say "one" which is a value of target attribute of the node, FirstLink of first Item nodes , then "two" from the second Item node. The part ['any vaue'] in node.at("//Links/FirstLink")['target']['any vaue'] is a call to the String#[] method.
Remember below approach will give you the flexibility of the use regular expression too.
nodeset = doc.xpath("//Item").reject do |node|
node.at("//Links/FirstLink")['target']['any vaue']
end
Now nodeset contains only the required Item nodes. Now I use #map, passing each item node inside it to collect the content of the String node. Then #at method with an expression //Data/String, will select the String node. Then #text, will give you the content of each String node.
nodeset.map { |n| n.at('//Data/String').text } # => ["content1"]
how can you ignore nodes which have a certain innertext but you don't know the innertext of the other nodes:
<row>
<column>test</columm>
</row>
<row>
<column>???</columm>
</row>
this is what I tried but didn't work
row/column[not(.='test')]
row/column[.!='test']
row/column[not(text()='test')]
row/column[text()!='test']
row[column[text()!='test']]/column
This will get you the rows where the first <column> is not test.
//row[column[1][. != 'test']]
See http://www.xpathtester.com/obj/1ddc1930-ad7f-424c-9800-85df95fe6af3
(hit "Test!") to run it
If my XML is like this:
<sql result="success">
<row>
<column>
<name>USER_ID</name>
<value>TEST</value>
</column>
<column>
<name>EMAIL_ADDRESS</name>
<value>xxx#yyyy.com</value>
</column>
</row>
</sql>
How do I extract just the text of the node retrieved with this XPath:
//value[preceding-sibling::name[1][. = 'USER_ID']]
Just append the /text() to get the text child of the element:
//value[preceding-sibling::name[1][. = 'USER_ID']]/text()