proof corrections on comments for rocket survey data sample

xquery version "3.1";
declare namespace ebb="http://newtfire.org";
declare variable $rocketColl := collection('/db/rocket/');
(: ebb: This script introduces you to user-defined functions in XQuery.
These can be useful for taking values from your XQuery variables and performing a conversion, as we're doing here.
Since the Rocket Launches project has multiple attributes encoding launch dates and landing dates in the same xs:dateTime format,
and since they work with the xs:duration datatype too, for coding how long a mission lasted,
we can work with these data to do "date arithmetic" to make simple graphs and charts if we can convert it to a simple decimal notation.
Let's start reading this by skipping past the section I've marked off with asterisks as USER-DEFINED FUNCTIONS, and come back to it :)

(: **************** USER-DEFINED FUNCTIONS **************************:)
(: CONVERT DATETIME to DECIMAL :)
(: ebb :This user-defined function will unpack the date components from an input xs:dateTime datatype.
We will first extract the year from the datatype with year-from-dateTime(),
and then use format-dateTime() to return a 'picture string' that will give us the date's numerical position in the year.
We then divide that date number by 365 (since most years have 365 days).
This will give us a decimal format we can use in making charts and graphs.
Note how we use question marks and apply datatypes do define the function input and output.
:)
declare function ebb:dateDecimalConverter($dT as xs:dateTime?)
as xs:decimal?
{
let $year := year-from-dateTime($dT) ! xs:integer(.)
let $dayInYear := format-dateTime($dT, '[d]') ! xs:integer(.)
let $decimalDay := $dayInYear div 365
return $year + $decimalDay
};
(: CONVERT DURATION TO DECIMAL :)
(: ebb: The next user-defined function will convert the xs:duration datatype into a decimal value based on number of days.
 : If we input a duration indicating 1 day, 6 hours, and 0 minutes, we should return 1.25 from this function.
 : Our function takes four input arguments: $d (a value for the number of days), $h (a value for the number of hours),
 : $m (for the number of minutes), and $s (for the number of seconds). In our XQuery, we created variables to separate each of these values
 : and submitted them as the input to this function.
 : Here's what we're calculating: There are 24 hours in a day. (Divide the H portion by 24) and add it to the days.
 : There are 60 minutes in an hour, and 60 * 24 gives the total number of minutes in a day.
 : So we divide the M portion by 60 * 24) and add it to hours.
 : There are 60 seconds in a minute. and 60 * 60 * 24 gives us the total number of seconds in a day.
 : We then divide the S portion by 60 * 60 * 24 and add that value to minutes and hours to give us
 : the decimal proporition of a day in the duration. Finally we add that to the number of days in the duration.
 : :)
declare function ebb:durationConverter($d as xs:integer, $h as xs:integer, $m as xs:integer, $s as xs:integer?)
as xs:decimal?
{
let $TDec := $h div 24 + $m div (60 * 24) + $s div (60 * 60 * 24)
return $d + $TDec
};
(: *******************END USER-DEFINED FUNCTIONS ********************** :)

