Small tweaks to signature, clean up

Small tweaks to signature, clean up

xquery version "3.1";

(:~
 : Origami μ-templates
 :)

module namespace μ = 'http://xokomola.com/xquery/origami/μ';

declare function μ:xml($items as item()*)
as node()*
{
    μ:xml($items, [], μ:qname-resolver())
};

declare function μ:xml($items as item()*, $resolver-or-args as function(*))
as node()*
{
    if ($resolver-or-args instance of array(*)) then
        μ:xml($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:xml($items, [], $resolver-or-args)
};

declare function μ:xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    μ:to-xml($items, $args, $name-resolver)
};

declare function μ:json($items as item()*)
as xs:string
{
    μ:json($items, [], function($name) { $name })
};

declare function μ:json($items as item()*, $resolver-or-args as function(*))
as xs:string
{
    if ($resolver-or-args instance of array(*)) then
        μ:json($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:json($items, [], $resolver-or-args)
};

declare function μ:json($items as item()*, $args as array(*), $name-resolver as function(*))
as xs:string
{
    serialize(μ:to-json(if (count($items) gt 1) then array { $items } else $items, $args, $name-resolver), map { 'method': 'json' })
};

declare function μ:apply($items as item()*)
as item()*
{
    μ:apply($items, [])
};

declare function μ:apply($items as item()*, $args as array(*))
as item()*
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*) return $item
        case map(*) return  $item
        case function(*) return μ:apply(apply($item, $args), $args)
        default return $item
};

declare function μ:mu($xml)
as item()*
{
    μ:from-xml($xml)
};

declare %private function μ:to-json($items as item()*, $args as array(*), $name-resolver as function(*))
as item()*
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*)
        return
            array:fold-left($item, [],
                function($a,$b) {
                    if (count($b) gt 1) then
                        (: splice the sequence into the array to avoid JSON
                         : serialization error :)
                        array:join((
                            $a,
                            fold-left($b, [],
                                function($c,$d) {
                                    array:append($c, μ:to-json($d, $args, $name-resolver))
                                })
                        ))
                    else
                        array:append($a, μ:to-json($b, $args, $name-resolver))
                }
            )
        case map(*)
        return
            map:merge(
                map:for-each($item,
                    function($a,$b) {
                        map:entry($a, μ:to-json($b, $args, $name-resolver)) }))
        case function(*) return μ:to-json(apply($item, $args), $args, $name-resolver)
        case node() return μ:from-xml($item)
        default return $item
};

declare %private function μ:from-xml($xml)
as item()*
{
    for $node in $xml
    return
        typeswitch($node)
        case element()
        return
            array {
                name($node),
                if ($node/@*) then
                    map:merge((
                        for $a in $node/@*
                        return map:entry(name($a), data($a))))
                else
                    (),
                μ:from-xml($node/node())
            }
        case comment() | processing-instruction() return ()
        case text() return string($node)
        default return $node
};

declare %private function μ:to-xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*) return μ:to-element($item, $args, $name-resolver)
        case map(*) return  μ:to-attributes($item, $args, $name-resolver)
        case function(*) return μ:to-xml(apply($item, $args), $args, $name-resolver)
        case empty-sequence() return ()
        case node() return $item
        default return text { $item }
};

declare %private function μ:to-element($item as array(*), $args as array(*), $name-resolver as function(*))
as element()?
{
    if (array:size($item) gt 0) then
        element { $name-resolver(array:head($item)) } {
            array:fold-left(
                array:tail($item),
                (),
                function($n, $i) {
                    ($n, μ:to-xml($i, $args, $name-resolver))
                }
            )
        }
    else
        ()
};

declare %private function μ:to-attributes($item as map(*), $args as array(*), $name-resolver as function(*))
as attribute()*
{
    map:for-each($item,
        function($k,$v) {
            if (not(starts-with($k,'μ:'))) then
                attribute { $name-resolver($k) } {
                    data(
                        typeswitch ($v)
                        case function(*)
                        return apply($v, $args)
                        default
                        return $v
                    ) }
            else
                ()
        })
};

declare function μ:qname-resolver()
as function(xs:string) as xs:QName
{
    μ:qname(?, μ:ns(), ())
};

declare function μ:qname-resolver($ns-map as map(*))
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, ())
};

declare function μ:qname-resolver($ns-map as map(*), $default-ns as xs:string)
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, $default-ns)
};

(:~
 : As XQuery doesn't allow access to namespace nodes (as XSLT does)
 : construct them indirectly via QName#2.
 :)
declare function μ:qname($name as xs:string, $ns-map as map(*))
as xs:QName
{
    μ:qname($name, $ns-map, ())
};

