Implementing the W3C's official XQuery test suite.

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

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

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";

(:~ ----------------------------------------------------------------------------------------
     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

     * Change the $xqts:XQTS_HOME variable below to point to the directory into
     which you unzipped the XQTS sources.

     * 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:XQTS_HOME { "file:///d:/Data/XQTS/" };

declare function xqts:initialize() as empty() {
    let $output-coll := xdb:create-collection("/db/XQTS", "test-results")
    let $dummy := xdb:store("/db/XQTS/test-results", "results.xml", <test-result failed="0" passed="0"/>)
    return ()
};

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()*) as element() {
    <test-case name="{$test-name}" result="{if ($passed) then 'pass' else 'fail'}">
    {
        if (not($passed)) then (
            <result>{$result}</result>,
            <expected>{$expected}</expected>,
            <query>{$query}</query>
        ) else ()
    }
    </test-case>
};

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
        if ($output/@compare eq "Text") then
            let $text := util:file-read(concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath,
                "/", $output/text()))
            let $test := $text eq string($result[1])
            return
                xqts:print-result($case/@name, $test, $query, $result, $text)
        else if ($output/@compare eq "XML") then
            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)
        else if ($output/@compare eq "Fragment") then
            let $filePath := concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath, $output/text())
            let $xmlFrag := concat("<f>", util:file-read($filePath), "</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)
        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 $input := $case/catalog:input-file
   let $input-value :=
       if ($input eq "emptydoc") then
           ()
       else (
           let $source := root($case)//catalog:source[@ID = $input/text()]
           return
               if (empty($source)) then
                   concat("no input found: ", $input/text())
               else
                   doc(concat("/db/XQTS/", $source/@FileName))
       )
   let $context :=
       <static-context>
           <variable name="{$input/@variable}">{$input-value}</variable>
       </static-context>
   let $result :=
       util:catch("org.exist.xquery.XPathException",
           let $result :=
               util:eval-with-context($query, $context, false())
           return
               xqts:check-output($query, $result, $case),
           if (exists($case/catalog:expected-error)) then
               <test-case name="{$case/@name}" result="pass"/>
           else
               <test-case name="{$case/@name}" result="fail">
                   <exception>{$util:exception-message}</exception>
                   <query>{$query}</query>
               </test-case>
       )
   return $result
};

declare function xqts:finish() as empty() {
    let $result := doc("/db/XQTS/test-results/results.xml")/test-result
    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),
    $group as element()?) as empty() {
    let $result := xqts:run-test-case($case)
    return
        if ($group) then
            update insert $result into $group
        else
            update insert $result into doc("/db/XQTS/test-results/results.xml")/test-result
};

declare function xqts:run-test-group($group as element(catalog:test-group),
    $parent as element()?) as empty() {
    update insert <test-group name="{$group/@name}">{$group/catalog:GroupInfo}</test-group>
        into $parent,
    let $newGroup := $parent/test-group[last()]
    return (
        for $test in $group/catalog:test-case
        let $log := util:log("DEBUG", ("Running test case: ", string($test/@name)))
        return
            xqts:run-single-test-case($test, $newGroup),
        for $childGroup in $group/catalog:test-group
        let $log := util:log("DEBUG", ("Entering group: ", string($childGroup/@name)))
        return
            xqts:run-test-group($childGroup, $newGroup)
    )
};

declare function xqts:test-single($name as xs:string) as empty() {
    let $test := //catalog:test-case[@name = $name]
    return
        xqts:run-single-test-case($test, collection("/db/XQTS/test-results")/test-result)
};

declare function xqts:test-group($groupName as xs:string) as empty() {
    let $group := //catalog:test-group[@name = $groupName]
    return
        xqts:run-test-group($group, collection("/db/XQTS/test-results")/test-result)
};

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

xqts:initialize(),
(: xqts:test-single("Axes001"), :)
xqts:test-group("PathExpr"),
xqts:finish(),
doc("/db/XQTS/test-results/results.xml")

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

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";

(:~ ----------------------------------------------------------------------------------------
     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

     * Change the $xqts:XQTS_HOME variable below to point to the directory into
     which you unzipped the XQTS sources.

     * 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:XQTS_HOME { "file:///d:/Data/XQTS/" };

declare function xqts:initialize() as empty() {
    let $output-coll := xdb:create-collection("/db/XQTS", "test-results")
    let $dummy := xdb:store("/db/XQTS/test-results", "results.xml", <test-result failed="0" passed="0"/>)
    return ()
};

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()*) as element() {
    <test-case name="{$test-name}" result="{if ($passed) then 'pass' else 'fail'}">
    {
        if (not($passed)) then (
            <result>{$result}</result>,
            <expected>{$expected}</expected>,
            <query>{$query}</query>
        ) else ()
    }
    </test-case>
};

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
        if ($output/@compare eq "Text") then
            let $text := util:file-read(concat($xqts:XQTS_HOME, "ExpectedTestResults/", $case/@FilePath,
                "/", $output/text()))
            let $test := $text eq string($result[1])
            return
                xqts:print-result($case/@name, $test, $query, $result, $text)
        else if ($output/@compare eq "XML") then
            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)
        else
            <error>Unknown result type</error>
};

declare function xqts:run-test-case( $case as element(catalog:test-case)) as item()* {
   let $query := xqts:get-query($case)
   let $input := $case/catalog:input-file
   let $input-value :=
       if ($input eq "emptydoc") then
           ()
       else (
           let $source := root($case)//catalog:source[@ID = $input/text()]
           return
               if (empty($source)) then
                   concat("no input found: ", $input/text())
               else
                   doc(concat("/db/XQTS/", $source/@FileName))
       )
   let $context :=
       <static-context>
           <variable name="input-context">{$input-value}</variable>
       </static-context>
   let $result :=
       util:catch("org.exist.xquery.XPathException",
           let $result :=
               util:eval-with-context($query, $context, false())
           return
               xqts:check-output($query, $result, $case),
           if (exists($case/catalog:expected-error)) then
               <test-case name="{$case/@name}" result="pass"/>
           else
               <test-case name="{$case/@name}" result="fail">
                   <exception>{$util:exception-message}</exception>
                   <query>{$query}</query>
               </test-case>
       )
   return $result
};

declare function xqts:finish() as empty() {
    let $result := doc("/db/XQTS/test-results/results.xml")/test-result
    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),
    $group as element()?) as empty() {
    let $result := xqts:run-test-case($case)
    return
        if ($group) then
            update insert $result into $group
        else
            update insert $result into doc("/db/XQTS/test-results/results.xml")/test-result
};

declare function xqts:run-test-group($group as element(catalog:test-group),
    $parent as element()?) as empty() {
    update insert <test-group name="{$group/@name}">{$group/catalog:GroupInfo}</test-group>
        into $parent,
    let $newGroup := $parent/test-group[last()]
    return (
        for $test in $group/catalog:test-case
        let $log := util:log("DEBUG", ("Running test case: ", string($test/@name)))
        return
            xqts:run-single-test-case($test, $newGroup),
        for $childGroup in $group/catalog:test-group
        let $log := util:log("DEBUG", ("Entering group: ", string($childGroup/@name)))
        return
            xqts:run-test-group($childGroup, $newGroup)
    )
};

declare function xqts:test-single($name as xs:string) as empty() {
    let $test := //catalog:test-case[@name = $name]
    return
        xqts:run-single-test-case($test, collection("/db/XQTS/test-results")/test-result)
};

declare function xqts:test-group($groupName as xs:string) as empty() {
    let $group := //catalog:test-group[@name = $groupName]
    return
        xqts:run-test-group($group, collection("/db/XQTS/test-results")/test-result)
};

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

xqts:initialize(),
(: xqts:test-single("Axes001"), :)
xqts:test-group("PathExpr"),
xqts:finish(),
doc("/db/XQTS/test-results/results.xml")


courses xquery function name refactor

courses xquery function name refactor

declare function local:transform($nodefilter as function(element()) as xs:boolean, $nodeadd as function(element()) as item()*, $input as item()*) as item()* {
for $node in $input
   return
      typeswitch($node)
        case element()
         return
          if ($nodefilter($node)) then
              element {name($node)} {
                $nodeadd($node),
                for $att in $node/@*
                      return attribute {name($att)} {$att}
                ,
                for $child in $node
                   return local:transform($nodefilter, $nodeadd, $child/node())

              }
          else ()
        default return $node
};

