improving file module documentation

improving file module documentation

xquery version "3.0";

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

(:~
 : This module implements the file API EXPath specification available at http://expath.org/spec/file
 : @author Gabriel Petrovay, Matthias Brantner, Markus Pilman
 : @see http://expath.org/spec/file
 : @project external
 :)
module namespace file = "http://expath.org/ns/file";

import schema namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace ann = "http://www.zorba-xquery.com/annotations";
declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : Appends a sequence of items to a file. If the file pointed by
$file

 : does not exist, a new file will be created. Before writing to the file, the items
 : are serialized according to the
$serializer-params
.
 :
 : The semantics of
$serializer-params
 is the same as for the
 :
$params
 parameter of the fn:serialize
 : function.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @param $serializer-params Parameter to control the serialization of the
 :    content.
 : @return The empty sequence.
 : @error file:FOFL0004 If
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:append(
  $file as xs:string,
  $content as item()*,
  $serializer-params as element(output:serialization-parameters)?
) as empty-sequence()
{
  file:append-text(
    $file,
    fn:serialize($content, $serializer-params))
};

(:~
 : Appends a sequence of Base64 items as binary to a file. If the file pointed
 : by
$file
 does not exist, a new file will be created.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error file:FOFL0004 If
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:append-binary(
  $file as xs:string,
  $content as xs:base64Binary*
) as empty-sequence() external;

(:~
 : Appends a sequence of string items to a file.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error file:FOFL0004 If
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:append-text(
  $file as xs:string,
  $content as xs:string*
) as empty-sequence() external;

(:~
 : Copies a file or a directory given a source and a destination path/URI.
 :
 : @param $source The path/URI of the file or directory to copy.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0002 If the computed destination points to a file system item
 :    with a different type than the
$source
.
 : @error file:FOFL0003 If
$destination
 does not exist and it's
 :    parent directory does not exist either.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic %ann:sequential function file:copy(
  $source as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  if (file:exists($source)) then
    if (file:is-directory($source)) then
      file:copy-directory-impl($source, $destination)
    else
      file:copy-file-impl($source, $destination)
  else
    fn:error(xs:QName("file:FOFL0001"), fn:concat("The source path does not exists: ", $source))
};

(:~
 : Copies a file given a source and a destination path/URI.
 :
 : @param $sourceFile The path/URI of the file to copy.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0002 If the computed destination points to directory.
 : @error file:FOFL0003 If
$destination
 does not exist and it's
 :    parent directory does not exist either.
 : @error file:FOFL0004 If
$sourceFile
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:copy-file-impl(
  $sourceFile as xs:string,
  $destination as xs:string
) as empty-sequence() external;

(:~
 : Copies a source directory recursively to a destination path/URI.
 :
 : @param $sourceDir The path/URI of the directory to copy.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0002 If
$destination
 points to an existing file.
 : @error file:FOFL0003 If
$destination
 does not exist and it's
 :    parent directory does not exist either.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:copy-directory-impl(
  $sourceDir as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  if (file:is-file($destination)) then
    fn:error(xs:QName("file:FOFL0002"), fn:concat("The specified destination path already exists: ", $destination))
  else if (fn:not(file:exists($destination))) then
    let $dirname := file:dir-name($destination)
    return
      if (fn:not(file:exists($dirname))) then
        fn:error(xs:QName("file:FOFL0003"), fn:concat("The destination directory does not exist: ", $dirname))
      else
        {
          file:create-directory($destination);
          file:copy-directory-content($sourceDir, $destination)
        }

  else
    let $basename := file:base-name($sourceDir)
    let $newdir := fn:concat($destination, file:directory-separator(), $basename)
    return
      {
        file:create-directory($newdir);
        file:copy-directory-content($sourceDir, $newdir)
      }
};

(:~
 : Copies the content of a given directory to an existing destination
 : directory.
 :
 : @param $sourceDir The path/URI of the directory to copy the content from.
 : @param $destination The destination directory path/URI.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0003 If
$destination
 directory does not exist.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:copy-directory-content(
  $sourceDir as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  if (file:is-directory($destination)) then
    for $item in file:list($sourceDir)
    let $fullPath := fn:concat($sourceDir, file:directory-separator(), $item)
    return
      file:copy($fullPath, $destination)
  else
    fn:error(xs:QName("file:FOFL0003"), fn:concat("The specified destination directory does not exist: ", $destination))
};

(:~
 : Creates a directory.
 :
 : The operation is will create all the missing parent directories from the
 : given path.
 :
 : @param $dir The path/URI denoting the directory to be created.
 : @return The empty sequence.
 : @error file:FOFL0002 If the directory cannot be created because of an already
 :    existing file.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:create-directory(
  $dir as xs:string
) as empty-sequence() external;

(:~
 : Deletes a file or a directory from the file system.
 :
 : If
$path
 points to a directory the directory will be deleted
 : recursively.
 :
 : @param $path The path/URI of the file or directory to delete.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$path
 path does not exist.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic %ann:sequential function file:delete(
  $path as xs:string
) as empty-sequence()
{
  if (file:exists($path)) then
    if (file:is-directory($path)) then
      file:delete-directory-impl($path)
    else
      file:delete-file-impl($path)
  else
    fn:error(xs:QName("file:FOFL0001"), fn:concat("The path does not exists: ", $path))
};

(:~
 : Deletes a file from the file system.
 :
 : @param $file The path/URI of the file to delete.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$file
 path does not exist.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:delete-file-impl(
  $file as xs:string
) as empty-sequence() external;

(:~
 : Deletes a directory from the file system.
 :
 : @param $dir The path/URI of the directory to delete.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$dir
 path does not exist.
 : @error file:FOFL0003 If
$dir
 does not point to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:delete-directory-impl(
  $dir as xs:string
) as empty-sequence()
{
  for $item in file:list($dir)
  let $fullPath := fn:concat($dir, file:directory-separator(), $item)
  return
    if (file:is-directory($fullPath)) then
      file:delete-directory-impl($fullPath);
    else
      file:delete-file-impl($fullPath);

  file:delete-file-impl($dir)
};

(:~
 : Tests if a path/URI is already used in the file system.
 :
 : @param $path The path/URI to test for existence.
 : @return true if the path/URI points to an existing file system item.
 :)
declare %ann:nondeterministic function file:exists(
  $path as xs:string
) as xs:boolean external;

(:~
 : Tests if a path/URI points to a directory. On UNIX-based systems, the root
 : and the volume roots are considered directories.
 :
 : @param $dir The path/URI to test.
 : @return true if the path/URI points to a directory.
 :)
declare %ann:nondeterministic function file:is-directory(
  $dir as xs:string
) as xs:boolean external;

(:~
 : Tests if a path/URI points to a file.
 :
 : @param $dir The path/URI to test.
 : @return true if the path/URI points to a file.
 :)
declare %ann:nondeterministic function file:is-file(
  $file as xs:string
) as xs:boolean external;

(:~
 : Moves a file or directory given a source and a destination paths/URIs.
 :
 : @param $source The path/URI of the file to move.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0002 If
$source
 points to a directory and
 :
$destination
 points to an existing file.
 : @error file:FOFL0003 If
$destination
 does not exist and it's parent
 :    directory does not exist either.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:move(
  $source as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  file:copy($source, $destination);
  file:delete($source);
};

(:~
 : Reads the content of a file and returns a Base64 representation of the
 : content.
 :
 : @param $file The file to read.
 : @return The content of the file as Base64.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0004 If
$source
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-binary(
  $file as xs:string
) as xs:base64Binary external;

(:~
 : Reads the content of a file and returns a string representation of the
 : content.
 :
 : The operation is equivalent to calling:
 :
file:read-text($file, "UTF-8")
.
 :
 : @param $file The file to read.
 : @return The content of the file as string.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0004 If
$source
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text(
  $file as xs:string
) as xs:string
{
  file:read-text($file, "UTF-8")
};

(:~
 : Reads the content of a file using the specified encoding and returns a
 : string representation of the content.
 :
 : In Zorba only the following encodings are currently supported: "UTF-8",
 : "UTF8". The encoding parameter is case insensitive.
 :
 : @param $file The file to read.
 : @param $encoding The encoding used when reading the file.
 : @return The content of the file as string.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0004 If
$source
 points to a directory.
 : @error file:FOFL0006 If
$encoding
 is not supported.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text(
  $file as xs:string,
  $encoding as xs:string
) as xs:string external;

(:~
 : Reads the content of a file and returns a sequence of strings representing
 : the lines in the content of the file.
 :
 : The operation is equivalent to calling:
 :
file:read-text-lines($file, "UTF-8")
.
 :
 : @param $file The file to read.
 : @return The content of the file as a sequence of strings.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0004 If
$source
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text-lines(
  $file as xs:string
) as xs:string*
{
  file:read-text-lines($file, "UTF-8")
};

(:~
 : Reads the content of a file using the specified encoding and returns a
 : sequence of strings representing the lines in the content of the file.
 :
 : This implementation considers the LF (
) character as the line
 : separator. If a resulting line ends with the CR (
) character, this is
 : trimmed as well. This implementation will uniformly treat LF and CRLF as
 : line separators.
 :
 : In Zorba only the following encodings are currently supported: "UTF-8",
 : "UTF8". The encoding parameter is case insensitive.
 :
 : @param $file The file to read.
 : @param $encoding The encoding used when reading the file.
 : @return The content of the file as a sequence of strings.
 : @error file:FOFL0001 If the
$source
 path does not exist.
 : @error file:FOFL0004 If
$source
 points to a directory.
 : @error file:FOFL0006 If
$encoding
 is not supported.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text-lines(
  $file as xs:string,
  $encoding as xs:string
) as xs:string*
{
  let $content := file:read-text($file, $encoding)
  return fn:tokenize($content, "\n")
};

(:~
 : This is an internal function that copies an entire source directory to an
 : destination directory. The caller to this function must make sure that both
 : the source and destination point to existing directories.
 :
 : @param $sourceDir The existing source directory.
 : @param $destinationDir The existing destination directory.
 : @return The empty sequence.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:copy-directory(
  $sourceDir as xs:string,
  $destinationDir as xs:string
) as empty-sequence()
{
  let $name := file:base-name($sourceDir)
  let $destDir := fn:concat($destinationDir, file:directory-separator(), $name)
  return
    {
      file:create-directory($destDir);

      for $item in file:list($sourceDir)
      let $fullSrcPath := fn:concat($sourceDir, file:directory-separator(), $item)
      let $fullDestPath := fn:concat($destDir, file:directory-separator(), $item)
      return
        if (file:is-directory($fullSrcPath)) then
          file:copy-directory($fullSrcPath, $fullDestPath)
        else
          file:copy($fullSrcPath, $fullDestPath)
    }
};

(:~
 : Writes a sequence of items to a file. Before writing to the file, the items
 : are serialized according to the
$serializer-params
.
 :
 : The semantics of
$serializer-params
 is the same as for the
 :
$params
 parameter of the fn:serialize
 : function.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @param $serializer-params Parameter to control the serialization of the
 :        content.
 : @return The empty sequence.
 : @error file:FOFL0004 If
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:write(
  $file as xs:string,
  $content as item()*,
  $serializer-params as element(output:serialization-parameters)?
) as empty-sequence()
{
  file:write-text($file, fn:serialize($content, $serializer-params))
};

(:~
 : Writes a sequence of Base64 items as binary to a file.
 :
 : The operation is equivalent to calling:
 :
file:write-binary($file, $content, fn:true())
.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error file:FOFL0004 If
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:write-binary(
  $file as xs:string,
  $content as xs:base64Binary*
) as empty-sequence() external;

(:~
 : Writes a sequence of Base64 items as binary to a file.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error file:FOFL0004 If
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:write-binary(
  $file as xs:string,
  $content as xs:base64Binary*
) as empty-sequence() external;

(:~
 : Writes a sequence of string items to a file.
 :
 : The operation is equivalent to calling:
 :
file:write-text($file, $content, fn:true())
.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error file:FOFL0004 If
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:write-text(
  $file as xs:string,
  $content as xs:string*
) as empty-sequence() external;

(:~
 : Lists the file system items in a certain directory.
 :
 : The operation is equivalent to calling:
 :
file:list($dir, fn:false())
.
 :
 : @param $dir The path/URI of the directory to retrieve the children from.
 : @return The sequence of names of the direct children.
 : @error file:FOFL0003 If
$dir
 does not point to an existing directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:list(
  $dir as xs:string
) as xs:string* external;

(:~
 : Lists the file system items in a certain directory. The order of the items
 : in the resulting sequence is not defined. The "." and ".." items are not
 : returned. The returned paths are relative to the provided
$path
.
 :
 : If
$recursive
 evaluates to
fn:true()
, the operation
 : will recurse in the subdirectories.
 :
 : @param $dir The path/URI to retrieve the children from.
 : @param $recursive A boolean flag indicating whether the operation should be
 :    performed recursively.
 : @return A sequence of relative paths.
 : @error file:FOFL0003 If
$dir
 does not point to an existing directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:list(
  $path as xs:string,
  $recursive as xs:boolean
) as xs:string*
{
  for $f in file:list($path)
  let $full := fn:concat($path, file:directory-separator(), $f)
  return (
    $f,
    if ($recursive and file:is-directory($full)) then
      for $child in file:list($full, $recursive)
      return fn:concat($f, file:directory-separator(), $child)
    else
      ()
  )
};

(:~
 : Lists all files matching the given pattern in a given directory.
 : The order of the items in the result is not defined.
 : The "." and ".." items are not considered for the match.
 : The file paths are relative to the provided $path.
 :
 : The pattern is a glob expression supporting:
 :

 :

*
 for matching any number of unknown characters

 :

?
 for matching one unknown character

 :

 :
 : @param $path The path/URI to retrieve the children from.
 : @param $recursive A boolean flag indicating whether the operation should be
 :    performed recursively.
 : @param $pattern The file name pattern.
 : @return A sequence of file names matching the pattern.
 : @error file:FOFL0003 If
$dir
 does not point to an existing directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:list(
  $path as xs:string,
  $recursive as xs:boolean,
  $pattern as xs:string
) as xs:string* {
  for $file in file:list($path, $recursive)
  let $name := fn:tokenize($file, fn:concat("\", file:directory-separator()))[fn:last()]
  return
    if (fn:matches($name, file:glob-to-regex($pattern))) then
      $file
    else
      ()
};

(:~
 : A helper function that performs a trivial (not complete) glob to regex
 : pattern translation.
 :
 : @param $pattern The glob pattern.
 : @return A regex pattern corresponding to the glob pattern provided.
 :)
declare function file:glob-to-regex(
  $pattern as xs:string
) {
  let $pattern := fn:replace($pattern, '(\.|\[|\]|\\|\/|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\\$1')
  let $pattern := fn:replace($pattern, '\\\?', '.')
  let $pattern := fn:replace($pattern, '\\\*', '.*')
  return
    fn:concat("^", $pattern, "$")
};

(:~
 : Retrieves the timestamp of the last modification of the file system item
 : pointed by the path/URI.
 :
 : @param $path The file system item to read the last modification
 :    timestamp from.
 : @return The date and time of the last modification of the item.
 : @error file:FOFL0001 If the
$path
 does not exist.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:last-modified(
  $path as xs:string
) as xs:dateTime external;

(:~
 : Retrieves the size of a file.
 :
 : @param $file The file get the size.
 : @return An integer representing the size in bytes of the file.
 : @error file:FOFL0001 If the
$file
 does not exist.
 : @error file:FOFL0004 If the
$file
 points to a directory.
 : @error file:FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:size(
  $file as xs:string
) as xs:integer external;

(:~
 : This function returns the value of the operating system specific directory
 : separator. For example,
/
 on UNIX-based systems and
\

 : on Windows systems.
 :
 : @return The operating system specific directory separator.
 :)
declare function file:directory-separator() as xs:string external;

(:~
 : This function returns the value of the operating system specific path
 : separator. For example,
:
 on UNIX-based systems and
;

 : on Windows systems.
 :
 : @return The operating system specific path separator.
 :)
declare function file:path-separator() as xs:string external;

(:~
 : Transforms a relative path/URI into an absolute operating system path by
 : resolving it against the current working directory.
 :
 : No path existence check is made.
 :
 : @param $path The path/URI to transform.
 : @return The operating system file path.
 :)
declare function file:resolve-path(
  $path as xs:string
) as xs:string external;

(:~
 : Transforms a file system path into a URI with the file:// scheme. If the
 : path is relative, it is first resolved against the current working
 : directory.
 :
 : No path existence check is made.
 :
 : @param $path The path to transform.
 : @return The file URI corresponding to
path
.
 :)
declare function file:path-to-uri(
  $path as xs:string
) as xs:anyURI external;

(:~
 : Transforms a URI, an absolute path, or relative path to a native path on the
 : running platform.
 :
 : No path existence check is made.
 :
 : @param $path The uri or path to normalize.
 : @return The native path corresponding to
$path
.
 : @error file:FOFL9999 If an error occurs while trying to obtain the native path.
 :)
declare function file:path-to-native($path as xs:string) as xs:string external;

(:~
 : Returns the last component from the
$path
, deleting any
 : trailing directory-separator characters. If
$path
 consists
 : entirely directory-separator characters, the empty string is returned. If
 :
$path
 is the empty string, the string
"."
 is returned,
 : signifying the current directory.
 :
 : No path existence check is made.
 :
 : @param $path A file path/URI.
 : @return The base name of this file.
 :)
declare function file:base-name($path as xs:string) as xs:string
{
  let $delim := file:directory-separator()
  let $normalized-file :=
    let $n := file:prepare-for-dirname-and-base-name($path)
		return if ($delim eq "\" and fn:matches($n, "^[a-zA-Z]:$")) then
			concat($n, "\")
		else $n
  return
    if (matches($path, concat("^\", $delim, "+$"))) then
      ""
    else if (file:directory-separator() eq '\' and matches($path, "^[a-zA-Z]:\\?$")) then
      ""
    else if ($path eq "") then
      "."
    else
      replace($normalized-file, concat("^.*\", $delim), '')
};

(:~
 : Returns the last component from the
$path
, deleting any
 : trailing directory-separator characters and the
$suffix
. If path
 : consists entirely directory-separator characters, the empty string is
 : returned. If path is the empty string, the string
"."
 is
 : returned, signifying the current directory.
 :
 : No path existence check is made.
 :
 : The
$suffix
 can be used for example to eliminate file extensions.
 :
 : @param $path A file path/URI.
 : @param $suffix A suffix which should get deleted from the result.
 : @return The base-name of $path with a deleted $suffix.
 :)
declare function file:base-name($path as xs:string, $suffix as xs:string) as xs:string
{
  let $res := file:base-name($path)
  return
    if (fn:ends-with($res, $suffix) and $res ne ".") then
      fn:substring($res, 1, fn:string-length($suffix))
    else
      $res
};

(:~
 : This function is the converse of
file:base-name
. It returns a
 : string denoting the parent directory of the
$path
. Any trailing
 : directory-separator characters are not counted as part of the directory
 : name. If path is the empty string or contains no directory-separator string,
 :
"."
 is returned, signifying the current directory.
 :
 : No path existence check is made.
 :
 : @param $path The filename, of which the dirname should be get.
 : @return The name of the directory the file is in.
 :)
declare function file:dir-name($path as xs:string) as xs:string
{
  let $delim := file:directory-separator()
  let $normalized-file := file:prepare-for-dirname-and-base-name($path)
  return
    if (fn:matches($path, concat("^\", $delim, "+$"))) then
      $delim
    else if ($normalized-file eq $delim) then
      $delim
    else if (file:directory-separator() eq '\' and fn:matches($path, "^[a-zA-Z]:\\$")) then
      $path
    else if (file:directory-separator() eq '\' and fn:matches($normalized-file, "^[a-zA-Z]:$")) then
      fn:concat($normalized-file, '\')
    else if ($path eq "") then
      "."
    else if (fn:matches($normalized-file, fn:concat("\", $delim))) then
      fn:replace($normalized-file, fn:concat('^(.*)\', $delim,'.*'), '$1')
    else
      "."
};

(:~
 : This is a helper function used by dirname and base-name. This function takes a path as
 : input and normalizes it according to the rules states in dirname/base-name documentation
 : and normalizes it to a system specific path.
 :)
declare %private function file:prepare-for-dirname-and-base-name($path as xs:string) as xs:string
{
  let $delim := file:directory-separator()
  let $normalize-path := file:path-to-native($path)
  let $normalized :=
    if ($normalize-path eq $delim) then
      $normalize-path
    else
      fn:replace($normalize-path, fn:concat("\", $delim, '+$'), '')
  return $normalized
};

xquery version "3.0";

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

(:~
 : This module implements the file API EXPath specification available at http://expath.org/spec/file
 : @author Gabriel Petrovay, Matthias Brantner, Markus Pilman
 : @see http://expath.org/spec/file
 : @project external
 :)
module namespace file = "http://expath.org/ns/file";

import schema namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace ann = "http://www.zorba-xquery.com/annotations";
declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : Appends a sequence of items to a file. If the file pointed by
$file

 : does not exist, a new file will be created. Before writing to the file, the items
 : are serialized according to the
$serializer-params
.
 :
 : The semantics of
$serializer-params
 is the same as for the
 :
$params
 parameter of the fn:serialize
 : function.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @param $serializer-params Parameter to control the serialization of the
 :    content.
 : @return The empty sequence.
 : @error FOFL0004 If
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:append(
  $file as xs:string,
  $content as item()*,
  $serializer-params as element(output:serialization-parameters)?
) as empty-sequence()
{
  file:append-text(
    $file,
    fn:serialize($content, $serializer-params))
};

(:~
 : Appends a sequence of Base64 items as binary to a file. If the file pointed
 : by
$file
 does not exist, a new file will be created.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error FOFL0004 If
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:append-binary(
  $file as xs:string,
  $content as xs:base64Binary*
) as empty-sequence() external;

(:~
 : Appends a sequence of string items to a file.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error FOFL0004 If
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:append-text(
  $file as xs:string,
  $content as xs:string*
) as empty-sequence() external;

(:~
 : Copies a file or a directory given a source and a destination path/URI.
 :
 : @param $source The path/URI of the file or directory to copy.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0002 If the computed destination points to a file system item
 :    with a different type than the
$source
.
 : @error FOFL0003 If
$destination
 does not exist and it's
 :    parent directory does not exist either.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic %ann:sequential function file:copy(
  $source as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  if (file:exists($source)) then
    if (file:is-directory($source)) then
      file:copy-directory-impl($source, $destination)
    else
      file:copy-file-impl($source, $destination)
  else
    fn:error(xs:QName("file:FOFL0001"), fn:concat("The source path does not exists: ", $source))
};

(:~
 : Copies a file given a source and a destination path/URI.
 :
 : @param $sourceFile The path/URI of the file to copy.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0002 If the computed destination points to directory.
 : @error FOFL0003 If
$destination
 does not exist and it's
 :    parent directory does not exist either.
 : @error FOFL0004 If
$sourceFile
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:copy-file-impl(
  $sourceFile as xs:string,
  $destination as xs:string
) as empty-sequence() external;

(:~
 : Copies a source directory recursively to a destination path/URI.
 :
 : @param $sourceDir The path/URI of the directory to copy.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0002 If
$destination
 points to an existing file.
 : @error FOFL0003 If
$destination
 does not exist and it's
 :    parent directory does not exist either.
 : @error FOFL9999 If any other error occurs.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:copy-directory-impl(
  $sourceDir as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  if (file:is-file($destination)) then
    fn:error(xs:QName("file:FOFL0002"), fn:concat("The specified destination path already exists: ", $destination))
  else if (fn:not(file:exists($destination))) then
    let $dirname := file:dir-name($destination)
    return
      if (fn:not(file:exists($dirname))) then
        fn:error(xs:QName("file:FOFL0003"), fn:concat("The destination directory does not exist: ", $dirname))
      else
        {
          file:create-directory($destination);
          file:copy-directory-content($sourceDir, $destination)
        }

  else
    let $basename := file:base-name($sourceDir)
    let $newdir := fn:concat($destination, file:directory-separator(), $basename)
    return
      {
        file:create-directory($newdir);
        file:copy-directory-content($sourceDir, $newdir)
      }
};

(:~
 : Copies the content of a given directory to an existing destination
 : directory.
 :
 : @param $sourceDir The path/URI of the directory to copy the content from.
 : @param $destination The destination directory path/URI.
 : @return The empty sequence.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0003 If
$destination
 directory does not exist.
 : @error FOFL9999 If any other error occurs.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:copy-directory-content(
  $sourceDir as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  if (file:is-directory($destination)) then
    for $item in file:list($sourceDir)
    let $fullPath := fn:concat($sourceDir, file:directory-separator(), $item)
    return
      file:copy($fullPath, $destination)
  else
    fn:error(xs:QName("file:FOFL0003"), fn:concat("The specified destination directory does not exist: ", $destination))
};

(:~
 : Creates a directory.
 :
 : The operation is will create all the missing parent directories from the
 : given path.
 :
 : @param $dir The path/URI denoting the directory to be created.
 : @return The empty sequence.
 : @error FOFL0002 If the directory cannot be created because of an already
 :    existing file.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:create-directory(
  $dir as xs:string
) as empty-sequence() external;

(:~
 : Deletes a file or a directory from the file system.
 :
 : If
$path
 points to a directory the directory will be deleted
 : recursively.
 :
 : @param $path The path/URI of the file or directory to delete.
 : @return The empty sequence.
 : @error FOFL0001 If the
$path
 path does not exist.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic %ann:sequential function file:delete(
  $path as xs:string
) as empty-sequence()
{
  if (file:exists($path)) then
    if (file:is-directory($path)) then
      file:delete-directory-impl($path)
    else
      file:delete-file-impl($path)
  else
    fn:error(xs:QName("file:FOFL0001"), fn:concat("The path does not exists: ", $path))
};

(:~
 : Deletes a file from the file system.
 :
 : @param $file The path/URI of the file to delete.
 : @return The empty sequence.
 : @error FOFL0001 If the
$file
 path does not exist.
 : @error FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:delete-file-impl(
  $file as xs:string
) as empty-sequence() external;

(:~
 : Deletes a directory from the file system.
 :
 : @param $dir The path/URI of the directory to delete.
 : @return The empty sequence.
 : @error FOFL0001 If the
$dir
 path does not exist.
 : @error FOFL0003 If
$dir
 does not point to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:delete-directory-impl(
  $dir as xs:string
) as empty-sequence()
{
  for $item in file:list($dir)
  let $fullPath := fn:concat($dir, file:directory-separator(), $item)
  return
    if (file:is-directory($fullPath)) then
      file:delete-directory-impl($fullPath);
    else
      file:delete-file-impl($fullPath);

  file:delete-file-impl($dir)
};

(:~
 : Tests if a path/URI is already used in the file system.
 :
 : @param $path The path/URI to test for existence.
 : @return true if the path/URI points to an existing file system item.
 :)
declare %ann:nondeterministic function file:exists(
  $path as xs:string
) as xs:boolean external;

(:~
 : Tests if a path/URI points to a directory. On UNIX-based systems, the root
 : and the volume roots are considered directories.
 :
 : @param $dir The path/URI to test.
 : @return true if the path/URI points to a directory.
 :)
declare %ann:nondeterministic function file:is-directory(
  $dir as xs:string
) as xs:boolean external;

(:~
 : Tests if a path/URI points to a file.
 :
 : @param $dir The path/URI to test.
 : @return true if the path/URI points to a file.
 :)
declare %ann:nondeterministic function file:is-file(
  $file as xs:string
) as xs:boolean external;

(:~
 : Moves a file or directory given a source and a destination paths/URIs.
 :
 : @param $source The path/URI of the file to move.
 : @param $destination The destination path/URI.
 : @return The empty sequence.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0002 If
$source
 points to a directory and
 :
$destination
 points to an existing file.
 : @error FOFL0003 If
$destination
 does not exist and it's parent
 :    directory does not exist either.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:move(
  $source as xs:string,
  $destination as xs:string
) as empty-sequence()
{
  file:copy($source, $destination);
  file:delete($source);
};

(:~
 : Reads the content of a file and returns a Base64 representation of the
 : content.
 :
 : @param $file The file to read.
 : @return The content of the file as Base64.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0004 If
$source
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-binary(
  $file as xs:string
) as xs:base64Binary external;

(:~
 : Reads the content of a file and returns a string representation of the
 : content.
 :
 : The operation is equivalent to calling:
 :
file:read-text($file, "UTF-8")
.
 :
 : @param $file The file to read.
 : @return The content of the file as string.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0004 If
$source
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text(
  $file as xs:string
) as xs:string
{
  file:read-text($file, "UTF-8")
};

(:~
 : Reads the content of a file using the specified encoding and returns a
 : string representation of the content.
 :
 : In Zorba only the following encodings are currently supported: "UTF-8",
 : "UTF8". The encoding parameter is case insensitive.
 :
 : @param $file The file to read.
 : @param $encoding The encoding used when reading the file.
 : @return The content of the file as string.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0004 If
$source
 points to a directory.
 : @error FOFL0006 If
$encoding
 is not supported.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text(
  $file as xs:string,
  $encoding as xs:string
) as xs:string external;

(:~
 : Reads the content of a file and returns a sequence of strings representing
 : the lines in the content of the file.
 :
 : The operation is equivalent to calling:
 :
file:read-text-lines($file, "UTF-8")
.
 :
 : @param $file The file to read.
 : @return The content of the file as a sequence of strings.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0004 If
$source
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text-lines(
  $file as xs:string
) as xs:string*
{
  file:read-text-lines($file, "UTF-8")
};

(:~
 : Reads the content of a file using the specified encoding and returns a
 : sequence of strings representing the lines in the content of the file.
 :
 : This implementation considers the LF (
) character as the line
 : separator. If a resulting line ends with the CR (
) character, this is
 : trimmed as well. This implementation will uniformly treat LF and CRLF as
 : line separators.
 :
 : In Zorba only the following encodings are currently supported: "UTF-8",
 : "UTF8". The encoding parameter is case insensitive.
 :
 : @param $file The file to read.
 : @param $encoding The encoding used when reading the file.
 : @return The content of the file as a sequence of strings.
 : @error FOFL0001 If the
$source
 path does not exist.
 : @error FOFL0004 If
$source
 points to a directory.
 : @error FOFL0006 If
$encoding
 is not supported.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:read-text-lines(
  $file as xs:string,
  $encoding as xs:string
) as xs:string*
{
  let $content := file:read-text($file, $encoding)
  return fn:tokenize($content, "\n")
};

(:~
 : This is an internal function that copies an entire source directory to an
 : destination directory. The caller to this function must make sure that both
 : the source and destination point to existing directories.
 :
 : @param $sourceDir The existing source directory.
 : @param $destinationDir The existing destination directory.
 : @return The empty sequence.
 :)
declare %private %ann:nondeterministic %ann:sequential function file:copy-directory(
  $sourceDir as xs:string,
  $destinationDir as xs:string
) as empty-sequence()
{
  let $name := file:base-name($sourceDir)
  let $destDir := fn:concat($destinationDir, file:directory-separator(), $name)
  return
    {
      file:create-directory($destDir);

      for $item in file:list($sourceDir)
      let $fullSrcPath := fn:concat($sourceDir, file:directory-separator(), $item)
      let $fullDestPath := fn:concat($destDir, file:directory-separator(), $item)
      return
        if (file:is-directory($fullSrcPath)) then
          file:copy-directory($fullSrcPath, $fullDestPath)
        else
          file:copy($fullSrcPath, $fullDestPath)
    }
};

(:~
 : Writes a sequence of items to a file. Before writing to the file, the items
 : are serialized according to the
$serializer-params
.
 :
 : The semantics of
$serializer-params
 is the same as for the
 :
$params
 parameter of the fn:serialize
 : function.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @param $serializer-params Parameter to control the serialization of the
 :        content.
 : @return The empty sequence.
 : @error FOFL0004 If
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:write(
  $file as xs:string,
  $content as item()*,
  $serializer-params as element(output:serialization-parameters)?
) as empty-sequence()
{
  file:write-text($file, fn:serialize($content, $serializer-params))
};

(:~
 : Writes a sequence of Base64 items as binary to a file.
 :
 : The operation is equivalent to calling:
 :
file:write-binary($file, $content, fn:true())
.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error FOFL0004 If
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:write-binary(
  $file as xs:string,
  $content as xs:base64Binary*
) as empty-sequence() external;

(:~
 : Writes a sequence of Base64 items as binary to a file.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error FOFL0004 If
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:sequential function file:write-binary(
  $file as xs:string,
  $content as xs:base64Binary*
) as empty-sequence() external;

(:~
 : Writes a sequence of string items to a file.
 :
 : The operation is equivalent to calling:
 :
file:write-text($file, $content, fn:true())
.
 :
 : @param $file The path/URI of the file to write the content to.
 : @param $content The content to be serialized to the file.
 : @return The empty sequence.
 : @error FOFL0004 If
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %private %ann:sequential function file:write-text(
  $file as xs:string,
  $content as xs:string*
) as empty-sequence() external;

(:~
 : Lists the file system items in a certain directory.
 :
 : The operation is equivalent to calling:
 :
file:list($dir, fn:false())
.
 :
 : @param $dir The path/URI of the directory to retrieve the children from.
 : @return The sequence of names of the direct children.
 : @error FOFL0003 If
$dir
 does not point to an existing directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:list(
  $dir as xs:string
) as xs:string* external;

(:~
 : Lists the file system items in a certain directory. The order of the items
 : in the resulting sequence is not defined. The "." and ".." items are not
 : returned. The returned paths are relative to the provided
$path
.
 :
 : If
$recursive
 evaluates to
fn:true()
, the operation
 : will recurse in the subdirectories.
 :
 : @param $dir The path/URI to retrieve the children from.
 : @param $recursive A boolean flag indicating whether the operation should be
 :    performed recursively.
 : @return A sequence of relative paths.
 : @error FOFL0003 If
$dir
 does not point to an existing directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:list(
  $path as xs:string,
  $recursive as xs:boolean
) as xs:string*
{
  for $f in file:list($path)
  let $full := fn:concat($path, file:directory-separator(), $f)
  return (
    $f,
    if ($recursive and file:is-directory($full)) then
      for $child in file:list($full, $recursive)
      return fn:concat($f, file:directory-separator(), $child)
    else
      ()
  )
};

(:~
 : Lists all files matching the given pattern in a given directory.
 : The order of the items in the result is not defined.
 : The "." and ".." items are not considered for the match.
 : The file paths are relative to the provided $path.
 :
 : The pattern is a glob expression supporting:
 :

 :

*
 for matching any number of unknown characters

 :

?
 for matching one unknown character

 :

 :
 : @param $path The path/URI to retrieve the children from.
 : @param $recursive A boolean flag indicating whether the operation should be
 :    performed recursively.
 : @param $pattern The file name pattern.
 : @return A sequence of file names matching the pattern.
 : @error FOFL0003 If
$dir
 does not point to an existing directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:list(
  $path as xs:string,
  $recursive as xs:boolean,
  $pattern as xs:string
) as xs:string* {
  for $file in file:list($path, $recursive)
  let $name := fn:tokenize($file, fn:concat("\", file:directory-separator()))[fn:last()]
  return
    if (fn:matches($name, file:glob-to-regex($pattern))) then
      $file
    else
      ()
};

(:~
 : A helper function that performs a trivial (not complete) glob to regex
 : pattern translation.
 :
 : @param $pattern The glob pattern.
 : @return A regex pattern corresponding to the glob pattern provided.
 :)
declare function file:glob-to-regex(
  $pattern as xs:string
) {
  let $pattern := fn:replace($pattern, '(\.|\[|\]|\\|\/|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\\$1')
  let $pattern := fn:replace($pattern, '\\\?', '.')
  let $pattern := fn:replace($pattern, '\\\*', '.*')
  return
    fn:concat("^", $pattern, "$")
};

(:~
 : Retrieves the timestamp of the last modification of the file system item
 : pointed by the path/URI.
 :
 : @param $path The file system item to read the last modification
 :    timestamp from.
 : @return The date and time of the last modification of the item.
 : @error FOFL0001 If the
$path
 does not exist.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:last-modified(
  $path as xs:string
) as xs:dateTime external;

(:~
 : Retrieves the size of a file.
 :
 : @param $file The file get the size.
 : @return An integer representing the size in bytes of the file.
 : @error FOFL0001 If the
$file
 does not exist.
 : @error FOFL0004 If the
$file
 points to a directory.
 : @error FOFL9999 If any other error occurs.
 :)
declare %ann:nondeterministic function file:size(
  $file as xs:string
) as xs:integer external;

(:~
 : This function returns the value of the operating system specific directory
 : separator. For example,
/
 on UNIX-based systems and
\

 : on Windows systems.
 :
 : @return The operating system specific directory separator.
 :)
declare function file:directory-separator() as xs:string external;

(:~
 : This function returns the value of the operating system specific path
 : separator. For example,
:
 on UNIX-based systems and
;

 : on Windows systems.
 :
 : @return The operating system specific path separator.
 :)
declare function file:path-separator() as xs:string external;

(:~
 : Transforms a relative path/URI into an absolute operating system path by
 : resolving it against the current working directory.
 :
 : No path existence check is made.
 :
 : @param $path The path/URI to transform.
 : @return The operating system file path.
 :)
declare function file:resolve-path(
  $path as xs:string
) as xs:string external;

(:~
 : Transforms a file system path into a URI with the file:// scheme. If the
 : path is relative, it is first resolved against the current working
 : directory.
 :
 : No path existence check is made.
 :
 : @param $path The path to transform.
 : @return The file URI corresponding to
path
.
 :)
declare function file:path-to-uri(
  $path as xs:string
) as xs:anyURI external;

(:~
 : Transforms a URI, an absolute path, or relative path to a native path on the
 : running platform.
 :
 : No path existence check is made.
 :
 : @param $path The uri or path to normalize.
 : @return The native path corresponding to
$path
.
 : @error FOFL9999 If an error occurs while trying to obtain the native path.
 :)
declare function file:path-to-native($path as xs:string) as xs:string external;

(:~
 : Returns the last component from the
$path
, deleting any
 : trailing directory-separator characters. If
$path
 consists
 : entirely directory-separator characters, the empty string is returned. If
 :
$path
 is the empty string, the string
"."
 is returned,
 : signifying the current directory.
 :
 : No path existence check is made.
 :
 : @param $path A file path/URI.
 : @return The base name of this file.
 :)
declare function file:base-name($path as xs:string) as xs:string
{
  let $delim := file:directory-separator()
  let $normalized-file :=
    let $n := file:prepare-for-dirname-and-base-name($path)
		return if ($delim eq "\" and fn:matches($n, "^[a-zA-Z]:$")) then
			concat($n, "\")
		else $n
  return
    if (matches($path, concat("^\", $delim, "+$"))) then
      ""
    else if (file:directory-separator() eq '\' and matches($path, "^[a-zA-Z]:\\?$")) then
      ""
    else if ($path eq "") then
      "."
    else
      replace($normalized-file, concat("^.*\", $delim), '')
};

(:~
 : Returns the last component from the
$path
, deleting any
 : trailing directory-separator characters and the
$suffix
. If path
 : consists entirely directory-separator characters, the empty string is
 : returned. If path is the empty string, the string
"."
 is
 : returned, signifying the current directory.
 :
 : No path existence check is made.
 :
 : The
$suffix
 can be used for example to eliminate file extensions.
 :
 : @param $path A file path/URI.
 : @param $suffix A suffix which should get deleted from the result.
 : @return The base-name of $path with a deleted $suffix.
 :)
declare function file:base-name($path as xs:string, $suffix as xs:string) as xs:string
{
  let $res := file:base-name($path)
  return
    if (fn:ends-with($res, $suffix) and $res ne ".") then
      fn:substring($res, 1, fn:string-length($suffix))
    else
      $res
};

(:~
 : This function is the converse of
file:base-name
. It returns a
 : string denoting the parent directory of the
$path
. Any trailing
 : directory-separator characters are not counted as part of the directory
 : name. If path is the empty string or contains no directory-separator string,
 :
"."
 is returned, signifying the current directory.
 :
 : No path existence check is made.
 :
 : @param $path The filename, of which the dirname should be get.
 : @return The name of the directory the file is in.
 :)
declare function file:dir-name($path as xs:string) as xs:string
{
  let $delim := file:directory-separator()
  let $normalized-file := file:prepare-for-dirname-and-base-name($path)
  return
    if (fn:matches($path, concat("^\", $delim, "+$"))) then
      $delim
    else if ($normalized-file eq $delim) then
      $delim
    else if (file:directory-separator() eq '\' and fn:matches($path, "^[a-zA-Z]:\\$")) then
      $path
    else if (file:directory-separator() eq '\' and fn:matches($normalized-file, "^[a-zA-Z]:$")) then
      fn:concat($normalized-file, '\')
    else if ($path eq "") then
      "."
    else if (fn:matches($normalized-file, fn:concat("\", $delim))) then
      fn:replace($normalized-file, fn:concat('^(.*)\', $delim,'.*'), '$1')
    else
      "."
};

(:~
 : This is a helper function used by dirname and base-name. This function takes a path as
 : input and normalizes it according to the rules states in dirname/base-name documentation
 : and normalizes it to a system specific path.
 :)
declare %private function file:prepare-for-dirname-and-base-name($path as xs:string) as xs:string
{
  let $delim := file:directory-separator()
  let $normalize-path := file:path-to-native($path)
  let $normalized :=
    if ($normalize-path eq $delim) then
      $normalize-path
    else
      fn:replace($normalize-path, fn:concat("\", $delim, '+$'), '')
  return $normalized
};


[bugfix] wrong netedit jar name

[bugfix] wrong netedit jar name

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

xquery version "1.0";

import module namespace jnlp    = "http://exist-db.org/xquery/jnlp" at "jnlp.xqm";
import module namespace request = "http://exist-db.org/xquery/request";

declare variable $jnlp {

    <jnlp spec="1.0+" href="gate.jnlp">
      <information>
        <title>eXist Gate client</title>
        <vendor>exist-db.org</vendor>
        <homepage href="http://exist-db.org/"/>
        <description>Java applet for automate document managment via REST.</description>
        <description kind="short">eXist Gate client</description>
        <description kind="tooltip">eXist Gate client</description>
        <icon href="/exist/resources/jnlp_logo.jpg" kind="splash"/>
        <icon href="/exist/resources/jnlp_logo.jpg"/>
        <icon href="/exist/resources/jnlp_icon_64x64.gif" width="64" height="64"/>
        <icon href="/exist/resources/jnlp_icon_32x32.gif" width="32" height="32"/>
      </information>
      <security>
        <all-permissions/>
      </security>
      <resources>
        <j2se version="1.6+"/>
        <jar href="exist-netedit.jar" main="true"/>
        <jar href="commons-codec-%latest%.jar"/>
        <jar href="commons-httpclient-%latest%.jar"/>
        <jar href="commons-logging-%latest%.jar"/>
      </resources>
      <applet-desc documentBase="{request:get-attribute("codebase")}"
          name="Gate" main-class="org.exist.gate.GateApplet" width="1" height="1"/>
    </jnlp>

};

jnlp:prepare($jnlp)

xquery version "1.0";

import module namespace jnlp    = "http://exist-db.org/xquery/jnlp" at "jnlp.xqm";
import module namespace request = "http://exist-db.org/xquery/request";

declare variable $jnlp {

    <jnlp spec="1.0+" href="gate.jnlp">
      <information>
        <title>eXist Gate client</title>
        <vendor>exist-db.org</vendor>
        <homepage href="http://exist-db.org/"/>
        <description>Java applet for automate document managment via REST.</description>
        <description kind="short">eXist Gate client</description>
        <description kind="tooltip">eXist Gate client</description>
        <icon href="/exist/resources/jnlp_logo.jpg" kind="splash"/>
        <icon href="/exist/resources/jnlp_logo.jpg"/>
        <icon href="/exist/resources/jnlp_icon_64x64.gif" width="64" height="64"/>
        <icon href="/exist/resources/jnlp_icon_32x32.gif" width="32" height="32"/>
      </information>
      <security>
        <all-permissions/>
      </security>
      <resources>
        <j2se version="1.6+"/>
        <jar href="exist-gate.jar" main="true"/>
        <jar href="commons-codec-%latest%.jar"/>
        <jar href="commons-httpclient-%latest%.jar"/>
        <jar href="commons-logging-%latest%.jar"/>
      </resources>
      <applet-desc documentBase="{request:get-attribute("codebase")}"
          name="Gate" main-class="org.exist.gate.GateApplet" width="1" height="1"/>
    </jnlp>

};

jnlp:prepare($jnlp)


refactored. no context information displayed now.

xquery version "1.0";

(: ~
 : Module Name:             new conceptual domain webpage and XQuery
 :
 : Module overview          Creates a new Organization
 :)

(:~
 :    @author Rakesh Dhaval
 :    @version 0.1
~ :)

  import module namespace
  lib-forms="http://www.cagrid.org/xquery/library/forms"
  at "../edit/m-lib-forms.xquery";

  import module namespace
  lib-util="http://www.cagrid.org/xquery/library/util"
  at "../library/m-lib-util.xquery";

  import module namespace
  lib-rendering="http://www.cagrid.org/xquery/library/rendering"
  at "../web/m-lib-rendering.xquery";

  import module namespace
    lib-make-admin-item="http://www.cagrid.org/xquery/library/make-admin-item"
    at "../edit/m-lib-make-admin-item.xquery";

declare namespace openMDR = "http://www.cagrid.org/schema/openMDR";
declare namespace ISO11179= "http://www.cagrid.org/schema/ISO11179";
declare namespace session="http://exist-db.org/xquery/session";
declare namespace response="http://exist-db.org/xquery/response";
declare namespace exist = "http://exist.sourceforge.net/NS/exist";

declare function local:organisation(
   $reg-auth as xs:string,
   $administrative-note as xs:string,
   $administrative-status as xs:string,
   $administered-by as xs:string,
   $submitted-by as xs:string,
   $registered-by as xs:string,

   $organization_name as xs:string?,
   $organization_mail_address as xs:string?,
   $contact_name as xs:string?,
   $contact_title as xs:string?,
   $contact_information as xs:string?

   ) as xs:boolean
{
   let $version := '0.1'
   let $data-identifier := lib-forms:generate-id()
   let $new-identifier := concat($reg-auth, '-', $data-identifier, '-', $version)
   let $doc-name := concat($new-identifier,'.xml')

	 let $organization-identifier := lib-forms:generate-id()
	 let $contact-identifier := lib-forms:generate-id()

   let $content := (
            lib-make-admin-item:administration-record($administrative-note,$administrative-status,'Recorded'),
            lib-make-admin-item:custodians($administered-by,$registered-by,$submitted-by)

    )

   (: compose the document :)
   let $document :=
            element openMDR:Organization {
     										attribute organization_identifier{$organization-identifier},
           							        element openMDR:organization_name{$organization_name},
                							element openMDR:organization_mail_address{$organization_mail_address},
                							element openMDR:Contact {
    							            attribute contact_identifier{$contact-identifier},
    							            element openMDR:contact_name{$contact_name},
    							            element openMDR:contact_title{$contact_title},
    							            element openMDR:contact_information{$contact_information}
    							            }
							             }

   let $collection := 'organisation'
   let $message := lib-forms:store-document($document)
   return
      if ($message='stored')
      then true()
      else response:redirect-to(xs:anyURI(concat("../web/login.xquery?calling_page=newOrganisation.xquery&",$message)))
};

declare function local:input-page(
   $message as xs:string?,
   $reg-auth as xs:string?,
   $administrative-note  as xs:string?,
   $administrative-status  as xs:string?,
   $administered-by  as xs:string?,
   $submitted-by  as xs:string?,
   $registered-by  as xs:string?,

   $org_name as xs:string?,
   $org_mail_address as xs:string?,
   $contact-name as xs:string?,
   $contact-title as xs:string?,
   $contact-information as xs:string?,
   $action as xs:string?

   ) {
   let $skip-name := substring-after($action,'delete naming entry')
   let $skip-name-index := if ($skip-name>'') then xs:int($skip-name) else 0

   return

                This form will allow you to create a new Organisation in the metadata repository

          <form name="new_organisation" action="newOrganisation.xquery" method="post" class="cagridForm" enctype="multipart/form-data">

             {lib-forms:edit-admin-item-only($reg-auth,
                     $administrative-note,
                     $administrative-status,
                     $administered-by,
                     $submitted-by,
                     $registered-by,

                     $action)}

Organization

                {

                  	Organization Name
                    <input type="text" name="org_name"></input>

,

                  	Organization Email Address
                    <input type="text" name="org_mail_address"></input>

              	}

Contact

                {

                  	Name
                    <input type="text" name="contact-name"></input>

,

                  	Title
                    <input type="text" name="contact-title"></input>

,

                  	Information
                    <input type="text" name="contact-information"></input>

              	}

                      	<input type="submit" name="update" value="Store"/>
                      	<input type="submit" name="update" value="Clear"/>

          </form>

{$message}

   };

declare function local:success-page()
{
   let $calling-page := request:get-parameter("calling-page","")
   return

Organization created

Return to maintenance menu

Create another Organization

};

declare option exist:serialize "media-type=text/html method=xhtml doctype-public=-//W3C//DTD XHTML 1.0 Transitional//EN doctype-system=http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-transitional.dtd";

   session:create(),
   let $title as xs:string := "Creating a New Organisation"
   let $reg-auth := request:get-parameter('registration-authority','')
   let $administrative-note := request:get-parameter('administrative-note','')
   let $administrative-status := request:get-parameter('administrative-status','')
   let $administered-by := request:get-parameter('administered-by','')
   let $submitted-by := request:get-parameter('submitted-by','')
   let $registered-by := request:get-parameter('registered-by','')

   let $organization_name :=request:get-parameter('org_name','')
   let $organization_mail_address :=request:get-parameter('org_mail_address','')
   let $contact_name :=request:get-parameter('contact-name','')
   let $contact_title :=request:get-parameter('contact-title','')
   let $contact_information :=request:get-parameter('contact-information','')
   let $action := request:get-parameter('update','')

   return

      lib-rendering:txfrm-webpage(
      $title,
      if ($action='Store')
      then
         (
         if (
               local:organisation
                  (
                     $reg-auth,
                     $administrative-note,
                     $administrative-status,
                     $administered-by,
                     $submitted-by,
                     $registered-by,

                     $organization_name,
                     $organization_mail_address,
                     $contact_name,
                     $contact_title,
                     $contact_information
                  )
            )
         then local:success-page()
         else (local:input-page(
            'could not store document',
                     $reg-auth,
                     $administrative-note,
                     $administrative-status,
                     $administered-by,
                     $submitted-by,
                     $registered-by,

                     $organization_name,
                     $organization_mail_address,
                     $contact_name,
                     $contact_title,
                     $contact_information,
                     $action
                  )
               )
         )
          else local:input-page(
                     '',
                     $reg-auth,
                     $administrative-note,
                     $administrative-status,
                     $administered-by,
                     $submitted-by,
                     $registered-by,

                     $organization_name,
                     $organization_mail_address,
                     $contact_name,
                     $contact_title,
                     $contact_information,
                     $action
                  )
         )

xquery version "1.0";

(: ~
 : Module Name:             new context webpage and XQuery
 :
 : Module Version           2.0
 :
 : Date                     25 March 2009
 :
 : Copyright                caGrid
 :
 : Module overview          Creates a new context
 :
 :)

(:~
 :    @author Steve Harris
 :    @version 0.1
 :
 :    @author Steve Harris
 :    @version 2.0
 :     now allows searching for concept terms
~ :)

  import module namespace
  lib-forms="http://www.cagrid.org/xquery/library/forms"
  at "../edit/m-lib-forms.xquery";

  import module namespace
  lib-util="http://www.cagrid.org/xquery/library/util"
  at "../library/m-lib-util.xquery";

  import module namespace
  lib-rendering="http://www.cagrid.org/xquery/library/rendering"
  at "../web/m-lib-rendering.xquery";

declare namespace openMDR = "http://www.cagrid.org/schema/openMDR";
declare namespace ISO11179= "http://www.cagrid.org/schema/ISO11179";
declare namespace session="http://exist-db.org/xquery/session";
declare namespace response="http://exist-db.org/xquery/response";
declare namespace exist = "http://exist.sourceforge.net/NS/exist";

declare function local:context(
   $reg-auth as xs:string,
   $administrative-note as xs:string,
   $administrative-status as xs:string,
   $administered-by as xs:string,
   $submitted-by as xs:string,
   $registered-by as xs:string,
   $country-identifiers as xs:string*,
   $language-identifiers as xs:string*,
   $names as xs:string*,
   $definitions as xs:string*,
   $sources as xs:string*,
   $preferred as xs:string?
   ) as xs:boolean
{
   let $version := '0.1'
   let $data-identifier := lib-forms:generate-id()
   let $new-identifier := concat($reg-auth, '-', $data-identifier, '-', $version)
   let $doc-name := concat($new-identifier,'.xml')

   let $content := (
            element openMDR:administered_item_administration_record {
               element openMDR:administrative_note {$administrative-note},
               element openMDR:administrative_status {$administrative-status},
               element openMDR:creation_date {current-date()},
               element openMDR:effective_date {current-date()},
               element openMDR:last_change_date {current-date()},
               element openMDR:registration_status {'Recorded'}
               },
               element openMDR:administered_by {$administered-by},
               element openMDR:registered_by {$registered-by},
               element openMDR:submitted_by {$submitted-by},

               for $name at $pos in $names
               return
                  element openMDR:having {
                     element openMDR:context_identifier {$new-identifier},
                     element openMDR:containing {
                        element openMDR:language_section_language_identifier {
                           element openMDR:country_identifier {$country-identifiers[$pos]},
                           element openMDR:language_identifier {$language-identifiers[$pos]}
                           },
                        element openMDR:name {$name},
                        element openMDR:definition_text {$definitions[$pos]},
                        element openMDR:preferred_designation {(xs:int($preferred) = xs:int($pos))},
                        element openMDR:definition_source_reference {$sources[$pos]}
                        }
                  }

    )

   (: compose the document :)
   let $document :=
      element openMDR:Context {
            attribute item_registration_authority_identifier {$reg-auth},
            attribute data_identifier {$data-identifier},
            attribute version {$version},
            $content
           }

   let $collection := 'context'
   let $message := lib-forms:store-document($document)
   return
      if ($message='stored')
      then true()
      else response:redirect-to(xs:anyURI(concat("login.xquery?calling_page=newContext.xquery&",$message)))
};

declare function local:input-page(
   $message as xs:string?,
   $reg-auth as xs:string?,
   $administrative-note  as xs:string?,
   $administrative-status  as xs:string?,
   $administered-by  as xs:string?,
   $submitted-by  as xs:string?,
   $registered-by  as xs:string?,
   $country-identifiers as xs:string*,
   $language-identifiers as xs:string*,
   $names as xs:string*,
   $definitions as xs:string*,
   $sources as xs:string*,
   $action as xs:string?,
   $preferred as xs:string?
   ) {
   let $skip-name := substring-after($action,'delete naming entry')
   let $skip-name-index := if ($skip-name>'') then xs:int($skip-name) else 0

   return

                This form will allow you to create a new context in the metadata repository

          <form name="new_context" action="newContext.xquery" method="post" class="cagridForm" enctype="multipart/form-data">

                {

New naming entry
,

                       Name

                          {lib-forms:input-element('names',70,'')}

,

                       Definition
                       {lib-forms:text-area-element('definitions', 5, 70, '')}

,

                       Language Identifier

                          {lib-forms:select-from-simpleType-enum('Country_Identifier','country-identifiers', false(), '')}
                          {lib-forms:select-from-simpleType-enum('Language_Identifier','language-identifiers', false(), '')}

,

                       Source
                       {lib-forms:input-element('sources',70,'')}

                  }

Context metadata

Registration Authority	 {lib-forms:make-select-registration-authority('')}

Registered by	 {lib-forms:make-select-registered_by('')}

Administered by	 {lib-forms:make-select-administered_by('')}

Submitted by	 {lib-forms:make-select-submitted_by($submitted-by)}

Administrative Status	{lib-forms:select-from-simpleType-enum('Administrative_Status','administrative-status', false(), $administrative-status)}

Administrative Note	{lib-forms:text-area-element('administrative-note', 5, 70,$administrative-note)}

	<input type="submit" name="update" value="Store"/>	<input type="submit" name="update" value="Clear"/>

          </form>

{$message}

   };

declare function local:success-page()
{
   let $calling-page := request:get-parameter("calling-page","")
   return

Context class created

Return to maintenance menu

Create another context

};

declare option exist:serialize "media-type=text/html method=xhtml doctype-public=-//W3C//DTD XHTML 1.0 Transitional//EN doctype-system=http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-transitional.dtd";

   session:create(),
   let $title as xs:string := "Creating a New Context"
   let $reg-auth := request:get-parameter('registration-authority',())
   let $administrative-note := request:get-parameter('administrative-note',())
   let $administrative-status := request:get-parameter('administrative-status','')
   let $administered-by := request:get-parameter('administered-by','')
   let $submitted-by := request:get-parameter('submitted-by','')
   let $registered-by := request:get-parameter('registered-by','')
   let $country-identifiers := request:get-parameter('country-identifiers','')
   let $language-identifiers := request:get-parameter('language-identifiers','')
   let $names := request:get-parameter('names',())
   let $definitions := request:get-parameter('definitions',())
   let $sources := request:get-parameter('sources',())
   let $preferred := request:get-parameter('preferred',())
   let $action := request:get-parameter('update','')

   return

      lib-rendering:txfrm-webpage(
      $title,
      if ($action='Store')
      then
         (
         if (
               local:context
                  (
                     $reg-auth,
                     $administrative-note,
                     $administrative-status,
                     $administered-by,
                     $submitted-by,
                     $registered-by,
                     $country-identifiers,
                     $language-identifiers,
                     $names,
                     $definitions,
                     $sources,
                     $preferred
                  )
            )
         then local:success-page()
         else (local:input-page(
            'could not store document',
                     $reg-auth,
                     $administrative-note,
                     $administrative-status,
                     $administered-by,
                     $submitted-by,
                     $registered-by,
                     $country-identifiers,
                     $language-identifiers,
                     $names,
                     $definitions,
                     $sources,
                     $action,
                     $preferred
                  )
               )
         )
      else local:input-page
               (
               '',
               $reg-auth,
               $administrative-note,
               $administrative-status,
               $administered-by,
               $submitted-by,
               $registered-by,
               $country-identifiers,
               $language-identifiers,
               $names,
               $definitions,
               $sources,
               $action,
               $preferred
               )
         )


normalize careServicesRequest element so it is a csd:careServicesRequest

(:~
: This is the Care Services Discovery RESTful document processor
: @version 1.0
: @see https://github.com/his-interop/openinfoman
:
:)
module namespace csr_proc = "https://github.com/his-interop/openinfoman/csr_proc";

(:import module namespace csd_bsq = "https://github.com/his-interop/openinfoman/csd_bsq" at "csd_base_stored_queries.xqm";
import module namespace csd_hwr = "https://github.com/his-interop/openinfoman-hwr/csd_hwr" at "csd_health_worker_registry.xqm";
import module namespace csd_hwru = "https://github.com/his-interop/openinfoman-hwr/csd_hwru" at "csd_health_worker_registry_updating.xqm";
:)

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

(:this should proabably be moved to the database :)
declare variable $csr_proc:stored_query_dir := "../resources/stored_query_definitions";
declare variable $csr_proc:stored_updating_query_dir := "../resources/stored_updating_query_definitions";

declare variable $csr_proc:stored_functions_doc := 'stored_functions.xml';
declare variable $csr_proc:stored_updating_functions_doc := 'stored_updating_functions.xml';

declare function csr_proc:stored_functions($db) {
 db:open($db,$csr_proc:stored_functions_doc)/careServicesFunctions/csd:careServicesFunction
};

declare function csr_proc:stored_updating_functions($db) {
 db:open($db,$csr_proc:stored_updating_functions_doc)/careServicesFunctions/csd:careServicesFunction
};

declare updating function csr_proc:init($db) {
  (
    if (not (db:is-xml($db,$csr_proc:stored_updating_functions_doc))) then
      db:add($db, <careServicesFunctions/>,$csr_proc:stored_updating_functions_doc)
    else
      ()
      ,
    if (not (db:is-xml($db,$csr_proc:stored_functions_doc))) then
      db:add($db, <careServicesFunctions/>,$csr_proc:stored_functions_doc)
    else
      ()
  )
};

declare updating function csr_proc:clear_stored_functions($db) {
  (
    db:delete($db,$csr_proc:stored_functions_doc)
    ,db:delete($db,$csr_proc:stored_updating_functions_doc)
    ,csr_proc:init($db)
    )

};

declare updating function csr_proc:load_functions_from_files($db) {
  (
    if (file:is-dir($csr_proc:stored_query_dir)) then
      for $file in file:list($csr_proc:stored_query_dir,boolean('false'),"*.xml")
      let $func := doc(concat($csr_proc:stored_query_dir,'/',$file))/csd:careServicesFunction
      return if (exists($func)) then csr_proc:load_stored_function($db,$func) else ()
    else()
      ,
    if (file:is-dir($csr_proc:stored_updating_query_dir)) then
      for $file in file:list($csr_proc:stored_updating_query_dir,boolean('false'),"*.xml")
      let $func := doc(concat($csr_proc:stored_updating_query_dir,'/',$file))/csd:careServicesFunction
      return if (exists($func)) then csr_proc:load_stored_updating_function($db,$func) else ()
    else ()
  )
};

declare updating function csr_proc:delete_stored_function($db,$uuid) {
  let $stored_updating_functions := db:open($db,$csr_proc:stored_updating_functions_doc)/careServicesFunctions/*
  let $stored_functions := db:open($db,$csr_proc:stored_functions_doc)/careServicesFunctions/*
  let $functions := ($stored_functions,$stored_updating_functions)

  let $old := $functions[@uuid = $uuid]
  return if ($old) then delete node $old else ()

};

declare updating function csr_proc:load_stored_updating_function($db,$func) {
  let $stored_updating_functions := db:open($db,$csr_proc:stored_updating_functions_doc)/careServicesFunctions
  let $uuid := $func/@uuid
  let $old := $stored_updating_functions/csd:careServicesFunction[@uuid = $uuid]
  return
    if (exists($uuid)) then
      if (exists($old))
	then
	(delete node $old,
	insert node $func into $stored_updating_functions
	)
      else
	insert node $func into $stored_updating_functions
  else ()
};

declare updating function csr_proc:load_stored_function($db,$func) {
  let $stored_functions := db:open($db,$csr_proc:stored_functions_doc)/careServicesFunctions
  let $uuid := $func/@uuid
  let $old := $stored_functions/csd:careServicesFunction[@uuid = $uuid]
  return
    if (exists($uuid) and exists($stored_functions)) then
      if (exists($old))
	then
	(delete node $old,
	insert node $func into $stored_functions
	)
      else
	insert node $func into $stored_functions
    else ()
};

declare function csr_proc:process_CSR($db,$careServicesRequest, $doc)
{
let $func :=$careServicesRequest/csd:function
let $adhoc :=$careServicesRequest/csd:expression
return if (exists($func))
then
  csr_proc:process_CSR_stored($db,$func,$doc)
else if (exists($adhoc))
then
  csr_proc:process_CSR_adhoc($adhoc,$doc)
else
 (:
  <rest:response>
    <http:response status="400" message="Invalid care services request.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>
  :)
  $careServicesRequest

};

declare function csr_proc:process_CSR_adhoc($expression,$doc)
{

let $expr :=string($expression)
return if ($expr) then
  let $result := xquery:eval($expr,map{"":=$doc})
  return(
   <rest:response>
   <http:response status="200" >
      <http:header name="Content-Type" value="{$expression/@content-type}"/>
    </http:response>
  </rest:response>
  ,$result
 )
else
   <rest:response>
     <http:response status="400" message="No ad-hoc expression" />
  </rest:response>
};

declare function csr_proc:process_CSR_stored($db,$function,$doc)
{
let $uuid := $function/@uuid
let $stored_functions := csr_proc:stored_functions($db)
let $definition := $stored_functions[@uuid = $uuid][1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@uuid)
let $requestParams :=
  if ($function/csd:requestParams) then $function/csd:requestParams
  else if ($function/requestParams) then <csd:requestParams>{$function/requestParams/*}</csd:requestParams>
  else <csd:requestParams/>
let $result0 :=
  if (exists($definition)) then
    xquery:eval($definition,map{'':=$doc,'careServicesRequest':=$requestParams})
  else
    ()
let $result1:=
  if ($function/@encapsulated) then
    csr_proc:wrap_result($result0,$content_type)
  else $result0
return if ($result0) then
  (
  <rest:response>
    <http:response status="200" >
      <http:header name="Content-Type" value="{$content_type}"/>
    </http:response>
  </rest:response>
  ,
  $result1
  )
else
  <rest:response>
    <http:response status="404" message="No registered function with UUID='{$function/@uuid}'.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>

};

declare function csr_proc:wrap_result($result,$content-type) {
 <csd:careServicesResponse content-type="{$content-type}"><csd:result>{$result}</csd:result></csd:careServicesResponse>
};

declare function csr_proc:create_adhoc_doc($adhoc_query,$content_type) {
let $content := if ($content_type) then $content_type else "application/xml"
return
 <csd:careServicesRequest xmlns:csd='urn:ihe:iti:csd:2013'>
  <csd:expression content-type='{$content}'>
  {$adhoc_query}
  </csd:expression>
</csd:careServicesRequest>

};

declare function csr_proc:get_function_definition($db,$uuid) {
  csr_proc:stored_functions($db)[@uuid = $uuid][1]
};

declare function csr_proc:get_updating_function_definition($db,$uuid) {
  csr_proc:stored_updating_functions($db)[@uuid = $uuid][1]
};

declare updating function csr_proc:process_updating_CSR($db,$careServicesRequest, $doc)
{
(:not allowing ad-hoc updates:)
let $function :=$careServicesRequest//csd:function
let $uuid := $function/@uuid
let $stored_updating_functions := csr_proc:stored_updating_functions($db)
let $definition := $stored_updating_functions[@uuid = $uuid][1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@uuid)
let $requestParams :=
  if ($function/requestParams) then $function/requestParams
  else if ($function/csd:requestParams) then $function/csd:requestParams
  else <requestParams/>
return if (exists($definition)) then
  xquery:update($definition,map{'':=$doc,'careServicesRequest':=$requestParams})
else
    db:output(
    <rest:response>
      <http:response status="404" message="No registered updating function with UUID='{$function/@uuid} ">
	<http:header name="Content-Language" value="en"/>
	<http:header name="Content-Type" value="text/html; charset=utf-8"/>
      </http:response>
    </rest:response>
       )
};

declare function csr_proc:lookup_stored_content_type($db,$uuid)
{
  let $stored_updating_functions := csr_proc:stored_updating_functions($db)
  let $stored_functions := csr_proc:stored_functions($db)
  return string(($stored_functions[@uuid = $uuid]/@content-type, $stored_updating_functions[@uuid = $uuid]/@content-type  , "text/xml")[1])
};

(:~
: This is the Care Services Discovery RESTful document processor
: @version 1.0
: @see https://github.com/his-interop/openinfoman
:
:)
module namespace csr_proc = "https://github.com/his-interop/openinfoman/csr_proc";

(:import module namespace csd_bsq = "https://github.com/his-interop/openinfoman/csd_bsq" at "csd_base_stored_queries.xqm";
import module namespace csd_hwr = "https://github.com/his-interop/openinfoman-hwr/csd_hwr" at "csd_health_worker_registry.xqm";
import module namespace csd_hwru = "https://github.com/his-interop/openinfoman-hwr/csd_hwru" at "csd_health_worker_registry_updating.xqm";
:)

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

(:this should proabably be moved to the database :)
declare variable $csr_proc:stored_query_dir := "../resources/stored_query_definitions";
declare variable $csr_proc:stored_updating_query_dir := "../resources/stored_updating_query_definitions";

declare variable $csr_proc:stored_functions_doc := 'stored_functions.xml';
declare variable $csr_proc:stored_updating_functions_doc := 'stored_updating_functions.xml';

declare function csr_proc:stored_functions($db) {
 db:open($db,$csr_proc:stored_functions_doc)/careServicesFunctions/csd:careServicesFunction
};

declare function csr_proc:stored_updating_functions($db) {
 db:open($db,$csr_proc:stored_updating_functions_doc)/careServicesFunctions/csd:careServicesFunction
};

declare updating function csr_proc:init($db) {
  (
    if (not (db:is-xml($db,$csr_proc:stored_updating_functions_doc))) then
      db:add($db, <careServicesFunctions/>,$csr_proc:stored_updating_functions_doc)
    else
      ()
      ,
    if (not (db:is-xml($db,$csr_proc:stored_functions_doc))) then
      db:add($db, <careServicesFunctions/>,$csr_proc:stored_functions_doc)
    else
      ()
  )
};

declare updating function csr_proc:clear_stored_functions($db) {
  (
    db:delete($db,$csr_proc:stored_functions_doc)
    ,db:delete($db,$csr_proc:stored_updating_functions_doc)
    ,csr_proc:init($db)
    )

};

declare updating function csr_proc:load_functions_from_files($db) {
  (
    if (file:is-dir($csr_proc:stored_query_dir)) then
      for $file in file:list($csr_proc:stored_query_dir,boolean('false'),"*.xml")
      let $func := doc(concat($csr_proc:stored_query_dir,'/',$file))/csd:careServicesFunction
      return if (exists($func)) then csr_proc:load_stored_function($db,$func) else ()
    else()
      ,
    if (file:is-dir($csr_proc:stored_updating_query_dir)) then
      for $file in file:list($csr_proc:stored_updating_query_dir,boolean('false'),"*.xml")
      let $func := doc(concat($csr_proc:stored_updating_query_dir,'/',$file))/csd:careServicesFunction
      return if (exists($func)) then csr_proc:load_stored_updating_function($db,$func) else ()
    else ()
  )
};

declare updating function csr_proc:delete_stored_function($db,$uuid) {
  let $stored_updating_functions := db:open($db,$csr_proc:stored_updating_functions_doc)/careServicesFunctions/*
  let $stored_functions := db:open($db,$csr_proc:stored_functions_doc)/careServicesFunctions/*
  let $functions := ($stored_functions,$stored_updating_functions)

  let $old := $functions[@uuid = $uuid]
  return if ($old) then delete node $old else ()

};

declare updating function csr_proc:load_stored_updating_function($db,$func) {
  let $stored_updating_functions := db:open($db,$csr_proc:stored_updating_functions_doc)/careServicesFunctions
  let $uuid := $func/@uuid
  let $old := $stored_updating_functions/csd:careServicesFunction[@uuid = $uuid]
  return
    if (exists($uuid)) then
      if (exists($old))
	then
	(delete node $old,
	insert node $func into $stored_updating_functions
	)
      else
	insert node $func into $stored_updating_functions
  else ()
};

declare updating function csr_proc:load_stored_function($db,$func) {
  let $stored_functions := db:open($db,$csr_proc:stored_functions_doc)/careServicesFunctions
  let $uuid := $func/@uuid
  let $old := $stored_functions/csd:careServicesFunction[@uuid = $uuid]
  return
    if (exists($uuid) and exists($stored_functions)) then
      if (exists($old))
	then
	(delete node $old,
	insert node $func into $stored_functions
	)
      else
	insert node $func into $stored_functions
    else ()
};

declare function csr_proc:process_CSR($db,$careServicesRequest, $doc)
{
let $func :=$careServicesRequest/csd:function
let $adhoc :=$careServicesRequest/csd:expression
return if (exists($func))
then
  csr_proc:process_CSR_stored($db,$func,$doc)
else if (exists($adhoc))
then
  csr_proc:process_CSR_adhoc($adhoc,$doc)
else
 (:
  <rest:response>
    <http:response status="400" message="Invalid care services request.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>
  :)
  $careServicesRequest

};

declare function csr_proc:process_CSR_adhoc($expression,$doc)
{

let $expr :=string($expression)
return if ($expr) then
  let $result := xquery:eval($expr,map{"":=$doc})
  return(
   <rest:response>
   <http:response status="200" >
      <http:header name="Content-Type" value="{$expression/@content-type}"/>
    </http:response>
  </rest:response>
  ,$result
 )
else
   <rest:response>
     <http:response status="400" message="No ad-hoc expression" />
  </rest:response>
};

declare function csr_proc:process_CSR_stored($db,$function,$doc)
{
let $uuid := $function/@uuid
let $stored_functions := csr_proc:stored_functions($db)
let $definition := $stored_functions[@uuid = $uuid][1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@uuid)
let $requestParams :=
  if ($function/requestParams) then $function/requestParams
  else if ($function/csd:requestParams) then $function/csd:requestParams
  else <requestParams/>
let $result0 :=
  if (exists($definition)) then
    xquery:eval($definition,map{'':=$doc,'careServicesRequest':=$requestParams})
  else
    ()
let $result1:=
  if ($function/@encapsulated) then
    csr_proc:wrap_result($result0,$content_type)
  else $result0
return if ($result0) then
  (
  <rest:response>
    <http:response status="200" >
      <http:header name="Content-Type" value="{$content_type}"/>
    </http:response>
  </rest:response>
  ,
  $result1
  )
else
  <rest:response>
    <http:response status="404" message="No registered function with UUID='{$function/@uuid}'.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>

};

declare function csr_proc:wrap_result($result,$content-type) {
 <csd:careServicesResponse content-type="{$content-type}"><csd:result>{$result}</csd:result></csd:careServicesResponse>
};

declare function csr_proc:create_adhoc_doc($adhoc_query,$content_type) {
let $content := if ($content_type) then $content_type else "application/xml"
return
 <csd:careServicesRequest xmlns:csd='urn:ihe:iti:csd:2013'>
  <csd:expression content-type='{$content}'>
  {$adhoc_query}
  </csd:expression>
</csd:careServicesRequest>

};

declare function csr_proc:get_function_definition($db,$uuid) {
  csr_proc:stored_functions($db)[@uuid = $uuid][1]
};

declare function csr_proc:get_updating_function_definition($db,$uuid) {
  csr_proc:stored_updating_functions($db)[@uuid = $uuid][1]
};

declare updating function csr_proc:process_updating_CSR($db,$careServicesRequest, $doc)
{
(:not allowing ad-hoc updates:)
let $function :=$careServicesRequest//csd:function
let $uuid := $function/@uuid
let $stored_updating_functions := csr_proc:stored_updating_functions($db)
let $definition := $stored_updating_functions[@uuid = $uuid][1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@uuid)
let $requestParams :=
  if ($function/requestParams) then $function/requestParams
  else if ($function/csd:requestParams) then $function/csd:requestParams
  else <requestParams/>
return if (exists($definition)) then
  xquery:update($definition,map{'':=$doc,'careServicesRequest':=$requestParams})
else
    db:output(
    <rest:response>
      <http:response status="404" message="No registered updating function with UUID='{$function/@uuid} ">
	<http:header name="Content-Language" value="en"/>
	<http:header name="Content-Type" value="text/html; charset=utf-8"/>
      </http:response>
    </rest:response>
       )
};

declare function csr_proc:lookup_stored_content_type($db,$uuid)
{
  let $stored_updating_functions := csr_proc:stored_updating_functions($db)
  let $stored_functions := csr_proc:stored_functions($db)
  return string(($stored_functions[@uuid = $uuid]/@content-type, $stored_updating_functions[@uuid = $uuid]/@content-type  , "text/xml")[1])
};