xquery parameter query - xpath

I'm trying to query an exist-db with xquery by taking parameters from the URL and building up seach parameters
xquery version "1.0";
declare namespace request="http://exist-db.org/xquery/request";
declare namespace xs="http://www.w3.org/2001/XMLSchema";
declare option exist:serialize "method=xml media-type=text/xml omit-xml-declaration=no indent=yes";
let $param1:= request:get-parameter("param1",'0')
let $person :=
if($param1 = '0')
then "'*'"
else concat('contributions/person/#val="',$param1,'"')
return
<xml>
{
for $x in subsequence(//foo/bar[$person],1,3)
return $x
}
</xml>
The code above shows that I get the parameter from the url $param1.
variable $person checks to see if there was a parameter and based on that creates a query parameter. This variable works fine, from testing it prints out either '*' for no param or
contributions/person/#val='hello, world'
When I run the query it prints out as if the value is '*'. In the for $x part, can I pass a variable like that? I've tried putting concat($person,'') with the same results. Hardcoding the full path gives me the results I'm looking for, but I'm looking to create something more dynamic.
To note: there is only one variable, $person, but there will be others once I get it to work

I think ideally you would avoid dynamic string evaluation. In this example, some pretty simple reorganization would solve the problem without it:
<xml>
{
for $x in subsequence(//foo/bar[
if ($param1 = '0')
then *
else (contributions/person/#val = $param1)
],1,3)
return $x
}
</xml>
However, you can use eval(), but keep in mind there are security risks:
<xml>
{
for $x in subsequence(eval(
concat('//foo/bar[',$person,']')
),1,3)
return $x
}
</xml>

Related

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

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.

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)]

Xquery - return only distinct attributes from if/then

I've been trying to write a query to get distinct attribute values after using if/then to determine whether I'll use the element in the first place. Here's my example xml and the query i've written so far:
<donors>
<donor donor_id="x21" cn_id="x12">
<homeless>$1201</homeless>
<conservation>$300</conservation>
<cancerResearch>$250</cancerResearch>
</donor>
<donor donor_id="x23" cn_id="x13">
<homeless>$121</homeless>
<conservation>$30</conservation>
<cancerResearch>$50</cancerResearch>
</donor>
<donor donor_id="x24" cn_id="x14">
<homeless>$1201</homeless>
<cancerResearch>$250</cancerResearch>
</donor>
<donor donor_id="x25" cn_id="x12">
<homeless>$1201</homeless>
<conservation>$300</conservation>
<cancerResearch>$250</cancerResearch>
</donor>
</donors>
I want to first get all donors who have a child "conservation". I've done the following for that:
<conservationists>
{
for $x in //donor
return
if(exists($x/conservation))
then <conservationist cn_id="{$x/#cn_id}/>
else ()
}
</conservationists>
I tried wrapping the whole thing in distinct-values but that just gave nothing, and every where else I tried doing something to that effect I just ended up with an end tag.
This is one possible way :
<conservationists>
{
for $x in distinct-values(//donor[conservation]/#cn_id)
return
<conservationist cn_id="{$x}"/>
}
</conservationists>
xpathtester demo
The expression distinct-values(//donor[conservation]/#cn_id) returns distinct values of cn_id attribute from donor elements that have at least one conservation child element.

Use Datasource Properties in XPath Expression of SoapUI

I need to know whether it is possible to use a datasource property in XPath Expression panel of XPath Match Configuration. For instance, if we have the following XML document:
<ns1:Ions>
<ns1:Ion>UI</ns1:Ion>
<ns1:IonType>X</ns1:IonType>
<ns1:StartDate>2010-05-10</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>HH</ns1:Ion>
<ns1:IonType>RI</ns1:IonType>
<ns1:StartDate>1998-11-23</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>CF</ns1:Ion>
<ns1:IonType>A</ns1:IonType>
<ns1:StartDate>2000-06-10</ns1:StartDate>
</ns1:Ions>
I need to evaluate to see whether a content of IonType is 'A' only if its sibling node, Ion, has a value of 'CF'. I was hoping to accomplish this by setting XPath Match Configuration as following:
XPath Expression (DataSourceInput#ION is 'CF')
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()=${DataSourceInput#ION}]]/ns1:IonType/text()
Expected Results (DataSourceInput#ION_TYPE is 'A')
${DataSourceInput#ION_TYPE}
Running the test would result in SoapUI [Pro] to error the following, Missing content for xpath declare. If I replace ${DataSourceInput#ION} with an actual value, i.e. 'CF', the test works accordingly (I even tried place single quotes around ${DataSourceInput#ION}, but it didn't work).
Is there another way of accomplish this in SoapUI?
I try what you do and it works for me if I put single quotes around the property:
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()='${DataSourceInput#ION}']]/ns1:IonType/text()
Did you check that testStep name is exactly DataSourceInput? If there are spaces in the TestStep name (i.e your testStep name is Data Source Input you have to put ${Data Source Input#ION}).
Anyway I give you another way to do so, you can add a testStep of type groovy script after the testStep where you are getting the <Ions>response, and check the assert here like follows:
// get xml holder
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context);
def ionsHolder = groovyUtils.getXmlHolder("IonsTestStepName#response");
// generate xpath expression
def xpathExpression = "//*:Ions[*:Ion[text()='" + context.expand('${DataSourceInput#ION}') + "']]/*:IonType/text()";
log.info xpathExpression;
// get the node value
def nodeValue = ionsHolder.getNodeValue(xpathExpression);
// check expected value
assert nodeValue == context.expand('${DataSourceInput#ION_TYPE}'),'ERROR IONS VALUE';
Hope this helps,

php xpath query on and xpath result

Can I use an xpath query on a result already obtained using xpath?
In most hosting languages/environments (like XSLT, XQuery, DOM) you can. Don't know about PHP, but it would be strange if it doesn't allow this.
Of course, the result of the first query must be a node-set, in order for a future "/" operator to be possible/allowed/successful on it.
I have done it in PHP/SimpleXML. The thing that I didn't understand at first is that you're still dealing with the full SimpleXML object, so if you start with "/nodename", you're operating on root. If you start with "nodename" you are starting at the beginning of the result node. Here's my example:
$parsed=simplexml_load_string($XML);
$s = '/ItemSearchResponse/Items/Item';
$items = $parsed->xpath($s);
foreach($items as $item)
{
$s = 'ItemAttributes/Feature';
$features[]=$item->xpath($s);
$s = 'ASIN';
$asins[]=$item->xpath($s);
$s = 'ImageSets/ImageSet[#Category="primary"]';
$primary_img_set=$item->xpath($s);
$s = 'MediumImage/URL';
$medium_image_url[] = $primary_img_set[0]->xpath($s);
}
In PHP, for example, you can run a query with a context, i.e. a given node. So if you have got a DOMNodeList as a result of the first query you can do things like this:
$query1 = '//p';
$query2 = './a'; // do not forget the dot
$node = $xpath->query($query1)->item(0);
$result = $xpath->query($query2, $node);
Of course this is a silly example because it could have been done just in one shot with the correct XPath experssion but I believe it illustrates your question.

Resources