let $nf1 := function($node as element()) as xs:boolean {name($node) != 'name'},
	$na1 := function($node as element()) as item()* {if (name($node) = 'courses') then
                    for $na in distinct-values($node/course/taken_by/student/name/text()) return element {'newET0'} {  for $nu in distinct-values($node/course/taken_by/student[name/text() = $na]/@sno) return
                  element {'newET00'} {attribute {'sno'} {$nu}},
                  element {'name'} {$na}
                  }
                else ()}
  return
	local:transform($nf1, $na1, doc("test.xml")/courses)

declare function local:transform($nodefilter as function(element()) as xs:boolean, $nodeadd as function(element()) as item()*, $input as item()*) as item()* {
for $node in $input
   return
      typeswitch($node)
        case element()
         return
          if ($nodefilter($node)) then
              element {name($node)} {
                $nodeadd($node),
                for $att in $node/@*
                      return attribute {name($att)} {$att}
                ,
                for $child in $node
                   return local:transform($nodefilter, $nodeadd, $child/node())

              }
          else ()
        default return $node
};

let $f := function($node as element()) as xs:boolean {name($node) != 'name'},
	$g := function($node as element()) as item()* {if (name($node) = 'courses') then
                    for $na in distinct-values($node/course/taken_by/student/name/text()) return element {'newET0'} {  for $nu in distinct-values($node/course/taken_by/student[name/text() = $na]/@sno) return
                  element {'newET00'} {attribute {'sno'} {$nu}},
                  element {'name'} {$na}
                  }
                else ()}
  return
	local:transform($f, $g, doc("test.xml")/courses)


remove unnecessary annotations and clean-up indentation Approved: Ghislain Fourny, Till Westmann

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

module namespace guestbook = "http://www.28msec.com/templates/guestbook/guestbook";

import module namespace ddl = "http://www.zorba-xquery.com/modules/store/static/collections/ddl";
import module namespace dml = "http://www.zorba-xquery.com/modules/store/static/collections/dml";

declare namespace ann = "http://www.zorba-xquery.com/annotations";

declare collection guestbook:entries as node()*;
declare variable $guestbook:entries as xs:QName := xs:QName("guestbook:entries");

declare function guestbook:list()
{
  let $entries := dml:collection($guestbook:entries)
  let $num_entries := fn:count($entries)
  return
    if($num_entries = 0)
    then

No entries, yet.

    else
      for $entry in $entries[position() gt $num_entries - 5]
      return  1
};

declare function guestbook:window() {
  let $x := 1 to 1000
  for tumbling window $w in $x
  start at $s when fn:true()
  end at $e when $e - $s eq 51
  return <window>{ $w }</window>
};

declare %ann:sequential function guestbook:init() {
  ddl:create($guestbook:entries);
};

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

module namespace guestbook = "http://www.28msec.com/templates/guestbook/guestbook";

import module namespace ddl = "http://www.zorba-xquery.com/modules/store/static/collections/ddl";
import module namespace dml = "http://www.zorba-xquery.com/modules/store/static/collections/dml";

declare namespace ann = "http://www.zorba-xquery.com/annotations";

declare %ann:ordered collection guestbook:entries as node()*;
declare variable $guestbook:entries as xs:QName := xs:QName("guestbook:entries");

declare %ann:sequential function guestbook:list()
{
  let $entries := dml:collection($guestbook:entries)
  let $num_entries := fn:count($entries)
  return
    if($num_entries = 0)
    then

No entries, yet.

    else
      for $entry in $entries[position() gt $num_entries - 5]
      return  1
};

declare function guestbook:window() {
 let $x := 1 to 1000
 for tumbling window $w in $x
 start at $s when fn:true()
 end at $e when $e - $s eq 51
 return <window>{ $w }</window>
};

declare %ann:sequential function guestbook:init() {
  ddl:create($guestbook:entries);
};


Removing the datatype binding on date (only because XsltForms date picker is not wanted)

xquery version "1.0";
declare namespace exist = "http://exist.sourceforge.net/NS/exist";
declare namespace request="http://exist-db.org/xquery/request";

(:

Punch list:
(must have)

(probably need)
  * Add file manifest viewer (subform)

(might want)
  * Add trigger to see preview on save (may involve xq)
  * See if modals can be improved
  * Add keyboard command bindings

:)

declare option exist:serialize "method=xhtml media-type=text/xml";
declare option exist:serialize "indent=no";
declare option exist:serialize "process-xsl-pi=no";

let $attribute := request:set-attribute("betterform.filter.ignoreResponseBody", "true")

let $base_url := '/metadata/'
let $load_path := 'posted_data/'
let $param := request:get-parameter("fid", 0)

let $form_url := concat($base_url, $load_path, $param)

let $form :=

    <title>Shelley-Godwin Archive Metadata Editor</title>
    <link rel="stylesheet" type="text/css" href="../lib/bootstrap/css/bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" href="../lib/FontAwesome/css/font-awesome.css" />
    <link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.8.2/css/jquery.dataTables.css" />
    <link rel="stylesheet" type="text/css" href="../lib/fancyBox/source/jquery.fancybox.css" />

    <xf:model id="teiHeader">
      <xf:instance id="itemMD" xmlns="http://www.tei-c.org/ns/1.0"
        src= "{$form_url}">
      </xf:instance>

      <xf:instance id="control-codes" xmlns="">
        <data>
          <output-filename>temp.xml</output-filename>
          <institution-id></institution-id>
        </data>
      </xf:instance>

      <xf:bind id="title"
        nodeset="/tei:TEI/tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:title[@type='main']"
        required="true()"></xf:bind>
      <xf:bind id="date"
        nodeset="/tei:TEI/tei:teiHeader/tei:profileDesc/tei:creation/tei:date/@when"></xf:bind>
      <xf:bind id="desc-label" nodeset="//tei:msDesc/tei:msContents/tei:summary" required="true()"></xf:bind>

      <xf:bind id="call-number" nodeset="//tei:msDesc/tei:msIdentifier/tei:idno" required="true()"></xf:bind>

      <xf:bind id="xtra-foliation" nodeset="instance('control-codes')/boolean1" type="boolean"></xf:bind>
      <xf:bind id="foliation-modern"
        nodeset="//tei:msDesc/tei:physDesc/tei:objectDesc/tei:supportDesc/tei:foliation[@xml:id='modern']"></xf:bind>

      <xf:bind id="meditor"
        nodeset="/tei:TEI/tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:respStmt[@xml:id='md_editor']/tei:name"
        required="true()"></xf:bind>
      <xf:bind id="metadata-source"
        nodeset="//tei:msDesc/tei:additional/tei:adminInfo/tei:recordHist/tei:p/tei:bibl"
        required="true()"></xf:bind>

      <xf:bind id="change-author" nodeset="//tei:revisionDesc/tei:change/@who" readonly="true()"></xf:bind>
      <xf:bind id="change" nodeset="//tei:revisionDesc/tei:change" readonly="true()"></xf:bind>

      <xf:submission id="save" method="put" replace="none">
        <xf:resource value="concat('/metadata/posted_data/', instance('control-codes')/output-filename)"></xf:resource>
      </xf:submission>

      <xf:message level="modal" ev:event="xforms-submit-done">Saved!</xf:message>

    </xf:model>

Metadata Editor

for the Shelley-Godwin Archive

          <xf:trigger>
            <xf:label> Basics</xf:label>
            <xf:toggle case="basics" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger id="holdings">
            <xf:label> Holdings Info</xf:label>
            <xf:toggle case="holdings_info" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Physical Description</xf:label>
            <xf:toggle case="physical_description" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Hands</xf:label>
            <xf:toggle case="hand_info" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Data Provenance</xf:label>
            <xf:toggle case="prov" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger id="related-items">
            <xf:label> Related Items</xf:label>
            <xf:toggle case="surrogates" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Changelog</xf:label>
            <xf:toggle case="admin_info" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>

        <xf:switch>
          <xf:case id="basics">

1 of 7

Basic Item Description

          <xf:group>

              <xf:input id="main-title" bind="title" incremental="true">
                <xf:label>Main title: </xf:label>
              </xf:input>

              <xf:input id="creation-date" appearance="minimal" bind="date">
                <xf:label>Date created: </xf:label>

              </xf:input>

              <xf:textarea id="item-label" bind="desc-label">
                <xf:label>Short description: </xf:label>
              </xf:textarea>

          </xf:group>

                <xf:trigger id="next-to-holdings">
                  <xf:label>Next →</xf:label>
                  <xf:toggle case="holdings_info" ev:event="DOMActivate"></xf:toggle>
                </xf:trigger>

        </xf:case>
          <xf:case id="holdings_info">

