Handling undefined number of external variables - xquery

I'm using baseX in a REST environment and I'm quite stuck trying to run an .xq script with an undefined number of GET variables (could be 1 but could be 10)
I'd like to make my xq script generic about that and construct my query independently.
Is there a way to achieve that, playing with array or sending differently my variables, or I dunno how ?
here is my API call
http://basex:8984/rest/?run=WEB-INF/data/test.xq&$tag=p&value=sciences&tag2=p&value2=test&tag3=testdzq
here is my text.xq
declare variable $tag external;
declare variable $value external;
declare variable $tag2 external;
declare variable $value2 external;
<documents>
{for $doc in collection("testdb2")
where $doc//*[name() eq $tag]/text()[matches(., $value )]
and $doc//*[name() eq $tag2]/text()[matches(., $value2 )]
return <doc>{$doc//titleStmt/title/text()}</doc>
}
</documents>
Thanks !

found this here (see http-params function) https://www.balisage.net/Proceedings/vol18/print/Murray01/BalisageVol18-Murray01.html
(: BaseX example :)
(: In the controller ... :)
module namespace c = "http://balisage.net/ns/Bal2016murr0319/controller";
import module namespace request = "http://exquery.org/ns/request";
import module namespace item = "http://balisage.net/ns/Bal2016murr0319/views/item" at "views/item.xqm";
(:~ Returns a map containing the HTTP request parameters. :)
declare function c:http-params()
as map(*)
{
map:merge(
for $name in request:parameter-names()
let $value := request:parameter($name)
return map:entry($name, $value)
)
};
(:~ Calls the appropriate view, based on user input. :)
declare function c:get-view()
as element(html)
{
(: get HTTP request parameters :)
let $params := c:http-params()
return
if (map:get($params, "id")) then
(: the presence of "id" indicates that the user is requesting the item-level page for this unique identifier :)
(: call the item-level view :)
item:get-html($params)
else if ... (: call some other view :)
else if ... (: call some other view :)
else (: call the view for the home page ... :)
};

Related

Call custom xquery function in eXist-db using url

How to call a custom xquery function in exist-db using the REST API ?
Is it possible to have more than 1 function in the xquery file ?
declare function local:toto() as node() {
return doc("/db/ProjetXML/alice.xml")/raweb/identification/projectName)
};
declare function local:pomme() as node() {
return doc("/db/ProjetXML/carmen.xml")/raweb/identification/projectSize);
};
If I call it using :
http://localhost:8080/exist/rest/db/ProjetXML/orange.xqy?_query=local:toto()
I get the following error :
err:XPST0017 Call to undeclared function: local:toto [at line 1, column 1, source: local:toto()]
Your help is appreciated.
You have syntax errors in your XQuery:
You have two functions named local:toto(). Each function must have a distinct name.
There is no semicolon following the function definition, i.e. } should be };.
Also you should remove the return expression, as there is no preceding binding.
Another option would be to parameterize the input file, e.g.:
import module namespace request="http://exist-db.org/xquery/request";
declare function local:toto($name as xs:string) as node() {
let $doc :=
if($name eq "carmen")then
doc("/db/ProjetXML/carmen.xml")
else
doc("/db/ProjetXML/alice.xml")
return
$doc/raweb/identification/projectName);
};
local:toto(request:get-parameter("name", "alice"))
You can then call this via the REST Server using a URL like:
http://localhost:8080/exist/rest/db/ProjetXML/orange.xqy?name=carmen

How to evaluate the query by supplying a variable whose value is evaluated at runtime?

I have a piece of code where I want to return total number of documents in a specific forest. I want to determine whether a forest doc count is 0 or more.
declare function local:forest-doc-count($db-name,
$f-id as xs:unsignedLong) as xs:integer {
let $_ := xdmp:log(xdmp:describe(fn:concat("$db-name -->", $db-name), (),()))
let $query := 'xquery version "1.0-ml";
declare variable $f-id as xs:unsignedLong external;
xdmp:estimate(cts:search(fn:doc(), (), (), (), $f-id))'
let $count := xdmp:eval($query , (),
map:entry("f-id", $f-id),
map:entry("database", xdmp:database($db-name))
)
return $count
};
where $f-id is forest Id and $db-name is Database name. For now I'm getting
Undefined variable $f-id
Declare an external variable in the query and then bind a value to the external variable in the call.
The following sketch is untested, but something along these lines should work:
declare function local:forest-doc-count(
$db-name as xs:string,
$f-id as xs:unsignedLong
) as xs:integer {
xdmp:eval(
'xquery version "1.0-ml";
declare variable $f-id as xs:unsignedLong external;
xdmp:estimate(cts:search(fn:doc(), (), (), (), $f-id))',
map:entry("f-id", $f-id),
map:entry("database", xdmp:database($db-name))
)
};
A footnote: You might want to declare the type of parameters and return values when they are determinate to take advantage of type checking.
Hoping that's useful,

