XQuery/搜索、分页和排序
外观
< XQuery
这些示例使用一个简单的 XML 文件,其中包含有关全球地震的数据。该数据来自Swivel。示例使用“创建自定义视图”中介绍的通用表格查看器进行输出。
此示例在地震位置中搜索字符串。
declare option exist:serialize "method=xhtml media-type=text/html indent=yes"; import module namespace wikiutil = "http://www.cems.uwe.ac.uk/xmlwiki" at "util.xqm"; let $search := request:get-parameter("search","") let $matches := //Earthquake[contains(Location,$search)] return <html> <head> <title>Search Earthquakes for {$search}</title> </head> <body> <h1>Search Earthquakes</h1> <form>Search for <input type="text" name="search" value="{$search}"/> </form> { wikiutil:sequence-to-table($matches) } </body> </html>
此脚本实现搜索结果的分页。这里针对每次调用重复完整搜索,交互状态保存在隐藏的输入中。
declare option exist:serialize "method=xhtml media-type=text/html indent=yes"; import module namespace wikiutil = "http://www.cems.uwe.ac.uk/xmlwiki" at "util.xqm"; let $search := request:get-parameter("search","") let $start:= xs:integer(request:get-parameter("start", "1")) let $records := xs:integer(request:get-parameter("records", "5")) let $action := request:get-parameter("action","search") let $allMatches := //Earthquake[contains(Location,$search)] (: compute the limits for this page :) let $max := count($result) let $start := if ($action = "Previous") then max(($start - $records, 1)) else if ($action="Next") then if ($max <$start +$records) then $start else $start +$records else if ($action="Search") then 1 else $start let $end := min (($start + $records - 1,$max)) (: restrict the full set of matches to this subsequence :) let $matches := subsequence($allMatches,$start,$records) return <html> <head> <title>Search Earthquakes </title> </head> <body> <h1>Search Earthquakes</h1> <form > Search Location for <input type="text" name="search" value="{$search}"/> <input type="submit" name="action" value="Search"/> <br/> <input type="hidden" name="start" value="{$start}"/> <input type="submit" name="action" value="Previous"/> <input type="submit" name="action" value="Next"/> <p>Displaying {$start} to {$end} out of {$max} records found.</p> {wikiutil:sequence-to-table($matches) } <p>Records per Page <input type="text" name="records" value="{$records}"/></p> </form> </body> </html>
为了对列进行排序,我们在每列添加一个提交按钮。这需要扩展通用表格查看器以按选定列对节点进行排序。
declare function wikiutil:sequence-to-table($seq,$sort) { <table border="1"> <tr> {for $node in $seq[1]/* return <th><input type="submit" name="Sort" value="{name($node)}"/></th> } </tr> {for $row in $seq let $sortBy := data($row/*[name(.) = $sort]) order by $sortBy return <tr> {for $node in $seq[1]/* let $data := data($row/*[name(.)=name($node)]) return <td>{$data}</td> } </tr> } </table> };
declare option exist:serialize "method=xhtml media-type=text/html indent=yes"; import module namespace wikiutil = "http://www.cems.uwe.ac.uk/xmlwiki" at "util.xqm"; let $search := request:get-parameter("search","") let $sort := request:get-parameter("Sort","Date") let $matches := //Earthquake[contains(Location,$search)] return <html> <head> <title>Search Earthquakes}</title> </head> <body> <h1>Search Earthquakes</h1> <form>Search Location for <input type="text" name="search" value="{$search}"/> {wikiutil:sequence-to-table($matches,$sort)} </form> </body> </html>
请注意,排序按字符串值进行:按震级排序仅偶然成功,而按死亡人数排序则失败。
改进之处是允许连续点击列标题以反转排序方向。这需要在交互状态中添加两个更多项,即当前排序顺序和当前方向,以及对表格生成器的更改。理想情况下,我们希望能够说出类似以下的内容
for $row .. let $sortBy := .. let $direction := if (..) then "ascending" else "descending" order by $sortBy $direction
但这并非有效的 FLWOR 表达式。相反,我们需要两个 FLWOR 表达式,一个用于每个方向。
declare function wikiutil:sequence-to-table($seq,$sort,$direction) { <table border="1"> <tr> {for $node in $seq[1]/* return <th><input type="submit" name="Sort" value="{name($node)}"/></th> } </tr> { if ($direction = 1) then for $row in $seq let $sortBy := data($row/*[name(.) = $sort]) order by $sortBy ascending return <tr> {for $node in $seq[1]/* let $data := data($row/*[name(.)=name($node)]) return <td>{$data}</td> } </tr> else for $row in $seq let $sortBy := data($row/*[name(.) = $sort]) order by $sortBy descending return <tr> {for $node in $seq[1]/* let $data := data($row/*[name(.)=name($node)]) return <td>{$data}</td> } </tr> } </table> };
然后脚本变为
import module namespace wikiutil = "http://www.cems.uwe.ac.uk/xmlwiki" at "util.xqm"; declare option exist:serialize "method=xhtml media-type=text/html indent=yes"; let $search := request:get-parameter("search","") let $sort := request:get-parameter("Sort","Date") let $lastSort := request:get-parameter("LastSort","") let $lastDirection := number(request:get-parameter("LastDirection","1")) let $direction := if ($lastSort = $sort) then - $lastDirection else 1 let $matches := //Earthquake[contains(Location,$search)] return <html> <head> <title>Search Earthquakes</title> </head> <body> <h1>Search Earthquakes</h1> <form>Search Location for <input type="text" name="search" value="{$search}"/> <input type="hidden" name="LastSort" value="{$sort}"/> <input type="hidden" name="LastDirection" value="{$direction}"/> { wikiutil:sequence-to-table($matches,$sort, $direction) } </form> </body> </html>
通过提供表格模式,可以获得对输出的更大控制。此模式可以指定列的顺序和列标题,以及潜在的转换指令。
我们可以将表格模式提供为一系列 Column 定义
<Schema> <Column name="Location" heading="Earthquake location"/> <Column name="Magnitude" heading="Magnitude (Richter Scale)"/> <Column name="Date" /> </Schema>
基于模式的函数如下所示
declare function wikiutil:sequence-to-table-with-schema($seq,$schema) { <table border="1"> <tr> {for $column in $schema/Column return <th>{string( ($column/@heading,$column/@name)[1])}</th> } </tr> {for $row in $seq return <tr> {for $column in $schema/Column let $data := data($row/*[name(.)=$column/@name]) return <td>{$data}</td> } </tr> } </table> };
请注意,使用了 XQuery 习惯用法来计算列标题,如果提供了标题,则使用该标题,否则使用节点名称
($column/@heading,$column/@name)[1]
这会计算序列中的第一个非空项,这是一种更简洁且更通用的替代方法,而不是
if (exists($column/@heading)) then $column/@heading else $column/@name