let $launchDateTimes := $rocketColl//launch/@sDateTime
for $l in $launchDateTimes
(: ebb: To do date arithmetic, take a look at these functions: https://www.w3.org/TR/xpath-functions-31/#dates-times
 : We'll write a user-defined function to convert this datatype into a decimal notation. :)
let $lDec := ebb:dateDecimalConverter($l)
let $m := $rocketColl//launch[@sDateTime = $l]/preceding-sibling::sts
let $mID := $m  ! tokenize(., ':')[1]

let $duration := $m/following-sibling::duration/@time
(: ebb: To do duration arithmetic, start by looking at the functions here:
: https://www.w3.org/TR/xpath-functions-31/#durations
 : Let's try representing durations in terms of days with a decimal.
 : We'll write a user-defined function to convert hours, minutes, and seconds into a fraction of the day. :)
let $durDays := days-from-duration($duration)
let $durHours := hours-from-duration($duration)
let $durMins := minutes-from-duration($duration)
let $durSecs := seconds-from-duration($duration)
(: Now in the next line, we send our variables to our user-defined function for turning date datatypes into decimal values. :)
let $durDayDec := ebb:durationConverter($durDays, $durHours, $durMins, $durSecs)

order by $lDec
return concat('Launch Date: ', $l, ', mission: ', $mID, ': This Launch Decimal Date: ', $lDec,
': Duration: ', $duration, ': Decimal Notation:', $durDayDec)

xquery version "3.1";
declare namespace ebb="http://newtfire.org";
declare variable $rocketColl := collection('/db/rocket/');
(: ebb: This script introduces you to user-defined functions in XQuery.
These can be useful for taking values from your XQuery variables and performing a conversion, as we're doing here.
Since the Rocket Launches project has multiple attributes encoding launch dates and landing dates in the same xs:dateTime format,
and since they work with the xs:duration datatype too, for coding how long a mission lasted,
we can work with these data to do "date arithmetic" to make simple graphs and charts if we can convert it to a simple decimal notation.
Let's start reading this by skipping past the section I've marked off with asterisks as USER-DEFINED FUNCTIONS, and come back to it :)

(: **************** USER-DEFINED FUNCTIONS **************************:)
(: CONVERT DATETIME to DECIMAL :)
(: ebb :This user-defined function will unpack the date components from an input xs:dateTime datatype.
We will first extract the year from the datatype with year-from-dateTime(),
and then use format-dateTime() to return a 'picture string' that will give us the date's numerical position in the year.
We then divide that date number by 365 (since most years have 365 days).
This will give us a decimal format we can use in making charts and graphs.
Note how we use question marks and apply datatypes do define the function input and output.
:)
declare function ebb:dateDecimalConverter($dT as xs:dateTime?)
as xs:decimal?
{
let $year := year-from-dateTime($dT) ! xs:integer(.)
let $dayInYear := format-dateTime($dT, '[d]') ! xs:integer(.)
let $decimalDay := $dayInYear div 365
return $year + $decimalDay
};
(: CONVERT DURATION TO DECIMAL :)
(: ebb: The next user-defined function will convert the xs:duration datatype into a decimal value based on number of days.
 : If we input a duration indicating 1 day, 6 hours, and 0 minutes, we should return 1.25 from this function.
 : Our function takes four input arguments: $d (a value for the number of days), $h (a value for the number of hours),
 : $m (for the number of minutes), and $s (for the number of seconds). In our XQuery, we created variables to separate each of these values
 : and submitted them as the input to this function.
 : Here's what we're calculating: There are 24 hours in a day. (Divide the H portion by 24) and add it to the days.
 : There are 60 minutes in an hour, and 60 * 24 gives the total number of minutes in a day.
 : So we divide the M portion by 60 * 24) and add it to hours.
 : There are 60 seconds in a minute. and 60 * 60 * 24 gives us the total number of seconds in a day.
 : We then divide the S portion by 60 * 60 * 24 and add that value to minutes and hours to give us
 : the decimal proporition of a day in the duration. Finally we add that to the number of days in the duration.
 : :)
declare function ebb:durationConverter($d as xs:integer, $h as xs:integer, $m as xs:integer, $s as xs:integer?)
as xs:decimal?
{
let $TDec := $h div 24 + $m div (60 * 24) + $s div (60 * 60 * 24)
return $d + $TDec
};
(: *******************END USER-DEFINED FUNCTIONS ********************** :)

let $launchDateTimes := $rocketColl//launch/@sDateTime
for $l in $launchDateTimes
(: To do date arithmetic, take a look at these functions: https://www.w3.org/TR/xpath-functions-31/#dates-times
 : We'll write a user-defined function to convert this datatype into a decimal notation. :)
let $lDec := ebb:dateDecimalConverter($l)
let $m := $rocketColl//launch[@sDateTime = $l]/preceding-sibling::sts
let $mID := $m  ! tokenize(., ':')[1]

let $duration := $m/following-sibling::duration/@time
(: ebb: To do duration arithmetic, start by looking at the functions here:
: https://www.w3.org/TR/xpath-functions-31/#durations
 : Let's try representing durations in terms of days with a decimal.
 : We'll write a user-defined function to convert hours, minutes, and seconds into a fraction of the day. :)
let $durDays := days-from-duration($duration)
let $durHours := hours-from-duration($duration)
let $durMins := minutes-from-duration($duration)
let $durSecs := seconds-from-duration($duration)
(: Now in line 43, we send our variables to our user-defined function for turning date datatypes into decimal values. :)
let $durDayDec := ebb:durationConverter($durDays, $durHours, $durMins, $durSecs)

order by $lDec
return concat('Launch Date: ', $l, ', mission: ', $mID, ': This Launch Decimal Date: ', $lDec,
': Duration: ', $duration, ': Decimal Notation:', $durDayDec)


improving date arithmetic example

improving date arithmetic example

xquery version "3.1";
declare namespace ebb="http://newtfire.org";
declare variable $rocketColl := collection('/db/rocket/');
(: ebb: This XQuery introduces you to user-defined functions in XQuery.
These can be useful for taking values from your XQuery variables and performing a conversion, as we're doing here.
Since the Rocket Launches project has multiple attributes encoding launch dates and landing dates in the same xs:dateTime format,
and since they work with the xs:duration datatype too, for coding how long a mission lasted,
we can work with these data to do "date arithmetic" to make simple graphs and charts if we can convert it to a simple decimal notation.
Let's start reading this by skipping past the section I've marked off with asterisks as USER-DEFINED FUNCTIONS, and come back to it :)

(: **************** USER-DEFINED FUNCTIONS **************************:)
(: CONVERT DATETIME to DECIMAL :)
(: ebb :This user-defined function will unpack the date components from an input xs:dateTime datatype.
We will first extract the year from the datatype with year-from-dateTime(),
and then use format-dateTime() to return a 'picture string' that will give us the date's numerical position in the year.
We then divide that date number by 365 (since most years have 365 days).
This will give us a decimal format we can use in making charts and graphs. :)
declare function ebb:dateDecimalConverter($dT as xs:dateTime)
as xs:decimal?
{
let $year := year-from-dateTime($dT) ! xs:integer(.)
let $dayInYear := format-dateTime($dT, '[d]') ! xs:integer(.)
let $decimalDay := $dayInYear div 365
return $year + $decimalDay
};
(: CONVERT DURATION TO DECIMAL :)
(: ebb: The next user-defined function will convert the xs:duration datatype into a decimal value based on number of days.
 : So if we input a duration indicating 1 day, 6 hours, and 0 minutes, we should return 1.25 from this function.
 : Here's what we're calculating: There are 24 hours in a day. (Divide the H portion by 24) and add it to the days.
 : There are 60 minutes in an hour. (Divide the M portion by 60 * 24) and add it to hours.
 : There are 60 seconds in a minute (Divide the S portion by 60 * 60 * 24) and add it to minutes and hours
 : :)
declare function ebb:durationConverter($d as xs:integer, $h as xs:integer, $m as xs:integer, $s as xs:integer?)
as xs:decimal?
{
let $TDec := $h div 24 + $m div (60 * 24) + $s div (60 * 60 * 24)
return $d + $TDec
};
(: *******************END USER-DEFINED FUNCTIONS ********************** :)

let $launchDateTimes := $rocketColl//launch/@sDateTime
for $l in $launchDateTimes
(: To do date arithmetic, take a look at these functions: https://www.w3.org/TR/xpath-functions-31/#dates-times
 : We'll write a user-defined function to convert this datatype into a decimal notation. :)
let $lDec := ebb:dateDecimalConverter($l)
let $m := $rocketColl//launch[@sDateTime = $l]/preceding-sibling::sts
let $mID := $m  ! tokenize(., ':')[1]

let $duration := $m/following-sibling::duration/@time
(: ebb: To do duration arithmetic, start by looking at the functions here: https://www.w3.org/TR/xpath-functions-31/#durations
 : Let's try representing durations in terms of days with a decimal.
 : We'll write a user-defined function to convert hours, minutes, and seconds into a fraction of the day. :)
let $durDays := days-from-duration($duration)
let $durHours := hours-from-duration($duration)
let $durMins := minutes-from-duration($duration)
let $durSecs := seconds-from-duration($duration)
(: Now in line 43, we send our variables to our user-defined function for turning date datatypes into decimal values. :)
let $durDayDec := ebb:durationConverter($durDays, $durHours, $durMins, $durSecs)

order by $lDec
return concat('Launch Date: ', $l, ', mission: ', $mID, ': This Launch Decimal Date: ', $lDec, ': Duration: ', $duration, ': Decimal Notation:', $durDayDec)

xquery version "3.1";
declare namespace ebb="http://newtfire.org";
declare variable $rocketColl := collection('/db/rocket/');
(: ebb :The user-defined function in line 6 will unpack the date components from the dateTime in this attribute using year-from-dateTime(), and a picture string that will give us the date's numerical position as the nth day in the year. We can turn that into a decimal by dividing it by 365 (since most years have 365 days). This will give us a decimal format we can use. :)
declare function ebb:dateDecimalConverter($dT as xs:dateTime)
as xs:decimal?
{
let $year := year-from-dateTime($dT) ! xs:integer(.)
let $dayInYear := format-dateTime($dT, '[d]') ! xs:integer(.)
let $decimalDay := $dayInYear div 365
return $year + $decimalDay
};
(: ebb: The next user-defined function will convert the xs:duration datatype into a decimal value based on number of days.
 : So if we input a duration indicating 1 day, 6 hours, and 0 minutes, we should return 1.25 from this function.
 : Here's what we're calculating: There are 24 hours in a day. (Divide the H portion by 24) and add it to the days.
 : There are 60 minutes in an hour. (Divide the M portion by 60 * 24) and add it to hours.
 : There are 60 seconds in a minute (Divide the S portion by 60 * 60 * 24) and add it to minutes and hours
 : :)
declare function ebb:durationConverter($d as xs:integer, $h as xs:integer, $m as xs:integer, $s as xs:integer?)
as xs:decimal?
{
let $TDec := $h div 24 + $m div (60 * 24) + $s div (60 * 60 * 24)
return $d + $TDec
};

let $launchDateTimes := $rocketColl//launch/@sDateTime
for $l in $launchDateTimes
(: To do date arithmetic, take a look at these functions: https://www.w3.org/TR/xpath-functions-31/#dates-times
 : We'll write a user-defined function to convert this datatype into a decimal notation. :)
let $lDec := ebb:dateDecimalConverter($l)
let $m := $rocketColl//launch[@sDateTime = $l]/preceding-sibling::sts
let $mID := $m  ! tokenize(., ':')[1]

let $duration := $m/following-sibling::duration/@time
(: ebb: To do duration arithmetic, start by looking at the functions here: https://www.w3.org/TR/xpath-functions-31/#durations
 : Let's try representing durations in terms of days with a decimal. We'll write a user-defined function to convert hours, minutes, and seconds into a fraction of the day. :)
let $durDays := days-from-duration($duration)
let $durHours := hours-from-duration($duration)
let $durMins := minutes-from-duration($duration)
let $durSecs := seconds-from-duration($duration)
(: Now in line 43, we send our variables to our user-defined function for turning date datatypes into decimal values. :)
let $durDayDec := ebb:durationConverter($durDays, $durHours, $durMins, $durSecs)

order by $lDec
return concat('Launch Date: ', $l, ', mission: ', $mID, ': This Launch Decimal Date: ', $lDec, ': Duration: ', $duration, ': Decimal Notation:', $durDayDec)


Fixed format-integer-1-11.

Fixed format-integer-1-11.

(:
 : The format is NOT a decimal digit pattern because it does not contain at
 : least one Unicode digit.  Hence, the format is an unknown format and treated
 : as if it were the format "1".
 :)

fn:format-integer( 1234, "#,###" ) eq "1234"


Annotate the EXPath file functions with versioned type restrictions.

xquery version "3.0";
(:~
 : File Module 1.0 (EXPath Module 20 February 2015)
 :
 : @see http://expath.org/spec/file/1.0
 : @see http://docs.basex.org/wiki/File_Module
 :)
module namespace file = "http://expath.org/ns/file";

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

declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";

declare option o:requires "expath-file/1.0-20100517";

declare option o:implements-module "basex/7.0 as expath-file/1.0-20100517"; (: NOTE: 7.0 is the earliest version definitions are available for. :)
declare option o:implements-module "basex/7.3 as expath-file/1.0-20120614";
declare option o:implements-module "basex/7.8 as expath-file/1.0-20131203";
declare option o:implements-module "basex/8.0 as expath-file/1.0-20150220";

declare option o:implements-module "saxon/pe/9.5 as expath-file/1.0-20150220"; (: TODO: Map saxon versions to the correct file versions. :)
declare option o:implements-module "saxon/ee/9.5 as expath-file/1.0-20150220"; (: TODO: Map saxon versions to the correct file versions. :)

declare %a:since("basex", "7.0") %a:until("basex", "7.3", "file:dir-separator#0") variable $file:directory-separator external;
declare %a:since("basex", "7.0") %a:until("basex", "7.3", "file:path-separator#0") variable $file:path-separator external;

declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:append($path as xs:string, $items as item()*) as empty-sequence() external;
declare %a:restrict-since("$params", "expath-file", "1.0-20120614", "element(output:serialization-parameters)")
        %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:append($path as xs:string, $items as item()*, $params as item()) as empty-sequence() external;
declare %a:restrict-since("$value", "expath-file", "1.0-20120614", "xs:base64Binary")
        %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:append-binary($path as xs:string, $value as (xs:base64Binary|xs:hexBinary)) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text($path as xs:string, $value as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text($path as xs:string, $value as xs:string, $encoding as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text-lines($path as xs:string, $values as xs:string*) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text-lines($path as xs:string, $values as xs:string*, $encoding as xs:string) as empty-sequence() external;
declare %a:until("expath-file", "1.0-20150220") function file:base-dir() as xs:string? external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") %a:since("expath-file", "1.0-20131203", "file:name#1") %a:until("basex", "7.8", "file:name#1") function file:base-name($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") %a:since("expath-file", "1.0-20131203") %a:until("basex", "7.8") function file:base-name($path as xs:string, $suffix as xs:string) as xs:string external;
declare %a:until("expath-file", "1.0-20150220") function file:children($dir as xs:string) as xs:string* external;
declare %a:restrict-since("return", "expath-file", "1.0-20120614", "xs:boolean")
        %a:restrict-since("return", "basex", "7.0", "empty-sequence()")
        %a:since("expath-file", "1.0-20100517") function file:copy($source as xs:string, $destination as xs:string) as xs:boolean? external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:copy($source as xs:string, $destination as xs:string, $overwrite as xs:boolean) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") function file:create-dir($dir as xs:string) as empty-sequence() external;
declare %a:since("basex", "7.0") %a:until("basex", "7.3", "file:create-dir#1") function file:create-directory($dir as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-dir($prefix as xs:string, $suffix as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-dir($prefix as xs:string, $suffix as xs:string, $dir as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-file($prefix as xs:string, $suffix as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-file($prefix as xs:string, $suffix as xs:string, $dir as xs:string) as xs:string external;
declare %a:until("expath-file", "1.0-20150220") function file:current-dir() as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:delete($path as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.2.1") function file:delete($path as xs:string, $recursive as xs:boolean) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") %a:until("expath-file", "1.0-20131203", "file:parent#1") %a:until("basex", "7.8", "file:parent#1") function file:dir-name($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:dir-separator() as xs:string external;
declare %a:since("expath-file", "1.0-20100517") function file:exists($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:files($path as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:files($path as xs:string, $pattern as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:files($path as xs:string, $pattern as xs:string, $recursive as xs:boolean) as xs:string* external;
declare %a:since("basex", "8.2") function file:is-absolute($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") function file:is-dir($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:since("basex", "7.0") %a:until("expath-file", "1.0-20120614", "file:is-dir#1") %a:until("basex", "7.3", "file:is-dir#1") function file:is-directory($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") function file:is-file($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") function file:last-modified($path as xs:string) as xs:dateTime external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.3") function file:line-separator() as xs:string external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:list($dir as xs:string) as xs:string* external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:list($dir as xs:string, $recursive as xs:boolean) as xs:string* external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:list($dir as xs:string, $recursive as xs:boolean, $pattern as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdir($dir as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdir($dir as xs:string, $create as xs:boolean) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdirs($dir as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdirs($dir as xs:string, $create as xs:boolean) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:move($source as xs:string, $target as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20131203") function file:name($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") function file:parent($path as xs:string) as xs:string? external;
declare %a:since("expath-file", "1.0-20100517") function file:path-separator() as xs:string external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614", "file:path-to-native#1") function file:path-to-full-path($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:path-to-native($path as xs:string) as xs:string external;
declare %a:restrict-since("return", "expath-file", "1.0-20120614", "xs:anyURI")
        %a:restrict-since("return", "basex", "7.0", "xs:string")
        %a:since("expath-file", "1.0-20100517") function file:path-to-uri($path as xs:string) as (xs:anyURI|xs:string) external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614", "file:read-binary#1") function file:read($file as xs:string) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-binary($path as xs:string) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20131203") function file:read-binary($path as xs:string, $offset as xs:integer) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20131203") function file:read-binary($path as xs:string, $offset as xs:integer, $length as xs:integer) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") function file:read-html($file as xs:string, $tidyOptions as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20100517") function file:read-text($file as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-text($path as xs:string, $encoding as xs:string) as xs:string external;
declare %a:since("basex", "8.5") function file:read-text($path as xs:string, $encoding as xs:string, $fallback as xs:boolean) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-text-lines($path as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-text-lines($path as xs:string, $encoding as xs:string) as xs:string* external;
declare %a:since("basex", "8.5") function file:read-text-lines($path as xs:string, $encoding as xs:string, $fallback as xs:boolean) as xs:string* external;
declare %a:since("basex", "9.0") function file:read-text-lines($path as xs:string, $encoding as xs:string, $fallback as xs:boolean, $offset as xs:integer) as xs:string* external;
declare %a:since("basex", "9.0") function file:read-text-lines($path as xs:string, $encoding as xs:string, $fallback as xs:boolean, $offset as xs:integer, $length as xs:integer) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:read-xml($file as xs:string) as node() external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:read-xml($file as xs:string, $tidy as xs:boolean) as node() external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:remove($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "8.2") function file:resolve-path($path as xs:string) as xs:string external;
declare %a:since("basex", "8.2") function file:resolve-path($path as xs:string, $base as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:size($file as xs:string) as xs:integer external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:temp-dir() as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:write($path as xs:string, $items as item()*) as empty-sequence() external;
declare %a:restrict-since("$params", "expath-file", "1.0-20120614", "node()*")
        %a:restrict-since("$params", "basex", "7.0", "item()")
        %a:restrict-since("return", "expath-file", "1.0-20120614", "xs:boolean")
        %a:restrict-since("return", "basex", "7.0", "empty-sequence()")
        %a:since("expath-file", "1.0-20100517") function file:write($file as xs:string, $content as item()*, $params as item()*) as xs:boolean? external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:write($file as xs:string, $content as item()*, $params as node()*, $append as xs:boolean) as xs:boolean external;
declare %a:restrict-since("$value", "expath-file", "1.0-20120614", "xs:base64Binary")
        %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:write-binary($path as xs:string, $value as (xs:base64Binary|xs:hexBinary)) as empty-sequence() external;
declare %a:restrict-since("$value", "expath-file", "1.0-20120614", "xs:base64Binary")
        %a:since("expath-file", "1.0-20131203") function file:write-binary($path as xs:string, $value as (xs:base64Binary|xs:hexBinary), $offset as xs:integer) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text($path as xs:string, $value as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text($path as xs:string, $value as xs:string, $encoding as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text-lines($path as xs:string, $values as xs:string*) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text-lines($path as xs:string, $values as xs:string*, $encoding as xs:string) as empty-sequence() external;

xquery version "3.0";
(:~
 : File Module 1.0 (EXPath Module 20 February 2015)
 :
 : @see http://expath.org/spec/file/1.0
 : @see http://docs.basex.org/wiki/File_Module
 :)
module namespace file = "http://expath.org/ns/file";

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

declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";

declare option o:requires "expath-file/1.0-20100517";

declare option o:implements-module "basex/7.0 as expath-file/1.0-20100517"; (: NOTE: 7.0 is the earliest version definitions are available for. :)
declare option o:implements-module "basex/7.3 as expath-file/1.0-20120614";
declare option o:implements-module "basex/7.8 as expath-file/1.0-20131203";
declare option o:implements-module "basex/8.0 as expath-file/1.0-20150220";

declare option o:implements-module "saxon/pe/9.5 as expath-file/1.0-20150220"; (: TODO: Map saxon versions to the correct file versions. :)
declare option o:implements-module "saxon/ee/9.5 as expath-file/1.0-20150220"; (: TODO: Map saxon versions to the correct file versions. :)

declare %a:since("basex", "7.0") %a:until("basex", "7.3", "file:dir-separator#0") variable $file:directory-separator external;
declare %a:since("basex", "7.0") %a:until("basex", "7.3", "file:path-separator#0") variable $file:path-separator external;

declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:append($path as xs:string, $items as item()*) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:append($path as xs:string, $items as item()*, $params (: as [expath-file/1.0-20120614]element(output:serialization-parameters) [basex/7.0]item() :)) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:append-binary($path as xs:string, $value (: as [expath-file/1.0-20120614]xs:base64Binary [basex/7.6](xs:base64Binary|xs:hexBinary) :)) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text($path as xs:string, $value as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text($path as xs:string, $value as xs:string, $encoding as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text-lines($path as xs:string, $values as xs:string*) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:append-text-lines($path as xs:string, $values as xs:string*, $encoding as xs:string) as empty-sequence() external;
declare %a:until("expath-file", "1.0-20150220") function file:base-dir() as xs:string? external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") %a:since("expath-file", "1.0-20131203", "file:name#1") %a:until("basex", "7.8", "file:name#1") function file:base-name($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") %a:since("expath-file", "1.0-20131203") %a:until("basex", "7.8") function file:base-name($path as xs:string, $suffix as xs:string) as xs:string external;
declare %a:until("expath-file", "1.0-20150220") function file:children($dir as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") function file:copy($source as xs:string, $destination as xs:string) (: as [expath-file/1.0-20120614]xs:boolean [basex/7.0]empty-sequence() :) external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:copy($source as xs:string, $destination as xs:string, $overwrite as xs:boolean) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") function file:create-dir($dir as xs:string) as empty-sequence() external;
declare %a:since("basex", "7.0") %a:until("basex", "7.3", "file:create-dir#1") function file:create-directory($dir as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-dir($prefix as xs:string, $suffix as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-dir($prefix as xs:string, $suffix as xs:string, $dir as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-file($prefix as xs:string, $suffix as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:create-temp-file($prefix as xs:string, $suffix as xs:string, $dir as xs:string) as xs:string external;
declare %a:until("expath-file", "1.0-20150220") function file:current-dir() as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:delete($path as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.2.1") function file:delete($path as xs:string, $recursive as xs:boolean) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") %a:until("expath-file", "1.0-20131203", "file:parent#1") %a:until("basex", "7.8", "file:parent#1") function file:dir-name($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:dir-separator() as xs:string external;
declare %a:since("expath-file", "1.0-20100517") function file:exists($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:files($path as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:files($path as xs:string, $pattern as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:files($path as xs:string, $pattern as xs:string, $recursive as xs:boolean) as xs:string* external;
declare %a:since("basex", "8.2") function file:is-absolute($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") function file:is-dir($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:since("basex", "7.0") %a:until("expath-file", "1.0-20120614", "file:is-dir#1") %a:until("basex", "7.3", "file:is-dir#1") function file:is-directory($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") function file:is-file($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") function file:last-modified($path as xs:string) as xs:dateTime external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.3") function file:line-separator() as xs:string external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:list($dir as xs:string) as xs:string* external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:list($dir as xs:string, $recursive as xs:boolean) as xs:string* external;
declare %a:until("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:list($dir as xs:string, $recursive as xs:boolean, $pattern as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdir($dir as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdir($dir as xs:string, $create as xs:boolean) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdirs($dir as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:mkdirs($dir as xs:string, $create as xs:boolean) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:move($source as xs:string, $target as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20131203") function file:name($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20131203") function file:parent($path as xs:string) as xs:string? external;
declare %a:since("expath-file", "1.0-20100517") function file:path-separator() as xs:string external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614", "file:path-to-native#1") function file:path-to-full-path($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:path-to-native($path as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20100517") function file:path-to-uri($path as xs:string) (: as [expath-file/1.0-20120614]xs:anyURI [basex/7.0]xs:string :) external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614", "file:read-binary#1") function file:read($file as xs:string) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-binary($path as xs:string) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20131203") function file:read-binary($path as xs:string, $offset as xs:integer) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20131203") function file:read-binary($path as xs:string, $offset as xs:integer, $length as xs:integer) as xs:base64Binary external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") function file:read-html($file as xs:string, $tidyOptions as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20100517") function file:read-text($file as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-text($path as xs:string, $encoding as xs:string) as xs:string external;
declare %a:since("basex", "8.5") function file:read-text($path as xs:string, $encoding as xs:string, $fallback as xs:boolean) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-text-lines($path as xs:string) as xs:string* external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:read-text-lines($path as xs:string, $encoding as xs:string) as xs:string* external;
declare %a:since("basex", "8.5") function file:read-text-lines($path as xs:string, $encoding as xs:string, $fallback as xs:boolean) as xs:string* external;
declare %a:since("basex", "9.0") function file:read-text-lines($path as xs:string, $encoding as xs:string, $fallback as xs:boolean, $offset as xs:integer) as xs:string* external;
declare %a:since("basex", "9.0") function file:read-text-lines($path as xs:string, $encoding as xs:string, $fallback as xs:boolean, $offset as xs:integer, $length as xs:integer) as xs:string* external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:read-xml($file as xs:string) as node() external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:read-xml($file as xs:string, $tidy as xs:boolean) as node() external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:remove($path as xs:string) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "8.2") function file:resolve-path($path as xs:string) as xs:string external;
declare %a:since("basex", "8.2") function file:resolve-path($path as xs:string, $base as xs:string) as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:size($file as xs:string) as xs:integer external;
declare %a:since("expath-file", "1.0-20131203") %a:since("basex", "7.7") function file:temp-dir() as xs:string external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:write($path as xs:string, $items as item()*) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20100517") function file:write($file as xs:string, $content as item()*, $params (: as [expath-file/1.0-20120614]node()* [basex/7.0]item() :)) (: as [expath-file/1.0-20120614]xs:boolean [basex/7.0]empty-sequence() :) external;
declare %a:since("expath-file", "1.0-20100517") %a:until("expath-file", "1.0-20120614") %a:missing("basex", "7.0") function file:write($file as xs:string, $content as item()*, $params as node()*, $append as xs:boolean) as xs:boolean external;
declare %a:since("expath-file", "1.0-20120614") %a:since("basex", "7.0") function file:write-binary($path as xs:string, $value (: as [expath-file/1.0-20120614]xs:base64Binary [basex/7.0](xs:base64Binary|xs:hexBinary) :)) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20131203") function file:write-binary($path as xs:string, $value (: as [expath-file/1.0-20120614]xs:base64Binary [basex/7.0](xs:base64Binary|xs:hexBinary) :), $offset as xs:integer) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text($path as xs:string, $value as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text($path as xs:string, $value as xs:string, $encoding as xs:string) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text-lines($path as xs:string, $values as xs:string*) as empty-sequence() external;
declare %a:since("expath-file", "1.0-20120614") function file:write-text-lines($path as xs:string, $values as xs:string*, $encoding as xs:string) as empty-sequence() external;


removed debugging info

removed debugging info

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

import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
import module namespace csd_mcs = "https://github.com/openhie/openinfoman/csd_mcs";

declare   namespace   csd = "urn:ihe:iti:csd:2013";
declare namespace xquery = "http://basex.org/modules/xquery";

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

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

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

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

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

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

};

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

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

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

};

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

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

declare function csr_proc:process_CSR($db,$careServicesRequest, $doc_name,$base_url)
{
csr_proc:process_CSR($db,$careServicesRequest, $doc_name, $base_url,map:merge(()))
};

declare function csr_proc:process_CSR($db,$careServicesRequest, $doc_name,$base_url,$bindings as map(*))
{
let $function :=$careServicesRequest/csd:function
let $adhoc :=$careServicesRequest/csd:expression
return if (exists($function))
then
  let $urn := string($function/@urn)
  return
    if ($urn = 'urn:ihe:iti:csd:2014:adhoc')
    then
      csr_proc:process_CSR_adhoc($db,$careServicesRequest/csd:function,$doc_name,$bindings)
    else
      let $csr :=
      <csd:careServicesRequest>
	<csd:function urn="{$urn}" resource='{$doc_name}' base_url='{$base_url}'>
	  {($function/*)[1]}
	</csd:function>
      </csd:careServicesRequest>
      return csr_proc:process_CSR_stored($db,$csr,$bindings)
else if (exists($adhoc))
then
  csr_proc:process_CSR_adhoc($db,$adhoc,$doc_name,$bindings)
else
 (:
  <rest:response>
    <http:response status="400" message="Invalid care services request.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>
  :)
  $careServicesRequest

};

declare function csr_proc:process_CSR_adhoc($db,$expression,$doc_name) {
   csr_proc:process_CSR_adhoc($expression,$doc_name,map:merge(()))
};

declare function csr_proc:process_CSR_adhoc($db,$expression,$doc_name,$bindings as map(*))
{

let $doc :=  csd_dm:open_document($db,$doc_name)

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

declare function csr_proc:process_CSR_stored($db,$careServicesRequest)
{
  csr_proc:process_CSR_stored($db,$careServicesRequest,map:merge(()))
};

declare function csr_proc:process_CSR_stored($db,$careServicesRequest,$bindings as map(*))
{
let $function :=$careServicesRequest/csd:function
let $stored_functions := csr_proc:stored_functions($db)
let $urn := string($function/@urn)
let $definition := ($stored_functions[@urn = $urn])[1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@urn)
let $doc_name := string($function/@resource)
let $doc :=  csd_dm:open_document($db,$doc_name)

let $results0 := csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest,$bindings)

return if (exists($definition)) then
  let $results1:=
    if ($function/@encapsulated) then
      csr_proc:wrap_result($results0,$content_type)
    else $results0
  return
    (
    <rest:response>
      <http:response status="200" >
	<http:header name="Content-Type" value="{$content_type}"/>
      </http:response>
    </rest:response>
    ,
    $results1
    )
else
  <rest:response>
    <http:response status="404" message="No registered function with URN='{$function/@urn}'.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>
};

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

declare function csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest)
{
  csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest,map:merge(()))
};

declare function csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest,$bindings as map(*))
{
let $function :=
  if (exists($careServicesRequest/csd:function ))
  then $careServicesRequest/csd:function
  else $careServicesRequest

let $urn :=
  if (exists($careServicesRequest/csd:function ))
  then string($function/@urn)
  else string($function/@function)

let $doc_name :=  string($function/@resource)
let $base_url :=  string($function/@base_url)

let $stored_functions := csr_proc:stored_functions($db)
let $definition := ($stored_functions[@urn = $urn])[1]/csd:definition/text()

let $options := csr_proc:lookup_stored_options($db,$function/@urn)

let $requestParams :=
 <csd:requestParams resource='{$doc_name}' function='{$urn}' base_url='{$base_url}'>
   {
     if ($function/csd:requestParams) then $function/csd:requestParams/*
   else $function/requestParams/*
   }
 </csd:requestParams>

let $csr_bindings :=  map{'':$doc,'careServicesRequest':$requestParams}
let $all_bindings :=  map:merge(($csr_bindings, $bindings))

return if (exists($definition)) then
  xquery:eval($definition,$all_bindings,$options)
else ()
};

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

};

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

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

declare function csr_proc:get_any_function_definition($db,$urn) {
  (csr_proc:stored_updating_functions($db)[@urn = $urn] , csr_proc:stored_functions($db)[@urn = $urn])[1]
};

declare updating function csr_proc:process_updating_CSR($db,$function,$doc_name,$base_url)
{
  csr_proc:process_updating_CSR($db,$function,$doc_name,$base_url,map:merge(()))
};

declare updating function csr_proc:process_updating_CSR_results($db,$function)
{
  csr_proc:process_updating_CSR_results($db,$function,map:merge(()))
};

declare updating function csr_proc:process_updating_CSR_results($db,$careServicesRequest, $bindings as map(*)) {
let $function :=$careServicesRequest//csd:function
let $doc_name := string($function/@resource)
let $base_url := string($function/@base_url)
let $doc := csd_dm:open_document($db,$doc_name)
let $urn := $function/@urn
let $stored_updating_functions := csr_proc:stored_updating_functions($db)
let $definition := $stored_updating_functions[@urn = $urn][1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@urn)
let $requestParams := <csd:requestParams resource='{$doc_name}' function='{$urn}' base_url='{$base_url}'>
  {
    if ($function/csd:requestParams) then $function/csd:requestParams/*
    else $function/requestParams/*
  }
</csd:requestParams>

let $csr_bindings :=  map{'':$doc,'careServicesRequest':$requestParams}
let $all_bindings :=  map:merge(($csr_bindings, $bindings))

let $options := csr_proc:lookup_stored_options($db,$function/@urn)

return if (exists($definition)) then
  (
  xquery:update($definition,$all_bindings,$options)
  )
else  ()

};

declare updating function csr_proc:process_updating_CSR_stored_results($db,$doc,$careServicesRequest)
{
  csr_proc:process_updating_CSR_stored_results($db,$doc,$careServicesRequest,map:merge(()))
};

declare updating function csr_proc:process_updating_CSR_stored_results($db,$doc,$careServicesRequest,$bindings as map(*))
{
let $stored_updating_functions := csr_proc:stored_updating_functions($db)

let $csr_bindings :=  map{'':$doc,'careServicesRequest':$careServicesRequest}
let $all_bindings :=  map:merge(($csr_bindings, $bindings))
let $function := $careServicesRequest/@function
let $options := csr_proc:lookup_stored_options($db,$function)
let $definition := $stored_updating_functions[@urn = $function][1]/csd:definition/text()

return if (exists($definition)) then
  xquery:update($definition,$all_bindings,$options)
else
    db:output(
    <rest:response>
      <http:response status="404" message="No registered updating function with URN='{$function}">
	<http:header name="Content-Language" value="en"/>
	<http:header name="Content-Type" value="text/html; charset=utf-8"/>
      </http:response>
    </rest:response>
       )
};

declare updating function csr_proc:process_updating_CSR($db,$careServicesRequest, $doc_name, $base_url,$bindings as map(*))
{

let $doc := csd_dm:open_document($db,$doc_name)
let $function :=$careServicesRequest//csd:function
let $urn := $function/@urn
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@urn)
let $requestParams := <csd:requestParams resource="{$doc_name}" function="{$urn}" base_url="{$base_url}">
  {
    if ($function/csd:requestParams) then $function/csd:requestParams/*
    else $function/requestParams/*
  }
</csd:requestParams>
return csr_proc:process_updating_CSR_stored_results($db,$doc,$requestParams,$bindings)
};

declare function csr_proc:lookup_stored_options($db,$urn)
{
  let $stored_updating_functions := csr_proc:stored_updating_functions($db)
  let $stored_functions := csr_proc:stored_functions($db)
  let $func := ($stored_functions[@urn = $urn], $stored_updating_functions[@urn = $urn])[1]
   return  ($func/csd:extension[@urn='urn:openhie.org:openinfoman:csr_processor' and  @type='xquery:options']/xquery:options)[1]
     (: See: http://docs.basex.org/wiki/XQuery_Module :)
};

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

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

import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
import module namespace csd_mcs = "https://github.com/openhie/openinfoman/csd_mcs";

declare   namespace   csd = "urn:ihe:iti:csd:2013";
declare namespace xquery = "http://basex.org/modules/xquery";

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

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

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

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

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

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

};

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

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

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

};

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

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

declare function csr_proc:process_CSR($db,$careServicesRequest, $doc_name,$base_url)
{
csr_proc:process_CSR($db,$careServicesRequest, $doc_name, $base_url,map:merge(()))
};

declare function csr_proc:process_CSR($db,$careServicesRequest, $doc_name,$base_url,$bindings as map(*))
{
let $function :=$careServicesRequest/csd:function
let $adhoc :=$careServicesRequest/csd:expression
return if (exists($function))
then
  let $urn := string($function/@urn)
  return
    if ($urn = 'urn:ihe:iti:csd:2014:adhoc')
    then
      <m>{csr_proc:process_CSR_adhoc($db,$careServicesRequest/csd:function,$doc_name,$bindings) }</m>
    else
      let $csr :=
      <csd:careServicesRequest>
	<csd:function urn="{$urn}" resource='{$doc_name}' base_url='{$base_url}'>
	  {($function/*)[1]}
	</csd:function>
      </csd:careServicesRequest>
      return csr_proc:process_CSR_stored($db,$csr,$bindings)
else if (exists($adhoc))
then
  csr_proc:process_CSR_adhoc($db,$adhoc,$doc_name,$bindings)
else
 (:
  <rest:response>
    <http:response status="400" message="Invalid care services request.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>
  :)
  $careServicesRequest

};

declare function csr_proc:process_CSR_adhoc($db,$expression,$doc_name) {
   csr_proc:process_CSR_adhoc($expression,$doc_name,map:merge(()))
};

declare function csr_proc:process_CSR_adhoc($db,$expression,$doc_name,$bindings as map(*))
{

let $doc :=  csd_dm:open_document($db,$doc_name)

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

declare function csr_proc:process_CSR_stored($db,$careServicesRequest)
{
  csr_proc:process_CSR_stored($db,$careServicesRequest,map:merge(()))
};

declare function csr_proc:process_CSR_stored($db,$careServicesRequest,$bindings as map(*))
{
let $function :=$careServicesRequest/csd:function
let $stored_functions := csr_proc:stored_functions($db)
let $urn := string($function/@urn)
let $definition := ($stored_functions[@urn = $urn])[1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@urn)
let $doc_name := string($function/@resource)
let $doc :=  csd_dm:open_document($db,$doc_name)

let $results0 := csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest,$bindings)

return if (exists($definition)) then
  let $results1:=
    if ($function/@encapsulated) then
      csr_proc:wrap_result($results0,$content_type)
    else $results0
  return
    (
    <rest:response>
      <http:response status="200" >
	<http:header name="Content-Type" value="{$content_type}"/>
      </http:response>
    </rest:response>
    ,
    $results1
    )
else
  <rest:response>
    <http:response status="404" message="No registered function with URN='{$function/@urn}'.">
      <http:header name="Content-Language" value="en"/>
      <http:header name="Content-Type" value="text/html; charset=utf-8"/>
    </http:response>
  </rest:response>
};

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

declare function csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest)
{
  csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest,map:merge(()))
};

declare function csr_proc:process_CSR_stored_results($db,$doc,$careServicesRequest,$bindings as map(*))
{
let $function :=
  if (exists($careServicesRequest/csd:function ))
  then $careServicesRequest/csd:function
  else $careServicesRequest

let $urn :=
  if (exists($careServicesRequest/csd:function ))
  then string($function/@urn)
  else string($function/@function)

let $doc_name :=  string($function/@resource)
let $base_url :=  string($function/@base_url)

let $stored_functions := csr_proc:stored_functions($db)
let $definition := ($stored_functions[@urn = $urn])[1]/csd:definition/text()

let $options := csr_proc:lookup_stored_options($db,$function/@urn)

let $requestParams :=
 <csd:requestParams resource='{$doc_name}' function='{$urn}' base_url='{$base_url}'>
   {
     if ($function/csd:requestParams) then $function/csd:requestParams/*
   else $function/requestParams/*
   }
 </csd:requestParams>

let $csr_bindings :=  map{'':$doc,'careServicesRequest':$requestParams}
let $all_bindings :=  map:merge(($csr_bindings, $bindings))

return if (exists($definition)) then
  xquery:eval($definition,$all_bindings,$options)
else ()
};

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

};

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

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

declare function csr_proc:get_any_function_definition($db,$urn) {
  (csr_proc:stored_updating_functions($db)[@urn = $urn] , csr_proc:stored_functions($db)[@urn = $urn])[1]
};

declare updating function csr_proc:process_updating_CSR($db,$function,$doc_name,$base_url)
{
  csr_proc:process_updating_CSR($db,$function,$doc_name,$base_url,map:merge(()))
};

declare updating function csr_proc:process_updating_CSR_results($db,$function)
{
  csr_proc:process_updating_CSR_results($db,$function,map:merge(()))
};

declare updating function csr_proc:process_updating_CSR_results($db,$careServicesRequest, $bindings as map(*)) {
let $function :=$careServicesRequest//csd:function
let $doc_name := string($function/@resource)
let $base_url := string($function/@base_url)
let $doc := csd_dm:open_document($db,$doc_name)
let $urn := $function/@urn
let $stored_updating_functions := csr_proc:stored_updating_functions($db)
let $definition := $stored_updating_functions[@urn = $urn][1]/csd:definition/text()
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@urn)
let $requestParams := <csd:requestParams resource='{$doc_name}' function='{$urn}' base_url='{$base_url}'>
  {
    if ($function/csd:requestParams) then $function/csd:requestParams/*
    else $function/requestParams/*
  }
</csd:requestParams>

let $csr_bindings :=  map{'':$doc,'careServicesRequest':$requestParams}
let $all_bindings :=  map:merge(($csr_bindings, $bindings))

let $options := csr_proc:lookup_stored_options($db,$function/@urn)

return if (exists($definition)) then
  (
  xquery:update($definition,$all_bindings,$options)
  )
else  ()

};

declare updating function csr_proc:process_updating_CSR_stored_results($db,$doc,$careServicesRequest)
{
  csr_proc:process_updating_CSR_stored_results($db,$doc,$careServicesRequest,map:merge(()))
};

declare updating function csr_proc:process_updating_CSR_stored_results($db,$doc,$careServicesRequest,$bindings as map(*))
{
let $stored_updating_functions := csr_proc:stored_updating_functions($db)

let $csr_bindings :=  map{'':$doc,'careServicesRequest':$careServicesRequest}
let $all_bindings :=  map:merge(($csr_bindings, $bindings))
let $function := $careServicesRequest/@function
let $options := csr_proc:lookup_stored_options($db,$function)
let $definition := $stored_updating_functions[@urn = $function][1]/csd:definition/text()

return if (exists($definition)) then
  xquery:update($definition,$all_bindings,$options)
else
    db:output(
    <rest:response>
      <http:response status="404" message="No registered updating function with URN='{$function}">
	<http:header name="Content-Language" value="en"/>
	<http:header name="Content-Type" value="text/html; charset=utf-8"/>
      </http:response>
    </rest:response>
       )
};

declare updating function csr_proc:process_updating_CSR($db,$careServicesRequest, $doc_name, $base_url,$bindings as map(*))
{

let $doc := csd_dm:open_document($db,$doc_name)
let $function :=$careServicesRequest//csd:function
let $urn := $function/@urn
let $content_type := csr_proc:lookup_stored_content_type($db,$function/@urn)
let $requestParams := <csd:requestParams resource="{$doc_name}" function="{$urn}" base_url="{$base_url}">
  {
    if ($function/csd:requestParams) then $function/csd:requestParams/*
    else $function/requestParams/*
  }
</csd:requestParams>
return csr_proc:process_updating_CSR_stored_results($db,$doc,$requestParams,$bindings)
};

declare function csr_proc:lookup_stored_options($db,$urn)
{
  let $stored_updating_functions := csr_proc:stored_updating_functions($db)
  let $stored_functions := csr_proc:stored_functions($db)
  let $func := ($stored_functions[@urn = $urn], $stored_updating_functions[@urn = $urn])[1]
   return  ($func/csd:extension[@urn='urn:openhie.org:openinfoman:csr_processor' and  @type='xquery:options']/xquery:options)[1]
     (: See: http://docs.basex.org/wiki/XQuery_Module :)
};

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


[apidoc] Support generating the MarkLogic 10.0 apidoc function signatures.

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

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

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

declare variable $language as xs:string := "xquery"; (: xquery | javascript | rest :)

declare function local:apidoc-language($doc as element()) as xs:string {
    ($doc/@class, "xquery")[1]
};

(: namespaces -------------------------------------------------------------- :)

declare variable $module-namespaces := map:merge(
    for $import in $apidoc//apidoc:module/apidoc:summary//(*:p|*:pre)
    let $match := fn:analyze-string(($import/*:code, $import)[1], "import module (namespace\s*([a-z0-9]+)\s*=\s*)?""([^""]+)""")/fn:match[1]
    let $prefix := $import/ancestor::apidoc:module/@lib
    let $namespace := $match/fn:group[@nr = "3"]/string()
    where exists($namespace)
    return map:entry($prefix, $namespace)
);

declare variable $xquery-namespaces := map {
    "local": "http://www.w3.org/2005/xquery-local-functions",
    "fn": "http://www.w3.org/2005/xpath-functions",
    "xml": "http://www.w3.org/XML/1998/namespace",
    "xs": "http://www.w3.org/2001/XMLSchema",
    "xsi": "http://www.w3.org/2001/XMLSchema-instance"
};

declare variable $marklogic-5-builtin := map {
    "cts": "http://marklogic.com/cts",
    "dbg": "http://marklogic.com/xdmp/dbg",
    "dir": "http://marklogic.com/xdmp/directory",
    "error": "http://marklogic.com/xdmp/error",
    "map": "http://marklogic.com/xdmp/map",
    "math": "http://marklogic.com/xdmp/math",
    "prof": "http://marklogic.com/xdmp/profile",
    "sec": "http://marklogic.com/security",
    "spell": "http://marklogic.com/xdmp/spell",
    "xdmp": "http://marklogic.com/xdmp"
};

declare variable $marklogic-6-builtin := map {
    "err": "http://www.w3.org/2005/xqt-error"
};

declare variable $marklogic-7-builtin := map {
    "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
    "sc": "http://marklogic.com/xdmp/schema-components",
    "sql": "http://marklogic.com/xdmp/sql"
};

declare variable $marklogic-10-builtin := map {
    "cntk": "http://marklogic.com/cntk",
    "ort": "http://marklogic.com/onnxruntime"
};

declare variable $marklogic-additional := map {
    "as": "http://marklogic.com/xdmp/assignments",
    "db": "http://marklogic.com/xdmp/database",
    "dg": "xdmp:document-get",
    "cl": "http://marklogic.com/xdmp/clusters",
    "exsl": "http://exslt.org/common",
    "extusr": "http://marklogic.com/xdmp/external/user",
    "gr": "http://marklogic.com/xdmp/group",
    "lang": "http://marklogic.com/xdmp/language",
    "list": "http://marklogic.com/manage/package/list",
    "md": "urn:oasis:names:tc:SAML:2.0:metadata",
    "mt": "http://marklogic.com/xdmp/mimetypes",
    "pkg": "http://marklogic.com/manage/package",
    "s": "http://www.w3.org/2009/xpath-functions/analyze-string",
    "sch": "http://purl.oclc.org/dsdl/schematron",
    "x509": "http://marklogic.com/xdmp/x509",
    "xdt": "http://www.w3.org/2004/10/xpath-datatypes",
    "xi": "http://www.w3.org/2001/XInclude"
};

declare variable $namespaces := map:merge((
    $module-namespaces,
    $xquery-namespaces,
    $marklogic-5-builtin,
    $marklogic-6-builtin,
    $marklogic-7-builtin,
    $marklogic-10-builtin,
    $marklogic-additional,
    ()
));

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

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

declare variable $functions as element(apidoc:function)* := $apidoc//apidoc:function[
    local:function-language(.) = $language and not(@name = "")
];

declare function local:function-language($function as element(apidoc:function)) as xs:string {
    let $bucket := $function/@bucket
    return switch ($bucket)
    case "REST Resources API" return "rest"
    default return local:apidoc-language($function)
};

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

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

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

declare function local:function-parameter-type($function as element(apidoc:function), $name as xs:string) as xs:string {
    let $type := local:function-parameter($function, $name)/@type/string()
    return switch ($type)
    case "atomic type" return "item()*"
    case "xs:string)" return "xs:string"
    case "xs:dateTime? | xs:date? | xs:string?" return "(xs:dateTime?|xs:date?|xs:string?)"
    case "binary())" return "binary()"
    case "element(sec:query-rolesets)*)" return "element(sec:query-rolesets)*"
    case "function()" return "function(*)"
    case "item()* | map:map" return "(item()*|map:map)"
    case "(binary()|node())*" return "(binary()*|node()*)"
    case "(binary()|node())?" return "(binary()?|node()?)"
    case "(element()|map:map)?" case "element()?|map:map?" return "(element()?|map:map?)"
    case "(node()? | map:map)?" return "(node()?|map:map?)"
    case "(cts:order|xs:string)*" return "(cts:order*|xs:string*)"
    case "[xs:string?]" return "xs:string?"
    default return replace($type, ",\.\.\.", "...")
};

declare function local:function-return-type($function as element(apidoc:function)) as xs:string {
    let $type := normalize-space($function/apidoc:return[local:apidoc-language(.) = $language])
    return switch ($type)
    case "An XML representation of the certificate." return "element()?"
    case "element())" case "Element()" return "element()"
    case "element()|map:map" return "(element()|map:map)"
    case "element(xsd:schema)*" return "element(xs:schema)*"
    case "empty-sequence" case "empty sequence" return "empty-sequence()"
    case "xs:string)" return "xs:string"
    case "xs:unsignedLong)" return "xs:unsignedLong"
    case "pkgins:install($pkgname)" return "element(pkg:install-status)"
    case "pkgins:revert($ticket)" return "element(pkg:revert-status)"
    case "query-results-serialize($results)" return "item()*"
    case "The return data type is the data type of the date argument" return "item()"
    case "" return
        switch (local:function-name($function))
        case "admin:appserver-set-output-indent-tabs" return "element(configuration)"
        default return ""
    default return $type
};

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

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

for $prefix in map:keys($namespaces)
let $namespace := map:get($namespaces, $prefix)
order by $prefix
return ``[declare namespace `{$prefix}` = "`{$namespace}`";]``
,
for $function in $functions
order by $function/@lib, $function/@name
return local:function-xquery-signatures($function)

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

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

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

declare variable $language as xs:string := "xquery"; (: xquery | javascript | rest :)

declare function local:apidoc-language($doc as element()) as xs:string {
    ($doc/@class, "xquery")[1]
};

(: namespaces -------------------------------------------------------------- :)

declare variable $module-namespaces := map:merge(
    for $import in $apidoc//apidoc:module/apidoc:summary//(*:p|*:pre)
    let $match := fn:analyze-string(($import/*:code, $import)[1], "import module (namespace\s*([a-z0-9]+)\s*=\s*)?""([^""]+)""")/fn:match[1]
    let $prefix := $import/ancestor::apidoc:module/@lib
    let $namespace := $match/fn:group[@nr = "3"]/string()
    where exists($namespace)
    return map:entry($prefix, $namespace)
);

declare variable $xquery-namespaces := map {
    "local": "http://www.w3.org/2005/xquery-local-functions",
    "fn": "http://www.w3.org/2005/xpath-functions",
    "xml": "http://www.w3.org/XML/1998/namespace",
    "xs": "http://www.w3.org/2001/XMLSchema",
    "xsi": "http://www.w3.org/2001/XMLSchema-instance"
};

declare variable $marklogic-5-builtin := map {
    "cts": "http://marklogic.com/cts",
    "dbg": "http://marklogic.com/xdmp/dbg",
    "dir": "http://marklogic.com/xdmp/directory",
    "error": "http://marklogic.com/xdmp/error",
    "map": "http://marklogic.com/xdmp/map",
    "math": "http://marklogic.com/xdmp/math",
    "prof": "http://marklogic.com/xdmp/profile",
    "sec": "http://marklogic.com/security",
    "spell": "http://marklogic.com/xdmp/spell",
    "xdmp": "http://marklogic.com/xdmp"
};

declare variable $marklogic-6-builtin := map {
    "err": "http://www.w3.org/2005/xqt-error"
};

declare variable $marklogic-7-builtin := map {
    "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
    "sc": "http://marklogic.com/xdmp/schema-components",
    "sql": "http://marklogic.com/xdmp/sql"
};

declare variable $marklogic-additional := map {
    "as": "http://marklogic.com/xdmp/assignments",
    "db": "http://marklogic.com/xdmp/database",
    "dg": "xdmp:document-get",
    "cl": "http://marklogic.com/xdmp/clusters",
    "exsl": "http://exslt.org/common",
    "extusr": "http://marklogic.com/xdmp/external/user",
    "gr": "http://marklogic.com/xdmp/group",
    "lang": "http://marklogic.com/xdmp/language",
    "list": "http://marklogic.com/manage/package/list",
    "md": "urn:oasis:names:tc:SAML:2.0:metadata",
    "mt": "http://marklogic.com/xdmp/mimetypes",
    "pkg": "http://marklogic.com/manage/package",
    "s": "http://www.w3.org/2009/xpath-functions/analyze-string",
    "sch": "http://purl.oclc.org/dsdl/schematron",
    "x509": "http://marklogic.com/xdmp/x509",
    "xdt": "http://www.w3.org/2004/10/xpath-datatypes",
    "xi": "http://www.w3.org/2001/XInclude"
};

declare variable $namespaces := map:merge((
    $module-namespaces,
    $xquery-namespaces,
    $marklogic-5-builtin,
    $marklogic-6-builtin,
    $marklogic-7-builtin,
    $marklogic-additional,
    ()
));

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

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

declare variable $functions as element(apidoc:function)* := $apidoc//apidoc:function[
    local:function-language(.) = $language and not(@name = "")
];

declare function local:function-language($function as element(apidoc:function)) as xs:string {
    let $bucket := $function/@bucket
    return switch ($bucket)
    case "REST Resources API" return "rest"
    default return local:apidoc-language($function)
};

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

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

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

declare function local:function-parameter-type($function as element(apidoc:function), $name as xs:string) as xs:string {
    let $type := local:function-parameter($function, $name)/@type/string()
    return switch ($type)
    case "atomic type" return "item()*"
    case "xs:string)" return "xs:string"
    case "xs:dateTime? | xs:date? | xs:string?" return "(xs:dateTime?|xs:date?|xs:string?)"
    case "binary())" return "binary()"
    case "element(sec:query-rolesets)*)" return "element(sec:query-rolesets)*"
    case "function()" return "function(*)"
    case "item()* | map:map" return "(item()*|map:map)"
    case "(binary()|node())*" return "(binary()*|node()*)"
    case "(binary()|node())?" return "(binary()?|node()?)"
    case "(element()|map:map)?" case "element()?|map:map?" return "(element()?|map:map?)"
    case "(node()? | map:map)?" return "(node()?|map:map?)"
    case "(cts:order|xs:string)*" return "(cts:order*|xs:string*)"
    case "[xs:string?]" return "xs:string?"
    default return replace($type, ",\.\.\.", "...")
};

declare function local:function-return-type($function as element(apidoc:function)) as xs:string {
    let $type := normalize-space($function/apidoc:return[local:apidoc-language(.) = $language])
    return switch ($type)
    case "An XML representation of the certificate." return "element()?"
    case "element())" case "Element()" return "element()"
    case "element()|map:map" return "(element()|map:map)"
    case "element(xsd:schema)*" return "element(xs:schema)*"
    case "empty-sequence" return "empty-sequence()"
    case "xs:string)" return "xs:string"
    case "xs:unsignedLong)" return "xs:unsignedLong"
    case "pkgins:install($pkgname)" return "element(pkg:install-status)"
    case "pkgins:revert($ticket)" return "element(pkg:revert-status)"
    case "query-results-serialize($results)" return "item()*"
    case "The return data type is the data type of the date argument" return "item()"
    case "" return
        switch (local:function-name($function))
        case "admin:appserver-set-output-indent-tabs" return "element(configuration)"
        default return ""
    default return $type
};

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

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

for $prefix in map:keys($namespaces)
let $namespace := map:get($namespaces, $prefix)
order by $prefix
return ``[declare namespace `{$prefix}` = "`{$namespace}`";]``
,
for $function in $functions
order by $function/@lib, $function/@name
return local:function-xquery-signatures($function)