內容管理系統的 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) :)
};