I am attempting to build up an XML document to return from a function as below. Why does the evaluation substitution work if I use let to store it in another variable first? Can I not evaluate the expression in place? As you can see in the result at the end, the resulting XML is only populated with the variable value where I had stored it in the $x variable.
declare function local:oim-to-canonical($oimContent as node()) {
let $x := $oimContent/account/domain/text()
return
<person xmlns="http://schemas.abbvienet.com/people-db/model">
<account>
<domain>{ $oimContent/account/domain/text() }</domain>
<username>{ $oimContent/account/username/text() }</username>
<status>{ $oimContent/account/status/text() }</status>
<x>{ $x }</x>
</account>
</person>
};
local:oim-to-canonical(
<person>
<account>
<domain>MYDOMAIN</domain>
<username>ttt</username>
<status>ENABLED</status>
</account>
</person>
)
Results in:
<person xmlns="http://schemas.abbvienet.com/people-db/model">
<account>
<domain/>
<username/>
<status/>
<x>MYDOMAIN</x>
</account>
</person>
Is this the correct behavior?
Building on the comment after the answer you wrote, you're right that wildcarding the namespace in the XPath will work:
<domain>{ $oimContent/*:account/*:domain/text() }</domain>
However, this is considered a bad practice when it's avoidable. In order to execute that XPath, MarkLogic needs to do more work than if the namespace is provided. The challenge is that your input XML is using the empty namespace and there's no way to specify that. I would modify the input to use a namespace:
xquery version "1.0-ml";
declare namespace inp = "input";
declare function local:oim-to-canonical($oimContent as node()) {
let $x := $oimContent/inp:account/inp:domain/text()
return
<person xmlns="http://schemas.abbvienet.com/people-db/model">
<account>
<domain>{ $oimContent/inp:account/inp:domain/text() }</domain>
<username>{ $oimContent/inp:account/inp:username/text() }</username>
<status>{ $oimContent/inp:account/inp:status/text() }</status>
<x>{ $x }</x>
</account>
</person>
};
local:oim-to-canonical(
<person xmlns="input">
<account>
<domain>MYDOMAIN</domain>
<username>ttt</username>
<status>ENABLED</status>
</account>
</person>
)
This allows your XPath to be more explicit. Small scale, the difference may not matter, but at scale the difference adds up.
I concur with Dave that using a namespace for the input makes life easier. Just for the sake of completeness, you can also prevent the issue by not using literal XML with a default namespace, but by using element constructors instead:
declare variable $ns := "http://schemas.abbvienet.com/people-db/model";
declare function local:oim-to-canonical($oimContent as node()) {
let $x := $oimContent/account/domain/text()
return
element { fn:QName($ns, "person") } {
element { fn:QName($ns, "account") } {
element { fn:QName($ns, "domain") } { $oimContent/account/domain/text() },
element { fn:QName($ns, "username") } { $oimContent/account/username/text() },
element { fn:QName($ns, "status") } { $oimContent/account/status/text() },
element { fn:QName($ns, "x") } { $x }
}
}
};
local:oim-to-canonical(
<person>
<account>
<domain>MYDOMAIN</domain>
<username>ttt</username>
<status>ENABLED</status>
</account>
</person>
)
I usually prefer literal XML though, as it is more dense/less clutter..
HTH!
Related
I'm new to xquery. Why does the first xquery statement work but the second doesn't? The first has multiple xml elements at the second level and the first has multiple at the top level.
let $payload := <root><foo>bar</foo></root>
return
<root>
{
if (exists($payload/foo)) then
<prop>
<key>mykey</key>
<value>bar</value>
</prop>
else
""
}
</root>
and this doesn't
let $payload := <root><foo>bar</foo></root>
return
<root>
{
if (exists($payload/foo)) then
<key>mykey</key>
<value>bar</value>
else
""
}
</root>
You will need to wrap your element into parentheses and separate the elements with commas as there is no enclosing root element:
if (exists($payload/foo)) then (
<key>mykey</key>,
<value>bar</value>
) else (
""
)
A single element constructor is a valid expression:
<key>mykey</key>
A sequence of two element constructors (with no separator) is not:
<key>mykey</key>
<value>bar</value>
Note that this differs from XSLT, where such element constructors (called literal result elements) always appear as part of a "sequence constructor", and a sequence constructor allows multiple elements to appear.
Since you are starting to learn XQuery it might interest you that instead of returning an empty string you can also return an empty sequence instead.
let $payload := <root><foo>bar</foo></root>
return
<root>
{
if (exists($payload/foo))
then (
<key>mykey</key>,
<value>bar</value>
)
else ()
}
</root>
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
xqy file:
import module namespace functx="http://www.functx.com";
declare variable $defaultXMLNS:="http://www.test.com#";
declare variable $defaultXMLBase:=$defaultXMLNS;
declare function local:descriptionConstructorTail(
$seq as item()*,
$i as xs:integer?,
$res as item()*
)
{
if($i <= 0)
then $res
else local:descriptionConstructorTail($seq,
$i - 1,
functx:value-union($res,
(<test1 about="{$seq[$i]/#value}"/>)))
};
declare function local:descriptionConstructor($pnode as node()*)
{
local:descriptionConstructorTail($pnode//xs:enumeration,
count($pnode//xs:enumeration),
())
};
element test
{
local:descriptionConstructor(doc("./test2.xsd"))
}
xsd file:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified"
elementFormDefault="qualified">
<xs:simpleType name="size">
<xs:restriction base="xs:string">
<xs:enumeration value="small" />
<xs:enumeration value="medium" />
<xs:enumeration value="large" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
I have been messing around with this small program for 2 hours. The output is always not expected.
$ basex test2.xqy
<test/>
For the example xsd file above, I want to have output like this:
<test>
<test1 about="small"/>
<test1 about="medium"/>
<test1 about="large"/>
</test>
This example may not seen logical, because you don't have to use recursive function to do this. But I wanna do this as an exercise for recursive function.
While the functx library is a very convenient collection of utility functions, is it important to know what exactly those do so you can avoid surprises like this one. functx:value-union(...) is implemented as follows:
declare function functx:value-union(
$arg1 as xs:anyAtomicType*,
$arg2 as xs:anyAtomicType*
) as xs:anyAtomicType* {
distinct-values(($arg1, $arg2))
};
So it just applies distinct-values(...) to the concatenation of its inputs. Since this function works on values of typexs:anyAtomicType, your XML nodes are atomized. This only leaves their text contents, which your elements don't have. So you just build the union of empty sequences over and over.
This should work fine:
declare function local:descriptionConstructorTail($seq as item()*, $i as xs:integer?, $res as item()*) {
if($i <= 0) then $res
else local:descriptionConstructorTail($seq, $i - 1, distinct-values(($res, $seq[$i]/#value)))
};
declare function local:descriptionConstructor($pnode as node()*) {
let $enums := $pnode//xs:enumeration
for $res in local:descriptionConstructorTail($enums, count($enums), ())
return <test1 about="{$res}"/>
};
element test {
local:descriptionConstructor(doc("./test2.xsd"))
}
By the way your recursive function follows the recursion scheme encoded by the higher-order function fold-right(...), so you could also write:
declare function local:descriptionConstructor($pnode as node()*) {
for $res in
fold-right(
$pnode//xs:enumeration,
(),
function($enum, $res) {
distinct-values(($enum/#value, $res))
}
)
return <test1 about="{$res}"/>
};
element test {
local:descriptionConstructor(doc("./test2.xsd"))
}
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 }
}
}
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 }