Number of ancestors of a precise node - xpath

As usual my question is simple but I dont seem to be able to do what I want :
<test targetAttribute="level 1">
<test targetAttribute="level 2">
<test targetAttribute="level 3">
<test targetAttribute="level 4">
<test targetAttribute="level 5">
</test>
</test>
</test>
</test>
</test>
I want to know how many ancestors have the //test/#targetAttribute="level 5" node.
I have been trying thousand things, nothing is working for me :
count(//test/#targetAttribute="level 5"/ancestor::*)
//test/#targetAttribute="level 5"/count(ancestor::*)
count(ancestor::*[//test/#targetAttribute="level 5"])
...
I just don't seem to be able to find what I am looking at on google...

The notion of ancestor is known for element, so, first, you need to find the target element which has #targetAttribute="level 5" :
//test[#targetAttribute='level 5']
From here, you should be able to modify the above XPath to return count of ancestor elements of the target element :
count(//test[#targetAttribute='level 5']/ancestor::*)
Demo

Related

Xpath query to take nth occurrence from last occurrence

How to take last 3rd occurrence in xpath for below xml.
When I try with below query it not works. Expected to be Last Third Occurence
Xpath I tried:
/LINK/TEST[last(3)]/NAME
XML:
<LINK>
<N_Number_of_TESTs>
<NAME>N_Number_of_Names</NAME>
</N_Number_of_TESTs>
<TEST>
<NAME>Last Third Occurence</NAME>
</TEST>
<TEST>
<NAME>Last Second Occurence</NAME>
</TEST>
<TEST>
<NAME>Last Occurence</NAME>
</TEST>
</LINK>
You can use /LINK/TEST[last() - 2]/NAME.
The following will select the third-from-last TEST element:
//TEST[count(following-sibling::TEST)=2]

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}

XPath remove single node (via Saxon CLI)

I want to remove a node from an XML file (using SaxonHE9-8-0-11J):
<project name="Build">
<property name="src" value="src/main/resources" />
<property name="target" value="target/classes" />
<condition property="target.exists">
<available file="target" />
</condition>
</project>
Apparently there are 2 ways I can do this.
XPath1: using a not function
XPath2: using an except clause. But both simply return the entire node-set.
With a not function:
saxonb-xquery -s:test.xml -qs:'*[not(local-name()="condition")]'
With an except clause:
saxonb-xquery -s:test.xml -qs:'* except condition'
With -explain switch the queries are:
<query>
<body>
<filterExpression>
<axis name="child" nodeTest="element()"/>
<operator op="ne (on empty return true())">
<functionCall name="local-name">
<dot/>
</functionCall>
<literal value="condition" type="xs:string"/>
</operator>
</filterExpression>
</body>
</query>
and
<query>
<body>
<operator op="except">
<axis name="child" nodeTest="element()"/>
<path>
<root/>
<axis name="descendant" nodeTest="element(condition, xs:anyType)"/>
</path>
</operator>
</body>
</query>
In general, XPath select nodes from one or more input documents, it doesn't allow you to construct new ones, for that you need XSLT or XQuery. And removing the condition child of the project root, if that is what you want to achieve, is something you need XSLT or XQuery for, with XPath, even if you use /*/(* except condition), you then get all children except the condition element, but as a sequence, not wrapped into a a root.
So with XQuery you could use
/*/element {node-name()} { * except condition }
as a compact but generic way to reconstruct any root with all child elements except the condition: https://xqueryfiddle.liberty-development.net/948Fn5b
Whether you get such an expression through a command line shell is a different problem, on Windows with a Powershell window and the cmd shell it works for me to use
-qs:"/*/element {node-name()} { * except condition }"

Xpath Query to Loop Multiple Child Node

<Sections>
<Classes>
<Class>
<ClassStd>VI</ClassStd>
<ClassName>XYZ</ClassName>
</Class>
<Class>
<ClassStd>VII</ClassStd>
<ClassName>ABC</ClassName>
</Class>
</Classes>
<Classes>
<Class>
<ClassStd>VIII</ClassStd>
<ClassName>EFG</ClassName>
</Class>
<Class>
<ClassStd>IX</ClassStd>
<ClassName>MNO</ClassName>
</Class>
</Classes>
</Sections>
I want to get the ClassName values (XYZ,ABC,EfG,MNO) using Xpath. I tried using
//Sections/Classes/Class/*/ClassName/text() and other Xpath queries but i'm not getting desired results. I want to loop through every Classes and Every Class and get the ClassName values. Since the number of Classes or Class is fixed i have to loop till the end to get Values. How i can construct such a loop in Xpath ?
You need to change your xpath to:
//sections/classes/classname/text()

great ancestor & great great ancestor

1/ I have rule checker that forbid elements depending on a xpath expression.
2/ Every "test" element can contain a "test" element recursivly
3/ I want to forbid the "non usage" of an attibute for the firsts 3 "test" elements
Exemple:
<test targetAttribute="level 1">
<test targetAttribute="level 2">
<test targetAttribute="level 3">
<test targetAttribute="level 4">
<test targetAttribute="level 5">
</test>
</test>
</test>
</test>
</test>
targetAttribute attribute is mandatory for the firsts 3 levels only all other descendant element from level3 have their targetAttribute optionnal.
Here are my xpath:
//test[not(targetAttribute)]/ancestor[1]::test (level1)
//test[not(targetAttribute)]/ancestor[2]::test (level2)
//test[not(targetAttribute)]/ancestor[3]::test (level3)
But it doesn't work ! I also tried without success:
//test/ancestor[1]::test[not(targetAttribute)]
I'm becoming crazy #_# can someone help me plz ?
In order to select an attribute you need to use # before the attribute name. So
//test[not(targetAttribute)]
should be changed to
//test[not(#targetAttribute)]
and it will get all the test elements that do not contain this #targetAttribute.
Second thing.
When you want to select the first closest ancestor test you should use index after the test, like so:
/ancestor::test[1]
this will select the closes ancestor that is test (immediate parent in this case).
/ancestor::test[2]
will give you the grandparent and 3 will produce grand-grand parent.
Also you should probably filter out ancestors that don't have #targetAttribute
Not sure what exactly you are trying to accomplish, but just a try:
//test[not(#targetAttribute)]/ancestor::test[#targetAttribute][3] (level1)
//test[not(#targetAttribute)]/ancestor::test[#targetAttribute][2] (level2)
//test[not(#targetAttribute)]/ancestor::test[#targetAttribute][1] (level3)

Resources