refactor(xquery): remove verbose fn prefix from generate-query-utils.xql (#956)

module namespace test = "http://www.jenitennison.com/xslt/unit-test";

(::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::)
(:  File:       generate-query-utils.xql                                    :)
(:  Author:     Jeni Tennison                                               :)
(:  URL:        http://github.com/xspec/xspec                               :)
(:  Tags:                                                                   :)
(:    Copyright (c) 2008, 2010 Jeni Tennison (see end of file.)             :)
(: ------------------------------------------------------------------------ :)

import module namespace x = "http://www.jenitennison.com/xslt/xspec"
  at "../common/xspec-utils.xquery";

declare function test:deep-equal(
    $seq1 as item()*,
    $seq2 as item()*,
    $flags as xs:string
  ) as xs:boolean
{
  if (contains($flags, '1')) then
    let $flags as xs:string := translate($flags, '1', '')
      return
        if ( $seq1 instance of xs:string and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, string-join($seq2, ''), $flags)
        else if ( $seq1 instance of xs:double and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, xs:double(string-join($seq2, '')), $flags)
        else if ( $seq1 instance of xs:decimal and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, xs:decimal(string-join($seq2, '')), $flags)
        else if ( $seq1 instance of xs:integer and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, xs:integer(string-join($seq2, '')), $flags)
        else
          test:deep-equal($seq1, $seq2, $flags)
  else if (empty($seq1) or empty($seq2)) then
    empty($seq1) and empty($seq2)
  else if (count($seq1) = count($seq2)) then
    every $i in (1 to count($seq1))
    satisfies test:item-deep-equal($seq1[$i], $seq2[$i], $flags)
  else if ( $seq1 instance of text() and $seq2 instance of text()+ ) then
    test:deep-equal($seq1, text { string-join($seq2, '') }, $flags)
  else
    false()
};

declare function test:item-deep-equal(
    $item1 as item(),
    $item2 as item(),
    $flags as xs:string
  ) as xs:boolean
{
  if ( $item1 instance of node() and $item2 instance of node() ) then
    test:node-deep-equal($item1, $item2, $flags)
  else if (not($item1 instance of node()) and not($item2 instance of node())) then
    deep-equal($item1, $item2)
  else
    false()
};

declare function test:node-deep-equal(
    $node1 as node(),
    $node2 as node(),
    $flags as xs:string
  ) as xs:boolean
{
  if ( $node1 instance of document-node() and $node2 instance of document-node() ) then
    test:deep-equal(test:sorted-children($node1, $flags), test:sorted-children($node2, $flags), $flags)
  else if ( $node1 instance of element() and $node2 instance of element() ) then
    if (node-name($node1) eq node-name($node2)) then
      let $atts1 as attribute()* := test:sort-named-nodes($node1/@*)
      let $atts2 as attribute()* := test:sort-named-nodes($node2/@*)
        return
          if ( test:deep-equal($atts1, $atts2, $flags) ) then
            if (count($node1/node()) = 1 and $node1/text() = '...') then
              true()
            else
              test:deep-equal(test:sorted-children($node1, $flags), test:sorted-children($node2, $flags), $flags)
          else
            false()
    else
      false()
  else if ( $node1 instance of text() and $node1 = '...' ) then
    true()
  else if ( $node1 instance of text() and $node2 instance of text() ) then
    string($node1) eq string($node2)
  else if ( ( $node1 instance of attribute() and $node2 instance of attribute() )
            or ( $node1 instance of processing-instruction()
                 and $node2 instance of processing-instruction())
            or ( $node1 instance of namespace-node()
                 and $node2 instance of namespace-node() ) ) then
    deep-equal(node-name($node1), node-name($node2))
      and (string($node1) eq string($node2) or string($node1) = '...')
  else if ( $node1 instance of comment() and $node2 instance of comment() ) then
    string($node1) eq string($node2) or string($node1) = '...'
  else
    false()
};

declare function test:sorted-children(
    $node as node(),
    $flags as xs:string
  ) as node()*
{
  $node/child::node()
  except ($node/text()[not(normalize-space())][contains($flags, 'w')][not($node/self::test:ws)],
          $node/test:message)
};

(: Aim to be identical to:
 :
 :     <xsl:perform-sort select="$nodes">
 :        <xsl:sort select="namespace-uri(.)" />
 :        <xsl:sort select="local-name(.)" />
 :     </xsl:perform-sort>
 :)
declare function test:sort-named-nodes($nodes as node()*) as node()*
{
  if (empty($nodes)) then
    ()
  else
    let $idx := test:named-nodes-minimum($nodes)
      return (
        $nodes[$idx],
        test:sort-named-nodes(remove($nodes, $idx))
      )
};

(: Return the "minimum" of $nodes, using the order defined by
 : test:sort-named-nodes().
 :)
declare function test:named-nodes-minimum($nodes as node()+) as xs:integer
{
  (: if there is only one node, this is the minimum :)
  if (empty($nodes[2])) then
    1
  (: if not, init the temp minimum on the first one, then walk through the sequence :)
  else
    test:named-nodes-minimum($nodes, node-name($nodes[1]), 1, 2)
};

declare function test:named-nodes-minimum(
    $nodes as node()+,
    $min   as xs:QName,
    $idx   as xs:integer,
    $curr  as xs:integer
  ) as xs:integer
{
  if ($curr gt count($nodes)) then
    $idx
  else if (test:qname-lt(node-name($nodes[$curr]), $min)) then
    test:named-nodes-minimum($nodes, node-name($nodes[$curr]), $curr, $curr + 1)
  else
    test:named-nodes-minimum($nodes, $min, $idx, $curr + 1)
};

declare function test:qname-lt($n1 as xs:QName, $n2 as xs:QName) as xs:boolean
{
  if (namespace-uri-from-QName($n1) eq namespace-uri-from-QName($n2)) then
    local-name-from-QName($n1) lt local-name-from-QName($n2)
  else
    namespace-uri-from-QName($n1) lt namespace-uri-from-QName($n2)
};

declare function test:report-sequence(
    $sequence as item()*,
    $wrapper-name as xs:string
  ) as element()
{
  test:report-sequence($sequence, $wrapper-name, ())
};

declare function test:report-sequence(
    $sequence as item()*,
    $wrapper-name as xs:string,
    $test as attribute(test)?
  ) as element()
{
  let $wrapper-ns as xs:string := 'http://www.jenitennison.com/xslt/xspec'

  let $attribute-nodes as attribute()* := $sequence[. instance of attribute()]
  let $document-nodes as document-node()* := $sequence[. instance of document-node()]
  let $namespace-nodes as node()* := $sequence[. instance of namespace-node()]
  let $text-nodes as text()* := $sequence[. instance of text()]

  let $report-element as element() :=
    element
      { QName($wrapper-ns, $wrapper-name) }
      {
        $test,

        (
          (: Empty :)
          if (empty($sequence))
          then attribute select { "()" }

          (: One or more atomic values :)
          else if ($sequence instance of xs:anyAtomicType+)
          then (
            let $atomic-value-reports as xs:string+ :=
              (for $value in $sequence return test:report-atomic-value($value))
            return attribute select { string-join($atomic-value-reports, ', ') }
          )

          (: One or more nodes of the same type which can be a child of document node :)
          else if (
            ($sequence instance of comment()+)
            or ($sequence instance of element()+)
            or ($sequence instance of processing-instruction()+)
            or ($sequence instance of text()+)
          )
          then (
            attribute select { concat('/', x:node-type($sequence[1]), '()') },
            for $node in $sequence return test:report-node($node)
          )

          (: Single document node :)
          else if ($sequence instance of document-node())
          then (
            (: People do not always notice '/' in the report HTML. So express it more verbosely.
              Also the expression must match the one in ../reporter/format-xspec-report.xsl. :)
            attribute select { "/self::document-node()" },
            test:report-node($sequence)
          )

          (: One or more nodes which can be stored in an element safely and without losing each position.
            Those nodes include document nodes and text nodes. By storing them in an element, they will
            be unwrapped and/or merged with adjacent nodes. When it happens, the report does not
            represent the sequence precisely. That's ok, because
              * Otherwise the report will be cluttered with pseudo elements.
              * XSpec in general including its test:deep-equal() inclines to merge them. :)
          else if (($sequence instance of node()+) and not($attribute-nodes or $namespace-nodes))
          then (
            attribute select { "/node()" },
            for $node in $sequence return test:report-node($node)
          )

          (: Otherwise each item needs to be represented as a pseudo element :)
          else (
            attribute select {
              concat(
                (: Select the pseudo elements :)
                '/*',

                (
                  (: If all items are instance of node, they can be expressed in @select.
                    (Document nodes are unwrapped, though.) :)
                  if ($sequence instance of node()+)
                  then (
                    let $expressions as xs:string+ := (
                      '@*'[$attribute-nodes],
                      'namespace::*'[$namespace-nodes],
                      'node()'[$sequence except ($attribute-nodes | $namespace-nodes)]
                    )
                    let $multi-expr as xs:boolean := (count($expressions) ge 2)
                    return
                      concat(
                        '/',
                        '('[$multi-expr],
                        string-join($expressions, ' | '),
                        ')'[$multi-expr]
                      )
                  )
                  else (
                    (: Not all items can be expressed in @select. Just leave the pseudo elements selected. :)
                  )
                )
              )
            },

            for $item in $sequence
            return test:report-pseudo-item($item, $wrapper-ns)
          )
        )
      }

  (: Output the report element :)
  return (
    (: TODO: If too many nodes, save the report element as an external doc :)
    $report-element
  )
};

declare function test:report-pseudo-item(
    $item as item(),
    $wrapper-ns as xs:string
  ) as element()
{
  let $local-name-prefix as xs:string := 'pseudo-'
  return (
    if ($item instance of xs:anyAtomicType)
    then
      element
        { QName($wrapper-ns, concat($local-name-prefix, 'atomic-value')) }
        { test:report-atomic-value($item) }

    else if ($item instance of node())
    then
      element
        { QName($wrapper-ns, concat($local-name-prefix, x:node-type($item))) }
        { test:report-node($item) }

    (: TODO: function(*) including array(*) and map(*) :)

    else
      element
        { QName($wrapper-ns, concat($local-name-prefix, 'other')) }
        {}
  )
};

(:
  Copies the nodes while wrapping whitespace-only text nodes in <test:ws>
:)
declare function test:report-node(
    $node as node()
    ) as node()
{
  if (($node instance of text()) and not(normalize-space($node))) then
    element test:ws { $node }
  else if ( $node instance of document-node() ) then
    document {
      for $child in $node/child::node() return test:report-node($child)
    }
  else if ( $node instance of element() ) then
    element { node-name($node) } {
      (
        for $prefix in in-scope-prefixes($node)
          return namespace { $prefix } { namespace-uri-for-prefix($prefix, $node) }
      ),
      $node/attribute(),
      (for $child in $node/child::node() return test:report-node($child))
    }
  else $node
};

declare function test:report-atomic-value($value as xs:anyAtomicType) as xs:string
{
  (: Derived types must be handled before their base types :)

  (: String types :)
  (: xs:normalizedString: Requires schema-aware processor :)
  if ( $value instance of xs:string ) then
    x:quote-with-apos($value)

  (: Derived numeric types: Requires schema-aware processor :)

  (: Numeric types which can be expressed as numeric literals:
    http://www.w3.org/TR/xpath20/#id-literals :)
  else if ( $value instance of xs:integer ) then
    string($value)
  else if ( $value instance of xs:decimal ) then
    x:decimal-string($value)
  else if ( $value instance of xs:double ) then
    (: Do not report xs:double as a numeric literal. Report as xs:double() constructor instead.
      Justifications below.
      * Expression of xs:double as a numeric literal is a bit complicated:
        http://www.w3.org/TR/xpath-functions/#casting-to-string
      * xs:double is not used as frequently as xs:integer
      * xs:double() constructor is valid expression. It's just some more verbose than a numeric literal. :)
    test:report-atomic-value-as-constructor($value)

  else if ( $value instance of xs:QName ) then
    x:QName-expression($value)

  else
    test:report-atomic-value-as-constructor($value)
};

declare function test:report-atomic-value-as-constructor($value as xs:anyAtomicType) as xs:string
{
  (: Constructor usually has the same name as type :)
  let $constructor-name as xs:string := test:atom-type($value)

  (: Cast as either xs:integer or xs:string :)
  let $casted-value as xs:anyAtomicType := (
    if ( $value instance of xs:integer )
    then
      (: Force casting down to integer, by first converting to string :)
      (string($value) cast as xs:integer)
    else
      string($value)
  )

  (: Constructor parameter:
    Either numeric literal of integer or string literal :)
  let $costructor-param as xs:string :=
    test:report-atomic-value($casted-value)

  return concat($constructor-name, '(', $costructor-param, ')')
};

declare function test:atom-type($value as xs:anyAtomicType) as xs:string
{
  (: Grouped as the spec does: http://www.w3.org/TR/xslt20/#built-in-types
    Groups are in the reversed order so that the derived types are before the primitive types,
    otherwise xs:integer is recognised as xs:decimal, xs:yearMonthDuration as xs:duration, and so on. :)

  (: A schema-aware XSLT processor additionally supports: :)

  (:    * All other built-in types defined in [XML Schema Part 2] :)
  (: Requires schema-aware processor :)

  (: Every XSLT 2.0 processor includes the following named type definitions in the in-scope schema components: :)

  (:    * The following types defined in [XPath 2.0] :)
  if ( $value instance of xs:yearMonthDuration ) then
    'xs:yearMonthDuration'
  else if ($value instance of xs:dayTimeDuration ) then
    'xs:dayTimeDuration'
  (: xs:anyAtomicType: Abstract :)
  (: xs:untyped: Not atomic :)
  else if ( $value instance of xs:untypedAtomic ) then
    'xs:untypedAtomic'

  (:    * The types xs:anyType and xs:anySimpleType. :)
  (: Not atomic :)

  (:    * The derived atomic type xs:integer defined in [XML Schema Part 2]. :)
  else if ( $value instance of xs:integer ) then
    'xs:integer'

  (:    * All the primitive atomic types defined in [XML Schema Part 2], with the exception of xs:NOTATION. :)
  else if ( $value instance of xs:string ) then
    'xs:string'
  else if ( $value instance of xs:boolean ) then
    'xs:boolean'
  else if ( $value instance of xs:decimal ) then
    'xs:decimal'
  else if ( $value instance of xs:double ) then
    'xs:double'
  else if ( $value instance of xs:float ) then
    'xs:float'
  else if ( $value instance of xs:date ) then
    'xs:date'
  else if ( $value instance of xs:time ) then
    'xs:time'
  else if ( $value instance of xs:dateTime ) then
    'xs:dateTime'
  else if ( $value instance of xs:duration ) then
    'xs:duration'
  else if ( $value instance of xs:QName ) then
    'xs:QName'
  else if ( $value instance of xs:anyURI ) then
    'xs:anyURI'
  else if ( $value instance of xs:gDay ) then
    'xs:gDay'
  else if ( $value instance of xs:gMonthDay ) then
    'xs:gMonthDay'
  else if ( $value instance of xs:gMonth ) then
    'xs:gMonth'
  else if ( $value instance of xs:gYearMonth ) then
    'xs:gYearMonth'
  else if ( $value instance of xs:gYear ) then
    'xs:gYear'
  else if ( $value instance of xs:base64Binary ) then
    'xs:base64Binary'
  else if ( $value instance of xs:hexBinary ) then
    'xs:hexBinary'
  else
    'xs:anyAtomicType'
};

(: ------------------------------------------------------------------------ :)
(:  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT.               :)
(:                                                                          :)
(:  Copyright (c) 2008, 2010 Jeni Tennison                                  :)
(:                                                                          :)
(:  The contents of this file are subject to the MIT License (see the URI   :)
(:  http://www.opensource.org/licenses/mit-license.php for details).        :)
(:                                                                          :)
(:  Permission is hereby granted, free of charge, to any person obtaining   :)
(:  a copy of this software and associated documentation files (the         :)
(:  "Software"), to deal in the Software without restriction, including     :)
(:  without limitation the rights to use, copy, modify, merge, publish,     :)
(:  distribute, sublicense, and/or sell copies of the Software, and to      :)
(:  permit persons to whom the Software is furnished to do so, subject to   :)
(:  the following conditions:                                               :)
(:                                                                          :)
(:  The above copyright notice and this permission notice shall be          :)
(:  included in all copies or substantial portions of the Software.         :)
(:                                                                          :)
(:  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,         :)
(:  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF      :)
(:  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  :)
(:  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY    :)
(:  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,    :)
(:  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE       :)
(:  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                  :)
(: ------------------------------------------------------------------------ :)

module namespace test = "http://www.jenitennison.com/xslt/unit-test";

(::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::)
(:  File:       generate-query-utils.xql                                    :)
(:  Author:     Jeni Tennison                                               :)
(:  URL:        http://github.com/xspec/xspec                               :)
(:  Tags:                                                                   :)
(:    Copyright (c) 2008, 2010 Jeni Tennison (see end of file.)             :)
(: ------------------------------------------------------------------------ :)

import module namespace x = "http://www.jenitennison.com/xslt/xspec"
  at "../common/xspec-utils.xquery";

declare namespace fn = "http://www.w3.org/2005/xpath-functions";

declare function test:deep-equal(
    $seq1 as item()*,
    $seq2 as item()*,
    $flags as xs:string
  ) as xs:boolean
{
  if ( fn:contains($flags, '1') ) then
    let $flags as xs:string := fn:translate($flags, '1', '')
      return
        if ( $seq1 instance of xs:string and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, fn:string-join($seq2, ''), $flags)
        else if ( $seq1 instance of xs:double and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, xs:double(fn:string-join($seq2, '')), $flags)
        else if ( $seq1 instance of xs:decimal and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, xs:decimal(fn:string-join($seq2, '')), $flags)
        else if ( $seq1 instance of xs:integer and $seq2 instance of text()+ ) then
          test:deep-equal($seq1, xs:integer(fn:string-join($seq2, '')), $flags)
        else
          test:deep-equal($seq1, $seq2, $flags)
  else if ( fn:empty($seq1) or fn:empty($seq2) ) then
    fn:empty($seq1) and fn:empty($seq2)
  else if ( fn:count($seq1) = fn:count($seq2) ) then
    every $i in (1 to fn:count($seq1))
    satisfies test:item-deep-equal($seq1[$i], $seq2[$i], $flags)
  else if ( $seq1 instance of text() and $seq2 instance of text()+ ) then
    test:deep-equal($seq1, text { fn:string-join($seq2, '') }, $flags)
  else
    fn:false()
};

declare function test:item-deep-equal(
    $item1 as item(),
    $item2 as item(),
    $flags as xs:string
  ) as xs:boolean
{
  if ( $item1 instance of node() and $item2 instance of node() ) then
    test:node-deep-equal($item1, $item2, $flags)
  else if ( fn:not($item1 instance of node()) and fn:not($item2 instance of node()) ) then
    fn:deep-equal($item1, $item2)
  else
    fn:false()
};

declare function test:node-deep-equal(
    $node1 as node(),
    $node2 as node(),
    $flags as xs:string
  ) as xs:boolean
{
  if ( $node1 instance of document-node() and $node2 instance of document-node() ) then
    test:deep-equal(test:sorted-children($node1, $flags), test:sorted-children($node2, $flags), $flags)
  else if ( $node1 instance of element() and $node2 instance of element() ) then
    if ( fn:node-name($node1) eq fn:node-name($node2) ) then
      let $atts1 as attribute()* := test:sort-named-nodes($node1/@*)
      let $atts2 as attribute()* := test:sort-named-nodes($node2/@*)
        return
          if ( test:deep-equal($atts1, $atts2, $flags) ) then
            if ( fn:count($node1/node()) = 1 and $node1/text() = '...' ) then
              fn:true()
            else
              test:deep-equal(test:sorted-children($node1, $flags), test:sorted-children($node2, $flags), $flags)
          else
            fn:false()
    else
      fn:false()
  else if ( $node1 instance of text() and $node1 = '...' ) then
    fn:true()
  else if ( $node1 instance of text() and $node2 instance of text() ) then
    fn:string($node1) eq fn:string($node2)
  else if ( ( $node1 instance of attribute() and $node2 instance of attribute() )
            or ( $node1 instance of processing-instruction()
                 and $node2 instance of processing-instruction())
            or ( $node1 instance of namespace-node()
                 and $node2 instance of namespace-node() ) ) then
    fn:deep-equal( fn:node-name($node1), fn:node-name($node2) )
      and ( fn:string($node1) eq fn:string($node2) or fn:string($node1) = '...' )
  else if ( $node1 instance of comment() and $node2 instance of comment() ) then
    fn:string($node1) eq fn:string($node2) or fn:string($node1) = '...'
  else
    fn:false()
};

declare function test:sorted-children(
    $node as node(),
    $flags as xs:string
  ) as node()*
{
  $node/child::node()
  except ( $node/text()[fn:not(fn:normalize-space())][fn:contains($flags, 'w')][fn:not($node/self::test:ws)],
           $node/test:message )
};

(: Aim to be identical to:
 :
 :     <xsl:perform-sort select="$nodes">
 :        <xsl:sort select="namespace-uri(.)" />
 :        <xsl:sort select="local-name(.)" />
 :     </xsl:perform-sort>
 :)
declare function test:sort-named-nodes($nodes as node()*) as node()*
{
  if ( fn:empty($nodes) ) then
    ()
  else
    let $idx := test:named-nodes-minimum($nodes)
      return (
        $nodes[$idx],
        test:sort-named-nodes(fn:remove($nodes, $idx))
      )
};

(: Return the "minimum" of $nodes, using the order defined by
 : test:sort-named-nodes().
 :)
declare function test:named-nodes-minimum($nodes as node()+) as xs:integer
{
  (: if there is only one node, this is the minimum :)
  if ( fn:empty($nodes[2]) ) then
    1
  (: if not, init the temp minimum on the first one, then walk through the sequence :)
  else
    test:named-nodes-minimum($nodes, fn:node-name($nodes[1]), 1, 2)
};

declare function test:named-nodes-minimum(
    $nodes as node()+,
    $min   as xs:QName,
    $idx   as xs:integer,
    $curr  as xs:integer
  ) as xs:integer
{
  if ( $curr gt fn:count($nodes) ) then
    $idx
  else if ( test:qname-lt(fn:node-name($nodes[$curr]), $min) ) then
    test:named-nodes-minimum($nodes, fn:node-name($nodes[$curr]), $curr, $curr + 1)
  else
    test:named-nodes-minimum($nodes, $min, $idx, $curr + 1)
};

declare function test:qname-lt($n1 as xs:QName, $n2 as xs:QName) as xs:boolean
{
  if ( fn:namespace-uri-from-QName($n1) eq fn:namespace-uri-from-QName($n2) ) then
    fn:local-name-from-QName($n1) lt fn:local-name-from-QName($n2)
  else
    fn:namespace-uri-from-QName($n1) lt fn:namespace-uri-from-QName($n2)
};

declare function test:report-sequence(
    $sequence as item()*,
    $wrapper-name as xs:string
  ) as element()
{
  test:report-sequence($sequence, $wrapper-name, ())
};

declare function test:report-sequence(
    $sequence as item()*,
    $wrapper-name as xs:string,
    $test as attribute(test)?
  ) as element()
{
  let $wrapper-ns as xs:string := 'http://www.jenitennison.com/xslt/xspec'

  let $attribute-nodes as attribute()* := $sequence[. instance of attribute()]
  let $document-nodes as document-node()* := $sequence[. instance of document-node()]
  let $namespace-nodes as node()* := $sequence[. instance of namespace-node()]
  let $text-nodes as text()* := $sequence[. instance of text()]

  let $report-element as element() :=
    element
      { fn:QName($wrapper-ns, $wrapper-name) }
      {
        $test,

        (
          (: Empty :)
          if (fn:empty($sequence))
          then attribute select { "()" }

          (: One or more atomic values :)
          else if ($sequence instance of xs:anyAtomicType+)
          then (
            let $atomic-value-reports as xs:string+ :=
              (for $value in $sequence return test:report-atomic-value($value))
            return attribute select { fn:string-join($atomic-value-reports, ', ') }
          )

          (: One or more nodes of the same type which can be a child of document node :)
          else if (
            ($sequence instance of comment()+)
            or ($sequence instance of element()+)
            or ($sequence instance of processing-instruction()+)
            or ($sequence instance of text()+)
          )
          then (
            attribute select { fn:concat('/', x:node-type($sequence[1]), '()') },
            for $node in $sequence return test:report-node($node)
          )

          (: Single document node :)
          else if ($sequence instance of document-node())
          then (
            (: People do not always notice '/' in the report HTML. So express it more verbosely.
              Also the expression must match the one in ../reporter/format-xspec-report.xsl. :)
            attribute select { "/self::document-node()" },
            test:report-node($sequence)
          )

          (: One or more nodes which can be stored in an element safely and without losing each position.
            Those nodes include document nodes and text nodes. By storing them in an element, they will
            be unwrapped and/or merged with adjacent nodes. When it happens, the report does not
            represent the sequence precisely. That's ok, because
              * Otherwise the report will be cluttered with pseudo elements.
              * XSpec in general including its test:deep-equal() inclines to merge them. :)
          else if (($sequence instance of node()+) and fn:not($attribute-nodes or $namespace-nodes))
          then (
            attribute select { "/node()" },
            for $node in $sequence return test:report-node($node)
          )

          (: Otherwise each item needs to be represented as a pseudo element :)
          else (
            attribute select {
              fn:concat(
                (: Select the pseudo elements :)
                '/*',

                (
                  (: If all items are instance of node, they can be expressed in @select.
                    (Document nodes are unwrapped, though.) :)
                  if ($sequence instance of node()+)
                  then (
                    let $expressions as xs:string+ := (
                      '@*'[$attribute-nodes],
                      'namespace::*'[$namespace-nodes],
                      'node()'[$sequence except ($attribute-nodes | $namespace-nodes)]
                    )
                    let $multi-expr as xs:boolean := (fn:count($expressions) ge 2)
                    return
                      fn:concat(
                        '/',
                        '('[$multi-expr],
                        fn:string-join($expressions, ' | '),
                        ')'[$multi-expr]
                      )
                  )
                  else (
                    (: Not all items can be expressed in @select. Just leave the pseudo elements selected. :)
                  )
                )
              )
            },

            for $item in $sequence
            return test:report-pseudo-item($item, $wrapper-ns)
          )
        )
      }

  (: Output the report element :)
  return (
    (: TODO: If too many nodes, save the report element as an external doc :)
    $report-element
  )
};

declare function test:report-pseudo-item(
    $item as item(),
    $wrapper-ns as xs:string
  ) as element()
{
  let $local-name-prefix as xs:string := 'pseudo-'
  return (
    if ($item instance of xs:anyAtomicType)
    then
      element
        { fn:QName($wrapper-ns, fn:concat($local-name-prefix, 'atomic-value')) }
        { test:report-atomic-value($item) }

    else if ($item instance of node())
    then
      element
        { fn:QName($wrapper-ns, fn:concat($local-name-prefix, x:node-type($item))) }
        { test:report-node($item) }

    (: TODO: function(*) including array(*) and map(*) :)

    else
      element
        { fn:QName($wrapper-ns, fn:concat($local-name-prefix, 'other')) }
        {}
  )
};

(:
  Copies the nodes while wrapping whitespace-only text nodes in <test:ws>
:)
declare function test:report-node(
    $node as node()
    ) as node()
{
  if ( ($node instance of text()) and fn:not(fn:normalize-space($node)) ) then
    element test:ws { $node }
  else if ( $node instance of document-node() ) then
    document {
      for $child in $node/child::node() return test:report-node($child)
    }
  else if ( $node instance of element() ) then
    element { fn:node-name($node) } {
      (
        for $prefix in fn:in-scope-prefixes($node)
          return namespace { $prefix } { fn:namespace-uri-for-prefix($prefix, $node) }
      ),
      $node/attribute(),
      (for $child in $node/child::node() return test:report-node($child))
    }
  else $node
};

declare function test:report-atomic-value($value as xs:anyAtomicType) as xs:string
{
  (: Derived types must be handled before their base types :)

  (: String types :)
  (: xs:normalizedString: Requires schema-aware processor :)
  if ( $value instance of xs:string ) then
    x:quote-with-apos($value)

  (: Derived numeric types: Requires schema-aware processor :)

  (: Numeric types which can be expressed as numeric literals:
    http://www.w3.org/TR/xpath20/#id-literals :)
  else if ( $value instance of xs:integer ) then
    fn:string($value)
  else if ( $value instance of xs:decimal ) then
    x:decimal-string($value)
  else if ( $value instance of xs:double ) then
    (: Do not report xs:double as a numeric literal. Report as xs:double() constructor instead.
      Justifications below.
      * Expression of xs:double as a numeric literal is a bit complicated:
        http://www.w3.org/TR/xpath-functions/#casting-to-string
      * xs:double is not used as frequently as xs:integer
      * xs:double() constructor is valid expression. It's just some more verbose than a numeric literal. :)
    test:report-atomic-value-as-constructor($value)

  else if ( $value instance of xs:QName ) then
    x:QName-expression($value)

  else
    test:report-atomic-value-as-constructor($value)
};

declare function test:report-atomic-value-as-constructor($value as xs:anyAtomicType) as xs:string
{
  (: Constructor usually has the same name as type :)
  let $constructor-name as xs:string := test:atom-type($value)

  (: Cast as either xs:integer or xs:string :)
  let $casted-value as xs:anyAtomicType := (
    if ( $value instance of xs:integer )
    then
      (: Force casting down to integer, by first converting to string :)
      (fn:string($value) cast as xs:integer)
    else
      fn:string($value)
  )

  (: Constructor parameter:
    Either numeric literal of integer or string literal :)
  let $costructor-param as xs:string :=
    test:report-atomic-value($casted-value)

  return fn:concat($constructor-name, '(', $costructor-param, ')')
};

declare function test:atom-type($value as xs:anyAtomicType) as xs:string
{
  (: Grouped as the spec does: http://www.w3.org/TR/xslt20/#built-in-types
    Groups are in the reversed order so that the derived types are before the primitive types,
    otherwise xs:integer is recognised as xs:decimal, xs:yearMonthDuration as xs:duration, and so on. :)

  (: A schema-aware XSLT processor additionally supports: :)

  (:    * All other built-in types defined in [XML Schema Part 2] :)
  (: Requires schema-aware processor :)

  (: Every XSLT 2.0 processor includes the following named type definitions in the in-scope schema components: :)

  (:    * The following types defined in [XPath 2.0] :)
  if ( $value instance of xs:yearMonthDuration ) then
    'xs:yearMonthDuration'
  else if ($value instance of xs:dayTimeDuration ) then
    'xs:dayTimeDuration'
  (: xs:anyAtomicType: Abstract :)
  (: xs:untyped: Not atomic :)
  else if ( $value instance of xs:untypedAtomic ) then
    'xs:untypedAtomic'

  (:    * The types xs:anyType and xs:anySimpleType. :)
  (: Not atomic :)

  (:    * The derived atomic type xs:integer defined in [XML Schema Part 2]. :)
  else if ( $value instance of xs:integer ) then
    'xs:integer'

  (:    * All the primitive atomic types defined in [XML Schema Part 2], with the exception of xs:NOTATION. :)
  else if ( $value instance of xs:string ) then
    'xs:string'
  else if ( $value instance of xs:boolean ) then
    'xs:boolean'
  else if ( $value instance of xs:decimal ) then
    'xs:decimal'
  else if ( $value instance of xs:double ) then
    'xs:double'
  else if ( $value instance of xs:float ) then
    'xs:float'
  else if ( $value instance of xs:date ) then
    'xs:date'
  else if ( $value instance of xs:time ) then
    'xs:time'
  else if ( $value instance of xs:dateTime ) then
    'xs:dateTime'
  else if ( $value instance of xs:duration ) then
    'xs:duration'
  else if ( $value instance of xs:QName ) then
    'xs:QName'
  else if ( $value instance of xs:anyURI ) then
    'xs:anyURI'
  else if ( $value instance of xs:gDay ) then
    'xs:gDay'
  else if ( $value instance of xs:gMonthDay ) then
    'xs:gMonthDay'
  else if ( $value instance of xs:gMonth ) then
    'xs:gMonth'
  else if ( $value instance of xs:gYearMonth ) then
    'xs:gYearMonth'
  else if ( $value instance of xs:gYear ) then
    'xs:gYear'
  else if ( $value instance of xs:base64Binary ) then
    'xs:base64Binary'
  else if ( $value instance of xs:hexBinary ) then
    'xs:hexBinary'
  else
    'xs:anyAtomicType'
};

(: ------------------------------------------------------------------------ :)
(:  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT.               :)
(:                                                                          :)
(:  Copyright (c) 2008, 2010 Jeni Tennison                                  :)
(:                                                                          :)
(:  The contents of this file are subject to the MIT License (see the URI   :)
(:  http://www.opensource.org/licenses/mit-license.php for details).        :)
(:                                                                          :)
(:  Permission is hereby granted, free of charge, to any person obtaining   :)
(:  a copy of this software and associated documentation files (the         :)
(:  "Software"), to deal in the Software without restriction, including     :)
(:  without limitation the rights to use, copy, modify, merge, publish,     :)
(:  distribute, sublicense, and/or sell copies of the Software, and to      :)
(:  permit persons to whom the Software is furnished to do so, subject to   :)
(:  the following conditions:                                               :)
(:                                                                          :)
(:  The above copyright notice and this permission notice shall be          :)
(:  included in all copies or substantial portions of the Software.         :)
(:                                                                          :)
(:  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,         :)
(:  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF      :)
(:  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  :)
(:  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY    :)
(:  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,    :)
(:  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE       :)
(:  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                  :)
(: ------------------------------------------------------------------------ :)


Added return types for two functions.

Added return types for two functions.

xquery version "1.0";

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

(:~
 : This module provides the functions defined by the JSONiq specification,
 : sections 1.7 (Functions) and 1.10 (Update Primitives). JSONiq extends
 : the XQuery specification to also deal with JSON data natively. See
 :
 :     http://www.jsoniq.org/
 :
 : for details.
 :
 : This module depends on having the JSONiq feature enabled in Zorba,
 : i.e., Zorba must be compiled with ZORBA_WITH_JSON.
 :
 : @author Markos Zaharioudakis, Matthias Brantner, Ghislain Fourny
 :)
module namespace jn = "http://www.jsoniq.org/functions";

import module namespace schema = "http://www.zorba-xquery.com/modules/schema";

declare namespace jdm = "http://www.jsoniq.org/";

declare namespace err = "http://www.w3.org/2005/xqt-errors";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "1.0";

(:~
 : This function has the same semantics as fn:parse-xml(), except that
 : it parses the string as JSON (not XML), and returns an Object or
 : Array rather than an XML document.
 :
 : @param $j A string containing a valid JSON text.
 : @return a JSON Object or Array item.
 :)
declare function jn:parse-json($j as xs:string) as json-item()? external;

(:~
 : Returns the names used in the object.
 : The names will be returned in an implementation-defined order
 :
 : @param $o A JSON Object.
 : @return The names of pairs in the object.
 :)
declare function jn:keys($o as object()) as xs:string* external;

(:~
 : Returns the value of a JSON Pair with a given name within a given JSON object.
 : If no such pair exists in the object, returns the empty sequence.
 :
 : @param $o A JSON Object.
 : @param $name The name of the pair whose value is to be retrieved
 : @return the value of specified pair within the given object, or the empty sequence.
 :)
(: obsolete - use $o($name) instead :)
declare function jn:value($o as object(), $name as xs:string) as item()? external;

(:~
 : Returns the size of a JSON Array. The size of an Array is
 : the number of members contained within it.
 :
 : @param $j A JSON Array.
 : @return The number of items in $j.
 :)
declare function jn:size($j as array()) as xs:integer external;

(:~
 : Returns the member of an Array at the specified position (starting from 1).
 : If the position is out of bounds of the array, returns the empty sequence.
 :
 : @param $a A JSON Array.
 : @param $p The position in the array.
 : @return The member at the specified position, or empty sequence.
 :)
(: obsolete - use $a($p) instead :)
declare function jn:member($a as array(), $p as xs:integer) as item()? external;

(:~
 : Creates an object from the specified pairs of another given object.
 : Specifically, for each name in $names, if the object $o has a pair with
 : that name, then a copy of that pair is included in the new object.
 :
 : @param $o A JSON Object.
 : @param $names The names of the pairs to copy out of $o and insert into the new object
 : @return The new object.
 :)
declare function jn:project($o as object(), $names as xs:string*) as object() external;

(:~
 : Returns the members of an Array.
 :
 : @param $a A JSON Array.
 : @return The members of the specified array.
 :)
declare function jn:members($o as array()) as item()* external;

(:~
 : Recursively "flatten" a JSON Array, by replacing any arrays with their
 : members. Equivalent to
 :
 :   define function jn:flatten($arg as array()) {
 :     for $value in jn:values($arg)
 :     return
 :       if ($value instance of array())
 :       then jn:flatten($value)
 :       else $value
 :   };
 :
 : @param $a A JSON Array.
 : @return The flattened version of $a.
 :)
declare function jn:flatten($a as array()) as item()* external;

(:~
 : This function allows dynamic object construction by merging all
 : its object parameters into a single object with a so-called "simple
 : object union". A simple object union creates a new object, the pairs
 : property of which is obtained by accumulating the pairs of all operand
 : objects. An error jerr:JNDY0003 is raised if two pairs with the same
 : name are encountered.
 :
 : @param $o A sequence of objects.
 : @return The simple object union.
 :)
declare function jn:object($o as object()*) as object()
{
  {| $o |}
};

(:~
 : This function dynamically builds an object, like jn:object, except that
 : it does not throw an error upon pair collision. Instead, it aggregates them
 : into an array.
 :
 : @param $o A sequence of objects.
 : @return The accumulated object.
 :)
declare function jn:accumulate($o as object()*) as object()
{
  {[ $o ]}
};

(:~
 : This function returns all Objects contained within a JSON item, regardless of
 : depth.
 :
 : @param $i A JSON item.
 : @return Its descendant objects.
 :)
declare function jn:descendant-objects($i as json-item()) as object()*
{
  if ($i instance of object())
  then
    (
      $i,
      for $v in jn:values($i)
      where $v instance of json-item()
      return jn:descendant-objects($v)
    )
  else if ($i instance of array())
  then
    (
      for $v in jn:members($i)
      where $v instance of json-item()
      return jn:descendant-objects($v)
    )
  else
    ()
};

(:~
 : This function returns all pairs contained within an object, recursively.
 :
 : @param $o An object.
 : @return All direct and indirect descendant pairs.
 :)
declare function jn:descendant-pairs($o as object()) as object()*
{
  for $k in jn:keys($o)
  return (
    { $k : $o($k) },
    if ($o($k) instance of object())
    then
      jn:descendant-pairs($o($k))
    else ()
  )
};

(:~ This function returns the intersection of two objects, and aggregates
 : values corresponding to the same name into an array.
 :
 : @param $o A sequence of objects.
 : @return Their insersection.
 :)
declare function jn:intersect($o as object()*) as object()
{
  {|
    let $common-keys := jn:keys($o[1])[ every $object in $o[position() > 1]
                                           satisfies jn:keys($object) = . ]
    for $key in $common-keys
    let $values := $o($key)
    return
      if (count($values) eq 1)
      then { $key : $values }
      else { $key : [ $values ] }
  |}
};

(:~
 : This functions returns all values in an Object.
 : @param $i An object.
 : @return Its values.
 :)
declare function jn:values($i as object()) as item()*
{
  for $k in jn:keys($i)
  return $i($k)
};

xquery version "1.0";

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

(:~
 : This module provides the functions defined by the JSONiq specification,
 : sections 1.7 (Functions) and 1.10 (Update Primitives). JSONiq extends
 : the XQuery specification to also deal with JSON data natively. See
 :
 :     http://www.jsoniq.org/
 :
 : for details.
 :
 : This module depends on having the JSONiq feature enabled in Zorba,
 : i.e., Zorba must be compiled with ZORBA_WITH_JSON.
 :
 : @author Markos Zaharioudakis, Matthias Brantner, Ghislain Fourny
 :)
module namespace jn = "http://www.jsoniq.org/functions";

import module namespace schema = "http://www.zorba-xquery.com/modules/schema";

declare namespace jdm = "http://www.jsoniq.org/";

declare namespace err = "http://www.w3.org/2005/xqt-errors";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "1.0";

(:~
 : This function has the same semantics as fn:parse-xml(), except that
 : it parses the string as JSON (not XML), and returns an Object or
 : Array rather than an XML document.
 :
 : @param $j A string containing a valid JSON text.
 : @return a JSON Object or Array item.
 :)
declare function jn:parse-json($j as xs:string) as json-item()? external;

(:~
 : Returns the names used in the object.
 : The names will be returned in an implementation-defined order
 :
 : @param $o A JSON Object.
 : @return The names of pairs in the object.
 :)
declare function jn:keys($o as object()) as xs:string* external;

(:~
 : Returns the value of a JSON Pair with a given name within a given JSON object.
 : If no such pair exists in the object, returns the empty sequence.
 :
 : @param $o A JSON Object.
 : @param $name The name of the pair whose value is to be retrieved
 : @return the value of specified pair within the given object, or the empty sequence.
 :)
(: obsolete - use $o($name) instead :)
declare function jn:value($o as object(), $name as xs:string) as item()? external;

(:~
 : Returns the size of a JSON Array. The size of an Array is
 : the number of members contained within it.
 :
 : @param $j A JSON Array.
 : @return The number of items in $j.
 :)
declare function jn:size($j as array()) as xs:integer external;

(:~
 : Returns the member of an Array at the specified position (starting from 1).
 : If the position is out of bounds of the array, returns the empty sequence.
 :
 : @param $a A JSON Array.
 : @param $p The position in the array.
 : @return The member at the specified position, or empty sequence.
 :)
(: obsolete - use $a($p) instead :)
declare function jn:member($a as array(), $p as xs:integer) as item()? external;

(:~
 : Creates an object from the specified pairs of another given object.
 : Specifically, for each name in $names, if the object $o has a pair with
 : that name, then a copy of that pair is included in the new object.
 :
 : @param $o A JSON Object.
 : @param $names The names of the pairs to copy out of $o and insert into the new object
 : @return The new object.
 :)
declare function jn:project($o as object(), $names as xs:string*) as object() external;

(:~
 : Returns the members of an Array.
 :
 : @param $a A JSON Array.
 : @return The members of the specified array.
 :)
declare function jn:members($o as array()) as item()* external;

(:~
 : Recursively "flatten" a JSON Array, by replacing any arrays with their
 : members. Equivalent to
 :
 :   define function jn:flatten($arg as array()) {
 :     for $value in jn:values($arg)
 :     return
 :       if ($value instance of array())
 :       then jn:flatten($value)
 :       else $value
 :   };
 :
 : @param $a A JSON Array.
 : @return The flattened version of $a.
 :)
declare function jn:flatten($a as array()) as item()* external;

(:~
 : This function allows dynamic object construction by merging all
 : its object parameters into a single object with a so-called "simple
 : object union". A simple object union creates a new object, the pairs
 : property of which is obtained by accumulating the pairs of all operand
 : objects. An error jerr:JNDY0003 is raised if two pairs with the same
 : name are encountered.
 :
 : @param $o A sequence of objects.
 : @return The simple object union.
 :)
declare function jn:object($o as object()*) as object()
{
  {| $o |}
};

(:~
 : This function dynamically builds an object, like jn:object, except that
 : it does not throw an error upon pair collision. Instead, it aggregates them
 : into an array.
 :
 : @param $o A sequence of objects.
 : @return The accumulated object.
 :)
declare function jn:accumulate($o as object()*) as object()
{
  {[ $o ]}
};

(:~
 : This function returns all Objects contained within a JSON item, regardless of
 : depth.
 :
 : @param $i A JSON item.
 : @return Its descendant objects.
 :)
declare function jn:descendant-objects($i as json-item()) as object()*
{
  if ($i instance of object())
  then
    (
      $i,
      for $v in jn:values($i)
      where $v instance of json-item()
      return jn:descendant-objects($v)
    )
  else if ($i instance of array())
  then
    (
      for $v in jn:members($i)
      where $v instance of json-item()
      return jn:descendant-objects($v)
    )
  else
    ()
};

(:~
 : This function returns all pairs contained within an object, recursively.
 :
 : @param $o An object.
 : @return All direct and indirect descendant pairs.
 :)
declare function jn:descendant-pairs($o as object())
{
  for $k in jn:keys($o)
  return (
    { $k : $o($k) },
    if ($o($k) instance of object())
    then
      jn:descendant-pairs($o($k))
    else ()
  )
};

(:~ This function returns the intersection of two objects, and aggregates
 : values corresponding to the same name into an array.
 :
 : @param $o A sequence of objects.
 : @return Their insersection.
 :)
declare function jn:intersect($o as object()*)
{
  {|
    let $common-keys := jn:keys($o[1])[ every $object in $o[position() > 1]
                                           satisfies jn:keys($object) = . ]
    for $key in $common-keys
    let $values := $o($key)
    return
      if (count($values) eq 1)
      then { $key : $values }
      else { $key : [ $values ] }
  |}
};

(:~
 : This functions returns all values in an Object.
 : @param $i An object.
 : @return Its values.
 :)
declare function jn:values($i as object()) as item()*
{
  for $k in jn:keys($i)
  return $i($k)
};


added utility function: replace-multi()

added utility function: replace-multi()

module namespace repo-utils = "http://aac.ac.at/content_repository/utils";

import module namespace xdb="http://exist-db.org/xquery/xmldb";
import module namespace diag =  "http://www.loc.gov/zing/srw/diagnostic/" at  "modules/diagnostics/diagnostics.xqm";
import module namespace request="http://exist-db.org/xquery/request";

(:~ HELPER functions - configuration, caching, data-access
:)

(:
: this all cannot be defined as global variable, because we want different configurations,
: thus the $config has to be sent as param everywhere
: declare variable $repo-utils:config := doc("config.xml");
: declare variable $repo-utils:mappings := doc(repo-utils:config-value('mappings'));
: declare variable $repo-utils:data-collection := collection(repo-utils:config-value('data.path'));
: declare variable $repo-utils:md-collection := collection(repo-utils:config-value('metadata.path'));
: declare variable $repo-utils:cachePath as xs:string := repo-utils:config-value('cache.path');
:)

declare variable $repo-utils:xmlExt as xs:string := ".xml";
declare variable $repo-utils:responseFormatXml as xs:string := "xml";
declare variable $repo-utils:responseFormatJSon as xs:string := "json";
declare variable $repo-utils:responseFormatText as xs:string := "text";
declare variable $repo-utils:responseFormatHTML as xs:string := "html";
declare variable $repo-utils:responseFormatHTMLpage as xs:string := "htmlpage";

declare variable $repo-utils:sys-config-file := "conf/config-system.xml";

(:~ not solved yet - look at cr-xq/core/config.xqm: config:param-value('base-url') !  :)
declare function repo-utils:base-url($config as item()*) as xs:string* {
    (:let $server-base := if (repo-utils:config-value($config, 'server.base') = '') then ''  else repo-utils:config-value($config, 'server.base')
    let $config-base-url := if (repo-utils:config-value($config, 'base.url') = '') then request:get-uri() else repo-utils:config-value($config, 'base.url')
    return concat($server-base, $config-base-url):)
    let $url := request:get-url()
    return $url
};

declare function repo-utils:config($config-file as xs:string) as node()* {
let $sys-config := if (doc-available($repo-utils:sys-config-file)) then doc($repo-utils:sys-config-file) else (),
    $config := if (doc-available($config-file)) then (doc($config-file), $sys-config)
                        else diag:diagnostics("general-error", concat("config not available: ", $config-file))
    return $config
};

declare function repo-utils:config-value($config, $key as xs:string) as xs:string* {
    ($config/config[not(@type='system' or @type='module')]//(param|property)[@key=$key],
      $config/config[@type='module']//(param|property)[@key=$key],
      $config/config[@type='system']//(param|property)[@key=$key])[1]
};

(:declare function repo-utils:config-value($config, $key as xs:string) as xs:string* {
    ($config[not(@type='system')]//(param|property)[@key=$key], $config[@type='system']//property[@key=$key])[1]
};
:)
(:~ Get value of a param based on a key, from config or from request-param (precedence) :)
declare function repo-utils:param-value($config, $key as xs:string, $default as xs:string) as xs:string* {

    let $param := request:get-parameter($key, $default)
    return if ($param) then $param else $config//(param|property)[@key=$key]
};

(:~ returns db-collection (as nodeset) based on the identifier in x-context, looked up in the mapping or default collection as defined in config

@returns nodeset of given context
 if no x-context match or no context provided returns default collection from the config (data.path )
 (if available, otherwise again empty result)

WATCHME: changed to lax handling of the context (if no match - go to default)
        gives better recall, but may confuse - alternatively: strict: empty result if x-context does not match
TODO: accept $x-context as xs:string*
:)
declare function repo-utils:context-to-collection($x-context as xs:string, $config) as node()* {
(:      let $mappings := doc(repo-utils:config-value($config, 'mappings')):)
      let $dbcoll-path := repo-utils:context-to-collection-path($x-context, $config)
    return if ($dbcoll-path eq "" ) then ()
                else collection($dbcoll-path)
};

(:~ returns path to db-collection (as nodeset) based on the identifier in x-context,
looked up in the mapping or default data.path as defined in config

@returns dbcoll-path of given context
 if no x-context match or no context provided returns default collection from the config (data.path )
 (if available, otherwise again empty result)

WATCHME: changed to lax handling of the context (if no match - go to default)
        gives better recall, but may confuse - alternatively: strict: empty result if x-context does not match
changed again: only give default if x-context is empty string or 'default'-string.
T
:)
declare function repo-utils:context-to-collection-path($x-context as xs:string, $config) as xs:string {
      let $mappings:= doc(repo-utils:config-value($config, 'mappings'))
    return    (: if ($x-context) then :)
            if (exists($mappings//map[xs:string(@key) eq $x-context]/@path)) then
                    $mappings//map[xs:string(@key) eq $x-context]/xs:string(@path)
                (: else "" :)
                  else if (exists(repo-utils:config-value($config, 'data.path')) and
                            ($x-context = ('', 'default'))) then
                        repo-utils:config-value($config, 'data.path')
                  else ""
};

(:~ Get the resource by PID/handle/URL or some known identifier.
  TODO: NOT ADAPTED YET! (taken from CMD)
  TODO: be clear about resource itself and metadata-record!
:)
(:declare function repo-utils:get-resource-by-id($id as xs:string) as node()* {
  let $collection := collection($cr:dataPath)
  return
    if ($id eq "" or $id eq $cr:collectionRoot) then
    $collection//IsPartOf[. = $cr:collectionRoot]/ancestor::CMD
  else
    util:eval(concat("$collection/ft:query(descendant::MdSelfLink, <term>", xdb:decode($id), "</term>)/ancestor::CMD"))
 (\: $collection/descendant::MdSelfLink[. = xdb:decode($id)]/ancestor::CMD :\)
};
:)

(:~ Checks whether the document is available or not.
  generic, currently not used
:)
declare function repo-utils:is-doc-available($collection as xs:string, $doc-name as xs:string) as xs:boolean {
  fn:doc-available(fn:concat($collection, "/", $doc-name))
};

declare function repo-utils:is-in-cache($doc-name as xs:string,$config) as xs:boolean {
  fn:doc-available(fn:concat(repo-utils:config-value($config, 'cache.path'), "/", $doc-name))
};

declare function repo-utils:get-from-cache($doc-name as xs:string,$config) as item()* {
      fn:doc(fn:concat(repo-utils:config-value($config, 'cache.path'), "/", $doc-name))
};

(:~ Store the data in cache. Uses own writer-account
:)
declare function repo-utils:store-in-cache($doc-name as xs:string, $data as node(),$config) as item()* {
  let $clarin-writer := fn:doc("/db/cr/writer.xml"),
  $cache-path := repo-utils:config-value($config, 'cache.path'),
  $dummy := xdb:login($cache-path, $clarin-writer//write-user/text(), $clarin-writer//write-user-cred/text()),
  $store := (: util:catch("org.exist.xquery.XPathException", :) xdb:store($cache-path, $doc-name, $data), (: , ()) :)
  $stored-doc := fn:doc(concat($cache-path, "/", $doc-name))
  return $stored-doc
(:  ():)
};

(:
this tries to create sub-collections in index for
individual data-collection - but it makes the retrieval etc. also more complicated
so rather encoding the data-collection also in the name

declare function repo-utils:store-in-cache($data-collection as item(), $doc-name as xs:string, $data as node()) as item()* {
    let $data-coll-name := collection-name($data-collection)
    let $index-coll-path := concat($repo-utils:cachePath, "/", $data-coll-name )
   let $create-coll := if (not(xmldb:collection-available($index-coll-path ))) then
                           xmldb:create-collection($repo-utils:cachePath, $data-collname) else ()
  let $store-result := repo-utils:store($index-coll-path, $doc-name, $data, true())

return $store-result
};
:)

(:<options><option key="update">yes</option></options>:)
declare function repo-utils:store($collection as xs:string, $doc-name as xs:string, $data as node(), $overwrite as xs:boolean) as item()* {
  let $clarin-writer := fn:doc("writer.xml"),
  $dummy := xdb:login($collection, $clarin-writer//write-user/text(), $clarin-writer//write-user-cred/text())

  let $rem := if ($overwrite and doc-available(concat($collection, $doc-name))) then xdb:remove($collection, $doc-name) else ()

  let $store := (: util:catch("org.exist.xquery.XPathException", :) xdb:store($collection, $doc-name, $data), (: , ()) :)
  $stored-doc := fn:doc(concat($collection, "/", $doc-name))
  return $stored-doc
};

declare function repo-utils:gen-cache-id($type-name as xs:string, $keys as xs:string+, $depth as xs:string) as xs:string {
    repo-utils:gen-cache-id($type-name , ($keys, $depth) )
};
(:~ Create document name with md5-hash for selected collections (or types) for reuse.
:)
declare function repo-utils:gen-cache-id($type-name as xs:string, $keys as xs:string+) as xs:string {
       let $sanitized-names := for $key in $keys return repo-utils:sanitize-name($key)
    return
(:    fn:concat($name-prefix, "-", util:hash(string-join($sorted-names, ""), "MD5"), $repo-utils:xmlExt):)
    fn:concat($type-name, "-", string-join($sanitized-names, "-"), $repo-utils:xmlExt)
};

(:~ wipes out problematic characters from names
:)
declare function repo-utils:sanitize-name($name as xs:string) as xs:string {
    translate($name, ":/",'_')
};

declare function repo-utils:serialise-as($item as node()?, $format as xs:string, $operation as xs:string, $config) as item()? {
    repo-utils:serialise-as($item, $format, $operation, $config, ())
};

(:~ transform result to HTML, JSON, or leave as XML - according to format parameter

@param $parameters optional additional parameters to be passed to xsl
:)
declare function repo-utils:serialise-as($item as node()?, $format as xs:string, $operation as xs:string, $config, $parameters as node()* ) as item()? {
        if ($format eq $repo-utils:responseFormatJSon) then

	       let $xslDoc := repo-utils:xsl-doc($operation, $format, $config )
	       let $res := if ($xslDoc) then
	                           let $option := util:declare-option("exist:serialize", "method=text media-type=application/json")
	                           return       transform:transform($item,$xslDoc,
                                 			<parameters><param name="format" value="{$format}"/>
                                 			            <param name="x-context" value="{repo-utils:param-value($config, 'x-context', '' )}"/>
                                 			            <param name="base_url" value="{repo-utils:base-url($config)}"/>
                                 			            <param name="mappings-file" value="{repo-utils:config-value($config, 'mappings')}"/>
                                 			            <param name="scripts_url" value="{repo-utils:config-value($config, 'scripts.url')}"/>
                                 			             <param name="site_name" value="{repo-utils:config-value($config, 'site.name')}"/>
                                 			             <param name="site_logo" value="{repo-utils:config-value($config, 'site.logo')}"/>
                                 			             {$parameters/param}
                                 			</parameters>)
                            else
                                let $option := util:declare-option("exist:serialize", "method=json media-type=application/json")
                                return $item
	       return $res
	    else if (contains($format, $repo-utils:responseFormatHTML)) then
	           let $xslDoc := repo-utils:xsl-doc($operation, $format, $config )
	           let $res := transform:transform($item,$xslDoc,
              			<parameters><param name="format" value="{$format}"/>
              			            <param name="x-context" value="{repo-utils:param-value($config, 'x-context', '' )}"/>
              			            <param name="base_url" value="{repo-utils:base-url($config)}"/>
              			            <param name="mappings-file" value="{repo-utils:config-value($config, 'mappings')}"/>
              			            <param name="scripts_url" value="{repo-utils:config-value($config, 'scripts.url')}"/>
              			             <param name="site_name" value="{repo-utils:config-value($config, 'site.name')}"/>
              			             <param name="site_logo" value="{repo-utils:config-value($config, 'site.logo')}"/>
              			             {$parameters/param}
              			</parameters>)
(:               let $option := util:declare-option("exist:serialize", "method=xml media-type=text/html"):)
let $option := util:declare-option("exist:serialize", "method=xhtml media-type=text/html")
	           return $res
	   else
	       let $option := util:declare-option("exist:serialize", "method=xml media-type=application/xml")
	    return $item
};
(: $repo-utils:responseFormatXml, $repo-utils:responseFormatText:)

	    (:let $option :=
	              if (contains($format, $repo-utils:responseFormatHTML)) then
	                  util:declare-option("exist:serialize", "method=xml media-type=text/xhtml")
	              else
    	              util:declare-option("exist:serialize", "method=xml media-type=application/xml")
:)

(:~ get appropriate xsl-stylesheet (based on operation and format)
preference for a specific for operation-format,
default: operation :)
declare function repo-utils:xsl-doc($operation as xs:string, $format as xs:string, $config) as item()? {
    let $scripts-path := repo-utils:config-value($config, 'scripts.path'),
        $xsldoc := if (doc-available(concat($scripts-path, repo-utils:config-value($config, concat($operation, '-', $format, ".xsl"))))) then
                          doc(concat($scripts-path, repo-utils:config-value($config, concat($operation, '-', $format, ".xsl"))))
                      else if (doc-available(concat($scripts-path, repo-utils:config-value($config, concat($operation, ".xsl"))))) then
                            doc (concat($scripts-path, repo-utils:config-value($config, concat($operation, ".xsl"))))
                       else ()
        return $xsldoc
};
	          	(: $item :)

(:~ helper function. Performs multiple replacements, using pairs of replace parameters. based on standard xpath2 function replace()
taken from: http://www.xqueryfunctions.com/xq/functx_replace-multi.html
:)
declare function repo-utils:replace-multi   ( $arg as xs:string? ,    $changeFrom as xs:string* ,    $changeTo as xs:string* )  as xs:string? {

   if (count($changeFrom) > 0)
   then repo-utils:replace-multi(
          replace($arg, $changeFrom[1],
                  repo-utils:if-absent($changeTo[1],'')),
          $changeFrom[position() > 1],
          $changeTo[position() > 1])
   else $arg
 } ;

 (:~ used by replace-multi()
taken from: http://www.xqueryfunctions.com/xq/functx_if-absent.html
:)
declare function repo-utils:if-absent ( $arg as item()* , $value as item()* )  as item()* {
    if (exists($arg)) then $arg else $value
 } ;

(:
testing when trying to log import 2012-10-24 - not used
declare function repo-utils:write-log ($action as xs:string, $dataset as xs:string) {

			let $log-doc := repo-utils:config
			let $data := <log timestamp="{$timestamp}">{current-dateTime()}
			             </log>
			  return	xmldb:store($data-path, "test.log", $data )

	let $time := util:system-dateTime()
    let $upd-dummy :=
        update insert <xpath key="{$xpath/@key}" label="{$xpath/@label}" dur="{$duration}">{$answer}</xpath> into $result-doc/result

    return $result-doc

};:)

module namespace repo-utils = "http://aac.ac.at/content_repository/utils";

import module namespace xdb="http://exist-db.org/xquery/xmldb";
import module namespace diag =  "http://www.loc.gov/zing/srw/diagnostic/" at  "modules/diagnostics/diagnostics.xqm";
import module namespace request="http://exist-db.org/xquery/request";

(:~ HELPER functions - configuration, caching, data-access
:)

(:
: this all cannot be defined as global variable, because we want different configurations,
: thus the $config has to be sent as param everywhere
: declare variable $repo-utils:config := doc("config.xml");
: declare variable $repo-utils:mappings := doc(repo-utils:config-value('mappings'));
: declare variable $repo-utils:data-collection := collection(repo-utils:config-value('data.path'));
: declare variable $repo-utils:md-collection := collection(repo-utils:config-value('metadata.path'));
: declare variable $repo-utils:cachePath as xs:string := repo-utils:config-value('cache.path');
:)

declare variable $repo-utils:xmlExt as xs:string := ".xml";
declare variable $repo-utils:responseFormatXml as xs:string := "xml";
declare variable $repo-utils:responseFormatJSon as xs:string := "json";
declare variable $repo-utils:responseFormatText as xs:string := "text";
declare variable $repo-utils:responseFormatHTML as xs:string := "html";
declare variable $repo-utils:responseFormatHTMLpage as xs:string := "htmlpage";

declare variable $repo-utils:sys-config-file := "conf/config-system.xml";

(:~ not solved yet - look at cr-xq/core/config.xqm: config:param-value('base-url') !  :)
declare function repo-utils:base-url($config) as xs:string* {
    (:let $server-base := if (repo-utils:config-value($config, 'server.base') = '') then ''  else repo-utils:config-value($config, 'server.base')
    let $config-base-url := if (repo-utils:config-value($config, 'base.url') = '') then request:get-uri() else repo-utils:config-value($config, 'base.url')
    return concat($server-base, $config-base-url):)
    let $url := request:get-url()
    return $url
};

declare function repo-utils:config($config-file as xs:string) as node()* {
let $sys-config := if (doc-available($repo-utils:sys-config-file)) then doc($repo-utils:sys-config-file) else (),
    $config := if (doc-available($config-file)) then (doc($config-file), $sys-config)
                        else diag:diagnostics("general-error", concat("config not available: ", $config-file))
    return $config
};

declare function repo-utils:config-value($config, $key as xs:string) as xs:string* {
    ($config/config[not(@type='system' or @type='module')]//(param|property)[@key=$key],
      $config/config[@type='module']//(param|property)[@key=$key],
      $config/config[@type='system']//(param|property)[@key=$key])[1]
};

(:declare function repo-utils:config-value($config, $key as xs:string) as xs:string* {
    ($config[not(@type='system')]//(param|property)[@key=$key], $config[@type='system']//property[@key=$key])[1]
};
:)
(:~ Get value of a param based on a key, from config or from request-param (precedence) :)
declare function repo-utils:param-value($config, $key as xs:string, $default as xs:string) as xs:string* {

    let $param := request:get-parameter($key, $default)
    return if ($param) then $param else $config//(param|property)[@key=$key]
};

(:~ returns db-collection (as nodeset) based on the identifier in x-context, looked up in the mapping or default collection as defined in config

@returns nodeset of given context
 if no x-context match or no context provided returns default collection from the config (data.path )
 (if available, otherwise again empty result)

WATCHME: changed to lax handling of the context (if no match - go to default)
        gives better recall, but may confuse - alternatively: strict: empty result if x-context does not match
TODO: accept $x-context as xs:string*
:)
declare function repo-utils:context-to-collection($x-context as xs:string, $config) as node()* {
(:      let $mappings := doc(repo-utils:config-value($config, 'mappings')):)
      let $dbcoll-path := repo-utils:context-to-collection-path($x-context, $config)
    return if ($dbcoll-path eq "" ) then ()
                else collection($dbcoll-path)
};

(:~ returns path to db-collection (as nodeset) based on the identifier in x-context,
looked up in the mapping or default data.path as defined in config

@returns dbcoll-path of given context
 if no x-context match or no context provided returns default collection from the config (data.path )
 (if available, otherwise again empty result)

WATCHME: changed to lax handling of the context (if no match - go to default)
        gives better recall, but may confuse - alternatively: strict: empty result if x-context does not match
changed again: only give default if x-context is empty string or 'default'-string.
T
:)
declare function repo-utils:context-to-collection-path($x-context as xs:string, $config) as xs:string {
      let $mappings:= doc(repo-utils:config-value($config, 'mappings'))
    return    (: if ($x-context) then :)
            if (exists($mappings//map[xs:string(@key) eq $x-context]/@path)) then
                    $mappings//map[xs:string(@key) eq $x-context]/xs:string(@path)
                (: else "" :)
                  else if (exists(repo-utils:config-value($config, 'data.path')) and
                            ($x-context = ('', 'default'))) then
                        repo-utils:config-value($config, 'data.path')
                  else ""
};

(:~ Get the resource by PID/handle/URL or some known identifier.
  TODO: NOT ADAPTED YET! (taken from CMD)
  TODO: be clear about resource itself and metadata-record!
:)
(:declare function repo-utils:get-resource-by-id($id as xs:string) as node()* {
  let $collection := collection($cr:dataPath)
  return
    if ($id eq "" or $id eq $cr:collectionRoot) then
    $collection//IsPartOf[. = $cr:collectionRoot]/ancestor::CMD
  else
    util:eval(concat("$collection/ft:query(descendant::MdSelfLink, <term>", xdb:decode($id), "</term>)/ancestor::CMD"))
 (\: $collection/descendant::MdSelfLink[. = xdb:decode($id)]/ancestor::CMD :\)
};
:)

(:~ Checks whether the document is available or not.
  generic, currently not used
:)
declare function repo-utils:is-doc-available($collection as xs:string, $doc-name as xs:string) as xs:boolean {
  fn:doc-available(fn:concat($collection, "/", $doc-name))
};

declare function repo-utils:is-in-cache($doc-name as xs:string,$config) as xs:boolean {
  fn:doc-available(fn:concat(repo-utils:config-value($config, 'cache.path'), "/", $doc-name))
};

declare function repo-utils:get-from-cache($doc-name as xs:string,$config) as item()* {
      fn:doc(fn:concat(repo-utils:config-value($config, 'cache.path'), "/", $doc-name))
};

(:~ Store the data in cache. Uses own writer-account
:)
declare function repo-utils:store-in-cache($doc-name as xs:string, $data as node(),$config) as item()* {
  let $clarin-writer := fn:doc("/db/cr/writer.xml"),
  $cache-path := repo-utils:config-value($config, 'cache.path'),
  $dummy := xdb:login($cache-path, $clarin-writer//write-user/text(), $clarin-writer//write-user-cred/text()),
  $store := (: util:catch("org.exist.xquery.XPathException", :) xdb:store($cache-path, $doc-name, $data), (: , ()) :)
  $stored-doc := fn:doc(concat($cache-path, "/", $doc-name))
  return $stored-doc
(:  ():)
};

(:
this tries to create sub-collections in index for
individual data-collection - but it makes the retrieval etc. also more complicated
so rather encoding the data-collection also in the name

declare function repo-utils:store-in-cache($data-collection as item(), $doc-name as xs:string, $data as node()) as item()* {
    let $data-coll-name := collection-name($data-collection)
    let $index-coll-path := concat($repo-utils:cachePath, "/", $data-coll-name )
   let $create-coll := if (not(xmldb:collection-available($index-coll-path ))) then
                           xmldb:create-collection($repo-utils:cachePath, $data-collname) else ()
  let $store-result := repo-utils:store($index-coll-path, $doc-name, $data, true())

return $store-result
};
:)

(:<options><option key="update">yes</option></options>:)
declare function repo-utils:store($collection as xs:string, $doc-name as xs:string, $data as node(), $overwrite as xs:boolean) as item()* {
  let $clarin-writer := fn:doc("writer.xml"),
  $dummy := xdb:login($collection, $clarin-writer//write-user/text(), $clarin-writer//write-user-cred/text())

  let $rem := if ($overwrite and doc-available(concat($collection, $doc-name))) then xdb:remove($collection, $doc-name) else ()

  let $store := (: util:catch("org.exist.xquery.XPathException", :) xdb:store($collection, $doc-name, $data), (: , ()) :)
  $stored-doc := fn:doc(concat($collection, "/", $doc-name))
  return $stored-doc
};

declare function repo-utils:gen-cache-id($type-name as xs:string, $keys as xs:string+, $depth as xs:string) as xs:string {
    repo-utils:gen-cache-id($type-name , ($keys, $depth) )
};
(:~ Create document name with md5-hash for selected collections (or types) for reuse.
:)
declare function repo-utils:gen-cache-id($type-name as xs:string, $keys as xs:string+) as xs:string {
       let $sanitized-names := for $key in $keys return repo-utils:sanitize-name($key)
    return
(:    fn:concat($name-prefix, "-", util:hash(string-join($sorted-names, ""), "MD5"), $repo-utils:xmlExt):)
    fn:concat($type-name, "-", string-join($sanitized-names, "-"), $repo-utils:xmlExt)
};

(:~ wipes out problematic characters from names
:)
declare function repo-utils:sanitize-name($name as xs:string) as xs:string {
    translate($name, ":/",'_')
};

declare function repo-utils:serialise-as($item as node()?, $format as xs:string, $operation as xs:string, $config) as item()? {
    repo-utils:serialise-as($item, $format, $operation, $config, ())
};

(:~ transform result to HTML, JSON, or leave as XML - according to format parameter

@param $parameters optional additional parameters to be passed to xsl
:)
declare function repo-utils:serialise-as($item as node()?, $format as xs:string, $operation as xs:string, $config, $parameters as node()* ) as item()? {
        if ($format eq $repo-utils:responseFormatJSon) then

	       let $xslDoc := repo-utils:xsl-doc($operation, $format, $config )
	       let $res := if ($xslDoc) then
	                           let $option := util:declare-option("exist:serialize", "method=text media-type=application/json")
	                           return       transform:transform($item,$xslDoc,
                                 			<parameters><param name="format" value="{$format}"/>
                                 			            <param name="x-context" value="{repo-utils:param-value($config, 'x-context', '' )}"/>
                                 			            <param name="base_url" value="{repo-utils:base-url($config)}"/>
                                 			            <param name="mappings-file" value="{repo-utils:config-value($config, 'mappings')}"/>
                                 			            <param name="scripts_url" value="{repo-utils:config-value($config, 'scripts.url')}"/>
                                 			             <param name="site_name" value="{repo-utils:config-value($config, 'site.name')}"/>
                                 			             <param name="site_logo" value="{repo-utils:config-value($config, 'site.logo')}"/>
                                 			             {$parameters/param}
                                 			</parameters>)
                            else
                                let $option := util:declare-option("exist:serialize", "method=json media-type=application/json")
                                return $item
	       return $res
	    else if (contains($format, $repo-utils:responseFormatHTML)) then
	           let $xslDoc := repo-utils:xsl-doc($operation, $format, $config )
	           let $res := transform:transform($item,$xslDoc,
              			<parameters><param name="format" value="{$format}"/>
              			            <param name="x-context" value="{repo-utils:param-value($config, 'x-context', '' )}"/>
              			            <param name="base_url" value="{repo-utils:base-url($config)}"/>
              			            <param name="mappings-file" value="{repo-utils:config-value($config, 'mappings')}"/>
              			            <param name="scripts_url" value="{repo-utils:config-value($config, 'scripts.url')}"/>
              			             <param name="site_name" value="{repo-utils:config-value($config, 'site.name')}"/>
              			             <param name="site_logo" value="{repo-utils:config-value($config, 'site.logo')}"/>
              			             {$parameters/param}
              			</parameters>)
(:               let $option := util:declare-option("exist:serialize", "method=xml media-type=text/html"):)
let $option := util:declare-option("exist:serialize", "method=xhtml media-type=text/html")
	           return $res
	   else
	       let $option := util:declare-option("exist:serialize", "method=xml media-type=application/xml")
	    return $item
};
(: $repo-utils:responseFormatXml, $repo-utils:responseFormatText:)

	    (:let $option :=
	              if (contains($format, $repo-utils:responseFormatHTML)) then
	                  util:declare-option("exist:serialize", "method=xml media-type=text/xhtml")
	              else
    	              util:declare-option("exist:serialize", "method=xml media-type=application/xml")
:)

(:~ get appropriate xsl-stylesheet (based on operation and format)
preference for a specific for operation-format,
default: operation :)
declare function repo-utils:xsl-doc($operation as xs:string, $format as xs:string, $config) as item()? {
    let $scripts-path := repo-utils:config-value($config, 'scripts.path'),
        $xsldoc := if (doc-available(concat($scripts-path, repo-utils:config-value($config, concat($operation, '-', $format, ".xsl"))))) then
                          doc(concat($scripts-path, repo-utils:config-value($config, concat($operation, '-', $format, ".xsl"))))
                      else if (doc-available(concat($scripts-path, repo-utils:config-value($config, concat($operation, ".xsl"))))) then
                            doc (concat($scripts-path, repo-utils:config-value($config, concat($operation, ".xsl"))))
                       else ()
        return $xsldoc
};
	          	(: $item :)

(:
testing when trying to log import 2012-10-24 - not used
declare function repo-utils:write-log ($action as xs:string, $dataset as xs:string) {

			let $log-doc := repo-utils:config
			let $data := <log timestamp="{$timestamp}">{current-dateTime()}
			             </log>
			  return	xmldb:store($data-path, "test.log", $data )

	let $time := util:system-dateTime()
    let $upd-dummy :=
        update insert <xpath key="{$xpath/@key}" label="{$xpath/@label}" dur="{$duration}">{$answer}</xpath> into $result-doc/result

    return $result-doc

};:)


BaseX: The ft:tokenize and ft:normalize string parameter allows an empty sequence in BaseX 9.1.

xquery version "3.1";
(:~
 : BaseX full-text functions
 :
 : @see http://docs.basex.org/Full-Text_Module
 :)
module namespace ft = "http://basex.org/modules/ft";

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

declare option o:requires "basex/7.0"; (: NOTE: 7.0 is the earliest version definitions are available for. :)

declare type ft-string = (
    %a:since("basex", "7.0") %a:until("basex", "9.1") for xs:string |
    %a:since("basex", "9.1") for xs:string?
);

declare %a:since("basex", "7.0") function ft:search($db as xs:string, $terms as item()*) as text()* external;
declare %a:since("basex", "7.2") function ft:search($db as xs:string, $terms as item()*, $options as map(*)?) as text()* external;
declare %a:since("basex", "7.8") function ft:contains($input as item()*, $terms as item()*) as xs:boolean external;
declare %a:since("basex", "7.8") function ft:contains($input as item()*, $terms as item()*, $options as map(*)?) as xs:boolean external;
declare %a:since("basex", "7.0") function ft:mark($nodes as node()*) as node()* external;
declare %a:since("basex", "7.0") function ft:mark($nodes as node()*, $name as xs:string) as node()* external;
declare %a:since("basex", "7.0") function ft:extract($nodes as node()*) as node()* external;
declare %a:since("basex", "7.0") function ft:extract($nodes as node()*, $name as xs:string) as node()* external;
declare %a:since("basex", "7.0") function ft:extract($nodes as node()*, $name as xs:string, $length as xs:integer) as node()* external;
declare %a:since("basex", "7.0") function ft:count($nodes as node()*) as xs:integer external;
declare %a:since("basex", "7.0") function ft:score($item as item()*) as xs:double* external;
declare %a:since("basex", "7.1") function ft:tokens($db as xs:string) as element(value)* external;
declare %a:since("basex", "7.1") function ft:tokens($db as xs:string, $prefix as xs:string) as element(value)* external;
declare %a:since("basex", "7.1") function ft:tokenize($string as ft-string) as xs:string* external;
declare %a:since("basex", "7.1") function ft:tokenize($string as ft-string, $options as map(*)?) as xs:string* external;
declare %a:since("basex", "8.0") function ft:normalize($string as ft-string) as xs:string external;
declare %a:since("basex", "8.0") function ft:normalize($string as ft-string, $options as map(*)?) as xs:string external;

xquery version "3.1";
(:~
 : BaseX full-text functions
 :
 : @see http://docs.basex.org/Full-Text_Module
 :)
module namespace ft = "http://basex.org/modules/ft";

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

declare option o:requires "basex/7.0"; (: NOTE: 7.0 is the earliest version definitions are available for. :)

declare %a:since("basex", "7.0") function ft:search($db as xs:string, $terms as item()*) as text()* external;
declare %a:since("basex", "7.2") function ft:search($db as xs:string, $terms as item()*, $options as map(*)?) as text()* external;
declare %a:since("basex", "7.8") function ft:contains($input as item()*, $terms as item()*) as xs:boolean external;
declare %a:since("basex", "7.8") function ft:contains($input as item()*, $terms as item()*, $options as map(*)?) as xs:boolean external;
declare %a:since("basex", "7.0") function ft:mark($nodes as node()*) as node()* external;
declare %a:since("basex", "7.0") function ft:mark($nodes as node()*, $name as xs:string) as node()* external;
declare %a:since("basex", "7.0") function ft:extract($nodes as node()*) as node()* external;
declare %a:since("basex", "7.0") function ft:extract($nodes as node()*, $name as xs:string) as node()* external;
declare %a:since("basex", "7.0") function ft:extract($nodes as node()*, $name as xs:string, $length as xs:integer) as node()* external;
declare %a:since("basex", "7.0") function ft:count($nodes as node()*) as xs:integer external;
declare %a:since("basex", "7.0") function ft:score($item as item()*) as xs:double* external;
declare %a:since("basex", "7.1") function ft:tokens($db as xs:string) as element(value)* external;
declare %a:since("basex", "7.1") function ft:tokens($db as xs:string, $prefix as xs:string) as element(value)* external;
declare %a:since("basex", "7.1") function ft:tokenize($string as xs:string?) as xs:string* external;
declare %a:since("basex", "7.1") function ft:tokenize($string as xs:string?, $options as map(*)?) as xs:string* external;
declare %a:since("basex", "8.0") function ft:normalize($string as xs:string?) as xs:string external;
declare %a:since("basex", "8.0") function ft:normalize($string as xs:string?, $options as map(*)?) as xs:string external;