XPath check for non-existing node - xpath

Im having a bit of trouble finding the right XPath syntax to check if a particular node in my XML exists. I'm only allowed to use XPath (so no XSL or something else like that, it has to be a pure XPath expression syntax).
I have an XML and it has a node Filename but it doesn't exist in every case. When the filename isn't specified, my LiveCycle proces will use a different route to fill in the filename. But how do I check if the Filename node exists?

Similar to count but maybe more direct depending of what you want is the function boolean
boolean(//Filename)
This returns true if "Filename" node exist and false if not.

You can use the count function - passing in the path of the nodes you are checking.
If they do not exist, then the value of count will be 0:
count(//Filename) = 0

Suppose you have the following XML document:
<top>
<function>
<filenamex>c:\a\y\z\myFile.xml</filenamex>
<default>Default.xml</default>
</function>
</top>
then this XPath expression selects either the filename element when it's present or the default element when no filename element is specified:
(/*/function/filename
|
/*/function/default
)
[1]
The shortest way to check if the filename element exists is:
/*/function/filename
So the first XPath expression could be re-written in the equivalent (but somewhat longer):
/*/function/filename
|
/*/function/default[not(/*/function/filename)]

Given the example Xml from another answer
<top>
<function>
<filenamex>c:\a\y\z\myFile.xml</filenamex>
<default>Default.xml</default>
</function>
</top>
To get nodes WITH node "filenamex" use /top/function[filenamex]
To get nodes WITHOUT node "filenamex" use /top/function[not(filenamex)]
I felt it necessary to answer here as the other answers did not work as advertised in XmlSpy

Related

XPath to retrive XML tag value without Namespace and prefix

I have the following XML -
<d><m:properties xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:AllTexts/>
<d:BomFlag/>
<d:OrderNumber>9489</d:OrderNumber>
<d:LineNumber>000000</d:LineNumber>
<d:VcFlag>Y</d:VcFlag>
<d:PricingFlag/>
<d:TextType>H</d:TextType>
<d:TextId>ZC01</d:TextId>
<d:TextLineNo>1</d:TextLineNo>
<d:TextLine>ecom header text 1</d:TextLine>
and trying to retrieve the TextLine nodelist as based on TextId = ZC01 -
<TextLine>ecom header text1</TextLine>
when I applied the xpath as --> //m:properties[d:TextId = 'ZC01']/d:TextLine
I get the output as -
<d:TextLine xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">ecom header text 1</d:TextLine>
how can I remove the prefix and namespace? I tried using local-name(), but that didn't work
May be used it wrong way.
Thank you for your help!
Thanks
Sugata
XPath is a selection language: it can only retrieve nodes that are actually there, it can't change them in any way. If the selected element has a prefix and namespace in the original, then it will have a prefix and namespace in the result.
However, you need to distinguish what the XPath selects (a node) from the way it the result is displayed. This depends on the application that is evaluating the XPath. The two popular ways of displaying a node selected by an XPath expression are (a) by serialising the node as XML (which is what we see in your case), and (b) by showing a path to the selected node, such as /d/m:properties/d:TextLine. You haven't told us how you are evaluating the XPath expression or displaying its result, and you may have options here.
But perhaps you should consider XSLT or XQuery, which (unlike XPath) allow you to construct new XML that differs from your original.

BaseX XQuery error: root(): no context value bound

I am trying to run the following XQuery expression in BaseX to extract elements between two succeeding headings. (as an article section).
xquery for $x in doc("test.xq")//h2,
$y in $x/following-sibling::h2[1]
return //*[$x/following::* and $y/preceding::*]
But it gives the error
Error:
Stopped at D:/Program Files/BaseX/data/test.xq, 1/74:
[XPDY0002] root(): no context value bound.
By the expression I mean if $x is heading and $y is the first heading following $x, then select the common text for $x/following::* and $y/preceding::*
However I am not sure my expression works, but my question here is how can execute my intended query without error?
If you have also an expression which works for my need, that is welcomed.
[...] to extract elements between two succeeding headings [...]
You need something more like:
for $x in doc("test.xq")//h2
return $x/following-sibling::*[preceding-sibling::h2[1] is $x]
but on its own it won't give you anything useful because the XPath and XQuery data model only has flat sequences, not "multi-dimensional arrays". When you have a for that returns a sequence of values for each "iteration", the overall result of the for expression is the concatenation of all the result sequences, so as written above this expression will simply return you all the elements in every "section" in a single flat list. If you want to group the elements by section then you'd need to construct a new XML element for each group
for $x in doc("test.xq")//h2
return
<section>{$x/following-sibling::*[preceding-sibling::h2[1] is $x]}</section>
The error (as documented here) comes from this expression:
//*[$x/following::* and $y/preceding::*]
which begins with //. The abbreviation // stands for /descendant-or-self::node()/, which of course begins with /. The XPath standard says:
A / by itself selects the root node of the document containing the
context node. If it is followed by a relative location path, then the
location path selects the set of nodes that would be selected by the
relative location path relative to the root node of the document
containing the context node.
But from what you've shown us, there is nothing indicating that you've established a context node. So XPath doesn't have any way to know what document contains the context node. That's what the error message is referring to when it says
root(): no context value bound
To fix the error, you could precede the // with an explicit doc(...) or any other explicit way to set the context:
doc("test.xq")//*[$x/following::* and $y/preceding::*]
or
root($x)//*[$x/following::* and $y/preceding::*]
This should get rid of the error, but as Ian Roberts has written, it won't give you the result you want. See his answer for that.

