Xquery - how do I apply a map to a sequence? - dictionary

I would like to apply a function to each element in a sequence and get the result. Can someone point me in the right direction?
e.g.
declare function local:ToTextNode($string as xs:string) as Node()
{
text { $string }
};
I want to apply the above to:
('foo','bar','baz')
...and get a sequence of nodes.

Use the simple map operator !, but it requires an XQuery processor implementing XQuery 3.0.
declare function local:ToTextNode($string as xs:string) as node()
{
text { $string }
};
('foo','bar','baz') ! local:ToTextNode(.)
You do not need to define a function for that, you can also directly use the text node constructor:
('foo','bar','baz') ! text { . }
If your XQuery engine does not support the map operator (yet), you will have to use a flwor expression:
for $i in ('foo','bar','baz')
return text { $i }

Related

Invoking an XQuery Updating function results in the error If any subexpression is updating, then all must be updating

My XML document contains a record of the movement of an object. The XML document consists of a series of observations. Each observation contains the lat/long location of the object, the lat/long of the observing sensor, and a date/time stamp.
<Track-History>
<Track-ID>XYZ</Track-ID>
<Observation>
<Target-Latitude>10.3</Target-Latitude>
<Target-Longitude>20.8</Target-Longitude>
<Observer-Latitude>40.0</Observer-Latitude>
<Observer-Longitude>50.0</Observer-Longitude>
<DateTime>20230202T071700.00</DateTime>
</Observation>
<Observation>
<Target-Latitude>15.1</Target-Latitude>
<Target-Longitude>25.2</Target-Longitude>
<Observer-Latitude>40.0</Observer-Latitude>
<Observer-Longitude>50.0</Observer-Longitude>
<DateTime>20230202T071800.00</DateTime>
</Observation>
</Track-History>
I want to fuzz the locations of the object by rounding the decimal lat and long values. So I created this updating function:
declare updating function f:fuzzPoint($lat as element(), $long as element())
{
replace node $lat with
element {name($lat)} {round(number(data($lat)))},
replace node $long with
element {name($long)} {round(number(data($long)))}
};
When I invoke that function:
{f:fuzzPoint($obs/Target-Latitude, $obs/Target-Longitude)}
I get this error message:
If any subexpression is updating, then all must be updating
Oxygen XML gives a red squiggly line under the function arguments, so apparently the arguments must be updating. Yes? If so, how to make the arguments updating?
Below is my complete XQuery Update program.
declare namespace f = "function";
declare variable $Track-History := doc('Track-History.xml');
declare variable $track-history-points := (for $i in $Track-History//Observation return [$i/Target-Latitude, $i/Target-Longitude]);
declare variable $AOR := (); (: should be a sequence of points, fake it for now :)
declare function f:isInside($points, $polygon) as xs:boolean
{
true()
};
declare updating function f:fuzzPoint($lat as element(), $long as element())
{
replace node $lat with
element {name($lat)} {round(number(data($lat)))},
replace node $long with
element {name($long)} {round(number(data($long)))}
};
if (f:isInside($track-history-points, $AOR)) then
for $obs in $Track-History//Observation return
replace node $obs with
<Observation>
{f:fuzzPoint($obs/Target-Latitude, $obs/Target-Longitude)}
{$obs/Observer-Latitude}
{$obs/Observer-Longitude}
{$obs/DateTime}
</Observation>
else
replace node $Track-History/* with
<Track-History/>
Inside an update expression – replace node $obs with ... – it’s not allowed to perform other updates. The code works if you modify f:fuzzPoint such that it returns the new element nodes:
declare function f:fuzzPoint($lat as element(), $long as element()) {
element { name($lat) } { round(number(data($lat))) },
element { name($long) } { round(number(data($long))) }
};
The updating keyword in the function declaration makes sense if you move the update operation inside the function body:
declare updating function f:replace($obs as element()) {
let $lat := $obs/Target-Latitude
let $long := $obs/Target-Longitude
return replace node $obs with <Observation>{
element { name($lat) } { round(number(data($lat))) },
element { name($long) } { round(number(data($long))) },
$obs/Observer-Latitude,
$obs/Observer-Longitude,
$obs/DateTime
}</Observation>
};
if (f:isInside($track-history-points, $AOR)) then
for $obs in $Track-History//Observation return f:replace($obs)
else
replace node $Track-History/* with
<Track-History/>
I wonder whether
declare namespace f = "function";
declare variable $Track-History := doc('Track-History.xml');
declare variable $track-history-points := (for $i in $Track-History//Observation return [$i/Target-Latitude, $i/Target-Longitude]);
declare variable $AOR := (); (: should be a sequence of points, fake it for now :)
declare function f:isInside($points, $polygon) as xs:boolean
{
true()
};
declare function f:fuzzValue($v as xs:double) as xs:double
{
round($v)
};
if (f:isInside($track-history-points, $AOR)) then
for $lv in $Track-History//Observation/(Target-Latitude, Target-Longitude)
return
replace value of node $lv
with f:fuzzValue($lv)
else
replace node $Track-History/* with
<Track-History/>
is all you want to do there.

If statement that assigns a variable a string value

I am new to XQuery and struggling to find anything online that teaches me simple stuff like this. I am trying to just pass in 2 strings, where 1 string is returned as the tag and 1 string is returned as a new value. So far I have this, but am getting build errors.
declare function util:format-promo-tracking-element(
$fieldName as xs:string,
$stringValue as xs:string)
{
let $elementName := $fieldName
if($stringValue = '5.00') then (
let $stringValue := '4.05'
return
element {$elementName}
{
data($stringValue)
}
)
else()
};
I have tried moving the return statement to the end as well.
XQuery is an functional language so trying to assign a new value to an existing variable is not allowed.
I think you want something like this.
declare function util:format-promo-tracking-element(
$fieldName as xs:string,
$stringValue as xs:string)
{
let $newValue := if ($stringValue = '5.00') then '4.05' else $stringValue
return
element {$fieldName}
{
$newValue
}
};
There are quite a few basic examples on Wikibooks.

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 change the XML structure using XQuery

I have a XML file containing Employees Name and the Job done by them.
The structure of the XML file is -
<Employee>AAA#A#B#C#D</Employee>
<Employee>BBB#A#B#C#D</Employee>
<Employee>CCC#A#B#C#D</Employee>
<Employee>DDD#A#B#C#D</Employee>
There are thousands of records and I have to change structure to -
<Employee>
<Name>AAA</Name>
<Jobs>
<Job>A</Job>
<Job>B</Job>
<Job>C</Job>
<Job>D</Job>
</Jobs>
</Employee>
How to get this done using XQuery in BaseX ?
3 XQuery functions, substring-before, substring-after and tokenize are used to get
the required output.
substring-before is used to get the Name.
Similarly, the substring-after is used to get the Job portion.
Then the tokenize function, is used to split the Jobs.
let $data :=
<E>
<Employee>AAA#A#B#C#D</Employee>
<Employee>BBB#A#B#C#D</Employee>
<Employee>CCC#A#B#C#D</Employee>
<Employee>DDD#A#B#C#D</Employee>
</E>
for $x in $data/Employee
return
<Employee>
{<Name>{substring-before($x,"#")}</Name>}
{<Jobs>{
for $tag in tokenize(substring-after($x,"#"),'#')
return
<Job>{$tag}</Job>
}</Jobs>
}</Employee>
HTH...
Tokenizing the string is probably easier and faster. tokenize($string, $pattern) splits $string using the regular expression $pattern, head($seq) returns the first value of a sequence and tail($seq) all but the first. You could also use positional predicates of course, but these functions are easier to read.
for $employee in //Employee
let $tokens := tokenize($employee, '[##]')
return element Employee {
element Name { head($tokens) },
element Jobs {
for $job in tail($tokens)
return element Job { $job }
}
}

XQuery - Variables in function definition

Is it possible to declare a variable to be used only inside of function declaration? If so - how to do it?
Variable declaration can only be placed in the prolog of a query, but you can wrap your function code with a FLWOR expression, consisting of a single LET and RETURN clause. An example:
declare function local:func() {
let $var := ...your variable...
return
...your actual code...
};
Hope this helps,
Christian
You can use XQuery Scripting to declare local variables.
declare %a:sequential function local:func() {
variable $var := ....;
...actual code...
}
XQuery Scripting is described in the following tutorial: http://www.zorba-xquery.com/site2/doc/latest/zorba/html/scripting_tutorial.html

Resources