2 of 7

Holdings Information

          <xf:group ref="//tei:msDesc/tei:msIdentifier">

              <xf:select1 id="select-repo" ref="tei:repository" appearance="minimal">
                <xf:label>Host institution</xf:label>
                <xf:item>
                  <xf:label>British Library</xf:label>
                  <xf:value>bl</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>Harvard University, Houghton Library</xf:label>
                  <xf:value>mh</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>Huntington Library</xf:label>
                  <xf:value>hu</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>New York Public Library</xf:label>
                  <xf:value>pf</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>Oxford University, Bodleian Library</xf:label>
                  <xf:value>ox</xf:value>
                </xf:item>
                <xf:action ev:event="xforms-value-changed">
                  <xf:setvalue ref="instance('control-codes')/institution-id" value="instance('itemMD')//tei:repository"></xf:setvalue>
                </xf:action>
              </xf:select1>

              <xf:input id="lib-collection" ref="tei:collection" appearance="minimal">
                <xf:label>Name of collection: </xf:label>
              </xf:input>

              <xf:input id="lib-idno" bind="call-number" appearance="minimal">
                <xf:label>Call number/shelf mark for item: </xf:label>
                <xf:action ev:event="xforms-value-changed">
                  <xf:setvalue ref="instance('control-codes')/output-filename" value="concat(lower-case(translate(instance('itemMD')//tei:msDesc/tei:msIdentifier/tei:idno, ' .,', '_')), '.xml')"></xf:setvalue>
                </xf:action>
              </xf:input>

          </xf:group>

                  <xf:trigger id="prev-to-basics">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="basics" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-physDesc">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="physical_description" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="physical_description">

3 of 7

Physical Description Aspects of the form, support, and extent of a manuscript, as well as the styles of writing present

          <xf:group ref="//tei:msDesc/tei:physDesc/tei:objectDesc/tei:supportDesc">

              <xf:input id="physical-extent" ref="tei:extent" appearance="minimal">
              <xf:label>Extent: </xf:label>
            </xf:input>

              <xf:textarea id="physical-support" ref="tei:support/tei:p" appearance="minimal">
                <xf:label>Describe the material support of the item: </xf:label>
              </xf:textarea>

              <xf:textarea id="foliation-orig" ref="tei:foliation[@xml:id='original']/tei:p">
                <xf:label>Describe any (original) foliation scheme present: </xf:label>
              </xf:textarea>

              <xf:input bind="xtra-foliation">
              <xf:label>Record another foliation scheme?</xf:label>
            </xf:input>
              <xf:textarea id="foliation-mod" bind="foliation-modern">
              <xf:label>Describe any additional foliation schemes present: </xf:label>
            </xf:textarea>

              <xf:textarea id="physical-condition" ref="tei:condition/tei:p">
                <xf:label>Describe the condition of the item: </xf:label>
              </xf:textarea>

          </xf:group>

                  <xf:trigger id="prev-to-holdings">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="holdings_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-hands">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="hand_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="hand_info">

4 of 7

Manuscript Hands

          <xf:group>

              <xf:textarea id="manu-hands" ref="//tei:msDesc/tei:physDesc/tei:handDesc/tei:p">
                <xf:label>Describe hands found in the item: </xf:label>
              </xf:textarea>

          </xf:group>

                  <xf:trigger id="prev-to-physDesc">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="physical_description" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-prov">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="prov" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="prov">

5 of 7

Data Provenance

          <xf:group ref="//tei:msDesc/tei:additional/tei:adminInfo/tei:recordHist">

              <xf:input id="metadata-creator" bind="meditor">
                <xf:label>Metadata created/edited for SGA by: </xf:label>
              </xf:input>

              <xf:textarea id="source" bind="metadata-source">
                <xf:label>Source used: </xf:label>
              </xf:textarea>

          </xf:group>

                  <xf:trigger id="prev-to-hands">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="hand_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-surrogates">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="surrogates" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="surrogates">

6 of 7

Related Items Most of the items in the Shelley-Godwin Archive have been published in facsimile editions.
              This section records information about those surrogate items.

          <xf:group ref="//tei:msDesc/tei:additional/tei:listBibl">

              <xf:textarea id="surrogate1" ref="tei:bibl">
                <xf:label>Citation for related/additional surrogate for this item: </xf:label>
              </xf:textarea>

          </xf:group>

                <xf:trigger id="next-to-manifest">
                  <xf:label> Link page images to this Item</xf:label>
                <xf:toggle case="file_manifest" ev:event="DOMActivate"></xf:toggle>
              </xf:trigger>

                  <xf:trigger id="prev-to-prov">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="prov" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="surrogate-to-finish">
                    <xf:label>Finish</xf:label>
                    <xf:toggle case="admin_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="file_manifest">

Link Page Images Label image files with descriptive information indicating their relationship to the original item

<xf:output id="institution-code" ref="instance('control-codes')/institution-id"></xf:output>

Select an item:

Loading

Retrieving page images for this item. Please wait.

                                filename
                                link
                                label

                        <button id="jsSave" class="btn btn-primary">Save</button>

                  <xf:trigger id="prev-to-manifest">
                    <xf:label>← Back</xf:label>
                    <xf:toggle case="file_manifest" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="img-to-finish">
                    <xf:label>Finish</xf:label>
                    <xf:toggle case="admin_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

          </xf:case>
          <xf:case id="admin_info">

7 of 7

Edit Changelog Help the Shelley-Godwin Archive keep track of everyone's contributions

                Timestamp
                Author
                Change(s)

          <xf:repeat id="change-repeat" nodeset="instance('itemMD')//tei:revisionDesc/tei:change">

                    <xf:output ref="./@when"></xf:output>

                    <xf:input ref="./@who">
                        <xf:action ev:event="DOMFocusIn">
                            <xf:load resource="javascript:makeEditable()"></xf:load>
                        </xf:action>
                    </xf:input>

                    <xf:input ref="."></xf:input>

            </xf:repeat>

          <xf:trigger>
                <xf:label>Add an entry</xf:label>
                    <xf:action ev:event="DOMActivate">
                    <xf:insert nodeset="//tei:revisionDesc/tei:change" at="last()" position="after"/>
                    <xf:setvalue ref="instance('control-codes')/output-filename" value="concat(lower-case(translate(instance('itemMD')//tei:msDesc/tei:msIdentifier/tei:idno, ' .,', '_')), '.xml')"></xf:setvalue>
                    <xf:setvalue ref="//tei:revisionDesc/tei:change[last()]/@when" value="now()"></xf:setvalue>
                    <xf:setvalue ref="//tei:revisionDesc/tei:change[last()]/@who" value="'Click to edit'"></xf:setvalue>
                    <xf:setvalue ref="//tei:revisionDesc/tei:change[last()]" value="'Click to edit'"></xf:setvalue>
                    </xf:action>
          </xf:trigger>

                  <xf:trigger id="prev-from-submit">
                    <xf:label>← Go back</xf:label>
                    <xf:toggle case="surrogates" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:submit id="finish" submission="save">
                    <xf:label>Save</xf:label>
                  </xf:submit>

        </xf:case>
        </xf:switch>

            Powered by Bootstrap, Font Awesome, and poetry.

let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="http://50.19.209.106/fs/xforms/xsltforms/xsltforms.xsl" '}
let $css-pi := processing-instruction css-conversion {'no'}

return ($xslt-pi,$css-pi,$form)

xquery version "1.0";
declare namespace exist = "http://exist.sourceforge.net/NS/exist";
declare namespace request="http://exist-db.org/xquery/request";

(:

Punch list:
(must have)

(probably need)
  * Add file manifest viewer (subform)

(might want)
  * Add trigger to see preview on save (may involve xq)
  * See if modals can be improved
  * Add keyboard command bindings

:)

declare option exist:serialize "method=xhtml media-type=text/xml";
declare option exist:serialize "indent=no";
declare option exist:serialize "process-xsl-pi=no";

let $attribute := request:set-attribute("betterform.filter.ignoreResponseBody", "true")

let $base_url := '/metadata/'
let $load_path := 'posted_data/'
let $param := request:get-parameter("fid", 0)

let $form_url := concat($base_url, $load_path, $param)

