Fixed documentation.

Fixed 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
 :)
module namespace file = "http://expath.org/ns/file";

import schema namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace an = "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 %an: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 %an: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 %an: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 %an:nondeterministic %an: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 %an: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 %an:nondeterministic %an: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 %an:nondeterministic %an: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 %an: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 %an:nondeterministic %an: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 %an: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 %an:nondeterministic %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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.
 :
 : If compiled with ICU, then Zorba supports any encoding that ICU supports;
 : otherwise Zorba only supports ASCII and UTF-8.
 : 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 %an: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 %an: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.
 :
 : If compiled with ICU, then Zorba supports any encoding that ICU supports;
 : otherwise Zorba only supports ASCII and UTF-8.
 : 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 %an:nondeterministic function file:read-text-lines(
  $file as xs:string,
  $encoding as xs:string
) as xs:string* external;

(:~
 : 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 %an:nondeterministic %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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
) 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 %an: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 %an: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 $escapedDelim :=
    if ($delim eq "/") then
      $delim
    else
      fn:concat("\", $delim)
  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
      fn:concat($n, "\")
    else $n
  return
    if (fn:matches($path, fn:concat("^", $escapedDelim, "+$"))) then
      ""
    else if ($delim eq "\" and fn:matches($path, "^[a-zA-Z]:\\?$")) then
      ""
    else if ($path eq "") then
      "."
    else
      fn:replace($normalized-file, fn:concat("^.*", $escapedDelim), "")
};

(:~
 : 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 $escapedDelim :=
    if ($delim eq "/") then
      $delim
    else
      fn:concat("\", $delim)
  let $normalized-file := file:prepare-for-dirname-and-base-name($path)
  return
    if (fn:matches($path, fn:concat("^", $escapedDelim, "+$"))) then
      $delim
    else if ($normalized-file eq $delim) then
      $delim
    else if ($delim eq "\" and fn:matches($path, "^[a-zA-Z]:\\$")) then
      $path
    else if ($delim 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, $escapedDelim)) then
      fn:replace($normalized-file, fn:concat("^(.*)", $escapedDelim, ".*"), "$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 $escapedDelim :=
    if ($delim eq "/") then
      $delim
    else
      fn:concat("\", $delim)
  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($escapedDelim, "+$"), "")
  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
 :)
module namespace file = "http://expath.org/ns/file";

import schema namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace an = "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 %an: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 %an: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 %an: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 %an:nondeterministic %an: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 %an: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 %an:nondeterministic %an: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 %an:nondeterministic %an: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 %an: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 %an:nondeterministic %an: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 %an: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 %an:nondeterministic %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an:nondeterministic function file:read-text-lines(
  $file as xs:string,
  $encoding as xs:string
) as xs:string* external;

(:~
 : 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 %an:nondeterministic %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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 %an: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
) 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 %an: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 %an: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 $escapedDelim :=
    if ($delim eq "/") then
      $delim
    else
      fn:concat("\", $delim)
  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
      fn:concat($n, "\")
    else $n
  return
    if (fn:matches($path, fn:concat("^", $escapedDelim, "+$"))) then
      ""
    else if ($delim eq "\" and fn:matches($path, "^[a-zA-Z]:\\?$")) then
      ""
    else if ($path eq "") then
      "."
    else
      fn:replace($normalized-file, fn:concat("^.*", $escapedDelim), "")
};

(:~
 : 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 $escapedDelim :=
    if ($delim eq "/") then
      $delim
    else
      fn:concat("\", $delim)
  let $normalized-file := file:prepare-for-dirname-and-base-name($path)
  return
    if (fn:matches($path, fn:concat("^", $escapedDelim, "+$"))) then
      $delim
    else if ($normalized-file eq $delim) then
      $delim
    else if ($delim eq "\" and fn:matches($path, "^[a-zA-Z]:\\$")) then
      $path
    else if ($delim 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, $escapedDelim)) then
      fn:replace($normalized-file, fn:concat("^(.*)", $escapedDelim, ".*"), "$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 $escapedDelim :=
    if ($delim eq "/") then
      $delim
    else
      fn:concat("\", $delim)
  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($escapedDelim, "+$"), "")
  return $normalized
};


webui API updates

webui API updates

module namespace page = 'http://basex.org/modules/web-page';

(:Import other namespaces.  :)
import module namespace csd_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
import module namespace csd_webui =  "https://github.com/openhie/openinfoman/csd_webui";
import module namespace csr_proc = "https://github.com/openhie/openinfoman/csr_proc";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
import module namespace csd_mcs = "https://github.com/openhie/openinfoman/csd_mcs";
import module namespace functx = "http://www.functx.com";
import module namespace json = "http://basex.org/modules/json";
import module namespace request = "http://exquery.org/ns/request";

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

declare function page:is_rapidpro($search_name) {
  let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)
  let $ext := $function//csd:extension[  @urn='urn:openhie.org:openinfoman:adapter' and @type='rapidpro']
  return (count($ext) > 0)
};

declare function page:get_actions($search_name) {
  let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)
  return
    (
    for $act in $function//csd:extension[  @urn='urn:openhie.org:openinfoman:adapter:rapidpro:action']/@type
    return string($act)
  )
};

declare
  %rest:path("/CSD/csr/{$doc_name}/careServicesRequest/{$search_name}/adapter/rapidpro")
  %output:method("xhtml")
  function page:show_endpoints($search_name,$doc_name)
{
    if (not(page:is_rapidpro($search_name)) )
      then ('Not a RapidPro Compatible stored function'    )
    else
      let $actions := page:get_actions($search_name)
      let $contents :=

RapidPro Operations on {$doc_name}

        {
          if ($actions = 'get')
	  then

JSON Contacts  -- GET

	     {
	       let $url := csd_webui:generateURL( "CSD/csr/" , $doc_name , "/careServicesRequest/",$search_name, "/adapter/rapidpro/get")
	       return
Get Contacts

	     }

	  else ()
	}

        {
          if ($actions = 'WebHookGET')
	  then

WebHook  -- GET

	     {
	       let $url := csd_webui:generateURL( "CSD/csr/" , $doc_name , "/careServicesRequest/",$search_name, "/adapter/rapidpro/createDXF")
	       return
WebHook defined at {$url}

	     }

	  else ()
	}
        {
          if ($actions = 'WebHookPOST')
	  then

WebHook  -- POST

	     {
	       let $url := csd_webui:generateURL( "CSD/csr/" , $doc_name , "/careServicesRequest/",$search_name, "/adapter/rap/createDXF")
	       return
WebHook defined at {$url}

	     }

	  else ()
	}

      return csd_webui:wrapper($contents)
};

declare
  %rest:path("/CSD/csr/{$doc_name}/careServicesRequest/{$search_name}/adapter/rapidpro/WebHook")
  %rest:query-param("event","{$event}")
  %rest:query-param("relayer","{$relayer}")
  %rest:query-param("relayer_phone","{$relayer_phone}")
  %rest:query-param("phone","{$phone}")
  %rest:query-param("flow","{$flow}")
  %rest:query-param("step","{$step}")
  %rest:query-param("values","{$values}")
  %output:media-type("application/json")
  %rest:POST
  %rest:GET
  function page:webhook($search_name,$doc_name,$event,$relayer,$relayer_phone,$phone,$flow,$step,$values)
{
  if (not(page:is_rapidpro($search_name)) )
    then ('Not a RapidPro Compatible stored function'    )
  else
    let $doc :=  csd_dm:open_document($csd_webconf:db,$doc_name)
    let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)

    let $careServicesRequest :=
      <csd:careServicesRequest>
       <csd:function urn="{$search_name}" resource="{$doc_name}" base_url="{csd_webui:generateURL()}">
         <csd:requestParams >
	   <event type="{$event}"/>
	   <relayer id="{$relayer}"/>
	   <relayer_phone number="{$relayer_phone}"/>
	   <phone number="{$phone}"/>
	   <flow id="{$flow}"/>
	   <step id="{$step}"/>
           <values>
	     {
	       if ($values)
	       then    json:parse($values,map{'format':'attributes'})/json/*
	       else ()
	     }
	   </values>
	   {
	     for $param in request:parameter-names()
	     return <query name="{$param}">{request:parameter($param)}</query>
	   }
         </csd:requestParams>
       </csd:function>
      </csd:careServicesRequest>
    return csr_proc:process_CSR_stored_results($csd_webconf:db, $doc,$careServicesRequest)
};

declare
  %rest:path("/CSD/csr/{$doc_name}/careServicesRequest/{$search_name}/adapter/rapidpro/get")
  function page:get($search_name,$doc_name)
{
  if (not(page:is_rapidpro($search_name)) )
    then

    {csr_proc:get_function_definition($csd_webconf:db,$search_name)}
    {(concat('Not a RapidPro Compatible stored function: ', $search_name )    )}

  else
    let $doc :=  csd_dm:open_document($csd_webconf:db,$doc_name)
    let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)

    let $careServicesRequest :=
      <csd:careServicesRequest>
       <csd:function urn="{$search_name}" resource="{$doc_name}" base_url="{csd_webui:generateURL()}">
         <csd:requestParams/>
       </csd:function>
     </csd:careServicesRequest>
    let $content := csr_proc:process_CSR_stored_results($csd_webconf:db, $doc,$careServicesRequest)
    let $output := $function/@content-type
    let $mime :=
      if (exists($output))
      then string($output)
      else "text/html"
    return
    (
      <rest:response>
	<http:response status="200" >
          <http:header name='Content-Type' value="{$mime}"/>
	  <http:header name='Content-Disposition' value="attachment; filename='{$doc_name}.json'"/>
	</http:response>
      </rest:response>
      ,$content
    )

};

module namespace page = 'http://basex.org/modules/web-page';

(:Import other namespaces.  :)
import module namespace csd_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
import module namespace csr_proc = "https://github.com/openhie/openinfoman/csr_proc";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
import module namespace csd_mcs = "https://github.com/openhie/openinfoman/csd_mcs";
import module namespace functx = "http://www.functx.com";
import module namespace json = "http://basex.org/modules/json";
import module namespace request = "http://exquery.org/ns/request";

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

declare function page:is_rapidpro($search_name) {
  let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)
  let $ext := $function//csd:extension[  @urn='urn:openhie.org:openinfoman:adapter' and @type='rapidpro']
  return (count($ext) > 0)
};

declare function page:get_actions($search_name) {
  let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)
  return
    (
    for $act in $function//csd:extension[  @urn='urn:openhie.org:openinfoman:adapter:rapidpro:action']/@type
    return string($act)
  )
};

declare
  %rest:path("/CSD/csr/{$doc_name}/careServicesRequest/{$search_name}/adapter/rapidpro")
  %output:method("xhtml")
  function page:show_endpoints($search_name,$doc_name)
{
    if (not(page:is_rapidpro($search_name)) )
      then ('Not a RapidPro Compatible stored function'    )
    else
      let $actions := page:get_actions($search_name)
      let $contents :=

RapidPro Operations on {$doc_name}

        {
          if ($actions = 'get')
	  then

JSON Contacts  -- GET

	     {
	       let $url := concat($csd_webconf:baseurl, "CSD/csr/" , $doc_name , "/careServicesRequest/",$search_name, "/adapter/rapidpro/get")
	       return
Get Contacts

	     }

	  else ()
	}

        {
          if ($actions = 'WebHookGET')
	  then

WebHook  -- GET

	     {
	       let $url := concat($csd_webconf:baseurl, "CSD/csr/" , $doc_name , "/careServicesRequest/",$search_name, "/adapter/rapidpro/createDXF")
	       return
WebHook defined at {$url}

	     }

	  else ()
	}
        {
          if ($actions = 'WebHookPOST')
	  then

WebHook  -- POST

	     {
	       let $url := concat($csd_webconf:baseurl, "CSD/csr/" , $doc_name , "/careServicesRequest/",$search_name, "/adapter/rap/createDXF")
	       return
WebHook defined at {$url}

	     }

	  else ()
	}

      return csd_webconf:wrapper($contents)
};

declare
  %rest:path("/CSD/csr/{$doc_name}/careServicesRequest/{$search_name}/adapter/rapidpro/WebHook")
  %rest:query-param("event","{$event}")
  %rest:query-param("relayer","{$relayer}")
  %rest:query-param("relayer_phone","{$relayer_phone}")
  %rest:query-param("phone","{$phone}")
  %rest:query-param("flow","{$flow}")
  %rest:query-param("step","{$step}")
  %rest:query-param("values","{$values}")
  %output:media-type("application/json")
  %rest:POST
  %rest:GET
  function page:webhook($search_name,$doc_name,$event,$relayer,$relayer_phone,$phone,$flow,$step,$values)
{
  if (not(page:is_rapidpro($search_name)) )
    then ('Not a RapidPro Compatible stored function'    )
  else
    let $doc :=  csd_dm:open_document($csd_webconf:db,$doc_name)
    let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)

    let $careServicesRequest :=
      <csd:careServicesRequest>
       <csd:function urn="{$search_name}" resource="{$doc_name}" base_url="{$csd_webconf:baseurl}">
         <csd:requestParams >
	   <event type="{$event}"/>
	   <relayer id="{$relayer}"/>
	   <relayer_phone number="{$relayer_phone}"/>
	   <phone number="{$phone}"/>
	   <flow id="{$flow}"/>
	   <step id="{$step}"/>
           <values>
	     {
	       if ($values)
	       then    json:parse($values,map{'format':'attributes'})/json/*
	       else ()
	     }
	   </values>
	   {
	     for $param in request:parameter-names()
	     return <query name="{$param}">{request:parameter($param)}</query>
	   }
         </csd:requestParams>
       </csd:function>
      </csd:careServicesRequest>
    return csr_proc:process_CSR_stored_results($csd_webconf:db, $doc,$careServicesRequest)
};

declare
  %rest:path("/CSD/csr/{$doc_name}/careServicesRequest/{$search_name}/adapter/rapidpro/get")
  function page:get($search_name,$doc_name)
{
  if (not(page:is_rapidpro($search_name)) )
    then

    {csr_proc:get_function_definition($csd_webconf:db,$search_name)}
    {(concat('Not a RapidPro Compatible stored function: ', $search_name )    )}

  else
    let $doc :=  csd_dm:open_document($csd_webconf:db,$doc_name)
    let $function := csr_proc:get_function_definition($csd_webconf:db,$search_name)

    let $careServicesRequest :=
      <csd:careServicesRequest>
       <csd:function urn="{$search_name}" resource="{$doc_name}" base_url="{$csd_webconf:baseurl}">
         <csd:requestParams/>
       </csd:function>
     </csd:careServicesRequest>
    let $content := csr_proc:process_CSR_stored_results($csd_webconf:db, $doc,$careServicesRequest)
    let $output := $function/@content-type
    let $mime :=
      if (exists($output))
      then string($output)
      else "text/html"
    return
    (
      <rest:response>
	<http:response status="200" >
          <http:header name='Content-Type' value="{$mime}"/>
	  <http:header name='Content-Disposition' value="attachment; filename='{$doc_name}.json'"/>
	</http:response>
      </rest:response>
      ,$content
    )

};


[testsuite] XQuery test suite: test element now accepts an attribute ignore="yes" to temporarily disable a test. Port of rev 13962.

svn path=/stable/eXist-1.4.x/; revision=13963

xquery version "1.0";

module namespace t="http://exist-db.org/xquery/testing";

import module namespace xdb="http://exist-db.org/xquery/xmldb";
import module namespace xdiff="http://exist-db.org/xquery/xmldiff"
at "java:org.exist.xquery.modules.xmldiff.XmlDiffModule";

declare function t:setup-action($action) {
    typeswitch ($action)
        case element(create-collection) return
            xdb:create-collection($action/@parent, $action/@name)
        case element(store) return
			t:store($action)
		case element(store-files) return
            t:store-files($action)
        case element(remove-collection) return
            xdb:remove($action/@collection)
        case element(remove-document) return
            xdb:remove($action/@collection, $action/@name)
        default return
            ()
};

declare function t:store($action as element(store)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    let $data :=
		if ($action/*) then
			$action/*[1]
		else
			$action/string()
	return
        xdb:store($action/@collection, $action/@name, $data, $type)
};

declare function t:store-files($action as element(store-files)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    return
        xdb:store-files-from-pattern($action/@collection, $action/@dir, $action/@pattern, $type)
};

declare function t:setup($setup as element(setup)) {
    for $action in $setup/*
    return
        t:setup-action($action)
};

declare function t:tearDown($tearDown as element(tearDown)?) {
    for $action in $tearDown/*
    return
        t:setup-action($action)
};

declare function t:declare-variable($var as element(variable)) as item()? {
    let $children := $var/*
    return
        if (empty($children)) then
            string-join($var/node(), '')
        else
            util:serialize($children, ())
};

declare function t:init-prolog($test as element(test)) {
	let $imports := $test/../imports
	let $vars :=
	    string-join(
        	for $var in $test/../variable
        	return
        	    concat("declare variable $", $var/@name, " := ", t:declare-variable($var), ";"),
            ""
        )
	return
		string-join(($imports, $vars, $test/../functions), '')
};

declare function t:test($result as item()*) {
    if ($result instance of xs:boolean) then
        $result
    else
        exists($result)
};

declare function t:run-test($test as element(test), $count as xs:integer) {
	let $context := t:init-prolog($test)
	let $null :=
	   if ($test/@trace eq 'yes') then
	       (system:clear-trace(), system:enable-tracing(true(), false()))
       else ()
    let $highlight-option := concat("highlight-matches=",
          if ($test/expected//@*[matches(., '^(\|{3}).*\1$')] and $test/expected//exist:match) then "both"
          else if ($test/expected//@*[matches(., '^(\|{3}).*\1$')]) then "attributes"
          else if ($test/expected//exist:match) then "elements"
          else "none"
          )
    let $serialize-options :=
      let $decls := ($test/../*[name() ne 'test']|$test/code)[matches(., 'declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|').*?\4[;)]')]
      let $ops1 := $decls/replace(., "declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|')(.*?)\4[;)]", "_|$5_")
      let $ops2 :=
        for $a in $ops1
        for $b in tokenize($a, '_')[starts-with(., '|')]
        return tokenize(substring-after($b, '|'), '\s+')
      return if (count($ops2[matches(., 'highlight-matches')]))
        then string-join($ops2, ' ')
        else string-join(($ops2, $highlight-option), ' ')
    let $queryOutput :=
		util:catch("*",
			util:eval(concat($context, $test/code/string())),
			<error>Compilation error: {$util:exception-message}</error>
		)
	let $output := if ($test/@trace eq 'yes') then system:trace() else $queryOutput
    let $expanded :=
		if ($output instance of node()) then
        	util:expand($output, $serialize-options)
		else
			$output
    let $expected :=
        if ($test/@output eq 'text') then
            data($test/expected)
        else $test/expected/node()
    let $OK :=
        if ($test/xpath) then
            t:test(t:xpath($output, $test/xpath))
        else if ($test/@output eq 'text') then
            normalize-space(string-join(for $x in $output return string($x),' ')) eq normalize-space($expected)
        else
            let $xn := t:normalize($expanded)
            let $en := t:normalize($expected)
            return
        	   deep-equal($xn, $en)
    return
        <test n="{$count}" pass="{$OK}">
        {
            if (not($OK)) then
                ($test/task, $test/expected, <result>{$expanded}</result>)
            else
                ()
        }
        </test>
};

declare function t:normalize($nodes as node()*) {
	for $node in $nodes return t:normalize-node($node)
};

declare function t:normalize-node($node as node()) {
	typeswitch ($node)
		case element() return
			element { node-name($node) } {
				$node/@*, for $child in $node/node() return t:normalize-node($child)
			}
		case text() return
			let $norm := normalize-space($node)
			return
				if (string-length($norm) eq 0) then () else $node
		default return
			$node
};

declare function t:xpath($output as item()*, $xpath as element()) {
    for $prefix in in-scope-prefixes($xpath)
    let $nsuri := namespace-uri-for-prefix($prefix, $xpath)
    return
        if ($prefix != 'xml') then
            util:declare-namespace($prefix, $nsuri)
        else (),
    let $expr := $xpath/string()
    return
        if (matches($expr, "^\s*/")) then
            util:eval(concat("$output", $expr))
        else
            util:eval($expr)
};

declare function t:run-testSet($set as element(TestSet)) {
    let $copy := util:expand($set)
    let $null := t:setup($copy/setup)
    let $result := util:expand(
           <TestSet>
           {$copy/testName}
           {$copy/description}
           {
               for $test at $p in $copy/test[empty(@ignore) or @ignore = "no"]
               return
                   t:run-test($test, $p)
           }
           </TestSet>
        )
    let $null := t:tearDown($copy/tearDown)
    return $result
};

declare function t:format-testResult($result) {
    let $formatted :=

{$result/testName}

{$result/description/p}

          {for $test in $result//test
           return

                    {$test/@n/.}
                    {$test/@pass/.}

          }

     return
        $formatted
};

xquery version "1.0";

module namespace t="http://exist-db.org/xquery/testing";

import module namespace xdb="http://exist-db.org/xquery/xmldb";
import module namespace xdiff="http://exist-db.org/xquery/xmldiff"
at "java:org.exist.xquery.modules.xmldiff.XmlDiffModule";

declare function t:setup-action($action) {
    typeswitch ($action)
        case element(create-collection) return
            xdb:create-collection($action/@parent, $action/@name)
        case element(store) return
			t:store($action)
		case element(store-files) return
            t:store-files($action)
        case element(remove-collection) return
            xdb:remove($action/@collection)
        case element(remove-document) return
            xdb:remove($action/@collection, $action/@name)
        default return
            ()
};

declare function t:store($action as element(store)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    let $data :=
		if ($action/*) then
			$action/*[1]
		else
			$action/string()
	return
        xdb:store($action/@collection, $action/@name, $data, $type)
};

declare function t:store-files($action as element(store-files)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    return
        xdb:store-files-from-pattern($action/@collection, $action/@dir, $action/@pattern, $type)
};

declare function t:setup($setup as element(setup)) {
    for $action in $setup/*
    return
        t:setup-action($action)
};

declare function t:tearDown($tearDown as element(tearDown)?) {
    for $action in $tearDown/*
    return
        t:setup-action($action)
};

declare function t:declare-variable($var as element(variable)) as item()? {
    let $children := $var/*
    return
        if (empty($children)) then
            string-join($var/node(), '')
        else
            util:serialize($children, ())
};

declare function t:init-prolog($test as element(test)) {
	let $imports := $test/../imports
	let $vars :=
	    string-join(
        	for $var in $test/../variable
        	return
        	    concat("declare variable $", $var/@name, " := ", t:declare-variable($var), ";"),
            ""
        )
	return
		string-join(($imports, $vars, $test/../functions), '')
};

declare function t:test($result as item()*) {
    if ($result instance of xs:boolean) then
        $result
    else
        exists($result)
};

declare function t:run-test($test as element(test), $count as xs:integer) {
	let $context := t:init-prolog($test)
	let $null :=
	   if ($test/@trace eq 'yes') then
	       (system:clear-trace(), system:enable-tracing(true(), false()))
       else ()
    let $highlight-option := concat("highlight-matches=",
          if ($test/expected//@*[matches(., '^(\|{3}).*\1$')] and $test/expected//exist:match) then "both"
          else if ($test/expected//@*[matches(., '^(\|{3}).*\1$')]) then "attributes"
          else if ($test/expected//exist:match) then "elements"
          else "none"
          )
    let $serialize-options :=
      let $decls := ($test/../*[name() ne 'test']|$test/code)[matches(., 'declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|').*?\4[;)]')]
      let $ops1 := $decls/replace(., "declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|')(.*?)\4[;)]", "_|$5_")
      let $ops2 :=
        for $a in $ops1
        for $b in tokenize($a, '_')[starts-with(., '|')]
        return tokenize(substring-after($b, '|'), '\s+')
      return if (count($ops2[matches(., 'highlight-matches')]))
        then string-join($ops2, ' ')
        else string-join(($ops2, $highlight-option), ' ')
    let $queryOutput :=
		util:catch("*",
			util:eval(concat($context, $test/code/string())),
			<error>Compilation error: {$util:exception-message}</error>
		)
	let $output := if ($test/@trace eq 'yes') then system:trace() else $queryOutput
    let $expanded :=
		if ($output instance of node()) then
        	util:expand($output, $serialize-options)
		else
			$output
    let $expected :=
        if ($test/@output eq 'text') then
            data($test/expected)
        else $test/expected/node()
    let $OK :=
        if ($test/xpath) then
            t:test(t:xpath($output, $test/xpath))
        else if ($test/@output eq 'text') then
            normalize-space(string-join(for $x in $output return string($x),' ')) eq normalize-space($expected)
        else
            let $xn := t:normalize($expanded)
            let $en := t:normalize($expected)
            return
        	   deep-equal($xn, $en)
    return
        <test n="{$count}" pass="{$OK}">
        {
            if (not($OK)) then
                ($test/task, $test/expected, <result>{$expanded}</result>)
            else
                ()
        }
        </test>
};

declare function t:normalize($nodes as node()*) {
	for $node in $nodes return t:normalize-node($node)
};

declare function t:normalize-node($node as node()) {
	typeswitch ($node)
		case element() return
			element { node-name($node) } {
				$node/@*, for $child in $node/node() return t:normalize-node($child)
			}
		case text() return
			let $norm := normalize-space($node)
			return
				if (string-length($norm) eq 0) then () else $node
		default return
			$node
};

declare function t:xpath($output as item()*, $xpath as element()) {
    for $prefix in in-scope-prefixes($xpath)
    let $nsuri := namespace-uri-for-prefix($prefix, $xpath)
    return
        if ($prefix != 'xml') then
            util:declare-namespace($prefix, $nsuri)
        else (),
    let $expr := $xpath/string()
    return
        if (matches($expr, "^\s*/")) then
            util:eval(concat("$output", $expr))
        else
            util:eval($expr)
};

declare function t:run-testSet($set as element(TestSet)) {
    let $copy := util:expand($set)
    let $null := t:setup($copy/setup)
    let $result := util:expand(
           <TestSet>
           {$copy/testName}
           {$copy/description}
           {
               for $test at $p in $copy/test
               return
                   t:run-test($test, $p)
           }
           </TestSet>
        )
    let $null := t:tearDown($copy/tearDown)
    return $result
};

declare function t:format-testResult($result) {
    let $formatted :=

{$result/testName}

{$result/description/p}

          {for $test in $result//test
           return

                    {$test/@n/.}
                    {$test/@pass/.}

          }

     return
        $formatted
};


fixed typo

fixed typo

xquery version "1.0";

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

(:~
 : This module provides string related functions.
 :
 : @author Matthias Brantner
 : @project XDM/atomic
 :
 :)
module namespace uri = "http://www.zorba-xquery.com/modules/uri";

declare namespace zerr = "http://www.zorba-xquery.com/errors";

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

(:~
 : Percent-decodes (aka URL decoding) the given string.
 :
 : All percent encoded octets will be translated into their
 : decoded UTF-8 representation.
 :
 : Please note that the percent encoding guarantees that a string
 : consists of ASCII characters only. Passing a string that contains
 : non-ASCII characters results in undefined behavior.
 :
 : @param $s the string to decode
 :
 : @return the percent decoded string
 :)
declare function uri:decode($u as xs:string) as xs:string
{
  uri:decode($u, fn:false())
};

(:~
 : Percent-decodes (aka URL decoding) the given string.
 :
 : All percent encoded octets will be translated into their
 : decoded UTF-8 representation.
 :
 : If $decode-plus is specified all occurrences of the char '+'
 : will be replaced with a space ' ' before the percent decoding
 : happens.
 :
 : Please note that the percent encoding guarantees that a string
 : consists of ASCII characters only. Passing a string that contains
 : non-ASCII characters results in undefined behavior.
 :
 : @param $s the string to decode
 : @param $decode-plus whether '+' chars will be replaced with spaces
 :
 : @return the percent decoded string
 :)
declare function uri:decode(
  $u as xs:string,
  $decode-plus as xs:boolean) as xs:string
{
  uri:decode($u, $decode-plus, "UTF-8")
};

(:~
 : Percent-decodes (aka URL decoding) the given string.
 :
 : All percent encoded octets will be translated into their
 : decoded UTF-8 representation.
 :
 : If $decode-plus is specified all occurrences of the char '+'
 : will be replaced with a space ' ' before the percent decoding
 : happens.
 :
 : The $charset parameter specifies the source charset after precent
 : decoding. It is used to convert the decoded string into UTF-8.
 :
 : Please note that the percent encoding guarantees that a string
 : consists of ASCII characters only. Passing a string that contains
 : non-ASCII characters results in undefined behavior.
 :
 : @param $s the string to decode
 : @param $decode-plus whether '+' chars will be replaced with spaces
 : @param $charset the source charset of the string after percent decoding
 :
 : @return the percent decoded string
 :
 : @error zerr:ZXQP0006 if the given charset is unknown or not supported
 :
 : @error zerr:ZOSE0006 if there is an error transcoding the string
 :)
declare function uri:decode(
  $s as xs:string,
  $decode-plus as xs:boolean,
  $charset as xs:string) as xs:string external;

xquery version "1.0";

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

(:~
 : This module provides string related functions.
 :
 : @author Matthias Brantner
 : @project XDM/atomic
 :
 :)
module namespace uri = "http://www.zorba-xquery.com/modules/uri";

declare namespace zerr = "http://www.zorba-xquery.com/errors";

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

(:~
 : Percent-decodes (aka URL decoding) the given string.
 :
 : All percent encoded octets will be translated into their
 : decoded UTF-8 representation.
 :
 : Please note that the percent encoding guarantees that a string
 : consists of ASCII characters only. Passing a string that contains
 : non-ASCII characeters results in undefined behavior.
 :
 : @param $s the string to decode
 :
 : @return the percent decoded string
 :)
declare function uri:decode($u as xs:string) as xs:string
{
  uri:decode($u, fn:false())
};

(:~
 : Percent-decodes (aka URL decoding) the given string.
 :
 : All percent encoded octets will be translated into their
 : decoded UTF-8 representation.
 :
 : If $decode-plus is specified all occurrences of the char '+'
 : will be replaced with a space ' ' before the percent decoding
 : happens.
 :
 : Please note that the percent encoding guarantees that a string
 : consists of ASCII characters only. Passing a string that contains
 : non-ASCII characeters results in undefined behavior.
 :
 : @param $s the string to decode
 : @param $decode-plus whether '+' chars will be replaced with spaces
 :
 : @return the percent decoded string
 :)
declare function uri:decode(
  $u as xs:string,
  $decode-plus as xs:boolean) as xs:string
{
  uri:decode($u, $decode-plus, "UTF-8")
};

(:~
 : Percent-decodes (aka URL decoding) the given string.
 :
 : All percent encoded octets will be translated into their
 : decoded UTF-8 representation.
 :
 : If $decode-plus is specified all occurrences of the char '+'
 : will be replaced with a space ' ' before the percent decoding
 : happens.
 :
 : The $charset parameter specifies the source charset after precent
 : decoding. It is used to convert the decoded string into UTF-8.
 :
 : Please note that the percent encoding guarantees that a string
 : consists of ASCII characters only. Passing a string that contains
 : non-ASCII characeters results in undefined behavior.
 :
 : @param $s the string to decode
 : @param $decode-plus whether '+' chars will be replaced with spaces
 : @param $charset the source charset of the string after percent decoding
 :
 : @return the percent decoded string
 :
 : @error zerr:ZXQP0006 if the given charset is unknown or not supported
 :
 : @error zerr:ZOSE0006 if there is an error transcoding the string
 :)
declare function uri:decode(
  $s as xs:string,
  $decode-plus as xs:boolean,
  $charset as xs:string) as xs:string external;


At logout do a redirect back to the home page - otherwise the jquery tree does not seem to be correctly reloaded

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

xquery version "1.0";

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

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

declare function local:set-user() {
    session:create(),
    let $user := request:get-parameter("user", ())
    let $password := request:get-parameter("password", ())
    let $session-user-credential := security:get-user-credential-from-session()
    return
        if ($user) then (
            security:store-user-credential-in-session($user, $password),
            <set-attribute name="xquery.user" value="{$user}"/>,
            <set-attribute name="xquery.password" value="{$password}"/>
        ) else if ($session-user-credential != '') then (
            <set-attribute name="xquery.user" value="{$session-user-credential[1]}"/>,
            <set-attribute name="xquery.password" value="{$session-user-credential[2]}"/>
        ) else (
            <set-attribute name="xquery.user" value="{$security:GUEST_CREDENTIALS[1]}"/>,
            <set-attribute name="xquery.password" value="{$security:GUEST_CREDENTIALS[2]}"/>
        )
};

if ($exist:path eq '/') then
    <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
		<redirect url="index.xml"/>
	</dispatch>
(:  Main page: index.xml is a template, which is passed through
    search.xql and the db2xhtml stylesheet. search.xql will run
    the actual search and expand the index.xml template.
:)
else if ($exist:resource eq 'index.xml') then

    if(request:get-parameter("logout",()))then
    (
        session:clear(),
        <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
            <redirect url="index.xml"/>
        </dispatch>
    )
    else
    (
        <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
            { local:set-user() }
            <view>
                <forward url="search.xql">
                    <!-- Errors should be passed through instead of terminating the request -->
            		<set-attribute name="xquery.report-errors" value="yes"/>
                </forward>
    		</view>
    	</dispatch>
        (:  Retrieve an item from the query results stored in the HTTP session. The
    	format of the URL will be /sandbox/results/X, where X is the number of the
    	item in the result set :)
	)
else if ($exist:resource eq 'retrieve') then
	<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
	   { local:set-user() }
		<forward url="{$exist:controller}/session.xql">
		</forward>
	</dispatch>
else if (matches($exist:path, '(resources/|styles/syntax|scripts/|logo.jpg|default-style2.css|curvycorners.js)')) then
    let $newPath := replace($exist:path, '^.*((styles/|scripts/|logo).*)$', '/$1')
    return
        <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
    		<forward url="{$newPath}"/>
    		<cache-control cache="yes"/>
    	</dispatch>
else
    (: everything else is passed through :)
    <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
        { local:set-user() }
        <cache-control cache="yes"/>
    </dispatch>

xquery version "1.0";

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

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

declare function local:logout() {
    let $logout := request:get-parameter("logout", ())
    return
        if ($logout) then
            session:clear()
        else
            ()
};

declare function local:set-user() {
    session:create(),
    local:logout(),
    let $user := request:get-parameter("user", ())
    let $password := request:get-parameter("password", ())
    let $session-user-credential := security:get-user-credential-from-session()
    return
        if ($user) then (
            security:store-user-credential-in-session($user, $password),
            <set-attribute name="xquery.user" value="{$user}"/>,
            <set-attribute name="xquery.password" value="{$password}"/>
        ) else if ($session-user-credential != '') then (
            <set-attribute name="xquery.user" value="{$session-user-credential[1]}"/>,
            <set-attribute name="xquery.password" value="{$session-user-credential[2]}"/>
        ) else (
            <set-attribute name="xquery.user" value="{$security:GUEST_CREDENTIALS[1]}"/>,
            <set-attribute name="xquery.password" value="{$security:GUEST_CREDENTIALS[2]}"/>
        )
};

if ($exist:path eq '/') then
    <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
		<redirect url="index.xml"/>
	</dispatch>
(:  Main page: index.xml is a template, which is passed through
    search.xql and the db2xhtml stylesheet. search.xql will run
    the actual search and expand the index.xml template.
:)
else if ($exist:resource eq 'index.xml') then
    <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
        { local:set-user() }
        <view>
            <forward url="search.xql">
                <!-- Errors should be passed through instead of terminating the request -->
        		<set-attribute name="xquery.report-errors" value="yes"/>
            </forward>
		</view>
	</dispatch>
(:  Retrieve an item from the query results stored in the HTTP session. The
	format of the URL will be /sandbox/results/X, where X is the number of the
	item in the result set :)
else if ($exist:resource eq 'retrieve') then
	<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
	   { local:set-user() }
		<forward url="{$exist:controller}/session.xql">
		</forward>
	</dispatch>
else if (matches($exist:path, '(resources/|styles/syntax|scripts/|logo.jpg|default-style2.css|curvycorners.js)')) then
    let $newPath := replace($exist:path, '^.*((styles/|scripts/|logo).*)$', '/$1')
    return
        <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
    		<forward url="{$newPath}"/>
    		<cache-control cache="yes"/>
    	</dispatch>
else
    (: everything else is passed through :)
    <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
        { local:set-user() }
        <cache-control cache="yes"/>
    </dispatch>