Newly created collections should be private

svn path=/trunk/eXist/; revision=12295

xquery version "1.0";

declare namespace op="http://exist-db.org/xquery/biblio/operations";

declare namespace request="http://exist-db.org/xquery/request";
declare namespace response="http://exist-db.org/xquery/response";
declare namespace util="http://exist-db.org/xquery/util";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";

declare variable $rwx------ := 448;
declare variable $rwxrwx--- := 504;
declare variable $rwxrwxrwx := 511;

declare function op:create-collection($parent as xs:string, $name as xs:string) as element(status) {
    let $collection := xmldb:create-collection($parent, $name),

    (: by default newly created collections are private :)
    $current-group := xmldb:get-group($collection),
    $null := xmldb:set-collection-permissions($collection, request:get-attribute("xquery.user"), $current-group, $rwx------) return
        <status id="created">{$collection}</status>
};

declare function op:move-collection($parent as xs:string, $path as xs:string) as element(status) {
    let $null := xmldb:move($parent, $path) return
        <status id="moved" from="{$parent}">{$path}</status>
};

declare function op:remove-collection($collection as xs:string) as element(status) {
    let $null := xmldb:remove($collection) return
        <status id="removed">{$collection}</status>
};

declare function op:update-collection-permissions($collection as xs:string, $restriction as xs:string, $user-group as xs:string?) as element(status) {
    let $null := if($restriction eq "user") then
        (
            (: onlu this user can access, so restrict full access to user :)
            let $current-group := xmldb:get-group($collection) return
                xmldb:set-collection-permissions($collection, request:get-attribute("xquery.user"), $current-group, $rwx------)
        )
        else if($restriction eq "group")then
        (
            (: anyone in the group can access, so restrict full access to group :)
            let $current-owner := xmldb:get-owner($collection) return
                xmldb:set-collection-permissions($collection, $current-owner, $user-group, $rwxrwx---)
        )
        else
        (
            (: anyone can access, so allow full access to everyone :)
            let $current-owner := xmldb:get-owner($collection),
            $current-group := xmldb:get-group($collection) return
                xmldb:set-collection-permissions($collection, $current-owner, $current-group, $rwxrwxrwx)
        )
    return
        <status id="permissions">{$restriction}{if($restriction eq "group")then(concat(": ", $user-group))else()}</status>
};

declare function op:unknown-action($action as xs:string) {
        response:set-status-code(403),

Unknown action: {$action}.

};

let $action := request:get-parameter("action", ()),
$collection := request:get-parameter("collection", ())
return
    if($action eq "create-collection")then
        op:create-collection($collection, request:get-parameter("name",()))
    else if($action eq "move-collection")then
        op:move-collection($collection, request:get-parameter("path",()))
    else if($action eq "remove-collection")then
        op:remove-collection($collection)
    else if($action eq "update-collection-permissions")then
        op:update-collection-permissions($collection, request:get-parameter("restriction", ()), request:get-parameter("userGroup",()))
    else
        op:unknown-action($action)

xquery version "1.0";

declare namespace op="http://exist-db.org/xquery/biblio/operations";

declare namespace request="http://exist-db.org/xquery/request";
declare namespace response="http://exist-db.org/xquery/response";
declare namespace util="http://exist-db.org/xquery/util";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";

declare variable $rwx------ := 448;
declare variable $rwxrwx--- := 504;
declare variable $rwxrwxrwx := 511;

declare function op:create-collection($parent as xs:string, $name as xs:string) as element(status) {
    let $collection := xmldb:create-collection($parent, $name) return
        <status id="created">{$collection}</status>
};

declare function op:move-collection($parent as xs:string, $path as xs:string) as element(status) {
    let $null := xmldb:move($parent, $path) return
        <status id="moved" from="{$parent}">{$path}</status>
};

declare function op:remove-collection($collection as xs:string) as element(status) {
    let $null := xmldb:remove($collection) return
        <status id="removed">{$collection}</status>
};

declare function op:update-collection-permissions($collection as xs:string, $restriction as xs:string, $user-group as xs:string?) as element(status) {
    let $null := if($restriction eq "user") then
        (
            (: onlu this user can access, so restrict full access to user :)
            let $current-group := xmldb:get-group($collection) return
                xmldb:set-collection-permissions($collection, request:get-attribute("xquery.user"), $current-group, $rwx------)
        )
        else if($restriction eq "group")then
        (
            (: anyone in the group can access, so restrict full access to group :)
            let $current-owner := xmldb:get-owner($collection) return
                xmldb:set-collection-permissions($collection, $current-owner, $user-group, $rwxrwx---)
        )
        else
        (
            (: anyone can access, so allow full access to everyone :)
            let $current-owner := xmldb:get-owner($collection),
            $current-group := xmldb:get-group($collection) return
                xmldb:set-collection-permissions($collection, $current-owner, $current-group, $rwxrwxrwx)
        )
    return
        <status id="permissions">{$restriction}{if($restriction eq "group")then(concat(": ", $user-group))else()}</status>
};

declare function op:unknown-action($action as xs:string) {
        response:set-status-code(403),

Unknown action: {$action}.

};

let $action := request:get-parameter("action", ()),
$collection := request:get-parameter("collection", ())
return
    if($action eq "create-collection")then
        op:create-collection($collection, request:get-parameter("name",()))
    else if($action eq "move-collection")then
        op:move-collection($collection, request:get-parameter("path",()))
    else if($action eq "remove-collection")then
        op:remove-collection($collection)
    else if($action eq "update-collection-permissions")then
        op:update-collection-permissions($collection, request:get-parameter("restriction", ()), request:get-parameter("userGroup",()))
    else
        op:unknown-action($action)


modified the introduction for the module documentation

xquery version "1.0";
(:
 : Copyright 2006-2011 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module provides a function (np:node-position) that given, a node,
 : returns positional information about the node in the form of an xs:anyURI
 : item. The module also defines functions that use such positional information
 : to determine: (1) structural relationships between two nodes (e.g. if one
 : is the ancestor of another) and (2) properties of a node (e.g. its level in
 : the tree).
 :
 : Within this module, the temp "node position" will be used to refer to an
 : xs:anyURI item that is returned by the np:node-position function.
 :
 : @author Federico Cavalieri
 :)
module namespace np = "http://www.zorba-xquery.com/modules/node-position";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : Compute a stable and opaque structural information representation
 : (with type xs:anyURI) for a given node.
 :
 :
The returned URI is stable, i.e. it does not change when other
 : nodes are inserted, deleted or modified.

 :
 :
Each node in a snapshot has a different URI. Note however that
 : different nodes in different snapshots might have the same URI.

 :
 : @param $arg the node for which the structural information URI
 :  should be computed
 :
 : @return the opaque structural information URI of the node.
 :)
declare function np:node-position(
  $arg as node()
) as xs:anyURI external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is an ancestor of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential descendant structural information
 : @param $s-info2 the potential ancestor structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is an ancestor of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:ancestor-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a descendant of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential ancestor structural information
 : @param $s-info2 the potential descendant structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a descendant of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:descendant-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is the parent of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential child structural information
 : @param $s-info2 the potential parent structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is the parent of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:parent-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a child of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential parent structural information
 : @param $s-info2 the potential child structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a child of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:child-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is an attribute of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential parent structural information
 : @param $s-info2 the potential attribute structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is an attribute of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:attribute-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a following-sibling of the node
 : whose structural information is given as first argument.
 :
 : @param $s-info1 the potential preceding-sibling structural information
 : @param $s-info2 the potential following-sibling structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a following-sibling of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:following-sibling-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a preceding-sibling of the node
 : whose structural information is given as first argument.
 :
 : @param $s-info1 the potential following-sibling structural information
 : @param $s-info2 the potential preceding-sibling structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a preceding-sibling of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:preceding-sibling-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is following the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential preceding node structural information
 : @param $s-info2 the potential following node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is following the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:following-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is following in document order the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential preceding node structural information
 : @param $s-info2 the potential following node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is following in document order the node whose structural information
 : is $s-info1; false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:following-in-document-order-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is preceding the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential following node structural information
 : @param $s-info2 the potential preceding node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is preceding the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:preceding-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is preceding the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential following node structural information
 : @param $s-info2 the potential preceding node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is preceding in document order the node whose structural information
 : is $s-info1; false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:preceding-in-document-order-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Computes the level of a node in the tree given its structural
 : information.
 :
 : Note: The first level has the number one.
 :
 : @param $s-info the structural information of the node which
 :  level should be computed
 :
 : @return The level in the tree as xs:integer of the node whose
 : structural information is $s-info.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:level(
  $s-info as xs:anyURI) as xs:integer external;