let $form :=

    <title>Shelley-Godwin Archive Metadata Editor</title>
    <link rel="stylesheet" type="text/css" href="../lib/bootstrap/css/bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" href="../lib/FontAwesome/css/font-awesome.css" />
    <link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.8.2/css/jquery.dataTables.css" />
    <link rel="stylesheet" type="text/css" href="../lib/fancyBox/source/jquery.fancybox.css" />

    <xf:model id="teiHeader">
      <xf:instance id="itemMD" xmlns="http://www.tei-c.org/ns/1.0"
        src= "{$form_url}">
      </xf:instance>

      <xf:instance id="control-codes" xmlns="">
        <data>
          <output-filename>temp.xml</output-filename>
          <institution-id></institution-id>
        </data>
      </xf:instance>

      <xf:bind id="title"
        nodeset="/tei:TEI/tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:title[@type='main']"
        required="true()"></xf:bind>
      <xf:bind id="date"
        nodeset="/tei:TEI/tei:teiHeader/tei:profileDesc/tei:creation/tei:date/@when" type="gYear"></xf:bind>
      <xf:bind id="desc-label" nodeset="//tei:msDesc/tei:msContents/tei:summary" required="true()"></xf:bind>

      <xf:bind id="call-number" nodeset="//tei:msDesc/tei:msIdentifier/tei:idno" required="true()"></xf:bind>

      <xf:bind id="xtra-foliation" nodeset="instance('control-codes')/boolean1" type="boolean"></xf:bind>
      <xf:bind id="foliation-modern"
        nodeset="//tei:msDesc/tei:physDesc/tei:objectDesc/tei:supportDesc/tei:foliation[@xml:id='modern']"></xf:bind>

      <xf:bind id="meditor"
        nodeset="/tei:TEI/tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:respStmt[@xml:id='md_editor']/tei:name"
        required="true()"></xf:bind>
      <xf:bind id="metadata-source"
        nodeset="//tei:msDesc/tei:additional/tei:adminInfo/tei:recordHist/tei:p/tei:bibl"
        required="true()"></xf:bind>

      <xf:bind id="change-author" nodeset="//tei:revisionDesc/tei:change/@who" readonly="true()"></xf:bind>
      <xf:bind id="change" nodeset="//tei:revisionDesc/tei:change" readonly="true()"></xf:bind>

      <xf:submission id="save" method="put" replace="none">
        <xf:resource value="concat('/metadata/posted_data/', instance('control-codes')/output-filename)"></xf:resource>
      </xf:submission>

      <xf:message level="modal" ev:event="xforms-submit-done">Saved!</xf:message>

    </xf:model>

Metadata Editor

for the Shelley-Godwin Archive

          <xf:trigger>
            <xf:label> Basics</xf:label>
            <xf:toggle case="basics" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger id="holdings">
            <xf:label> Holdings Info</xf:label>
            <xf:toggle case="holdings_info" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Physical Description</xf:label>
            <xf:toggle case="physical_description" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Hands</xf:label>
            <xf:toggle case="hand_info" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Data Provenance</xf:label>
            <xf:toggle case="prov" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger id="related-items">
            <xf:label> Related Items</xf:label>
            <xf:toggle case="surrogates" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>
          <xf:trigger>
            <xf:label> Changelog</xf:label>
            <xf:toggle case="admin_info" ev:event="DOMActivate"></xf:toggle>
          </xf:trigger>

        <xf:switch>
          <xf:case id="basics">

1 of 7

Basic Item Description

          <xf:group>

              <xf:input id="main-title" bind="title" incremental="true">
                <xf:label>Main title: </xf:label>
              </xf:input>

              <xf:input id="creation-date" appearance="minimal" bind="date">
                <xf:label>Date created: </xf:label>

              </xf:input>

              <xf:textarea id="item-label" bind="desc-label">
                <xf:label>Short description: </xf:label>
              </xf:textarea>

          </xf:group>

                <xf:trigger id="next-to-holdings">
                  <xf:label>Next →</xf:label>
                  <xf:toggle case="holdings_info" ev:event="DOMActivate"></xf:toggle>
                </xf:trigger>

        </xf:case>
          <xf:case id="holdings_info">

2 of 7

Holdings Information

          <xf:group ref="//tei:msDesc/tei:msIdentifier">

              <xf:select1 id="select-repo" ref="tei:repository" appearance="minimal">
                <xf:label>Host institution</xf:label>
                <xf:item>
                  <xf:label>British Library</xf:label>
                  <xf:value>bl</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>Harvard University, Houghton Library</xf:label>
                  <xf:value>mh</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>Huntington Library</xf:label>
                  <xf:value>hu</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>New York Public Library</xf:label>
                  <xf:value>pf</xf:value>
                </xf:item>
                <xf:item>
                  <xf:label>Oxford University, Bodleian Library</xf:label>
                  <xf:value>ox</xf:value>
                </xf:item>
                <xf:action ev:event="xforms-value-changed">
                  <xf:setvalue ref="instance('control-codes')/institution-id" value="instance('itemMD')//tei:repository"></xf:setvalue>
                </xf:action>
              </xf:select1>

              <xf:input id="lib-collection" ref="tei:collection" appearance="minimal">
                <xf:label>Name of collection: </xf:label>
              </xf:input>

              <xf:input id="lib-idno" bind="call-number" appearance="minimal">
                <xf:label>Call number/shelf mark for item: </xf:label>
                <xf:action ev:event="xforms-value-changed">
                  <xf:setvalue ref="instance('control-codes')/output-filename" value="concat(lower-case(translate(instance('itemMD')//tei:msDesc/tei:msIdentifier/tei:idno, ' .,', '_')), '.xml')"></xf:setvalue>
                </xf:action>
              </xf:input>

          </xf:group>

                  <xf:trigger id="prev-to-basics">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="basics" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-physDesc">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="physical_description" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="physical_description">

3 of 7

Physical Description Aspects of the form, support, and extent of a manuscript, as well as the styles of writing present

          <xf:group ref="//tei:msDesc/tei:physDesc/tei:objectDesc/tei:supportDesc">

              <xf:input id="physical-extent" ref="tei:extent" appearance="minimal">
              <xf:label>Extent: </xf:label>
            </xf:input>

              <xf:textarea id="physical-support" ref="tei:support/tei:p" appearance="minimal">
                <xf:label>Describe the material support of the item: </xf:label>
              </xf:textarea>

              <xf:textarea id="foliation-orig" ref="tei:foliation[@xml:id='original']/tei:p">
                <xf:label>Describe any (original) foliation scheme present: </xf:label>
              </xf:textarea>

              <xf:input bind="xtra-foliation">
              <xf:label>Record another foliation scheme?</xf:label>
            </xf:input>
              <xf:textarea id="foliation-mod" bind="foliation-modern">
              <xf:label>Describe any additional foliation schemes present: </xf:label>
            </xf:textarea>

              <xf:textarea id="physical-condition" ref="tei:condition/tei:p">
                <xf:label>Describe the condition of the item: </xf:label>
              </xf:textarea>

          </xf:group>

                  <xf:trigger id="prev-to-holdings">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="holdings_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-hands">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="hand_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="hand_info">

4 of 7

Manuscript Hands

          <xf:group>

              <xf:textarea id="manu-hands" ref="//tei:msDesc/tei:physDesc/tei:handDesc/tei:p">
                <xf:label>Describe hands found in the item: </xf:label>
              </xf:textarea>

          </xf:group>

                  <xf:trigger id="prev-to-physDesc">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="physical_description" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-prov">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="prov" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="prov">

5 of 7

Data Provenance

          <xf:group ref="//tei:msDesc/tei:additional/tei:adminInfo/tei:recordHist">

              <xf:input id="metadata-creator" bind="meditor">
                <xf:label>Metadata created/edited for SGA by: </xf:label>
              </xf:input>

              <xf:textarea id="source" bind="metadata-source">
                <xf:label>Source used: </xf:label>
              </xf:textarea>

          </xf:group>

                  <xf:trigger id="prev-to-hands">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="hand_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="next-to-surrogates">
                    <xf:label>Next →</xf:label>
                    <xf:toggle case="surrogates" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="surrogates">

6 of 7

Related Items Most of the items in the Shelley-Godwin Archive have been published in facsimile editions.
              This section records information about those surrogate items.

          <xf:group ref="//tei:msDesc/tei:additional/tei:listBibl">

              <xf:textarea id="surrogate1" ref="tei:bibl">
                <xf:label>Citation for related/additional surrogate for this item: </xf:label>
              </xf:textarea>

          </xf:group>

                <xf:trigger id="next-to-manifest">
                  <xf:label> Link page images to this Item</xf:label>
                <xf:toggle case="file_manifest" ev:event="DOMActivate"></xf:toggle>
              </xf:trigger>

                  <xf:trigger id="prev-to-prov">
                    <xf:label>← Previous</xf:label>
                    <xf:toggle case="prov" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="surrogate-to-finish">
                    <xf:label>Finish</xf:label>
                    <xf:toggle case="admin_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

        </xf:case>
          <xf:case id="file_manifest">