declare function μ:qname($name as xs:string, $ns-map as map(*), $default-ns as xs:string?)
as xs:QName
{
    if (contains($name, ':')) then
        let $prefix := substring-before($name,':')
        let $local := substring-after($name, ':')
        let $ns := $ns-map($prefix)
        return
            if ($ns = $default-ns) then
                QName($ns, $local)
            else
                QName($ns, concat($prefix,':',$local))
    else
        if ($default-ns) then
            QName($default-ns, $name)
        else
            QName((), $name)
};

declare function μ:ns()
as map(*)
{
    μ:ns(map {})
};

declare function μ:ns($ns-map as map(*))
as map(*)
{
    map:merge((
        $ns-map,
        for $ns in doc(concat(file:base-dir(),'/nsmap.xml'))/nsmap/*
        return
            map:entry(string($ns/@prefix), string($ns/@uri))
    ))
};

declare function μ:cons($a,$b)
as item()*
{
    ($a,$b)
};

declare function μ:head($mu as array(*)?)
as item()*
{
    if (empty($mu)) then
        ()
    else
        array:head($mu)
};

declare function μ:tail($mu as array(*)?)
as item()*
{
    if (not(empty($mu))) then
        tail(μ:seq($mu))
    else
        ()
};

(:~
 : Remove level of array and change it into a normal sequence.
 :)
declare function μ:seq($mu as array(*))
as item()*
{
    typeswitch ($mu)
        case array(*)
            return array:fold-left($mu, (), μ:cons#2)
        default
            return $mu
};

declare function μ:content($mu)
as item()*
{
    typeswitch($mu)
    case array(*)
    return
        let $c := array:tail($mu)
        return
            if (array:head($c) instance of map(*)) then
                array:tail($c)
            else
                $c
    default
    return $mu/node()
};

xquery version "3.1";

(:~ Origami μ-templates :)

(: TODO: add XML serialization options :)
(: TODO: add option to move all ns declarations at document element (sane namespaces) !? :)
(: TODO: better json serialization :)
(: TODO: remove args from :xml :)
(: TODO: can we scan $items for function items and if so return a function automatically? :)

(: DONE: add ways to configure how names are mapped to qnames (e.g. define a mapping a la JSON-LD) :)
(: DONE: add default element namespace :)

module namespace μ = 'http://xokomola.com/xquery/origami/μ';

declare function μ:xml($items as item()*)
as node()*
{
    μ:xml($items, [], μ:qname-resolver())
};

declare function μ:xml($items as item()*, $resolver-or-args as function(*))
as node()*
{
    if ($resolver-or-args instance of array(*)) then
        μ:xml($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:xml($items, [], $resolver-or-args)
};

declare function μ:xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    μ:to-xml($items, $args, $name-resolver)
};

declare function μ:json($items as item()*)
as xs:string
{
    μ:json($items, [], function($name) { $name })
};

declare function μ:json($items as item()*, $resolver-or-args as function(*))
as xs:string
{
    if ($resolver-or-args instance of array(*)) then
        μ:json($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:json($items, [], $resolver-or-args)
};

declare function μ:json($items as item()*, $args as array(*), $name-resolver as function(*))
as xs:string
{
    serialize(μ:to-json(if (count($items) gt 1) then array { $items } else $items, $args, $name-resolver), map { 'method': 'json' })
};

declare function μ:apply($items as item()*)
as item()*
{
    μ:apply($items, [])
};

declare function μ:apply($items as item()*, $args as array(*))
as item()*
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*) return $item
        case map(*) return  $item
        case function(*) return μ:apply(apply($item, $args), $args)
        default return $item
};

declare function μ:mu($xml)
{
    μ:from-xml($xml)
};

declare %private function μ:to-json($items as item()*, $args as array(*), $name-resolver as function(*))
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*)
        return
            array:fold-left($item, [],
                function($a,$b) {
                    if (count($b) gt 1) then
                        (: splice the sequence into the array to avoid JSON
                         : serialization error :)
                        array:join((
                            $a,
                            fold-left($b, [],
                                function($c,$d) {
                                    array:append($c, μ:to-json($d, $args, $name-resolver))
                                })
                        ))
                    else
                        array:append($a, μ:to-json($b, $args, $name-resolver))
                }
            )
        case map(*)
        return
            map:merge(
                map:for-each($item,
                    function($a,$b) {
                        map:entry($a, μ:to-json($b, $args, $name-resolver)) }))
        case function(*) return μ:to-json(apply($item, $args), $args, $name-resolver)
        case node() return μ:from-xml($item)
        default return $item
};

declare %private function μ:from-xml($xml)
as item()*
{
    for $node in $xml
    return
        typeswitch($node)
        case element()
        return
            array {
                name($node),
                if ($node/@*) then
                    map:merge((
                        for $a in $node/@*
                        return map:entry(name($a), data($a))))
                else
                    (),
                μ:from-xml($node/node())
            }
        case comment() | processing-instruction() return ()
        case text() return string($node)
        default return $node
};

declare %private function μ:to-xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*) return μ:to-element($item, $args, $name-resolver)
        case map(*) return  μ:to-attributes($item, $args, $name-resolver)
        case function(*) return μ:to-xml(apply($item, $args), $args, $name-resolver)
        case empty-sequence() return ()
        case node() return $item
        default return text { $item }
};

declare %private function μ:to-element($item as array(*), $args as array(*), $name-resolver as function(*))
as element()?
{
    if (array:size($item) gt 0) then
        element { $name-resolver(array:head($item)) } {
            array:fold-left(
                array:tail($item),
                (),
                function($n, $i) {
                    ($n, μ:to-xml($i, $args, $name-resolver))
                }
            )
        }
    else
        ()
};

declare %private function μ:to-attributes($item as map(*), $args as array(*), $name-resolver as function(*))
as attribute()*
{
    map:for-each($item,
        function($k,$v) {
            if (not(starts-with($k,'μ:'))) then
                attribute { $name-resolver($k) } {
                    data(
                        typeswitch ($v)
                        case function(*)
                        return apply($v, $args)
                        default
                        return $v
                    ) }
            else
                ()
        })
};

declare function μ:qname-resolver()
as function(xs:string) as xs:QName
{
    μ:qname(?, μ:ns(), ())
};

declare function μ:qname-resolver($ns-map as map(*))
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, ())
};

declare function μ:qname-resolver($ns-map as map(*), $default-ns as xs:string)
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, $default-ns)
};

(:~
 : As XQuery doesn't allow access to namespace nodes (as XSLT does)
 : construct them indirectly via QName#2.
 :)
declare function μ:qname($name as xs:string, $ns-map as map(*))
as xs:QName
{
    μ:qname($name, $ns-map, ())
};

declare function μ:qname($name as xs:string, $ns-map as map(*), $default-ns as xs:string?)
as xs:QName
{
    if (contains($name, ':')) then
        let $prefix := substring-before($name,':')
        let $local := substring-after($name, ':')
        let $ns := $ns-map($prefix)
        return
            if ($ns = $default-ns) then
                QName($ns, $local)
            else
                QName($ns, concat($prefix,':',$local))
    else
        if ($default-ns) then
            QName($default-ns, $name)
        else
            QName((), $name)
};

declare function μ:ns()
as map(*)
{
    μ:ns(map {})
};

declare function μ:ns($ns-map as map(*))
as map(*)
{
    map:merge((
        $ns-map,
        for $ns in doc(concat(file:base-dir(),'/nsmap.xml'))/nsmap/*
        return
            map:entry(string($ns/@prefix), string($ns/@uri))
    ))
};

declare function μ:cons($a,$b)
as item()*
{
    ($a,$b)
};

declare function μ:head($mu as array(*)?)
as item()*
{
    if (empty($mu)) then
        ()
    else
        array:head($mu)
};

declare function μ:tail($mu as array(*)?)
as item()*
{
    if (not(empty($mu))) then
        tail(μ:seq($mu))
    else
        ()
};

(:~
 : Remove level of array and change it into a normal sequence.
 :)
declare function μ:seq($mu as array(*))
as item()*
{
    typeswitch ($mu)
        case array(*)
            return array:fold-left($mu, (), μ:cons#2)
        default
            return $mu
};

declare function μ:content($mu)
as item()*
{
    typeswitch($mu)
    case array(*)
    return
        let $c := array:tail($mu)
        return
            if (array:head($c) instance of map(*)) then
                array:tail($c)
            else
                $c
    default
    return $mu/node()
};


[docs.xforms] tidied up

[docs.xforms] tidied up

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

xquery version "1.0";

import module namespace request="http://exist-db.org/xquery/request";
import module namespace xdb = "http://exist-db.org/xquery/xmldb";

(:~
    Initialize the todo application
:)
declare function local:setup() {
    if (not(collection("/db/todo"))) then
        let $coll := xdb:create-collection("/db", "todo")
        let $home := system:get-exist-home()
    	let $dir :=
    		if (doc-available(concat("file:///", $home, "/webapp/download.xml"))) then
    			concat($home, "/webapp")
    		else if(ends-with($home, "WEB-INF")) then
    			substring-before($home, "WEB-INF")
    		else
    			concat($home)
    	return (
    		xdb:store-files-from-pattern("/db/todo", concat($dir, "/xforms/tasks"), "todo-projects.xml", "text/xml"),
    		xdb:store("/db/todo", "3d5ce5a0-2420-4eb9-a94f-fcbe05589f99.xml",
    		    <todo id="3d5ce5a0-2420-4eb9-a94f-fcbe05589f99">
                    <name>Add some tasks and projects</name>
                    <project>default</project>
                    <priority>0</priority>
                    <progress/>
                    <description/>
                </todo>
            )
    	)
    else ()
};

let $dummy := local:setup()
let $uri := request:get-uri()
let $context := request:get-context-path()
let $path := substring-after($uri, $context)
let $name := replace($uri, '^.*/([^/]+)$', '$1')
return
    (: send docbook docs through the db2xhtml stylesheet :)
   if (ends-with($uri, 'examples.xml') or ends-with($uri, 'xforms.xml')) then
         <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
 			<view>
 				<forward servlet="XSLTServlet">
 					<set-attribute name="xslt.stylesheet"
 						value="/stylesheets/db2html.xsl"/>
 				</forward>
 			</view>
             <cache-control cache="no"/>
 		</dispatch>
    else if (ends-with($uri, ".xml")) then
        <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
			<view>
				<forward servlet="XSLTServlet">
					<set-attribute name="xslt.stylesheet"
						value="/stylesheets/db2xhtml.xsl"/>
				</forward>
			</view>
			<cache-control cache="yes"/>
		</dispatch>
    (: make sure the global css and js files are resolved :)
    else if ($name = ('default-style.css', 'curvycorners.js')) then
        let $newPath := replace($path, '^.*/([^/]+/[^/]+)$', '/$1')
        return
            <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
    			<forward url="{$newPath}"/>
    			<cache-control cache="yes"/>
    		</dispatch>

    else
        (: everything else is passed through :)
        <ignore xmlns="http://exist.sourceforge.net/NS/exist">
            <cache-control cache="yes"/>
    	</ignore>

xquery version "1.0";

import module namespace request="http://exist-db.org/xquery/request";
import module namespace xdb = "http://exist-db.org/xquery/xmldb";

(:~
    Initialize the todo application
:)
declare function local:setup() {
    if (not(collection("/db/todo"))) then
        let $coll := xdb:create-collection("/db", "todo")
        let $home := system:get-exist-home()
    	let $dir :=
    		if (doc-available(concat("file:///", $home, "/webapp/download.xml"))) then
    			concat($home, "/webapp")
    		else if(ends-with($home, "WEB-INF")) then
    			substring-before($home, "WEB-INF")
    		else
    			concat($home)
    	return (
    		xdb:store-files-from-pattern("/db/todo", concat($dir, "/xforms/tasks"), "todo-projects.xml", "text/xml"),
    		xdb:store("/db/todo", "3d5ce5a0-2420-4eb9-a94f-fcbe05589f99.xml",
    		    <todo id="3d5ce5a0-2420-4eb9-a94f-fcbe05589f99">
                    <name>Add some tasks and projects</name>
                    <project>default</project>
                    <priority>0</priority>
                    <progress/>
                    <description/>
                </todo>
            )
    	)
    else ()
};

let $dummy := local:setup()
let $uri := request:get-uri()
let $context := request:get-context-path()
let $path := substring-after($uri, $context)
let $name := replace($uri, '^.*/([^/]+)$', '$1')
return
    (: send docbook docs through the db2xhtml stylesheet :)
    if (ends-with($uri, 'examples.xml') or ends-with($uri, 'xforms.xml')) then
        <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
			<view>
				<forward servlet="XSLTServlet">
					<set-attribute name="xslt.stylesheet"
						value="/stylesheets/db2html.xsl"/>
				</forward>
			</view>
            <cache-control cache="no"/>
		</dispatch>
    else
        <ignore xmlns="http://exist.sourceforge.net/NS/exist">
            <cache-control cache="yes"/>
    	</ignore>


corrected incorrect regular expressions

corrected incorrect regular expressions

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 ann = "http://www.zorba-xquery.com/annotations";
declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 :

*
 for matching any number of unknown characters

 :

?
 for matching one unknown character

 :

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

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

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

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

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

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

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

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

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

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

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

(:~
 : Returns the last component from the
$path
, deleting any
 : trailing directory-separator characters. If
$path
 consists
 : entirely directory-separator characters, the empty string is returned. If
 :
$path
 is the empty string, the string
"."
 is returned,
 : signifying the current directory.
 :
 : No path existence check is made.
 :
 : @param $path A file path/URI.
 : @return The base name of this file.
 :)
declare function file:base-name($path as xs:string) as xs:string
{
  let $delim := file:directory-separator()
  let $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 ann = "http://www.zorba-xquery.com/annotations";
declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 :

*
 for matching any number of unknown characters

 :

?
 for matching one unknown character

 :

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


[ignore] bibliographic demo: always add mods:extension to the instance data for a tab.

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

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

(: get-instance.xq - get the instance data to load into the edit XForms application.
This version of get-instance can prune the mods record to only load the instance data that is needed for a given tab.

Note that the instance that this script returns MUST include an ID for saving.

There are many sub-parts to the form.  Each part needs to map the right part of an instance into one tab
:)

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

(: This is the document we are going to edit if we are not creating a new record :)
let $id := request:get-parameter('id', '')
let $new := request:get-parameter('new', '')

(: TODO - check for required id parameter.  In this case we need either an id or new. :)

(: This is the ID of the tab but we just use tab in the URL.  If no tab-id is
   specified then we use the title tab.  :)
let $tab-id := request:get-parameter('tab-id', 'title')
let $debug := request:get-parameter('debug', 'false')

(: The data collection passed in the URL :)
let $collection := request:get-parameter('data', ())

let $app-collection := $style:db-path-to-app
let $data-collection :=
    if ($collection) then
        $collection
    else
        $style:db-path-to-app-data

(: If we are creating a new form, then just get the part of the new-instance.  Else get the data from the correct document
   in the mods data collection that has the correct collection id :)
let $full-instance :=
   if ($new = 'true' or $id = 'new' or $id='')
      then doc(concat($style:db-path-to-app, '/edit/new-instance.xml'))/mods:mods
      else collection($data-collection)//mods:mods[@ID = $id]

(: open the tab databse so for a given tab, we go into the tab database and get the right path :)
let $tab-data := doc(concat($style:db-path-to-app, '/edit/tab-data.xml'))/tabs

(: get the tab data for this tab. :)
let $tab-data := $tab-data/tab[tab-id = $tab-id]

(: get a list of all the XPath expressions to include in this instance used by the form :)
let $paths := $tab-data/path/text()

(: build up a string of prefix:element pairs for doing an eval :)
let $path-string :=
string-join((
    for $path in $paths
    return
      concat('mods:', $path),
    'mods:extension')
  , ', ')

(: now get the eval string ready for use :)
let $eval-string := concat('$full-instance/', '(', $path-string, ')')

return
<mods:mods ID="{$id}">

  { (: this is used for debuggin only.  Just add the debug=true to the URL and it will be added to the output :)
  if ($debug = 'true')
    then <debug>
      <id>{$id}</id>
      <tab-id>{$tab-id}</tab-id>
      <instance>{$full-instance}</instance>
      <path-string>{$path-string}</path-string>
      <eval-string>{$eval-string}</eval-string>
    </debug> else ()
  }

  { (: this is where we run the query that gets just the data we need for this tab :)
  util:eval($eval-string)}

  </mods:mods>

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

(: get-instance.xq - get the instance data to load into the edit XForms application.
This version of get-instance can prune the mods record to only load the instance data that is needed for a given tab.

Note that the instance that this script returns MUST include an ID for saving.

There are many sub-parts to the form.  Each part needs to map the right part of an instance into one tab
:)

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

(: This is the document we are going to edit if we are not creating a new record :)
let $id := request:get-parameter('id', '')
let $new := request:get-parameter('new', '')

(: TODO - check for required id parameter.  In this case we need either an id or new. :)

(: This is the ID of the tab but we just use tab in the URL.  If no tab-id is
   specified then we use the title tab.  :)
let $tab-id := request:get-parameter('tab-id', 'title')
let $debug := request:get-parameter('debug', 'false')

(: The data collection passed in the URL :)
let $collection := request:get-parameter('data', ())

let $app-collection := $style:db-path-to-app
let $data-collection :=
    if ($collection) then
        $collection
    else
        $style:db-path-to-app-data

(: If we are creating a new form, then just get the part of the new-instance.  Else get the data from the correct document
   in the mods data collection that has the correct collection id :)
let $full-instance :=
   if ($new = 'true' or $id = 'new' or $id='')
      then doc(concat($style:db-path-to-app, '/edit/new-instance.xml'))/mods:mods
      else collection($data-collection)//mods:mods[@ID = $id]

(: open the tab databse so for a given tab, we go into the tab database and get the right path :)
let $tab-data := doc(concat($style:db-path-to-app, '/edit/tab-data.xml'))/tabs

(: get the tab data for this tab. :)
let $tab-data := $tab-data/tab[tab-id = $tab-id]

(: get a list of all the XPath expressions to include in this instance used by the form :)
let $paths := $tab-data/path/text()

(: build up a string of prefix:element pairs for doing an eval :)
let $path-string :=
string-join(
    for $path in $paths
    return
      concat('mods:', $path)
  , ', ')

(: now get the eval string ready for use :)
let $eval-string := concat('$full-instance/', '(', $path-string, ')')

return
<mods:mods ID="{$id}">

  { (: this is used for debuggin only.  Just add the debug=true to the URL and it will be added to the output :)
  if ($debug = 'true')
    then <debug>
      <id>{$id}</id>
      <tab-id>{$tab-id}</tab-id>
      <instance>{$full-instance}</instance>
      <path-string>{$path-string}</path-string>
      <eval-string>{$eval-string}</eval-string>
    </debug> else ()
  }

  { (: this is where we run the query that gets just the data we need for this tab :)
  util:eval($eval-string)}

  </mods:mods>


cleaned up UI for adapters

cleaned up UI for adapters

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

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_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
declare namespace csd = "urn:ihe:iti:csd:2013";

declare
  %rest:path("/CSD/adapter")
  %rest:GET
  %output:method("xhtml")
  function page:show_types()
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))
  let $types := distinct-values($funcs/csd:extension[@urn='urn:openhie.org:openinfoman:adapter']/@type)
  let $type_list :=

      {
      for $type in $types
      let $s_type := string($type)
      let $href := concat($csd_webconf:baseurl ,"CSD/adapter/" , $s_type)
      return
{$s_type}

      }

  return page:wrapper((
Available Adapaters
, $type_list))
};

declare
  %rest:path("/CSD/adapter/{$type}/{$search_name}")
  %output:method("xhtml")
  function page:show_endpoints($search_name,$type)
{
  let $function := csr_proc:get_any_function_definition($csd_webconf:db,$search_name)
  let $extensions :=  $function/csd:extension[@urn='urn:openhie.org:openinfoman:adapter' and  @type=$type]

  let $contents :=
    if (count($extensions) = 0)
      (:not a read fhir entity query. should 404 or whatever is required by FHIR :)
    then ("Not a " , $type , " compatible stored functions" )
    else

{$type} Documents

{string($function/@urn)}

          {
  	    for $doc_name in csd_dm:registered_documents($csd_webconf:db)
	    return

	      {string($doc_name)}

	  }

  return csd_webconf:wrapper($contents)

};

declare
  %rest:path("/CSD/adapter/{$type}")
  %rest:GET
  %output:method("xhtml")
  function page:show_type($type)
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))[./csd:extension[@type = $type] ]
  let $adaptations :=

