在 XPath 3.1 的 array:filter 中,我可以指定一个过滤函数,它接受多个参数(即一个值来*反对*测试项目)吗?

In XPath 3.1's array:filter, can I specify a filtering function that takes more than one argument (i.e. a value to test the item *against*)?

我想根据其中一个键的值过滤一组地图。问题似乎是 array:filter() 的第二个参数是一个仅接受单个项目参数的函数:根据 XPath and XQuery Operators 3.1 specs.

array:filter($array as array(*), $function as function(item()*) as xs:boolean) as array(*)

但是当我执行 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
};

从表面上看,这看起来像是一个 eXist-db 错误。变量 $wid 是匿名函数闭包的一部分,它的值应该是可访问的;您的代码看起来不错——尽管没有完整的重现(源文档和预期结果)我还没有在其他地方测试过它。

将第二个变量传递给过滤函数

要动态过滤项目列表,通常需要传递第二个动态参数。

读取作用域变量的 lambda 或匿名函数的常用方法是:

let $upper-bound := 4 (: this might be read from user input :)
return filter(1 to 9, function ($item) {
  $item < $upper-bound
})

通过一些元编程并利用参数占位符 ? 这也可以重写为

declare function local:filter ($item, $upper-bound) {
  $item < $upper-bound
};

let $upper-bound := 4
return filter(1 to 9, local:filter(?, $upper-bound))

local:filter(?, $upper-bound) 将 return 一个元数为 1 的函数,这正是 fn:filterarray:filter 所期望的。 主要好处是它允许重用过滤器函数(这里 local:filter)。 现在也可以在导入的模块中定义此函数。