MarkLogic: Create a local:eval-root helper function.

(:
 : Copyright (C) 2018-2021 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 namespace eval = "xdmp:eval";

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 variable $content-type-header-prefix as xs:string external := "X-Content-Type-";
declare variable $derived-type-header-prefix as xs:string external := "X-Derived-";

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:server-coordinate-system($server as xs:unsignedLong?) {
    let $server-coordinate-system := local:function("xdmp:server-coordinate-system#1")
    return if (exists($server) and exists($server-coordinate-system)) then
        $server-coordinate-system($server)
    else
        ()
};

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:use-modules-root($server-root) {
    exists($server-root) and string-length($module-root) ne 0 and not($module-root eq $server-root)
};

declare function local:eval-static-check($static-check as xs:boolean) {
    <eval:static-check>{$static-check}</eval:static-check>
};

declare function local:eval-database($database as xs:unsignedLong?) {
    if (exists($database)) then
        <eval:database>{$database}</eval:database>
    else
        ()
};

declare function local:eval-default-collation($collation as xs:string?) {
    if (exists($collation)) then
        <eval:default-collation>{$collation}</eval:default-collation>
    else
        ()
};

declare function local:eval-default-coordinate-system($coordinate-system as xs:string?) {
    if (exists($coordinate-system)) then
        <eval:default-coordinate-system>{$coordinate-system}</eval:default-coordinate-system>
    else
        ()
};

declare function local:eval-default-xquery-version($xquery-version as xs:string?) {
    if (exists($xquery-version) and $mimetype eq "application/xquery") then
        <eval:default-xquery-version>{$xquery-version}</eval:default-xquery-version>
    else
        ()
};

declare function local:eval-modules($database as xs:unsignedLong?) {
    if (exists($database)) then
        <eval:modules>{$database}</eval:modules>
    else
        ()
};

declare function local:eval-root($root as xs:string?) {
    if (exists($root)) then
        <eval:root>{$root}</eval:root>
    else
        ()
};

declare function local:eval-options() {
    let $server := local:nullize($server) ! xdmp:server(.)
    let $database :=
        let $id := local:nullize($database) ! xdmp:database(.)
        return if (exists($id)) then
            $id
        else if (exists($server)) then
            xdmp:server-database($server)
        else
            ()
    return <eval:options>{
        local:eval-static-check($mode eq "validate"),
        local:eval-database($database),
        local:eval-default-collation($server ! xdmp:server-collation(.)),
        local:eval-default-coordinate-system($server ! local:server-coordinate-system(.)),
        local:eval-default-xquery-version($server ! xdmp:server-default-xquery-version($server)),
        let $server-root := $server ! xdmp:server-root(.)
        return if (local:use-modules-root($server-root)) then
            (local:eval-modules(0), local:eval-root($module-root)) (: file system :)
        else
            (), (: use the default server settings :)
        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
                <eval:transaction-mode>update</eval:transaction-mode>
            else
                <eval:transaction-mode>query</eval:transaction-mode>
        else
            (: MarkLogic 8.0-7 and 9.0-2 support <update> :)
            <eval:update>{$updating}</eval:update>
    }</eval: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($content-type-header-prefix || (count($retvals) + 1), $rdf-output-format)
        return (
            for $retval at $i in $retvals
            let $_ := local:derived-type-name($retval) ! xdmp:add-response-header($derived-type-header-prefix || $i, .)
            return $retval,
            $rdf-output
        )
    else
        for $retval at $i in ($retvals, $triples)
        let $_ := local:derived-type-name($retval) ! xdmp:add-response-header($derived-type-header-prefix || $i, .)
        return $retval
} catch * {
    let $_ := xdmp:add-response-header($derived-type-header-prefix || 1, "err:error")
    return $err:additional
}

(:
 : Copyright (C) 2018-2021 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 namespace eval = "xdmp:eval";

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 variable $content-type-header-prefix as xs:string external := "X-Content-Type-";
declare variable $derived-type-header-prefix as xs:string external := "X-Derived-";

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:server-coordinate-system($server as xs:unsignedLong?) {
    let $server-coordinate-system := local:function("xdmp:server-coordinate-system#1")
    return if (exists($server) and exists($server-coordinate-system)) then
        $server-coordinate-system($server)
    else
        ()
};

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:use-modules-root($server-root) {
    exists($server-root) and string-length($module-root) ne 0 and not($module-root eq $server-root)
};

declare function local:eval-static-check($static-check as xs:boolean) {
    <eval:static-check>{$static-check}</eval:static-check>
};

declare function local:eval-database($database as xs:unsignedLong?) {
    if (exists($database)) then
        <eval:database>{$database}</eval:database>
    else
        ()
};

declare function local:eval-default-collation($collation as xs:string?) {
    if (exists($collation)) then
        <eval:default-collation>{$collation}</eval:default-collation>
    else
        ()
};

declare function local:eval-default-coordinate-system($coordinate-system as xs:string?) {
    if (exists($coordinate-system)) then
        <eval:default-coordinate-system>{$coordinate-system}</eval:default-coordinate-system>
    else
        ()
};

declare function local:eval-default-xquery-version($xquery-version as xs:string?) {
    if (exists($xquery-version) and $mimetype eq "application/xquery") then
        <eval:default-xquery-version>{$xquery-version}</eval:default-xquery-version>
    else
        ()
};

declare function local:eval-modules($database as xs:unsignedLong?) {
    if (exists($database)) then
        <eval:modules>{$database}</eval:modules>
    else
        ()
};

declare function local:eval-options() {
    let $server := local:nullize($server) ! xdmp:server(.)
    let $database :=
        let $id := local:nullize($database) ! xdmp:database(.)
        return if (exists($id)) then
            $id
        else if (exists($server)) then
            xdmp:server-database($server)
        else
            ()
    return <eval:options>{
        local:eval-static-check($mode eq "validate"),
        local:eval-database($database),
        local:eval-default-collation($server ! xdmp:server-collation(.)),
        local:eval-default-coordinate-system($server ! local:server-coordinate-system(.)),
        local:eval-default-xquery-version($server ! xdmp:server-default-xquery-version($server)),
        let $server-root := $server ! xdmp:server-root(.)
        return if (local:use-modules-root($server-root)) then
            (local:eval-modules(0), <eval:root>{$module-root}</eval:root>) (: file system :)
        else
            (), (: use the default server settings :)
        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
                <eval:transaction-mode>update</eval:transaction-mode>
            else
                <eval:transaction-mode>query</eval:transaction-mode>
        else
            (: MarkLogic 8.0-7 and 9.0-2 support <update> :)
            <eval:update>{$updating}</eval:update>
    }</eval: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($content-type-header-prefix || (count($retvals) + 1), $rdf-output-format)
        return (
            for $retval at $i in $retvals
            let $_ := local:derived-type-name($retval) ! xdmp:add-response-header($derived-type-header-prefix || $i, .)
            return $retval,
            $rdf-output
        )
    else
        for $retval at $i in ($retvals, $triples)
        let $_ := local:derived-type-name($retval) ! xdmp:add-response-header($derived-type-header-prefix || $i, .)
        return $retval
} catch * {
    let $_ := xdmp:add-response-header($derived-type-header-prefix || 1, "err:error")
    return $err:additional
}


Fixed last issue in retrieving XQTS results. Usable again!

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

declare option exist:serialize "method=xml indent=yes";
declare option exist:output-size-limit "-1";
<test-suite-result xmlns="http://www.w3.org/2005/02/query-test-XQTSResult"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

        <implementation name="{util:system-property("product-version")}" version="{util:system-property("product-build")}" anonymous-result-column="false">

                <organization name="eXist Open Source native XML database"  website="http://www.exist-db.org" anonymous="false"/>

                <submittor name="eXist XQTS testsuite runner" email="me@not.com"/>

                <description>

eXist is an Open Source native XML database featuring efficient,
                        index-based XQuery processing, automatic indexing, extensions for full-text
                        search,  XUpdate  support and tight integration with existing XML development tools.

                </description>

                <implementation-defined-items>

                </implementation-defined-items>

                <features>
{ for $feature in doc('/db/XQTS/XQTSCatalog.xml')/*:test-suite/*:features/*:feature
							return
							<feature name="{$feature/@name}" supported="true"/>
						}</features>

                <context-properties>

                </context-properties>

        </implementation>

        <syntax>XQuery</syntax>

        <test-run dateRun="{fn:current-dateTime()}">
                <test-suite version="{doc('/db/XQTS/XQTSCatalog.xml')/*:test-suite/@version}"/>
                <transformation>

No unusual transformations.

                </transformation>
                <comparison>

No unusual comparisons

                </comparison>
                <otherComments>

The test run took n seconds.

                </otherComments>
        </test-run>

{
    for $case in fn:collection(  '/db/XQTS' )//*:test-case
    return <test-case name="{$case/@name}" result="{$case/@result}"  />
}
</test-suite-result>

declare option exist:serialize "method=xml indent=yes";
declare option exist:output-size-limit "-1";
<test-suite-result>

        <implementation name="{util:system-property("product-version")}" version="{util:system-property("product-build")}" anonymous-result-column="false">

                <organization name="eXist Open Source native XML database"  website="http://www.exist-db.org" anonymous="false"/>

                <submittor name="eXist XQTS testsuite runner" email="me@not.com"/>

                <description>

eXist is an Open Source native XML database featuring efficient,
                        index-based XQuery processing, automatic indexing, extensions for full-text
                        search,  XUpdate  support and tight integration with existing XML development tools.

                </description>

                <implementation-defined-items>

                </implementation-defined-items>

                <features>
{ for $feature in doc('/db/XQTS/XQTSCatalog.xml')/*:test-suite/*:features/*:feature
							return
							<feature name="{$feature/@name}" supported="true"/>
						}</features>

                <context-properties>

                </context-properties>

        </implementation>

        <syntax>XQuery</syntax>

        <test-run dateRun="{fn:current-dateTime()}">
                <test-suite version="{doc('/db/XQTS/XQTSCatalog.xml')/*:test-suite/@version}"/>
                <transformation>

No unusual transformations.

                </transformation>
                <comparison>

No unusual comparisons

                </comparison>
                <otherComments>

The test run took n seconds.

                </otherComments>
        </test-run>

{
    for $case in fn:collection(  '/db/XQTS' )//test-case
    return <test-case name="{$case/@name}" result="{$case/@result}"  />
}
</test-suite-result>


Modify argument type change comment for BaseX convert:bytes-to-hex function.

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

import module namespace a = "http://reecedunn.co.uk/xquery/annotations" at "res://reecedunn.co.uk/xquery/annotations.xqy";

declare %a:since("basex", "7.3") function convert:binary-to-string($bytes as xs:anyAtomicType) as xs:string external;
declare %a:since("basex", "7.3") function convert:binary-to-string($bytes as xs:anyAtomicType, $encoding as xs:string) as xs:string external;
declare %a:since("basex", "8.5") function convert:binary-to-string($bytes as xs:anyAtomicType, $encoding as xs:string, $fallback as xs:boolean) as xs:string external;
declare %a:since("basex", "7.3") function convert:string-to-base64($string as xs:string) as xs:base64Binary external;
declare %a:since("basex", "7.3") function convert:string-to-base64($string as xs:string, $encoding as xs:string) as xs:base64Binary external;
declare %a:since("basex", "7.3") function convert:string-to-hex($string as xs:string) as xs:hexBinary external;
declare %a:since("basex", "7.3") function convert:string-to-hex($string as xs:string, $encoding as xs:string) as xs:hexBinary external;
declare %a:since("basex", "7.3") %a:until("basex", "9.0") %a:see-also("basex", "9.0", "convert:integers-to-base64") function convert:bytes-to-base64($input as xs:byte*) as xs:base64Binary (: $input as [7.3]xs:byte [8.7]xs:integer :) external;
declare %a:since("basex", "9.0") function convert:integers-to-base64($integers as xs:integer*) as xs:base64Binary external;
declare %a:since("basex", "7.3") %a:until("basex", "9.0") %a:see-also("basex", "9.0", "convert:integers-to-hex") function convert:bytes-to-hex($input as xs:integer*) as xs:hexBinary (: $input as [7.3]xs:byte [8.7]xs:integer :) external;
declare %a:since("basex", "7.3") function convert:integers-to-hex($integers as xs:integer*) as xs:hexBinary external;
declare %a:since("basex", "9.0") function convert:binary-to-integers($binary as xs:anyAtomicType) as xs:integer* external;
declare %a:since("basex", "7.3") function convert:binary-to-bytes($binary as xs:anyAtomicType) as xs:byte* external;
declare %a:since("basex", "7.3") function convert:integer-to-base($number as xs:integer, $base as xs:integer) as xs:string external;
declare %a:since("basex", "7.3") function convert:integer-from-base($string as xs:string, $base as xs:integer) as xs:integer external;
declare %a:since("basex", "7.5") function convert:integer-to-dateTime($milliseconds as xs:integer) as xs:dateTime external;
declare %a:since("basex", "7.5") function convert:dateTime-to-integer($dateTime as xs:dateTime) as xs:integer external;
declare %a:since("basex", "7.5") function convert:integer-to-dayTime($milliseconds as xs:integer) as xs:dayTimeDuration external;
declare %a:since("basex", "7.5") function convert:dayTime-to-integer($dayTime as xs:dayTimeDuration) as xs:integer external;

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

import module namespace a = "http://reecedunn.co.uk/xquery/annotations" at "res://reecedunn.co.uk/xquery/annotations.xqy";

declare %a:since("basex", "7.3") function convert:binary-to-string($bytes as xs:anyAtomicType) as xs:string external;
declare %a:since("basex", "7.3") function convert:binary-to-string($bytes as xs:anyAtomicType, $encoding as xs:string) as xs:string external;
declare %a:since("basex", "8.5") function convert:binary-to-string($bytes as xs:anyAtomicType, $encoding as xs:string, $fallback as xs:boolean) as xs:string external;
declare %a:since("basex", "7.3") function convert:string-to-base64($string as xs:string) as xs:base64Binary external;
declare %a:since("basex", "7.3") function convert:string-to-base64($string as xs:string, $encoding as xs:string) as xs:base64Binary external;
declare %a:since("basex", "7.3") function convert:string-to-hex($string as xs:string) as xs:hexBinary external;
declare %a:since("basex", "7.3") function convert:string-to-hex($string as xs:string, $encoding as xs:string) as xs:hexBinary external;
declare %a:since("basex", "7.3") %a:until("basex", "9.0") %a:see-also("basex", "9.0", "convert:integers-to-base64") function convert:bytes-to-base64($input as xs:byte*) as xs:base64Binary (: $input [7.3]xs:byte [8.7]xs:integer :) external;
declare %a:since("basex", "9.0") function convert:integers-to-base64($integers as xs:integer*) as xs:base64Binary external;
declare %a:since("basex", "7.3") %a:until("basex", "9.0") %a:see-also("basex", "9.0", "convert:integers-to-hex") function convert:bytes-to-hex($input as xs:integer*) as xs:hexBinary (: $input [7.3]xs:byte [8.7]xs:integer :) external;
declare %a:since("basex", "7.3") function convert:integers-to-hex($integers as xs:integer*) as xs:hexBinary external;
declare %a:since("basex", "9.0") function convert:binary-to-integers($binary as xs:anyAtomicType) as xs:integer* external;
declare %a:since("basex", "7.3") function convert:binary-to-bytes($binary as xs:anyAtomicType) as xs:byte* external;
declare %a:since("basex", "7.3") function convert:integer-to-base($number as xs:integer, $base as xs:integer) as xs:string external;
declare %a:since("basex", "7.3") function convert:integer-from-base($string as xs:string, $base as xs:integer) as xs:integer external;
declare %a:since("basex", "7.5") function convert:integer-to-dateTime($milliseconds as xs:integer) as xs:dateTime external;
declare %a:since("basex", "7.5") function convert:dateTime-to-integer($dateTime as xs:dateTime) as xs:integer external;
declare %a:since("basex", "7.5") function convert:integer-to-dayTime($milliseconds as xs:integer) as xs:dayTimeDuration external;
declare %a:since("basex", "7.5") function convert:dayTime-to-integer($dayTime as xs:dayTimeDuration) as xs:integer external;


Allow literal, link-based and expression based resources within one collection

xquery version "3.0";

(:~
 : XTriples
 :
 : A generic webservice to extract rdf statements from XML resources
 :
 : @author Torsten Schrade
 : @email <Torsten.Schrade@adwmainz.de>
 : @version 1.2.0
 : @licence MIT
 :
 : Main module containing the webservice
:)

(: ### PROLOGUE ### :)

import module namespace functx="http://www.functx.com";

declare namespace xtriples = "http://xtriples.spatialhumanities.de/";

(: ### GLOBAL VARIABLES :)

declare variable $setConfiguration := xtriples:getConfiguration();
declare variable $setFormat := xtriples:getFormat();
declare variable $xtriplesWebserviceURL := "http://xtriples.spatialhumanities.de/";
declare variable $any23WebserviceURL := "http://any23.org/";
declare variable $redeferWebserviceURL := "http://rhizomik.net/redefer-services/";
declare variable $redeferWebserviceRulesURL := "http://rhizomik.net:8080/html/redefer/rdf2svg/showgraph.jrule";

(: ### CONFIGURATION FUNCTIONS ### :)

(: retrieves the XTriples configuration from GET or POST :)
declare function xtriples:getConfiguration() {

	(: checks GET/POST for a configuration sent with the configuration parameter :)
	let $submittedConfiguration := request:get-parameter("configuration", "")
	(: checks the request body for a configuration :)
	let $submittedConfigurationPOSTBody := request:get-data()

	let $setConfiguration :=
		(: case 1 - URI to a configuration file was sent - GET request :)
		if (substring($submittedConfiguration, 1, 4) = "http") then
			fn:doc(xs:anyURI($submittedConfiguration))
		(: case 2 - nothing submitted with a configuration parameter, use request body - direct POST request :)
		else if ($submittedConfiguration = "") then
			if ($submittedConfigurationPOSTBody) then
				$submittedConfigurationPOSTBody
			(: if no configuration could be retrieved to this point use a standard config that issues an error :)
			else
				fn:doc("configuration.xml")
		(: case 3 - full configuration sent with the configuration parameter - form style POST request :)
		else util:parse($submittedConfiguration)

	return $setConfiguration
};

(: gets and sets the return format :)
declare function xtriples:getFormat() {

	let $HEADER := request:get-header("format")
	let $GET := request:get-parameter("format", "rdf")
	let $setFormat :=
		if ($HEADER != "") then $HEADER
		else $GET

	return $setFormat
};

(: ### CRAWLING FUNCTIONS ### :)

(: evaluates an XPATH expression in curly braces within a (URI) string and returns the string :)
declare function xtriples:expressionBasedUriResolver($uri as xs:string, $currentResource as node()) as xs:string {

	let $expression := concat('$currentResource', substring-after(substring-before($uri, "}"), "{"))
	let $result := if (xtriples:expressionSanityCheck($expression) = true()) then
		try { util:eval($expression) } catch * { $err:description }
		else ""
	let $uriWithSubstitution := replace($uri, "\{.*\}", $result)

	return $uriWithSubstitution
};

(: evaluates a possible XPATH expression in curly braces within a document string and retrieves the document :)
declare function xtriples:expressionBasedResourceResolver($collection as node()*, $resource as node()*) as item()* {

	let $collectionContent := if (fn:doc-available($collection/@uri)) then fn:doc($collection/@uri) else ""

	let $resourcesURI := string($resource/@uri)
	let $resourcesExpression := concat('$collectionContent', substring-after(substring-before($resourcesURI, "}"), "{"))
	let $resourcesNodes := if (xtriples:expressionSanityCheck($resourcesExpression) = true()) then
		try { util:eval($resourcesExpression) } catch * { $err:description }
		else $resourcesExpression

	let $resources :=
		for $resource at $index in $resourcesNodes
			return
				if ($resource instance of element()) then
					$resource
				else
					element {"resource"} {
						attribute {"uri"} {replace($resourcesURI, "\{.*\}", $resource)}
					}
	return $resources
};

(: gets resources from a collection, possibly expression based :)
declare function xtriples:getResources($collection as node()*) as item()* {

	let $resources :=
		for $resource in $collection/resource
		return
			if (matches($resource/@uri, "\{") and matches($resource/@uri, "\}")) then
				xtriples:expressionBasedResourceResolver($collection, $resource)
			else $resource

	return $resources
};

(: ### EXTRACTION FUNCTIONS ### :)

(:
Separate relatively similar functions are used for subject, predicate and object extraction.
This keeps the query simpler even if it repeats some code. At the same time it opens up the possibility
to treat statement part extractions different in future versions (as it is already the case with subject/object
vs predicate extraction routines.
:)

(: safety filter for XPATH/XQuery expressions - dissallows executions from dangerous/not needed function namespaces :)
declare function xtriples:expressionSanityCheck($expression as xs:string) as xs:boolean {

	let $pattern := "((fn:.*\(.*\))|(doc*\(.*\))|(collection*\(.*\))|(v:.*\(.*\))|(backups:.*\(.*\))|(compression:.*\(.*\))|(contentextraction:.*\(.*\))|(counter:.*\(.*\))|(cqlparser:.*\(.*\))|(datetime:.*\(.*\))|(examples:.*\(.*\))|(exi:.*\(.*\))|(file:.*\(.*\))|(httpclient:.*\(.*\))|(image:.*\(.*\))|(inspection:.*\(.*\))|(jindi:.*\(.*\))|(kwic:.*\(.*\))|(lucene:.*\(.*\))|(mail:.*\(.*\))|(math:.*\(.*\))|(ngram:.*\(.*\))|(repo:.*\(.*\))|(request:.*\(.*\))|(response:.*\(.*\))|(scheduler:.*\(.*\))|(securitymanager:.*\(.*\))|(sequences:.*\(.*\))|(session:.*\(.*\))|(sort:.*\(.*\))|(sql:.*\(.*\))|(system:.*\(.*\))|(testing:.*\(.*\))|(text:.*\(.*\))|(transform:.*\(.*\))|(util:.*\(.*\))|(validation:.*\(.*\))|(xmldb:.*\(.*\))|(xmldiff:.*\(.*\))|(xqdoc:.*\(.*\))|(xslfo:.*\(.*\))|(config:.*\(.*\))|(docbook:.*\(.*\))|(app:.*\(.*\))|(dash:.*\(.*\))|(service:.*\(.*\))|(login-helper:.*\(.*\))|(packages:.*\(.*\))|(service:.*\(.*\))|(usermanager:.*\(.*\))|(demo:.*\(.*\))|(cex:.*\(.*\))|(ex:.*\(.*\))|(apputil:.*\(.*\))|(site:.*\(.*\))|(pretty:.*\(.*\))|(date:.*\(.*\))|(tei2:.*\(.*\))|(dbutil:.*\(.*\))|(docs:.*\(.*\))|(dq:.*\(.*\))|(review:.*\(.*\))|(epub:.*\(.*\))|(l18n:.*\(.*\))|(intl:.*\(.*\))|(restxq:.*\(.*\))|(tmpl:.*\(.*\))|(templates:.*\(.*\))|(trigger:.*\(.*\))|(jsjson:.*\(.*\))|(xqdoc:.*\(.*\)))"
	let $check := matches($expression, $pattern)

	return if ($check = true()) then false() else true()
};

(: extracts subject statements from the current resource :)
declare function xtriples:extractSubjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/subject/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))
			else ""
		else ""

	let $subjectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/subject))
		else concat('$currentResource', string($statement/subject))

	let $subjectExpression :=
		if (matches($subjectExpressionConcatenation, "$repeatIndex")) then replace($subjectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $subjectExpressionConcatenation

	let $subjectNodes := if (xtriples:expressionSanityCheck($subjectExpression) = true()) then
		try { util:eval($subjectExpression) } catch * { $err:description }
		else $subjectExpression

	let $subjects :=
		for $subjectValue in $subjectNodes
		return functx:copy-attributes(<subject>{string($subjectValue)}</subject>, $statement/subject)

	return $subjects
};

(: extracts predicate statements from the current resource :)
declare function xtriples:extractPredicate($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/predicate/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))
			else ""
		else ""

	let $predicateExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/predicate))
		else concat('$currentResource', string($statement/predicate))

	let $predicateExpression :=
		if (matches($predicateExpressionConcatenation, "$repeatIndex")) then replace($predicateExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $predicateExpressionConcatenation

	let $predicateValue := if (xtriples:expressionSanityCheck($predicateExpression) = true()) then
		try { string(util:eval($predicateExpression)) } catch * { $err:description }
		else $predicateExpression

	let $predicate := <predicate prefix="{$statement/predicate/@prefix}">{string($predicateValue)}</predicate>

	return $predicate
};

(: extracts object statements from the current resource :)
declare function xtriples:extractObjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/object/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))
			else ""
		else ""

	let $objectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/object))
		else concat("$currentResource", string($statement/object))

	let $objectExpression :=
		if (matches($objectExpressionConcatenation, "$repeatIndex")) then replace($objectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $objectExpressionConcatenation

	let $objectNodes := if (xtriples:expressionSanityCheck($objectExpression) = true()) then
		try { util:eval($objectExpression) } catch * { $err:description }
		else $objectExpression

	let $objects :=
		for $objectValue in $objectNodes
		return functx:copy-attributes(<object>{string($objectValue)}</object>, $statement/object)

	return $objects
};

(: extracts expressions in prepend/append attributes from the current resource :)
declare function xtriples:extractPrependAppend($currentResource as node(), $expressionString as xs:string*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as xs:string* {
	let $expression := substring-after(substring-before($expressionString, "}"), "{")
	let $result :=
		if ($expression and xtriples:expressionSanityCheck($expression) = true()) then
			try {
				replace($expressionString, "\{.*\}", util:eval(concat("$currentResource", $expression)))
			} catch * { $err:description }
		else $expressionString
	return $result
};

(: XTriples core function - evaluates all configured statements for the current resource :)
declare function xtriples:extractTriples($currentResource as node(), $resourceIndex as xs:integer, $configuration as node()*) as item()* {

	(: set the content of the current resource :)
	let $currentResource :=
		(: it can be a resource tag with an URI :)
		if (xs:anyURI($currentResource/@uri)) then
			fn:doc($currentResource/@uri)
		(: or a resource tag with children :)
		else $currentResource/*

	(: start statement pattern extraction :)
	for $statement in $configuration//triples/*

		let $repeat := if ($statement/@repeat > 0) then $statement/@repeat else 1

		for $repeatIndex in (1 to $repeat)

			(: if a condition expression is given in the current statement, evaluate it :)
			let $condition :=
				if (exists($statement/condition)) then
					if (xtriples:expressionSanityCheck(string($statement/condition)) = true()) then
						try { util:eval(concat('$currentResource', string($statement/condition))) } catch * { $err:description }
					else true()
				else true()

			(: n possible subjects per statement declaration :)
			let $subjects :=
				if (starts-with(string($statement/subject), '/')) then
					xtriples:extractSubjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/subject

			(: 1 predicate per statement declaration :)
			let $predicate :=
				if (starts-with(string($statement/predicate), '/')) then
					xtriples:extractPredicate($currentResource, $statement, $repeatIndex, $resourceIndex)
				else <predicate prefix="{$statement/predicate/@prefix}">{string($statement/predicate)}</predicate>

			(: n possible objects per statement declaration :)
			let $objects :=
				if (starts-with(string($statement/object), '/')) then
					xtriples:extractObjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/object

			(: build statements - but only if the condition expression returned any value - boolean, string, node set etc. :)
			let $statements :=
				if ($condition != false()) then
					for $subject in $subjects
						let $subjectReturn :=
							if ($subject = "") then "" else
							for $object in $objects

								let $subjectPrepend := xtriples:extractPrependAppend($currentResource, $subject/@prepend, $repeatIndex, $resourceIndex)
								let $subjectAppend := xtriples:extractPrependAppend($currentResource, $subject/@append, $repeatIndex, $resourceIndex)

								let $predicatePrepend := xtriples:extractPrependAppend($currentResource, $predicate/@prepend, $repeatIndex, $resourceIndex)
								let $predicateAppend := xtriples:extractPrependAppend($currentResource, $predicate/@append, $repeatIndex, $resourceIndex)

								let $objectPrepend := xtriples:extractPrependAppend($currentResource, $object/@prepend, $repeatIndex, $resourceIndex)
								let $objectAppend := xtriples:extractPrependAppend($currentResource, $object/@append, $repeatIndex, $resourceIndex)

								let $objectReturn :=
									if ($object = "") then ""
									else <statement>{(
										functx:remove-attributes(functx:copy-attributes(<subject>{concat($subjectPrepend, $subject, $subjectAppend)}</subject>, $subject), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<predicate>{concat($predicatePrepend, $predicate, $predicateAppend)}</predicate>, $predicate), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<object>{concat($objectPrepend, $object, $objectAppend)}</object>, $object), ('append', 'prepend'))
									)}</statement>
							return $objectReturn
					return $subjectReturn
				else ""

	return $statements
};

(: ### FORMAT FUNCTIONS ### :)

(: internal/intermediate XTriples RDF format, one RDF node per XTriples statement - will later be condensed by the any23 webservice to "real" RDF :)
declare function xtriples:generateRDFTriples($xtriples as node()*) as item()* {

	for $statement in $xtriples//statements/statement

		let $subjectType := $statement/subject/@type
		let $subjectPrefix := $statement/subject/@prefix
		let $subjectUri := $xtriples//vocabularies/vocabulary[@prefix=$subjectPrefix]/@uri
		let $subjectValue := $statement/subject

		let $predicatePrefix := $statement/predicate/@prefix
		let $predicateUri := $xtriples//vocabularies/vocabulary[@prefix=$predicatePrefix]/@uri
		let $predicateValue := $statement/predicate

		let $objectType := $statement/object/@type
		let $objectPrefix := $statement/object/@prefix
		let $objectUri := $xtriples//vocabularies/vocabulary[@prefix=$objectPrefix]/@uri
		let $objectValue := $statement/object/text()

		(: rdf triple construction using computed constructors with qualified names; builds single rdf triples which are later combined by any23 webservice :)
		let $triple :=
			(: outer RDF tag :)
			element {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:Description")} {
			(: either is a blank node, then gets rdf:nodeID attribute :)
			(if ($subjectType = 'bnode') then
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $subjectValue }
			(: or is a URI, then gets rdf:about attribute :)
			else
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:about")} { concat($subjectUri, $subjectValue) }),
				(: predicate = inner element of the RDF tag :)
				element {QName($predicateUri, concat($predicatePrefix, ":", $predicateValue))} {
				(: object uri; set rdf:resource as attribute to predicate :)
				if ($objectType = 'uri') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:resource")} {
						if ($objectPrefix) then concat($objectUri, $objectValue) else $objectValue
					}
				(: object blank node; set rdf:nodeIFD as attribute to predicate :)
				else if ($objectType = 'bnode') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $objectValue }
				(: typed object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@datatype) then
					(attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:datatype")} { concat("http://www.w3.org/2001/XMLSchema#", $statement/object/@datatype) }, $objectValue)
				(: language tagged object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@lang) then
					(attribute {"xml:lang"} { $statement/object/@lang }, $objectValue)
				(: plain object literals; set value as text node :)
				else
					$objectValue
			}
		}

		return
			$triple
};

(: gets the internal RDF format :)
declare function xtriples:getRDFTriples($xtriples as node()*, $vocabularies as node()*) as item()* {
	let $rdfTriples := <rdftriples>{$vocabularies}{xtriples:generateRDFTriples($xtriples)}</rdftriples>
	return $rdfTriples
};

(: gets the internal RDF format and sends it to any23 for further transformation :)
declare function xtriples:getRDF($xtriples as node()*, $vocabularies as node()*) as item()* {
	(: internal RDF format :)
	let $rdfstylesheet := doc("rdf.xsl")
	let $rdfTriples := xtriples:getRDFTriples($xtriples, $vocabularies)
	let $rdfInternal := transform:transform($rdfTriples, $rdfstylesheet, ())

	(: official RDF format via any23 :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "rdfxml")), $rdfInternal, false(), $headers)
	let $rdf := $POST_request//httpclient:body/*

	return $rdf
};

(: gets ntriples format from any23 by sending in extracted RDF :)
declare function xtriples:getNTRIPLES($rdf as node()*) as item()* {

	(: url encoded ntriples :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nt")), $rdf, false(), $headers)
	let $ntriples := util:unescape-uri(replace(string($POST_request//httpclient:body), '%00', ''), "UTF-8")

	return $ntriples
};

(: gets turtle format from any23 by sending in extracted RDF :)
declare function xtriples:getTURTLE($rdf as node()*) as item()* {

	(: eXist returns base64Binary turtle :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "turtle")), $rdf, false(), $headers)
	let $turtle := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $turtle
};

(: gets nquads format from any23 by sending in extracted RDF :)
declare function xtriples:getNQUADS($rdf as node()*) as item()* {

	(: eXist returns base64Binary nquads :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nq")), $rdf, false(), $headers)
	let $nquads := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $nquads
};

(: gets json format from any23 by sending in extracted RDF :)
declare function xtriples:getJSON($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "json")), $rdf, false(), $headers)
	let $json := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $json
};

(: gets trix format from any23 by sending in extracted RDF :)
declare function xtriples:getTRIX($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "trix")), $rdf, false(), $headers)
	let $trix := $POST_request//httpclient:body/*

	return $trix
};

(: gets svg format from rhizomik webservice by temporarily storing the extracted RDF and pointing the redefer to the temporary file :)
declare function xtriples:getSVG($rdf as node()*) as item()* {

	(: svg format with temporary file :)
	let $filename := concat(util:uuid(), ".xml")
	let $store := xmldb:store("/db/apps/xtriples/temp/", $filename, $rdf)
	let $svgHeaders :=
		<headers>
			<header name="format" value="RDF/XML"/>
			<header name="mode" value="svg" />
			<headers name="rules" value="{$redeferWebserviceRulesURL}" />
		</headers>

	let $GET_request := httpclient:get(xs:anyURI(concat($redeferWebserviceURL, "render?rdf=", $xtriplesWebserviceURL, "temp/", $filename, "&format=RDF/XML&mode=svg&rules=", $redeferWebserviceRulesURL)), false(), $svgHeaders)
	let $svg := $GET_request//httpclient:body/*
	let $delete := xmldb:remove("/db/apps/xtriples/temp/", $filename)

	return $svg
};

(: ### QUERY BODY ### :)

(: set basic vars :)
let $collection := $setConfiguration/xtriples/collection
let $configuration := $setConfiguration/xtriples/configuration
let $vocabularies := $configuration/vocabularies

(: dynamic namespace declaration for all configured vocabularies :)
let $namespaces :=
	if ($configuration/vocabularies/*) then
		for $namespace in $configuration/vocabularies/*
		return util:declare-namespace($namespace/@prefix, $namespace/@uri)
	else ""

(: get resources for extraction :)
let $resources := xtriples:getResources($collection)

let $maxResources :=
	if ($collection/@max > 0) then
		$collection/@max
	else 0

(: extract triples from resources :)
let $triples :=
	for $resource at $resourceIndex in $resources
		let $currentResource :=
			if (name($resource) = 'resource') then
				$resource
			else <resource>{$resource}</resource>
	return
		if ($maxResources > 0 and $resourceIndex <= $maxResources)
		then
			<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
		else if ($maxResources = 0)
		then
			<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
		else ""

let $xtriples :=
	<xtriples>
		<configuration>
			{$vocabularies}
			<triples>{$triples}</triples>
		</configuration>
		{functx:copy-attributes(<collection>{$resources}</collection>, $collection)}
	</xtriples>

return (
	if ($setFormat = "xtriples") then
		$xtriples
	else if ($setFormat = "rdftriples") then
		xtriples:getRDFTriples($xtriples, $vocabularies)
	else if ($setFormat = "ntriples") then (
		response:set-header("Content-Type", "text/plain; charset=UTF-8"),
		response:stream(xtriples:getNTRIPLES(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "turtle") then (
		response:set-header("Content-Type", "text/turtle; charset=UTF-8"),
		response:stream(xtriples:getTURTLE(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "nquads") then (
		response:set-header("Content-Type", "text/x-nquads; charset=UTF-8"),
		response:stream(xtriples:getNQUADS(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "json") then (
		response:set-header("Content-Type", "text/json; charset=UTF-8"),
		response:stream(xtriples:getJSON(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "trix") then (
		response:set-header("Content-Type", "application/trix; charset=UTF-8"),
		xtriples:getTRIX(xtriples:getRDF($xtriples, $vocabularies))
		)
	else if ($setFormat = "svg") then
		(: response:set-header("Content-Type", "image/svg+xml; charset=UTF-8"), :)
		xtriples:getSVG(xtriples:getRDF($xtriples, $vocabularies))
	else
		(: response:set-header("Content-Type", "application/rdf+xml; charset=UTF-8"), :)
		xtriples:getRDF($xtriples, $vocabularies)
)

xquery version "3.0";

(:~
 : XTriples
 :
 : A generic webservice to extract rdf statements from XML resources
 :
 : @author Torsten Schrade
 : @email <Torsten.Schrade@adwmainz.de>
 : @version 1.2.0
 : @licence MIT
 :
 : Main module containing the webservice
:)

(: ### PROLOGUE ### :)

import module namespace functx="http://www.functx.com";

declare namespace xtriples = "http://xtriples.spatialhumanities.de/";

(: ### GLOBAL VARIABLES :)

declare variable $setConfiguration := xtriples:getConfiguration();
declare variable $setFormat := xtriples:getFormat();
declare variable $xtriplesWebserviceURL := "http://xtriples.spatialhumanities.de/";
declare variable $any23WebserviceURL := "http://any23.org/";
declare variable $redeferWebserviceURL := "http://rhizomik.net/redefer-services/";
declare variable $redeferWebserviceRulesURL := "http://rhizomik.net:8080/html/redefer/rdf2svg/showgraph.jrule";

(: ### CONFIGURATION FUNCTIONS ### :)

(: retrieves the XTriples configuration from GET or POST :)
declare function xtriples:getConfiguration() {

	(: checks GET/POST for a configuration sent with the configuration parameter :)
	let $submittedConfiguration := request:get-parameter("configuration", "")
	(: checks the request body for a configuration :)
	let $submittedConfigurationPOSTBody := request:get-data()

	let $setConfiguration :=
		(: case 1 - URI to a configuration file was sent - GET request :)
		if (substring($submittedConfiguration, 1, 4) = "http") then
			fn:doc(xs:anyURI($submittedConfiguration))
		(: case 2 - nothing submitted with a configuration parameter, use request body - direct POST request :)
		else if ($submittedConfiguration = "") then
			if ($submittedConfigurationPOSTBody) then
				$submittedConfigurationPOSTBody
			(: if no configuration could be retrieved to this point use a standard config that issues an error :)
			else
				fn:doc("configuration.xml")
		(: case 3 - full configuration sent with the configuration parameter - form style POST request :)
		else util:parse($submittedConfiguration)

	return $setConfiguration
};

(: gets and sets the return format :)
declare function xtriples:getFormat() {

	let $HEADER := request:get-header("format")
	let $GET := request:get-parameter("format", "rdf")
	let $setFormat :=
		if ($HEADER != "") then $HEADER
		else $GET

	return $setFormat
};

(: ### CRAWLING FUNCTIONS ### :)

(: evaluates an XPATH expression in curly braces within a (URI) string and returns the string :)
declare function xtriples:expressionBasedUriResolver($uri as xs:string, $currentResource as node()) as xs:string {

	let $expression := concat('$currentResource', substring-after(substring-before($uri, "}"), "{"))
	let $result := if (xtriples:expressionSanityCheck($expression) = true()) then
		try { util:eval($expression) } catch * { $err:description }
		else ""
	let $uriWithSubstitution := replace($uri, "\{.*\}", $result)

	return $uriWithSubstitution
};

(: evaluates a possible XPATH expression in curly braces within a document string and retrieves the document :)
declare function xtriples:expressionBasedResourceResolver($collection as node()*, $resource as node()*) as item()* {

	let $collectionContent := if (fn:doc-available($collection/@uri)) then fn:doc($collection/@uri) else ""

	let $resourcesURI := string($resource/@uri)
	let $resourcesExpression := concat('$collectionContent', substring-after(substring-before($resourcesURI, "}"), "{"))
	let $resourcesNodes := if (xtriples:expressionSanityCheck($resourcesExpression) = true()) then
		try { util:eval($resourcesExpression) } catch * { $err:description }
		else $resourcesExpression

	let $resources :=
		for $resource at $index in $resourcesNodes
			return
				if ($resource instance of element()) then
					$resource
				else
					element {"resource"} {
						attribute {"uri"} {replace($resourcesURI, "\{.*\}", $resource)}
					}
	return $resources
};

(: gets resources from a collection, possibly expression based :)
declare function xtriples:getResources($collection as node()*) as item()* {

	let $resources :=
		for $resource in $collection/resource
		return
			if (matches($resource/@uri, "\{") and matches($resource/@uri, "\}")) then
				xtriples:expressionBasedResourceResolver($collection, $resource)
			else $resource

	return $resources
};

(: ### EXTRACTION FUNCTIONS ### :)

(:
Separate relatively similar functions are used for subject, predicate and object extraction.
This keeps the query simpler even if it repeats some code. At the same time it opens up the possibility
to treat statement part extractions different in future versions (as it is already the case with subject/object
vs predicate extraction routines.
:)

(: safety filter for XPATH/XQuery expressions - dissallows executions from dangerous/not needed function namespaces :)
declare function xtriples:expressionSanityCheck($expression as xs:string) as xs:boolean {

	let $pattern := "((fn:.*\(.*\))|(doc*\(.*\))|(collection*\(.*\))|(v:.*\(.*\))|(backups:.*\(.*\))|(compression:.*\(.*\))|(contentextraction:.*\(.*\))|(counter:.*\(.*\))|(cqlparser:.*\(.*\))|(datetime:.*\(.*\))|(examples:.*\(.*\))|(exi:.*\(.*\))|(file:.*\(.*\))|(httpclient:.*\(.*\))|(image:.*\(.*\))|(inspection:.*\(.*\))|(jindi:.*\(.*\))|(kwic:.*\(.*\))|(lucene:.*\(.*\))|(mail:.*\(.*\))|(math:.*\(.*\))|(ngram:.*\(.*\))|(repo:.*\(.*\))|(request:.*\(.*\))|(response:.*\(.*\))|(scheduler:.*\(.*\))|(securitymanager:.*\(.*\))|(sequences:.*\(.*\))|(session:.*\(.*\))|(sort:.*\(.*\))|(sql:.*\(.*\))|(system:.*\(.*\))|(testing:.*\(.*\))|(text:.*\(.*\))|(transform:.*\(.*\))|(util:.*\(.*\))|(validation:.*\(.*\))|(xmldb:.*\(.*\))|(xmldiff:.*\(.*\))|(xqdoc:.*\(.*\))|(xslfo:.*\(.*\))|(config:.*\(.*\))|(docbook:.*\(.*\))|(app:.*\(.*\))|(dash:.*\(.*\))|(service:.*\(.*\))|(login-helper:.*\(.*\))|(packages:.*\(.*\))|(service:.*\(.*\))|(usermanager:.*\(.*\))|(demo:.*\(.*\))|(cex:.*\(.*\))|(ex:.*\(.*\))|(apputil:.*\(.*\))|(site:.*\(.*\))|(pretty:.*\(.*\))|(date:.*\(.*\))|(tei2:.*\(.*\))|(dbutil:.*\(.*\))|(docs:.*\(.*\))|(dq:.*\(.*\))|(review:.*\(.*\))|(epub:.*\(.*\))|(l18n:.*\(.*\))|(intl:.*\(.*\))|(restxq:.*\(.*\))|(tmpl:.*\(.*\))|(templates:.*\(.*\))|(trigger:.*\(.*\))|(jsjson:.*\(.*\))|(xqdoc:.*\(.*\)))"
	let $check := matches($expression, $pattern)

	return if ($check = true()) then false() else true()
};

(: extracts subject statements from the current resource :)
declare function xtriples:extractSubjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/subject/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))
			else ""
		else ""

	let $subjectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/subject))
		else concat('$currentResource', string($statement/subject))

	let $subjectExpression :=
		if (matches($subjectExpressionConcatenation, "$repeatIndex")) then replace($subjectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $subjectExpressionConcatenation

	let $subjectNodes := if (xtriples:expressionSanityCheck($subjectExpression) = true()) then
		try { util:eval($subjectExpression) } catch * { $err:description }
		else $subjectExpression

	let $subjects :=
		for $subjectValue in $subjectNodes
		return functx:copy-attributes(<subject>{string($subjectValue)}</subject>, $statement/subject)

	return $subjects
};

(: extracts predicate statements from the current resource :)
declare function xtriples:extractPredicate($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/predicate/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))
			else ""
		else ""

	let $predicateExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/predicate))
		else concat('$currentResource', string($statement/predicate))

	let $predicateExpression :=
		if (matches($predicateExpressionConcatenation, "$repeatIndex")) then replace($predicateExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $predicateExpressionConcatenation

	let $predicateValue := if (xtriples:expressionSanityCheck($predicateExpression) = true()) then
		try { string(util:eval($predicateExpression)) } catch * { $err:description }
		else $predicateExpression

	let $predicate := <predicate prefix="{$statement/predicate/@prefix}">{string($predicateValue)}</predicate>

	return $predicate
};

(: extracts object statements from the current resource :)
declare function xtriples:extractObjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/object/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))
			else ""
		else ""

	let $objectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/object))
		else concat("$currentResource", string($statement/object))

	let $objectExpression :=
		if (matches($objectExpressionConcatenation, "$repeatIndex")) then replace($objectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $objectExpressionConcatenation

	let $objectNodes := if (xtriples:expressionSanityCheck($objectExpression) = true()) then
		try { util:eval($objectExpression) } catch * { $err:description }
		else $objectExpression

	let $objects :=
		for $objectValue in $objectNodes
		return functx:copy-attributes(<object>{string($objectValue)}</object>, $statement/object)

	return $objects
};

(: extracts expressions in prepend/append attributes from the current resource :)
declare function xtriples:extractPrependAppend($currentResource as node(), $expressionString as xs:string*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as xs:string* {
	let $expression := substring-after(substring-before($expressionString, "}"), "{")
	let $result :=
		if ($expression and xtriples:expressionSanityCheck($expression) = true()) then
			try {
				replace($expressionString, "\{.*\}", util:eval(concat("$currentResource", $expression)))
			} catch * { $err:description }
		else $expressionString
	return $result
};

(: XTriples core function - evaluates all configured statements for the current resource :)
declare function xtriples:extractTriples($currentResource as node(), $resourceIndex as xs:integer, $configuration as node()*) as item()* {

	(: ensure there is a XML resource - it could still be just a URI to a XML resource :)
	let $currentResource :=
		if (xs:anyURI($currentResource/@uri)) then
			fn:doc($currentResource/@uri)
		else $currentResource

	(: start statement pattern extraction :)
	for $statement in $configuration//triples/*

		let $repeat := if ($statement/@repeat > 0) then $statement/@repeat else 1

		for $repeatIndex in (1 to $repeat)

			(: if a condition expression is given in the current statement, evaluate it :)
			let $condition :=
				if (exists($statement/condition)) then
					if (xtriples:expressionSanityCheck(string($statement/condition)) = true()) then
						try { util:eval(concat('$currentResource', string($statement/condition))) } catch * { $err:description }
					else true()
				else true()

			(: n possible subjects per statement declaration :)
			let $subjects :=
				if (starts-with(string($statement/subject), '/')) then
					xtriples:extractSubjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/subject

			(: 1 predicate per statement declaration :)
			let $predicate :=
				if (starts-with(string($statement/predicate), '/')) then
					xtriples:extractPredicate($currentResource, $statement, $repeatIndex, $resourceIndex)
				else <predicate prefix="{$statement/predicate/@prefix}">{string($statement/predicate)}</predicate>

			(: n possible objects per statement declaration :)
			let $objects :=
				if (starts-with(string($statement/object), '/')) then
					xtriples:extractObjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/object

			(: build statements - but only if the condition expression returned any value - boolean, string, node set etc. :)
			let $statements :=
				if ($condition != false()) then
					for $subject in $subjects
						let $subjectReturn :=
							if ($subject = "") then "" else
							for $object in $objects

								let $subjectPrepend := xtriples:extractPrependAppend($currentResource, $subject/@prepend, $repeatIndex, $resourceIndex)
								let $subjectAppend := xtriples:extractPrependAppend($currentResource, $subject/@append, $repeatIndex, $resourceIndex)

								let $predicatePrepend := xtriples:extractPrependAppend($currentResource, $predicate/@prepend, $repeatIndex, $resourceIndex)
								let $predicateAppend := xtriples:extractPrependAppend($currentResource, $predicate/@append, $repeatIndex, $resourceIndex)

								let $objectPrepend := xtriples:extractPrependAppend($currentResource, $object/@prepend, $repeatIndex, $resourceIndex)
								let $objectAppend := xtriples:extractPrependAppend($currentResource, $object/@append, $repeatIndex, $resourceIndex)

								let $objectReturn :=
									if ($object = "") then ""
									else <statement>{(
										functx:remove-attributes(functx:copy-attributes(<subject>{concat($subjectPrepend, $subject, $subjectAppend)}</subject>, $subject), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<predicate>{concat($predicatePrepend, $predicate, $predicateAppend)}</predicate>, $predicate), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<object>{concat($objectPrepend, $object, $objectAppend)}</object>, $object), ('append', 'prepend'))
									)}</statement>
							return $objectReturn
					return $subjectReturn
				else ""

	return $statements
};

(: ### FORMAT FUNCTIONS ### :)

(: internal/intermediate XTriples RDF format, one RDF node per XTriples statement - will later be condensed by the any23 webservice to "real" RDF :)
declare function xtriples:generateRDFTriples($xtriples as node()*) as item()* {

	for $statement in $xtriples//statements/statement

		let $subjectType := $statement/subject/@type
		let $subjectPrefix := $statement/subject/@prefix
		let $subjectUri := $xtriples//vocabularies/vocabulary[@prefix=$subjectPrefix]/@uri
		let $subjectValue := $statement/subject

		let $predicatePrefix := $statement/predicate/@prefix
		let $predicateUri := $xtriples//vocabularies/vocabulary[@prefix=$predicatePrefix]/@uri
		let $predicateValue := $statement/predicate

		let $objectType := $statement/object/@type
		let $objectPrefix := $statement/object/@prefix
		let $objectUri := $xtriples//vocabularies/vocabulary[@prefix=$objectPrefix]/@uri
		let $objectValue := $statement/object/text()

		(: rdf triple construction using computed constructors with qualified names; builds single rdf triples which are later combined by any23 webservice :)
		let $triple :=
			(: outer RDF tag :)
			element {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:Description")} {
			(: either is a blank node, then gets rdf:nodeID attribute :)
			(if ($subjectType = 'bnode') then
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $subjectValue }
			(: or is a URI, then gets rdf:about attribute :)
			else
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:about")} { concat($subjectUri, $subjectValue) }),
				(: predicate = inner element of the RDF tag :)
				element {QName($predicateUri, concat($predicatePrefix, ":", $predicateValue))} {
				(: object uri; set rdf:resource as attribute to predicate :)
				if ($objectType = 'uri') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:resource")} {
						if ($objectPrefix) then concat($objectUri, $objectValue) else $objectValue
					}
				(: object blank node; set rdf:nodeIFD as attribute to predicate :)
				else if ($objectType = 'bnode') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $objectValue }
				(: typed object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@datatype) then
					(attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:datatype")} { concat("http://www.w3.org/2001/XMLSchema#", $statement/object/@datatype) }, $objectValue)
				(: language tagged object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@lang) then
					(attribute {"xml:lang"} { $statement/object/@lang }, $objectValue)
				(: plain object literals; set value as text node :)
				else
					$objectValue
			}
		}

		return
			$triple
};

(: gets the internal RDF format :)
declare function xtriples:getRDFTriples($xtriples as node()*, $vocabularies as node()*) as item()* {
	let $rdfTriples := <rdftriples>{$vocabularies}{xtriples:generateRDFTriples($xtriples)}</rdftriples>
	return $rdfTriples
};

(: gets the internal RDF format and sends it to any23 for further transformation :)
declare function xtriples:getRDF($xtriples as node()*, $vocabularies as node()*) as item()* {
	(: internal RDF format :)
	let $rdfstylesheet := doc("rdf.xsl")
	let $rdfTriples := xtriples:getRDFTriples($xtriples, $vocabularies)
	let $rdfInternal := transform:transform($rdfTriples, $rdfstylesheet, ())

	(: official RDF format via any23 :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "rdfxml")), $rdfInternal, false(), $headers)
	let $rdf := $POST_request//httpclient:body/*

	return $rdf
};

(: gets ntriples format from any23 by sending in extracted RDF :)
declare function xtriples:getNTRIPLES($rdf as node()*) as item()* {

	(: url encoded ntriples :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nt")), $rdf, false(), $headers)
	let $ntriples := util:unescape-uri(replace(string($POST_request//httpclient:body), '%00', ''), "UTF-8")

	return $ntriples
};

(: gets turtle format from any23 by sending in extracted RDF :)
declare function xtriples:getTURTLE($rdf as node()*) as item()* {

	(: eXist returns base64Binary turtle :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "turtle")), $rdf, false(), $headers)
	let $turtle := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $turtle
};

(: gets nquads format from any23 by sending in extracted RDF :)
declare function xtriples:getNQUADS($rdf as node()*) as item()* {

	(: eXist returns base64Binary nquads :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nq")), $rdf, false(), $headers)
	let $nquads := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $nquads
};

(: gets json format from any23 by sending in extracted RDF :)
declare function xtriples:getJSON($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "json")), $rdf, false(), $headers)
	let $json := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $json
};

(: gets trix format from any23 by sending in extracted RDF :)
declare function xtriples:getTRIX($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "trix")), $rdf, false(), $headers)
	let $trix := $POST_request//httpclient:body/*

	return $trix
};

(: gets svg format from rhizomik webservice by temporarily storing the extracted RDF and pointing the redefer to the temporary file :)
declare function xtriples:getSVG($rdf as node()*) as item()* {

	(: svg format with temporary file :)
	let $filename := concat(util:uuid(), ".xml")
	let $store := xmldb:store("/db/apps/xtriples/temp/", $filename, $rdf)
	let $svgHeaders :=
		<headers>
			<header name="format" value="RDF/XML"/>
			<header name="mode" value="svg" />
			<headers name="rules" value="{$redeferWebserviceRulesURL}" />
		</headers>

	let $GET_request := httpclient:get(xs:anyURI(concat($redeferWebserviceURL, "render?rdf=", $xtriplesWebserviceURL, "temp/", $filename, "&format=RDF/XML&mode=svg&rules=", $redeferWebserviceRulesURL)), false(), $svgHeaders)
	let $svg := $GET_request//httpclient:body/*
	let $delete := xmldb:remove("/db/apps/xtriples/temp/", $filename)

	return $svg
};

(: ### QUERY BODY ### :)

(: set basic vars :)
let $collection := $setConfiguration/xtriples/collection
let $configuration := $setConfiguration/xtriples/configuration
let $vocabularies := $configuration/vocabularies

(: dynamic namespace declaration for all configured vocabularies :)
let $namespaces :=
	if ($configuration/vocabularies/*) then
		for $namespace in $configuration/vocabularies/*
		return util:declare-namespace($namespace/@prefix, $namespace/@uri)
	else ""

(: @todo - test if each resource has exactly one child :)
let $resources :=
	(: configuration case 1 - all resources are provided statically within the collection :)
	if ($collection//resource/*) then
		$collection//resource/*
	(: otherwise resources should be retrieved from uris or by expression :)
	else xtriples:getResources($collection)

let $maxResources :=
	if ($collection/@max > 0) then
		$collection/@max
	else 0

(: extract triples from resources :)
let $triples :=
	for $currentResource at $resourceIndex in $resources
	return
		if ($maxResources > 0 and $resourceIndex <= $maxResources)
		then
			<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
		else if ($maxResources = 0)
		then
			<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
		else ""

let $xtriples :=
	<xtriples>
		<configuration>
			{$vocabularies}
			<triples>{$triples}</triples>
		</configuration>
		{functx:copy-attributes(<collection>{$resources}</collection>, $collection)}
	</xtriples>

return (
	if ($setFormat = "xtriples") then
		$xtriples
	else if ($setFormat = "rdftriples") then
		xtriples:getRDFTriples($xtriples, $vocabularies)
	else if ($setFormat = "ntriples") then (
		response:set-header("Content-Type", "text/plain; charset=UTF-8"),
		response:stream(xtriples:getNTRIPLES(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "turtle") then (
		response:set-header("Content-Type", "text/turtle; charset=UTF-8"),
		response:stream(xtriples:getTURTLE(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "nquads") then (
		response:set-header("Content-Type", "text/x-nquads; charset=UTF-8"),
		response:stream(xtriples:getNQUADS(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "json") then (
		response:set-header("Content-Type", "text/json; charset=UTF-8"),
		response:stream(xtriples:getJSON(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "trix") then (
		response:set-header("Content-Type", "application/trix; charset=UTF-8"),
		xtriples:getTRIX(xtriples:getRDF($xtriples, $vocabularies))
		)
	else if ($setFormat = "svg") then
		(: response:set-header("Content-Type", "image/svg+xml; charset=UTF-8"), :)
		xtriples:getSVG(xtriples:getRDF($xtriples, $vocabularies))
	else
		(: response:set-header("Content-Type", "application/rdf+xml; charset=UTF-8"), :)
		xtriples:getRDF($xtriples, $vocabularies)
)