(:~
 : Determines whether two nodes are siblings given their structural information.
 :
 : @param $s-info1 a node structural information
 : @param $s-info2 a node structural information
 :
 : @return true if the two nodes whose structural information are $s-info1
 : and $s-info2 are siblings; false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:sibling-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether a node is an attribute given its structural information.
 :
 : @param $s-info the potential attribute structural information
 :
 : @return true if the node whose structural information is $s-info
 : is an attribute; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-attribute(
  $s-info1 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether a node is a comment given its structural information.
 :
 : @param $s-info the potential comment structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a comment; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-comment(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is a document given its structural information.
 :
 : @param $s-info the potential document structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a document; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-document(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is an element given its structural information.
 :
 : @param $s-info the potential element structural information
 :
 : @return true if the node whose structural information is $s-info
 : is an element; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-element(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is a processing-instruction given its structural information.
 :
 : @param $s-info the potential processing-instruction structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a processing-instruction; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-processing-instruction(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is a text given its structural information.
 :
 : @param $s-info the potential text node structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a text node; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-text(
  $s-info1 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether two nodes belong to the same tree given their
 : structural information.
 :
 : @param $s-info1 a node structural information
 : @param $s-info2 a node structural information
 :
 : @return true if the two node whose structural information are $s-info1
 : and $s-info2 belong to the same tree.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:in-same-tree-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether a node belongs to a collection given its structural
 : information.
 :
 : @param $s-info the node structural information
 :
 : @return true if the node whose structural information is $s-info
 : is in a collection; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:in-collection(
  $s-info as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether two nodes belong to the same collection given their
 : structural information.
 :
 : @param $s-info1 a node structural information
 : @param $s-info2 a node structural information
 :
 : @return true if the two node whose structural information are $s-info1
 : and $s-info2 belong to the same collection.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:in-same-collection-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

xquery version "1.0";
(:
 : Copyright 2006-2011 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module allows to retrieve an xs:anyURI representation of a
 : node position. This module also defines function that determine:
 : (1) the relationship between two nodes (e.g. if one is the ancestor
 : of another) and (2) properties of a node (e.g. its level in the tree)
 : given the corresponding nodes positions.
 :
 : @author Federico Cavalieri
 :)
module namespace np = "http://www.zorba-xquery.com/modules/node-position";
declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : Compute a stable and opaque structural information representation
 : (with type xs:anyURI) for a given node.
 :
 :
The returned URI is stable, i.e. it does not change when other
 : nodes are inserted, deleted or modified.

 :
 :
Each node in a snapshot has a different URI. Note however that
 : different nodes in different snapshots might have the same URI.

 :
 : @param $arg the node for which the structural information URI
 :  should be computed
 :
 : @return the opaque structural information URI of the node.
 :)
declare function np:node-position(
  $arg as node()
) as xs:anyURI external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is an ancestor of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential descendant structural information
 : @param $s-info2 the potential ancestor structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is an ancestor of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:ancestor-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a descendant of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential ancestor structural information
 : @param $s-info2 the potential descendant structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a descendant of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:descendant-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is the parent of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential child structural information
 : @param $s-info2 the potential parent structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is the parent of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:parent-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a child of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential parent structural information
 : @param $s-info2 the potential child structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a child of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:child-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is an attribute of the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential parent structural information
 : @param $s-info2 the potential attribute structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is an attribute of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:attribute-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a following-sibling of the node
 : whose structural information is given as first argument.
 :
 : @param $s-info1 the potential preceding-sibling structural information
 : @param $s-info2 the potential following-sibling structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a following-sibling of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:following-sibling-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is a preceding-sibling of the node
 : whose structural information is given as first argument.
 :
 : @param $s-info1 the potential following-sibling structural information
 : @param $s-info2 the potential preceding-sibling structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is a preceding-sibling of the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:preceding-sibling-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is following the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential preceding node structural information
 : @param $s-info2 the potential following node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is following the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:following-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is following in document order the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential preceding node structural information
 : @param $s-info2 the potential following node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is following in document order the node whose structural information
 : is $s-info1; false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:following-in-document-order-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is preceding the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential following node structural information
 : @param $s-info2 the potential preceding node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is preceding the node whose structural information is $s-info1;
 : false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:preceding-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether the node whose structural information is
 : given as second argument is preceding the node whose
 : structural information is given as first argument.
 :
 : @param $s-info1 the potential following node structural information
 : @param $s-info2 the potential preceding node structural information
 :
 : @return true if the node whose structural information is $s-info2
 : is preceding in document order the node whose structural information
 : is $s-info1; false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:preceding-in-document-order-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Computes the level of a node in the tree given its structural
 : information.
 :
 : Note: The first level has the number one.
 :
 : @param $s-info the structural information of the node which
 :  level should be computed
 :
 : @return The level in the tree as xs:integer of the node whose
 : structural information is $s-info.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:level(
  $s-info as xs:anyURI) as xs:integer external;

(:~
 : Determines whether two nodes are siblings given their structural information.
 :
 : @param $s-info1 a node structural information
 : @param $s-info2 a node structural information
 :
 : @return true if the two nodes whose structural information are $s-info1
 : and $s-info2 are siblings; false otherwise.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:sibling-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether a node is an attribute given its structural information.
 :
 : @param $s-info the potential attribute structural information
 :
 : @return true if the node whose structural information is $s-info
 : is an attribute; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-attribute(
  $s-info1 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether a node is a comment given its structural information.
 :
 : @param $s-info the potential comment structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a comment; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-comment(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is a document given its structural information.
 :
 : @param $s-info the potential document structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a document; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-document(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is an element given its structural information.
 :
 : @param $s-info the potential element structural information
 :
 : @return true if the node whose structural information is $s-info
 : is an element; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-element(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is a processing-instruction given its structural information.
 :
 : @param $s-info the potential processing-instruction structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a processing-instruction; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-processing-instruction(
  $s-info1 as xs:anyURI) as xs:boolean external;

  (:~
 : Determines whether a node is a text given its structural information.
 :
 : @param $s-info the potential text node structural information
 :
 : @return true if the node whose structural information is $s-info
 : is a text node; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:is-text(
  $s-info1 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether two nodes belong to the same tree given their
 : structural information.
 :
 : @param $s-info1 a node structural information
 : @param $s-info2 a node structural information
 :
 : @return true if the two node whose structural information are $s-info1
 : and $s-info2 belong to the same tree.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:in-same-tree-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether a node belongs to a collection given its structural
 : information.
 :
 : @param $s-info the node structural information
 :
 : @return true if the node whose structural information is $s-info
 : is in a collection; false otherwise.
 :
 : @error zerr:ZAPI0028 if the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:in-collection(
  $s-info as xs:anyURI) as xs:boolean external;

(:~
 : Determines whether two nodes belong to the same collection given their
 : structural information.
 :
 : @param $s-info1 a node structural information
 : @param $s-info2 a node structural information
 :
 : @return true if the two node whose structural information are $s-info1
 : and $s-info2 belong to the same collection.
 :
 : @error zerr:ZAPI0028 if one of the given URI is not a valid node structural
 : information computed by the <tt>np:node-position</tt> function.
 :)
declare function np:in-same-collection-of(
  $s-info1 as xs:anyURI,
  $s-info2 as xs:anyURI) as xs:boolean external;


show exist version and build number

show exist version and build number

svn path=/trunk/eXist-1.0/; revision=712

xquery version "1.0";

(: prints a list of all built-in functions :)
declare namespace util="http://exist-db.org/xquery/util";

<document xmlns:xi="http://www.w3.org/2001/XInclude">

    	<logo src="logo.jpg"/>
    	<title>Open Source Native XML Database</title>
		<author email="wolfgang@exist-db.org">Wolfgang M. Meier</author>


move to csd_webconf:db for everything

move to csd_webconf:db for everything

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/openhie/openinfoman
:
:)
module namespace csd_mcs = "https://github.com/openhie/openinfoman/csd_mcs";
import module namespace csd_lsc = "https://github.com/openhie/openinfoman/csd_lsc" ;
import module namespace csd_psd = "https://github.com/openhie/openinfoman/csd_psd" ;
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm" ;

declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare variable $csd_mcs:merged_services_doc := 'merged_services.xml';

declare function csd_mcs:store_exists() {
  db:is-xml($csd_webconf:db, $csd_mcs:merged_services_doc)
};

declare updating function csd_mcs:init_store() {
  db:add($csd_webconf:db, csd_dm:blank_directory(), $csd_mcs:merged_services_doc)
};

(:
: Merges together all documents registered with the document manager
:)
declare updating function csd_mcs:merge($dest,$sources) {

  (: Merges together all cached documents only
  for $name in csd_psd:registered_directories($db)
  return csd_lsc:refresh_doc(db:open($db,$csd_mcs:merged_services_doc),csd_lsc:get_cache($db,$name))
  :)
  let $dest_doc := csd_dm:open_document($dest)
  return
    if (exists($dest_doc))
    then
      for $name in $sources
      return if ($name != $dest) then
	csd_lsc:refresh_doc($dest_doc, csd_dm:open_document($name))
      else ()
    else ()

};

declare function csd_mcs:get() {
  db:open($csd_webconf:db,$csd_mcs:merged_services_doc)
};

declare updating function csd_mcs:empty() {
  db:replace($csd_webcond:db, $csd_mcs:merged_services_doc, csd_dm:blank_directory())
};

declare function csd_mcs:get_merge_doc_name() {
  let $name := 'merged_remote_services'
  return $name
};

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/openhie/openinfoman
:
:)
module namespace csd_mcs = "https://github.com/openhie/openinfoman/csd_mcs";
import module namespace csd_lsc = "https://github.com/openhie/openinfoman/csd_lsc" ;
import module namespace csd_psd = "https://github.com/openhie/openinfoman/csd_psd" ;
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm" ;

declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare variable $csd_mcs:merged_services_doc := 'merged_services.xml';

declare function csd_mcs:store_exists($db) {
  db:is-xml($db, $csd_mcs:merged_services_doc)
};

declare updating function csd_mcs:init_store($db) {
  db:add($db, csd_dm:blank_directory(), $csd_mcs:merged_services_doc)
};

(:
: Merges together all documents registered with the document manager
:)
declare updating function csd_mcs:merge($db,$dest,$sources) {

  (: Merges together all cached documents only
  for $name in csd_psd:registered_directories($db)
  return csd_lsc:refresh_doc(db:open($db,$csd_mcs:merged_services_doc),csd_lsc:get_cache($db,$name))
  :)
  let $dest_doc := csd_dm:open_document($dest)
  return
    if (exists($dest_doc))
    then
      for $name in $sources
      return if ($name != $dest) then
	csd_lsc:refresh_doc($dest_doc, csd_dm:open_document($name))
      else ()
    else ()

};

declare function csd_mcs:get($db) {
  db:open($db,$csd_mcs:merged_services_doc)
};

declare updating function csd_mcs:empty($db) {
  db:replace($db, $csd_mcs:merged_services_doc, csd_dm:blank_directory())
};

declare function csd_mcs:get_merge_doc_name() {
  let $name := 'merged_remote_services'
  return $name
};


fix for the categorization in the xqdoc documentation

(:
 : Copyright 2006-2009 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module defines a set of function which can be used
 : to determine (1) the relationship between two nodes (e.g. if one
 : is the ancestor if another) and (2) properties of given
 : nodes (e.g. their level in the tree).
 :
 : @author Matthias Brantner
 : @project XDM/node
 :)
module namespace node = "http://www.zorba-xquery.com/modules/node";

(:~
 : Determines whether the node given as second argument is an
 : ancestor of the node given as first argument.
 :
 : @param $node1 the potential descendant
 : @param $node2 the potential ancestor
 :
 : @return true if $node2 is an ancestor of $node1; false otherwise.
 :)
declare function node:ancestor-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : descendant of the node given as first argument.
 :
 : @param $node1 the potential ancestor
 : @param $node2 the potential descendant
 :
 : @return true if $node2 is a descendant of $node1; false otherwise.
 :)
declare function node:descendant-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : parent of the node given as first argument.
 :
 : @param $node1 the potential child
 : @param $node2 the potential parent
 :
 : @return true if $node2 is a parent of $node1; false otherwise.
 :)
declare function node:parent-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : child of the node given as first argument.
 :
 : @param $node1 the potential parent
 : @param $node2 the potential child
 :
 : @return true if $node2 is a child of $node1; false otherwise.
 :)
declare function node:child-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : following-sibling of the node given as first argument.
 :
 : @param $node1 the potential preceding-sibling
 : @param $node2 the potential following-sibling
 :
 : @return true if $node2 is a following-sibling of $node1; false otherwise.
 :)
declare function node:following-sibling-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : preceding-sibling of the node given as first argument.
 :
 : @param $node1 the potential following-sibling
 : @param $node2 the potential preceding-sibling
 :
 : @return true if $node2 is a preceding-sibling of $node1; false otherwise.
 :)
declare function node:preceding-sibling-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is
 : following the node given as first argument.
 :
 : @param $node1 the potential preceding node
 : @param $node2 the potential following node
 :
 : @return true if $node2 is following the node $node1; false otherwise.
 :)
declare function node:following-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is
 : preceding the node given as first argument.
 :
 : @param $node1 the potential following node
 : @param $node2 the potential preceding node
 :
 : @return true if $node2 is preceding the node $node1; false otherwise.
 :)
declare function node:preceding-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Computes the level of a given node in the tree.
 :
 : Note: The first level has the number one.
 :
 : @param $node the node for which the level should be computed
 :
 : @return The level as xs:integer of the given node in the tree.
 :)
declare function node:level(
  $node as node()) as xs:integer external;

(:~
 : Computes the least common ancestor of two given nodes in
 : the tree.
 :
 : @param $node1 the first node
 : @param $node2 the second node
 :
 : @return the least common ancestor of the two given nodes or
 :   the empty sequence if the two nodes are not part of the same
 :   tree.
 :)
declare function node:least-common-ancestor(
  $node1 as node(),
  $node2 as node()) as node()? external;

(:
 : Copyright 2006-2009 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module defines a set of function which can be used
 : to determine (1) the relationship between two nodes (e.g. if one
 : is the ancestor if another) and (2) properties of given
 : nodes (e.g. their level in the tree).
 :
 : @author Matthias Brantner
 : @project "XDM/node"
 :)
module namespace node = "http://www.zorba-xquery.com/modules/node";

(:~
 : Determines whether the node given as second argument is an
 : ancestor of the node given as first argument.
 :
 : @param $node1 the potential descendant
 : @param $node2 the potential ancestor
 :
 : @return true if $node2 is an ancestor of $node1; false otherwise.
 :)
declare function node:ancestor-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : descendant of the node given as first argument.
 :
 : @param $node1 the potential ancestor
 : @param $node2 the potential descendant
 :
 : @return true if $node2 is a descendant of $node1; false otherwise.
 :)
declare function node:descendant-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : parent of the node given as first argument.
 :
 : @param $node1 the potential child
 : @param $node2 the potential parent
 :
 : @return true if $node2 is a parent of $node1; false otherwise.
 :)
declare function node:parent-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : child of the node given as first argument.
 :
 : @param $node1 the potential parent
 : @param $node2 the potential child
 :
 : @return true if $node2 is a child of $node1; false otherwise.
 :)
declare function node:child-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : following-sibling of the node given as first argument.
 :
 : @param $node1 the potential preceding-sibling
 : @param $node2 the potential following-sibling
 :
 : @return true if $node2 is a following-sibling of $node1; false otherwise.
 :)
