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