fixed href for testing careServiceRequest

module namespace page = 'http://basex.org/modules/web-page';
import module namespace csd = "urn:ihe:iti:csd:2013" at "../repo/csd_base_library.xqm";
import module namespace csr_proc = "https://github.com/his-interop/openinfoman/csr_proc" at "../repo/csr_processor.xqm";
import module namespace request = "http://exquery.org/ns/request";

import module namespace csd_dm = "https://github.com/his-interop/openinfoman/csd_dm" at "../repo/csd_document_manager.xqm";
declare variable $page:db := 'provider_directory';
declare variable $page:test_docs :=  file:list("../test_docs/",boolean('false'),"*.xml");

declare
  %output:method("xhtml")
  %rest:path("/CSD/test/{$name}")
  %rest:GET
function page:show_test_menu($name) {
  page:wrapper(page:test_menu($name))
};

declare
  %rest:path("/CSD/test/{$name}/{$test}")
  %rest:GET
  function page:test($name, $test as xs:string)
{
let $test_doc := page:get_test_doc($test)
return if ($test_doc) then
  csr_proc:process_CSR($test_doc/csd:careServicesRequest,csd_dm:open_document($page:db,$name))
else
   (:need better error handling:)

Shame on you

};

declare
  %rest:path("/CSD/test_source/{$test}")
  %rest:GET
  %output:method("xml")
  function page:test_source(
    $test as xs:string)
{
let $test_doc := page:get_test_doc($test)
return if ($test_doc) then
 $test_doc
else

Shame on you

};

declare function page:get_test_doc($test) {
let $dir_base := "../test_docs/"
let $dir := file:resolve-path($dir_base)
let $file := file:resolve-path(concat($dir_base,$test,".xml"))
return if (starts-with($file,$dir) and file:exists($file))
then
  doc($file)
else
  ()
};

declare
  %rest:path("/CSD/test")
  %rest:GET
  %output:method("xhtml")
  function page:list_tests()
{
let $response:=

Registered Documents

  {
    for $name in csd_dm:registered_documents($page:db)
    return

Tests for {$name}
{page:test_menu($name)}

  }

return page:wrapper($response)
};

declare function page:test_menu($name)
{

  To test submission on your machine you can do:

  curl --form "fileupload=@test.xml" {request:scheme()}://{request:hostname()}:{request:port()}/CSD/csr/{$name}/careServicesRequest

  where test.xml is one of the downloaded source documents below

    {for $test_doc in $page:test_docs
    order by $test_doc
    let $test := file:base-name($test_doc,".xml")
    return

    {$test}: process on server  /
     download source

    }

};