declare function node:following-sibling-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is a
 : preceding-sibling of the node given as first argument.
 :
 : @param $node1 the potential following-sibling
 : @param $node2 the potential preceding-sibling
 :
 : @return true if $node2 is a preceding-sibling of $node1; false otherwise.
 :)
declare function node:preceding-sibling-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is
 : following the node given as first argument.
 :
 : @param $node1 the potential preceding node
 : @param $node2 the potential following node
 :
 : @return true if $node2 is following the node $node1; false otherwise.
 :)
declare function node:following-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Determines whether the node given as second argument is
 : preceding the node given as first argument.
 :
 : @param $node1 the potential following node
 : @param $node2 the potential preceding node
 :
 : @return true if $node2 is preceding the node $node1; false otherwise.
 :)
declare function node:preceding-of(
  $node1 as node(),
  $node2 as node()) as xs:boolean external;

(:~
 : Computes the level of a given node in the tree.
 :
 : Note: The first level has the number one.
 :
 : @param $node the node for which the level should be computed
 :
 : @return The level as xs:integer of the given node in the tree.
 :)
declare function node:level(
  $node as node()) as xs:integer external;

(:~
 : Computes the least common ancestor of two given nodes in
 : the tree.
 :
 : @param $node1 the first node
 : @param $node2 the second node
 :
 : @return the least common ancestor of the two given nodes or
 :   the empty sequence if the two nodes are not part of the same
 :   tree.
 :)
