XQTS: Added "Inspect" comparison method using text compare. Sufficient for many situations

svn path=/trunk/eXist-1.0/; revision=3164

declare namespace xqts="http://exist-db.org/xquery/xqts";
declare namespace catalog="http://www.w3.org/2005/02/query-test-XQTSCatalog";

declare namespace props="java:java.io.File";

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

(:~ ----------------------------------------------------------------------------------------
     W3C XQuery Test Suite

     This is the main module for running the XQTS on eXist. You can either
     run the whole test suite, a specific test group or a single test case.

     Setup:

     * Make sure the XmlDiff module is registered in conf.xml

     * Edit config.xml and change basedir to point to the directory into
     which you unzipped the XQTS sources. Note: it needs to be an URI.

     * Create a collection /db/XQTS in the database.

     * From the XQTS directory, upload XQTSCatalog.xml into the created
     collection.

     * Upload the "TestSources" directory so the source docs can be found
     in /db/XQTS/TestSources.

     * Run this script with the client.
     ------------------------------------------------------------------------------------------- :)
declare variable $xqts:CONFIG { xqts:initialize() };
declare variable $xqts:XQTS_HOME { xqts:path-to-uri($xqts:CONFIG/basedir/text()) };

declare function xqts:initialize() as element() {
    let $home := util:system-property("exist.home")
    let $path := concat($home, "/webapp/xqts")
    let $collection := xdb:create-collection("/db", "XQTS")
    let $config0 := doc("/db/XQTS")/config
    let $config :=
        if ($config0) then
            $config0
        else
            let $stored := xdb:store-files-from-pattern("/db/XQTS", $path, "*.xml", "text/xml")
            return
                doc("/db/XQTS/config.xml")/config
    let $xqtsHome := $config/basedir/text()
    let $catalog0 := doc("/db/XQTS/XQTSCatalog.xml")
    let $catalog :=
        if ($catalog0) then $catalog0
        else
            xdb:store($collection, "XQTSCatalog.xml",
                xs:anyURI(xqts:path-to-uri(concat($xqtsHome, "XQTSCatalog.xml"))))
    let $sources0 := collection("/db/XQTS/TestSources")
    let $sources :=
        if ($sources0) then
            $sources0
        else
            let $sourceCol := xdb:create-collection("/db/XQTS", "TestSources")
            return
                xdb:store-files-from-pattern($sourceCol, concat($xqtsHome, "TestSources"), "*.xml", "text/xml")
    return
        $config
};

declare function xqts:path-to-uri($path as xs:string) as xs:string {
        if (starts-with($path, "/")) then
            concat("file://", $path)
        else
            concat("file:///", $path)
};

declare function xqts:create-progress-file($testCount as xs:int) as empty() {
    let $results := xdb:store("/db/XQTS", "progress.xml",
        <progress total="{$testCount}" done="0" failed="0" passed="0"/>)
    return ()
};

declare function xqts:report-progress($test-case as element()) as empty() {
    let $result := string($test-case/@result)
    let $progress := doc("/db/XQTS/progress.xml")/progress
    let $counter := $progress/@done
    return (
        update value $counter with xs:int($counter + 1),
        if ($result eq 'fail') then
            update value $progress/@failed with xs:int($progress/@failed + 1)
        else
            update value $progress/@passed with xs:int($progress/@passed + 1)
    )
};

declare function xqts:create-collections($group as element(catalog:test-group)) as node() {
    let $rootColl := xdb:create-collection("/db/XQTS", "test-results")
    let $ancestors := reverse(($group/ancestor::catalog:test-group, $group))
    let $collection := xqts:create-collections($rootColl, $ancestors, "/db/XQTS/test-results")
    let $results := xdb:store($collection, "results.xml", <test-result failed="0" passed="0"/>)
    return
        doc($results)
};

declare function xqts:create-collections($parent as object,
    $pathElements as element()+, $currentPath as xs:string)
as object {
    let $next := $pathElements[last()]
    let $remainder := subsequence($pathElements, 1, count($pathElements) - 1)
    let $newColl := xdb:create-collection($parent, $next/@name)
    return
        if ($remainder) then
            xqts:create-collections($newColl, $remainder, concat($currentPath, "/", $next/@name))
        else
            $newColl
};

declare function xqts:get-query($case as element(catalog:test-case)) {
   let $query-name := $case//catalog:query/@name
   let $path := concat( $xqts:XQTS_HOME, "Queries/XQuery/", $case/@FilePath, $query-name, ".xq" )
   let $xq-string := util:file-read($path)
   return $xq-string
};

