xQuery remove non selected elements - xquery

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
}
};

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.

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

What “Attribute node cannot follow non-attribute node in element content” tells me

one-attr.xml
<requestConfirmation xmlns="http://example/confirmation">
<trade>
<amount>
<currency id="settlementCurrency">USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</requestConfirmation>
two-attr.xml
<requestConfirmation xmlns="http://example/confirmation">
<trade>
<cal>
<c>PRECEDING</c>
<bcs id="businessCenters">
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
</cal>
<amount>
<currency id="settlementCurrency" currencyScheme="http://example/iso4">USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</requestConfirmation>
I use XQuery to transform the id attribute into element. There are only two documents like two-attr.xml out of 70K documents.
Apparently, the currency element already has value USD. I got below error in the ML QConsole when transforming two-attr.xml. I got very similar error in Oxygen.
XDMP-ATTRSEQ: (err:XQTY0024) $node/#*[fn:local-name(.) = $attr] -- Attribute node cannot follow non-attribute node in element content
My XQuery module:
declare namespace hof = "http://fc.fasset/function";
declare function hof:remove-attr-except
( $node as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $attr in $node/#*
return
if (local-name($attr) = $keepAttr)
then (element {fn:QName ($newNs, name($attr))} {data($attr)})
else
$node/#*[name() = $keepAttr], hof:transform-ns-root-flatten($node/node(), $newNs, $keepAttr)
};
declare function hof:transform-ns-root-flatten
( $nodes as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $node in $nodes
return
typeswitch($node)
case $node as element()
return (element { fn:QName ($newNs, local-name($node)) }
{ hof:remove-attr-except($node, $newNs, $keepAttr) }
)
case $node as document-node()
return hof:transform-ns-root-flatten($node/node(), $newNs, fn:normalize-space($keepAttr))
default return $node
};
(: let $inXML := doc("/fasset/bug/two-attr.xml") :)
let $inXML :=
let $inXML :=
<requestConfirmation xmlns="http://example/confirmation">
<trade>
<cal>
<c>PRECEDING</c>
<bcs id="businessCenters">
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
</cal>
<amount>
<currency id="settlementCurrency" currencyScheme="http://example/iso4">USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</requestConfirmation>
let $input := $inXML/*[name() = name($inXML/*)]/*
let $ns := "schema://fc.fasset/execution"
let $root := "executionReport"
let $keep := "id"
return
element { fn:QName ($ns, $root) }
{ hof:transform-ns-root-flatten($input, $ns, $keep) }
Then I switch XSLT to transform two-attr.xml. Surprisingly, the XSLT transform is a success.
<xsl:param name="ns" as="xs:string">schema://fc.fasset/product</xsl:param>
<xsl:param name="attr" static="yes" as="xs:string*" select="'href', 'id'"/>
=================================
<xsl:template match="#*">
<xsl:choose>
<xsl:when test="local-name() = $attr">
<xsl:element name="{local-name()}" namespace="{$ns}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
The collective successful underlying transform is against the one-attr.xml model. Java|ML API, Oxygen, XSLT returns the same result:
<amount>
<currency>
<id>settlementCurrency</id>USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
Now here is the rub: it doesn’t look like a valid XML. For although I can get the currency text value
doc("/product/eqd/a7c1db2d.xml")//prod:trade//prod:amount/prod:currency/text()
, I expect below result to facilitate the search engine:
<executionReport xmlns="schema://fc.fasset/execution">
<trade>
<cal>
<c>PRECEDING</c>
<bcs>
<id>businessCenters</id>
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
</cal>
<amount>
<currency>USD</currency>
<id>settlementCurrency</id>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</executionReport>
Among the following solutions, the latest result is as below:
<executionReport xmlns="schema://fc.fasset/execution">
<cal>
<c>PRECEDING</c>
<bcs>
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
<!-- Line9: id is out of <bcs> element and its context is completed lost! -->
<id>businessCenters</id>
</cal>
<amount>
<currency>USD</currency>
<!-- Line14: id is in the correct position! -->
<id>settlementCurrency</id>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</executionReport>
How can I get my XQuery and XSLT module work?
You can't create attributes after you have started creating child nodes. So, if you are transforming the #id into <id> then you have to do that AFTER you have copied the other attributes.
The shortest and easiest way to avoid the problem is to sort the attributes, ensuring that the ones that will be copied forward are processed first, then the ones that will be converted to elements.
You could achieve that by sorting the sequence of items returned from the hof:remove-attr-except() function, ensuring that the sequence has attributes and then the elements:
element { fn:QName ($newNs, local-name($node)) }
{ for $item in (hof:remove-attr-except($node, $newNs, $keepAttr))
order by $item instance of attribute() descending
return $item }
You could also just have two separate FLWOR with a where clause that processes the $keepAttr and then those that will be converted into elements:
declare function hof:remove-attr-except
( $node as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $attr in $node/#*
where not(local-name($attr) = $keepAttr)
return
$node/#*[name() = $keepAttr], hof:transform-ns-root-flatten($node/node(), $newNs, $keepAttr)
,
for $attr in $node/#*
where local-name($attr) = $keepAttr
return
element {fn:QName ($newNs, name($attr))} {data($attr)}
};
But if you want those new elements to be outside of the original element, and you don't want to retain the attributes then I would change the processing of the element in your typeswitch, so that you call the function that converts those attributes into elements outside of the element constructor:
declare namespace hof = "http://fc.fasset/function";
declare function hof:attr-to-element
( $node as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $attr in $node/#*
where local-name($attr) = $keepAttr
return
element {fn:QName ($newNs, name($attr))} {data($attr)}
};
declare function hof:transform-ns-root-flatten
( $nodes as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $node in $nodes
return
typeswitch($node)
case $node as element()
return (element { fn:QName ($newNs, local-name($node)) }
{ hof:transform-ns-root-flatten($node/node(), $newNs, $keepAttr) }
,
hof:attr-to-element($node, $newNs, $keepAttr)
)
case $node as document-node()
return hof:transform-ns-root-flatten($node/node(), $newNs, fn:normalize-space($keepAttr))
default return $node
};
The code above produces the following output from the provided input XML:
<executionReport xmlns="schema://fc.fasset/execution">
<amount>
<currency>USD</currency>
<id>settlementCurrency</id>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</executionReport>

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.

Recursive function returning boolean, with a for loop inside

My data is a binary tree, and will check through every child, returning true if it finds the data i want, if not, it keeps looking for it.
In some way i want to return the variable #exists or something.. Well anyone might have a solution for my problem. I was thinking something like this but i couldn't get it to work! (code-snippet)
declare function local:test($id as xs:integer, $topic as xs:integer) as xs:boolean {
let $exists := fn:false()
for $x in ...
return
if .. then
set exists to fn:true()
else
set exists to exists OR local:test($x,$topic)
return #exists in some way
};
This is a case for an XQuery quantified expression. Using that, your function translates to
declare function local:test($id as xs:integer, $topic as xs:integer) as xs:boolean
{
some $x in ...
satisfies
if (..) then
fn:true()
else
local:test($x,$topic)
};
As it was already mentioned XQuery is a functional language. You can't just set variable and return it. Your query can be though rewritten like:
declare function local:test($id as xs:integer, $topic as xs:integer) as xs:boolean {
exists(for $x in ...
where (: here is condition expression on $x :)
return $x)
};
Function exists(Expr) returns true if the value of Expr is not the empty sequence; otherwise, the function returns false.
In this case exists returns true if there is $x which meets specified condition.
You cannot change the values of variables in xquery.
Is your whole function not just this:
declare function local:test($topic as xs:integer) as xs:boolean {
... OR local:test($topic/...)
};

Resources