declare function node:least-common-ancestor(
  $node1 as node(),
  $node2 as node()) as node()? external;


Reimplemented scope qualification. Result appears to be correct

(: =====================================================

   DITA Link Manager Model

   Model component of Model/View/Controller. Maintains
   the link management data.

   Provides queries to create and update resource use records,
   dependency indexes, etc.

   These queries are called whenever new content is added to
   given database.

   Author: W. Eliot Kimber

   Copyright (c) 2015 DITA For Small Teams
   Licensed under Apache License 2

   ===================================================== :)

module namespace lmm="http://dita-for-small-teams.org/xquery/modules/linkmgr-model";

declare namespace dfst="http://dita-for-small-teams.org";

import module namespace df="http://dita-for-small-teams.org/xquery/modules/dita-utils";
import module namespace bxutil="http://dita-for-small-teams.org/xquery/modules/basex-utils";
import module namespace lmutil="http://dita-for-small-teams.org/xquery/modules/linkmgmt-utils";
import module namespace dfstcnst="http://dita-for-small-teams.org/xquery/modules/dfst-constants";
import module namespace relpath="http://dita-for-small-teams.org/xquery/modules/relpath-utils";

(:~
 : Create or update the link management indexes for the specified repository
 :
 :)
declare %updating function lmm:updateLinkManagementIndexes(
                               $contentDbName as xs:string,
                               $metadataDbName as xs:string) {
   (: Query the database to find all links and for each link, record a resource use
      record in the database.

      Every addressible element has a "resource key" which combines the URI of
      the containing document and the element's @id value (only elements with
      @id attributes are addressible.

      The link management indexes are stored in a separate database from
      the content database: every branch-specific database has a corresponding
      metadata database that contains the where-used records, resolved maps,
      key space information, and any other metadata you might want to store.

      For each document used there is a directory whose name is the hash
      of the document's URI (which will be unique within a branch). Within
      that directory are the use records for elements within that document.
      This arrangement has the effect of providing a reference count for
      for each document, making it quick to determine if a given document
      is used by any links. It also allows for narrowing the scope of
      where-used queries to just the entries for the document.

    :)

    (: Need to handle both context-free links (direct URI references) and
       context-specific links (key references).

       For context-specific links have to process all the maps and construct
       resolved maps to serve as the resolution context. This could also
       be modeled as updating the key spaces but I think it makes more
       sense to have the maps be the focus, from which key spaces are derived,
       rather than making the key spaces the primary focus.

     :)

    let $logID := 'linkMgmtIndex' (: ID of the log to log messages to :)

    let $directLinks := lmutil:findAllDirectLinks($contentDbName)

    return (
      try {
          db:delete($metadataDbName, $dfstcnst:where-used-dir)
      } catch * {
          (: FIXME: Log to log doc :)
          db:output(<error>Exception deleting where-used directory "{$dfstcnst:where-used-dir}": {$err:description}</error>)
      },

      (: Now create new resource use records for direct links :)

      lmm:createDirectLinkResourceRecords($metadataDbName, $directLinks, $logID),

      (: Now create resolved maps and key space documents for each of the root maps.
       :)
      lmm:constructKeySpaces(
         $contentDbName,
         $metadataDbName,
         $logID),

      (: Now create resource use records for all the indirect links: :)
      lmm:createIndirectLinkResourceRecords(
                  $metadataDbName,
                  lmutil:findAllIndirectLinks($contentDbName),
                  $logID)
    )

};

declare %updating function lmm:createDirectLinkResourceRecords(
        $metadataDbName as xs:string,
        $directLinks as map(*)*,
        $logID as xs:string) {
    for $linkItem in $directLinks
       let $resolveResult := lmutil:resolveDirectLink($linkItem)
       return lmm:createOrUpdateResourceUseRecord(
                   $metadataDbName,
                   $linkItem,
                   $resolveResult,
                   $logID)

};

declare %updating function lmm:createIndirectLinkResourceRecords(
                               $metadataDbName as xs:string,
                               $indirectLinks,
                               $logID as xs:string) {
    for $linkItem in $indirectLinks
        let $resolveResult as map(*) := lmutil:resolveIndirectLink($linkItem)
        return lmm:createOrUpdateResourceUseRecord(
                   $metadataDbName,
                   $linkItem,
                   $resolveResult,
                   $logID)
};

(: Given a link and the resolved result, creates a resource use record for each
   target the link resolved to.

 :)
declare %updating function lmm:createOrUpdateResourceUseRecord(
                              $metadataDbName,
                              $linkItem as map(*),
                              $resolveResult as map(*),
                              $logID as xs:string) {
   let $resolveResult as map(*) := lmutil:resolveDirectLink($linkItem)
   let $targets := $resolveResult('target')
   for $target in $targets
       return lmm:createOrUpdateResourceUseRecordForLinkTarget(
                    $metadataDbName,
                    $linkItem,
                    $target,
                    $logID)
};

