XQuery: debugging recursive function - xquery

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"))
}

Related

How do I call a function in xquery

Xml format is like:
<outer_element>
<list>
<leaf name="abc">
<D:a value=""/>
<D:b value="2"/>
<D:c value="3"/>
<D:text_enabled value="true"/>
</leaf>
<leaf name="xyz">
....
</leaf>
</list>
</outer_element>
This is just the structure, below is the xquery for removing certain portion of the xml: to remove leafs that have D:text_enabled value="true"
declare namespace my="my.uri";
declare variable $flag;
declare function my:isEnabled($node as node()) as xs:boolean
{
let $flag :=
for $child in $node/node()
return string-join(if(name($child) eq "D:text_enabled" and $child/#value eq "true") then "true" else "false" , " ")
return contains($flag,"true")
};
declare function my:filtering($node as node())
{
typeswitch ($node)
case element()
return
if (string(local-name($node)) = "import")
then
()
else
if(string(local-name($node)) = "leaf" and $flag=isEnabled($node) )
then
()
else
element
{
node-name($node)
} {
$node/#*
,
for $child in $node/node()
return my:filtering($child )
}
default
return
if (string(local-name($node)) = "import")
then
()
else
$node
};
let $input := doc("input.xml")
return
for $elem in $input/*
return
my:filtering($elem)
Errors I am getting are:
XPST0003: XQuery syntax error in #...eclare variable $flag; declare#:
Expected ':=' or 'external' in variable declaration
XPST0008: XQuery static error in #... = "leaf" and $flag=isEnabled#:
Variable $flag has not been declared
Static error(s) in query
This is nothing to do with calling a function.
A global variable must either be declared external, in which case a value is supplied by the calling application:
declare variable $flag external;
or it must be initialised to a value:
declare variable $flag := false();
But actually, you're not using the global variable at all, so you can just delete the declaration. You've got another quite separate local variable called $flag, that doesn't need a global declaration.
Some further suggestions:
if (string(local-name($node)) = "import") is better written as `if ($node[self::*:import])
As far as I can tell, the code
let $flag :=
for $child in $node/node()
return string-join(if(name($child) eq "D:text_enabled" and $child/#value eq "true") then "true" else "false" , " ")
return contains($flag,"true")
can probably be written as
exists($node/D:text_enabled[#value eq "true"])

xQuery remove non selected elements

I have an XML document named employees.xml:
<employees>
<row>
<emp_no>10001</emp_no>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
<row>
<emp_no>10002</emp_no>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
</employees>
I want to write a function named my-remove-elements that will remove non-selected attributes. For example, only keep first_name and last_name in the XML document:
<employees>
<row>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
<row>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
</employees>
The definition of my function is:
declare function local:my-remove-elements($input as element(), $remove-names as xs:string*) as element() {
element {node-name($input) }
{$input/#*,
for $child in $input/node()[name(.)=$remove-names]
return
if ($child instance of element())
then local:my-remove-elements($child, $remove-names)
else $child
}
};
This is the way I call it:
let $doc := doc("employees.xml")
return
local:my-remove-elements($doc, ('first_name', 'last_name'))
It throws me "err:XPTY0004 ... is not a sub-type of element()..."
I have changed the code:
let $rows:= doc("employees.xml")//row
return
local:my-remove-elements($rows, ('first_name', 'last_name'))
this time is still: "err:XPTY0004: The actual cardinality for parameter 1 does not match the cardinality in the function's signature ...". Do you know how to fix this and make it work?
Here’s one possible solution:
declare function local:remove-elements(
$node as node(),
$keep-names as xs:string*
) as node()? {
if($node instance of element()) then (
let $name := name($node)
where $name = $keep-names
return element { $name } {
$node/#*,
for $child in $node/node()
return local:remove-elements($child, $keep-names)
}
) else (
$node
)
};
local:remove-elements(
doc("employees.xml")/employees,
('employees', 'row', 'first_name', 'last_name')
)
Here’s another one, which uses XQuery Update:
declare function local:remove-elements(
$node as node(),
$keep-names as xs:string*
) as node()? {
copy $c := $node
modify delete node $c//*[not(name() = $keep-names)]
return $c
};
local:remove-elements(
doc("employees.xml")/employees,
('employees', 'row', 'first_name', 'last_name')
)
The error err:XPTY0004 is because you are invoking the function passing a document-node() as the first parameter, but the first parameter needs to be an element().
Change how you are invoking it, passing in the document element (i.e. $doc/*) instead of the document-node()
Then, you need to adjust the logic in your function. If you were only to select $child nodes that are equal to the $remove-names (which is confusing, consider renaming to something like $keep-leaf-names), then the employees elements would be excluded. Move that filter down, and add logic to process if the element has children or it's name() is equal to any of the $remove-names.
declare function local:my-remove-elements($input as element(), $remove-names as xs:string*) as element() {
element {node-name($input) }
{$input/#*,
for $child in $input/node()
return
if ($child instance of element()) then
if ($child/* or name($child)=$remove-names) then
local:my-remove-elements($child, $remove-names)
else ()
else $child
}
};

getting this error while migrating from xquery 2004 to xquery 1.0 version

//Invalid Xquery content:line 89, column 44: {http://www.w3.org/2005/xqt-errors}XPST0003 [{http://xmlns.oracle.com/xdk/xquery/error}XPST0003c]: Syntax error. "/ns1:produ" is unexpected
Xquery after migrating to 1.0 V
xquery version "1.0";
(: Caring Included Logic Reset/#actioncode :)
(: Caring Fix#358 :)
(: Included CR for Project Caring :)
(: Added a xf:escape-dot function as a part of Jan - Break Fix :)
(: Included changes for Project Caring :)
(: Included mapping for accountGroups - CUG Migration :)
(: Included changes for Project AggregatedAlerts :)
(:: pragma bea:global-element-parameter parameter="$manageServiceOrder" element="ns3:manageServiceOrder" location="../../schemas/pf.bil.BillingProvisioning_3.0.xsd" ::)
(:: pragma bea:global-element-return element="ns0:CreateOrderCustomerRequest" location="../../../../resources_3.0/util/BRMAdapter/interfaces/InfranetUtilityCreate.xsd" ::)
declare namespace ns2 = "http://vodafone.com.au/ebo/vha/ProvisioningAndFulfilmentComponents/V3.0";
declare namespace ns1 = "http://vodafone.com.au/ebo/vha/CommonComponents/V3.0";
declare namespace ns3 = "http://vodafone.com.au/pf/bil/ebm/BillingProvisioning/V3.0";
declare namespace ns0 = "http://www.vodafone.co.au/infranetUtilityCreate/schema";
declare namespace xf = "http://tempuri.org/pf.bil_3.0/resources/billingProvisioning/BRM/EBO.SyncSubscription.billingProvisioning_xForm_BRM.createOrder/";
declare namespace temp = "http://www.w3.org/2001/XMLSchema";
declare xqse function xf:formatDateTimes($dateTime as element(*)*) as xs:dateTime?
{
declare $dateTimeValue :=data($dateTime);
try
{
return value (xs:dateTime(replace(fn-bea:dateTime-to-string-with-format("yyyyMMddHHmmss",$dateTimeValue),"^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$","$1-$2-$3T$4:$5:$6")));
}
catch (* into $err, $msg)
{
return value (xs:dateTime(replace(fn-bea:date-to-string-with-format("yyyyMMdd",$dateTimeValue),"^(\d{4})(\d{2})(\d{2})$","$1-$2-$3T00:00:00")));
};
};
declare xqse function xf:isValidChargeShareAtribute( $chargeShareLines as element(*)) as xs:boolean?
{
try
{
iterate $attribute over $chargeShareLines/ns1:productService/ns1:attributes/ns1:attribute[fn:upper-case(ns1:name/text()) eq "CSG REFERENCE" or fn:upper-case(ns1:name/text()) eq "CSG SPONSORSHIP"]
{
if (
( ($attribute/ns1:value ne "" and $chargeShareLines/#actionCode eq "New")
or
$chargeShareLines/#actionCode eq "Modified"
or
($attribute/ns1:value ne "" and $chargeShareLines/#actionCode eq "Deleted")
)
) then return value (fn:boolean("true")) else return value( (fn:boolean("false")));
};
}
catch (* into $err, $msg)
{ return value (fn:boolean("false"));
};
};
declare function xf:lookupDVMWrapper($dvmName as xs:string, $sourceColumn as xs:string,$sourceValue as xs:string, $targetColumn as xs:string, $needAnException as xs:boolean)as element(*)
{
let $dvmres :=(fn-bea:lookupDVM($dvmName,$sourceColumn,$sourceValue,$targetColumn,'NO DATA FOUND'))
return
if ((($dvmres='') or ($dvmres='NO DATA FOUND')) and $needAnException)
then
<dvmresp>
<dvmexception>
{
concat($sourceValue,' for ',$sourceColumn,' in ',$dvmName,'.dvm', ' not found' )
}
</dvmexception>
</dvmresp>
else if((($dvmres='') or ($dvmres='NO DATA FOUND')) and not($needAnException))
then
<dvmresp></dvmresp>
else
<dvmresp>
{
$dvmres
}
</dvmresp>
};
declare function xf:formatDate( $dateTime as element(*)*) as xs:dateTime?
{
let $dateTimeValue :=data($dateTime)
return xs:dateTime(replace(fn-bea:date-to-string-with-format("yyyyMMdd",$dateTimeValue),"^(\d{4})(\d{2})(\d{2})$","$1-$2-$3T00:00:00"))
};
declare function xf:calculateVFPeriodEndDate($manageServiceOrder as element (*),$serviceLines as element(*))
as xs:dateTime? {
if ($serviceLines/#actionCode eq "Deleted" or $serviceLines/#actionCode eq "Suspended")
then xf:formatDateTimes(element{"billEffectiveDate"}{ $manageServiceOrder/ns3:serviceOrder/ns2:billingDetails/ns2:billEffectiveDate/text()})
else if (fn:string-length(data($serviceLines/ns1:productService/ns1:periodEndDate)) >0 and data($serviceLines/ns1:productService/ns1:periodEndDate) ne "")
then xf:formatDate( element { "periodEndDate" } {if(fn:contains(data($serviceLines/ns1:productService/ns1:periodEndDate),"T"))then
fn:substring-before(data($serviceLines/ns1:productService/ns1:periodEndDate),"T")
else
$serviceLines/ns1:productService/ns1:periodEndDate/text()})
else if (
for $expiryDate in $serviceLines/ns1:productService/ns1:attributes/ns1:attribute[ns1:name/text() eq "Expiry Date"]/ns1:value/text()
return
if (fn:string-length($expiryDate) >0) then xf:formatDateTimes( element { "expiryDate" } {$expiryDate}) else() )
then
(
for $expiryDate in $serviceLines/ns1:productService/ns1:attributes/ns1:attribute[fn:upper-case(ns1:name/text()) eq "EXPIRY DATE"]/ns1:value/text()
return xf:formatDateTimes( element { "expiryDate" } {$expiryDate})
)
else ()
};
There are several things here that aren't allowed in XQuery 1.0. The ones that stand out are:
declare $dateTimeValue :=data($dateTime);
You can't declare local variables like that. You need let $v := expr return XXX.
return value (xs:dateTime(...);
The keyword return value is wrong and should be removed.
iterate $attribute over $chargeShareLines
I've no idea what the iterate expression does, but it's not XQuery. Looks like some variant of a for expression, but I don't know how it differs.
try {
requires XQuery 3.0/3.1, and the syntax is slightly different from yours.

expression evaluation does not work

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!

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

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 }

Resources