在 XPath 3.1 的 array:filter 中,我可以指定一个采用多个参数(即用于测试项目*针对*的值)的过滤函数吗?
我想根据其中一个键的值过滤映射数组。问题似乎是 array:filter() 的第二个参数是一个仅接受单个项目参数的函数: array:filter($array as array(*), $function as function(item()*) as xs:boolean) as array(*) 根据 XPath 和 XQuery 运算符 3.1 规范。
但是当我执行 array:filter($routingTable, function ($i) {$i?input ne $wid})
时,在调用之前的某个地方定义了 $wid
,该变量没有被传递到函数中,并且比较错过了关键条目。 (在调用 array:filter
之前使用调试输出检查 $wid
的值,确认它具有正确的值。检查 $i?input
匿名函数内部也确认了这个值。但是检查匿名函数内部的 $wid
会使其显示为空。)
所以我想也许我需要传递作为第二个参数与过滤函数进行比较的值,但是当我执行 array:filter($routingTable, function ($i, $wid) {etc...
,我得到一个 java.lang.ArrayIndexOutOfBoundsException
错误。我假设这个是由于该函数的参数过多。我应该如何解决这个问题?
对于它的价值,我的 XQuery 处理器是 eXist-db (6.1.0),这里是更完整的代码(有问题的调用(我假设))是最后一个函数的第二行):
(: for a sequence of nodes, I build maps and put them into an array. that is my routing table that is then posted :)
declare function my:createRoutes($wid as xs:string) {
let $index := doc($config:index-root || "/" || $wid || ".xml")/my:index
let $routingTable := array{fn:for-each($index//my:node, function($k) {my:buildRoutingInfoNode($wid, $k)} )}
return my:postRoutingTable($routingTable)
};
(: helper function to create a map from a node :)
declare function my:buildRoutingInfoNode($wid as xs:string, $item as element(my:node)) {
map { "input" : concat($wid, ":", $item/@citeID/string()), "outputs" : array { ( $item/@crumb/string(), 'yes' ) } }
};
(: here the routing table is posted. However, if the entries are already present in the "live" table, I need to clean them from there first :)
declare function my:postRoutingTable($routes as array(*)) as xs:integer {
if (array:size($routes) = 0) then
0
else
let $testmap := $routes?1 (: okay, that's a bit cheap: I just check the first of the new entries. :)
let $src := $testmap?input
let $dest := $testmap?outputs
return if (not(my:isInRoutingTable($src, $dest))) then
... post via http request ...
else (: At least one key is already present, need to clean routing table for $wid first :)
let $wid := substring-before($src, ":")
let $cleanStatus := my:cleanRoutingTable($wid)
return if ($cleanStatus ge 0) then
my:postRoutingTable($routes) (: retry ... :)
else
-1 (: cleaning failed ... :)
};
(: remove all entries about the $wid (so that I can add them again) :)
declare function my:cleanRoutingTable($wid as xs:string) as xs:integer {
let $routingTable := my:getRoutingTable() (: get "live" table :)
let $cleanedRT := array:filter($routingTable, function ($i) {
substring($i?input, 1, 5) ne $wid
}) (: remove all entries concerning the to-be-posted $wid :)
let $deleteStatus := my:deleteRoutingTable() (: drop the complete live table :)
return if (array:size($cleanedRT) > 0) then (: if after removing entries, anything is left of the original "live" table, :)
my:postRoutingTable($cleanedRT) (: then re-post this "cleaned" table :)
else -1
};
I want to filter an array of maps based on a value for one of the keys. The problems seems to be that the second parameter of array:filter()
is a function that accepts only a single item parameter: array:filter($array as array(*), $function as function(item()*) as xs:boolean) as array(*)
according to the XPath and XQuery Operators 3.1 specs.
But when I do array:filter($routingTable, function ($i) {$i?input ne $wid})
, with $wid
being defined somewhere before this call, this variable is not being passed into the function and the comparison misses the crucial entries. (Checking the value of $wid
with debugging output right before the call to array:filter
confirms it has the correct value. Checking $i?input
inside the anonymous functions confirms this value, too. But checking $wid
inside the anonymous function makes it appear it is empty.)
So I thought maybe I need to pass the value to compare against to the filtering function as a second parameter, but when I do array:filter($routingTable, function ($i, $wid) {etc...
, I get a java.lang.ArrayIndexOutOfBoundsException
error. I assume this is due to the excess argument of the function. How should I go about this?
For what it's worth, my XQuery processor is eXist-db (6.1.0) and here is more complete code (the call that's at issue (I assume) is the second line of the last function):
(: for a sequence of nodes, I build maps and put them into an array. that is my routing table that is then posted :)
declare function my:createRoutes($wid as xs:string) {
let $index := doc($config:index-root || "/" || $wid || ".xml")/my:index
let $routingTable := array{fn:for-each($index//my:node, function($k) {my:buildRoutingInfoNode($wid, $k)} )}
return my:postRoutingTable($routingTable)
};
(: helper function to create a map from a node :)
declare function my:buildRoutingInfoNode($wid as xs:string, $item as element(my:node)) {
map { "input" : concat($wid, ":", $item/@citeID/string()), "outputs" : array { ( $item/@crumb/string(), 'yes' ) } }
};
(: here the routing table is posted. However, if the entries are already present in the "live" table, I need to clean them from there first :)
declare function my:postRoutingTable($routes as array(*)) as xs:integer {
if (array:size($routes) = 0) then
0
else
let $testmap := $routes?1 (: okay, that's a bit cheap: I just check the first of the new entries. :)
let $src := $testmap?input
let $dest := $testmap?outputs
return if (not(my:isInRoutingTable($src, $dest))) then
... post via http request ...
else (: At least one key is already present, need to clean routing table for $wid first :)
let $wid := substring-before($src, ":")
let $cleanStatus := my:cleanRoutingTable($wid)
return if ($cleanStatus ge 0) then
my:postRoutingTable($routes) (: retry ... :)
else
-1 (: cleaning failed ... :)
};
(: remove all entries about the $wid (so that I can add them again) :)
declare function my:cleanRoutingTable($wid as xs:string) as xs:integer {
let $routingTable := my:getRoutingTable() (: get "live" table :)
let $cleanedRT := array:filter($routingTable, function ($i) {
substring($i?input, 1, 5) ne $wid
}) (: remove all entries concerning the to-be-posted $wid :)
let $deleteStatus := my:deleteRoutingTable() (: drop the complete live table :)
return if (array:size($cleanedRT) > 0) then (: if after removing entries, anything is left of the original "live" table, :)
my:postRoutingTable($cleanedRT) (: then re-post this "cleaned" table :)
else -1
};
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
从表面上看,这看起来像是一个 eXist-db 错误。变量
$wid
是匿名函数闭包的一部分,它的值应该是可访问的;你的代码看起来不错——尽管没有完整的重现(源文档和预期结果),我还没有在其他地方测试过它。On the face of it this looks like an eXist-db bug. The variable
$wid
is part of the closure of the anonymous function and its value should be accessible; your code looks fine -- though without a complete repro (source document and expected results) I haven't tested it elsewhere.将第二个变量传递给过滤器函数
要动态过滤项目列表,通常需要传递第二个动态参数。
读取作用域变量的 lambda 或匿名函数的常用方法是:
通过一点元编程并利用参数占位符
?
这也可以重写为local:filter(?, $upper-bound)
将返回一个参数为 1 的函数,这正是fn:filter
和array:filter
所期望的。主要好处是它允许重用过滤器函数(这里是
local:filter
)。现在也可以在导入的模块中定义此函数。
Passing a second variable to a filter function
To dynamically filter a list of items, one often needs to pass a second, dynamic parameter.
The usual way of a lambda or anonymous function that reads a scoped variable would be:
With a little meta programming and leveraging the argument placeholder
?
this can also be rewritten aslocal:filter(?, $upper-bound)
will return a function with an arity of 1 which is exactly whatfn:filter
andarray:filter
expect.Main benefit is that it allows to reuse filter functions (here
local:filter
).This function can now also be defined in an imported module.