declare function page:wrapper($response) {

    <link href="{request:scheme()}://{request:hostname()}:{request:port()}/static/bootstrap/css/bootstrap.css" rel="stylesheet"/>
    <link href="{request:scheme()}://{request:hostname()}:{request:port()}/static/bootstrap/css/bootstrap-theme.css" rel="stylesheet"/>


added  as external var in modtimes search

declare namespace csd = 'urn:ihe:iti:csd:2013';
declare variable $careServicesRequest as item() external;
let $ptime := max( for $dt in /csd:CSD/csd:providerDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $otime := max( for $dt in /csd:CSD/csd:organizationDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $ftime := max( for $dt in /csd:CSD/csd:facilityDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $stime := max( for $dt in /csd:CSD/csd:serviceDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $time := max (($ptime,$otime,$ftime,$stime))
return
  <csd:CSD updated="{$time}">
    <csd:providerDirectory updated="{$ptime}"/>
    <csd:facilityDirectory updated="{$ftime}"/>
    <csd:organizationDirectory updated="{$otime}"/>
    <csd:serviceDirectory updated="{$stime}"/>
  </csd:CSD>

declare namespace csd = 'urn:ihe:iti:csd:2013';

let $ptime := max( for $dt in /csd:CSD/csd:providerDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $otime := max( for $dt in /csd:CSD/csd:organizationDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $ftime := max( for $dt in /csd:CSD/csd:facilityDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $stime := max( for $dt in /csd:CSD/csd:serviceDirectory/csd:provider/csd:record/@updated return xs:dateTime($dt))
let $time := max (($ptime,$otime,$ftime,$stime))
return
  <csd:CSD updated="{$time}">
    <csd:providerDirectory updated="{$ptime}"/>
    <csd:facilityDirectory updated="{$ftime}"/>
    <csd:organizationDirectory updated="{$otime}"/>
    <csd:serviceDirectory updated="{$stime}"/>
  </csd:CSD>


Key space URI construction corrected

Key space URI construction corrected

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

   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";

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 as map(*)*,
                               $logID as xs:string) {
    for $linkItem in $indirectLinks
        let $resolveResult as map(*) := lmutil:resolveIndirectLink($metadataDbName, $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 :)
       error('LMI-UpdateUseRecord001',
             concat('Error storing use record to "', $useRecordUri, '": ', $err:description))
    }
};

(:~
 : 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.

     Key spaces are constructed from resolved maps and stored in the metadata
     database.

   :)

   let $contentMaps := collection($contentDbName)/*[df:class(., 'map/map')][lmutil:isRootMap(.)]
   (: Sequence of maps containing the things to be stored :)
   let $dataToStore as map(*)* := lmm:constructKeySpacesToBeStored($contentMaps)
   return
      for $obj in $dataToStore
            let $ditaMap := $obj('resolvedMapMap')('map')
            let $resolvedMapMap := $obj('resolvedMapMap')
            let $keySpace as element(keyspace) := $obj('keySpace')
            return ((: FIXME: Write to log. db:output(<info>Resolving map {document-uri(root($ditaMap))}...</info>), :)
                    lmm:storeResolvedMap($resolvedMapMap,
                                         $metadataDbName,
                                         $obj('log'),
                                         $logID),
                    lmm:storeKeySpace(
                                         $keySpace,
                                         $metadataDbName,
                                         $logID)
                     )
};

(:~
 : Constructs the XML key space documents to be stored for each content map.
 :
 : @param contentMaps Sequence of content (unresolved) map elements.
 : @return Sequence of maps with two items:
 :
 : 'resolvedMapMap' : The resolved map data map. Includes the content map
 :                    as an item.
 : 'keySpace' : The XML representation of the key space for the map
 :)
declare function lmm:constructKeySpacesToBeStored($contentMaps as element()*) as map(*)* {
  let $result :=
    for $ditaMap in $contentMaps
        let $resolvedMapMap as map(*) := lmm:resolveMap($ditaMap)
        let $dataMap := lmm:constructKeySpaceForResolvedMap($resolvedMapMap)
        return $dataMap
  return $result
};

(:~
 : Store a keyspace document in the metadata database.
 :
 : @param keySpace XML document representing the key space to be stored
 : @param metadataDbName Name of the metadata database to store the
 :        resolved map in
 : @logID ID of the log to write the messages to.
 :)
declare %updating function lmm:storeKeySpace(
                     $keySpace as element(keyspace),
                     $metadataDbName as xs:string,
                     $logID as xs:string) {
  let $keySpaceURI := lmutil:getKeySpaceURIForKeySpace($keySpace)
 (: FIXME: This is a hack. The map URI as stored and used for
            resource IDs must include the database name, but
            for db:replace the path part must not include the
            database name.

            Probably better to capture the path and metadata
            name separately in the dataMap but that's a more
            involved refactor.
   :)
  let $uriPath := substring-after($keySpaceURI, $metadataDbName)
  return (db:replace($metadataDbName, $uriPath, $keySpace) (: ,
          FIXME: Write to log doc.
          db:output((<info>Stored key space "{$keySpaceURI}"</info>)):)
          )
};

(:~
 : Given a resolved DITA map, construct the key space map
 : for it.
 :
 : The key space map reflects the hierarchy of key spaces defined
 : in the map. Every root map represents at least one, possibly
 : empty, key space.
 :
 : @param map Source content map (returned in the result map)
 : @param resolvedMap Resolved map constructed from the source map
 : @return Map containing the members:
 :
 :   map : The content map
 :   resolvedMap : The resolved map the key spaces were constructed from
 :   keySpace : The key space document rooted at the resolved map.
 :)
declare function lmm:constructKeySpaceForResolvedMap(
                       $resolvedMapMap as map(*)) as map(*) {
   let $ditaMap as element() := $resolvedMapMap('map')
   let $keyspace as element(keyspace) :=
         lmm:constructKeySpace(
                 $resolvedMapMap,
                 $resolvedMapMap('resolvedMap'))

   let $result :=
      map{ 'map' : $ditaMap,
           'resolvedMapMap' : $resolvedMapMap,
           'keySpace' : $keyspace
         }
   return $result
};

(:~
 : Given a content map element, return the corresponding keyspace document.
 : The key space must have already been constructed.
 :)
declare function lmm:getKeySpaceForMap($contentMap as element()) as element()? {
    let $resolvedMapURI := lmutil:getResolvedMapURIForMap($contentMap)
    let $metadataDbName as xs:string := bxutil:getMetadataDbNameForDoc(root($contentMap))

    let $keyspaceURI := lmutil:getKeySpaceURIForResolvedMapURI($resolvedMapURI)
    let $result := collection($keyspaceURI)/keyspace
    return $result
};

(:~
 : The identity of a key space is the element that defines it, either map
 : or a topicref. In order to resolve a key you must know the key space
 : hierarchy because you have to start with the root key space, see if
 : the key-as-referenced is defined in that space, then if not, walk
 : down the ancestor tree until you either find a match for the key
 : name or run out of options.
 :
 : Logically, an ancestor key space reflects in itself all the scope-qualified
 : names of the keys from its descentant key space that it does not
 : explicitly override, meaning that, starting from any descedant key space you
 : can resolve any fully-qualified key name.
 :
 : @param resolvedMapMap Data map that represents the resolved map.
 : @param spaceDefiner Element, map or topicref, that defines a new key space.
 :
 : @return Keyspace element, which will contain any descendant keyspaces
 :)
declare function lmm:constructKeySpace(
                      $resolvedMapMap as map(*),
                      $spaceDefiner as element()) as element() {
  let $keyspaceMap := lmm:constructKeySpaceMap($resolvedMapMap, $spaceDefiner, ())
  let $result := lmm:keySpaceMapToXML($keyspaceMap)
  return $result
};

(:~
 : Given a key space map, return an XML serialization of it suitalbe for storing
 : in the XML metadata store
 :)
declare function lmm:keySpaceMapToXML($keyspaceMap as map(*)) as element(keyspace) {

let $definer as element() := $keyspaceMap('definer')
let $scopeNames as xs:string* := $keyspaceMap('scopeNames')
let $keydefs as map(*)? := $keyspaceMap('keydefs')
let $childScopes as map(*)* := $keyspaceMap('childScopes')
let $resolvedMapMap as map(*) := $keyspaceMap('resolvedMapMap')
let $ditaMap := $resolvedMapMap('map')
let $contentMapDocURI := document-uri(root($ditaMap))
let $resolvedMapURI := $resolvedMapMap?resolvedMapURI

(: NOTE: The definer ID has to be with respect to the resolve map,
         not the content map, as the same content element could
         occur multiple times in the resolved map, e.g., if the
         same submap is included multiple times or as a result
         of branch filtering.

   NOTE: Have to specify the resolved map URI explicitly here
         because the definer element hasn't actually been
         writen to the repo so it doesn't have a document
         URI yet.
:)
let $definerID := lmutil:constructResourceKeyForElement($resolvedMapURI, $definer)

let $result :=
<keyspace
  resolvedMap="{$resolvedMapMap('resolvedMapURI')}"
  contentMap="{document-uri(root($ditaMap))}"
  spaceDefiner="{$definerID}"
>{
 <scopeNames>{
   for $name in $scopeNames
       return <scopeName>{$name}</scopeName>
 }</scopeNames>,
 <keys>{
 for $keyName in map:keys($keydefs) order by $keyName
     return <key name="{$keyName}">{
               for $keydef in $keydefs($keyName)
                   let $keydefResID := lmutil:constructResourceKeyForElement($keydef)
                   return element {name($keydef)} {
                     $keydef/@*,
                     $keydef/node()
                   }
             }</key>
 }</keys>,
 <childScopes>{
 for $childScope in $childScopes
     return lmm:keySpaceMapToXML($childScope)
 }</childScopes>
}</keyspace>

return $result

};

(:~
 : Construct map object that reflects the key space rooted at the specified
 : key space defining element.
 :
 : The map is a map of key names to key definitions, where a given key name
 : may reflect multiple definitions. The key definitions are in precedence
 : order from highest to lowest.
 :
 : @param resolvedMapMap Data map representing the resolved map
 : @param spaceDefiner Element, map or topicref, that defines a new key space.
 : @parentSpace The parent key space (enables walking up the key space ancestry)
 : @return map object with the following members:
 :
 : 'scopeNames' : A sequence, possibly empty, of the scope names for the scope.
 :                (Only the root scope can have no name).
 : 'keydefs'     : Sequence of key-to-definition maps for each of the keys
 :                 defined in the key space or its descendant key spaces.
 : 'childSpaces' : Sequence of maps objects representing the child key spaces
 :                 of this key space.
 : 'parentSpace' : The parent key space's map. The root space has no parent.
 : 'resolvedMapMap' : The resolved map data map.
 :)
declare function lmm:constructKeySpaceMap(
                         $resolvedMapMap as map(*),
                         $spaceDefiner as element(),
                         $parentSpace as map(*)?
                        ) as map(*) {

   (: Walk the element tree, collecting key definitions and child key
      key spaces as we go.

      Result is the key space map.
      :)
   let $initialKeySpaceMap :=
           map{'definer' : $spaceDefiner,
               'scopeNames' : if ($spaceDefiner/@keyscopes)
                                 then tokenize($spaceDefiner/@keyscopes, ' ')
                                 else (),
               'parentSpace' : $parentSpace,
               'keydefs' : map {},
               'resolvedMapMap' : $resolvedMapMap
              }
   let $thisScopeKeyDefs :=
       $spaceDefiner//descendant-or-self::*[df:class(., 'map/topicref')][@keys]

   let $result := lmm:addKeyDefs($initialKeySpaceMap, $thisScopeKeyDefs)
   return $result
};

(:~
 : Add a set of key definitions to a key space, returning the updated
 : key space map.
 :
 : Calls itself recursively to process the list of elements.
 :
 : @param keySpace Key space map to add the key definitions to
 : @param elems Key-defining elements
 : @return New key space map with the key definitions added.
 :)
declare function lmm:addKeyDefs($keySpace, $elems as element()*) as map(*) {

let $result :=
    if (count($elems) = 0)
       then $keySpace
       else
         let $newKeySpace := lmm:addKeyDef($keySpace, $elems[1])
         return lmm:addKeyDefs($newKeySpace, tail($elems))

return $result
};

(:~
 : Adds a key-defining element's definition to the key space if
 : it actually defines a key (this function can be called on any
 : topicref)
 :
 : @param keySpace The key space to add the definition to
 : @param elem The key-defining element to add
 : @return Updated key space map.
 :)
declare function lmm:addKeyDef($keySpace, $elem as element()) as map(*) {
  let $keyDefs := $keySpace('keydefs') (: Map of key name to sequence of key defs :)

  let $keyNames := tokenize($elem/@keys, ' ')
  let $newKeyDefs :=
     map:merge((
       $keyDefs,
       for $keyName in $keyNames
           let $existingDefsForKey :=
                if (exists($keyDefs))
                   then $keyDefs($keyName)
                   else ()

           return map{$keyName :
                      ($existingDefsForKey, $elem)
                     }))
  let $result := map:put($keySpace, 'keydefs', $newKeyDefs)
  return $result
};

(:~
 : Store a resolved map in the metadata database.
 :
 : @param map DITA map that is the root of the resolved map.
 : @param resolvedMap The resolved map constructed from the content map
 : @param metadataDbName Name of the metadata database to store the
 :        resolved map in
 : @log Sequence, possibly empty, of log message elements.
 : @logID ID of the log to write the messages to.
 :)
declare %updating function lmm:storeResolvedMap(
                                    $dataMap as map(*),
                                    $metadataDbName as xs:string,
                                    $log as element()*,
                                    $logID as xs:string) {
  let $map := $dataMap('map')
  let $resolvedMap := $dataMap('resolvedMap')
  let $log := $dataMap('log')
  let $resolvedMapURI := $dataMap('resolvedMapURI')
  (: FIXME: This is a hack. The map URI as stored and used for
            resource IDs must include the database name, but
            for db:replace the path part must not include the
            database name.

            Probably better to capture the path and metadata
            name separately in the dataMap but that's a more
            involved refactor.
   :)
  let $uriPath := substring-after($resolvedMapURI, $metadataDbName)
  return (db:replace($metadataDbName, $uriPath, $resolvedMap) (:,
          FIXME: Write to log
          db:output(($log, <info>Stored resolved map "{$resolvedMapURI}"</info>)) :)
          )

};

(:~
 : Takes a root map and returns the resolved map within a map
 : containing the original map, resolved map, and any log messages:
 :
 : @param map Content map to be resolved.
 :
 : @return Map with the following members:
 :
 : map : The input map
 : resolvedMap : The resolved map
 : resolvedMapURI: The URI of the resolved map. This is the URI the map
 : will be stored at (but may not yet be stored at due to order of update
 : operations)
 : log : Sequence, possibly empty, of log entry elements.
 :
 : 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()) as map(*) {

    let $resolvedMapURI := lmutil:getResolvedMapURIForMap($map)

    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))) },
              attribute resID {lmutil:constructResourceKeyForElement($resolvedMapURI, $map)},
              $map/@*,
              for $node in $map/node()
                  return lmm:resolveMapHandleNode($node)
            }
    let $result :=
       map{
           'map' : $map,
           'resolvedMap' : $resolvedMap,
           'resolvedMapURI' : $resolvedMapURI,
           'log' : ()
          }
    return $result
};

(:~
 : Simple identity transform dispatch handler for nodes of any type.
 :)
declare function lmm:resolveMapHandleNode(
                     $node as node()) as node()* {
  typeswitch ($node)
    case element() return lmm:resolveMapHandleElement(
                              $node)
    default return $node
};

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

(:~
 : Copy an element as for xsl:copy instruction.
 :)
declare function lmm:resolveMapCopy(
                    $elem as element()) as element()* {
   let $result :=
     element {name($elem)} {
        if (df:class($elem, 'map/topicref'))
           then attribute contentResID {lmutil:constructResourceKeyForElement($elem)}
           else (),
        for $node in ($elem/@*, $elem/node())
            return lmm:resolveMapHandleNode($node)
     }
   return $result
};

(:~
 : Handle topicrefs. If the topicref is to a 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()) as element()* {
   let $result :=
     if ($elem/@format = ('ditamap') and df:getEffectiveScope($elem) = ('local'))
        then lmm:resolveMapHandleMapRef($elem)
        else lmm:resolveMapCopy($elem)
   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()) 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)
      }
    </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(*)) as attribute() {

  let $baseKeyNames as xs:string* :=
                    tokenize(string($keysAtt), " ")
  let $expandedKeyNames :=
      for $keyName in $baseKeyNames
          return lmm:scopeQualifyKeyName(
                      $keyName,
                      $keyScopes)
  let $result := attribute {name($keysAtt)}
                {string-join($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";

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 as map(*)*,
                               $logID as xs:string) {
    for $linkItem in $indirectLinks
        let $resolveResult as map(*) := lmutil:resolveIndirectLink($metadataDbName, $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 :)
       error('LMI-UpdateUseRecord001',
             concat('Error storing use record to "', $useRecordUri, '": ', $err:description))
    }
};

(:~
 : 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.

     Key spaces are constructed from resolved maps and stored in the metadata
     database.

   :)

   let $contentMaps := collection($contentDbName)/*[df:class(., 'map/map')][lmutil:isRootMap(.)]
   (: Sequence of maps containing the things to be stored :)
   let $dataToStore as map(*)* := lmm:constructKeySpacesToBeStored($contentMaps)
   return
      for $obj in $dataToStore
            let $ditaMap := $obj('resolvedMapMap')('map')
            let $resolvedMapMap := $obj('resolvedMapMap')
            let $keySpace as element(keyspace) := $obj('keySpace')
            return ((: FIXME: Write to log. db:output(<info>Resolving map {document-uri(root($ditaMap))}...</info>), :)
                    lmm:storeResolvedMap($resolvedMapMap,
                                         $metadataDbName,
                                         $obj('log'),
                                         $logID),
                    lmm:storeKeySpace(
                                         $keySpace,
                                         $metadataDbName,
                                         $logID)
                     )
};

(:~
 : Constructs the XML key space documents to be stored for each content map.
 :
 : @param contentMaps Sequence of content (unresolved) map elements.
 : @return Sequence of maps with two items:
 :
 : 'resolvedMapMap' : The resolved map data map. Includes the content map
 :                    as an item.
 : 'keySpace' : The XML representation of the key space for the map
 :)
declare function lmm:constructKeySpacesToBeStored($contentMaps as element()*) as map(*)* {
  let $result :=
    for $ditaMap in $contentMaps
        let $resolvedMapMap as map(*) := lmm:resolveMap($ditaMap)
        let $dataMap := lmm:constructKeySpaceForResolvedMap($resolvedMapMap)
        return $dataMap
  return $result
};

(:~
 : Store a keyspace document in the metadata database.
 :
 : @param keySpace XML document representing the key space to be stored
 : @param metadataDbName Name of the metadata database to store the
 :        resolved map in
 : @logID ID of the log to write the messages to.
 :)
declare %updating function lmm:storeKeySpace(
                     $keySpace as element(keyspace),
                     $metadataDbName as xs:string,
                     $logID as xs:string) {
  let $keySpaceURI := lmutil:getKeySpaceURIForKeySpace($keySpace)
  return (db:replace($metadataDbName, $keySpaceURI, $keySpace) (: ,
          FIXME: Write to log doc.
          db:output((<info>Stored key space "{$keySpaceURI}"</info>)):)
          )
};

(:~
 : Given a resolved DITA map, construct the key space map
 : for it.
 :
 : The key space map reflects the hierarchy of key spaces defined
 : in the map. Every root map represents at least one, possibly
 : empty, key space.
 :
 : @param map Source content map (returned in the result map)
 : @param resolvedMap Resolved map constructed from the source map
 : @return Map containing the members:
 :
 :   map : The content map
 :   resolvedMap : The resolved map the key spaces were constructed from
 :   keySpace : The key space document rooted at the resolved map.
 :)
declare function lmm:constructKeySpaceForResolvedMap(
                       $resolvedMapMap as map(*)) as map(*) {
   let $ditaMap as element() := $resolvedMapMap('map')
   let $keyspace as element(keyspace) :=
         lmm:constructKeySpace(
                 $resolvedMapMap,
                 $resolvedMapMap('resolvedMap'))

   let $result :=
      map{ 'map' : $ditaMap,
           'resolvedMapMap' : $resolvedMapMap,
           'keySpace' : $keyspace
         }
   return $result
};

(:~
 : Given a content map element, return the corresponding keyspace document.
 : The key space must have already been constructed.
 :)
declare function lmm:getKeySpaceForMap($contentMap as element()) as element()? {
    let $resolvedMapURI := lmutil:getResolvedMapURIForMap($contentMap)
    let $metadataDbName as xs:string := bxutil:getMetadataDbNameForDoc(root($contentMap))

    let $keyspaceURI := lmutil:getKeySpaceURIForResolvedMapURI($resolvedMapURI)
    let $result := collection($keyspaceURI)/keyspace
    return $result
};

(:~
 : The identity of a key space is the element that defines it, either map
 : or a topicref. In order to resolve a key you must know the key space
 : hierarchy because you have to start with the root key space, see if
 : the key-as-referenced is defined in that space, then if not, walk
 : down the ancestor tree until you either find a match for the key
 : name or run out of options.
 :
 : Logically, an ancestor key space reflects in itself all the scope-qualified
 : names of the keys from its descentant key space that it does not
 : explicitly override, meaning that, starting from any descedant key space you
 : can resolve any fully-qualified key name.
 :
 : @param resolvedMapMap Data map that represents the resolved map.
 : @param spaceDefiner Element, map or topicref, that defines a new key space.
 :
 : @return Keyspace element, which will contain any descendant keyspaces
 :)
declare function lmm:constructKeySpace(
                      $resolvedMapMap as map(*),
                      $spaceDefiner as element()) as element() {
  let $keyspaceMap := lmm:constructKeySpaceMap($resolvedMapMap, $spaceDefiner, ())
  let $result := lmm:keySpaceMapToXML($keyspaceMap)
  return $result
};

(:~
 : Given a key space map, return an XML serialization of it suitalbe for storing
 : in the XML metadata store
 :)
declare function lmm:keySpaceMapToXML($keyspaceMap as map(*)) as element(keyspace) {

let $definer as element() := $keyspaceMap('definer')
let $scopeNames as xs:string* := $keyspaceMap('scopeNames')
let $keydefs as map(*)? := $keyspaceMap('keydefs')
let $childScopes as map(*)* := $keyspaceMap('childScopes')
let $resolvedMapMap as map(*) := $keyspaceMap('resolvedMapMap')
let $ditaMap := $resolvedMapMap('map')
let $contentMapDocURI := document-uri(root($ditaMap))
let $resolvedMapURI := $resolvedMapMap?resolvedMapURI

(: NOTE: The definer ID has to be with respect to the resolve map,
         not the content map, as the same content element could
         occur multiple times in the resolved map, e.g., if the
         same submap is included multiple times or as a result
         of branch filtering.

   NOTE: Have to specify the resolved map URI explicitly here
         because the definer element hasn't actually been
         writen to the repo so it doesn't have a document
         URI yet.
:)
let $definerID := lmutil:constructResourceKeyForElement($resolvedMapURI, $definer)

let $result :=
<keyspace
  resolvedMap="{$resolvedMapMap('resolvedMapURI')}"
  contentMap="{document-uri(root($ditaMap))}"
  spaceDefiner="{$definerID}"
>{
 <scopeNames>{
   for $name in $scopeNames
       return <scopeName>{$name}</scopeName>
 }</scopeNames>,
 <keys>{
 for $keyName in map:keys($keydefs) order by $keyName
     return <key name="{$keyName}">{
               for $keydef in $keydefs($keyName)
                   let $keydefResID := lmutil:constructResourceKeyForElement($keydef)
                   return element {name($keydef)} {
                     $keydef/@*,
                     $keydef/node()
                   }
             }</key>
 }</keys>,
 <childScopes>{
 for $childScope in $childScopes
     return lmm:keySpaceMapToXML($childScope)
 }</childScopes>
}</keyspace>

return $result

};

(:~
 : Construct map object that reflects the key space rooted at the specified
 : key space defining element.
 :
 : The map is a map of key names to key definitions, where a given key name
 : may reflect multiple definitions. The key definitions are in precedence
 : order from highest to lowest.
 :
 : @param resolvedMapMap Data map representing the resolved map
 : @param spaceDefiner Element, map or topicref, that defines a new key space.
 : @parentSpace The parent key space (enables walking up the key space ancestry)
 : @return map object with the following members:
 :
 : 'scopeNames' : A sequence, possibly empty, of the scope names for the scope.
 :                (Only the root scope can have no name).
 : 'keydefs'     : Sequence of key-to-definition maps for each of the keys
 :                 defined in the key space or its descendant key spaces.
 : 'childSpaces' : Sequence of maps objects representing the child key spaces
 :                 of this key space.
 : 'parentSpace' : The parent key space's map. The root space has no parent.
 : 'resolvedMapMap' : The resolved map data map.
 :)
declare function lmm:constructKeySpaceMap(
                         $resolvedMapMap as map(*),
                         $spaceDefiner as element(),
                         $parentSpace as map(*)?
                        ) as map(*) {

   (: Walk the element tree, collecting key definitions and child key
      key spaces as we go.

      Result is the key space map.
      :)
   let $initialKeySpaceMap :=
           map{'definer' : $spaceDefiner,
               'scopeNames' : if ($spaceDefiner/@keyscopes)
                                 then tokenize($spaceDefiner/@keyscopes, ' ')
                                 else (),
               'parentSpace' : $parentSpace,
               'keydefs' : map {},
               'resolvedMapMap' : $resolvedMapMap
              }
   let $thisScopeKeyDefs :=
       $spaceDefiner//descendant-or-self::*[df:class(., 'map/topicref')][@keys]

   let $result := lmm:addKeyDefs($initialKeySpaceMap, $thisScopeKeyDefs)
   return $result
};

(:~
 : Add a set of key definitions to a key space, returning the updated
 : key space map.
 :
 : Calls itself recursively to process the list of elements.
 :
 : @param keySpace Key space map to add the key definitions to
 : @param elems Key-defining elements
 : @return New key space map with the key definitions added.
 :)
declare function lmm:addKeyDefs($keySpace, $elems as element()*) as map(*) {

let $result :=
    if (count($elems) = 0)
       then $keySpace
       else
         let $newKeySpace := lmm:addKeyDef($keySpace, $elems[1])
         return lmm:addKeyDefs($newKeySpace, tail($elems))

return $result
};

(:~
 : Adds a key-defining element's definition to the key space if
 : it actually defines a key (this function can be called on any
 : topicref)
 :
 : @param keySpace The key space to add the definition to
 : @param elem The key-defining element to add
 : @return Updated key space map.
 :)
declare function lmm:addKeyDef($keySpace, $elem as element()) as map(*) {
  let $keyDefs := $keySpace('keydefs') (: Map of key name to sequence of key defs :)

  let $keyNames := tokenize($elem/@keys, ' ')
  let $newKeyDefs :=
     map:merge((
       $keyDefs,
       for $keyName in $keyNames
           let $existingDefsForKey :=
                if (exists($keyDefs))
                   then $keyDefs($keyName)
                   else ()

           return map{$keyName :
                      ($existingDefsForKey, $elem)
                     }))
  let $result := map:put($keySpace, 'keydefs', $newKeyDefs)
  return $result
};

(:~
 : Store a resolved map in the metadata database.
 :
 : @param map DITA map that is the root of the resolved map.
 : @param resolvedMap The resolved map constructed from the content map
 : @param metadataDbName Name of the metadata database to store the
 :        resolved map in
 : @log Sequence, possibly empty, of log message elements.
 : @logID ID of the log to write the messages to.
 :)
declare %updating function lmm:storeResolvedMap(
                                    $dataMap as map(*),
                                    $metadataDbName as xs:string,
                                    $log as element()*,
                                    $logID as xs:string) {
  let $map := $dataMap('map')
  let $resolvedMap := $dataMap('resolvedMap')
  let $log := $dataMap('log')
  let $resolvedMapURI := $dataMap('resolvedMapURI')
  (: FIXME: This is a hack. The map URI as stored and used for
            resource IDs must include the database name, but
            for db:replace the path part must not include the
            database name.

            Probably better to capture the path and metadata
            name separately in the dataMap but that's a more
            involved refactor.
   :)
  let $uriPath := substring-after($resolvedMapURI, $metadataDbName)
  return (db:replace($metadataDbName, $uriPath, $resolvedMap) (:,
          FIXME: Write to log
          db:output(($log, <info>Stored resolved map "{$resolvedMapURI}"</info>)) :)
          )

};

(:~
 : Takes a root map and returns the resolved map within a map
 : containing the original map, resolved map, and any log messages:
 :
 : @param map Content map to be resolved.
 :
 : @return Map with the following members:
 :
 : map : The input map
 : resolvedMap : The resolved map
 : resolvedMapURI: The URI of the resolved map. This is the URI the map
 : will be stored at (but may not yet be stored at due to order of update
 : operations)
 : log : Sequence, possibly empty, of log entry elements.
 :
 : 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()) as map(*) {

    let $resolvedMapURI := lmutil:getResolvedMapURIForMap($map)

    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))) },
              attribute resID {lmutil:constructResourceKeyForElement($resolvedMapURI, $map)},
              $map/@*,
              for $node in $map/node()
                  return lmm:resolveMapHandleNode($node)
            }
    let $result :=
       map{
           'map' : $map,
           'resolvedMap' : $resolvedMap,
           'resolvedMapURI' : $resolvedMapURI,
           'log' : ()
          }
    return $result
};

(:~
 : Simple identity transform dispatch handler for nodes of any type.
 :)
declare function lmm:resolveMapHandleNode(
                     $node as node()) as node()* {
  typeswitch ($node)
    case element() return lmm:resolveMapHandleElement(
                              $node)
    default return $node
};

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

(:~
 : Copy an element as for xsl:copy instruction.
 :)
declare function lmm:resolveMapCopy(
                    $elem as element()) as element()* {
   let $result :=
     element {name($elem)} {
        if (df:class($elem, 'map/topicref'))
           then attribute contentResID {lmutil:constructResourceKeyForElement($elem)}
           else (),
        for $node in ($elem/@*, $elem/node())
            return lmm:resolveMapHandleNode($node)
     }
   return $result
};

(:~
 : Handle topicrefs. If the topicref is to a 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()) as element()* {
   let $result :=
     if ($elem/@format = ('ditamap') and df:getEffectiveScope($elem) = ('local'))
        then lmm:resolveMapHandleMapRef($elem)
        else lmm:resolveMapCopy($elem)
   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()) 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)
      }
    </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(*)) as attribute() {

  let $baseKeyNames as xs:string* :=
                    tokenize(string($keysAtt), " ")
  let $expandedKeyNames :=
      for $keyName in $baseKeyNames
          return lmm:scopeQualifyKeyName(
                      $keyName,
                      $keyScopes)
  let $result := attribute {name($keysAtt)}
                {string-join($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 :)


removed "save new" button which was unuseful

xquery version "1.0";

declare namespace demo="http://luxdb.net/demo";

import module namespace config="http://luxdb.net/demo/config" at "config.xqy";
import module namespace layout="http://luxdb.net/demo/layout" at "layout.xqy";

declare variable $lux:http as document-node() external;

(: Query Tool :)

(: load any JS files here - they'll go at the end of the body :)
declare function demo:javascript-postamble() {

};

declare function demo:main () {
  let $params := $lux:http/http/params/param
  let $query := $params[@name='query']/value/string()
  let $body :=

        <form action="../xquery" id="search" name="search" method="post" target="results">
          <input type="hidden" name="wt" value="lux" />
          <input type="hidden" name="lux.contentType" value="text/xml" />
          <input type="hidden" name="lux.xml-xsl-stylesheet" value="{config:path('chrome-xml.xsl', $lux:http)}" />
          <textarea id="q" name="q" cols="80" rows="10">{$query}</textarea>

          <input type="submit" value="go" />
          (use ctrl-Enter)
        </form>

    { demo:javascript-postamble () }
  /*

  return layout:outer('/query.xqy', $body, (), $lux:http)
};

demo:main ()

xquery version "1.0";

declare namespace demo="http://luxdb.net/demo";

import module namespace config="http://luxdb.net/demo/config" at "config.xqy";
import module namespace layout="http://luxdb.net/demo/layout" at "layout.xqy";

declare variable $lux:http as document-node() external;

(: Query Tool :)

(: load any JS files here - they'll go at the end of the body :)
declare function demo:javascript-postamble() {

};

declare function demo:main () {
  let $params := $lux:http/http/params/param
  let $query := $params[@name='query']/value/string()
  let $body :=

        <form action="../xquery" id="search" name="search" method="post" target="results">
          <input type="hidden" name="wt" value="lux" />
          <input type="hidden" name="lux.contentType" value="text/xml" />
          <input type="hidden" name="lux.xml-xsl-stylesheet" value="{config:path('chrome-xml.xsl', $lux:http)}" />
          <textarea id="q" name="q" cols="80" rows="10">{$query}</textarea>

          <input type="submit" value="go" />
          (use ctrl-Enter)
          <input type="button" value="save new" onclick="saveQuery()" />
          (max 10)
        </form>

    { demo:javascript-postamble () }
  /*

  return layout:outer('/query.xqy', $body, (), $lux:http)
};

demo:main ()


[apps biblo]  Very early version of save.xq for the MODS editor.  Works for new items by creating a new file with a UUID but not yet working for updates.

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

xquery version "1.0";

(: XQuery script to save a new MODS record from an incomming HTTP POST :)

import module namespace style = "http://exist-db.org/mods-style" at "../../../modules/style.xqm";

declare namespace xf="http://www.w3.org/2002/xforms";
declare namespace xforms="http://www.w3.org/2002/xforms";
declare namespace ev="http://www.w3.org/2001/xml-events";
declare namespace mods="http://www.loc.gov/mods/v3";
declare default element namespace "http://www.loc.gov/mods/v3";

let $title := 'MODS Record Save'

(: this service takes an incomming POST and saves the appropriate records :)
(: note that if the incomming mods:identifier in missing a new file will automatically be created :)
(: the user must have write and update access to the data collection :)

let $app-collection := $style:db-path-to-app
let $data-collection := $style:db-path-to-app-data

(: this is where the form "POSTS" documents to this XQuery using the POST method of a submission :)
let $item := request:get-data()

(: check to see if we have an indentifier in the incomming post :)
let $incomming-id := $item/mods:identifier/text()

return
if ( string-length($incomming-id) = 0 )
   then
        let $new-id := util:uuid()
        let $file-name := concat($new-id, '.xml')
        let $login := xmldb:login($data-collection, 'admin', '')
        let $store := xmldb:store($data-collection, $file-name, $item)
        let $message := <results><message> file {$file-name} created OK </message></results>
        return $message
   else

(: else we are doing an update to an existing file :)

let $file-to-update := concat($incomming-id, '.xml')
let $file-path := concat($data-collection, '/', $file-to-update)
let $doc := doc($file-path)/mods:mods

(: If the incoming has any part then we update it in the document :)
let $titleInfo := if ($item/mods:titleInfo) then update replace $doc/mods:titleInfo with $item/mods:titleInfo else ()

(:
let $name := if ($item/name) then update replace $doc/name with $item/name else ()
let $originInfo := if ($item/originInfo) then update replace $doc/originInfo with $item/originInfo else ()
let $physicalDescription := if ($item/physicalDescription) then update replace $doc/physicalDescription with $item/physicalDescription else ()
let $targetAudience := if ($item/targetAudience) then update replace $doc/targetAudience with $item/targetAudience else ()
let $language := if ($item/language) then update replace $doc/part5 with $item/part5 else ()
let $typeOfResource := if ($item/typeOfResource) then update replace $doc/part5 with $item/part5 else ()
let $genre := if ($item/genre) then update replace $doc/part5 with $item/part5 else ()
let $subject := if ($item/subject) then update replace $doc/subject with $item/subject else ()
let $classification := if ($item/classification) then update replace $doc/classification with $item/classification else ()
let $abstract := if ($item/abstract) then update replace $doc/abstract with $item/abstract else ()
let $table-of-contents := if ($item/table-of-contents) then update replace $doc/table-of-contents with $item/table-of-contents else ()
let $note := if ($item/note) then update replace $doc/note with $item/note else ()
let $related := if ($item/related) then update replace $doc/related with $item/related else ()
let $identifier := if ($item/identifier) then update replace $doc/identifier with $item/identifier else ()
let $record-info := if ($item/record-info) then update replace $doc/record-info with $item/record-info else ()
let $access-condition := if ($item/access-condition) then update replace $doc/access-condition with $item/access-condition
:)

return
<results>
  <message>Save Status = OK</message>
</results>

xquery version "1.0";

(: XQuery script to save a new MODS record from an incomming HTTP POST :)

import module namespace style = "http://exist-db.org/mods-style" at "../../../modules/style.xqm";
import module namespace mods = "http://www.loc.gov/mods/v3" at "../modules/mods.xqm";

declare namespace xf="http://www.w3.org/2002/xforms";
declare namespace xforms="http://www.w3.org/2002/xforms";
declare namespace ev="http://www.w3.org/2001/xml-events";
declare namespace mods="http://www.loc.gov/mods/v3";

let $title := 'MODS Record Save Confirmation'

let $app-collection := $style:db-path-to-app
let $data-collection := $style:db-path-to-app-data
let $save-file := concat($style:db-path-to-app, '/edit/save.xml')

(: this is where the form "POSTS" documents to this XQuery using the POST method of a submission :)
let $item := request:get-data()

let $doc := doc($save-file)/data

(: in the incoming has any part then we update it in the document :)
let $titleInfo := if ($item//titleInfo) then update replace $doc/titleInfo with $item/titleInfo else ()
let $name := if ($item/name) then update replace $doc/name with $item/name else ()
let $originInfo := if ($item/originInfo) then update replace $doc/originInfo with $item/originInfo else ()
let $physicalDescription := if ($item/physicalDescription) then update replace $doc/physicalDescription with $item/physicalDescription else ()
let $targetAudience := if ($item/targetAudience) then update replace $doc/targetAudience with $item/targetAudience else ()
let $language := if ($item/language) then update replace $doc/part5 with $item/part5 else ()
let $typeOfResource := if ($item/typeOfResource) then update replace $doc/part5 with $item/part5 else ()
let $genre := if ($item/genre) then update replace $doc/part5 with $item/part5 else ()
let $subject := if ($item/subject) then update replace $doc/subject with $item/subject else ()
let $classification := if ($item/classification) then update replace $doc/classification with $item/classification else ()
let $abstract := if ($item/abstract) then update replace $doc/abstract with $item/abstract else ()
let $table-of-contents := if ($item/table-of-contents) then update replace $doc/table-of-contents with $item/table-of-contents else ()
let $note := if ($item/note) then update replace $doc/note with $item/note else ()
let $related := if ($item/related) then update replace $doc/related with $item/related else ()
let $identifier := if ($item/identifier) then update replace $doc/identifier with $item/identifier else ()
let $record-info := if ($item/record-info) then update replace $doc/record-info with $item/record-info else ()
let $access-condition := if ($item/access-condition) then update replace $doc/access-condition with $item/access-condition else ()

let $content :=
<div class="content">

  <message>Save Status = OK</message>

  {mods:tabs-table('title', true()) }

</div>

return $content


Fix for bug lp:944795: all special characters have to be escaped. Like you have escaped: < with < > with > you also have to replace & with &

xquery version "3.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 an XQuery API to full-text functions.
 :
Notes on the thesaurus

 : The ft:thesaurus-lookup function has "levels"
 : and "relationship" parameters.
 : The values for these are implementation-defined.
 : Zorba's default implementation uses the
 : WordNet lexical database,
 : version 3.0.
 :

 : In WordNet,
 : the number of "levels" that two phrases are apart
 : are how many hierarchical meanings apart they are.
 : For example,
 : "canary" is 5 levels away from "vertebrate"
 : (carary > finch > oscine > passerine > bird > vertebrate).
 :

 : When using the WordNet implementation,
 : Zorba supports all of the relationships (and their abbreviations)
 : specified by
 : ISO 2788
 : and
 : ANSI/NISO Z39.19-2005
 : with the exceptions of "HN" (history note)
 : and "X SN" (see scope note for).
 : Additionally, Zorba also supports all WordNet relationships.
 :

 :

 :     Rel.
 :     Meaning
 :     WordNet Rel.
 :

 :

 :     BT
 :     broader term
 :     hypernym
 :

 :

 :     BTG
 :     broader term generic
 :     hypernym
 :

 :

 :     BTI
 :     broader term instance
 :     instance hypernym
 :

 :

 :     BTP
 :     broader term partitive
 :     part meronym
 :

 :

 :     NT
 :     narrower term
 :     hyponym
 :

 :

 :     NTG
 :     narrower term generic
 :     hyponym
 :

 :

 :     NTI
 :     narrower term instance
 :     instance hyponym
 :

 :

 :     NTP
 :     narrower term partitive
 :     part holonym
 :

 :

 :     RT
 :     related term
 :     also see
 :

 :

 :     SN
 :     scope note
 :     n/a
 :

 :

 :     TT
 :     top term
 :     hypernym
 :

 :

 :     UF
 :     non-preferred term
 :     n/a
 :

 :

 :     USE
 :     preferred term
 :     n/a
 :

 :

 : Relationships are case-insensitive.
 :
Notes on tokens

 : The ft:tokenize()
 : and ft:tokenize-string() functions
 : return a sequence of <token> elements
 : containing attributes for each token:
 :

 :

 :      Attribute
 :      Type
 :      Meaning
 :

 :

 :      lang
 :      xs:language
 :      The language of the token.
 :

 :

 :      sentence
 :      xs:nonNegativeInteger
 :      The sentence number.
 :

 :

 :      paragraph
 :      xs:nonNegativeInteger
 :      The paragraph number.
 :

 :

 :      value
 :      xs:string
 :      The token string value.
 :

 :

 :      node-ref
 :      xs:string
 :      A reference to the originating node.
 :

 :

 :
Notes on tokenizer properties

 : The ft:tokenizer-properties() functions
 : return a single <tokenizer-properties> element
 : containing attributes that describe the tokenizer's properties:
 :

 :

 :      Attribute
 :      Type
 :      Meaning
 :

 :

 :      elements-separate-tokens
 :      xs:boolean
 :
 :        If true,
 :        XML elements separate tokens,
 :        for example
 :        <b>B</b>old
 :        would be 2 tokens instead of 1.
 :
 :

 :

 :)
module namespace ft = "http://www.zorba-xquery.com/modules/full-text";

import schema namespace ft-schema =
  "http://www.zorba-xquery.com/modules/full-text";

declare namespace err = "http://www.w3.org/2005/xqt-errors";
declare namespace zerr = "http://www.zorba-xquery.com/errors";

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

(:~
 : Gets the current
 : language:
 : either the langauge specified by the
 : declare ft-option using language statement (if any)
 : or the one returned by ft:host-lang() (if none).
 :
 : @return said language.
 :)
declare function ft:current-lang()
  as xs:language external;

(:~
 : Gets the host's current
 : language.
 : The "host" is the computer on which Zorba is running.
 : The host's current language is obtained as follows:
 :

 :

 :      For *nix systems:
 :

 :

 :          If setlocale(3) returns non-null,
 :          the language corresponding to that locale is used.
 :

 :

 :          Else, if the LANG environment variable is set,
 :          that language is ued.
 :

 :

 :          Otherwise, there is no default language.
 :

 :

 :

 :

 :      For Windows systems, the language corresponding to the locale returned
 :      by the GetLocaleInfoA() function is used.
 :

 :

 :
 : @return said language.
 :)
declare function ft:host-lang()
  as xs:language external;

(:~
 : Checks whether the given
 : language
 : is supported for stemming.
 :
 : @param $lang The language to check.
 : @return true only if the language is supported.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stem-lang-es-supported-true.xq
 :)
declare function ft:is-stem-lang-supported( $lang as xs:language )
  as xs:boolean external;

(:~
 : Checks whether the given word is a stop-word.
 :
 : @param $word The word to check.
 : @param $lang The
 : language
 : of $word.
 : @return true only if $word is a stop-word.
 : @error zerr:ZXQP8405 if $lang is not supported for stop-words.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stop-word-true-1.xq
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stop-word-true-3.xq
 :)
declare function ft:is-stop-word( $word as xs:string, $lang as xs:language )
  as xs:boolean external;

(:~
 : Checks whether the given word is a stop-word.
 :
 : @param $word The word to check.
 : The word's language
 : is assumed to be the one returned by ft:current-lang().
 : @return true only if $word is a stop-word.
 : @error err:FTST0009 if ft:current-lang() is not supported in
 : general.
 : @error zerr:ZXQP8405 if ft:current-lang() is not supported for
 : stop-words specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stop-word-true-2.xq
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stop-word-true-4.xq
 :)
declare function ft:is-stop-word( $word as xs:string )
  as xs:boolean external;

(:~
 : Checks whether the given
 : language
 : is supported for stop words.
 :
 : @param $lang The language to check.
 : @return true only if the language is supported.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stop-word-lang-en-supported-true.xq
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stop-word-lang-supported-false-1.xq
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-stop-word-lang-supported-false-2.xq
 :)
declare function ft:is-stop-word-lang-supported( $lang as xs:language )
  as xs:boolean external;

(:~
 : Checks whether the given
 : language
 : is supported for look-up using the default thesaurus.
 :
 : @param $lang The language to check.
 : @return true only if the language is supported.
 :)
declare function ft:is-thesaurus-lang-supported( $lang as xs:language )
  as xs:boolean external;

(:~
 : Checks whether the given
 : language
 : is supported for look-up using the thesaurus specified by the given URI.
 :
 : @param $uri The URI specifying the thesaurus to use.
 : @param $lang The language to check.
 : @return true only if the language is supported.
 : @error err:FTST0018 if $uri refers to a thesaurus
 : that is not found in the statically known thesauri.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-is-thesaurus-lang-supported-true-1.xq
 :)
declare function ft:is-thesaurus-lang-supported( $uri as xs:string,
                                                 $lang as xs:language )
  as xs:boolean external;

(:~
 : Stems the given word.
 :
 : @param $word The word to stem.
 : @param $lang The
 : language
 : of $word.
 : @return the stem of $word.
 : @error err:FTST0009 if $lang is not supported in general.
 : @error zerr:ZXQP8404 if $lang is not supported for stemming
 : specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-stem-1.xq
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-stem-2.xq
 :)
declare function ft:stem( $word as xs:string, $lang as xs:language )
  as xs:string external;

(:~
 : Stems the given word.
 :
 : @param $word The word to stem.
 : The word's language
 : is assumed to be the one returned by ft:current-lang().
 : @return the stem of $word.
 : @error err:FTST0009 if ft:current-lang() is not supported in
 : general.
 : @error zerr:ZXQP8404 if ft:current-lang() is not supported for
 : stemming specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-stem-3.xq
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-stem-4.xq
 :)
declare function ft:stem( $word as xs:string )
  as xs:string external;

(:~
 : Strips all diacritical marks from all characters.
 :
 : @param $string The string to strip diacritical marks from.
 : @return $string with diacritical marks stripped.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-strip-diacritics-1.xq
 :)
declare function ft:strip-diacritics( $string as xs:string )
  as xs:string external;

(:~
 : Looks-up the given phrase in the default thesaurus.
 :
 : @param $phrase The phrase to look up.
 : The phrase's
 : language
 : is assumed to be the one returned by ft:current-lang().
 : @return the original and related phrases.
 : @error err:FTST0009 if ft:current-lang() is not supported in
 : general.
 : @error zerr:ZXQP8401 if the thesaurus data file's version is not supported
 : by the currently running version of Zorba.
 : @error zerr:ZXQP8402 if the thesaurus data file's endianness does not match
 : that of the CPU on which Zorba is currently running.
 : @error zerr:ZXQP8403 if there was an error reading the thesaurus data.
 : @error zerr:ZXQP8405 if ft:current-lang() is not supported for
 : thesaurus look-up specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-thesaurus-lookup-1.xq
 :)
declare function ft:thesaurus-lookup( $phrase as xs:string )
  as xs:string+ external;

(:~
 : Looks-up the given phrase in the thesaurus specified by the given URI.
 :
 : @param $uri The URI specifying the thesaurus to use.
 : @param $phrase The phrase to look up.
 : @param $lang The
 : language
 : of $phrase.
 : @return the original and related phrases.
 : @error err:FTST0009 if $lang is not supported in general.
 : @error err:FTST0018 if $uri refers to a thesaurus
 : that is not found in the statically known thesauri.
 : @error zerr:ZOSE0001 if the thesaurus data file could not be found.
 : @error zerr:ZOSE0002 if the thesaurus data file is not a plain file.
 : @error zerr:ZXQP8401 if the thesaurus data file's version is not supported
 : by the currently running version of Zorba.
 : @error zerr:ZXQP8402 if the thesaurus data file's endianness does not match
 : that of the CPU on which Zorba is currently running.
 : @error zerr:ZXQP8403 if there was an error reading the thesaurus data file.
 : @error zerr:ZXQP8405 if $lang is not supported for thesaurus
 : look-up specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-thesaurus-lookup-2.xq
 :)
declare function ft:thesaurus-lookup( $uri as xs:string, $phrase as xs:string,
                                      $lang as xs:language )
  as xs:string+ external;

(:~
 : Looks-up the given phrase in a thesaurus.
 :
 : @param $uri The URI specifying the thesaurus to use.
 : @param $phrase The phrase to look up.
 : The phrase's
 : language
 : is assumed to be the one the one returned by ft:current-lang().
 : @return the original and related phrases.
 : @error err:FTST0009 if ft:current-lang() is unsupported in
 : general.
 : @error err:FTST0018 if $uri refers to a thesaurus
 : that is not found in the statically known thesauri.
 : @error zerr:ZOSE0001 if the thesaurus data file could not be found.
 : @error zerr:ZOSE0002 if the thesaurus data file is not a plain file.
 : @error zerr:ZXQP8401 if the thesaurus data file's version is not supported
 : by the currently running version of Zorba.
 : @error zerr:ZXQP8402 if the thesaurus data file's endianness does not match
 : that of the CPU on which Zorba is currently running.
 : @error zerr:ZXQP8403 if there was an error reading the thesaurus data file.
 : @error zerr:ZXQP8405 if ft:current-lang() is not supported for
 : thesaurus look-up specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-thesaurus-lookup-3.xq
 :)
declare function ft:thesaurus-lookup( $uri as xs:string, $phrase as xs:string )
  as xs:string+ external;

(:~
 : Looks-up the given phrase in a thesaurus.
 :
 : @param $uri The URI specifying the thesaurus to use.
 : @param $phrase The phrase to look up.
 : @param $lang The
 : language
 : of $phrase.
 : @param $relationship The relationship the results are to have to
 : $phrase.
 : @return the original and related phrases.
 : @error err:FTST0018 if $uri refers to a thesaurus
 : that is not found in the statically known thesauri.
 : @error err:FTST0009 if $lang is not supported in general.
 : @error zerr:ZOSE0001 if the thesaurus data file could not be found.
 : @error zerr:ZOSE0002 if the thesaurus data file is not a plain file.
 : @error zerr:ZXQP8401 if the thesaurus data file's version is not supported
 : by the currently running version of Zorba.
 : @error zerr:ZXQP8402 if the thesaurus data file's endianness does not match
 : that of the CPU on which Zorba is currently running.
 : @error zerr:ZXQP8403 if there was an error reading the thesaurus data file.
 : @error zerr:ZXQP8405 if $lang is not supported for thesaurus
 : look-up specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-thesaurus-lookup-4.xq
 :)
declare function ft:thesaurus-lookup( $uri as xs:string, $phrase as xs:string,
                                      $lang as xs:language,
                                      $relationship as xs:string )
  as xs:string+ external;

(:~
 : Looks-up the given phrase in a thesaurus.
 :
 : @param $uri The URI specifying the thesaurus to use.
 : @param $phrase The phrase to look up.
 : @param $lang The
 : language
 : of $phrase.
 : @param $relationship The relationship the results are to have to
 : $phrase.
 : @param $level-least The minimum number of levels within the thesaurus to be
 : travers$ed.
 : @param $level-most The maximum number of levels within the thesaurus to be
 : traversed.
 : @return the original and related phrases.
 : @error err:FOCA0003 if either $level-least or
 : $level-most is either negative or too large.
 : @error err:FTST0018 if $uri refers to a thesaurus
 : that is not found in the statically known thesauri.
 : @error err:FTST0009 if $lang is not supported in general.
 : @error zerr:ZOSE0001 if the thesaurus data file could not be found.
 : @error zerr:ZOSE0002 if the thesaurus data file is not a plain file.
 : @error zerr:ZXQP8401 if the thesaurus data file's version is not supported
 : by the currently running version of Zorba.
 : @error zerr:ZXQP8402 if the thesaurus data file's endianness does not match
 : that of the CPU on which Zorba is currently running.
 : @error zerr:ZXQP8403 if there was an error reading the thesaurus data file.
 : @error zerr:ZXQP8405 if $lang is not supported for thesaurus
 : look-up specifically.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-thesaurus-lookup-5.xq
 :)
declare function ft:thesaurus-lookup( $uri as xs:string, $phrase as xs:string,
                                      $lang as xs:language,
                                      $relationship as xs:string,
                                      $level-least as xs:integer,
                                      $level-most as xs:integer )
  as xs:string+ external;

(:~
 : Tokenizes the given document.
 :
 : @param $node The node to tokenize.
 : @param $lang The default
 : language
 : of $node.
 : @return a (possibly empty) sequence of tokens.
 : @error err:FTST0009 if $lang is not supported in general.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-tokenize-1.xq
 :)
declare function ft:tokenize( $node as node(), $lang as xs:language )
  as node()* external;

(:~
 : Tokenizes the given document.
 :
 : @param $node The node to tokenize.
 : The document's default
 : language
 : is assumed to be the one returned by ft:current-lang().
 : @return a (possibly empty) sequence of tokens.
 : @error err:FTST0009 if ft:current-lang() is not supported in
 : general.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-tokenize-2.xq
 :)
declare function ft:tokenize( $node as node() )
  as node()* external;

(:~
 : Tokenizes the given string.
 :
 : @param $string The string to tokenize.
 : @param $lang The default
 : language
 : of $string.
 : @return a (possibly empty) sequence of tokens.
 : @error err:FTST0009 if $lang is not supported in general.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-tokenize-string-1.xq
 :)
declare function ft:tokenize-string( $string as xs:string,
                                     $lang as xs:language )
  as xs:string* external;

(:~
 : Tokenizes the given string.
 :
 : @param $string The string to tokenize.
 : The string's default
 : language
 : is assumed to be the one returned by ft:current-lang().
 : @return a (possibly empty) sequence of tokens.
 : @error err:FTST0009 if ft:current-lang() is not supported in
 : general.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-tokenize-string-2.xq
 :)
declare function ft:tokenize-string( $string as xs:string )
  as xs:string* external;

(:~
 : Gets properties of the tokenizer for the given
 : language.
 :
 : @param $lang The langauage of the tokenizer to get the properties of.
 : @return said properties.
 : @error err:FTST0009 if $lang is not supported in general.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-tokenizer-properties-1.xq
 :)
declare function ft:tokenizer-properties( $lang as xs:language )
  as node() external;

(:~
 : Gets properties of the tokenizer for the
 : language
 : returned by ft:current-lang().
 :
 : @return said properties.
 : @error err:FTST0009 if ft:current-lang() is not supported in
 : general.
 : @example test/rbkt/Queries/zorba/fulltext/ft-module-tokenizer-properties-2.xq
 :)
declare function ft:tokenizer-properties()
  as node() external;

(: vim:set et sw=2 ts=2: :)


Add log-in icon

Add log-in icon

xquery version "1.0" encoding "utf-8";

import module namespace calli = "http://callimachusproject.org/rdf/2009/framework#" at "/callimachus/layout-functions.xq";

    <meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0,target-densityDpi=device-dpi"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="icon" href="/favicon.ico" />
    {calli:styles-href(<link rel="stylesheet" />)}
    {calli:scripts-src(
    <![endif]-->
    {calli:head-nodes()}

    {calli:body-attributes()}

                    {calli:home-href(Callimachus)}

                {calli:login-href(Sign in )}

                    <button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown">
                        Main menu

                    </button>

{calli:folder-href(Home folder)}

{calli:changes-href(Recent changes)}

{calli:callimachus-about-href(About Callimachus)}

{calli:callimachus-getting-started-href(Getting started)}

{calli:callimachus-feedback-href(Send feedback)}

                        {calli:head-links(

,
)}

{calli:whatlinkshere-href(What links here)}

{calli:relatedchanges-href(Related changes)}

{calli:permissions-href(Permissions)}

{calli:introspect-href(Introspect resource)}

Print this page

{calli:profile-href(Account)}

{calli:logout-href(Sign out)}

{calli:lookup-form('Lookup...')}

{calli:breadcrumb-links(

,
)}

            {calli:activate-nav(

{calli:view-href(View)}

{calli:edit-href(Edit)}

{calli:discussion-href(Discussion)}

{calli:describe-href(Describe)}

{calli:history-href(History)}

            )}

                {calli:body-hgroup()}
                {calli:body-sidebar(
)}
                {calli:error-alert(

                    <button type="button" class="close" data-dismiss="alert">×</button>

Oops!

)}
                {calli:body-nodes()}

                {calli:generator-p(
)}
                {calli:lastmod-time(
This resource was last modified at <time class="datetime-local"/>
)}

xquery version "1.0" encoding "utf-8";

import module namespace calli = "http://callimachusproject.org/rdf/2009/framework#" at "/callimachus/layout-functions.xq";

    <meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0,target-densityDpi=device-dpi"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="icon" href="/favicon.ico" />
    {calli:styles-href(<link rel="stylesheet" />)}
    {calli:scripts-src(
    <![endif]-->
    {calli:head-nodes()}

    {calli:body-attributes()}

                    {calli:home-href(Callimachus)}

                {calli:login-href(Sign in)}

                    <button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown">
                        Main menu

                    </button>

{calli:folder-href(Home folder)}

{calli:changes-href(Recent changes)}

{calli:callimachus-about-href(About Callimachus)}

{calli:callimachus-getting-started-href(Getting started)}

{calli:callimachus-feedback-href(Send feedback)}

                        {calli:head-links(

,
)}

{calli:whatlinkshere-href(What links here)}

{calli:relatedchanges-href(Related changes)}

{calli:permissions-href(Permissions)}

{calli:introspect-href(Introspect resource)}

Print this page

{calli:profile-href(Account)}

{calli:logout-href(Sign out)}

{calli:lookup-form('Lookup...')}

{calli:breadcrumb-links(

,
)}

            {calli:activate-nav(

{calli:view-href(View)}

{calli:edit-href(Edit)}

{calli:discussion-href(Discussion)}

{calli:describe-href(Describe)}

{calli:history-href(History)}

            )}

                {calli:body-hgroup()}
                {calli:body-sidebar(
)}
                {calli:error-alert(

                    <button type="button" class="close" data-dismiss="alert">×</button>

Oops!

)}
                {calli:body-nodes()}

                {calli:generator-p(
)}
                {calli:lastmod-time(
This resource was last modified at <time class="datetime-local"/>
)}


Preparing future : stronger typing in demo apps

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

xquery version "1.0";

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

declare function local:random($max as xs:integer)
as empty()
{
    let $r := ceiling(util:random() * $max) cast as xs:integer
    return (
        session:set-attribute("random", $r),
        session:set-attribute("guesses", 0)
    )
};

declare function local:guess($guess as xs:integer,
$rand as xs:integer) as element()
{
    let $count := session:get-attribute("guesses") + 1
    return (
        session:set-attribute("guesses", $count),
        if ($guess lt $rand) then

Your number is too small!

        else if ($guess gt $rand) then

Your number is too large!

        else (

Congratulations! You guessed the right number with
            {$count} tries. Try again!
,
            local:random(100)
        )
    )
};

declare function local:main() as node()?
{
    session:create(),
    let $rand := session:get-attribute("random"),
        $guess := xs:integer(request:get-parameter("guess", ()))
    return
		if ($rand) then
			if ($guess) then
				local:guess($guess, $rand)
			else

No input!

		else local:random(100)
};

    <title>Number Guessing</title>

        <form action="{session:encode-url(request:get-uri())}">

                        Guess a number

                    Number:
                    <input type="text" name="guess"
                        size="3"/>

                        <input type="submit"/>

        </form>
        { local:main() }

			View source code

xquery version "1.0";

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

declare function local:random($max as xs:integer)
as empty()
{
    let $r := ceiling(util:random() * $max) cast as xs:integer
    return (
        session:set-attribute("random", $r),
        session:set-attribute("guesses", 0)
    )
};

declare function local:guess($guess as xs:integer,
$rand as xs:integer) as element()
{
    let $count := session:get-attribute("guesses") + 1
    return (
        session:set-attribute("guesses", $count),
        if ($guess lt $rand) then

Your number is too small!

        else if ($guess gt $rand) then

Your number is too large!

        else (

Congratulations! You guessed the right number with
            {$count} tries. Try again!
,
            local:random(100)
        )
    )
};

declare function local:main() as node()?
{
    session:create(),
    let $rand := session:get-attribute("random"),
        $guess := request:get-parameter("guess", ())
    return
		if ($rand) then
			if ($guess) then
				local:guess($guess, $rand)
			else

No input!

		else local:random(100)
};

    <title>Number Guessing</title>

        <form action="{session:encode-url(request:get-uri())}">

                        Guess a number

                    Number:
                    <input type="text" name="guess"
                        size="3"/>

                        <input type="submit"/>

        </form>
        { local:main() }

			View source code