Link Page Images Label image files with descriptive information indicating their relationship to the original item

<xf:output id="institution-code" ref="instance('control-codes')/institution-id"></xf:output>

Select an item:

Loading

Retrieving page images for this item. Please wait.

                                filename
                                link
                                label

                        <button id="jsSave" class="btn btn-primary">Save</button>

                  <xf:trigger id="prev-to-manifest">
                    <xf:label>← Back</xf:label>
                    <xf:toggle case="file_manifest" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:trigger id="img-to-finish">
                    <xf:label>Finish</xf:label>
                    <xf:toggle case="admin_info" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

          </xf:case>
          <xf:case id="admin_info">

7 of 7

Edit Changelog Help the Shelley-Godwin Archive keep track of everyone's contributions

                Timestamp
                Author
                Change(s)

          <xf:repeat id="change-repeat" nodeset="instance('itemMD')//tei:revisionDesc/tei:change">

                    <xf:output ref="./@when"></xf:output>

                    <xf:input ref="./@who">
                        <xf:action ev:event="DOMFocusIn">
                            <xf:load resource="javascript:makeEditable()"></xf:load>
                        </xf:action>
                    </xf:input>

                    <xf:input ref="."></xf:input>

            </xf:repeat>

          <xf:trigger>
                <xf:label>Add an entry</xf:label>
                    <xf:action ev:event="DOMActivate">
                    <xf:insert nodeset="//tei:revisionDesc/tei:change" at="last()" position="after"/>
                    <xf:setvalue ref="instance('control-codes')/output-filename" value="concat(lower-case(translate(instance('itemMD')//tei:msDesc/tei:msIdentifier/tei:idno, ' .,', '_')), '.xml')"></xf:setvalue>
                    <xf:setvalue ref="//tei:revisionDesc/tei:change[last()]/@when" value="now()"></xf:setvalue>
                    <xf:setvalue ref="//tei:revisionDesc/tei:change[last()]/@who" value="'Click to edit'"></xf:setvalue>
                    <xf:setvalue ref="//tei:revisionDesc/tei:change[last()]" value="'Click to edit'"></xf:setvalue>
                    </xf:action>
          </xf:trigger>

                  <xf:trigger id="prev-from-submit">
                    <xf:label>← Go back</xf:label>
                    <xf:toggle case="surrogates" ev:event="DOMActivate"></xf:toggle>
                  </xf:trigger>

                  <xf:submit id="finish" submission="save">
                    <xf:label>Save</xf:label>
                  </xf:submit>

        </xf:case>
        </xf:switch>

            Powered by Bootstrap, Font Awesome, and poetry.

let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="http://50.19.209.106/fs/xforms/xsltforms/xsltforms.xsl" '}
let $css-pi := processing-instruction css-conversion {'no'}

return ($xslt-pi,$css-pi,$form)


adding diagnostics, when missing mapping-file

xquery version "3.0";
(:~ This module provides methods to transform CQL query to XPath
: @see http://clarin.eu/fcs
: @author Matej Durco
: @since 2012-03-01
: @version 1.1
:)
module namespace cql = "http://exist-db.org/xquery/cql";

import module namespace cqlparser = "http://exist-db.org/xquery/cqlparser";
import module namespace repo-utils = "http://aac.ac.at/content_repository/utils" at  "/db/cr/repo-utils.xqm";
import module namespace diag =  "http://www.loc.gov/zing/srw/diagnostic/" at  "/db/cr/modules/diagnostics/diagnostics.xqm";

declare variable $cql:transform-doc := doc("XCQL2Xpath.xsl");

(:~ use the extension module CQLParser (using cql-java library)
to parse the expression and return the xml version  of the parse-tree
or a diagnostic, on parsing error
:)
declare function cql:cql-to-xcql($cql-expression as xs:string) {
    try {
         (: util:parse(cqlparser:parse-cql($cql-expression, "XCQL")) :)
         cqlparser:parse-cql($cql-expression, "XCQL")
    }
       catch err:XPTY0004
    {
      diag:diagnostics("query-syntax-error", ($err:code , $err:description, $err:value))
    }
    catch *
    {
      diag:diagnostics("query-syntax-error", $cql-expression)
    }
};