declare function xqts:print-result($test-name as xs:string, $passed as xs:boolean, $query as xs:string,
    $result as item()*, $expected as item()*, $case as element(catalog:test-case)) as element() {
    <test-case name="{$test-name}" result="{if ($passed) then 'pass' else 'fail'}">
    {
        if (not($passed)) then (
            <result>{$result}</result>,
            if ($expected instance of element() and count($expected/*) > 10) then
            	<expected truncated="">{$expected/*[position() < 10]}</expected>
            else
                <expected>{$expected}</expected>,
            (: element {$case/catalog:input-file/@variable} { xqts:getInputValue($case) }, :)
            <query>{$query}</query>
        ) else ()
    }
    </test-case>
};

declare function xqts:normalize-text($result as item()*) as xs:string {
    let $str := string-join($result, " ")
    return
        (: Remove leading and trailing whitespace :)
        replace(replace($str, "^\s+", ""), "\s+$", "")
};

declare function xqts:check-output($query as xs:string, $result as item()*, $case as element(catalog:test-case)) {
    let $output := $case/catalog:output-file[last()]
    return
        (: Expected an error, but got a result :)
        if (exists($case/catalog:expected-error)) then
            <test-case name="{$case/@name}" result="fail">
                   <expected-error>{$case/catalog:expected-error/text()}</expected-error>
                   <query>{$query}</query>
            </test-case>
        (: Comparison method: "Text" :)
        else if ($output/@compare eq "Text") then
            let $text := util:file-read(concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath,
                "/", $output/text()))
            let $test := xqts:normalize-text($text) eq xqts:normalize-text($result)
            return
                xqts:print-result($case/@name, $test, $query, $result, $text, $case)
        (: Comparison method: "XML" :)
        else if ($output/@compare eq "XML") then
            util:catch("java.lang.Exception",
                let $filePath := concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath,
                    $output/text())
                let $expected := doc(xdb:store("/db", "temp.xml", xs:anyURI($filePath), "text/xml"))
                let $test := xdiff:compare($expected, $result)
                return
                    xqts:print-result($case/@name, $test, $query, $result, $expected, $case),
                (: Handle unexpected exceptions :)
                <test-case name="{$case/@name}" result="fail">
                   <exception>Exception while loading expected result: {$util:exception-message}</exception>
                   <query>{$query}</query>
                </test-case>
            )
        (: Comparison method: "Fragment" :)
        else if ($output/@compare eq "Fragment") then
            util:catch("java.lang.Exception",
                let $filePath := concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath, $output/text())
                let $expectedFrag := util:file-read($filePath)
                let $xmlFrag := concat("<f>", $expectedFrag, "</f>")
                let $log := util:log("DEBUG", ("Frag stored: ", $xmlFrag))
                let $expected := doc(xdb:store("/db", "temp.xml", $xmlFrag, "text/xml"))
                let $test := xdiff:compare($expected, <f>{$result}</f>)
                return
                    xqts:print-result($case/@name, $test, $query, $result, $expected, $case),
                (: Handle unexpected exceptions :)
                <test-case name="{$case/@name}" result="fail">
                   <exception>Exception while loading expected result fragment: {$util:exception-message}</exception>
                   <query>{$query}</query>
                </test-case>
            )
        (: Comparison method: "Inspect" :)
        (: A text compare is sufficient in many test cases :)
        else if ($output/@compare eq "Inspect") then
            let $text := util:file-read(concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath,
                "/", $output/text()))
            let $test := xqts:normalize-text($text) eq xqts:normalize-text($result)
            return
                xqts:print-result($case/@name, $test, $query, $result, $text, $case)

        (: Don't know how to compare :)
        else
            <error test="{$case/@name}">Unknown comparison method: {$output/@compare}.</error>
};

declare function xqts:run-test-case( $case as element(catalog:test-case)) as item()* {
   let $query := xqts:get-query($case)
   let $context :=
       <static-context>
       {
           for $input in $case/catalog:input-file
           return
               <variable name="{$input/@variable}">{xqts:get-input-value($input)}</variable>
       }
       </static-context>
   let $result :=
       util:catch("java.lang.Exception",
           let $result :=
               util:eval-with-context($query, $context, false())
           return
               xqts:check-output($query, $result, $case),
           if ($case//catalog:expected-error) then
               <test-case name="{$case/@name}" result="pass">
                   <exception>{$util:exception-message}</exception>
                   <expected-error>{$case//catalog:expected-error/text()}</expected-error>
                   <query>{$query}</query>
               </test-case>
           else
               <test-case name="{$case/@name}" result="fail">
                   <exception>{$util:exception-message}</exception>
                   <query>{$query}</query>
               </test-case>
       )
   return $result
};

declare function xqts:finish($result as element()) as empty() {
    let $passed := count($result//test-case[@result = 'pass'])
    let $failed := count($result//test-case[@result = 'fail'])
    return (
        update value $result/@passed with xs:int($passed),
        update value $result/@failed with xs:int($failed)
    )
};

declare function xqts:run-single-test-case($case as element(catalog:test-case),
    $resultRoot as element()?) as empty() {
    let $result := xqts:run-test-case($case)
    return (
        update insert $result into $resultRoot,
        let $added := $resultRoot/test-case[@name = $case/@name]
        return
            if ($added) then xqts:report-progress($added)
            else ()
    )
};

declare function xqts:run-test-group($group as element(catalog:test-group)) as empty() {
    (: Create the collection hierarchy for this group and get the results.xml doc to
        append to. :)
    let $resultsDoc := xqts:create-collections($group)
    let $tests := $group/catalog:test-case
    return (
        (: Execute the test cases :)
        for $test in $tests
        let $log := util:log("DEBUG", ("Running test case: ", string($test/@name)))
        return
            xqts:run-single-test-case($test, $resultsDoc/test-result),
        if ($tests) then
            xqts:finish($resultsDoc/test-result)
        else
            xdb:remove(util:collection-name($resultsDoc), util:document-name($resultsDoc)),
        (: Execute tests in child groups :)
        for $childGroup in $group/catalog:test-group
        let $log := util:log("DEBUG", ("Entering group: ", string($childGroup/@name)))
        return
            xqts:run-test-group($childGroup)
    )
};

declare function xqts:test-single($name as xs:string) as element() {
    let $test := //catalog:test-case[@name = $name]
    return
        if ($test) then
            let $resultsDoc := xqts:create-collections($test/parent::catalog:test-group)
            let $dummy := xqts:run-single-test-case($test, $resultsDoc/test-result)
            return
                $resultsDoc/test-result
        else
            <error>Test case not found: {$name}.</error>
};

declare function xqts:test-group($groupName as xs:string) as empty() {
    let $group := //catalog:test-group[@name = $groupName]
    return (
        xqts:create-progress-file(count($group//catalog:test-case)),
        xqts:run-test-group($group)
    )
};

declare function xqts:test-all() as empty() {
    for $test in /catalog:test-suite/catalog:test-group
    return
        xqts:test-group($test/@name)
};

declare function xqts:get-input-value($input as element(catalog:input-file)) as item()* {
   if ($input eq "emptydoc") then
       ()
   else (
       let $source := root($input)//catalog:source[@ID = $input/text()]
       return
           if (empty($source)) then
               concat("no input found: ", $input/text())
           else
               doc(concat("/db/XQTS/", $source/@FileName))
   )
};

declare function xqts:overall-result() {
    let $passed := count(//test-case[@result = "pass"])
    let $failed := count(//test-case[@result = "fail"])
    return
        <test-result failed="{$failed}" passed="{$passed}"
            percentage="{$passed div ($passed + $failed)}"/>
};

let $group := request:get-parameter('group', ())
return
    if ($group) then
        xqts:test-group($group)
    else
        ()

declare namespace xqts="http://exist-db.org/xquery/xqts";
declare namespace catalog="http://www.w3.org/2005/02/query-test-XQTSCatalog";

declare namespace props="java:java.io.File";

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

(:~ ----------------------------------------------------------------------------------------
     W3C XQuery Test Suite

     This is the main module for running the XQTS on eXist. You can either
     run the whole test suite, a specific test group or a single test case.

     Setup:

     * Make sure the XmlDiff module is registered in conf.xml

     * Edit config.xml and change basedir to point to the directory into
     which you unzipped the XQTS sources. Note: it needs to be an URI.

     * Create a collection /db/XQTS in the database.

     * From the XQTS directory, upload XQTSCatalog.xml into the created
     collection.

     * Upload the "TestSources" directory so the source docs can be found
     in /db/XQTS/TestSources.

     * Run this script with the client.
     ------------------------------------------------------------------------------------------- :)
declare variable $xqts:CONFIG { xqts:initialize() };
declare variable $xqts:XQTS_HOME { xqts:path-to-uri($xqts:CONFIG/basedir/text()) };

declare function xqts:initialize() as element() {
    let $home := util:system-property("exist.home")
    let $path := concat($home, "/webapp/xqts")
    let $collection := xdb:create-collection("/db", "XQTS")
    let $config0 := doc("/db/XQTS")/config
    let $config :=
        if ($config0) then
            $config0
        else
            let $stored := xdb:store-files-from-pattern("/db/XQTS", $path, "*.xml", "text/xml")
            return
                doc("/db/XQTS/config.xml")/config
    let $xqtsHome := $config/basedir/text()
    let $catalog0 := doc("/db/XQTS/XQTSCatalog.xml")
    let $catalog :=
        if ($catalog0) then $catalog0
        else
            xdb:store($collection, "XQTSCatalog.xml",
                xs:anyURI(xqts:path-to-uri(concat($xqtsHome, "XQTSCatalog.xml"))))
    let $sources0 := collection("/db/XQTS/TestSources")
    let $sources :=
        if ($sources0) then
            $sources0
        else
            let $sourceCol := xdb:create-collection("/db/XQTS", "TestSources")
            return
                xdb:store-files-from-pattern($sourceCol, concat($xqtsHome, "TestSources"), "*.xml", "text/xml")
    return
        $config
};

declare function xqts:path-to-uri($path as xs:string) as xs:string {
        if (starts-with($path, "/")) then
            concat("file://", $path)
        else
            concat("file:///", $path)
};

declare function xqts:create-progress-file($testCount as xs:int) as empty() {
    let $results := xdb:store("/db/XQTS", "progress.xml",
        <progress total="{$testCount}" done="0" failed="0" passed="0"/>)
    return ()
};

declare function xqts:report-progress($test-case as element()) as empty() {
    let $result := string($test-case/@result)
    let $progress := doc("/db/XQTS/progress.xml")/progress
    let $counter := $progress/@done
    return (
        update value $counter with xs:int($counter + 1),
        if ($result eq 'fail') then
            update value $progress/@failed with xs:int($progress/@failed + 1)
        else
            update value $progress/@passed with xs:int($progress/@passed + 1)
    )
};

declare function xqts:create-collections($group as element(catalog:test-group)) as node() {
    let $rootColl := xdb:create-collection("/db/XQTS", "test-results")
    let $ancestors := reverse(($group/ancestor::catalog:test-group, $group))
    let $collection := xqts:create-collections($rootColl, $ancestors, "/db/XQTS/test-results")
    let $results := xdb:store($collection, "results.xml", <test-result failed="0" passed="0"/>)
    return
        doc($results)
};

declare function xqts:create-collections($parent as object,
    $pathElements as element()+, $currentPath as xs:string)
as object {
    let $next := $pathElements[last()]
    let $remainder := subsequence($pathElements, 1, count($pathElements) - 1)
    let $newColl := xdb:create-collection($parent, $next/@name)
    return
        if ($remainder) then
            xqts:create-collections($newColl, $remainder, concat($currentPath, "/", $next/@name))
        else
            $newColl
};

declare function xqts:get-query($case as element(catalog:test-case)) {
   let $query-name := $case//catalog:query/@name
   let $path := concat( $xqts:XQTS_HOME, "Queries/XQuery/", $case/@FilePath, $query-name, ".xq" )
   let $xq-string := util:file-read($path)
   return $xq-string
};

declare function xqts:print-result($test-name as xs:string, $passed as xs:boolean, $query as xs:string,
    $result as item()*, $expected as item()*, $case as element(catalog:test-case)) as element() {
    <test-case name="{$test-name}" result="{if ($passed) then 'pass' else 'fail'}">
    {
        if (not($passed)) then (
            <result>{$result}</result>,
            if ($expected instance of element() and count($expected/*) > 10) then
            	<expected truncated="">{$expected/*[position() < 10]}</expected>
            else
                <expected>{$expected}</expected>,
            (: element {$case/catalog:input-file/@variable} { xqts:getInputValue($case) }, :)
            <query>{$query}</query>
        ) else ()
    }
    </test-case>
};

declare function xqts:normalize-text($result as item()*) as xs:string {
    let $str := string-join($result, " ")
    return
        (: Remove leading and trailing whitespace :)
        replace(replace($str, "^\s+", ""), "\s+$", "")
};

declare function xqts:check-output($query as xs:string, $result as item()*, $case as element(catalog:test-case)) {
    let $output := $case/catalog:output-file[last()]
    return
        (: Expected an error, but got a result :)
        if (exists($case/catalog:expected-error)) then
            <test-case name="{$case/@name}" result="fail">
                   <expected-error>{$case/catalog:expected-error/text()}</expected-error>
                   <query>{$query}</query>
            </test-case>
        (: Comparison method: "Text" :)
        else if ($output/@compare eq "Text") then
            let $text := util:file-read(concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath,
                "/", $output/text()))
            let $test := xqts:normalize-text($text) eq xqts:normalize-text($result)
            return
                xqts:print-result($case/@name, $test, $query, $result, $text, $case)
        (: Comparison method: "XML" :)
        else if ($output/@compare eq "XML") then
            util:catch("java.lang.Exception",
                let $filePath := concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath,
                    $output/text())
                let $expected := doc(xdb:store("/db", "temp.xml", xs:anyURI($filePath), "text/xml"))
                let $test := xdiff:compare($expected, $result)
                return
                    xqts:print-result($case/@name, $test, $query, $result, $expected, $case),
                (: Handle unexpected exceptions :)
                <test-case name="{$case/@name}" result="fail">
                   <exception>Exception while loading expected result: {$util:exception-message}</exception>
                   <query>{$query}</query>
                </test-case>
            )
        (: Comparison method: "Fragment" :)
        else if ($output/@compare eq "Fragment") then
            util:catch("java.lang.Exception",
                let $filePath := concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath, $output/text())
                let $expectedFrag := util:file-read($filePath)
                let $xmlFrag := concat("<f>", $expectedFrag, "</f>")
                let $log := util:log("DEBUG", ("Frag stored: ", $xmlFrag))
                let $expected := doc(xdb:store("/db", "temp.xml", $xmlFrag, "text/xml"))
                let $test := xdiff:compare($expected, <f>{$result}</f>)
                return
                    xqts:print-result($case/@name, $test, $query, $result, $expected, $case),
                (: Handle unexpected exceptions :)
                <test-case name="{$case/@name}" result="fail">
                   <exception>Exception while loading expected result fragment: {$util:exception-message}</exception>
                   <query>{$query}</query>
                </test-case>
            )
        (: Don't know how to compare :)
        else
            <error test="{$case/@name}">Unknown comparison method: {$output/@compare}.</error>
};

declare function xqts:run-test-case( $case as element(catalog:test-case)) as item()* {
   let $query := xqts:get-query($case)
   let $context :=
       <static-context>
       {
           for $input in $case/catalog:input-file
           return
               <variable name="{$input/@variable}">{xqts:get-input-value($input)}</variable>
       }
       </static-context>
   let $result :=
       util:catch("java.lang.Exception",
           let $result :=
               util:eval-with-context($query, $context, false())
           return
               xqts:check-output($query, $result, $case),
           if ($case//catalog:expected-error) then
               <test-case name="{$case/@name}" result="pass">
                   <exception>{$util:exception-message}</exception>
                   <expected-error>{$case//catalog:expected-error/text()}</expected-error>
                   <query>{$query}</query>
               </test-case>
           else
               <test-case name="{$case/@name}" result="fail">
                   <exception>{$util:exception-message}</exception>
                   <query>{$query}</query>
               </test-case>
       )
   return $result
};

declare function xqts:finish($result as element()) as empty() {
    let $passed := count($result//test-case[@result = 'pass'])
    let $failed := count($result//test-case[@result = 'fail'])
    return (
        update value $result/@passed with xs:int($passed),
        update value $result/@failed with xs:int($failed)
    )
};

declare function xqts:run-single-test-case($case as element(catalog:test-case),
    $resultRoot as element()?) as empty() {
    let $result := xqts:run-test-case($case)
    return (
        update insert $result into $resultRoot,
        let $added := $resultRoot/test-case[@name = $case/@name]
        return
            if ($added) then xqts:report-progress($added)
            else ()
    )
};

declare function xqts:run-test-group($group as element(catalog:test-group)) as empty() {
    (: Create the collection hierarchy for this group and get the results.xml doc to
        append to. :)
    let $resultsDoc := xqts:create-collections($group)
    let $tests := $group/catalog:test-case
    return (
        (: Execute the test cases :)
        for $test in $tests
        let $log := util:log("DEBUG", ("Running test case: ", string($test/@name)))
        return
            xqts:run-single-test-case($test, $resultsDoc/test-result),
        if ($tests) then
            xqts:finish($resultsDoc/test-result)
        else
            xdb:remove(util:collection-name($resultsDoc), util:document-name($resultsDoc)),
        (: Execute tests in child groups :)
        for $childGroup in $group/catalog:test-group
        let $log := util:log("DEBUG", ("Entering group: ", string($childGroup/@name)))
        return
            xqts:run-test-group($childGroup)
    )
};

declare function xqts:test-single($name as xs:string) as element() {
    let $test := //catalog:test-case[@name = $name]
    return
        if ($test) then
            let $resultsDoc := xqts:create-collections($test/parent::catalog:test-group)
            let $dummy := xqts:run-single-test-case($test, $resultsDoc/test-result)
            return
                $resultsDoc/test-result
        else
            <error>Test case not found: {$name}.</error>
};

declare function xqts:test-group($groupName as xs:string) as empty() {
    let $group := //catalog:test-group[@name = $groupName]
    return (
        xqts:create-progress-file(count($group//catalog:test-case)),
        xqts:run-test-group($group)
    )
};

declare function xqts:test-all() as empty() {
    for $test in /catalog:test-suite/catalog:test-group
    return
        xqts:test-group($test/@name)
};

declare function xqts:get-input-value($input as element(catalog:input-file)) as item()* {
   if ($input eq "emptydoc") then
       ()
   else (
       let $source := root($input)//catalog:source[@ID = $input/text()]
       return
           if (empty($source)) then
               concat("no input found: ", $input/text())
           else
               doc(concat("/db/XQTS/", $source/@FileName))
   )
};

declare function xqts:overall-result() {
    let $passed := count(//test-case[@result = "pass"])
    let $failed := count(//test-case[@result = "fail"])
    return
        <test-result failed="{$failed}" passed="{$passed}"
            percentage="{$passed div ($passed + $failed)}"/>
};

let $group := request:get-parameter('group', ())
return
    if ($group) then
        xqts:test-group($group)
    else
        ()


amended repo admin to work again

amended repo admin to work again

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

xquery version "1.0";

module namespace repomanager="http://exist-db.org/xquery/admin-interface/repo";

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

declare variable $repomanager:coll := "/db/system/repo";
declare variable $repomanager:repo-uri := if (request:get-parameter("repository-url", ())) then
              request:get-parameter("repository-url", ())
            else
              "http://demo.exist-db.org/exist/repo/public/all/";

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

	<entry>
		{
		if ($path eq tokenize($path, "/")[last()]) then
		  (<name>{tokenize($path, "/")[last()]}</name>,
           <path>{$path}</path>)
		else
		  (<name>{tokenize($path, "/")[last()]}</name>,
		  <path>{
		  let $segments := tokenize($path,"/")[not(position() eq last())]
		  for $segment in $segments
		  return

		    <segment>{$segment}</segment>

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

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

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

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

declare function repomanager:publicrepo() as element()
{
let $package-url := request:get-parameter("package-url", ())
return

Actions:

                {
                if (ends-with($package-url,'.xar')) then
                    (
uploaded package: {$package-url}
,

                    let $http-response := httpclient:get(xs:anyURI($package-url), false(), ())
                    let $name := tokenize($package-url, "/")[last()]
                    return
                    let $package-mimetype := "application/xar",
                    $package-data := xs:base64Binary($http-response/httpclient:body/text())
                    return
                    xmldb:store($repomanager:coll, $name, $package-data, $package-mimetype)
                    )
                else

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

                }

                Important: You must restart eXistdb to pick up any changes to the repository.

};

declare function repomanager:upload() as element()
{
    let $name := request:get-parameter("name", ()),
    $repocol :=  if (collection($repomanager:coll)) then () else xmldb:create-collection('/db/system','repo'),
    $docName := if($name) then $name else request:get-uploaded-file-name("upload"),
    $file := request:get-uploaded-file-data("upload") return

Actions:

                {
                if ($docName) then
                    (
uploaded package: {$docName}
,

                    xdb:decode-uri(xs:anyURI(xdb:store($repomanager:coll, xdb:encode-uri($docName), $file)))
                    )
                else

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

                }

                Important: You must restart eXistdb to pick up any changes to the repository.

};

declare function repomanager:deploy() as element()
{
    let $name := request:get-parameter("name", ())
     return

Actions:

deployed application: {$name}

                {
                    let $xar := util:binary-doc(concat($repomanager:coll,'/',$name,'.xar'))
                    let $application := compression:unzip($xar, util:function(xs:QName("local:entry-filter"), 3), (),  util:function(xs:QName("local:entry-data-deploy"), 4), ())
                    let $repo := $application//repo:meta
                    (: let $createrootcoll := xmldb:create-collection() :)
                    return

                        {
                        for $entry in $application
                        return

                         {if ($entry/type eq 'folder') then
                            let $p := $entry/path/segment/node()
                            let $l := $entry/path/segment[last()]/node()
                            let $p1 := string-join($p,'/')
                            let $colpath := concat('/db/',$p1)
                            return
                                 ('folder created: ',xmldb:create-collection(substring-before($colpath,$l), $l))
                         else
                            let $p := $entry/path/segment/node()
                            let $l := $entry/path/segment[last()]/node()
                            let $p1 := string-join($p,'/')
                            let $colpath := concat('/db/',$p1,'/')
                            return
                                if ($entry/name eq 'repo.xml' or $entry/name eq 'expath-pkg.xml')then
                                 ('repo meta file ignored: ', $entry/name)
                                else if (ends-with($entry/name,'.xml') or contains($entry/name,'.htm')) then
                                 ('file created: ',
                                    xmldb:store($colpath, $entry/name/node(), $entry/data/node())
                                 )
                                else
                                 ('file created: ',
                                    xmldb:store($colpath, $entry/name/node(), util:base64-decode($entry/data/text()))
                                 )

                        }

                        }

                }

                Important: You must restart eXistdb to pick up any changes to the repository.

};

declare function repomanager:activate() as element()
{
    let $name := request:get-parameter("name", ()),
    $hostname := request:get-hostname(),            (:  pkg-repo.jar needs http or file scheme :)
    $port := request:get-server-port()
     return

Actions:

activated package: {$name}

                {
                    repo:install(concat('http://',$hostname,':',$port,'/exist/rest',$repomanager:coll,'/',$name,'.xar'))
                }

                Important: You must restart eXistdb to pick up any changes to the repository.

};

declare function repomanager:deactivate() as element()
{
    let $name := request:get-parameter("name", ())
     return

Actions:

deactivated package: {$name}

                {
                    repo:remove($name)
                }

                Important: You must restart eXistdb to pick up any changes to the repository.

};

declare function repomanager:remove() as element()
{
    let $name := request:get-parameter("name", ())
     return

Actions:

removed package: {$name}

                {xmldb:remove($repomanager:coll,concat($name,'.xar'))}

};

declare function repomanager:process-action() as element()*
{
    let $action := request:get-parameter("action", ()) return
        util:catch("java.lang.Exception",
            if($action eq "remove") then
            (
                repomanager:remove()
            )
            else if($action eq "activate") then
            (
                repomanager:activate()
            )
            else if($action eq "deactivate") then
            (
                repomanager:deactivate()
            )
            else if($action eq "Upload Package") then
            (
                repomanager:upload()
            )
            else if($action eq "deploy") then
            (
                repomanager:deploy()
            )
            else if($action eq "Download from Public Repository") then
            (
                repomanager:publicrepo()
            )else(),

                An error occurred while processing the action:

                {$util:exception-message}

        )
};

declare function repomanager:main() as element() {
    let $action := lower-case(request:get-parameter("action", "set repository"))
    let $repocol :=  if (collection($repomanager:coll)) then () else xmldb:create-collection('/db/system','repo')

    return

Package Repository

        <form method="POST" enctype="multipart/form-data">
        { repomanager:process-action() }

                Name
                Description
                Date Installed
                Status
                Action

         {
         let $files := if (collection($repomanager:coll)) then collection($repomanager:coll)/util:document-name(.) else ()
         let $repos := repo:list()
         return

            for $file in $files[contains(.,'.xar')]
            let $package-name := substring-before($file,'.xar')
            let $xar := util:binary-doc(concat($repomanager:coll,'/',$file))

            let $meta := compression:unzip($xar, util:function(xs:QName("local:entry-filter"), 3), (),  util:function(xs:QName("local:entry-data"), 4), ())
            let $package := $meta//package:package
            let $pkg-name := $package/package:module/@name
            let $repo := $meta//repo:meta

            let $installed := exists($repos[. eq $pkg-name])
            return

                {$pkg-name}

                {$repo//repo:type}
                {$repo//repo:description}

                {xmldb:last-modified($repomanager:coll, concat($package-name,'.xar'))}

                {if ($installed) then
                    Active
                else
                    Inactive
                }

                {
                if($installed) then

                    ( deactivate,
                      if ($repo//repo:type eq 'application') then
                        ( ' | ',deploy )
                     else
                        ()
                     )
                 else
                 ( activate,' | ',
                 remove
                 )
                }

            }

                Important: You must restart eXistdb to pick up any changes in repository.

                        <input type="submit" name="action" value="Download from Public Repository"/>

                        <select size="5" width="200" style="width:340px" name="package-url">
                           {
                            let $packages := httpclient:get(xs:anyURI($repomanager:repo-uri),false(),())//httpclient:body/node()
                             return
                             for $package in $packages//package[contains(url/.,'.xar')]
                             return
                                <option value="{$package/url}">{tokenize($package/url, "/")[last()]}</option>
                           }
                        </select>

                         Public Repository URL :

                        <input name="repository-url" size="40" value="{$repomanager:repo-uri}"/>
                        <input type="submit" name="action" value="set repository"/>

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

                Note: You can upload example .xar packages located under webapp/repo/packages
          </form>

};

xquery version "1.0";

module namespace repomanager="http://exist-db.org/xquery/admin-interface/repo";

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

declare variable $repomanager:coll := "/db/system/repo";
declare variable $repomanager:repo-uri := if (request:get-parameter("repository-url", ())) then
              request:get-parameter("repository-url", ())
            else
              "http://demo.exist-db.org/exist/repo/public/all/";

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

	<entry>
		{
		if ($path eq tokenize($path, "/")[last()]) then
		  (<name>{tokenize($path, "/")[last()]}</name>,
           <path>{$path}</path>)
		else
		  (<name>{tokenize($path, "/")[last()]}</name>,
		  <path>{
		  let $segments := tokenize($path,"/")[not(position() eq last())]
		  for $segment in $segments
		  return

		    <segment>{$segment}</segment>

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

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

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

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

declare function repomanager:publicrepo() as element()
{
let $package-url := request:get-parameter("package-url", ())
return

Actions:

                {
                if (ends-with($package-url,'.xar')) then
                    (
uploaded package: {$package-url}
,

                    let $http-response := httpclient:get(xs:anyURI($package-url), false(), ())
                    let $name := tokenize($package-url, "/")[last()]
                    return
                    let $package-mimetype := "application/xar",
                    $package-data := xs:base64Binary($http-response/httpclient:body/text())
                    return
                    xmldb:store($repomanager:coll, $name, $package-data, $package-mimetype)
                    )
                else

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

                }

};

declare function repomanager:upload() as element()
{
    let $name := request:get-parameter("name", ()),
    $repocol :=  if (collection($repomanager:coll)) then () else xmldb:create-collection('/db/system','repo'),
    $docName := if($name) then $name else request:get-uploaded-file-name("upload"),
    $file := request:get-uploaded-file-data("upload") return

Actions:

                {
                if ($docName) then
                    (
uploaded package: {$docName}
,

                    xdb:decode-uri(xs:anyURI(xdb:store($repomanager:coll, xdb:encode-uri($docName), $file)))
                    )
                else

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

                }

};

declare function repomanager:deploy() as element()
{
    let $name := request:get-parameter("name", ())
     return

Actions:

deployed application: {$name}

                {
                    let $xar := util:binary-doc(concat($repomanager:coll,'/',$name,'.xar'))
                    let $application := compression:unzip($xar, util:function(xs:QName("local:entry-filter"), 3), (),  util:function(xs:QName("local:entry-data-deploy"), 4), ())
                    let $repo := $application//repo:meta
                    (: let $createrootcoll := xmldb:create-collection() :)
                    return

                        {
                        for $entry in $application
                        return

                         {if ($entry/type eq 'folder') then
                            let $p := $entry/path/segment/node()
                            let $l := $entry/path/segment[last()]/node()
                            let $p1 := string-join($p,'/')
                            let $colpath := concat('/db/',$p1)
                            return
                                 ('folder created: ',xmldb:create-collection(substring-before($colpath,$l), $l))
                         else
                            let $p := $entry/path/segment/node()
                            let $l := $entry/path/segment[last()]/node()
                            let $p1 := string-join($p,'/')
                            let $colpath := concat('/db/',$p1,'/')
                            return
                                if ($entry/name eq 'repo.xml' or $entry/name eq 'expath-pkg.xml')then
                                 ('repo meta file ignored: ', $entry/name)
                                else if (ends-with($entry/name,'.xml') or contains($entry/name,'.htm')) then
                                 ('file created: ',
                                    xmldb:store($colpath, $entry/name/node(), $entry/data/node())
                                 )
                                else
                                 ('file created: ',
                                    xmldb:store($colpath, $entry/name/node(), util:base64-decode($entry/data/text()))
                                 )

                        }

                        }

                }

};

declare function repomanager:activate() as element()
{
    let $name := request:get-parameter("name", ()),
    $hostname := request:get-hostname(),            (:  pkg-repo.jar needs http or file scheme :)
    $port := request:get-server-port()
     return

Actions:

activated package: {$name}

                {
                    repo:install(concat('http://',$hostname,':',$port,'/exist/rest',$repomanager:coll,'/',$name,'.xar'))
                }

};

declare function repomanager:deactivate() as element()
{
    let $name := request:get-parameter("name", ())
     return

Actions:

deactivated package: {$name}

                {
                    repo:remove($name)
                }

};

declare function repomanager:remove() as element()
{
    let $name := request:get-parameter("name", ())
     return

Actions:

removed package: {$name}

                {xmldb:remove($repomanager:coll,concat($name,'.xar'))}

};

declare function repomanager:process-action() as element()*
{
    let $action := request:get-parameter("action", ()) return
        util:catch("java.lang.Exception",
            if($action eq "remove") then
            (
                repomanager:remove()
            )
            else if($action eq "activate") then
            (
                repomanager:activate()
            )
            else if($action eq "deactivate") then
            (
                repomanager:deactivate()
            )
            else if($action eq "Upload Package") then
            (
                repomanager:upload()
            )
            else if($action eq "deploy") then
            (
                repomanager:deploy()
            )
            else if($action eq "Download from Public Repository") then
            (
                repomanager:publicrepo()
            )else(),

                An error occurred while processing the action:

                {$util:exception-message}

        )
};

declare function repomanager:main() as element() {
    let $action := lower-case(request:get-parameter("action", "set repository"))
    let $repocol :=  if (collection($repomanager:coll)) then () else xmldb:create-collection('/db/system','repo')

    return

Package Repository

        <form method="POST" enctype="multipart/form-data">
        { repomanager:process-action() }

                Name
                Description
                Date Installed
                Status
                Action

         {
         let $files := if (collection($repomanager:coll)) then collection($repomanager:coll)/util:document-name(.) else ()
         let $repos := repo:list()
         return

            for $file in $files[contains(.,'.xar')]
            let $package-name := substring-before($file,'.xar')
            let $xar := util:binary-doc(concat($repomanager:coll,'/',$file))

            let $meta := compression:unzip($xar, util:function(xs:QName("local:entry-filter"), 3), (),  util:function(xs:QName("local:entry-data"), 4), ())
            let $package := $meta//package:package
            let $repo := $meta//repo:meta

            let $installed := exists($repos[. eq $package-name])
            return

                {$package-name}

                {$repo//repo:type}
                {$repo//repo:description}

                {xmldb:last-modified($repomanager:coll, concat($package-name,'.xar'))}

                {if ($installed) then
                    Active
                else
                    Inactive
                }

                {
                if($installed) then

                    ( deactivate,
                      if ($repo//repo:type eq 'application') then
                        ( ' | ',deploy )
                     else
                        ()
                     )
                 else
                 ( activate,' | ',
                 remove
                 )
                }

            }

                Important: You must restart eXistdb to pick up any changes in repository.

                        <input type="submit" name="action" value="Download from Public Repository"/>

                        <select size="5" width="200" style="width:340px" name="package-url">
                           {
                            let $packages := httpclient:get(xs:anyURI($repomanager:repo-uri),false(),())//httpclient:body/node()
                             return
                             for $package in $packages//package[contains(url/.,'.xar')]
                             return
                                <option value="{$package/url}">{tokenize($package/url, "/")[last()]}</option>
                           }
                        </select>

                         Public Repository URL :

                        <input name="repository-url" size="40" value="{$repomanager:repo-uri}"/>
                        <input type="submit" name="action" value="set repository"/>

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

                Note: You can upload example .xar packages located under webapp/repo/packages
          </form>

};


minor: context resolution

minor: context resolution

xquery version "1.0";
(:~ overview/admin module for cr-xq
:)
module namespace cr-admin = "http://aac.ac.at/content_repository/admin";

import module namespace repo-utils = "http://aac.ac.at/content_repository/utils" at  "/db/cr/repo-utils.xqm";
import module namespace crday = "http://aac.ac.at/content_repository/data-ay" at  "/db/cr/crday.xqm";
import module namespace fcs = "http://clarin.eu/fcs/1.0" at "/db/cr/fcs.xqm";

declare function cr-admin:display-overview($config-path as xs:string) as item()* {

       let $config := doc($config-path),
           $mappings := doc(repo-utils:config-value($config, 'mappings'))

        let $opt := util:declare-option("exist:serialize", "media-type=text/html method=xhtml")

(:    {for $target in $config//target return {xs:string($target/@key)}	}
:)
let $overview :=

collection	path	size	ns	root-elem	base-elem	indexes	tests

           { for $map in $mappings//map[@key]
                    let $map-key := $map/xs:string(@key),
                        $map-dbcoll-path := $map/xs:string(@path),
(:                        $map-dbcoll:= if ($map-dbcoll-path ne '' and xmldb:collection-available (($map-dbcoll-path,"")[1])) then collection($map-dbcoll-path) else (),                      :)
                          $map-dbcoll:= repo-utils:context-to-collection($map-key, $config),
                        $root-elems := for $elem in distinct-values($map-dbcoll/*/name()) return $elem,
                        $ns-uris := for $ns in distinct-values($map-dbcoll/namespace-uri(*)) return $ns,
                        $queries-doc-name := cr-admin:check-queries-doc-name($config, $map-key),
                        $invoke-check-queries-href := concat('?x-context=', repo-utils:sanitize-name($map-key) ,'&config=', $config-path, '&operation=' ),
                        $queries := if (repo-utils:is-in-cache($queries-doc-name, $config)) then
                                                view
                                              else ()
                    return

                        {$map-key}
                        {$map-dbcoll-path}
                        {count($map-dbcoll)}
                        {$ns-uris}
                        {$root-elems}
                        {$map/xs:string(@base_elem)}
                        {count($map/index)}
                        {$queries} [run]

                        }

                        (:{for $target in $fcs-tests:config//target
                           let $target-key := $target/xs:string(@key)
                           let $test-result := fcs-tests:get-result($target-key , $testset-key )
                           let $root-elem := $test-result/*/name()
                           let $op := if ($test-result//diagnostic["result-unavailable" = xs:string(@key)]) then 'run' else 'view'
                           (\: display number of all and failed tests in the testset, operates on the HTML produced during the test-run :\)
                           let $view:= if ($test-result//diagnostic["result-unavailable" = xs:string(@key)]) then ()
                                       else
                                            let $show := if ($root-elem[1] eq 'testrun') then
                                                            {count($test-result//div[@class='test'][.//span[@class='value true']])}{"/"}
                                                                   {count($test-result//div[@class='test'][.//span[@class='value false']])}{"/"}
                                                                {count($test-result//div[@class='test'])}
                                                                {if (exists($test-result//diagnostics)) then !! else () }

                                                            else $root-elem
                                            return {$show}

                        return {$view} [run]
                        }:)

         return $overview
};

(: calls:
crday:query-internal($queries, $context as node()+, $result-path as xs:string, $result-filename as xs:string ) as item()* {
:)
declare function cr-admin:run-check-queries($config as node(), $x-context as xs:string, $run-flag as xs:boolean) as item()* {

    let $testset := doc(repo-utils:config-value($config, 'tests.path')),

        $cache-path := repo-utils:config-value($config, 'cache.path'),
        $queries-doc-name := cr-admin:check-queries-doc-name($config, $x-context),

  (: get the the results from cache, or create :)
  $result := if (exists($testset)) then
                if (repo-utils:is-in-cache($queries-doc-name, $config) and not($run-flag)) then
                    repo-utils:get-from-cache($queries-doc-name, $config)
                  else
                    let $context := repo-utils:context-to-collection($x-context, $config)
                    return crday:query-internal($testset, $context, $cache-path, $queries-doc-name)
                    (: no need to store, because already continuously stored during querying
                    return repo-utils:store-in-cache($index-doc-name , $data, $config) :)
               else
                <diagnostics>no testset available</diagnostics>

  return $result

};

(:~ return doc-name out of context and testset (from config) or empty string if testset does not exist :)
declare function cr-admin:check-queries-doc-name($config as node(), $x-context as xs:string) as xs:string {
let $testset := doc(repo-utils:config-value($config, 'tests.path')),
    $testset-name := if (exists($testset)) then util:document-name($testset) else (),
    $sanitized-xcontext := repo-utils:sanitize-name($x-context)
 return if (exists($testset)) then repo-utils:gen-cache-id("queries", ($sanitized-xcontext, $testset-name),"") else ""

};

xquery version "1.0";
(:~ overview/admin module for cr-xq
:)
module namespace cr-admin = "http://aac.ac.at/content_repository/admin";

import module namespace repo-utils = "http://aac.ac.at/content_repository/utils" at  "/db/cr/repo-utils.xqm";
import module namespace crday = "http://aac.ac.at/content_repository/data-ay" at  "/db/cr/crday.xqm";
import module namespace fcs = "http://clarin.eu/fcs/1.0" at "/db/cr/fcs.xqm";

declare function cr-admin:display-overview($config-path as xs:string) as item()* {

       let $config := doc($config-path),
           $mappings := doc(repo-utils:config-value($config, 'mappings'))

        let $opt := util:declare-option("exist:serialize", "media-type=text/html method=xhtml")

(:    {for $target in $config//target return {xs:string($target/@key)}	}
:)
let $overview :=

collection	path	size	ns	root-elem	base-elem	indexes

           { for $map in $mappings//map[@key]
                    let $map-key := $map/xs:string(@key),
                        $map-dbcoll-path := $map/xs:string(@path),
                        $map-dbcoll:= if ($map-dbcoll-path ne '' and xmldb:collection-available (($map-dbcoll-path,"")[1])) then collection($map-dbcoll-path) else (),
                        $root-elems := for $elem in distinct-values($map-dbcoll/*/name()) return $elem,
                        $ns-uris := for $ns in distinct-values($map-dbcoll/namespace-uri(*)) return $ns,
                        $queries-doc-name := cr-admin:check-queries-doc-name($config, $map-key),
                        $invoke-check-queries-href := concat('?x-context=', repo-utils:sanitize-name($map-key) ,'&config=', $config-path, '&operation=' ),
                        $queries := if (repo-utils:is-in-cache($queries-doc-name, $config)) then
                                                view
                                              else ()
                    return

                        {$map-key}
                        {$map-dbcoll-path}
                        {count($map-dbcoll)}
                        {$ns-uris}
                        {$root-elems}
                        {$map/xs:string(@base_elem)}
                        {count($map/index)}
                        {$queries} [run]

                        }

                        (:{for $target in $fcs-tests:config//target
                           let $target-key := $target/xs:string(@key)
                           let $test-result := fcs-tests:get-result($target-key , $testset-key )
                           let $root-elem := $test-result/*/name()
                           let $op := if ($test-result//diagnostic["result-unavailable" = xs:string(@key)]) then 'run' else 'view'
                           (\: display number of all and failed tests in the testset, operates on the HTML produced during the test-run :\)
                           let $view:= if ($test-result//diagnostic["result-unavailable" = xs:string(@key)]) then ()
                                       else
                                            let $show := if ($root-elem[1] eq 'testrun') then
                                                            {count($test-result//div[@class='test'][.//span[@class='value true']])}{"/"}
                                                                   {count($test-result//div[@class='test'][.//span[@class='value false']])}{"/"}
                                                                {count($test-result//div[@class='test'])}
                                                                {if (exists($test-result//diagnostics)) then !! else () }

                                                            else $root-elem
                                            return {$show}

                        return {$view} [run]
                        }:)

         return $overview
};

(: calls:
crday:query-internal($queries, $context as node()+, $result-path as xs:string, $result-filename as xs:string ) as item()* {
:)
declare function cr-admin:run-check-queries($config as node(), $x-context as xs:string, $run-flag as xs:boolean) as item()* {

    let $testset := doc(repo-utils:config-value($config, 'tests.path')),

        $cache-path := repo-utils:config-value($config, 'cache.path'),
        $queries-doc-name := cr-admin:check-queries-doc-name($config, $x-context),

  (: get the the results from cache, or create :)
  $result := if (exists($testset)) then
                if (repo-utils:is-in-cache($queries-doc-name, $config) and not($run-flag)) then
                    repo-utils:get-from-cache($queries-doc-name, $config)
                  else
                    let $context := repo-utils:context-to-collection($x-context, $config)
                    return crday:query-internal($testset, $context, $cache-path, $queries-doc-name)
                    (: no need to store, because already continuously stored during querying
                    return repo-utils:store-in-cache($index-doc-name , $data, $config) :)
               else
                <diagnostics>no testset available</diagnostics>

  return $result

};

(:~ return doc-name out of context and testset (from config) or empty string if testset does not exist :)
declare function cr-admin:check-queries-doc-name($config as node(), $x-context as xs:string) as xs:string {
let $testset := doc(repo-utils:config-value($config, 'tests.path')),
    $testset-name := if (exists($testset)) then util:document-name($testset) else (),
    $sanitized-xcontext := repo-utils:sanitize-name($x-context)
 return if (exists($testset)) then repo-utils:gen-cache-id("queries", ($sanitized-xcontext, $testset-name),"") else ""

};


new and improved date arithmetic method for rocket launches timeline

xquery version "3.1";
declare namespace ebb="http://newtfire.org";
declare variable $rocketColl as node()+ := collection('/db/rocket/');
declare variable $launchDateTimes as xs:dateTime+ := $rocketColl//launch/@sDateTime ! xs:dateTime(.) => sort();
declare variable $maxDateTime as xs:dateTime := $launchDateTimes => max();
declare variable $minDateTime as xs:dateTime := $launchDateTimes => min();

(: 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 :)
(: 2020-04-01 ebb: UPDATING this so we're changing how it works and using pure date arithmetic rather than an estimated base date-in-year divided by 365. Our timeline will be based on a quantity of days separating each rocket launch from the earliest rocket launch.  :)

declare function ebb:dateDecimalConverter($dT as xs:dateTime?)
as xs:decimal?
{
let $dateAsDuration := $dT - $minDateTime
return ebb:durationConverter($dateAsDuration)
};

(: CONVERT DURATION TO DECIMAL :)
(: ebb: The next user-defined function will convert the xs:duration datatype into a decimal value based on the portion of a year taken up : by a mission. Since missions are mostly just a few days or up to a week long, this should be a low decimal value  at most in the
 : hundredths (as in 7/365 is about .0191). We want this value to be based on the portion of year to be proportional with our year-based
 : decimal values for mission dates.
 : 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: We'll retain the total number of days as whole number, and convert the rest to a decimal. 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
 : a decimal conversion of days in the duration.
 : :)

declare function ebb:durationConverter($dur as xs:duration?)
as xs:decimal?
{
let $d := days-from-duration($dur)
let $h := hours-from-duration($dur)
let $m := minutes-from-duration($dur)
let $s := seconds-from-duration($dur)

let $durDec := $d + $h div (24) + $m div (24 * 60) + $s div (24 * 60 * 60)
return $durDec
};
(: *******************END USER-DEFINED FUNCTIONS ********************** :)

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. :)
(: ebb: In the next line, we send our variables to our user-defined function for turning date datatypes into decimal values. :)
let $durDayDec := ebb:durationConverter($duration)

(:  :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)
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 the portion of a year taken up : by a mission. Since missions are mostly just a few days or up to a week long, this should be a low decimal value  at most in the
 : hundredths (as in 7/365 is about .0191). We want this value to be based on the portion of year to be proportional with our year-based
 : decimal values for mission dates.
 : 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 365 days in a year. (Divide the D value by 365). There are 24 hours in a day. (Divide the H portion by 24 * 365) and add it to the days.
 : There are 60 minutes in an hour, and 60 * 24 * 365 gives the total number of minutes in a year.
 : So we divide the M portion by 60 * 24 * 365) and add it to hours.
 : There are 60 seconds in a minute. and 60 * 60 * 24 * 365 gives us the total number of seconds in a year.
 : We then divide the S portion by 60 * 60 * 24 * 365 and add that value to minutes and hours to give us
 : the decimal proporition of a year 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 $durDec := $d div 365 + $h div (365 * 24) + $m div (365 * 24 * 60) + $s div (365 * 24 * 60 * 60)
return $durDec
};
(: *******************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)
(: ebb: 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)


Updated test result to take into account the qname parser error message fix

(:
  Test for improved error messages logic:
  the error message should not contain the "missing comma?" hint
:)

import module namespace refl = "http://www.zorba-xquery.com/modules/reflection";
declare namespace err="http://www.w3.org/2005/xqt-errors";

try
{

refl:eval(
'declare namespace an = "http://www.zorba-xquery.com/annotations";

 declare %an:sequential function local:image(
   $id as xs:string, $width as xs:int*,
   $height as xs:int*, $ratio as xs:decimal*, $format as xs:string) as xs:base64Binary
 {
   variable $obj := null;

   if (fn:empty($obj)) then
     ()
   else
   {
     variable $svg := $obj("svg");
     if (fn:not(jn:is-null($obj("size")("width")))) then
     {
       variable $svg-width := $obj("size")("width");
       variable $svg-height := $obj("size")("height");

       if ($width) then
       {
         $svg-height := xs:int(($svg-height div $svg-width) * $width);
         $svg-width := $width;
       }
       else if ($height) then
       {
         $svg-width := xs:int(($svg-width div $svg-height) * $height);
         $svg-height := $height;
       }
       else if ($ratio) then
       {
         $svg-width := xs:int($svg-width * $ratio);
         $svg-height := xs:int($svg-height * $ratio);
       }
       else
         {}

       $svg := <a>{$svg}</a>/*;

       replace value of node $svg/@height width xs:string($svg-height) || "px";
     }
     else
     {
     }
   }

 };

()'
)

}
catch err:XPST0003
{
  $err:description eq 'invalid expression: syntax error, unexpected qualified name "width", expecting "with"'
}

(:
  Test for improved error messages logic:
  the error message should not contain the "missing comma?" hint
:)

import module namespace refl = "http://www.zorba-xquery.com/modules/reflection";
declare namespace err="http://www.w3.org/2005/xqt-errors";

try
{

refl:eval(
'declare namespace an = "http://www.zorba-xquery.com/annotations";

 declare %an:sequential function local:image(
   $id as xs:string, $width as xs:int*,
   $height as xs:int*, $ratio as xs:decimal*, $format as xs:string) as xs:base64Binary
 {
   variable $obj := null;

   if (fn:empty($obj)) then
     ()
   else
   {
     variable $svg := $obj("svg");
     if (fn:not(jn:is-null($obj("size")("width")))) then
     {
       variable $svg-width := $obj("size")("width");
       variable $svg-height := $obj("size")("height");

       if ($width) then
       {
         $svg-height := xs:int(($svg-height div $svg-width) * $width);
         $svg-width := $width;
       }
       else if ($height) then
       {
         $svg-width := xs:int(($svg-width div $svg-height) * $height);
         $svg-height := $height;
       }
       else if ($ratio) then
       {
         $svg-width := xs:int($svg-width * $ratio);
         $svg-height := xs:int($svg-height * $ratio);
       }
       else
         {}

       $svg := <a>{$svg}</a>/*;

       replace value of node $svg/@height width xs:string($svg-height) || "px";
     }
     else
     {
     }
   }

 };

()'
)

}
catch err:XPST0003
{
  $err:description eq 'invalid expression: syntax error, unexpected "QName", expecting "with"'
}