{$type}

        {
	    for $adapter_func in $funcs
            let $desc := $adapter_func/csd:description
	    let $urn := string($adapter_func/@urn)
	    return

	      Type ({$type})

		Document Index for {$urn}

{$desc}

	}

  let $contents :=
    (
      Adapters
      ,$adaptations
      )
  return page:wrapper($contents)
};

declare function page:wrapper($response) {
  let $headers :=
  (<link rel="stylesheet" type="text/css" media="screen"   href="{$csd_webconf:baseurl}static/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css"/>
  ,

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

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_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
declare namespace csd = "urn:ihe:iti:csd:2013";

declare
  %rest:path("/CSD/adapter")
  %rest:GET
  %output:method("xhtml")
  function page:show_types()
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))
  let $searches :=

      {
	for $adapter_func in $funcs[./csd:extension[@urn='urn:openhie.org:openinfoman:adapter']]
        let $desc := $adapter_func/csd:description
        let $types := $adapter_func/csd:extension[@urn='urn:openhie.org:openinfoman:adapter']/@type
	let $urn := string($adapter_func/@urn)
	return
	  for $type in $types
	  let $s_type := string($type)
	  return

Stored Function {$urn}

	      Type ({$s_type})

		Act on a Document

{string($desc)}

      }

  return page:wrapper($searches)
};

