内容管理系统的 XQuery/站点地图
您想使用 eXist 管理您的网站,并将每个集合用作 Web 内容文件夹。您需要一种方法来自动创建网站地图,以便在 Web 根文件夹中添加新集合时,网站导航菜单会自动更新。
我们将使用 eXist 的 get-child-collections() 函数来获取根集合的所有子集合。我们创建一个递归函数来遍历集合树。
来自 eXist 函数库,以下是 get-child-collections 函数的描述。
xmldb:get-child-collections($a as xs:string) xs:string* Returns a sequence of strings containing all the child collections of the collection specified in $a. The collection parameter can either be a simple collection path or an XMLDB URI.
如果我们有一个名为 /db/webroot 的集合,我们可以将此字符串作为参数传递给此函数,所有子集合将作为字符串序列返回。
然后我们可以创建一个递归函数,该函数作用于这些子集合中的每一个。
以下是用 get-child-collections() 函数的简单示例。您只需传递一个参数,该参数是集合的路径。它将返回该集合中所有子集合的序列。
xquery version "1.0";
let $children := xmldb:get-child-collections('/db/webroot')
return
<results>
<children>{$children}</children>
</results>
declare function local:sitemap($collection as xs:string) as node()* {
if (empty(xmldb:get-child-collections($collection)))
then ()
else
<ol>{
for $child in xmldb:get-child-collections($collection)
return
<li>
<a href="{concat('/exist/rest', $collection, '/', $child)}">{$child}</a>
{local:sitemap(concat($collection, '/', $child))}
</li>
}</ol>
};
这个递归函数接受一个字符串作为输入参数,并返回一个复杂的节点。结果是 HTML 有序列表结构。它首先进行测试以查看集合中是否存在任何子元素。如果不存在,它只返回。如果有新的子元素,则它创建一个新的有序列表,并遍历该集合中的所有子元素,为每个子元素创建一个新的列表项,然后调用自身。请注意,这本可以写成,使条件运算符仅在集合中存在子元素时才调用自身。
这是一个关于 尾递归 的示例。这种模式在 XQuery 函数中经常出现。
我们现在可以在 XHTML 页面模板中调用此程序来创建一个网页
xquery version "1.0";
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";
declare function local:sitemap($collection as xs:string) as node()* {
if (empty(xmldb:get-child-collections($collection)))
then ()
else
<ol>{
for $child in xmldb:get-child-collections($collection)
return
<li>
<a href="{concat('/exist/rest', $collection, '/', $child)}">{$child}</a>
{local:sitemap(concat($collection, '/', $child))}
</li>
}</ol>
};
<html>
<head>
<title>Sitemap</title>
</head>
<body>
<h1>Sitemap for collection /db/webroot</h1>
{local:sitemap('/db/webroot')}
</body>
</html>
有时导航栏的标题将不同于集合的名称。按照惯例,集合名称通常只是没有空格或大写字母的简短小写字母。导航栏通常具有包含空格和大写字母的标签。
以下示例使用查找表从 XML 文件中查找标题。
xquery version "1.0";
declare function local:sitemap($collection as xs:string) as node()* {
if (empty(xmldb:get-child-collections($collection)))
then ()
else
<ol>{
for $child in xmldb:get-child-collections($collection)
let $db-path := concat($collection, '/', $child)
let $path := concat('/exist/rest', $collection, '/', $child)
let $lookup :=
doc('/db/apps/sitemap/06-collection-titles.xml')/code-table/item[$db-path=path]/title/text()
order by $child
return
<li>
<a href="{if (empty($lookup))
then ($path)
else (concat($path, "/index.xhtml"))}">
{if (empty($lookup)) then ($child) else ($lookup)}
</a>
{local:sitemap(concat($collection, '/', $child))}
</li>
}</ol>
};
请注意,子集合都是按字母顺序排序的。在某些情况下,这可能不是您希望显示网站导航菜单的顺序。您可以向显示标题的 XML 文件添加一个 sort-order 参数元素,并使用该字段对子集合进行排序。
将此文件放在以下位置:/db/apps/sitemap/06-collection-titles.xml
<code-table>
<item>
<path>/db/webroot/about</path>
<title>About</title>
</item>
<item>
<path>/db/webroot/training</path>
<title>Training</title>
</item>
<item>
<path>/db/webroot/faqs</path>
<title>Frequently Asked Questions</title>
</item>
<item>
<path>/db/webroot/training/xforms</path>
<title>XForms</title>
</item>
<item>
<path>/db/webroot/training/rest</path>
<title>ReST</title>
</item>
<item>
<path>/db/webroot/training/xquery</path>
<title>XQuery</title>
</item>
<item>
<path>/db/webroot/training/tei</path>
<title>Text Encoding Initiative</title>
</item>
<item>
<path>/db/webroot/training/exist</path>
<title>eXist</title>
</item>
<item>
<path>/db/webroot/products</path>
<title>Products</title>
</item>
<item>
<path>/db/webroot/support</path>
<title>Support</title>
</item>
</code-table>
并非所有集合都应该显示在站点地图中。某些集合可能包含您不想在公共站点地图中显示的私有管理数据。有两种方法可以处理这个问题。您可以将一个通用的“已发布集合”列表保存在单独的 XML 文件中。或者,您可以在每个集合中存储一个小的 XML 文件来显示该集合的属性。默认情况下,此集合可以是公共的或私有的,具体取决于您编写函数的方式。
如果您的同事和您各自都在构建希望共享的 Web 应用程序,则第二种选择更具可移植性。通过在您的 collection-properties.xml 文件上进行标准化,您可以将属性存储在集合中,然后通过交换可以压缩的文件夹来交换它们与其他 eXist 站点。
以下是伪代码
declare function local:count-files-in-collection($collection as xs:string) as xs:integer {
let $child-collections := xmldb:get-child-collections($collection)
if (empty($child-collections))
then
(: return the count of number of files in this collection :)
else
(: for each subcollection call local:count-files-in-collection($child) :)
};