Find attribute names that start with a certain pattern

I am looking to find all attributes of an element that match a certain pattern.
So for an element
<element s2="1" name="aaaa" id="1" />
<element s3="1" name="aaaa" id="2" />
I would like to be able to find all attributes that start with 's' (returning the value of s1 for the first element and s3 for the value of the second element).
If this is outside of xpath's ability please let me know.
Use:
element/#*[starts-with(name(), 's')]
This XPath expression selects all atribute nodes whose name starts with the string 's' and that are attributes of elements named element that are children of the current node.
starts-with() is a standard function in XPath 1.0
element/#*[substring(name(), 1,1) = "s"]
will match any attribute that starts with 's'.
The function starts-with() might look better than using substring()
I've tested the given answers from both #Dimitre-Novatchev and #Ledhund, using lxml.html module in Python.
Both element/#*[starts-with(name(), 's')] and element/#*[substring(name(), 1,1) = "s"] return only the values of s2 and s3. You won't be able to know which value belong to which attribute.
I think in practice I would be more interested in finding the elements themselves that contain the attributes of names starting with specific characters rather than just their values.
To achieve that is very simple, just add /.. at the end,
element/#*[starts-with(name(), "s")]/..
or
element/#*[starts-with(name(), "s")]/parent::*
or
element/#*[starts-with(name(), "s")]/parent::node()
None from above worked for me.
So I did not some changes and it worked for me. :)
/*:UserCustomField[starts-with(#name, 'purchaseDate')]

Can't get nth node in Selenium

I try to write xpath expressions so that my tests won't be broken by small design changes. So instead of the expressions that Selenium IDE generates, I write my own.
Here's an issue:
//input[#name='question'][7]
This expression doesn't work at all. Input nodes named 'question' are spread across the page. They're not siblings.
I've tried using intermediate expression, but it also fails.
(//input[#name='question'])[2]
error = Error: Element (//input[#name='question'])[2] not found
That's why I suppose Seleniun has a wrong implementation of XPath.
According to XPath docs, the position predicate must filter by the position in the nodeset, so it must find the seventh input with the name 'question'. In Selenium this doesn't work. CSS selectors (:nth-of-kind) neither.
I had to write an expression that filters their common parents:
//*[contains(#class, 'question_section')][7]//input[#name='question']
Is this a Selenium specific issue, or I'm reading the specs wrong way? What can I do to make a shorter expression?
Here's an issue:
//input[#name='question'][7]
This expression doesn't work at all.
This is a FAQ.
[] has a higher priority than //.
The above expression selects every input element with #name = 'question', which is the 7th child of its parent -- and aparently the parents of input elements in the document that is not shown don't have so many input children.
Use (note the brackets):
(//input[#name='question'])[7]
This selects the 7th element input in the document that satisfies the conditions in the predicate.
Edit:
People, who know Selenium (Dave Hunt) suggest that the above expression is written in Selenium as:
xpath=(//input[#name='question'])[7]
If you want the 7th input with name attribute with a value of question in the source then try the following:
/descendant::input[#name='question'][7]

XPath concat multiple nodes

I'm not very familiar with xpath. But I was working with xpath expressions and setting them in a database. Actually it's just the BAM tool for biztalk.
Anyway, I have an xml which could look like:
<File>
<Element1>element1<Element1>
<Element2>element2<Element2>
<Element3>
<SubElement>sub1</SubElement>
<SubElement>sub2</SubElement>
<SubElement>sub3</SubElement>
<Element3>
</File>
I was wondering if there is a way to use an xpath expression of getting all the SubElements concatted? At the moment, I am using:
/*[local-name()='File']/*[local-name()='Element3']/*[local-name()='SubElement']
This works if it only has one index. But apparently my xml sometimes has more nodes, so it gives NULL. I could just use
/*[local-name()='File']/*[local-name()='Element3']/*[local-name()='SubElement'][0]
but I need all the nodes. Is there a way to do this?
Thanks a lot!
Edit: I changed the XML, I was wrong, it's different, it should look like this:
<item>
<element1>el1</element1>
<element2>el2</element2>
<element3>el3</element3>
<element4>
<subEl1>subel1a</subEl1>
<subEl2>subel2a</subEl2>
</element4>
<element4>
<subEl1>subel1b</subEl1>
<subEl2>subel2b</subEl2>
</element4>
</item>
And I need to have a one line code to get a result like: "subel2a subel2b";
I need the one line because I set this xpath expression as an xml attribute (not my choice, it's specified). I tried string-join but it's not really working.
string-join(/file/Element3/SubElement, ',')
/File/Element3/SubElement will match all of the SubElement elements in your sample XML. What are you using to evaluate it?
If your evaluation method is subject to the "first node rule", then it will only match the first one. If you are using a method that returns a nodeset, then it will return all of them.
You can get all SubElements by using:
//SubElement
But this won't keep them grouped together how you want. You will want to do a query for all elements that contain a SubElement (basically do a search for the parent of any SubElements).
//parent::SubElement
Once you have that, you could (depending on your programming language) loop through the parents and concatenate the SubElements.

Resources