declare
  %rest:path("/CSD/adapter/{$type}/{$search_name}")
  %output:method("xhtml")
  function page:show_endpoints($search_name,$type)
{
  let $function := csr_proc:get_any_function_definition($csd_webconf:db,$search_name)
  let $extensions :=  $function/csd:extension[@urn='urn:openhie.org:openinfoman:adapter' and  @type=$type]

  let $contents :=
    if (count($extensions) = 0)
      (:not a read fhir entity query. should 404 or whatever is required by FHIR :)
    then ("Not a " , $type , " compatible stored functions" )
    else

{$type} Documents

{string($function/@urn)}

          {
  	    for $doc_name in csd_dm:registered_documents($csd_webconf:db)
	    return

	      {string($doc_name)}

	  }

  return csd_webconf:wrapper($contents)

};

declare
  %rest:path("/CSD/adapter/{$type}")
  %rest:GET
  %output:method("xhtml")
  function page:show_type($type)
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))[./csd:extension[@type = $type] ]
  let $adaptations :=

{$type}

        {
	    for $adapter_func in $funcs
            let $desc := $adapter_func/csd:description
	    let $urn := string($adapter_func/@urn)
	    return

	      Type ({$type})

		Document Index for {$urn}

{$desc}

	}

  let $contents :=
    (
      Adapters
      ,$adaptations
      )
  return page:wrapper($contents)
};

