What is the XPath equivalent of the Coalesce operator? - xpath

I have the following paths:
/my:company/ns1:Audit/ns1:Visit/ns1:customField10
/my:company/ns1:Audit/ns1:Visit/ns1:accountNumber
I want to select ns1:customField10 if it is not empty, otherwise I want to return ns1:accountNumber.
I tried the following:
xml.SelectSingleNode(
"(/my:cobius/ns1:Audit/ns1:Visit/ns1:customField10 |
/my:cobius/ns1:Audit/ns1:Visit/ns1:accountNumber)", ns)
But it is always returning accountNumber. I tried flipping the order of customField10 and accountNumber, but no joy. It still returns accountNumber.
How can I do the equivalent of the coalesce operator in XPath?
Update:
Here's the final code:
xml.SelectSingleNode(
"(/my:company/ns1:Audit/ns1:Visit/ns1:customField10[normalize-space()] |
/my:company/ns1:Audit/ns1:Visit/ns1:accountNumber[not(normalize-space(../ns1:customField10))])", ns)

Your code would work if the node was missing (kind of equivalent to a NULL in SQL). But empty is not missing (like an empty string in SQL).
You can of course add a predicate:
/my:cobius/ns1:Audit/ns1:Visit/ns1:customField10[string-length(.)] | /my:cobius/ns1:Audit/ns1:Visit/ns1:accountNumber

Related

Need XPath and XQuery query