(:
 : Creates a use record reflecting the use of the target element by the document
 : that contains the link.
 :
 : The use records are organized into directories, one directory per used document,
 : where the directory name is the MD5 hash of the document URI (just to make a
 : shorter name that won't have any problematic characters).
 :
 : Each use record document's filename is the MD5 hash of the linking document's
 : URI plus the MD5 hash of the linking element.
 :
 : Potential problem: the same element could, in theory, link to the same target in
 : two different ways: as a link and as a conref, although this case is extremely
 : unlikely. Really need a way to distinguish each abstract link represented by
 : a given DITA link-establishing element but the current implementation doesn't
 : provide that abstraction. It could through a refactor. Keeping it simple for now.
 :)
declare %updating function lmm:createOrUpdateResourceUseRecordForLinkTarget(
           $metadataDbName as xs:string,
           $linkItem as map(*),
           $target as element(),
           $logID as xs:string) {
   let $targetDoc := root($target)
   let $link := $linkItem('link')
   let $containingDir := concat($dfstcnst:where-used-dir, '/',
                                lmutil:constructResourceKeyForElement($target),
                                '/')
   let $reskey := lmutil:constructResourceKeyForElement($link)
   let $recordFilename := concat('use-record_', $reskey, '.xml')
   let $format := if ($link/@format)
                     then string($link/@format)
                     else 'dita'
   let $scope := if ($link/@scope)
                     then string($link/@scope)
                     else 'local'
   let $linkContext := $linkItem('linkContext')

   (: df:getTitleForLinkElementContainer($link) :)
   let $useRecord :=
     <dfst:useRecord resourceKey="{$reskey}"
                     targetDoc="{document-uri($targetDoc)}"
                     usingDoc="{document-uri(root($link))}"
                     linkType="{df:getBaseLinkType($link)}"
                     linkClass="{string($link/@class)}"
                     linkContext="{$linkContext}"
                     format="{$format}"
                     scope="{$scope}"
     >
       <title>{if (df:class($link, 'map/topicref'))
                  then string((root($link)/*/*[df:class(., 'topic/title')] |
                        root($link)/*/@title)[1])
                  else string($link/ancestor::*[df:class(., 'topic/topic')][1]/*[df:class(., 'topic/title')])}</title>
     </dfst:useRecord>
    let $useRecordUri := relpath:newFile($containingDir, $recordFilename)
    return try {
       (
       if (db:exists($metadataDbName, $useRecordUri))
          then db:delete($metadataDbName, $useRecordUri)
          else(),
       db:replace($metadataDbName,
                  $useRecordUri,
                  $useRecord),
       (: FIXME: Write record to log doc :)
       db:output(<info>Stored use record "{$useRecordUri}"</info>)
       )
    } catch * {
       (: FIXME: Write record to log doc :)
       db:output(<error>Error storing use record to "{$useRecordUri}": {$err:description}</error>)
    }
};

(:~
 : Construct key spaces from root maps.
 :
 : @param contentDbName Name of datbase that contains the content to construct key spaces from
 : @param metadataDbName Name of metadata database to store construct key spaces in.
 :
 : Finds all the root maps (requires that all direct-link use records are up to date).
 :
 : For each root map, constructs and stores the resolved map.
 :
 :)
declare %updating function lmm:constructKeySpaces(
        $contentDbName as xs:string,
        $metadataDbName as xs:string,
        $logID as xs:string) {

  (: General process:

     1. Find all DITA maps

     2. For each map, construct a resolved map that preserves submap
        boundaries and original source map details.

     The resolved maps can then be used to determine the effective
     binding for any key without the need to separately construct
     key spaces. This technique was pioneered and suggested by
     Chris Nitchie, designer of the DITA 1.3 scoped keys mechanism.

     Note that there's no point in trying to determine if a given
     map is a "root map" as any map can, in theory, be used as a root
     map. Likewise, a map that happens to have no local-scope
     topicrefs to it may not be intended to be the root of a publication.

     One alternative to this approach is to only consider maps that have
     peer-scope topicrefs to them to be root maps (per the DITA 1.3
     meaning for peer-scope topicrefs to dita maps). In this case, there needs
     to be a "master map" that serves to simply identify the "top level
     root maps", that is, the maps that are considered by the document
     authors to be The root maps, that is, the maps they consider to
     be the roots of publications. Given this master map the system can
     then consider only peer-scope map references in order to determine
     the root maps and thus the maps for which key space construction
     is relevant.

   :)

   let $maps := collection($contentDbName)/*[df:class(., 'map/map')][lmutil:isRootMap(.)]
   return for $map in $maps
              return (db:output(<info>Resolving map {document-uri(root($map))}...</info>),
                      lmm:constructResolvedMap(
                                              $map,
                                              $metadataDbName,
                                              $logID)
                     )

};

(:~
 : Construct a resolved map suiteable for doing key resolution. The map
 : is stored in the metadata database.
 :
 : @param map DITA map that is the root of the resolved map.
 : @param metadataDbName Name of the metadata database to store the
 :        resolved map in
 :)
declare %updating function lmm:constructResolvedMap(
                                    $map as element(),
                                    $metadataDbName as xs:string,
                                    $logID as xs:string) {
  let $contentDbName := db:name($map)
  let $resolvedMap := lmm:resolveMap($map, $logID)
  let $resolvedMapURI := lmutil:getResolvedMapURIForMap($map)
  return (db:replace($metadataDbName, $resolvedMapURI, $resolvedMap),
          db:output(<info>Stored resolved map "{$resolvedMapURI}"</info>))

};

(:~
 : Takes a root map and returns the resolved version of the map.
 :
 : The resolution process preserves the original structure and
 : adds <topicgroup> elements to record submap boundaries.
 :
 : The resulting resolved map reflects all directly-referenced submaps.
 : Because the resolved map is used for constructing key spaces it is
 : not useful or necessary to resolve key-based map references as those
 : maps cannot contribute to the final set of effective key spaces.
 :)
declare function lmm:resolveMap(
                     $map as element(),
                     $logID as xs:string) as element() {

    (: Array of sequences of key scope names, lowest to highest
       (e.g., as for ancestor:: axis)
       Each sequence is the set of key scope names defined
       on a given map or topicref, establishing a new set
       of key scopes.

       There is always the root (anonymous) key scope
       bound to the root map. The root map can also
       establish additional scope names.
    :)
    let $keyScopes as array(*) :=
       [ (if ($map/@keyscope)
             then tokenize(string($map/@keyscope), ' ')
             else ()
         )
       ]
    let $resolvedMap :=
          element {name($map)}
            {
              attribute origMapURI { document-uri(root($map)) },
              attribute origMapDB { db:name($map) },
              attribute xml:base { encode-for-uri(document-uri(root($map))) },
              $map/@*,
              for $node in $map/node()
                  return lmm:resolveMapHandleNode($node, $keyScopes, $logID)
            }
    return $resolvedMap
};

(:~
 : Simple identity transform dispatch handler for nodes of any type.
 :)
declare function lmm:resolveMapHandleNode(
                     $node as node(),
                     $keyScopes as array(*),
                     $logID as xs:string) as node()* {
  typeswitch ($node)
    case element() return lmm:resolveMapHandleElement(
                              $node,
                              $keyScopes,
                              $logID)
    case attribute(keys) return lmm:expandKeyNames(
                                    $node,
                                    $keyScopes,
                                    $logID)
    default return $node
};

(:~
 : Apply identity transform to elements of any type.
 :)
declare function lmm:resolveMapHandleElement(
                       $elem as element(),
                       $keyScopes as array(*),
                       $logID as xs:string) as element()* {
   let $result :=
     if (df:class($elem, 'map/topicref'))
        then lmm:resolveMapHandleTopicref($elem, $keyScopes, $logID)
        else lmm:resolveMapCopy($elem, $keyScopes, $logID)
   return $result
};

(:~
 : Copy an element as for xsl:copy instruction.
 :)
declare function lmm:resolveMapCopy(
                    $elem as element(),
                    $keyScopes as array(*),
                    $logID as xs:string) as element()* {
   let $result :=
     element {name($elem)} {
        for $node in ($elem/@*, $elem/node())
            return lmm:resolveMapHandleNode($node, $keyScopes, $logID)
     }
   return $result
};

(:~
 : Handle topicrefs. If the topicref is to local-scope map, then resolve the topicref
 : to the map and include the map in the resolved result, otherwise apply normal
 : copy processing.
 :)
declare function lmm:resolveMapHandleTopicref(
                      $elem as element(),
                      $keyScopes as array(*),
                      $logID as xs:string) as element()* {
   let $result :=
     if ($elem/@format = ('ditamap') and df:getEffectiveScope($elem) = ('local'))
        then lmm:resolveMapHandleMapRef($elem, $keyScopes, $logID)
        else lmm:resolveMapCopy($elem, $keyScopes, $logID)
   return $result
};

(:~
 : Handle local-scope map references.
 :
 : Constructs a <dfst:submap> element that captures the details about the original
 : submap and then applies the identity tranform to the topicref and reltable
 : children of the submap.
 :
 : The dfst:submap element sets the xml:base attribute to the URI of the submap document
 : so that URI references copied from the included map will be correct.
 :
 :)
declare function lmm:resolveMapHandleMapRef(
                        $elem as element(),
                        $keyScopes as array(*),
                        $logID as xs:string) as element()* {
  let $resolutionMap as map(*) := df:resolveTopicRef($elem)
  let $submap := $resolutionMap('target')
  (: FIXME: add resolution messages to log once we get logging infrastructure in place :)
  return
    <dfst:submap
      xml:base="{document-uri(root($submap))}"
      origMapURI="{document-uri(root($submap))}"
      origMapClass="{string($submap/@class)}"
      class="+ map/topicref map-d/topicgroup dfst-d/submap "
    >
      {lmm:constructMergedKeyscopeAtt($elem, $submap)}
     <topicmeta class="- map/topicmeta ">
       <navtitle class="- map/navtitle ">{($submap/*[df:class(., 'topic/title')], string($submap/@title))[1]}</navtitle>
     </topicmeta>
      {for $e in $submap/*[df:class(., 'map/topicref') or df:class(., 'map/reltable')]
          return lmm:resolveMapHandleElement($e, $keyScopes, $logID)
      }
    </dfst:submap>
};

(:~
 : Merge the key scope names from two elements that may both specify @keyscope
 :)
declare function lmm:constructMergedKeyscopeAtt(
                        $elem1 as element(),
                        $elem2 as element()) as attribute()? {
   let $scopeNames as xs:string? := lmm:mergeKeyScopeNames($elem1, $elem2)
   let $result :=
       if ($scopeNames)
          then attribute keyscope {$scopeNames}
          else ()
   return $result
};

(:~
 : Merge the key scope names from two elements that may both specify @keyscope
 :)
declare function lmm:mergeKeyScopeNames($elem1 as element(), $elem2 as element()) as xs:string? {

  let $scopeNames := (tokenize($elem1/@keyscope), tokenize($elem2/@keyscope))
  let $result :=
      if (count($scopeNames) > 0)
         then string-join(distinct-values($scopeNames), " ")
         else ()
  return $result
};

(:~
 : Expand the @keys value to add scope qualifications for all the scopes
 : @return keys attribute node with the expanded values.
 :)
declare function lmm:expandKeyNames($keysAtt as attribute(keys),
                                    $keyScopes as array(*),
                                    $logID as xs:string) as attribute() {

  let $baseKeyNames as xs:string* :=
                    tokenize(string($keysAtt), " ")
  let $expandedKeyNames :=
      for $keyName in $baseKeyNames
          return lmm:scopeQualifyKeyName(
                      $keyName,
                      $keyScopes)
  (: FIXME: use of distinct-values() here is a hack to put off fixing the scope qualification
            logic so it doesn't result in duplicated key names.
   :)
  let $result := attribute {name($keysAtt)} {string-join(distinct-values($expandedKeyNames), ' ')}
  return $result
};

(:~
 : Given a key name and an array of key scope sequences returns the
 : names reflecting the qualification of the base key name using the
 : the scope hierarchy.
 :
 : @param keyName The base key name to be qualified
 : @param keyScopes Array of sequences of scope names, where each array member
 :                  reflects one level in the scope hierarchy, ordered from
 :                  highest (root scope, array index 1), lowest (array index last()).
 : @return List of qualified key names.
 :)
declare function lmm:scopeQualifyKeyName(
                     $keyName as xs:string,
                     $keyScopes as array(*)) as xs:string* {
  (: We process the key scopes from nearest (lowest) to
     farthest (highest) but the scopes come in ordered from
     highest to lowest. Reversing the scope array so that
     the called function can use array:head and array:tail
     on the array.
   :)
  let $result := ($keyName,
                 lmm:applyScopesToNames(
                           ($keyName),
                           array:reverse($keyScopes),
                           ()))
  return $result
};

(:~
 : Apply key scopes to a set of base names to produce
 : a set of scope-qualified names.
 :
 : @param baseNames Sequence of base names to be qualified.
 : @param keyScopes Array of sequences of key scope names.
 : @param qualifiedNames Sequence of qualified names
 :
 : @return Qualified names.
 :)
declare function lmm:applyScopesToNames(
                     $keyNames as xs:string+,
                     $keyScopes as array(*),
                     $qualifiedNames as xs:string*) as xs:string* {
  let $result :=
      if (array:size($keyScopes) = 0)
         then $qualifiedNames
         else
           let $newNames :=
               for $name in $keyNames
                   return for $scopeName in $keyScopes(1)
                          return $scopeName || "." || $name
           return lmm:applyScopesToNames(
                      $newNames,
                      array:tail($keyScopes),
                      ($qualifiedNames, $newNames)
                  )
  return $result
};

(: End of Module :)

(: =====================================================

   DITA Link Manager Model

   Model component of Model/View/Controller. Maintains
   the link management data.

   Provides queries to create and update resource use records,
   dependency indexes, etc.

   These queries are called whenever new content is added to
   given database.

   Author: W. Eliot Kimber

   Copyright (c) 2015 DITA For Small Teams
   Licensed under Apache License 2

   ===================================================== :)

module namespace lmm="http://dita-for-small-teams.org/xquery/modules/linkmgr-model";

declare namespace dfst="http://dita-for-small-teams.org";

import module namespace df="http://dita-for-small-teams.org/xquery/modules/dita-utils";
import module namespace bxutil="http://dita-for-small-teams.org/xquery/modules/basex-utils";
import module namespace lmutil="http://dita-for-small-teams.org/xquery/modules/linkmgmt-utils";
import module namespace dfstcnst="http://dita-for-small-teams.org/xquery/modules/dfst-constants";
import module namespace relpath="http://dita-for-small-teams.org/xquery/modules/relpath-utils";

(:~
 : Create or update the link management indexes for the specified repository
 :
 :)
declare %updating function lmm:updateLinkManagementIndexes(
                               $contentDbName as xs:string,
                               $metadataDbName as xs:string) {
   (: Query the database to find all links and for each link, record a resource use
      record in the database.

      Every addressible element has a "resource key" which combines the URI of
      the containing document and the element's @id value (only elements with
      @id attributes are addressible.

      The link management indexes are stored in a separate database from
      the content database: every branch-specific database has a corresponding
      metadata database that contains the where-used records, resolved maps,
      key space information, and any other metadata you might want to store.

      For each document used there is a directory whose name is the hash
      of the document's URI (which will be unique within a branch). Within
      that directory are the use records for elements within that document.
      This arrangement has the effect of providing a reference count for
      for each document, making it quick to determine if a given document
      is used by any links. It also allows for narrowing the scope of
      where-used queries to just the entries for the document.

    :)

    (: Need to handle both context-free links (direct URI references) and
       context-specific links (key references).

       For context-specific links have to process all the maps and construct
       resolved maps to serve as the resolution context. This could also
       be modeled as updating the key spaces but I think it makes more
       sense to have the maps be the focus, from which key spaces are derived,
       rather than making the key spaces the primary focus.

     :)

    let $logID := 'linkMgmtIndex' (: ID of the log to log messages to :)

    let $directLinks := lmutil:findAllDirectLinks($contentDbName)

    return (
      try {
          db:delete($metadataDbName, $dfstcnst:where-used-dir)
      } catch * {
          (: FIXME: Log to log doc :)
          db:output(<error>Exception deleting where-used directory "{$dfstcnst:where-used-dir}": {$err:description}</error>)
      },

      (: Now create new resource use records for direct links :)

      lmm:createDirectLinkResourceRecords($metadataDbName, $directLinks, $logID),

      (: Now create resolved maps and key space documents for each of the root maps.
       :)
      lmm:constructKeySpaces(
         $contentDbName,
         $metadataDbName,
         $logID),

      (: Now create resource use records for all the indirect links: :)
      lmm:createIndirectLinkResourceRecords(
                  $metadataDbName,
                  lmutil:findAllIndirectLinks($contentDbName),
                  $logID)
    )

};

declare %updating function lmm:createDirectLinkResourceRecords(
        $metadataDbName as xs:string,
        $directLinks as map(*)*,
        $logID as xs:string) {
    for $linkItem in $directLinks
       let $resolveResult := lmutil:resolveDirectLink($linkItem)
       return lmm:createOrUpdateResourceUseRecord(
                   $metadataDbName,
                   $linkItem,
                   $resolveResult,
                   $logID)

};

declare %updating function lmm:createIndirectLinkResourceRecords(
                               $metadataDbName as xs:string,
                               $indirectLinks,
                               $logID as xs:string) {
    for $linkItem in $indirectLinks
        let $resolveResult as map(*) := lmutil:resolveIndirectLink($linkItem)
        return lmm:createOrUpdateResourceUseRecord(
                   $metadataDbName,
                   $linkItem,
                   $resolveResult,
                   $logID)
};

(: Given a link and the resolved result, creates a resource use record for each
   target the link resolved to.

 :)
declare %updating function lmm:createOrUpdateResourceUseRecord(
                              $metadataDbName,
                              $linkItem as map(*),
                              $resolveResult as map(*),
                              $logID as xs:string) {
   let $resolveResult as map(*) := lmutil:resolveDirectLink($linkItem)
   let $targets := $resolveResult('target')
   for $target in $targets
       return lmm:createOrUpdateResourceUseRecordForLinkTarget(
                    $metadataDbName,
                    $linkItem,
                    $target,
                    $logID)
};

(:
 : Creates a use record reflecting the use of the target element by the document
 : that contains the link.
 :
 : The use records are organized into directories, one directory per used document,
 : where the directory name is the MD5 hash of the document URI (just to make a
 : shorter name that won't have any problematic characters).
 :
 : Each use record document's filename is the MD5 hash of the linking document's
 : URI plus the MD5 hash of the linking element.
 :
 : Potential problem: the same element could, in theory, link to the same target in
 : two different ways: as a link and as a conref, although this case is extremely
 : unlikely. Really need a way to distinguish each abstract link represented by
 : a given DITA link-establishing element but the current implementation doesn't
 : provide that abstraction. It could through a refactor. Keeping it simple for now.
 :)
declare %updating function lmm:createOrUpdateResourceUseRecordForLinkTarget(
           $metadataDbName as xs:string,
           $linkItem as map(*),
           $target as element(),
           $logID as xs:string) {
   let $targetDoc := root($target)
   let $link := $linkItem('link')
   let $containingDir := concat($dfstcnst:where-used-dir, '/',
                                lmutil:constructResourceKeyForElement($target),
                                '/')
   let $reskey := lmutil:constructResourceKeyForElement($link)
   let $recordFilename := concat('use-record_', $reskey, '.xml')
   let $format := if ($link/@format)
                     then string($link/@format)
                     else 'dita'
   let $scope := if ($link/@scope)
                     then string($link/@scope)
                     else 'local'
   let $linkContext := $linkItem('linkContext')

   (: df:getTitleForLinkElementContainer($link) :)
   let $useRecord :=
     <dfst:useRecord resourceKey="{$reskey}"
                     targetDoc="{document-uri($targetDoc)}"
                     usingDoc="{document-uri(root($link))}"
                     linkType="{df:getBaseLinkType($link)}"
                     linkClass="{string($link/@class)}"
                     linkContext="{$linkContext}"
                     format="{$format}"
                     scope="{$scope}"
     >
       <title>{if (df:class($link, 'map/topicref'))
                  then string((root($link)/*/*[df:class(., 'topic/title')] |
                        root($link)/*/@title)[1])
                  else string($link/ancestor::*[df:class(., 'topic/topic')][1]/*[df:class(., 'topic/title')])}</title>
     </dfst:useRecord>
    let $useRecordUri := relpath:newFile($containingDir, $recordFilename)
    return try {
       (
       if (db:exists($metadataDbName, $useRecordUri))
          then db:delete($metadataDbName, $useRecordUri)
          else(),
       db:replace($metadataDbName,
                  $useRecordUri,
                  $useRecord),
       (: FIXME: Write record to log doc :)
       db:output(<info>Stored use record "{$useRecordUri}"</info>)
       )
    } catch * {
       (: FIXME: Write record to log doc :)
       db:output(<error>Error storing use record to "{$useRecordUri}": {$err:description}</error>)
    }
};

(:~
 : Construct key spaces from root maps.
 :
 : @param contentDbName Name of datbase that contains the content to construct key spaces from
 : @param metadataDbName Name of metadata database to store construct key spaces in.
 :
 : Finds all the root maps (requires that all direct-link use records are up to date).
 :
 : For each root map, constructs and stores the resolved map.
 :
 :)
declare %updating function lmm:constructKeySpaces(
        $contentDbName as xs:string,
        $metadataDbName as xs:string,
        $logID as xs:string) {

  (: General process:

     1. Find all DITA maps

     2. For each map, construct a resolved map that preserves submap
        boundaries and original source map details.

     The resolved maps can then be used to determine the effective
     binding for any key without the need to separately construct
     key spaces. This technique was pioneered and suggested by
     Chris Nitchie, designer of the DITA 1.3 scoped keys mechanism.

     Note that there's no point in trying to determine if a given
     map is a "root map" as any map can, in theory, be used as a root
     map. Likewise, a map that happens to have no local-scope
     topicrefs to it may not be intended to be the root of a publication.

     One alternative to this approach is to only consider maps that have
     peer-scope topicrefs to them to be root maps (per the DITA 1.3
     meaning for peer-scope topicrefs to dita maps). In this case, there needs
     to be a "master map" that serves to simply identify the "top level
     root maps", that is, the maps that are considered by the document
     authors to be The root maps, that is, the maps they consider to
     be the roots of publications. Given this master map the system can
     then consider only peer-scope map references in order to determine
     the root maps and thus the maps for which key space construction
     is relevant.

   :)

   let $maps := collection($contentDbName)/*[df:class(., 'map/map')][lmutil:isRootMap(.)]
   return for $map in $maps
              return (db:output(<info>Resolving map {document-uri(root($map))}...</info>),
                      lmm:constructResolvedMap(
                                              $map,
                                              $metadataDbName,
                                              $logID)
                     )

};

(:~
 : Construct a resolved map suiteable for doing key resolution. The map
 : is stored in the metadata database.
 :
 : @param map DITA map that is the root of the resolved map.
 : @param metadataDbName Name of the metadata database to store the
 :        resolved map in
 :)
declare %updating function lmm:constructResolvedMap(
                                    $map as element(),
                                    $metadataDbName as xs:string,
                                    $logID as xs:string) {
  let $contentDbName := db:name($map)
  let $resolvedMap := lmm:resolveMap($map, $logID)
  let $resolvedMapURI := lmutil:getResolvedMapURIForMap($map)
  return (db:replace($metadataDbName, $resolvedMapURI, $resolvedMap),
          db:output(<info>Stored resolved map "{$resolvedMapURI}"</info>))

};

(:~
 : Takes a root map and returns the resolved version of the map.
 :
 : The resolution process preserves the original structure and
 : adds <topicgroup> elements to record submap boundaries.
 :
 : The resulting resolved map reflects all directly-referenced submaps.
 : Because the resolved map is used for constructing key spaces it is
 : not useful or necessary to resolve key-based map references as those
 : maps cannot contribute to the final set of effective key spaces.
 :)
declare function lmm:resolveMap(
                     $map as element(),
                     $logID as xs:string) as element() {

    (: Array of sequences of key scope names, lowest to highest
       (e.g., as for ancestor:: axis)
       Each sequence is the set of key scope names defined
       on a given map or topicref, establishing a new set
       of key scopes.

       There is always the root (anonymous) key scope
       bound to the root map. The root map can also
       establish additional scope names.
    :)
    let $keyScopes as array(*) :=
       [ (if ($map/@keyscope)
             then tokenize(string($map/@keyscope), ' ')
             else ()
         )
       ]
    let $resolvedMap :=
          element {name($map)}
            {
              attribute origMapURI { document-uri(root($map)) },
              attribute origMapDB { db:name($map) },
              attribute xml:base { encode-for-uri(document-uri(root($map))) },
              $map/@*,
              for $node in $map/node()
                  return lmm:resolveMapHandleNode($node, $keyScopes, $logID)
            }
    return $resolvedMap
};

(:~
 : Simple identity transform dispatch handler for nodes of any type.
 :)
declare function lmm:resolveMapHandleNode(
                     $node as node(),
                     $keyScopes as array(*),
                     $logID as xs:string) as node()* {
  typeswitch ($node)
    case element() return lmm:resolveMapHandleElement(
                              $node,
                              $keyScopes,
                              $logID)
    case attribute(keys) return lmm:expandKeyNames(
                                    $node,
                                    $keyScopes,
                                    $logID)
    default return $node
};

(:~
 : Apply identity transform to elements of any type.
 :)
declare function lmm:resolveMapHandleElement(
                       $elem as element(),
                       $keyScopes as array(*),
                       $logID as xs:string) as element()* {
   let $result :=
     if (df:class($elem, 'map/topicref'))
        then lmm:resolveMapHandleTopicref($elem, $keyScopes, $logID)
        else lmm:resolveMapCopy($elem, $keyScopes, $logID)
   return $result
};

(:~
 : Copy an element as for xsl:copy instruction.
 :)
declare function lmm:resolveMapCopy(
                    $elem as element(),
                    $keyScopes as array(*),
                    $logID as xs:string) as element()* {
   let $result :=
     element {name($elem)} {
        for $node in ($elem/@*, $elem/node())
            return lmm:resolveMapHandleNode($node, $keyScopes, $logID)
     }
   return $result
};

(:~
 : Handle topicrefs. If the topicref is to local-scope map, then resolve the topicref
 : to the map and include the map in the resolved result, otherwise apply normal
 : copy processing.
 :)
declare function lmm:resolveMapHandleTopicref(
                      $elem as element(),
                      $keyScopes as array(*),
                      $logID as xs:string) as element()* {
   let $result :=
     if ($elem/@format = ('ditamap') and df:getEffectiveScope($elem) = ('local'))
        then lmm:resolveMapHandleMapRef($elem, $keyScopes, $logID)
        else lmm:resolveMapCopy($elem, $keyScopes, $logID)
   return $result
};

(:~
 : Handle local-scope map references.
 :
 : Constructs a <dfst:submap> element that captures the details about the original
 : submap and then applies the identity tranform to the topicref and reltable
 : children of the submap.
 :
 : The dfst:submap element sets the xml:base attribute to the URI of the submap document
 : so that URI references copied from the included map will be correct.
 :
 :)
declare function lmm:resolveMapHandleMapRef(
                        $elem as element(),
                        $keyScopes as array(*),
                        $logID as xs:string) as element()* {
  let $resolutionMap as map(*) := df:resolveTopicRef($elem)
  let $submap := $resolutionMap('target')
  (: FIXME: add resolution messages to log once we get logging infrastructure in place :)
  return
    <dfst:submap
      xml:base="{document-uri(root($submap))}"
      origMapURI="{document-uri(root($submap))}"
      origMapClass="{string($submap/@class)}"
      class="+ map/topicref map-d/topicgroup dfst-d/submap "
    >
      {lmm:constructMergedKeyscopeAtt($elem, $submap)}
     <topicmeta class="- map/topicmeta ">
       <navtitle class="- map/navtitle ">{($submap/*[df:class(., 'topic/title')], string($submap/@title))[1]}</navtitle>
     </topicmeta>
      {for $e in $submap/*[df:class(., 'map/topicref') or df:class(., 'map/reltable')]
          return lmm:resolveMapHandleElement($e, $keyScopes, $logID)
      }
    </dfst:submap>
};

(:~
 : Merge the key scope names from two elements that may both specify @keyscope
 :)
declare function lmm:constructMergedKeyscopeAtt(
                        $elem1 as element(),
                        $elem2 as element()) as attribute()? {
   let $scopeNames as xs:string? := lmm:mergeKeyScopeNames($elem1, $elem2)
   let $result :=
       if ($scopeNames)
          then attribute keyscope {$scopeNames}
          else ()
   return $result
};

(:~
 : Merge the key scope names from two elements that may both specify @keyscope
 :)
declare function lmm:mergeKeyScopeNames($elem1 as element(), $elem2 as element()) as xs:string? {

  let $scopeNames := (tokenize($elem1/@keyscope), tokenize($elem2/@keyscope))
  let $result :=
      if (count($scopeNames) > 0)
         then string-join(distinct-values($scopeNames), " ")
         else ()
  return $result
};

(:~
 : Expand the @keys value to add scope qualifications for all the scopes
 : @return keys attribute node with the expanded values.
 :)
declare function lmm:expandKeyNames($keysAtt as attribute(keys),
                                    $keyScopes as array(*),
                                    $logID as xs:string) as attribute() {

  let $baseKeyNames as xs:string* :=
                    tokenize(string($keysAtt), " ")
  let $expandedKeyNames :=
      for $keyName in $baseKeyNames
          return lmm:scopeQualifyKeyName(
                      $keyName,
                      $keyScopes,
                      ($keyName))
  (: FIXME: use of distinct-values() here is a hack to put off fixing the scope qualification
            logic so it doesn't result in duplicated key names.
   :)
  let $result := attribute {name($keysAtt)} {string-join(distinct-values($expandedKeyNames), ' ')}
  return $result
};

(:~
 : Recursive function to construct list of scope-qualified key names for a given
 : base key.
 :
 : Takes the unqualified key name and adds to it the scope names from the end
 : of the keyScopes array. Adds the result to the incoming list of qualified
 : key names and then calls itself against the result and the cadr of the
 : key scope array.
 :)
declare function lmm:scopeQualifyKeyName(
                     $keyName as xs:string,
                     $keyScopes as array(*),
                     $qualfiedKeyNames as xs:string*) as xs:string* {
  let $result :=
    if (array:size($keyScopes) = 0)
       then $qualfiedKeyNames
       else
          let $scopeNames as xs:string* := $keyScopes?1  (: "?" is the array lookup operator :)
          let $newNames as xs:string* :=
              for $scopeName in $scopeNames
                  return $scopeName || '.' || $keyName
          return
            for $newName in $newNames
                 return lmm:scopeQualifyKeyName(
                           $newName,
                           if (array:size($keyScopes) = 1)
                              then array{}
                              else $keyScopes?(2 to array:size($keyScopes)),
                           ($qualfiedKeyNames, $newNames)
                           )
  return $result
};

(: End of Module :)