declare function page:wrapper($response) {
  let $headers :=
  (<link rel="stylesheet" type="text/css" media="screen"   href="{$csd_webconf:baseurl}static/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css"/>
  ,


Reformat the math built-in function definitions.

xquery version "3.0" encoding "UTF-8";
(:~
 : XPath and XQuery Functions and Operators: Trigonometric and exponential functions
 :
 : @see https://www.w3.org/TR/2014/REC-xpath-functions-30-20140408/#trigonometry
 :
 : This documentation includes material copied from or derived from the XPath and
 : XQuery Functions and Operators 3.0 specification. Copyright © 2017 W3C®
 : (MIT, ERCIM, Keio, Beihang).
 :)
module namespace math = "http://www.w3.org/2005/xpath-functions/math";

declare namespace a = "http://reecedunn.co.uk/xquery/annotations";
declare namespace o = "http://reecedunn.co.uk/xquery/options";

declare option o:requires "xpath-functions/3.0";

declare %a:since("xpath-functions", "3.0-20140408") function math:acos(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:asin(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:atan(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:atan2(
  $y as xs:double,
  $x as xs:double
) as xs:double external;

declare %a:since("xpath-functions", "3.0-20140408") function math:cos(
  $θ as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:exp(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:exp10(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:log(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:log10(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:pi() as xs:double external;

declare %a:since("xpath-functions", "3.0-20140408") function math:pow(
  $x as xs:double?,
  $y as xs:numeric
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:sin(
  $θ as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:sqrt(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("xpath-functions", "3.0-20140408") function math:tan(
  $θ as xs:double?
) as xs:double? external;

declare %a:since("basex", "7.0") function math:e() as xs:double external;

declare %a:since("basex", "7.0") function math:sinh(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("basex", "7.0") function math:cosh(
  $arg as xs:double?
) as xs:double? external;

declare %a:since("basex", "7.0") function math:tanh(
  $arg as xs:double?
) as xs:double? external;

declare %a:restrict-until("$string", "basex", "9.1", "xs:string")
        %a:since("basex", "7.3") function math:crc32(
  $string as xs:string?
) as xs:hexBinary? external;

declare %a:since("basex", "7.3") %a:until("basex", "7.5", "random:uuid#0") function math:uuid() external;

declare %a:since("basex", "7.0") %a:until("basex", "7.5", "random:double#0") function math:random() external;

xquery version "3.0" encoding "UTF-8";
(:~
 : XPath and XQuery Functions and Operators: Trigonometric and exponential functions
 :
 : @see https://www.w3.org/TR/2014/REC-xpath-functions-30-20140408/#trigonometry
 :
 : This documentation includes material copied from or derived from the XPath and
 : XQuery Functions and Operators 3.0 specification. Copyright © 2017 W3C®
 : (MIT, ERCIM, Keio, Beihang).
 :)
module namespace math = "http://www.w3.org/2005/xpath-functions/math";

declare namespace a = "http://reecedunn.co.uk/xquery/annotations";
declare namespace o = "http://reecedunn.co.uk/xquery/options";

declare option o:requires "xpath-functions/3.0";

declare %a:since("xpath-functions", "3.0-20140408") function math:acos($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:asin($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:atan($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:atan2($y as xs:double, $x as xs:double) as xs:double external;
declare %a:since("xpath-functions", "3.0-20140408") function math:cos($θ as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:exp($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:exp10($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:log($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:log10($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:pi() as xs:double external;
declare %a:since("xpath-functions", "3.0-20140408") function math:pow($x as xs:double?, $y as xs:numeric) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:sin($θ as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:sqrt($arg as xs:double?) as xs:double? external;
declare %a:since("xpath-functions", "3.0-20140408") function math:tan($θ as xs:double?) as xs:double? external;
declare %a:since("basex", "7.0") function math:e() as xs:double external;
declare %a:since("basex", "7.0") function math:sinh($arg as xs:double?) as xs:double? external;
declare %a:since("basex", "7.0") function math:cosh($arg as xs:double?) as xs:double? external;
declare %a:since("basex", "7.0") function math:tanh($arg as xs:double?) as xs:double? external;
declare %a:restrict-until("$string", "basex", "9.1", "xs:string")
        %a:since("basex", "7.3") function math:crc32($string as xs:string?) as xs:hexBinary? external;
declare %a:since("basex", "7.3") %a:until("basex", "7.5") %a:see-also("basex", "7.5", "random:uuid") function math:uuid() external;
declare %a:since("basex", "7.0") %a:until("basex", "7.5") %a:see-also("basex", "7.5", "random:double") function math:random() external;


Annotate the BaseX sessions functions with versioned type restrictions.

xquery version "3.0";
(:~
 : BaseX Sessions Module functions
 :
 : @see http://docs.basex.org/wiki/Sessions_Module
 :)
module namespace sessions = "http://basex.org/modules/sessions";

declare namespace a = "http://reecedunn.co.uk/xquery/annotations";
declare namespace o = "http://reecedunn.co.uk/xquery/options";

declare option o:requires-import "basex/7.5; location-uri=(none)";

declare %a:since("basex", "7.5") function sessions:ids() as xs:string external;
declare %a:since("basex", "7.5") function sessions:created($id as xs:string) as xs:dateTime external;
declare %a:since("basex", "7.5") function sessions:accessed($id as xs:string) as xs:dateTime external;
declare %a:since("basex", "7.5") function sessions:names($id as xs:string) as xs:string* external;
declare %a:restrict-until("return", "basex", "7.9", "xs:string?")
        %a:restrict-until("return", "basex", "8.0", "item()?")
        %a:since("basex", "7.5") function sessions:get($id as xs:string, $name as xs:string) as item()* external;
declare %a:restrict-until("return", "basex", "7.9", "xs:string")
        %a:restrict-until("return", "basex", "8.0", "item()")
        %a:restrict-until("$default", "basex", "7.9", "xs:string")
        %a:restrict-until("$default", "basex", "8.0", "item()")
        %a:since("basex", "7.5") function sessions:get($id as xs:string, $name as xs:string, $default as item()*) as item()* external;
declare %a:restrict-until("$value", "basex", "7.9", "xs:string")
        %a:restrict-until("$value", "basex", "8.0", "item()")
        %a:since("basex", "7.5") function sessions:set($name as xs:string, $value as item()*) as empty-sequence() external;
declare %a:since("basex", "7.5") function sessions:delete($name as xs:string) as empty-sequence() external;
declare %a:since("basex", "7.5") function sessions:close() as empty-sequence() external;

xquery version "3.0";
(:~
 : BaseX Sessions Module functions
 :
 : @see http://docs.basex.org/wiki/Sessions_Module
 :)
module namespace sessions = "http://basex.org/modules/sessions";

declare namespace a = "http://reecedunn.co.uk/xquery/annotations";
declare namespace o = "http://reecedunn.co.uk/xquery/options";

declare option o:requires-import "basex/7.5; location-uri=(none)";

declare %a:since("basex", "7.5") function sessions:ids() as xs:string external;
declare %a:since("basex", "7.5") function sessions:created($id as xs:string) as xs:dateTime external;
declare %a:since("basex", "7.5") function sessions:accessed($id as xs:string) as xs:dateTime external;
declare %a:since("basex", "7.5") function sessions:names($id as xs:string) as xs:string* external;
declare %a:since("basex", "7.5") function sessions:get($id as xs:string, $name as xs:string) as item()* (: [7.5] as xs:string? [7.9] as item()? [8.0] as item()* :) external;
declare %a:since("basex", "7.5") function sessions:get($id as xs:string, $name as xs:string, $default as item()* (: $default [7.5] as xs:string [8.0] as item()* :)) as item()* (: [7.5] as xs:string:) external;
declare %a:since("basex", "7.5") function sessions:set($name as xs:string, $value as item()* (: $value [7.5] as item() [8.0] as item()* :)) as empty-sequence() external;
declare %a:since("basex", "7.5") function sessions:delete($name as xs:string) as empty-sequence() external;
declare %a:since("basex", "7.5") function sessions:close() as empty-sequence() external;