check mtimes are actually valid date times before updating them in the DB

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/openhie/openinfoman
:
:)
module namespace csd_lsc = "https://github.com/openhie/openinfoman/csd_lsc";
import module namespace csd_psd = "https://github.com/openhie/openinfoman/csd_psd";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare function csd_lsc:get_document_name($name) {
  csd_dm:document_source($name)
(:  concat("local_services_cache/",$name,".xml") :)
};

declare function csd_lsc:directory_exists($db,$name) {
  db:is-xml($db,csd_lsc:get_document_name($name))
};

declare updating function csd_lsc:empty_cache($db,$name)
{
  (
    csd_dm:empty($db,$name)
    ,csd_lsc:set_service_directory_mtime($db,$name,$csd_lsc:beginning_of_time)
   )

};

declare function csd_lsc:get_cache($db,$name)
{
 if (csd_lsc:directory_exists($db,$name)) then
    db:open($db,csd_lsc:get_document_name($name))
  else csd_dm:blank_directory()
};

declare variable $csd_lsc:beginning_of_time := '1970-01-01T00:00:00+00:00';
declare variable $csd_lsc:cache_meta_doc := 'local_cache_meta.xml';

declare function csd_lsc:get_cache_data($db,$name)
{
  if ( not( db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    if ($name) then
      ()
    else
      <cacheData/>
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
    return if ($name) then
      $meta_doc/cacheData/serviceCache[@name = $name]
    else
      $meta_doc/cacheData
};

declare updating function csd_lsc:drop_cache_data($db,$name)
{
  if ( not(db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    ()
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
      return if ($name) then
	delete node $meta_doc/cacheData/serviceCache[@name = $name]
      else
	delete node $meta_doc/cacheData/*

};

declare  function csd_lsc:cache_meta_exists($db) {
  db:is-xml($db,$csd_lsc:cache_meta_doc)
};

declare updating function csd_lsc:init_cache_meta($db) {
  if ( not(csd_lsc:cache_meta_exists($db)))  then
    db:add($db, <cacheData/>,$csd_lsc:cache_meta_doc)
  else
      ()
};

declare updating function csd_lsc:set_service_directory_mtime($db,$name,$mtime)
{
  ( csd_lsc:init_cache_meta($db)
  ,
  try {
    let $dt :=  xs:dateTime($mtime) (: just to make sure it's valid :)
    let $meta :=  db:open($db,$csd_lsc:cache_meta_doc)/cacheData
    return
      if (not(exists($meta/serviceCache[@name = $name])))
      then insert node <serviceCache name="{$name}" mtime="{$csd_lsc:beginning_of_time}"/> into $meta
      else if (not(exists($meta/serviceCache[@name = $name]/@mtime)))
      then
        let $attr := attribute {"mtime"}{ $csd_lsc:beginning_of_time}
        return ( insert  node $attr into $meta/serviceCache[@name = $name])
      else
        replace value of node $meta/serviceCache[@name = $name]/@mtime with $mtime
  } catch * {
    trace($mtime,"Invalid date time sent")
    ()  (: do nothing :)
  }
  )

};

declare function csd_lsc:get_service_directory_mtime($db,$name)
{
  if ( db:is-xml($db,$csd_lsc:cache_meta_doc))  then
    let $mtime := text{db:open($db,$csd_lsc:cache_meta_doc)/cacheData/serviceCache[@name = $name]/@mtime}
    return if ($mtime) then
	$mtime
      else
	$csd_lsc:beginning_of_time
    else
      $csd_lsc:beginning_of_time

};

declare  updating  function csd_lsc:update_cache($db,$name)
{

  let $mtime :=  csd_lsc:get_service_directory_mtime($db,$name)
(:  querying against self during an update will cause a deadlock :)
(:  see or :)
(:  http://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.htmlhttp://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.html  :)
(:  possible work-around: http://docs.basex.org/wiki/Server_Protocol ? :)
  let $currtime := current-dateTime()
  let $result := csd_psd:poll_service_directory($db,$name,$mtime)
  let $cache_doc := csd_lsc:get_cache($db,$name)

  return (
    csd_lsc:refresh_doc($cache_doc,$result)
    ,csd_lsc:set_service_directory_mtime($db,$name,$currtime)

  )

};

declare updating function csd_lsc:refresh_doc($cache_doc,$updates)
{
  (
   csd_lsc:update_directory($cache_doc/csd:CSD/csd:organizationDirectory,$updates/(csd:CSD/csd:organizationDirectory|csd:organizationDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:facilityDirectory,$updates/(csd:CSD/csd:facilityDirectory|csd:facilityDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:serviceDirectory,$updates/(csd:CSD/csd:serviceDirectory|csd:serviceDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:providerDirectory,$updates/(csd:CSD/csd:providerDirectory|csd:providerDirectory))
  )
};

declare updating function csd_lsc:update_directory($oldDir,$newDir)
{
  for $new in  $newDir/*
  let $old := $oldDir/*[@entityID = $new/@entityID] (:there may be more than one:)
  let $t:=
    for ($o in $old)
    return delete node $old
  return  insert node $new into $oldDir
};

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/openhie/openinfoman
:
:)
module namespace csd_lsc = "https://github.com/openhie/openinfoman/csd_lsc";
import module namespace csd_psd = "https://github.com/openhie/openinfoman/csd_psd";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare function csd_lsc:get_document_name($name) {
  csd_dm:document_source($name)
(:  concat("local_services_cache/",$name,".xml") :)
};

declare function csd_lsc:directory_exists($db,$name) {
  db:is-xml($db,csd_lsc:get_document_name($name))
};

declare updating function csd_lsc:empty_cache($db,$name)
{
  (
    csd_dm:empty($db,$name)
    ,csd_lsc:set_service_directory_mtime($db,$name,$csd_lsc:beginning_of_time)
   )

};

declare function csd_lsc:get_cache($db,$name)
{
 if (csd_lsc:directory_exists($db,$name)) then
    db:open($db,csd_lsc:get_document_name($name))
  else csd_dm:blank_directory()
};

declare variable $csd_lsc:beginning_of_time := '1970-01-01T00:00:00+00:00';
declare variable $csd_lsc:cache_meta_doc := 'local_cache_meta.xml';

declare function csd_lsc:get_cache_data($db,$name)
{
  if ( not( db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    if ($name) then
      ()
    else
      <cacheData/>
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
    return if ($name) then
      $meta_doc/cacheData/serviceCache[@name = $name]
    else
      $meta_doc/cacheData
};

declare updating function csd_lsc:drop_cache_data($db,$name)
{
  if ( not(db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    ()
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
      return if ($name) then
	delete node $meta_doc/cacheData/serviceCache[@name = $name]
      else
	delete node $meta_doc/cacheData/*

};

declare  function csd_lsc:cache_meta_exists($db) {
  db:is-xml($db,$csd_lsc:cache_meta_doc)
};

declare updating function csd_lsc:init_cache_meta($db) {
  if ( not(csd_lsc:cache_meta_exists($db)))  then
    db:add($db, <cacheData/>,$csd_lsc:cache_meta_doc)
  else
      ()
};

declare updating function csd_lsc:set_service_directory_mtime($db,$name,$mtime)
{
  ( csd_lsc:init_cache_meta($db)
  ,
  let $meta :=  db:open($db,$csd_lsc:cache_meta_doc)/cacheData
  return
    if (not(exists($meta/serviceCache[@name = $name])))  then
      insert node <serviceCache name="{$name}" mtime="{$csd_lsc:beginning_of_time}"/> into $meta
    else if (not(exists($meta/serviceCache[@name = $name]/@mtime))) then
      let $attr := attribute {"mtime"}{ $csd_lsc:beginning_of_time}
      return ( insert  node $attr into $meta/serviceCache[@name = $name])
    else
      replace value of node $meta/serviceCache[@name = $name]/@mtime with $mtime
  )

};

declare function csd_lsc:get_service_directory_mtime($db,$name)
{
  if ( db:is-xml($db,$csd_lsc:cache_meta_doc))  then
    let $mtime := text{db:open($db,$csd_lsc:cache_meta_doc)/cacheData/serviceCache[@name = $name]/@mtime}
    return if ($mtime) then
	$mtime
      else
	$csd_lsc:beginning_of_time
    else
      $csd_lsc:beginning_of_time

};

declare  updating  function csd_lsc:update_cache($db,$name)
{

  let $mtime :=  csd_lsc:get_service_directory_mtime($db,$name)
(:  querying against self during an update will cause a deadlock :)
(:  see or :)
(:  http://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.htmlhttp://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.html  :)
(:  possible work-around: http://docs.basex.org/wiki/Server_Protocol ? :)
  let $currtime := current-dateTime()
  let $result := csd_psd:poll_service_directory($db,$name,$mtime)
  let $cache_doc := csd_lsc:get_cache($db,$name)

  return (
    csd_lsc:refresh_doc($cache_doc,$result)
    ,csd_lsc:set_service_directory_mtime($db,$name,$currtime)

  )

};

declare updating function csd_lsc:refresh_doc($cache_doc,$updates)
{
  (
   csd_lsc:update_directory($cache_doc/csd:CSD/csd:organizationDirectory,$updates/(csd:CSD/csd:organizationDirectory|csd:organizationDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:facilityDirectory,$updates/(csd:CSD/csd:facilityDirectory|csd:facilityDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:serviceDirectory,$updates/(csd:CSD/csd:serviceDirectory|csd:serviceDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:providerDirectory,$updates/(csd:CSD/csd:providerDirectory|csd:providerDirectory))
  )
};

declare updating function csd_lsc:update_directory($oldDir,$newDir)
{
  for $new in  $newDir/*
  let $old := $oldDir/*[@entityID = $new/@entityID] (:there may be more than one:)
  let $t:=
    for ($o in $old)
    return delete node $old
  return  insert node $new into $oldDir
};


- delete previous verison of the XQDoc documentation

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

import module namespace xqdoc       = "http://www.zorba-xquery.com/modules/xqdoc";
import module namespace batch       = "http://www.zorba-xquery.com/modules/xqdoc/batch";
import module namespace xqdoc-html  = "http://www.zorba-xquery.com/xqdoc-html" at "xqdoc-html.xqy";

declare namespace xqd = "http://www.xqdoc.org/1.0";
declare namespace z   = "http://zorba.io/manifest";
declare namespace xs  = "http://www.w3.org/2001/XMLSchema";

declare variable $zorbaManifestPath   as xs:string external;
declare variable $xhtmlRequisitesPath as xs:string external;
declare variable $xqdocBuildPath      as xs:string external;
declare variable $zorbaVersion        as xs:string external;

declare variable $slash := file:directory-separator();

(: Delete previous version of the documentation :)
if(file:exists($xqdocBuildPath))
then file:delete($xqdocBuildPath);
else ();

(: Copy Schemas :)
file:create-directory($xqdocBuildPath || $slash ||  "schemas");
let $base := $xhtmlRequisitesPath || $slash || ".." || $slash || ".." || $slash || ".." || $slash
for $schema in file:list($base, true(), "*.xsd")
where not(ends-with($schema, ".ent.xsd")) and not(ends-with($schema, ".dtd.xsd"))
let $schema-doc := doc($base || $slash || trace($schema, "schema"))
let $target-uri := $schema-doc/xs:schema/@targetNamespace/string()
return {
  file:write-text($xqdocBuildPath || $slash || "schemas" || $slash || replace(replace($target-uri, "http://", ""), "/", "_") || ".xsd", $schema-doc)
};

file:create-directory($xqdocBuildPath || $slash ||  "examples");
variable $manifest := parse-xml(file:read-text($zorbaManifestPath));
for $module in $manifest/z:manifest/z:module
let $uri := $module/z:uri/text()
let $xqdoc := xqdoc:xqdoc($uri)
let $module-name := tokenize($uri,"/")[last()]
return {
  (: Copy Examples :)
  for $example in $xqdoc//xqd:custom[@tag="example"]/text()
  let $source := $module/z:projectRoot/text() || $slash || $example
  let $test-name := trace(tokenize($source,"/")[last()], "example-name")
  let $destination := $xqdocBuildPath || $slash ||  "examples" || $slash || concat($module-name, "_", $test-name)
  let $base-dest := file:dir-name($destination)
  return {
    file:create-directory($base-dest);
    file:copy($source, $destination);
  }
}

(: XQDoc Batch :)
let $static-folders := ("js", "css", "images")
let $static-folders := for $static in $static-folders return $xhtmlRequisitesPath || $slash || $static
let $template := parse-xml(file:read-text($xhtmlRequisitesPath || $slash || "template.xml"))/*
let $output-folder := $xqdocBuildPath
let $modules := xqdoc-html:modules($manifest)
return batch:build-xqdoc($output-folder, $static-folders, $template, $modules);
trace("","XQDoc generated successfully!");

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

import module namespace xqdoc       = "http://www.zorba-xquery.com/modules/xqdoc";
import module namespace batch       = "http://www.zorba-xquery.com/modules/xqdoc/batch";
import module namespace xqdoc-html  = "http://www.zorba-xquery.com/xqdoc-html" at "xqdoc-html.xqy";

declare namespace xqd = "http://www.xqdoc.org/1.0";
declare namespace z   = "http://zorba.io/manifest";
declare namespace xs  = "http://www.w3.org/2001/XMLSchema";

declare variable $zorbaManifestPath   as xs:string external;
declare variable $xhtmlRequisitesPath as xs:string external;
declare variable $xqdocBuildPath      as xs:string external;
declare variable $zorbaVersion        as xs:string external;

declare variable $slash := file:directory-separator();

(: Delete previous version of the documentation
file:delete($xqdocBuildPath || $slash ||  "schemas");:)

(: Copy Schemas :)
file:create-directory($xqdocBuildPath || $slash ||  "schemas");
let $base := $xhtmlRequisitesPath || $slash || ".." || $slash || ".." || $slash || ".." || $slash
for $schema in file:list($base, true(), "*.xsd")
where not(ends-with($schema, ".ent.xsd")) and not(ends-with($schema, ".dtd.xsd"))
let $schema-doc := doc($base || $slash || trace($schema, "schema"))
let $target-uri := $schema-doc/xs:schema/@targetNamespace/string()
return {
  file:write-text($xqdocBuildPath || $slash || "schemas" || $slash || replace(replace($target-uri, "http://", ""), "/", "_") || ".xsd", $schema-doc)
};

file:create-directory($xqdocBuildPath || $slash ||  "examples");
variable $manifest := parse-xml(file:read-text($zorbaManifestPath));
for $module in $manifest/z:manifest/z:module
let $uri := $module/z:uri/text()
let $xqdoc := xqdoc:xqdoc($uri)
let $module-name := tokenize($uri,"/")[last()]
return {
  (: Copy Examples :)
  for $example in $xqdoc//xqd:custom[@tag="example"]/text()
  let $source := $module/z:projectRoot/text() || $slash || $example
  let $test-name := trace(tokenize($source,"/")[last()], "example-name")
  let $destination := $xqdocBuildPath || $slash ||  "examples" || $slash || concat($module-name, "_", $test-name)
  let $base-dest := file:dir-name($destination)
  return {
    file:create-directory($base-dest);
    file:copy($source, $destination);
  }
}

(: XQDoc Batch :)
let $static-folders := ("js", "css", "images")
let $static-folders := for $static in $static-folders return $xhtmlRequisitesPath || $slash || $static
let $template := parse-xml(file:read-text($xhtmlRequisitesPath || $slash || "template.xml"))/*
let $output-folder := $xqdocBuildPath
let $modules := xqdoc-html:modules($manifest)
return batch:build-xqdoc($output-folder, $static-folders, $template, $modules);
trace("","XQDoc generated successfully!");


[bugfix] Make comparison more robust

[bugfix] Make comparison more robust

(:
 : eXist-db Open Source Native XML Database
 : Copyright (C) 2001 The eXist-db Authors
 :
 : info@exist-db.org
 : http://www.exist-db.org
 :
 : This library is free software; you can redistribute it and/or
 : modify it under the terms of the GNU Lesser General Public
 : License as published by the Free Software Foundation; either
 : version 2.1 of the License, or (at your option) any later version.
 :
 : This library is distributed in the hope that it will be useful,
 : but WITHOUT ANY WARRANTY; without even the implied warranty of
 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 : Lesser General Public License for more details.
 :
 : You should have received a copy of the GNU Lesser General Public
 : License along with this library; if not, write to the Free Software
 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 :)
xquery version "3.1";

module namespace sysau = "http://exist-db.org/test/system/as-user";

import module namespace sm = "http://exist-db.org/xquery/securitymanager";
import module namespace system = "http://exist-db.org/xquery/system";

declare namespace test = "http://exist-db.org/xquery/xqsuite";

declare
    %test:assertEquals('admin')
function sysau:function-as-user-admin-inline() {
    system:function-as-user("admin", "", function() {
        (sm:id()//sm:username)[last()]/string(.)
    })
};

declare
    %test:assertEquals('guest')
function sysau:function-as-user-guest-inline() {
    system:function-as-user("guest", "guest", function() {
        (sm:id()//sm:username)[last()]/string(.)
    })
};

declare
    %test:assertEquals('admin')
function sysau:function-as-user-admin-reference() {
    system:function-as-user("admin", "", sysau:get-effective-user-id#0)
};

declare
    %test:assertEquals('guest')
function sysau:function-as-user-guest-reference() {
    system:function-as-user("guest", "guest", sysau:get-effective-user-id#0)
};

declare
    %test:assertError
function sysau:function-as-user-unknown() {
    system:function-as-user("unknown", "", function() {
        ()
    })
};

declare
    %private
function sysau:get-effective-user-id() as xs:string {
    (sm:id()//sm:username)[last()]/string(.)
};

(:
 : eXist-db Open Source Native XML Database
 : Copyright (C) 2001 The eXist-db Authors
 :
 : info@exist-db.org
 : http://www.exist-db.org
 :
 : This library is free software; you can redistribute it and/or
 : modify it under the terms of the GNU Lesser General Public
 : License as published by the Free Software Foundation; either
 : version 2.1 of the License, or (at your option) any later version.
 :
 : This library is distributed in the hope that it will be useful,
 : but WITHOUT ANY WARRANTY; without even the implied warranty of
 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 : Lesser General Public License for more details.
 :
 : You should have received a copy of the GNU Lesser General Public
 : License along with this library; if not, write to the Free Software
 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 :)
xquery version "3.1";

module namespace sysau = "http://exist-db.org/test/system/as-user";

import module namespace sm = "http://exist-db.org/xquery/securitymanager";
import module namespace system = "http://exist-db.org/xquery/system";

declare namespace test = "http://exist-db.org/xquery/xqsuite";

declare
    %test:assertEquals('admin')
function sysau:function-as-user-admin-inline() {
    system:function-as-user("admin", "", function() {
        sm:id()//sm:effective//sm:username/string()
    })
};

declare
    %test:assertEquals('guest')
function sysau:function-as-user-guest-inline() {
    system:function-as-user("guest", "guest", function() {
        sm:id()//sm:effective//sm:username/string()
    })
};

declare
    %test:assertEquals('admin')
function sysau:function-as-user-admin-reference() {
    system:function-as-user("admin", "", sysau:get-effective-user-id#0)
};

declare
    %test:assertEquals('guest')
function sysau:function-as-user-guest-reference() {
    system:function-as-user("guest", "guest", sysau:get-effective-user-id#0)
};

declare
    %test:assertError
function sysau:function-as-user-unknown() {
    system:function-as-user("unknown", "", function() {
        ()
    })
};

declare
    %private
function sysau:get-effective-user-id() as xs:string {
    sm:id()//sm:effective//sm:username/string()
};


fixed descriptions to show node() instead of just text(); added contributions from Loren (Version, Since, See)

Port of rev10964

git-svn-id: 1623499149f5af833cfad2814dce82e821210f6f@11678 8784284e-840f-0410-bd5a-a6a6012a0d1a

xquery version "1.0";

(: $Id$ :)

(:~
    Main XQuery script for the RESTful function docs application.

    The parameters passed in from controller.xql are:
        basepath: Used as base path for URLs
        module:   Module name
        function: Function name

    The controller.xql intercepts URLs like
        http://exist-db.org/exist/functions/xmldb/login
    and parses and passes these parameters transparently to the script:
        basepath: /exist/functions/
        module:   xmldb
        function: login
:)

(: eXist module namespaces :)
declare namespace request="http://exist-db.org/xquery/request";
declare namespace text="http://exist-db.org/xquery/text";
declare namespace util="http://exist-db.org/xquery/util";

(: Document namespaces :)
declare namespace xq="http://www.xqdoc.org/1.0";

declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

(: App root URL, passed in from controller.xql.  Used as base path for URLs. :)
declare variable $basepath := request:get-parameter('basepath', ());

(: Render a function or a set of functions :)
declare function local:render-function( $functionNode as element(xq:function)+, $showlinks as xs:boolean) as element() {
    let $parentModule := $functionNode/ancestor::xq:xqdoc/xq:module/xq:uri/text()
    let $parentModuleName := $functionNode/ancestor::xq:xqdoc/xq:module/xq:name/text()
    let $functionName := $functionNode/xq:name/text()
    let $functionSignature := $functionNode/xq:signature/text()
    let $functionDeprecated := $functionNode/xq:comment/xq:deprecated/text()
    let $functionDescription := $functionNode/xq:comment/xq:description/text()
    let $functionParagraphs := tokenize($functionDescription, "\n")
    let $functionVersion := $functionNode/xq:comment/xq:version/text()
    let $functionParameters := $functionNode/xq:comment/xq:param
    let $functionSees := $functionNode/xq:comment/xq:see
    let $functionReturns := $functionNode/xq:comment/xq:return/text()
    let $authors := $functionNode/xq:comment/xq:author
    let $sinces := $functionNode/xq:comment/xq:since
    return

{
                if ($showlinks) then
                    {$functionName}
                else $functionName
            }

{
                if (not(starts-with($functionSignature, $parentModuleName))) then
                    concat($parentModuleName, ':', $functionSignature)
                else
                    $functionSignature
            }

{
                for $desc in $functionParagraphs
                return

{$desc}

            }

{
                for $author in $authors
                return
                    {$author}
            }

            {
            if (string-length($functionVersion) > 0) then

Version {$functionVersion}

            else ()
            }

{
                if (empty($sinces)) then ()
                else

Since {string-join($sinces, ", ")}

            }

{
                for $see in $functionSees
                return

See {$see}

            }

{
                    for $parameter in $functionParameters
                    let $split := text:groups(normalize-space($parameter), "^(\$[^ ]+) (.*)$")
                    return

                            {$split[2]}
                            {$split[3]}

                }

            {
            if (string-length($functionReturns) > 0) then

Returns {$functionReturns}

            else (),
            if (string-length($functionDeprecated) > 0) then

Deprecated: {$functionDeprecated}

            else ()
            }

};

(: Render a module or set of modules :)
(: TODO add moduleSees, other standard XQDoc comments :)
declare function local:render-module($moduleURIs as xs:string+, $functionName as xs:string?, $showlinks as xs:boolean) as element()+ {
    for $moduleURI in $moduleURIs
	let $moduleDocs := /xq:xqdoc[xq:module/xq:uri eq $moduleURI]
	let $moduleName := if ($moduleURI = 'http://www.w3.org/2005/xpath-functions') then 'fn' else $moduleDocs/xq:module/xq:name/text()
	let $moduleDescription := $moduleDocs/xq:module/xq:comment/xq:description/node()
	order by $moduleURI
	return

{
                  if ($showlinks) then
                     {$moduleName}
                  else
                     $moduleName
               }

{$moduleURI}

{$moduleDescription}

{
	           (: Decide whether to render a single function or to render them all :)
	           if ($functionName) then
	               for $functionNode in $moduleDocs/xq:functions/xq:function[xq:name eq $functionName]
                   return local:render-function($functionNode, $showlinks)
               else
    	           for $functionNode in $moduleDocs/xq:functions/xq:function
    	           return local:render-function($functionNode, $showlinks)
	       }

};

(:
 : URL handling functions
 :)

(: Constructs a URL from a module URI :)
declare function local:moduleURI-to-URL($moduleURI as xs:string) as xs:string {
    if ($moduleURI = 'http://www.w3.org/2005/xpath-functions') then
        concat($basepath, 'fn')
    else
        concat($basepath, /xq:xqdoc/xq:module[xq:uri eq $moduleURI]/xq:name/text())
};

(: Constructs a URL from a module :)
declare function local:module-to-URL($module as xs:string) as xs:string {
    concat($basepath, $module)
};

(: Constructs a URL from a function :)
declare function local:function-to-URL($moduleURI as xs:string, $function as xs:string) {
    concat(local:moduleURI-to-URL($moduleURI), '/', $function)
};

(: Derives the module name from the module URI :)
declare function local:moduleURI-to-module($moduleURI as xs:string) as xs:string {
    if ($moduleURI = 'http://www.w3.org/2005/xpath-functions') then
        'fn'
    else
        /xq:xqdoc/xq:module[xq:uri eq $moduleURI]/xq:name/text()
};

(: Lists all modules :)
declare function local:list-modules($moduleURIs) {
    (

Reload documentation
        (click here if you enabled/disabled additional modules)

    ,

(eXist version: {util:system-property("product-version")},
        build: {util:system-property("product-build")}, functions: {count(//xq:function)}.)
        Modules have to be enabled in conf.xml to appear here.

    ,

Expand All or select a single module:

    ,

Name	Description	Namespace URI

{
            for $moduleURI in $moduleURIs
            let $moduleDocs := /xq:xqdoc[xq:module/xq:uri eq $moduleURI]
        	let $moduleName :=
        	   if ($moduleURI eq 'http://www.w3.org/2005/xpath-functions') then
        	       (: note: the xq:name for the xpath functions module is empty
        	        : since it's the default function namespace, but we need a name
        	        : for the link, so we just use 'fn' :)
        	       'fn'
               else $moduleDocs/xq:module/xq:name/text()
        	let $moduleDescription := $moduleDocs/xq:module/xq:comment/xq:description/node()
        	order by $moduleURI
            return

                    {$moduleName}
                    {$moduleDescription}
                    {$moduleURI}

        }

    )
};

(: Breadcrumbs for the page :)
declare function local:breadcrumbs($module, $function, $viewall) {

        Modules
        {
        if ($viewall) then
            ' > All Modules and Functions'
        else if ($module and not($function)) then
            (
            ' > '
            ,
            {$module}
            )
        else if ($module and $function) then
            (
            ' > '
            ,
            {$module}
            ,
            ' > '
            ,
            {$function}
            )
        else
            ' > Home'
        }

};

(: Footer-like 'return to home' link :)
declare function local:return-to-main() as element() {

        Return to list of all modules

};

(: Business logic :)
declare function local:main() {
    (: Get URL parameters :)
    let $module := request:get-parameter('module', ())
    let $function := request:get-parameter('function', ())
    let $showlinks := request:get-parameter('showlinks', 1) cast as xs:boolean
    let $q := request:get-parameter('q', ())

    (: Catch a request for 'all', i.e. the complete listing :)
    let $viewall := $module eq 'all'

    (: Derive module URIs based on URL parameter $module :)
    let $allModuleURIs := /xq:xqdoc/xq:module/xq:uri
    let $matchingModuleURIs := if ($module) then
        if ($module = 'fn') then
            'http://www.w3.org/2005/xpath-functions'
        else
            $allModuleURIs[following-sibling::xq:name eq $module]
        else ()
    let $moduleURIs := if ($matchingModuleURIs) then $matchingModuleURIs else $allModuleURIs

    return
        (
        (: Show documentation for all modules:)
        if ($viewall) then
            (
            local:breadcrumbs($module, $function, $viewall)
            ,
            local:render-module($moduleURIs, (), $showlinks)
            ,
            local:return-to-main()
            )
        (: Show just the list of modules :)
        else if (not($module)) then
            (
            local:breadcrumbs($module, $function, $viewall)
            ,
            local:list-modules($moduleURIs)
            )
        (: Show a single module :)
        else if ($module and $matchingModuleURIs) then
            if (count($matchingModuleURIs) gt 1) then
                (: TODO: Add handling for resolving modules with the same 'name' :)
                (
                local:breadcrumbs($module, $function, $viewall)
                ,
                local:render-module($moduleURIs, $function, $showlinks)
                ,
                local:return-to-main()
                )
            else
                (
                local:breadcrumbs($module, $function, $viewall)
                ,
                local:render-module($moduleURIs, $function, $showlinks)
                ,
                local:return-to-main()
                )
        (: Handle cases where there are no exact matching modules :)
        else
            (: else if ($module and not($matchingModules)) :)
            (: TODO add a fuzzy search :)
            (
            local:breadcrumbs((), (), $viewall)
            ,

Sorry, there are no modules that match "{$module}."  Please choose from the list below:

            ,
            local:list-modules($moduleURIs)
            )
        )

};

(: Module body :)
<book>
    <bookinfo>
        <graphic fileref="logo.jpg"/>
        <productname>Open Source Native XML Database</productname>
        <title>eXist Function Modules</title>
        <link rel="shortcut icon" href="../resources/exist_icon_16x16.ico"/>
		<link rel="icon" href="../resources/exist_icon_16x16.png" type="image/png"/>

    </bookinfo>

        <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="../sidebar.xml"/>

    <chapter>
        <title>A RESTful browser for eXist Function Modules</title>
        { local:main() }
    </chapter>
</book>

xquery version "1.0";

(: $Id$ :)

(:~
    Main XQuery script for the RESTful function docs application.

    The parameters passed in from controller.xql are:
        basepath: Used as base path for URLs
        module:   Module name
        function: Function name

    The controller.xql intercepts URLs like
        http://exist-db.org/exist/functions/xmldb/login
    and parses and passes these parameters transparently to the script:
        basepath: /exist/functions/
        module:   xmldb
        function: login
:)

(: eXist module namespaces :)
declare namespace request="http://exist-db.org/xquery/request";
declare namespace text="http://exist-db.org/xquery/text";
declare namespace util="http://exist-db.org/xquery/util";

(: Document namespaces :)
declare namespace xq="http://www.xqdoc.org/1.0";

declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

(: App root URL, passed in from controller.xql.  Used as base path for URLs. :)
declare variable $basepath := request:get-parameter('basepath', ());

(: Render a function or a set of functions :)
declare function local:render-function( $functionNode as element(xq:function)+, $showlinks as xs:boolean) as element() {
    let $parentModule := $functionNode/ancestor::xq:xqdoc/xq:module/xq:uri/text()
    let $parentModuleName := $functionNode/ancestor::xq:xqdoc/xq:module/xq:name/text()
    let $functionName := $functionNode/xq:name/text()
    let $functionSignature := $functionNode/xq:signature/text()
    let $functionDeprecated := $functionNode/xq:comment/xq:deprecated/text()
    let $functionDescription := $functionNode/xq:comment/xq:description/text()
    let $functionParagraphs := tokenize($functionDescription, "\n")
    let $functionVersion := $functionNode/xq:comment/xq:version
    let $functionParameters := $functionNode/xq:comment/xq:param
    let $functionReturns := $functionNode/xq:comment/xq:return/text()
    let $authors := $functionNode/xq:comment/xq:author
    let $sinces := $functionNode/xq:comment/xq:since
    return

{
                if ($showlinks) then
                    {$functionName}
                else $functionName
            }

{if (not(starts-with($functionSignature, $parentModuleName))) then concat($parentModuleName, ':', $functionSignature) else $functionSignature}

{
                for $desc in $functionParagraphs
                return

{$desc}

            }

{
                for $author in $authors
                return
                    {$author}
            }

{$functionVersion}

{
                for $since in $sinces
                return
                    {$since}
            }

{
                    for $parameter in $functionParameters
                    let $split := text:groups($parameter, "^(\$[^ ]+) (.*)$")
                    return

                            {$split[2]}
                            {$split[3]}

                }

            {
            if (string-length($functionReturns) > 0)
            then

Returns {$functionReturns}

            else (),
            if (string-length($functionDeprecated) > 0)
            then

Deprecated: {$functionDeprecated}

            else ()
            }

};

(: Render a module or set of modules :)
declare function local:render-module($moduleURIs as xs:string+, $functionName as xs:string?, $showlinks as xs:boolean) as element()+ {
    for $moduleURI in $moduleURIs
	let $moduleDocs := /xq:xqdoc[xq:module/xq:uri eq $moduleURI]
	let $moduleName := if ($moduleURI = 'http://www.w3.org/2005/xpath-functions') then 'fn' else $moduleDocs/xq:module/xq:name/text()
	let $moduleDescription := $moduleDocs/xq:module/xq:comment/xq:description/text()
	order by $moduleURI
	return

{
                  if ($showlinks) then
                     {$moduleName}
                  else
                     $moduleName
               }

{$moduleURI}

{$moduleDescription}

{
	           (: Decide whether to render a single function or to render them all :)
	           if ($functionName) then
	               for $functionNode in $moduleDocs/xq:functions/xq:function[xq:name eq $functionName]
                   return local:render-function($functionNode, $showlinks)
               else
    	           for $functionNode in $moduleDocs/xq:functions/xq:function
    	           return local:render-function($functionNode, $showlinks)
	       }

};

(:
 : URL handling functions
 :)

(: Constructs a URL from a module URI :)
declare function local:moduleURI-to-URL($moduleURI as xs:string) as xs:string {
    if ($moduleURI = 'http://www.w3.org/2005/xpath-functions') then
        concat($basepath, 'fn')
    else
        concat($basepath, /xq:xqdoc/xq:module[xq:uri eq $moduleURI]/xq:name/text())
};

(: Constructs a URL from a module :)
declare function local:module-to-URL($module as xs:string) as xs:string {
    concat($basepath, $module)
};

(: Constructs a URL from a function :)
declare function local:function-to-URL($moduleURI as xs:string, $function as xs:string) {
    concat(local:moduleURI-to-URL($moduleURI), '/', $function)
};

(: Derives the module name from the module URI :)
declare function local:moduleURI-to-module($moduleURI as xs:string) as xs:string {
    if ($moduleURI = 'http://www.w3.org/2005/xpath-functions') then
        'fn'
    else
        /xq:xqdoc/xq:module[xq:uri eq $moduleURI]/xq:name/text()
};

(: Lists all modules :)
declare function local:list-modules($moduleURIs) {
    (

Reload documentation
        (click here if you enabled/disabled additional modules)

    ,

(eXist version: {util:system-property("product-version")},
        build: {util:system-property("product-build")}, functions: {count(//xq:function)}.)
        Modules have to be enabled in conf.xml to appear here.

    ,

Expand All or select a single module:

    ,

Name	Description	Namespace URI

{
            for $moduleURI in $moduleURIs
            let $moduleDocs := /xq:xqdoc[xq:module/xq:uri eq $moduleURI]
        	let $moduleName :=
        	   if ($moduleURI eq 'http://www.w3.org/2005/xpath-functions') then
        	       (: note: the xq:name for the xpath functions module is empty
        	        : since it's the default function namespace, but we need a name
        	        : for the link, so we just use 'fn' :)
        	       'fn'
               else $moduleDocs/xq:module/xq:name/text()
        	let $moduleDescription := $moduleDocs/xq:module/xq:comment/xq:description/text()
        	order by $moduleURI
            return

                    {$moduleName}
                    {$moduleDescription}
                    {$moduleURI}

        }

    )
};

(: Breadcrumbs for the page :)
declare function local:breadcrumbs($module, $function, $viewall) {

        Modules
        {
        if ($viewall) then
            ' > All Modules and Functions'
        else if ($module and not($function)) then
            (
            ' > '
            ,
            {$module}
            )
        else if ($module and $function) then
            (
            ' > '
            ,
            {$module}
            ,
            ' > '
            ,
            {$function}
            )
        else
            ' > Home'
        }

};

(: Footer-like 'return to home' link :)
declare function local:return-to-main() as element() {

        Return to list of all modules

};

(: Business logic :)
declare function local:main() {
    (: Get URL parameters :)
    let $module := request:get-parameter('module', ())
    let $function := request:get-parameter('function', ())
    let $showlinks := request:get-parameter('showlinks', 1) cast as xs:boolean
    let $q := request:get-parameter('q', ())

    (: Catch a request for 'all', i.e. the complete listing :)
    let $viewall := $module eq 'all'

    (: Derive module URIs based on URL parameter $module :)
    let $allModuleURIs := /xq:xqdoc/xq:module/xq:uri
    let $matchingModuleURIs := if ($module) then
        if ($module = 'fn') then
            'http://www.w3.org/2005/xpath-functions'
        else
            $allModuleURIs[following-sibling::xq:name eq $module]
        else ()
    let $moduleURIs := if ($matchingModuleURIs) then $matchingModuleURIs else $allModuleURIs

    return
        (
        (: Show documentation for all modules:)
        if ($viewall) then
            (
            local:breadcrumbs($module, $function, $viewall)
            ,
            local:render-module($moduleURIs, (), $showlinks)
            ,
            local:return-to-main()
            )
        (: Show just the list of modules :)
        else if (not($module)) then
            (
            local:breadcrumbs($module, $function, $viewall)
            ,
            local:list-modules($moduleURIs)
            )
        (: Show a single module :)
        else if ($module and $matchingModuleURIs) then
            if (count($matchingModuleURIs) gt 1) then
                (: TODO: Add handling for resolving modules with the same 'name' :)
                (
                local:breadcrumbs($module, $function, $viewall)
                ,
                local:render-module($moduleURIs, $function, $showlinks)
                ,
                local:return-to-main()
                )
            else
                (
                local:breadcrumbs($module, $function, $viewall)
                ,
                local:render-module($moduleURIs, $function, $showlinks)
                ,
                local:return-to-main()
                )
        (: Handle cases where there are no exact matching modules :)
        else
            (: else if ($module and not($matchingModules)) :)
            (: TODO add a fuzzy search :)
            (
            local:breadcrumbs((), (), $viewall)
            ,

Sorry, there are no modules that match "{$module}."  Please choose from the list below:

            ,
            local:list-modules($moduleURIs)
            )
        )

};

(: Module body :)
<book>
    <bookinfo>
        <graphic fileref="logo.jpg"/>
        <productname>Open Source Native XML Database</productname>
        <title>eXist Function Modules</title>
        <link rel="shortcut icon" href="../resources/exist_icon_16x16.ico"/>
		<link rel="icon" href="../resources/exist_icon_16x16.png" type="image/png"/>

    </bookinfo>

        <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="../sidebar.xml"/>

    <chapter>
        <title>A RESTful browser for eXist Function Modules</title>
        { local:main() }
    </chapter>
</book>


adjust to appearance of 'vanished' state

Change-Id: Ia83bf0cbc568bb2dccdd86bdb9d68524221664ab
Reviewed-by: Oswald Buddenhagen <6a8868beda3e139b5ac22b2534a23d3e6ba81819@digia.com>
Reviewed-by: Lars Knoll <a812617a4072dcbeb80684eca0f369f0083af109@digia.com>

for $file in tokenize($files, codepoints-to-string(10))
    let $fresh := doc($file)/TS/context/message[not (translation/@type = ('obsolete', 'vanished'))]
    return if (count($fresh) gt 0)
           then concat($file, ":", count($fresh/translation[not (@type = 'unfinished')]) * 100 idiv count($fresh))
           else concat($file, ":n/a")

for $file in tokenize($files, codepoints-to-string(10))
    let $fresh := doc($file)/TS/context/message[not (translation/@type = 'obsolete')]
    return if (count($fresh) gt 0)
           then concat($file, ":", count($fresh/translation[not (@type = 'unfinished')]) * 100 idiv count($fresh))
           else concat($file, ":n/a")


[repo]  tweaking public repo ui

[repo]  tweaking public repo ui

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

xquery version "1.0";

declare namespace repo="http://exist-db.org/xquery/repo";

declare namespace xdb="http://exist-db.org/xquery/xmldb";
declare namespace package="http://expath.org/ns/pkg";

declare variable $repo-coll := "/db/repo";

declare function local:entry-data($path as xs:anyURI, $type as xs:string, $data as item()?, $param as item()*) as item()?
{

	<entry>
		<path>{$path}</path>
		<type>{$type}</type>
		<data>{$data}</data>
	</entry>
};

declare function local:entry-filter($path as xs:anyURI, $type as xs:string, $param as item()*) as xs:boolean
{

	true()
};

declare function local:process-action() as element()*
{
    let $repocol :=  if (collection($repo-coll)) then () else xmldb:create-collection('/db','repo')
    let $action := request:get-parameter("action", ())
    return
        util:catch("java.lang.Exception",
            if($action eq "Upload Package") then
              local:upload()
            else
              (),

                An error occurred while processing the action:

                {$util:exception-message}

        )
};

declare function local:upload() as element()
{
    let $name := request:get-parameter("name", ()),
    $docName := if($name) then $name else request:get-uploaded-file-name("upload"),
    $file := request:get-uploaded-file-data("upload") return

Actions:

                {
                if (contains($docName,'.xar')) then
                    (
uploaded package: {$docName}
,
                   xdb:decode-uri(xs:anyURI(xdb:store($repo-coll, xdb:encode-uri($docName), $file)))
                   ,
                    let $meta := compression:unzip($file, util:function(xs:QName("local:entry-filter"), 3), (),  util:function(xs:QName("local:entry-data"), 4), ())
                    let $package := $meta//package:package
                    let $repo := $meta//repo:meta
                    return
                     xdb:store($repo-coll, concat(substring-before($docName,'.xar'),'.xml'), <meta>{$package}{$repo}</meta>)
                   )
                else

Error uploading - Must be a valid Package archive (.xar file extension)

                }

};

    <title>eXist XML Database Public Package Repository: http://{request:get-hostname()}:{request:get-server-port()}/exist/repo/public/all/ </title>
    <link rel="stylesheet" type="text/css" href="../../styles/default-style2.css" />

eXist XML Database Public Repository : http://{request:get-hostname()}:{request:get-server-port()}/exist/repo/public/all/

 To install packages from this repository you will require:

In your eXist repo manager use this uri : http://{request:get-hostname()}:{request:get-server-port()}/exist/repo/public/all/

access permission to this repository (set by the administrator of this repository)

    {local:process-action()}

Library Packages

            Package Name
            Description
            Namespace(s)
            Date Created
            Author
            License
            Status

     {
     let $files := if (collection($repo-coll)) then collection($repo-coll)/util:document-name(.) else ()
     return
       for $file in $files[contains(.,'.xar')]
       let $name := substring-before($file,'.xar')
       let $package := document(concat($repo-coll,'/',$name,'.xml'))
       return
        if ($package//repo:type eq 'library') then

            {$name}

            {$package//repo:type}

            {$package//package:title/text()}

            {if ($package//package:java/package:namespace/text()) then (concat('java: ',$package//package:java/package:namespace/text()),
) else () }
            {if ($package//package:xquery/package:namespace/text()) then (concat('xquery: ',$package//package:xquery/package:namespace/text()),
) else () }
            {if ($package//package:xslt/package:import-uri/text()) then (concat('xslt: ',$package//package:xslt/package:import-uri/text()),
) else () }

            {xmldb:created($repo-coll, $file)}
            {$package//repo:author}
            {$package//repo:license}

        else
         ()

     }

Application Packages

            Package Name
            Description
            Namespace(s)
            Date Created
            Author
            License
            Status

     {
     let $files := if (collection($repo-coll)) then collection($repo-coll)/util:document-name(.) else ()
     return
       for $file in $files[contains(.,'.xar')]
       let $name := substring-before($file,'.xar')
       let $package := document(concat($repo-coll,'/',$name,'.xml'))
       return
        if ($package//repo:type eq 'application') then

            {$name}

            {if ($package//repo:deploy) then 'Application' else 'Library'}

            {$package//package:title/text()}

            {if ($package//package:java/package:namespace/text()) then (concat('java: ',$package//package:java/package:namespace/text()),
) else () }
            {if ($package//package:xquery/package:namespace/text()) then (concat('xquery: ',$package//package:xquery/package:namespace/text()),
) else () }
            {if ($package//package:xslt/package:import-uri/text()) then (concat('xslt: ',$package//package:xslt/package:import-uri/text()),
) else () }

            {xmldb:created($repo-coll, $file)}
            {$package//repo:author}
            {$package//repo:license}

        else
          ()

     }

Add Package

You will require access rights to upload new packages for distribution from this repository.

    <form method="POST" enctype="multipart/form-data">

        <input type="submit" name="action" value="Upload Package"/>
        <input type="file" size="30" name="upload"/>

    </form>

xquery version "1.0";

declare namespace repo="http://exist-db.org/xquery/repo";

declare namespace xdb="http://exist-db.org/xquery/xmldb";
declare namespace package="http://expath.org/ns/pkg";

declare variable $repo-coll := "/db/repo";

declare function local:entry-data($path as xs:anyURI, $type as xs:string, $data as item()?, $param as item()*) as item()?
{

	<entry>
		<path>{$path}</path>
		<type>{$type}</type>
		<data>{$data}</data>
	</entry>
};

declare function local:entry-filter($path as xs:anyURI, $type as xs:string, $param as item()*) as xs:boolean
{

	true()
};

declare function local:process-action() as element()*
{
    let $repocol :=  if (collection($repo-coll)) then () else xmldb:create-collection('/db','repo')
    let $action := request:get-parameter("action", ())
    return
        util:catch("java.lang.Exception",
            if($action eq "Upload Package") then
              local:upload()
            else
              (),

                An error occurred while processing the action:

                {$util:exception-message}

        )
};

declare function local:upload() as element()
{
    let $name := request:get-parameter("name", ()),
    $docName := if($name) then $name else request:get-uploaded-file-name("upload"),
    $file := request:get-uploaded-file-data("upload") return

Actions:

                {
                if (contains($docName,'.xar')) then
                    (
uploaded package: {$docName}
,
                   xdb:decode-uri(xs:anyURI(xdb:store($repo-coll, xdb:encode-uri($docName), $file)))
                   ,
                    let $meta := compression:unzip($file, util:function(xs:QName("local:entry-filter"), 3), (),  util:function(xs:QName("local:entry-data"), 4), ())
                    let $package := $meta//package:package
                    let $repo := $meta//repo:meta
                    return
                     xdb:store($repo-coll, concat(substring-before($docName,'.xar'),'.xml'), <meta>{$package}{$repo}</meta>)
                   )
                else

Error uploading - Must be a valid Package archive (.xar file extension)

                }

};

    <title>eXist XML Database Public Package Repository: http://{request:get-hostname()}:{request:get-server-port()}/exist/repo/public/all/ </title>
    <link rel="stylesheet" type="text/css" href="../../styles/default-style2.css" />

eXist XML Database Public Repository : http://{request:get-hostname()}:{request:get-server-port()}/exist/repo/public/all/

 To install packages from this repository you will require:

In your eXist repo manager use this uri : http://{request:get-hostname()}:{request:get-server-port()}/exist/repo/public/all/

access permission to this repository (set by the administrator of this repository)

    {local:process-action()}

Library Packages

            Package Name
            Description
            Namespace(s)
            Date Created
            Author
            License
            Status

     {
     let $files := if (collection($repo-coll)) then collection($repo-coll)/util:document-name(.) else ()
     return
       for $file in $files[contains(.,'.xar')]
       let $name := substring-before($file,'.xar')
       let $package := document(concat($repo-coll,'/',$name,'.xml'))
       return
        if (not($package//repo:type eq 'library')) then

            {$name}

            {$package//repo:type}

            {$package//package:title/text()}

            {if ($package//package:java/package:namespace/text()) then (concat('java: ',$package//package:java/package:namespace/text()),
) else () }
            {if ($package//package:xquery/package:namespace/text()) then (concat('xquery: ',$package//package:xquery/package:namespace/text()),
) else () }
            {if ($package//package:xslt/package:import-uri/text()) then (concat('xslt: ',$package//package:xslt/package:import-uri/text()),
) else () }

            {xmldb:created($repo-coll, $file)}
            {$package//repo:author}
            {$package//repo:license}

        else
         ()

     }

Application Packages

            Package Name
            Description
            Namespace(s)
            Date Created
            Author
            License
            Status

     {
     let $files := if (collection($repo-coll)) then collection($repo-coll)/util:document-name(.) else ()
     return
       for $file in $files[contains(.,'.xar')]
       let $name := substring-before($file,'.xar')
       let $package := document(concat($repo-coll,'/',$name,'.xml'))
       return
        if ($package//repo:type eq 'application') then

            {$name}

            {if ($package//repo:deploy) then 'Application' else 'Library'}

            {$package//package:title/text()}

            {if ($package//package:java/package:namespace/text()) then (concat('java: ',$package//package:java/package:namespace/text()),
) else () }
            {if ($package//package:xquery/package:namespace/text()) then (concat('xquery: ',$package//package:xquery/package:namespace/text()),
) else () }
            {if ($package//package:xslt/package:import-uri/text()) then (concat('xslt: ',$package//package:xslt/package:import-uri/text()),
) else () }

            {xmldb:created($repo-coll, $file)}
            {$package//repo:author}
            {$package//repo:license}

        else
          ()

     }

Add Package

You will require access rights to upload new packages for distribution from this repository.

    <form method="POST" enctype="multipart/form-data">

        <input type="submit" name="action" value="Upload Package"/>
        <input type="file" size="30" name="upload"/>

    </form>


[apidoc] Fix XQST0039 errors in the apidocs.

(:
 : Copyright (C) 2020 Reece H. Dunn
 :
 : 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.
 :)
xquery version "3.1";

declare namespace apidoc = "http://marklogic.com/xdmp/apidoc";
declare namespace map = "http://www.w3.org/2005/xpath-functions/map";

declare variable $apidoc as element(apidoc:apidoc) :=
    typeswitch (.)
    case xs:string return fn:parse-xml(.)/element()
    case document-node() return ./element()
    default return .;

(: functions --------------------------------------------------------------- :)

(: Several functions in the apidocs have duplicate parameter names in some versions, so fix these. :)
declare variable $param-fixes := map {
    "fn:subtract-dateTimes-yielding-dayTimeDuration": ("srcval1", "srcval2"),
    "fn:subtract-dateTimes-yielding-yearMonthDuration": ("srcval1", "srcval2"),
    "math:fmod": ("x", "y"),
    "math:rank": ("arg1", "arg2", "options")
};

declare variable $functions as element(apidoc:function)* := $apidoc//apidoc:function;

declare function local:function-name($function as element(apidoc:function)) as xs:string {
    ``[`{$function/@lib}`:`{$function/@name}`]``
};

declare function local:function-parameters($function as element(apidoc:function)) as xs:string* {
    let $names as xs:string* :=
        let $names := map:get($param-fixes, local:function-name($function))
        return if (exists($names)) then
            $names
        else
            $function/apidoc:params/apidoc:param/@name/string()
    return if (count($names) != count(distinct-values($names))) then
        fn:error(xs:QName("err:XQST0039"),``[Duplicate parameter name in `{local:function-name($function)}`]``)
    else
        $names
};

declare function local:function-parameter($function as element(apidoc:function), $name as xs:string) as element(apidoc:param) {
    let $names := map:get($param-fixes, local:function-name($function))
    return if (exists($names)) then
        let $i := fn:index-of($names, $name)
        return $function/apidoc:params/apidoc:param[$i]
    else
        $function/apidoc:params/apidoc:param[@name = $name]
};

declare function local:function-parameter-type($function as element(apidoc:function), $name as xs:string) as xs:string {
    local:function-parameter($function, $name)/@type
};

declare function local:function-return-type($function as element(apidoc:function)) as xs:string {
    $function/apidoc:return
};

declare function local:function-xquery-signatures($function as element(apidoc:function)) as xs:string* {
    let $name := local:function-name($function)
    let $params :=
        for $name in local:function-parameters($function)
        let $type := local:function-parameter-type($function, $name)
        return ``[$`{$name}` as `{$type}`]``
    let $type := local:function-return-type($function)
    return ``[declare function `{$name}`(`{string-join($params, ", ")}`) as `{$type}` external;]``
};

(: query ------------------------------------------------------------------- :)

for $function in $functions
order by $function/@lib, $function/@name
return local:function-xquery-signatures($function)

(:
 : Copyright (C) 2020 Reece H. Dunn
 :
 : 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.
 :)
xquery version "3.1";

declare namespace apidoc = "http://marklogic.com/xdmp/apidoc";

declare variable $apidoc as element(apidoc:apidoc) :=
    typeswitch (.)
    case xs:string return fn:parse-xml(.)/element()
    case document-node() return ./element()
    default return .;

(: functions --------------------------------------------------------------- :)

declare variable $functions as element(apidoc:function)* := $apidoc//apidoc:function;

declare function local:function-name($function as element(apidoc:function)) as xs:string {
    ``[`{$function/@lib}`:`{$function/@name}`]``
};

declare function local:function-parameters($function as element(apidoc:function)) as xs:string* {
    let $names as xs:string* := $function/apidoc:params/apidoc:param/@name/string()
    return if (count($names) != count(distinct-values($names))) then
        fn:error(xs:QName("err:XQST0039"),``[Duplicate parameter name in `{local:function-name($function)}`]``)
    else
        $names
};

declare function local:function-parameter-type($function as element(apidoc:function), $name as xs:string) as xs:string {
    $function/apidoc:params/apidoc:param[@name = $name]/@type
};

declare function local:function-return-type($function as element(apidoc:function)) as xs:string {
    $function/apidoc:return
};

declare function local:function-xquery-signatures($function as element(apidoc:function)) as xs:string* {
    let $name := local:function-name($function)
    let $params :=
        for $name in local:function-parameters($function)
        let $type := local:function-parameter-type($function, $name)
        return ``[$`{$name}` as `{$type}`]``
    let $type := local:function-return-type($function)
    return ``[declare function `{$name}`(`{string-join($params, ", ")}`) as `{$type}` external;]``
};

(: query ------------------------------------------------------------------- :)

for $function in $functions
order by $function/@lib, $function/@name
return local:function-xquery-signatures($function)


bug:38517 one more func sig change for this bug

(:
 Copyright 2002-2016 MarkLogic Corporation.  All Rights Reserved.

 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.
:)
xquery version "1.0-ml";

module namespace es = "http://marklogic.com/entity-services";

import module namespace esi = "http://marklogic.com/entity-services-impl" at "entity-services-impl.xqy";

import module namespace es-codegen = "http://marklogic.com/entity-services-codegen" at "entity-services-codegen.xqy";

import module namespace inst = "http://marklogic.com/entity-services-instance" at "entity-services-instance.xqy";

declare default function namespace "http://www.w3.org/2005/xpath-functions";
declare namespace search = "http://marklogic.com/appservices/search";

declare variable $ENTITY-TYPES-IRI := "http://marklogic.com/entity-services#";

declare option xdmp:mapping "false";

(:~
 : Creates an entity-type from a document node.
 : For JSON documents, this is equivalent to xdmp:json with validation.
 : For XML documents, we transform the input as well.
 :
 : @param $node A JSON or XML document containing an entity type definition.
 :)
declare function es:entity-type-from-node(
    $node as document-node()
) as map:map
{
    let $errors := esi:entity-type-validate($node)
    let $root := $node/node()
    return
        if ($errors)
        then fn:error( (), "ES-ENTITY-TYPE-INVALID", $errors)
        else
            if ($root/object-node())
            then xdmp:to-json($root)
            else esi:entity-type-from-xml($root)
};

(:~
 : Given an entity type, returns its XML representation
 : @param An entity type document.
 :)
declare function es:entity-type-to-xml(
    $entity-type
) as element(es:entity-type)
{
    esi:ensure-entity-type($entity-type)=>esi:entity-type-to-xml()
};

(:~
 : Given an entity type, returns its JSON representation
 : @param An entity type document.
 :)
declare function es:entity-type-to-json(
    $entity-type
) as object-node()
{
    xdmp:to-json(esi:ensure-entity-type($entity-type))/node()
};

(:~
 : Generates an XQuery module that can be customized and used
 : to support transforms associated with an entity type
 : @param $entity-type  An entity type object.
 : @return An XQuery module (text) that can be edited and installed in a modules database.
 :)
declare function es:conversion-module-generate(
    $entity-type
) as document-node()
{
    esi:ensure-entity-type($entity-type)=>es-codegen:conversion-module-generate()
};

(:~
 : Generate one test instance in XML for each entity type in the
 : entity type document payload.
 : @param An entity type document.
 :)
declare function es:entity-type-get-test-instances(
    $entity-type
) as element()*
{
    esi:ensure-entity-type($entity-type)=>esi:entity-type-get-test-instances()
};

(:~
 : Generate a JSON node that can be used with the Management Client API
 : to configure a database for this entity type.
 : Portions of this complete database properties file can be used
 : as building-blocks for the completed database properties
 : index configuration.
 : @param An entity type document.
 :)
declare function es:database-properties-generate(
    $entity-type
) as document-node()
{
    esi:ensure-entity-type($entity-type)=>esi:database-properties-generate()
};

(:~
 : Generate a schema that can validate entity instance documents.
 : @param An entity type document.
 : @return An XSD schema that can validate entity instances in XML form.
 :)
declare function es:schema-generate(
    $entity-type
) as element()*
{
    esi:ensure-entity-type($entity-type)=>esi:schema-generate()
};

(:~
 : Generates an extraction template that can surface the entity.
 : instance as a view in the rows index.
 : @param An entity type document.
 :)
declare function es:extraction-template-generate(
    $entity-type
) as document-node()
{
    document {
        esi:ensure-entity-type($entity-type)=>esi:extraction-template-generate()
    }
};

(:~
 : Generates an element for configuring Search API applications
 : Intended as a starting point for developing a search grammar tailored
 : for entity and relationship searches.
 : @param An entity type document.
 :)
declare function es:search-options-generate(
    $entity-type
)
{
    esi:ensure-entity-type($entity-type)=>esi:search-options-generate()
};

(:~
 : Given a document, gets the instance data
 : from it and returns instances as maps.
 : @param a document, usually es:envelope.
 : @return zero or more entity instances extracted from the document.
 :)
declare function es:instance-from-document(
    $document as document-node()
) as map:map*
{
    inst:instance-from-document($document)
};

(:~
 : Return the canonical XML representation of an instance from
 : a document.  This function does not transform; it's just a
 : projection of elements from a document.
 : @param a document, usually es:envelope.
 : @return zero or more elements that represent instances.
 :)
declare function es:instance-xml-from-document(
    $document as document-node()
) as element()*
{
    inst:instance-xml-from-document($document)
};

(:~
 : Returns the JSON serialization of an instance from
 : a document.
 : @param a document, usually es:envelope.
 : @return zero or more JSON structures that represent instances.
 :)
declare function es:instance-json-from-document(
    $document as document-node()
) as object-node()
{
    inst:instance-json-from-document($document)
};

(:~
 : Return the attachments from within an es:envelope document.
 : @param a document, usually es:envelope.
 : @return Anything that is contained within the es:attachments
 : element.
 :)
declare function es:instance-get-attachments(
    $document as document-node()
) as element()*
{
    inst:instance-get-attachments($document)
};

(:~
 : Fluent method to add key/value pairs to an entity instance, if the value exists.
 : @param $instance An instance of map:map to add a key to.
 : @param $property-key - The key to add to $instance.
 : @param $value - the value to add to $instance for the given key.
:)
declare function es:optional(
    $instance as map:map,
    $property-key as xs:string,
    $value as item()*
) as map:map
{
    if (exists($value))
    then
    map:put($instance, $property-key, $value)
    else (),
    $instance
};

(:
 Copyright 2002-2016 MarkLogic Corporation.  All Rights Reserved.

 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.
:)
xquery version "1.0-ml";

module namespace es = "http://marklogic.com/entity-services";

import module namespace esi = "http://marklogic.com/entity-services-impl" at "entity-services-impl.xqy";

import module namespace es-codegen = "http://marklogic.com/entity-services-codegen" at "entity-services-codegen.xqy";

import module namespace inst = "http://marklogic.com/entity-services-instance" at "entity-services-instance.xqy";

declare default function namespace "http://www.w3.org/2005/xpath-functions";
declare namespace search = "http://marklogic.com/appservices/search";

declare variable $ENTITY-TYPES-IRI := "http://marklogic.com/entity-services#";

declare option xdmp:mapping "false";

(:~
 : Creates an entity-type from a document node.
 : For JSON documents, this is equivalent to xdmp:json with validation.
 : For XML documents, we transform the input as well.
 :
 : @param $node A JSON or XML document containing an entity type definition.
 :)
declare function es:entity-type-from-node(
    $node as document-node()
) as map:map
{
    let $errors := esi:entity-type-validate($node)
    let $root := $node/node()
    return
        if ($errors)
        then fn:error( (), "ES-ENTITY-TYPE-INVALID", $errors)
        else
            if ($root/object-node())
            then xdmp:to-json($root)
            else esi:entity-type-from-xml($root)
};

(:~
 : Given an entity type, returns its XML representation
 : @param An entity type document.
 :)
declare function es:entity-type-to-xml(
    $entity-type
) as element(es:entity-type)
{
    esi:ensure-entity-type($entity-type)=>esi:entity-type-to-xml()
};

(:~
 : Given an entity type, returns its JSON representation
 : @param An entity type document.
 :)
declare function es:entity-type-to-json(
    $entity-type
) as object-node()
{
    xdmp:to-json(esi:ensure-entity-type($entity-type))/node()
};

(:~
 : Generates an XQuery module that can be customized and used
 : to support transforms associated with an entity type
 : @param $entity-type  An entity type object.
 : @return An XQuery module (text) that can be edited and installed in a modules database.
 :)
declare function es:conversion-module-generate(
    $entity-type
) as document-node()
{
    esi:ensure-entity-type($entity-type)=>es-codegen:conversion-module-generate()
};

(:~
 : Generate one test instance in XML for each entity type in the
 : entity type document payload.
 : @param An entity type document.
 :)
declare function es:entity-type-get-test-instances(
    $entity-type
) as element()*
{
    esi:ensure-entity-type($entity-type)=>esi:entity-type-get-test-instances()
};

(:~
 : Generate a JSON node that can be used with the Management Client API
 : to configure a database for this entity type.
 : Portions of this complete database properties file can be used
 : as building-blocks for the completed database properties
 : index configuration.
 : @param An entity type document.
 :)
declare function es:database-properties-generate(
    $entity-type
) as document-node()
{
    esi:ensure-entity-type($entity-type)=>esi:database-properties-generate()
};

(:~
 : Generate a schema that can validate entity instance documents.
 : @param An entity type document.
 : @return An XSD schema that can validate entity instances in XML form.
 :)
declare function es:schema-generate(
    $entity-type
) as element()*
{
    esi:ensure-entity-type($entity-type)=>esi:schema-generate()
};

(:~
 : Generates an extraction template that can surface the entity.
 : instance as a view in the rows index.
 : @param An entity type document.
 :)
declare function es:extraction-template-generate(
    $entity-type
) as document-node()
{
    document {
        esi:ensure-entity-type($entity-type)=>esi:extraction-template-generate()
    }
};

(:~
 : Generates an element for configuring Search API applications
 : Intended as a starting point for developing a search grammar tailored
 : for entity and relationship searches.
 : @param An entity type document.
 :)
declare function es:search-options-generate(
    $entity-type as map:map
)
{
    esi:ensure-entity-type($entity-type)=>esi:search-options-generate()
};

(:~
 : Given a document, gets the instance data
 : from it and returns instances as maps.
 : @param a document, usually es:envelope.
 : @return zero or more entity instances extracted from the document.
 :)
declare function es:instance-from-document(
    $document as document-node()
) as map:map*
{
    inst:instance-from-document($document)
};

(:~
 : Return the canonical XML representation of an instance from
 : a document.  This function does not transform; it's just a
 : projection of elements from a document.
 : @param a document, usually es:envelope.
 : @return zero or more elements that represent instances.
 :)
declare function es:instance-xml-from-document(
    $document as document-node()
) as element()*
{
    inst:instance-xml-from-document($document)
};

(:~
 : Returns the JSON serialization of an instance from
 : a document.
 : @param a document, usually es:envelope.
 : @return zero or more JSON structures that represent instances.
 :)
declare function es:instance-json-from-document(
    $document as document-node()
) as object-node()
{
    inst:instance-json-from-document($document)
};

(:~
 : Return the attachments from within an es:envelope document.
 : @param a document, usually es:envelope.
 : @return Anything that is contained within the es:attachments
 : element.
 :)
declare function es:instance-get-attachments(
    $document as document-node()
) as element()*
{
    inst:instance-get-attachments($document)
};

(:~
 : Fluent method to add key/value pairs to an entity instance, if the value exists.
 : @param $instance An instance of map:map to add a key to.
 : @param $property-key - The key to add to $instance.
 : @param $value - the value to add to $instance for the given key.
:)
declare function es:optional(
    $instance as map:map,
    $property-key as xs:string,
    $value as item()*
) as map:map
{
    if (exists($value))
    then
    map:put($instance, $property-key, $value)
    else (),
    $instance
};


Add ny-times stats

Add ny-times stats

xquery version "3.0";

(: A faster version of ny-times example :)

import module namespace xf = 'http://xokomola.com/xquery/origami'
    at '../core.xqm';

let $input := xf:html-resource(file:base-dir() || 'ny-times.html')

(:
let $input := xf:html-resource('http://www.nytimes.com')
:)

let $select-stories := xf:extract(xf:at('article[$in(@class,"story")]'))
let $select-headline := xf:at('((h2|h3|h5)//a)[1]', xf:text())
let $select-byline := xf:at('*[$in(@class,"byline")][1]', xf:text())
let $select-summary := xf:at('*[$in(@class,"summary")][1]', xf:text())

for $story in $select-stories($input)

    let $headline := $story => $select-headline()
    let $byline := $story => $select-byline()
    let $summary := $story => $select-summary()

    where $headline and $byline and $summary
    return
      <story>{
        $headline =>  xf:wrap(<headline/>),
        $byline => xf:wrap(<byline/>),
        $summary => xf:wrap(<summary/>)
      }</story>

(:
    Parsing: 506.85 ms
    Compiling: 31.99 ms
    Evaluating: 1.16 ms
    Printing: 826.16 ms
    Total Time: 1366.15 ms
:)


[feature] updated kwic module to support callback handlers, which can be used to ignore certain text nodes in the kwic output or to change the displayed text.

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

(:~
    KWIC module: formats query results to display keywords in context (KWIC). A configurable
    amount of text is displayed to the left and right of a matching keyword (or phrase).

    The module works with all indexes that support match highlighting (matches are tagged
    with an <exist:match> element). This includes the old full text index, the new
    Lucene-based full text index, as well as the NGram index.

    The kwic:summarize() function represents the main entry point into the module.
    To have more control over the text extraction context, you can also call
    kwic:get-summary() instead. For example, the following snippet will only print the
    first match within a given set of context nodes ($ancestor):

    let $matches := kwic:get-matches($hit)

    for $ancestor in $matches/ancestor::para | $matches/ancestor::title | $matches/ancestor::td

    return

        kwic:get-summary($ancestor, ($ancestor//exist:match)[1], $config)

:)
module namespace kwic="http://exist-db.org/xquery/kwic";

declare variable $kwic:CHARS_SUMMARY := 120;
declare variable $kwic:CHARS_KWIC := 40;

(:~
	Retrieve the following and preceding text chunks for a given match.

	@param $match the text node containing the match
	@param $mode the selection mode: either "previous" or "following"
:)
declare function kwic:get-context($root as element(), $match as element(exist:match), $mode as xs:string) as node()* {
	let $sibs :=
		if ($mode eq 'previous') then
			$match/preceding::text()
		else
			$match/text()/following::text()
	for $sib in $sibs
	where exists($root[.//$sib])
	return
		if ($sib/parent::exist:match) then
			{$sib}
		else
			$sib
};

(:~
	Like fn:substring, but takes a node argument. If the node is an element,
	a new element is created with the same node-name as the old one and the
	shortened text content.
:)
declare function kwic:substring($node as item(), $start as xs:int, $count as xs:int) as item()? {
	let $str := substring($node, $start, $count)
	return
		if ($node instance of element()) then
			element { node-name($node) } { $str }
		else
			$str
};

declare function kwic:display-text($text as text()?) as node()? {
    if ($text/parent::exist:match) then
    	{$text}
    else
        $text
};

declare function kwic:callback($callback as function?, $node as node(), $mode as xs:string) as xs:string? {
    if (exists($callback)) then
        util:call($callback, $node, $mode)
    else
        $node
};

(:~
	Generate the left-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-previous($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int, $callback as function?) {
	if ($node) then
		let $next := $node/preceding::text()[1]
		return
			if (empty($root//$next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
			    let $str := kwic:callback($callback, $next, "before")
			    return
    			    if (exists($str)) then
        				let $remaining := $max - $chars
        				return (
        				    "...",
        				    kwic:substring($str, string-length($str) - $remaining + 1, $remaining),
        				    $truncated
        			    )
        			else
        			    kwic:truncate-previous($root, $next, $truncated, $max, $chars, $callback)
			else
				kwic:truncate-previous($root, $next,
				    (kwic:callback($callback, $next, "before"), $truncated),
					$max, $chars + string-length($next), $callback)
	else
		$truncated
};

(:~
	Generate the right-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-following($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int, $callback as function?) {
	if ($node) then
		let $next := $node/following::text()[1]
		return
			if (empty($root//$next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
			    let $str := kwic:callback($callback, $next, "after")
			    return
    			    if (exists($str)) then
        				let $remaining := $max - $chars
        				return
        				    ($truncated, kwic:substring($next, 1, $remaining), "...")
        			else
        			    kwic:truncate-following($root, $next, $truncated, $max, $chars, $callback)
			else
				kwic:truncate-following($root, $next,
				    ($truncated, kwic:callback($callback, $next, "after")),
					$max, $chars + string-length($next), $callback)
	else
		$truncated
};

(:~
	Computes the total string length of the nodes in the argument sequence
:)
declare function kwic:string-length($nodes as item()*) as xs:integer {
	if (exists($nodes)) then
		sum(for $n in $nodes return string-length($n))
	else
		0
};

declare function kwic:get-summary($root as node(), $node as element(exist:match),
	$config as element(config)) as element() {
	kwic:get-summary($root, $node, $config, ())
};

(:~
	Print a summary of the match in $node. Output a predefined amount of text to
	the left and the right of the match.

	@param $root root element which should be used as context for the match. It defines the
	    boundaries for the text extraction. Text will be taken from this context.
	@param $node the exist:match element to process.
	@param $config configuration element which determines the behaviour of the function
	@param $callback (optional) reference to a callback function which will be called
	once for every text node before it is appended to the displayed text. The function
	should accept 2 parameters: 1) a single text node, 2) a string indicating the
	current direction in which text is appended, i.e. "before" or "after". The function
	may return the empty sequence if the current node should be ignore (e.g. if it belongs
	to a "footnote" which should not be displayed). Otherwise it should return a single
	string.
:)
declare function kwic:get-summary($root as node(), $node as element(exist:match),
	$config as element(config), $callback as function?) as element() {
	let $chars := xs:int($config/@width)
	let $table := $config/@table = ('yes', 'true')
	let $prevTrunc := kwic:truncate-previous($root, $node, (), $chars, 0, $callback)
	let $remain :=
		if (not($table)) then
			$chars * 2 - kwic:string-length($prevTrunc)
		else
			$chars
	let $followingTrunc := kwic:truncate-following($root, $node, (), $remain, 0, $callback)
	return
		if (not($table)) then

				{$prevTrunc}
				{
					if ($config/@link) then
						{ $node/text() }
					else
						{ $node/text() }
				}
				{$followingTrunc}

		else

				{$prevTrunc}

				{
					if ($config/@link) then
						{$node/text()}
					else
						$node/text()
				}

				{$followingTrunc}

};

(:~
    Return all matches within the specified element, $hit. Matches are returned as
    exist:match elements. The returned nodes are part of a new document whose
    root element is a copy of the specified $hit element.

    @param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
:)
declare function kwic:get-matches($hit as element()) as element(exist:match)* {
    let $expanded := util:expand($hit, "expand-xincludes=no")
	return $expanded//exist:match
};

(:~
	Main function of the KWIC module: takes the passed element and returns an
	XHTML fragment containing a chunk of text before and after the first full text
	match in the node.

	The optional config parameter is used to configure the behaviour of the function:

	<config width="character width" table="yes|no" link="URL to which the match is linked"/>

	By default, kwic:summarize returns an XHTML fragment with the following structure:

	<p xmlns="http://www.w3.org/1999/xhtml">
		<span class="previous">Text before match</span>
		<a href="passed URL if any" class="hi">The highlighted term</a>
		<span class="following">Text after match</span>
	</p>

	If table=yes is passed with the config element, a tr table row will be returned instead
	of a span (using the same class names).

	@param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
	@param $config configuration element to configure the behaviour of the function
:)
declare function kwic:summarize($hit as element(), $config as element(config))
as element()* {
    let $expanded := util:expand($hit, "expand-xincludes=no")
	for $match in $expanded//exist:match
	return
		kwic:get-summary($expanded, $match, $config)
};

(:~
    KWIC module: formats query results to display keywords in context (KWIC). A configurable
    amount of text is displayed to the left and right of a matching keyword (or phrase).

    The module works with all indexes that support match highlighting (matches are tagged
    with an <exist:match> element). This includes the old full text index, the new
    Lucene-based full text index, as well as the NGram index.

    The kwic:summarize() function represents the main entry point into the module.
    To have more control over the text extraction context, you can also call
    kwic:get-summary() instead. For example, the following snippet will only print the
    first match within a given set of context nodes ($ancestor):

    let $matches := kwic:get-matches($hit)

    for $ancestor in $matches/ancestor::para | $matches/ancestor::title | $matches/ancestor::td

    return

        kwic:get-summary($ancestor, ($ancestor//exist:match)[1], $config)

:)
module namespace kwic="http://exist-db.org/xquery/kwic";

declare variable $kwic:CHARS_SUMMARY := 120;
declare variable $kwic:CHARS_KWIC := 40;

(:~
	Retrieve the following and preceding text chunks for a given match.

	@param $match the text node containing the match
	@param $mode the selection mode: either "previous" or "following"
:)
declare function kwic:get-context($root as element(), $match as element(exist:match), $mode as xs:string) as node()* {
	let $sibs :=
		if ($mode eq 'previous') then
			$match/preceding::text()
		else
			$match/text()/following::text()
	for $sib in $sibs
	where exists($root[.//$sib])
	return
		if ($sib/parent::exist:match) then
			{$sib}
		else
			$sib
};

(:~
	Like fn:substring, but takes a node argument. If the node is an element,
	a new element is created with the same node-name as the old one and the
	shortened text content.
:)
declare function kwic:substring($node as node(), $start as xs:int, $count as xs:int) as item()? {
	let $str := substring($node, $start, $count)
	return
		if ($node instance of element()) then
			element { node-name($node) } { $str }
		else
			$str
};

declare function kwic:display-text($text as text()?) as node()? {
    if ($text/parent::exist:match) then
    	{$text}
    else
        $text
};

(:~
	Generate the left-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-previous($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int) {
	if ($node) then
		let $next := $node/preceding::text()[1]
		return
			if (empty($root//$next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
				let $remaining := $max - $chars
				return
					("...", kwic:substring($next, string-length($next) - $remaining, $remaining), $truncated)
			else
				kwic:truncate-previous($root, $next, ($next, $truncated),
					$max, $chars + string-length($next))
	else
		$truncated
};

(:~
	Generate the right-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-following($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int) {
	if ($node) then
		let $next := $node/following::text()[1]
		return
			if (empty($root//$next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
				let $remaining := $max - $chars
				return
					($truncated, kwic:substring($next, 1, $remaining), "...")
			else
				kwic:truncate-following($root, $next, ($truncated, $next),
					$max, $chars + string-length($next))
	else
		$truncated
};

(:~
	Computes the total string length of the nodes in the argument sequence
:)
declare function kwic:string-length($nodes as item()*) as xs:integer {
	if (exists($nodes)) then
		sum(for $n in $nodes return string-length($n))
	else
		0
};

(:~
	Print a summary of the match in $node. Output a predefined amount of text to
	the left and the right of the match.

	@param $root root element which should be used as context for the match. It defines the
	    boundaries for the text extraction. Text will be taken from this context.
	@param $node the exist:match element to process.
	@param $config configuration element which determines the behaviour of the function
:)
declare function kwic:get-summary($root as node(), $node as element(exist:match),
	$config as element(config)) as element() {
	let $chars := xs:int($config/@width)
	let $table := $config/@table = ('yes', 'true')
	let $prevTrunc := kwic:truncate-previous($root, $node, (), $chars, 0)
	let $remain :=
		if (not($table)) then
			$chars * 2 - kwic:string-length($prevTrunc)
		else
			$chars
	let $followingTrunc := kwic:truncate-following($root, $node, (), $remain, 0)
	return
		if (not($table)) then

				{$prevTrunc}
				{
					if ($config/@link) then
						{ $node/text() }
					else
						{ $node/text() }
				}
				{$followingTrunc}

		else

				{$prevTrunc}

				{
					if ($config/@link) then
						{$node/text()}
					else
						$node/text()
				}

				{$followingTrunc}

};

(:~
    Return all matches within the specified element, $hit. Matches are returned as
    exist:match elements. The returned nodes are part of a new document whose
    root element is a copy of the specified $hit element.

    @param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
:)
declare function kwic:get-matches($hit as element()) as element(exist:match)* {
    let $expanded := util:expand($hit, "expand-xincludes=no")
	return $expanded//exist:match
};

(:~
	Main function of the KWIC module: takes the passed element and returns an
	XHTML fragment containing a chunk of text before and after the first full text
	match in the node.

	The optional config parameter is used to configure the behaviour of the function:

	<config width="character width" table="yes|no" link="URL to which the match is linked"/>

	By default, kwic:summarize returns an XHTML fragment with the following structure:

	<p xmlns="http://www.w3.org/1999/xhtml">
		<span class="previous">Text before match</span>
		<a href="passed URL if any" class="hi">The highlighted term</a>
		<span class="following">Text after match</span>
	</p>

	If table=yes is passed with the config element, a tr table row will be returned instead
	of a span (using the same class names).

	@param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
	@param $config configuration element to configure the behaviour of the function
:)
declare function kwic:summarize($hit as element(), $config as element(config))
as element()* {
    let $expanded := util:expand($hit, "expand-xincludes=no")
	for $match in $expanded//exist:match
	return
		kwic:get-summary($expanded, $match, $config)
};