How to share markup snippets amongst functions in eXist-db?

I wonder whether there is a way how to share html code snippets in eXist-db. I have two different (more expected later) functions returning the same big html form for different results. It is annoying to maintain the same code when I change something in one of these. I have tried:
Saving it like html file and load it with doc() function (eXist complains it is not an xml file, it is binary.
Saving it like global variable into a separate module (eXist complains there is a problem with contexts). I don’t know how to pass such a variable without the namespace prefix.
Saving it like a function returning its own huge variable (eXist complains there is a problem with contexts).
What is the best practice?
UPDATE
Well, I have tried to put the snippet into a variable insinde a function loaded as a module. For me, it seems reasonable. However, I got an error when try to return that:
err:XPDY0002 Undefined context sequence for 'child::snip:snippet' [at line 62, column 13, source: /db/apps/karolinum-apps/modules/app.xql]
In function:a pp:book-search(node(), map, xs:string?) [34:9:/db/apps/karolinum-apps/modules/app.xql]
I am calling it like so:
declare function app:list-books($node as node(), $model as map(*)) {
for $resource in collection('/db/apps/karolinum-apps/data/mono')
let $author := $resource//tei:titleStmt/tei:author/text()
let $bookName := $resource//tei:titleStmt/tei:title/text()
let $bookUri := base-uri($resource)
let $imgPath := replace($bookUri, '[^/]*?$', '')
let $fileUri := ( '/exist/rest' || $bookUri )
let $fileName := replace($bookUri, '.*?/', '')
return
if ($resource//tei:titleStmt/tei:title)
then
snip:snippet
else ()
};
Any ideas, please?
UPDATE II
Here I have the function in the module:
module namespace snip = "http://46.28.111.241:8081/exist/db/apps/karolinum-apps/modules/snip";
declare function snip:snippet($node as node(), $model as map(*), $author as xs:string, $bookTitle as xs:string, $bookUri as xs:anyURI, $fileUri as xs:anyURI) as element()* {
let $snippet :=
(
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{$bookTitle} ({$author})</h3>
</div>
<div class="panel-body">
...
</div>
)
return $snippet
};
Here I am trying to call it:
declare function app:list-books($node as node(), $model as map(*)) {
for $resource in collection('/db/apps/karolinum-apps/data/mono')
let $author := $resource//tei:titleStmt/tei:author/text()
let $bookTitle := $resource//tei:titleStmt/tei:title/text()
let $bookUri := base-uri($resource)
let $fileUri := ('/exist/rest' || $bookUri)
let $fileName := replace($bookUri, '.*?/', '')
where not(util:is-binary-doc($bookUri))
order by $bookTitle, $author
return
snip:snippet($author, $bookTitle, $bookUri, $fileUri)
};
It throws:
err:XPST0017 error found while loading module app: Error while loading module app.xql: Function snip:snippet() is not defined in namespace 'http://46.28.111.241:8081/exist/db/apps/karolinum-apps/modules/snip' [at line 35, column 9]
When I tried to put the snippet into a variable, it was not possible to pass there those local variables used (it threw $fileUri is not set). Besides that I tried to change the returned type element()* but nothing helped.
All of your approaches should work. Let me address each one:
Is the HTML snippet well-formed XML? If so, save it as, e.g., form.xml or form.html (since by default eXist assumes files with the .html extension are well-formed; see mime-types.xml in your eXist installation folder) and refer to it with doc($path). If it is not well-formed, you can save it as form.txt and pull it in with util:binary-to-string(util:binary-doc($path)). Or make the HTML well-formed and use the first alternative.
This too is valid, so you must not be properly declaring or referring to the global variable. What is the exact error you are getting? Can you post a small example snippet that we could run to reproduce your results?
See #2.
I was very close. It was necessary to somehow pass parameters to the nested function and omit eXist’s typical $node as node(), $model as map(*) as arguments.
Templating function:
declare function app:list-books($node as node(), $model as map(*)) {
for $resource in collection('/db/apps/karolinum-apps/data/mono')
let $author := $resource//tei:titleStmt/tei:author/text()
let $bookTitle := $resource//tei:titleStmt/tei:title/text()
let $bookUri := base-uri($resource)
let $bookId := xs:integer(util:random() * 10000)
let $fileUri := ('/exist/rest' || $bookUri)
let $fileName := replace($bookUri, '.*?/', '')
where not(util:is-binary-doc($bookUri))
order by $bookTitle, $author
return
snip:snippet($author, $bookTitle, $bookUri, $bookId, $fileUri)
};
Snippet function:
declare function snip:snippet($author as xs:string, $bookTitle as xs:string, $bookUri as xs:anyURI, $bookId as xs:string, $fileUri as xs:anyURI) as element()* {
let $snippet :=
(
<div class="panel panel-default">
...
</div>
)
return $snippet
};

How to dynamically create a search query based on a set of quoted strings in MarkLogic

I have the following query, where i want to form a string of values from a list and i want to use that comma separated string as an or-query but it does not give any result, however when i return just the concatenated string it gives the exact value needed for the query.
The query is as follows:
xquery version "1.0-ml";
declare namespace html = "http://www.w3.org/1999/xhtml";
declare variable $docURI as xs:string external ;
declare variable $orQuery as xs:string external ;
let $tags :=
<tags>
<tag>"credit"</tag>
<tag>"bank"</tag>
<tag>"private banking"</tag>
</tags>
let $docURI := "/2012-10-22_CSGN.VX_(Citi)_Credit_Suisse_(CSGN.VX)__Model_Update.61198869.xml"
let $orQuery := (string-join($tags/tag, ','))
for $x in cts:search(doc($docURI)/doc/Content/Section/Paragraph, cts:or-query(($orQuery)))
let $r := cts:highlight($x, cts:or-query($orQuery), <b>{$cts:text}</b>)
return <result>{$r}</result>
The exact query that i want to run is :
cts:search(doc($docURI)/doc/Content/Section/Paragraph, cts:or-query(("credit","bank","private banking")))
and when i do
return (string-join($tags/tag, ','))
it gives me exactly what i require
"credit","bank","private banking"
But why does it not return any result in or-query?
The string-join step should not need to be string-join. That passes in a literal string. In xQuery, sequences are your friend.
I think you want to do something like this:
let $tags-to-search := ($tags/tag/text()!replace(., '^"|"$', '') ) (: a sequence of tags :)
cts:search(doc($docURI)/doc/Content/Section/Paragraph, cts:word-query($tags-to-search))
cts:word-query is the default query used for parameter 2 of search if you pass in a string. cts:word query also returns matches for any items in a sequence if presented with that.
https://docs.marklogic.com/cts:word-query
EDIT: Added the replace step for the quotes as suggested by Abel. This is specific to the data as presented by the original question. The overall approach remains the same.
Maybe do you need something like this
let $orQuery := for $tag in $tags/tag return cts:word-query($tag)
I used fn:tokenize instead it worked perfectly for my usecase
its because i was trying to pass these arguments from java using XCC api and it would not return anything with string values
xquery version "1.0-ml";
declare namespace html = "http://www.w3.org/1999/xhtml";
declare variable $docURI as xs:string external ;
declare variable $orQuery as xs:string external ;
let $input := "credit,bank"
let $tokens := fn:tokenize($input, ",")
let $docURI := "2012-11-19 0005.HK (Citi) HSBC Holdings Plc (0005.HK)_ Model Update.61503613.pdf"
for $x in cts:search(fn:doc($docURI), cts:or-query(($tokens)))
let $r := cts:highlight($x, cts:or-query(($tokens)), <b>{$cts:text}</b>)
return <result>{$r}</result>

Pass a map to External function

I have the following module
xquery version "1.0-ml";
declare variable $datasets as map:map external;
...
I want to call this so I do an xdmp:invoke like this
...
let $update := xdmp:invoke("/app/lib/my-module.xqy", (xs:QName("datasets"), $map), <options xmlns="xdmp:eval">
<modules>{xdmp:modules-database()}</modules>
</options>)
...
$map is of type map:map.
Running this gives me the following error
[1.0-ml] XDMP-ARG: xdmp:invoke("/app/lib/20140527-0916-copy-y-axis-labels-from-chart-to-dataset-...", fn:QName("", "datasets"), <options xmlns="xdmp:eval"><modules>0</modules></options>) -- Invalid argument
Why is that?
It's hard to know for sure from the limited code samples you've posted, but I think that your $map variable is bound to the empty sequence (the rough analogue of null in XPath/XQuery).
I've created the following main module, that simply returns the external variable $datasets:
xquery version "1.0-ml";
declare variable $datasets as map:map external;
$datasets
Invoking it as follows works correctly:
let $map := map:entry("key", "value")
return
xdmp:invoke("/test.xqy",
(xs:QName("datasets"), $map),
<options xmlns="xdmp:eval">
<modules>{xdmp:modules-database()}</modules>
</options>)
This results in the "invalid argument" error:
let $map := ()
return
xdmp:invoke("/test.xqy",
(xs:QName("datasets"), $map),
<options xmlns="xdmp:eval">
<modules>{xdmp:modules-database()}</modules>
</options>)
XQuery flattens sequences (they don't nest like s-expressions), so when $map is the empty sequence, the <options/> element becomes the value of the $datasets param, which is an invalid value for the external variable.
Update: Doh! #mblakele's comments below explain the error conditions ...
TL;DR: () is not an instance of map:map.

Resources