在 XPath 3.1 的 array:filter 中,我可以指定一个采用多个参数(即用于测试项目*针对*的值)的过滤函数吗?

发布于 2025-01-10 09:48:39 字数 3302 浏览 0 评论 0原文

我想根据其中一个键的值过滤映射数组。问题似乎是 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

小傻瓜 2025-01-17 09:48:39

从表面上看,这看起来像是一个 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.

美人骨 2025-01-17 09:48:39

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

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

读取作用域变量的 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) 将返回一个参数为 1 的函数,这正是 fn:filterarray: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:

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

With a little meta programming and leveraging the argument placeholder ? this can also be rewritten as

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) will return a function with an arity of 1 which is exactly what fn:filter and array: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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文