How to convert string to Xpath in Xquery function (BaseX) - xpath

I am writing Xquery function for BaseX which gets one of arguments as name of the element node. This name is then used in Xpath, but in general I cannot convert string to element.
This is how the method looks like
declare function prefix:getElementWithValue($root as document-node()?, $elem as xs:string?, $minVal as xs:float?, $maxVal as xs:float?)
as element()*
{
let $e := element {$elem} {""}
for $x in $root//SUBELEM
return if ($x//$e/#ATTRIB>=$minVal and $x//$e/#ATTRIB<=$maxVal) then ($x)
};
and the call
return prefix:getElementWithValue($db, "SomeElem", 10.0, 10.0)
and I am getting empty response from that. If I replace the $x//$e with $x//SomeElem it returns proper response. From the QueryPlan I see that the $e is treated as literal value. XPATH is not $x//SomeElem/#ATTRIB but $x//$e/#ATTRIB
So my question is how to covert string to type that can be used in XPATH?

XQuery does not have a standard function to evaluate a dynamically-constructed XPath expression.
Many XQuery processors offer some kind of extension function that does this, however. For example, BaseX offers query:eval():
https://docs.basex.org/wiki/XQuery_Module#xquery:eval
Note that variables in XQuery represent values, not fragments of expression text. Your expression $x//$e/#ATTRIB is equivalent to $x//"SomeElem"/#ATTRIB, which is quite different from $x//SomeElem/#ATTRIB.
If you know that $elem will always be an element name, then you can write $x//*[name()=$e]/#ATTRIB. But take care over namespaces.

Related

xquery optional where statements

In Xquery 3.1 I am processing the variable parameters from a form to search for matching XML documents. The XML documents look like this:
<listBibl xml:id="TC0001" type="collection">
<bibl>
<title type="collection">Bonum universale de apibus</title>
<affiliation corresp="dominican"/>
<author nymRef="thomas_cantipratensis"/>
<location corresp="flanders"/>
<othercontent>....</othercontent>
</bibl>
</listBibl>
The user can submit optional parameters against xml:id, affiliation, author, and location, and they can be parameters with multiple values (sequences).
If the user were to submit all parameters, the query might look like:
for $c in $mycollection//listBibl[#xml:id=($params_id)]
where $c/affiliation[#corresp=($params_affil)]
and $c/author[#nymRef=($params_author)]
and $c/location[#corresp=($params_location)]
return $c
But the user may leave certain parameters empty, effectively making each where statement optional.
The only solution I can currently put together is to have a series of if...then...else statements which account for each permutation of parameters.
Is there any way in Xpath or Xquery to account for the parameters being empty with a wildcard of some sort? In pseudo code, where * represents a wished-for wildcard:
where $c/affiliation[if ($params_affil)
then #corresp=($params_affil)
else #corresp=* ]
Many thanks.
Use predicates of the form
[$params_affil=("", #corresp)]
which matches if $params_affil is either a zero-length string or equal to #corresp. And make zero-length-string (rather than empty sequence) the default if the parameter is not supplied.
Alternatively if the default for an absent parameter is (), use
[empty($params_affil) or $params_affil=#corresp)]
If that gets too repetitive, put the logic in a user-declared function.
I think you can always declare and use your own function as a predicate expression e.g.
declare function local:check-item($item as node(), $values as item()*) as xs:boolean
{
if (exists($values))
then $item = $values
else true()
};
....
where $c/affiliation[local:check-item(#corresp, $params_affil)]

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

MarkLogic: how to search for an XPath using cts:uris

Is it possible to search for a uri whose document contains a certain XPath using cts:uris()? I thought it may be quicker than returning uris from a cts:search. Here is what I have currently:
declare function local:xpath-search($collection) {
for $i in cts:search(//a/b, cts:and-query((cts:collection-query($collection)) ))[1] return fn:base-uri($i)
} ;
Is there a quicker way to return documents that contain a match to the XPath //a/b, using cts:uris()?
You can use cts:element-query() to construct a cts:query that functions similar to the XPath expression //a/b searching for documents that have a elements that have b element descendants. It isn't exactly the same, and might give you some false positives, because it is really more akin to //a//b, but might be acceptable and can be used with cts:uris().
xquery version "1.0-ml";
declare function local:xpath-search($collection) {
cts:uris("", (),
cts:and-query((
cts:collection-query($collection),
cts:element-query(xs:QName("a"),
cts:element-query(xs:QName("b"), cts:and-query(()) ) ) )) )
};

How to use like in XPath?

I have a page that searches with filters. I have this code for example,
xmlTempResultSearch = xmlResidentListDisplay.selectNodes("//PeopleList/Row[#LastName != '"+txtSearch.value+"']");
xmlTempResultSearch.removeAll();
This selects the data that is not equal to the LastName inputted on the txtSearch textbox and then removes them from the result set so that its filtered to equal the last name on the txtSearch textbox.
My problem with this code is that it should be equal (=) to the txtSearch.value, what I want is that I want the result set LIKE the txtSearch.value. What happens on my page is that when I type 'santos' on the txtSearch textbox, its result set is all those last names with 'santos'. But when I type 'sant', nothing appears. I want the same result set with 'santos' because it all contains 'sant'
You can use all of the XPath (1.0) string functions. If you have XPath 2.0 available, then you can even use RegEx.
contains()
starts-with()
substring()
substring-before()
substring-after()
concat()
translate()
string-length()
There is no **ends-with() in XPath 1.0, but it can easily be expressed with this XPath 1.0 expression**:
substring($s, string-length($s) - string-length($t) +1) = $t
is true() exactly when the string $s ends with the string $t.
You can use start-with function and not function. Reference:
http://www.w3schools.com/xpath/xpath_functions.asp
xmlTempResultSearch = xmlResidentListDisplay.selectNodes("//PeopleList/Row[not(starts-with(#LastName,'"+ txtSearch.value +"'))]");
you can use contains() function of XPath:
xmlTempResultSearch = xmlResidentListDisplay.selectNodes("//PeopleList/Row[not(contains(#LastName,'"+txtSearch.value+"'))]");

How to get node value using variable node name?

I have an XML document like:
<data>
<item type="apple">
<misc>something</misc>
<appleValue>23</appleValue>
<misc2>something else</misc2>
</item>
<item type="banana">
<bananaValue>47</bananaValue>
<random>something</random>
</item>
</data>
I can get the items with doc("data.xml")/data/item but I need to get the text from the elements that end with Value. So I'd like to get "23" and "47", but I don't necessarily know the element names, meaning all I really know is there are elements that end in Value, I don't know if it's appleValue, bananaValue, etc. except that I could look at the type attribute and buildup a string.
let $type := (doc("data.xml")/data/item)[1]/#type
doc("data.xml")/data/item/$typeValue
...That last line is what I'm trying to get at, clearly that's not correct but I need to find elements whose name is known based on a variable (stored in a variable such as $type) and "Value".
Any ideas? I realize this variable element naming is strange/odd/bad...but that's the way it is and I have to deal with it.
I got it thanks to this post: Can XPath match on parts of an element's name?
doc("data.xml")/data/item/*[ends-with(name(), "Value")]
I would avoid using the name() function in favor of either node-name() or local-name(). The reason for this is that name() can give you different answers depending on what (and whether) namespace prefixes are used in the source. For example, the following three elements have the same exact name (QName):
<appleValue xmlns="http://example.com"/>
<x:appleValue xmlns:x="http://example.com"/>
<y:appleValue xmlns:y="http://example.com"/>
However, the name() function will give you a different answer for each one (appleValue, x:appleValue, and y:appleValue, respectively). So you're better off either ignoring the namespace by using local-name() (which returns the string appleValue for all three of the above cases) or explicitly specifying the namespace (even if it's empty, as Oliver showed), using node-name() (which returns a proper QName value, rather than a string). In this case, since you're not using namespaces (and since even if you added one later, the code will still work), I'd be slightly in favor of using local-name() as follows:
doc("data.xml")/data/item/*['Value' eq substring-after(local-name(),../#type)]
For elaboration on reasons to avoid the name() function (and exceptions), see "Perils of the name function".
You can access the name of the node using name(). XPath 1.0 does not have an "ends-with" function, but by using substring() and string-length() - 1 you can get there.
//item/*[ substring( name(), string-length(name() ) - 4 ) = 'Value']
A more precise way to implement this would be
for $item in doc("data.xml")/data/item
let $value-name := fn:QName('', concat($item/#type, 'Value'))
return $item/*[node-name() = $value-name]

Resources