(:~ translate a query in CQL-syntax to a corresponding XPath
:

:
1. parsing into XCQL (XML-representation of the parsed query

:
2. and transform via XCQL2Xpath.xsl-stylesheet

:

: @returns xpath-string, or if not a string, whatever came from the parsing (if not a string, it must be a diagnostic)
:)
declare function cql:cql2xpath($cql-expression as xs:string, $x-context as xs:string)  as item() {
    let $xcql := cql:cql-to-xcql($cql-expression)
(:    return transform:transform ($xcql, $cql:transform-doc, <parameters><param name="mappings-file" value="{repo-utils:config-value('mappings')}" /></parameters>):)
    return if (not($xcql instance of element(diagnostics))) then
                transform:transform ($xcql, $cql:transform-doc, <parameters><param name="x-context" value="{$x-context}" /></parameters> )
             else $xcql

};

(:~ a version that accepts mappings-file as param, but complains if the file does not exist
:)
declare function cql:cql2xpath($cql-expression as xs:string, $x-context as xs:string, $mappings as xs:string)  as item() {
    let $xcql := if (doc-available($mappings)) then  cql:cql-to-xcql($cql-expression)
                    else diag:diagnostics("mappings-missing", $mappings)
      return if (not($xcql[1] instance of element(diagnostics))) then
                transform:transform ($xcql, $cql:transform-doc,
                    <parameters><param name="x-context" value="{$x-context}" />
                        <param name="mappings-file" value="{$mappings}" /></parameters> )
                else $xcql
};

declare function cql:xcql2xpath ($xcql as node(), $x-context as xs:string)  as xs:string {

(:    return transform:transform ($xcql, $cql:transform-doc, <parameters><param name="mappings-file" value="{repo-utils:config-value('mappings')}" /></parameters>):)
transform:transform ($xcql, $cql:transform-doc, <parameters><param name="x-context" value="{$x-context}" /></parameters> )

};

xquery version "3.0";
(:~ This module provides methods to transform CQL query to XPath
: @see http://clarin.eu/fcs
: @author Matej Durco
: @since 2012-03-01
: @version 1.1
:)
module namespace cql = "http://exist-db.org/xquery/cql";

import module namespace cqlparser = "http://exist-db.org/xquery/cqlparser";
import module namespace repo-utils = "http://aac.ac.at/content_repository/utils" at  "/db/cr/repo-utils.xqm";
import module namespace diag =  "http://www.loc.gov/zing/srw/diagnostic/" at  "/db/cr/modules/diagnostics/diagnostics.xqm";

declare variable $cql:transform-doc := doc("XCQL2Xpath.xsl");

(:~ use the extension module CQLParser (using cql-java library)
to parse the expression and return the xml version  of the parse-tree
or a diagnostic, on parsing error
:)
declare function cql:cql-to-xcql($cql-expression as xs:string) {
    try {
         (: util:parse(cqlparser:parse-cql($cql-expression, "XCQL")) :)
         cqlparser:parse-cql($cql-expression, "XCQL")
    }
       catch err:XPTY0004
    {
      diag:diagnostics("query-syntax-error", ($err:code , $err:description, $err:value))
    }
    catch *
    {
      diag:diagnostics("query-syntax-error", $cql-expression)
    }
};

(:~ translate a query in CQL-syntax to a corresponding XPath
:

:
1. parsing into XCQL (XML-representation of the parsed query

:
2. and transform via XCQL2Xpath.xsl-stylesheet

:

: @returns xpath-string, or if not a string, whatever came from the parsing (if not a string, it must be a diagnostic)
:)
declare function cql:cql2xpath($cql-expression as xs:string, $x-context as xs:string)  as item() {
    let $xcql := cql:cql-to-xcql($cql-expression)
(:    return transform:transform ($xcql, $cql:transform-doc, <parameters><param name="mappings-file" value="{repo-utils:config-value('mappings')}" /></parameters>):)
    return if (not($xcql instance of element(diagnostics))) then
                transform:transform ($xcql, $cql:transform-doc, <parameters><param name="x-context" value="{$x-context}" /></parameters> )
             else $xcql

};

(:~ a version that accepts mappings-file as param
:)
declare function cql:cql2xpath($cql-expression as xs:string, $x-context as xs:string, $mappings as xs:string)  as item() {
    let $xcql := cql:cql-to-xcql($cql-expression)
      return if (not($xcql instance of element(diagnostics))) then
                transform:transform ($xcql, $cql:transform-doc,
                    <parameters><param name="x-context" value="{$x-context}" />
                        <param name="mappings-file" value="{$mappings}" /></parameters> )
                else $xcql
};

declare function cql:xcql2xpath ($xcql as node(), $x-context as xs:string)  as xs:string {

(:    return transform:transform ($xcql, $cql:transform-doc, <parameters><param name="mappings-file" value="{repo-utils:config-value('mappings')}" /></parameters>):)
transform:transform ($xcql, $cql:transform-doc, <parameters><param name="x-context" value="{$x-context}" /></parameters> )

};


Provide defaults to the MarkLogic run.xqy parameters to make it easier to test.

(:
 : Copyright (C) 2018-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 "1.0-ml";

import module namespace sem = "http://marklogic.com/semantics" at "/MarkLogic/semantics.xqy";

declare namespace o = "http://reecedunn.co.uk/xquery/options";
declare option o:implementation "marklogic/6.0";

(:~ Run a query on a MarkLogic server.
 :
 : @param $mode Whether to "run", "profile", or "debug" the query.
 : @param $mimetype The mimetype of the query.
 : @param $module-path The path of the module to invoke, or "" for evaluated scripts.
 : @param $query The query script to evaluate, or "" for invoked modules.
 : @param $vars A map of the variable values by the variable name.
 : @param $types A map of the variable types by the variable name.
 : @param $context-value The context item for XSLT queries as an XML object.
 : @param $context-path The context item for XSLT queries as a module file.
 : @param $rdf-output-format The mimetype to format sem:triple results as, or "" to leave them as is.
 : @param $updating An updating query if "true", or a non-updating query if "false".
 : @param $server The name of the server to use, or "" to not use a server.
 : @param $database The name of the database to use, or "" to not use a database.
 : @param $module-root The path to the server modules, or "" to use the default server value.
 :
 : This script has additional logic to map the semantics of the REST/XCC API to
 : the semantics of the API implemented in the xquery-intellij-plugin to support
 : the Run/Debug Configurations of the IntelliJ IDEs. Specifically:
 :
 : 1.  If there is an error, the REST API returns a "500 Internal Error" HTTP
 :     status. This catches the error and returns an element(err:error) object.
 :
 : 2.  The variables are passed via the REST API as xs:untypedAtomic. This
 :     converts the variables to the type they were specified as in the
 :     xquery-intellij-plugin API.
 :
 : 3.  The REST API only returns the primitive type of each item.
 :)

declare variable $mode as xs:string external := "run";
declare variable $mimetype as xs:string external := "application/xquery";
declare variable $module-path as xs:string external := "";
declare variable $query as xs:string external := "()";
declare variable $vars as xs:string external := "{}";
declare variable $types as xs:string external := "{}";
declare variable $context-value as xs:string external := "";
declare variable $context-path as xs:string external := "";
declare variable $rdf-output-format as xs:string external := "";
declare variable $updating as xs:string external := "false";
declare variable $server as xs:string external := "";
declare variable $database as xs:string external := "";
declare variable $module-root as xs:string external := "";

declare function local:nullize($value) {
    if (string-length($value) eq 0) then () else $value
};

declare function local:version() {
    substring-before(xdmp:version(), "-") cast as xs:double
};

declare function local:version-minor() {
    substring-after(xdmp:version(), "-") cast as xs:double
};

declare function local:function($ref as xs:string) {
    try { xdmp:eval($ref) } catch * { () }
};

declare function local:item($path as xs:string, $value as xs:string, $type as xs:string) {
    if (string-length($value) ne 0) then
        xdmp:unquote($value)
    else if (string-length($path) ne 0) then
        doc($path)
    else
        ()
};

declare function local:cast-as($value, $type) {
    switch ($type)
    case "xs:anyURI" return $value cast as xs:anyURI
    case "xs:base64Binary" return $value cast as xs:base64Binary
    case "xs:boolean" return $value cast as xs:boolean
    case "xs:byte" return $value cast as xs:byte
    case "xs:date" return $value cast as xs:date
    case "xs:dateTime" return $value cast as xs:dateTime
    case "xs:dateTimeStamp" return $value cast as xs:dateTime (: MarkLogic does not support XML Schema 1.1 Part 2 :)
    case "xs:dayTimeDuration" return $value cast as xs:dayTimeDuration
    case "xs:decimal" return $value cast as xs:decimal
    case "xs:double" return $value cast as xs:double
    case "xs:duration" return $value cast as xs:duration
    case "xs:float" return $value cast as xs:float
    case "xs:gDay" return $value cast as xs:gDay
    case "xs:gMonth" return $value cast as xs:gMonth
    case "xs:gMonthDay" return $value cast as xs:gMonthDay
    case "xs:gYear" return $value cast as xs:gYear
    case "xs:gYearMonth" return $value cast as xs:gYearMonth
    case "xs:hexBinary" return $value cast as xs:hexBinary
    case "xs:int" return $value cast as xs:int
    case "xs:integer" return $value cast as xs:integer
    case "xs:language" return $value cast as xs:language
    case "xs:long" return $value cast as xs:long
    case "xs:negativeInteger" return $value cast as xs:negativeInteger
    case "xs:nonNegativeInteger" return $value cast as xs:nonNegativeInteger
    case "xs:nonPositiveInteger" return $value cast as xs:nonPositiveInteger
    case "xs:normalizedString" return $value cast as xs:normalizedString
    case "xs:numeric" return $value cast as xs:numeric
    case "xs:positiveInteger" return $value cast as xs:positiveInteger
    case "xs:short" return $value cast as xs:short
    case "xs:string" return $value cast as xs:string
    case "xs:time" return $value cast as xs:time
    case "xs:token" return $value cast as xs:token
    case "xs:unsignedByte" return $value cast as xs:unsignedByte
    case "xs:unsignedInt" return $value cast as xs:unsignedInt
    case "xs:unsignedLong" return $value cast as xs:unsignedLong
    case "xs:unsignedShort" return $value cast as xs:unsignedShort
    case "xs:yearMonthDuration" return $value cast as xs:yearMonthDuration
    case "xs:ENTITY" return $value cast as xs:ENTITY
    case "xs:ID" return $value cast as xs:ID
    case "xs:IDREF" return $value cast as xs:IDREF
    case "xs:Name" return $value cast as xs:Name
    case "xs:NCName" return $value cast as xs:NCName
    case "xs:NMTOKEN" return $value cast as xs:NMTOKEN
    case "xs:QName" return $value cast as xs:QName
    default return $value
};

declare function local:parse-vars($values as map:map, $types as map:map) as item()* {
    for $key in map:keys($values)
    let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
    return (xs:QName($key), $value)
};

declare function local:parse-vars-map($values as map:map, $types as map:map) as map:map {
    let $ret := map:map()
    let $_ :=
        for $key in map:keys($values)
        let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
        return map:put($ret, $key, $value)
    return $ret
};

declare function local:derived-type-name($value) {
    (: NOTE: Ordering is important here -- reverse type hierarchy :)
    typeswitch ($value)
    case function(*) return "function(*)"
    (: xs:integer derived types :)
    case xs:positiveInteger return "xs:positiveInteger"
    case xs:unsignedByte return "xs:unsignedByte"
    case xs:unsignedShort return "xs:unsignedShort"
    case xs:unsignedInt return "xs:unsignedInt"
    case xs:unsignedLong return "xs:unsignedLong"
    case xs:nonNegativeInteger return "xs:nonNegativeInteger"
    case xs:byte return "xs:byte"
    case xs:short return "xs:short"
    case xs:int return "xs:int"
    case xs:long return "xs:long"
    case xs:negativeInteger return "xs:negativeInteger"
    case xs:nonPositiveInteger return "xs:nonPositiveInteger"
    (: xs:string derived types :)
    case xs:ENTITY return "xs:ENTITY"
    case xs:IDREF return "xs:IDREF"
    case xs:ID return "xs:ID"
    case xs:NCName return "xs:NCName"
    case xs:Name return "xs:Name"
    case xs:NMTOKEN return "xs:NMTOKEN"
    case xs:language return "xs:language"
    case xs:token return "xs:token"
    case xs:normalizedString return "xs:normalizedString"
    default return () (: primitive type, so does not need the type name to be specified. :)
};

declare function local:rdf-format($mimetype) {
    switch ($mimetype)
    case "application/n-quads" return "nquad"
    case "application/n-triples" return "ntriple"
    case "application/rdf+json" return "rdfjson"
    case "application/rdf+xml" return "rdfxml"
    case "application/trig" return "trig"
    case "application/vnd.marklogic.triples+xml" return "triplexml"
    case "text/n3" return "n3"
    case "text/turtle" return "turtle"
    default return fn:error("UNSUPPORTED-RDF-FORMAT", "Unsupported RDF format: " || $mimetype)
};

declare function local:eval-options() {
    let $server := local:nullize($server) ! xdmp:server(.)
    let $database := local:nullize($database) ! xdmp:database(.)
    return <options xmlns="xdmp:eval">{
        if ($mode eq "validate") then
            <static-check>true</static-check>
        else
            (),
        if (exists($database)) then
            <database>{$database}</database>
        else
            (),
        if (exists($server)) then
            <default-collation>{xdmp:server-collation($server)}</default-collation>
        else
            (),
        let $server-coordinate-system := local:function("xdmp:server-coordinate-system#1")
        return if (exists($server) and exists($server-coordinate-system)) then
            <default-coordinate-system>{$server-coordinate-system($server)}</default-coordinate-system>
        else
            (),
        if (exists($server) and $mimetype eq "application/xquery") then
            <default-xquery-version>{xdmp:server-default-xquery-version($server)}</default-xquery-version>
        else
            (),
        if (exists($server)) then
            <modules>{xdmp:server-modules-database($server)}</modules>
        else if (string-length($module-root) ne 0) then
            <modules>0</modules> (: file system :)
        else
            (),
        if (exists($server) or string-length($module-root) ne 0) then
            <root>{(local:nullize($module-root), xdmp:server-root($server))[1]}</root>
        else
            (),
        let $major := local:version()
        let $minor := local:version-minor()
        return if ($mode = "debug") then
            if ($updating = "true") then
                fn:error(xs:QName("err:FOER0000"), "Debugging an update query is not supported by MarkLogic.")
            else
                ()
        else if ($major lt 7.0 or ($major eq 8.0 and $minor lt 7.0) or ($major eq 9.0 and $minor lt 2.0)) then
            if ($updating = "true") then
                <transaction-mode>update</transaction-mode>
            else
                <transaction-mode>query</transaction-mode>
        else
            (: MarkLogic 8.0-7 and 9.0-2 support <update> :)
            <update>{$updating}</update>
    }</options>
};

declare function local:javascript() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support server-side JavaScript"
        )
    else if (string-length($query) ne 0) then
        switch ($mode)
        case "run" return
            let $f := local:function("xdmp:javascript-eval#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
    else
        switch ($mode)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
};

declare function local:sparql-query() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sparql-update() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support SPARQL update queries"
        )
    else switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql-update($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sql() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        if (local:version() < 9.0) then
            let $f := local:function("xdmp:sql#1")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query)
            return (xdmp:elapsed-time() - $start, $ret)
        else
            let $f := local:function("xdmp:sql#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, (), $variables)
            return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:xquery() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "debug" return dbg:eval($query, $variables, $options)
        case "profile" return prof:eval($query, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:eval($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:eval($query, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "debug" return dbg:invoke($module-path, $variables, $options)
        case "profile" return prof:invoke($module-path, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:invoke($module-path, $variables, $options)
        default return ()
};

declare function local:xslt() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $input := local:item($context-path, $context-value, "application/xml")
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "profile" return prof:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        case "run" return
            let $query := xdmp:unquote($query)
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-eval($query, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "profile" return prof:xslt-invoke($module-path, $input, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-invoke($module-path, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-invoke($module-path, $input, $variables, $options)
        default return ()
};

try {
    let $retvals :=
        switch ($mimetype)
        case "application/sparql-query" return local:sparql-query()
        case "application/sparql-update" return local:sparql-update()
        case "application/sql" return local:sql()
        case "application/vnd.marklogic-javascript" return local:javascript()
        case "application/xquery" return local:xquery()
        case "application/xslt+xml" return local:xslt()
        default return ()
    let $triples := for $item in $retvals where $item instance of sem:triple return $item
    let $retvals := for $item in $retvals where not($item instance of sem:triple) return $item
    return if (exists($triples) and $rdf-output-format ne "") then
        let $rdf-output := sem:rdf-serialize($triples, local:rdf-format($rdf-output-format))
        let $_ := xdmp:add-response-header("X-Content-Type-" || (count($retvals) + 1), $rdf-output-format)
        return (
            for $retval at $i in $retvals
            let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
            return $retval,
            $rdf-output
        )
    else
        for $retval at $i in ($retvals, $triples)
        let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
        return $retval
} catch * {
    let $_ := xdmp:add-response-header("X-Derived-1", "err:error")
    return $err:additional
}

(:
 : Copyright (C) 2018-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 "1.0-ml";

import module namespace sem = "http://marklogic.com/semantics" at "/MarkLogic/semantics.xqy";

declare namespace o = "http://reecedunn.co.uk/xquery/options";
declare option o:implementation "marklogic/6.0";

(:~ Run a query on a MarkLogic server.
 :
 : @param $mode Whether to "run", "profile", or "debug" the query.
 : @param $mimetype The mimetype of the query.
 : @param $module-path The path of the module to invoke, or "" for evaluated scripts.
 : @param $query The query script to evaluate, or "" for invoked modules.
 : @param $vars A map of the variable values by the variable name.
 : @param $types A map of the variable types by the variable name.
 : @param $context-value The context item for XSLT queries as an XML object.
 : @param $context-path The context item for XSLT queries as a module file.
 : @param $rdf-output-format The mimetype to format sem:triple results as, or "" to leave them as is.
 : @param $updating An updating query if "true", or a non-updating query if "false".
 : @param $server The name of the server to use, or "" to not use a server.
 : @param $database The name of the database to use, or "" to not use a database.
 : @param $module-root The path to the server modules, or "" to use the default server value.
 :
 : This script has additional logic to map the semantics of the REST/XCC API to
 : the semantics of the API implemented in the xquery-intellij-plugin to support
 : the Run/Debug Configurations of the IntelliJ IDEs. Specifically:
 :
 : 1.  If there is an error, the REST API returns a "500 Internal Error" HTTP
 :     status. This catches the error and returns an element(err:error) object.
 :
 : 2.  The variables are passed via the REST API as xs:untypedAtomic. This
 :     converts the variables to the type they were specified as in the
 :     xquery-intellij-plugin API.
 :
 : 3.  The REST API only returns the primitive type of each item.
 :)

declare variable $mode as xs:string external;
declare variable $mimetype as xs:string external;
declare variable $module-path as xs:string external;
declare variable $query as xs:string external;
declare variable $vars as xs:string external;
declare variable $types as xs:string external;
declare variable $context-value as xs:string external;
declare variable $context-path as xs:string external;
declare variable $rdf-output-format as xs:string external;
declare variable $updating as xs:string external;
declare variable $server as xs:string external;
declare variable $database as xs:string external;
declare variable $module-root as xs:string external;

declare function local:nullize($value) {
    if (string-length($value) eq 0) then () else $value
};

declare function local:version() {
    substring-before(xdmp:version(), "-") cast as xs:double
};

declare function local:version-minor() {
    substring-after(xdmp:version(), "-") cast as xs:double
};

declare function local:function($ref as xs:string) {
    try { xdmp:eval($ref) } catch * { () }
};

declare function local:item($path as xs:string, $value as xs:string, $type as xs:string) {
    if (string-length($value) ne 0) then
        xdmp:unquote($value)
    else if (string-length($path) ne 0) then
        doc($path)
    else
        ()
};

declare function local:cast-as($value, $type) {
    switch ($type)
    case "xs:anyURI" return $value cast as xs:anyURI
    case "xs:base64Binary" return $value cast as xs:base64Binary
    case "xs:boolean" return $value cast as xs:boolean
    case "xs:byte" return $value cast as xs:byte
    case "xs:date" return $value cast as xs:date
    case "xs:dateTime" return $value cast as xs:dateTime
    case "xs:dateTimeStamp" return $value cast as xs:dateTime (: MarkLogic does not support XML Schema 1.1 Part 2 :)
    case "xs:dayTimeDuration" return $value cast as xs:dayTimeDuration
    case "xs:decimal" return $value cast as xs:decimal
    case "xs:double" return $value cast as xs:double
    case "xs:duration" return $value cast as xs:duration
    case "xs:float" return $value cast as xs:float
    case "xs:gDay" return $value cast as xs:gDay
    case "xs:gMonth" return $value cast as xs:gMonth
    case "xs:gMonthDay" return $value cast as xs:gMonthDay
    case "xs:gYear" return $value cast as xs:gYear
    case "xs:gYearMonth" return $value cast as xs:gYearMonth
    case "xs:hexBinary" return $value cast as xs:hexBinary
    case "xs:int" return $value cast as xs:int
    case "xs:integer" return $value cast as xs:integer
    case "xs:language" return $value cast as xs:language
    case "xs:long" return $value cast as xs:long
    case "xs:negativeInteger" return $value cast as xs:negativeInteger
    case "xs:nonNegativeInteger" return $value cast as xs:nonNegativeInteger
    case "xs:nonPositiveInteger" return $value cast as xs:nonPositiveInteger
    case "xs:normalizedString" return $value cast as xs:normalizedString
    case "xs:numeric" return $value cast as xs:numeric
    case "xs:positiveInteger" return $value cast as xs:positiveInteger
    case "xs:short" return $value cast as xs:short
    case "xs:string" return $value cast as xs:string
    case "xs:time" return $value cast as xs:time
    case "xs:token" return $value cast as xs:token
    case "xs:unsignedByte" return $value cast as xs:unsignedByte
    case "xs:unsignedInt" return $value cast as xs:unsignedInt
    case "xs:unsignedLong" return $value cast as xs:unsignedLong
    case "xs:unsignedShort" return $value cast as xs:unsignedShort
    case "xs:yearMonthDuration" return $value cast as xs:yearMonthDuration
    case "xs:ENTITY" return $value cast as xs:ENTITY
    case "xs:ID" return $value cast as xs:ID
    case "xs:IDREF" return $value cast as xs:IDREF
    case "xs:Name" return $value cast as xs:Name
    case "xs:NCName" return $value cast as xs:NCName
    case "xs:NMTOKEN" return $value cast as xs:NMTOKEN
    case "xs:QName" return $value cast as xs:QName
    default return $value
};

declare function local:parse-vars($values as map:map, $types as map:map) as item()* {
    for $key in map:keys($values)
    let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
    return (xs:QName($key), $value)
};

declare function local:parse-vars-map($values as map:map, $types as map:map) as map:map {
    let $ret := map:map()
    let $_ :=
        for $key in map:keys($values)
        let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
        return map:put($ret, $key, $value)
    return $ret
};

declare function local:derived-type-name($value) {
    (: NOTE: Ordering is important here -- reverse type hierarchy :)
    typeswitch ($value)
    case function(*) return "function(*)"
    (: xs:integer derived types :)
    case xs:positiveInteger return "xs:positiveInteger"
    case xs:unsignedByte return "xs:unsignedByte"
    case xs:unsignedShort return "xs:unsignedShort"
    case xs:unsignedInt return "xs:unsignedInt"
    case xs:unsignedLong return "xs:unsignedLong"
    case xs:nonNegativeInteger return "xs:nonNegativeInteger"
    case xs:byte return "xs:byte"
    case xs:short return "xs:short"
    case xs:int return "xs:int"
    case xs:long return "xs:long"
    case xs:negativeInteger return "xs:negativeInteger"
    case xs:nonPositiveInteger return "xs:nonPositiveInteger"
    (: xs:string derived types :)
    case xs:ENTITY return "xs:ENTITY"
    case xs:IDREF return "xs:IDREF"
    case xs:ID return "xs:ID"
    case xs:NCName return "xs:NCName"
    case xs:Name return "xs:Name"
    case xs:NMTOKEN return "xs:NMTOKEN"
    case xs:language return "xs:language"
    case xs:token return "xs:token"
    case xs:normalizedString return "xs:normalizedString"
    default return () (: primitive type, so does not need the type name to be specified. :)
};

declare function local:rdf-format($mimetype) {
    switch ($mimetype)
    case "application/n-quads" return "nquad"
    case "application/n-triples" return "ntriple"
    case "application/rdf+json" return "rdfjson"
    case "application/rdf+xml" return "rdfxml"
    case "application/trig" return "trig"
    case "application/vnd.marklogic.triples+xml" return "triplexml"
    case "text/n3" return "n3"
    case "text/turtle" return "turtle"
    default return fn:error("UNSUPPORTED-RDF-FORMAT", "Unsupported RDF format: " || $mimetype)
};

declare function local:eval-options() {
    let $server := local:nullize($server) ! xdmp:server(.)
    let $database := local:nullize($database) ! xdmp:database(.)
    return <options xmlns="xdmp:eval">{
        if ($mode eq "validate") then
            <static-check>true</static-check>
        else
            (),
        if (exists($database)) then
            <database>{$database}</database>
        else
            (),
        if (exists($server)) then
            <default-collation>{xdmp:server-collation($server)}</default-collation>
        else
            (),
        let $server-coordinate-system := local:function("xdmp:server-coordinate-system#1")
        return if (exists($server) and exists($server-coordinate-system)) then
            <default-coordinate-system>{$server-coordinate-system($server)}</default-coordinate-system>
        else
            (),
        if (exists($server) and $mimetype eq "application/xquery") then
            <default-xquery-version>{xdmp:server-default-xquery-version($server)}</default-xquery-version>
        else
            (),
        if (exists($server)) then
            <modules>{xdmp:server-modules-database($server)}</modules>
        else if (string-length($module-root) ne 0) then
            <modules>0</modules> (: file system :)
        else
            (),
        if (exists($server) or string-length($module-root) ne 0) then
            <root>{(local:nullize($module-root), xdmp:server-root($server))[1]}</root>
        else
            (),
        let $major := local:version()
        let $minor := local:version-minor()
        return if ($mode = "debug") then
            if ($updating = "true") then
                fn:error(xs:QName("err:FOER0000"), "Debugging an update query is not supported by MarkLogic.")
            else
                ()
        else if ($major lt 7.0 or ($major eq 8.0 and $minor lt 7.0) or ($major eq 9.0 and $minor lt 2.0)) then
            if ($updating = "true") then
                <transaction-mode>update</transaction-mode>
            else
                <transaction-mode>query</transaction-mode>
        else
            (: MarkLogic 8.0-7 and 9.0-2 support <update> :)
            <update>{$updating}</update>
    }</options>
};

declare function local:javascript() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support server-side JavaScript"
        )
    else if (string-length($query) ne 0) then
        switch ($mode)
        case "run" return
            let $f := local:function("xdmp:javascript-eval#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
    else
        switch ($mode)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
};

declare function local:sparql-query() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sparql-update() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support SPARQL update queries"
        )
    else switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql-update($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sql() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        if (local:version() < 9.0) then
            let $f := local:function("xdmp:sql#1")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query)
            return (xdmp:elapsed-time() - $start, $ret)
        else
            let $f := local:function("xdmp:sql#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, (), $variables)
            return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:xquery() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "debug" return dbg:eval($query, $variables, $options)
        case "profile" return prof:eval($query, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:eval($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:eval($query, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "debug" return dbg:invoke($module-path, $variables, $options)
        case "profile" return prof:invoke($module-path, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:invoke($module-path, $variables, $options)
        default return ()
};

declare function local:xslt() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $input := local:item($context-path, $context-value, "application/xml")
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "profile" return prof:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        case "run" return
            let $query := xdmp:unquote($query)
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-eval($query, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "profile" return prof:xslt-invoke($module-path, $input, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-invoke($module-path, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-invoke($module-path, $input, $variables, $options)
        default return ()
};

try {
    let $retvals :=
        switch ($mimetype)
        case "application/sparql-query" return local:sparql-query()
        case "application/sparql-update" return local:sparql-update()
        case "application/sql" return local:sql()
        case "application/vnd.marklogic-javascript" return local:javascript()
        case "application/xquery" return local:xquery()
        case "application/xslt+xml" return local:xslt()
        default return ()
    let $triples := for $item in $retvals where $item instance of sem:triple return $item
    let $retvals := for $item in $retvals where not($item instance of sem:triple) return $item
    return if (exists($triples) and $rdf-output-format ne "") then
        let $rdf-output := sem:rdf-serialize($triples, local:rdf-format($rdf-output-format))
        let $_ := xdmp:add-response-header("X-Content-Type-" || (count($retvals) + 1), $rdf-output-format)
        return (
            for $retval at $i in $retvals
            let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
            return $retval,
            $rdf-output
        )
    else
        for $retval at $i in ($retvals, $triples)
        let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
        return $retval
} catch * {
    let $_ := xdmp:add-response-header("X-Derived-1", "err:error")
    return $err:additional
}