XQuery/XPath:使用count()和max()函数返回具有最高计数的元素

8

我有一个包含作者和编辑的XML文件。

<?xml version="1.0" encoding="UTF-8"?>
<?oxygen RNGSchema="file:textbook.rnc" type="compact"?>
<books xmlns="books">

    <book ISBN="i0321165810" publishername="OReilly">
        <title>XPath</title>
        <author>
            <name>
                <fname>Priscilla</fname>
                <lname>Walmsley</lname>
            </name>
        </author>
        <year>2007</year>
        <field>Databases</field>
    </book>

    <book ISBN="i0321165812" publishername="OReilly">
        <title>XQuery</title>
        <author>
           <name>
               <fname>Priscilla</fname>
               <lname>Walmsley</lname>
            </name>
        </author>
        <editor>
            <name>
                <fname>Lisa</fname>
                <lname>Williams</lname>
            </name>
        </editor>
        <year>2003</year>
        <field>Databases</field>
    </book>

    <publisher publishername="OReilly">
        <web-site>www.oreilly.com</web-site>
        <address>
            <street_address>hill park</street_address>
            <zip>90210</zip>
            <state>california</state>
        </address>
        <phone>400400400</phone>
        <e-mail>oreilly@oreilly.com</e-mail>
        <contact>
            <field>Databases</field>
            <name>
                <fname>Anna</fname>
                <lname>Smith</lname>
            </name>
        </contact>
    </publisher>
</books>

我正在寻找一种方法来返回被列为作者和/或编辑最多次的人。解决方案应该与XQuery 1.0(XPath 2.0)兼容。
我考虑使用FLWOR查询遍历所有的作者和编辑,然后计算唯一作者/编辑的数量,然后返回匹配最高计数的作者/编辑。但我还没有找到正确的解决方案。
是否有人有关于如何编写这样的FLWOR查询的建议? 是否可以使用XPath以更简单的方式完成?
4个回答

16

这可能会有所帮助:

declare default element namespace 'books';
(for $name in distinct-values($doc/books/*/*/name)
 let $entries := $doc/books/*[data(*/name) = $name]
 order by count($entries) descending
 return $entries/*/name)[1]

谢谢解决方案,Christian :) 是否有一种方法可以返回多个作者/编辑(如果适用)?例如,如果有两个共享相同(最大)计数的作者/编辑,是否可以返回它们? - Jea
3
在基督徒和我的解决方案中,只需删除结尾的[1],就可以获得所有具有最大值的节点。 - Dimitre Novatchev

7

这是一个纯XPath 2.0表达式,诚然不适合胆小的人:

(for $m in max(for $n in distinct-values(/*/b:book/(b:author | b:editor)
                                        /b:name/concat(b:fname, '|', b:lname)),
               $cnt in count(/*/b:book/(b:author | b:editor)
                             /b:name[$n eq concat(b:fname, '|', b:lname) ])
               return $cnt
               ),
     $name in /*/b:book/(b:author | b:editor)/b:name,
     $fullName in $name/concat(b:fname, '|',  b:lname),
     $count in count( /*/b:book/(b:author | b:editor)
                   /b:name[$fullName eq concat(b:fname, '|',  b:lname)])
  return
     if($count eq $m)
       then $name
       else ()
   )[1]

其中前缀"b:"与命名空间"books"相关联。

XSLT 2.0基于的验证:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:b="books">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/">
   <xsl:sequence select=
   "(for $m in max(for $n in distinct-values(/*/b:book/(b:author | b:editor)
                                            /b:name/concat(b:fname, '|', b:lname)),
                   $cnt in count(/*/b:book/(b:author | b:editor)
                                 /b:name[$n eq concat(b:fname, '|', b:lname) ])
                   return $cnt
                   ),
         $name in /*/b:book/(b:author | b:editor)/b:name,
         $fullName in $name/concat(b:fname, '|',  b:lname),
         $count in count( /*/b:book/(b:author | b:editor)
                       /b:name[$fullName eq concat(b:fname, '|',  b:lname)])
      return
         if($count eq $m)
           then $name
           else ()
       )[1]
   "/>
 </xsl:template>
</xsl:stylesheet>

当应用此转换到提供的XML文档时:
<books xmlns="books">
    <book ISBN="i0321165810" publishername="OReilly">
        <title>XPath</title>
        <author>
            <name>
                <fname>Priscilla</fname>
                <lname>Walmsley</lname>
            </name>
        </author>
        <year>2007</year>
        <field>Databases</field>
    </book>
    <book ISBN="i0321165812" publishername="OReilly">
        <title>XQuery</title>
        <author>
            <name>
                <fname>Priscilla</fname>
                <lname>Walmsley</lname>
            </name>
        </author>
        <editor>
            <name>
                <fname>Lisa</fname>
                <lname>Williams</lname>
            </name>
        </editor>
        <year>2003</year>
        <field>Databases</field>
    </book>
    <publisher publishername="OReilly">
        <web-site>www.oreilly.com</web-site>
        <address>
            <street_address>hill park</street_address>
            <zip>90210</zip>
            <state>california</state>
        </address>
        <phone>400400400</phone>
        <e-mail>oreilly@oreilly.com</e-mail>
        <contact>
            <field>Databases</field>
            <name>
                <fname>Anna</fname>
                <lname>Smith</lname>
            </name>
        </contact>
    </publisher>
</books>

所选的正确的name元素已被输出。
<name xmlns="books">
   <fname>Priscilla</fname>
   <lname>Walmsley</lname>
</name>

4
我一直觉得XPath中有一个遗漏:max()和min()函数返回最高/最低的值,而你通常想要的是集合中具有某个表达式的最高/最低值的对象。一种解决方案是对这个值对对象进行排序并从列表中取第一个/最后一个,但这似乎不太优雅。计算最小或最大值,然后选择其值匹配的项目同样不理想。在Saxon中,一直存在一对高阶扩展函数saxon:highest()和saxon:lowest(),它们接受一个序列和一个函数,并返回具有函数结果的最低或最高值的项目。好消息是,在XPath 3.0中,你可以自己编写这些函数(实际上,在规范中给出了这些示例用户编写的函数)。

提供这些示例的链接会很不错! - grtjn

2

您正在正确的轨道上。最简单的方法是将名称转换为字符串(例如用空格分隔),然后使用这些字符串:(请注意,以下代码未经测试)

let $names := (//editor | //author)/concat(fname, ' ', lname)
let $distinct-names := distinct-values($names)
let $name-count := for $name in $distinct-names return count($names[. = $name])
for $name at $pos in $distinct-names
where $name-count[$pos] = max($name-count)
return $name

或者,另一种方法:
(
  let $people := (//editor | //author)
  for $person in $people
  order by count($people[fname = $person/fname and
                         lname = $person/lname])
  return $person
)[last()]

@_Oliver:抱歉,即使在XQuery 3.0 / XPath 3.0中,这也是错误的。提示:看看:$names/count(index-of($names,.)$names恰好是原子值序列,但/运算符要求其左操作数为节点(集)。 - Dimitre Novatchev
@_Oliver:你的第一种方法也没有产生任何结果。在oXygen下使用Saxon 9.3.05进行了检查。 - Dimitre Novatchev
@Dimitre:关于'/'的观点很好。我已经删除了XPath示例。无论如何,那都是一个可怕的解决方案。 - Oliver Hallam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接