I'm working on Xpath/Xquery to return values of multiple child nodes based on a sibling node value in a single query. My XML looks like this
<FilterResults>
<FilterResult>
<ID>535</ID>
<Analysis>
<Name>ZZZZ</Name>
<Identifier>asdfg</Identifier>
<Result>High</Result>
<Score>0</Score>
</Analysis>
<Analysis>
<Name>XXXX</Name>
<Identifier>qwerty</Identifier>
<Result>Medium</Result>
<Score>0</Score>
</Analysis>
</FilterResult>
<FilterResult>
<ID>745</ID>
<Analysis>
<Name>XXXX</Name>
<Identifier>xyz</Identifier>
<Result>Critical</Result>
<Score>0</Score>
</Analysis>
<Analysis>
<Name>YYYY</Name>
<Identifier>qwerty</Identifier>
<Result>Medium</Result>
<Score>0</Score>
</Analysis>
</FilterResult>
</FilterResults>
I need to get values of Score and Identifier based on Name value. I'm currently trying with below query but not working as desired
fn:string-join((
for $Identifier in fn:distinct-values(FilterResults/FilterResult/Analysis[Name="XXXX"])
return fn:string-join((//Identifier,//Score),'-')),',')
The output i'm looking for is this
qwerty-0,xyz-0
Your question suggests some fundamental misunderstandings about XQuery, generally. It's hard to explain everything in a single answer, but 1) that is not how distinct-values works (it returns string values, not nodes), and 2) the double slash selections in your return statement are returning everything because they are not constrained by anything. The XPath you use inside the distinct-values call is very close, however.
Instead of calling distinct-values, you can assign the Analysis results of that XPath to a variable, iterate over them, and generate concatenated strings. Then use string-join to comma separate the full sequence. Note that in the return statement, the variable $a is used to concat only one pair of values at a time.
string-join(
let $analyses := FilterResults/FilterResult/Analysis[Name="XXXX"]
for $a in $analyses
return $a/concat(Identifier, '-', Score),
',')
=> qwerty-0,xyz-0

odata4 $filter is returning false results

I am using DataTables odata plugin to power up my html tables. When searching I am sending filter property that looks like this :
$filter=
indexof(tolower(ClientAlias/Name), 'wee') gt -1 or indexof(tolower(Product/Name), 'wee') gt -1 or indexof
(tolower(User/UserName), 'wee') gt -1 or indexof(tolower(Manager/FullName), 'wee') gt -1 and Status ne
webapi.Models.ContractStatus'Suspended' and Manager_Id eq '120'
However, in the results i get absolutely everything that matches the first filters with the indexof function. For example :
{
ClientAlias:Object{Name="weentertain"}
Manager:
Object { Id="55"}
}
Where the Manager.Id is not even close to the one that I am requesting with the Filter.
My question is, do the previous filters overwrite the last one, or I am requesting it in a wrong way?
First, note that the example you gave is filtering on a property named Manager_Id, but no such property appears in your sample results. Did you mean to filter on Manager/Id?
The results you are seeing are due to operator precedence. The and operator has higher precedence than or. You can override precedence by using parentheses to group the substring matches together.
Finally, you can simplify the substring matches by using the contains function in place of the indexof/eq combination.
Here's the rewritten filter (with line breaks inserted for readability):
$filter=
(contains(tolower(ClientAlias/Name), 'wee') or
contains(tolower(Product/Name), 'wee') or
contains(tolower(User/UserName), 'wee') or
contains(tolower(Manager/FullName), 'wee')) and
Status ne webapi.Models.ContractStatus'Suspended' and
Manager/Id eq '120'

Select attribute and text() in the same query

I would like to select a attribute and the text() value of a node in one query, e.g. I have
<Tag1 #myattr='test'>MyText</Tag1>
and I am interested in getting "test" and "MyText" with one query.
The obvious
//Tag1/#myattr | //Tag1/text()
fails due to the fact, that Unions are only allowed over node-sets.
Any ideas?
I think, given XPath 2.0, you want a sequence of string values which you get with //Tag1/(#myattr, .)/string(). If you want a single string then use //Tag1/string-join((#myattr, .), ' ').
BTW, your path //Tag1/#myattr | //Tag1/text() would select a sequence containing an attribute value and a text node. I don't see how that would fail.

Using xsl:key to store result of boolean expression

In my transformation there is an expression some elements are repeatedly tested against. To reduce redundancy I'd like to encapsulate this in an xsl:key like this (not working):
<xsl:key name="td-is-empty" match="td" use="not(./node()[normalize-space(.) or ./node()])" />
The expected behaviour is the key to yield a boolean value of true in case the expression is evaluated successfully and otherwise false. Then I'd like to use it as follows:
<xsl:template match="td[not(key('td-is-empty', .))]" />
Is this possible and in case yes, how?
I think with XSLT 1.0 a key value is always of type string so in your sample the key value with either be the string true or the string false. You could then call key('td-is-empty', 'true') to find all td element nodes for which the expression is true and key('td-is-empty', 'false') to find all td elements for which the expression is false.
You seem to want to do something differently with your key however, like storing the result of the use expression for each td node, based on node identity. I don't think that is how keys work in XSLT.
[edit]
You might however be able to express your requirement as
<xsl:template match="td[count(. | key('td-is-empty', 'false')) = count(key('td-is-empty', 'false'))]">...</xsl:template>
That matches those td elements which are a member of the set of elements found by key('td-is-empty', 'false').

XPath OR operator for different nodes

How can I do with XPath:
//bookstore/book/title or //bookstore/city/zipcode/title
Just //title won't work because I also have //bookstore/magazine/title
p.s. I saw a lot of or examples but mainly with attributes or single node structure.
All title nodes with zipcode or book node as parent:
Version 1:
//title[parent::zipcode|parent::book]
Version 2:
//bookstore/book/title|//bookstore/city/zipcode/title
Version 3: (results are sorted based on source data rather than the order of book then zipcode)
//title[../../../*[book] or ../../../../*[city/zipcode]]
or - used within true/false - a Boolean operator in xpath
| - a Union operator in xpath that appends the query to the right of the operator to the result set from the left query.
If you want to select only one of two nodes with union operator, you can use this solution:
(//bookstore/book/title | //bookstore/city/zipcode/title)[1]
It the element has two xpath. Then you can write two xpaths like below:
xpath1 | xpath2
Eg:
//input[#name="username"] | //input[#id="wm_login-username"]

Resources