How to pass attribute for parameter using XQUERY - xpath

Hi team,
<cars>
<car id="1">Ford</car>
<car num="2">Mazda</car>
<car vol="3">Toyota</car>
<car soid="4">Lexus</car>
</cars>
XQuery:
let $val := 1
let $attr := id
return //car[$attr=$val]
I dont know how to pass attribute name in parameter can you please help?

As an alternative, from XQuery 3.0 you have higher-order functions:
let $val := 1
let $attr := function($x){$x!#id}
return //car[$attr(.)=$val]]

XQuery is pretty static and doesn't much like dynamic path components. You can leverage local-name():
let $val := 1
let $attr := "id"
return //car[#*[local-name()=$attr and data(.)=$val]]
This is not particularly efficient, though. Work with static expressions when you can, even if this means a repeating a few conditional expressions here and there.

Related

How to declare array in xpath query using oracle

I am trying to create an array in XPath query using oracle, but it is treating as a string, below is the code snippet, can someone please help me where I am doing wrong.
XMLQUERY('
let $vals :=
if (count($Cntnt/emp/emp_content/emp_part)=1) then
("1")
else if (count($Cntnt/emp/emp_content/emp_part)=2) then
("00","01","10","11")
else if (count($Cntnt/emp/emp_content/emp_part)==3) then
("111","110","101","100","011","010","001","000")
else()
for $val in $vals
return concat ($val,"")
'
PASSING emp_xml AS "Cntnt" RETURNING CONTENT).getStringVal() as tst.

xquery efficient predicates versus for...where

I have a series of xml nodes that look like this:
let $x :=
<works>
<work xml:id="W1">
<author corresp="AU08"/>
<group corresp="GR03"/>
</work>
<work xml:id="W2">
<author corresp="AU09"/>
<group corresp="GR10"/>
</work>
<work xml:id="W3">
<author corresp="AU08"/>
<group corresp="GR05"/>
</work>
....
</works>
I have a search form that may or may not offer sequence parameters against work/#xml:id , work/author/#corresp, and work/affiliation/$corresp, for example:
let $xmlids := ("W1")
let $authors := ()
let $groups := ("GR05","GR08")
or
let $xmlids := ()
let $authors := ("AU01","AU08")
let $groups := ("GR05")
I am trying to create an efficient query in Xquery 3.1 (eXist 4.7) to account for the different permutations of parameters, while outputting work/. This has lead me to build an ugly series of nested if statements which trying to favour predicates over for ...where like the following:
if (count($xmlids) gt 0 and count($authors) gt 0 and count($groups) gt 0)
then
$x/id($xmlids)/author[#corresp=$authors]/parent::work/group[#corresp=$groups]/parent::work
else if (count($xmlids) gt 0 and count($authors) gt 0 and count($groups) eq 0)
then
$x/id($xmlids)/author[#corresp=$authors]/parent::work
else if ...
Is there a more efficient way to build an Xquery accounting for variable present/absent parameters?
Many thanks in advance.
I think for the predicates you just want #corresp=$authors or not(exists($authors)) and #corresp=$groups or not(exists($groups)).
For the id call I think you need
let $work-items := if ($x/id($xmlids)) then $x/id($xmlids) else $x/work
return $work-items[author[#corresp=$authors or not(exists($authors))] and group[#corresp=$groups or not(exists($groups))]]

eXist-DB / XQuery StringValue cannot be cast to AnyURIValue (using compression:zip)

In eXist 4.4/XQuery 3.1, I am building a function to compress a number of xml files into a zip using compression:zip.
I have one function which collects all the URIs for the documents to be compressed, schedule:get-document-uris-for-zip(xmlid as xs:string). This function returns lists of documents like the following:
/db/apps/deheresi/data/MS609-0001.xml
/db/apps/deheresi/data/MS609-0002.xml
/db/apps/deheresi/data/MS609-0003.xml
/db/apps/deheresi/data/MS609-0004.xml
/db/apps/deheresi/data/MS609-0005.xml
/db/apps/deheresi/data/MS609-0006.xml
/db/apps/deheresi/data/MS609-0007.xml
/db/apps/deheresi/data/MS609-0008.xml
/db/apps/deheresi/data/MS609-0009.xml
/db/apps/deheresi/data/MS609-0010.xml
This function is called by the compression function as follows
declare function schedule:create-zip-by-batch()
{
let $batch := doc(concat($globalvar:URIdocuments,"document_collections.xml"))
for $entry in $batch//collection[#compile="y"]
let $zipobject := compression:zip(schedule:get-document-uris-for-zip($entry/string(#xml:id)),false())
let $zipstore := xmldb:store("/db/apps/deheresi/documents",
"MS609_tei.zip",
$zipobject)
return $zipstore
};
This is throwing a cast error as follows, but I can't identify how to resolve this...
org.exist.xquery.value.StringValue cannot be cast to org.exist.xquery.value.AnyURIValue
Many thanks in advance.
Edit - I'm adding here the part of the function schedule:get-document-uris-for-zip(xmlid as xs:string) which outputs the list of URIs. The URIs are built through string concatenation:
(: get names of documents which meet criteria :)
let $list := xmldb:get-child-resources("/db/apps/deheresi/data")[starts-with(., $y/string(#filename)) and ends-with(., $y/string(#ext))]
(: create URI for each document :)
return
for $n in $list
return concat("/db/apps/deheresi/data/",$n)
You're right to find this function a bit confusing. The (eXist-specific) compression:zip() function $sources parameter is typed as if it is quite flexible way, as xs:anyType()+. But really it is quite strict about the two types of item it accepts: a sequence of URIs (i.e., of type xs:anyURI), or a sequence of <entry> elements:
<entry name="filename.ext"
type="collection|uri|binary|xml|text"
method="deflate|store"
>data</entry>
See https://exist-db.org/exist/apps/fundocs/view.html?uri=http://exist-db.org/xquery/compression#zip.2.
The problem with your code is that you are passing strings in your $sources parameter, and have not cast these strings as xs:anyURI.
Here is sample working code:
xquery version "3.1";
let $prepare :=
(
xmldb:create-collection("/db", "test"),
xmldb:store("/db/test", "test.xml", <test/>)
)
let $zip := compression:zip("/db/test/test.xml" cast as xs:anyURI, false())
return
xmldb:store("/db/test", "test.zip", $zip)

xs:string as element() in XQuery

Let's assume I have a XML files, one like this:
<SampleF>
<FirstNode AAA="Something" BBB="Something"></FirstNode>
<SecondNode CCC="Random" DDD="Random"></SecondNode>
</SampleF>
And second one like this:
<SampleF2>
<FirstNode>
<AAA>Something</AAA>
<BBB>Random</BBB>
</FirstNode>
</SampleF2>
And I would like to obtain from both (AAA="Something"/Something) of them as element(). How do I convert it? When in first case I get xs:string and in the second document-node().
I made something like this for the first example but I'm 100% certain there is a better way of doing this
declare function getElementFirstExample($message) as element() {
let $name := "AAA"
let $value := $message/*:SampleF1/*:FirstNode/#AAA
return element {$name} {"$value"}
};
Thank you in advance for your help and advices.
As I understand, you want the value of <FirstNodes/>s AAA attribute or child element, no matter whether it is in the element or attribute.
Use an alternative axis step for accessing both the attribute and element, and data(...) to retrieve the string value.
data(//FirstNode/(#AAA, AAA))
Putting this together for your function and explicit use case:
declare function getElementFirstExample($message) as element() {
let $name := "AAA"
let $value := data($message/*:SampleF1/*:FirstNode/(#AAA, *:AAA))
return element {$name} {"$value"}
};

Passing a subpath as a variable (type of node) or either eval() an string containing a relative XPATH

I want to succed with following functionality, and it works if you harcoded it:
declare function local:sort($collection as node()*, $filter as xs:SUBPATH?) as node()*{
for $element in $collection
order by
if ($filter) then ($element/$filter) (: OR SOME KIND OF fn:eval($filter) IF WE DEFINED $filter AS AN xs:string :)
else ($element/name()) (: Default :)
descending
return $element
};
And it could be called like that:
for $element in local:sort(doc('data')/Data/*,'/#myAttr')
return $element
or
for $element in local:sort(doc('data')/Data/*,'/subnode/subnode/name()')
return $element
or
for $element in local:sort(doc('data')/Data/*,()) (: This is the default of function = own elmentĀ“s name :)
return $element
Mx problem is passing the subpath. Either I need to know some kind of way to send a relative XPATH as an argument and type of node, or I need some kind of eval to pass from xs:string to a runtime valid code
Any help?
You could consider (a) generating a query in which the filter is hard-coded, or (b) using an eval() function specific to your XQuery vendor, or (c) using XQuery 3.0 higher-order functions if your chosen XQuery engine supports them yet.
I ended up doing the following, thanks to this idea How can I solve this autoncremental var case in XQUERY 1.0 FLOWR?:
(: This is a workaround solution as xquery:eval() is not working with var bindings until BaseX 7.3 version :)
(: It evals $context node + literal subpath with a pattern of '(/)lit/lit/lit/#attr' or '(/)lit/lit/lit' representing (/)lit/lit/lit/name(). If subpath null, it returns $context/name().:)
declare function u:eval-path($context as node()*, $subnodes as xs:string*) as item()* {
if(empty($subnodes)) then $context/name()
else(
if (count($subnodes) eq 1) then ( (: Last Element :)
if (starts-with($subnodes[1],'#')) then $context/#*[name()=substring-after($subnodes[1],'#')]
else $context/*[name()=$subnodes[1]]/name()
)
else if ($subnodes[1] eq '') then u:eval-path($context, $subnodes[position() gt 1])
else u:eval-path($context/*[name()=$subnodes[1]],$subnodes[position() gt 1])
)
};
(: Sorts the given collection by given criteria, which should be a pattern '(/)lit/lit/lit/#attr' or '(/)lit/lit/lit' representing (/)lit/lit/lit/name() :)
(: If criteria is null, everything is ordered by $elements/name(). Theres no way to filter intermediate nodes as in /lit/*[name()='X']/lit :)
declare function u:sort($collection as node()*, $criteria as xs:string?) as node()*{
for $element in $collection
order by u:eval-path($element,tokenize($criteria,'/'))
ascending
return $element
};

Resources