[testsuite] Tests for fn:serialize and fn:deep-equal on arrays.

(:~
 : Tests for the array datatype and associated functions.
 :)
module namespace arr="http://exist-db.org/test/arrays";

declare namespace test="http://exist-db.org/xquery/xqsuite";
declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace json="http://www.json.org";

declare variable $arr:SERIALIZE_JSON :=
    <output:serialization-parameters>
        <output:method>json</output:method>
        <output:indent>false</output:indent>
    </output:serialization-parameters>;

declare
    %test:setUp
function arr:setup() {
    let $json := '[{"key1": "value1", "key2": "value2", "key1": "value3"}]'
    let $coll := xmldb:create-collection("/db", "array-test")
    return
        xmldb:store($coll, "test.json", $json)
};

(:declare:)
(:    %test:tearDown:)
(:function arr:cleanup() {:)
(:    xmldb:remove("/db/array-test"):)
(:};:)

declare
    %test:args(1)
    %test:assertEquals(13)
    %test:args(3)
    %test:assertEquals(14)
function arr:function-item($pos as xs:int) {
    [13, 10, 14]($pos)
};

declare
    %test:args(1)
    %test:assertEquals(13)
    %test:args(3)
    %test:assertEquals(14)
function arr:function-item2($pos as xs:int) {
    let $a := [13, 10, 14]
    return
        $a($pos)
};

declare
    %test:args(1)
    %test:assertEquals(13)
    %test:args(3)
    %test:assertEquals(14)
function arr:function-item3($pos as xs:int) {
    let $a := [13, 10, 14]
    let $f := function($array as function(*), $position as xs:int) {
        $array($position)
    }
    return
        $f($a, $pos)
};

declare
    %test:assertError("FOAY0001")
function arr:function-item-out-of-bounds() {
    let $a := [13, 10, 14]
    return
        $a(22)
};

declare
    %test:assertError("err:XPTY0004")
function arr:function-item-invalid() {
    let $a := [13, 10, 14]
    return
        $a("x")
};

declare
    %test:args(1)
    %test:assertEmpty
    %test:args(2)
    %test:assertEquals(27, 17, 0)
function arr:square-constructor1($pos as xs:int) {
    let $a := [(), (27, 17, 0)]
    return $a($pos)
};

declare
    %test:args(1)
    %test:assertEquals("
test
")
    %test:args(2)
    %test:assertEquals(55)
function arr:square-constructor2($pos as xs:int) {
    let $a := [
test
, 55]
    return $a($pos)
};

declare
    %test:assertEquals(0)
function arr:square-constructor3() {
    let $a := []
    return array:size($a)
};

declare
    %test:args(1)
    %test:assertEquals(27)
    %test:args(2)
    %test:assertEquals(17)
function arr:curly-constructor1($pos as xs:int) {
    let $x := (27, 17, 0)
    let $a := array { $x }
    return $a($pos)
};

declare
    %test:args(1)
    %test:assertEquals(27)
    %test:args(2)
    %test:assertEquals(17)
function arr:curly-constructor2($pos as xs:int) {
    let $a := array { (), (27, 17, 0) }
    return $a($pos)
};

declare
    %test:assertTrue
function arr:deep-equal1() {
    deep-equal([1, <node/>, "a", ["b", "c"]], [1, <node/>, "a", ["b", "c"]])
};

declare
    %test:assertFalse
function arr:deep-equal2() {
    deep-equal([1, 2, "a", ["b", "c"]], [1, 2, "a", ["b", "a"]])
};

declare
    %test:assertEquals(2)
function arr:with-map1() {
    let $a := [ map { "a": 1, "b": 2 } ]
    return $a(1)("b")
};

declare
    %test:assertEquals(3)
function arr:with-map2() {
    let $a := map { "a": 1, "b": [2, 3, 4] }
    return $a("b")(2)
};

declare
    %test:assertEquals(5)
function arr:nested1() {
    let $a := [ [1, 2, 3], [4, 5, 6] ]
    return $a(2)(2)
};

declare
    %test:assertEquals(5)
function arr:nested2() {
    let $a := array { [1, 2, 3], [4, 5, 6] }
    return $a(2)(2)
};

declare
    %test:args(2)
function arr:size1() {
    array:size([
test
, 55])
};

declare
    %test:args(1)
function arr:size2() {
    array:size([[]])
};

declare
    %test:assertEquals(3, 4, 5)
function arr:append1() {
    array:append([1, 2], (3, 4, 5))(3)
};

declare
    %test:assertEmpty
function arr:append2() {
    array:append([1, 2], ())(3)
};

declare
    %test:assertEquals("a")
function arr:head1() {
    array:head(["a", "b"])
};

declare
    %test:assertEquals(2)
function arr:head2() {
    array:head([[1, 2], 3, 4, 5])(2)
};

declare
    %test:assertError("FOAY0001")
function arr:head-empty() {
    array:head([])
};

declare
    %test:assertEquals(2)
function arr:tail1() {
    array:tail([1, 2, 3])(1)
};

declare
    %test:assertEquals(2)
function arr:tail2() {
    array:tail([1, [2, 3]])(1)
};

declare
    %test:assertEquals(0)
function arr:tail3() {
    array:size(array:tail([1]))
};

declare
    %test:assertError("FOAY0001")
function arr:tail-empty() {
    array:tail([])
};

declare
    %test:args(2)
    %test:assertEquals("b", "c", "d")
    %test:args(5)
    %test:assertEmpty
function arr:subarray1($start as xs:int) {
    let $a := array:subarray(["a", "b", "c", "d"], $start)
    return
        $a?*
};

declare
    %test:args(5, 0)
    %test:assertEmpty
    %test:args(2, 0)
    %test:assertEmpty
    %test:args(2, 1)
    %test:assertEquals("b")
    %test:args(2, 2)
    %test:assertEquals("b", "c")
    %test:args(4, 1)
    %test:assertEquals("d")
    %test:args(1, 2)
    %test:assertEquals("a", "b")
    %test:args(1, 5)
    %test:assertError("FOAY0001")
    %test:args(0, 1)
    %test:assertError("FOAY0001")
    %test:args(1, "-1")
    %test:assertError("FOAY0002")
function arr:subarray2($start as xs:int, $length as xs:int) {
    let $a := array:subarray(["a", "b", "c", "d"], $start, $length)
    return
        $a?*
};

declare
    %test:args(1)
    %test:assertEquals("b", "c", "d")
    %test:args(2)
    %test:assertEquals("a", "c", "d")
    %test:args(4)
    %test:assertEquals("a", "b", "c")
    %test:args(0)
    %test:assertError("FOAY0001")
    %test:args(5)
    %test:assertError("FOAY0001")
function arr:remove1($pos as xs:int) {
    array:remove(["a", "b", "c", "d"], $pos)?*
};

declare
    %test:assertEmpty
function arr:remove2() {
    array:remove(["a"], 1)?*
};

declare
    %test:assertEquals("d", "c", "b", "a")
function arr:reverse1() {
    array:reverse(["a", "b", "c", "d"])?*
};

declare
    %test:assertEquals("c", "d", "a", "b")
function arr:reverse2() {
    array:reverse([("a", "b"), ("c", "d")])?*
};

declare
    %test:assertEquals("1", "2", "3", "4", "5")
function arr:reverse3() {
    array:reverse([(1 to 5)])?*
};

declare
    %test:assertEmpty
function arr:reverse4() {
    array:reverse([])?*
};

declare
    %test:assertEmpty
function arr:join1() {
    array:join(())?*
};

declare
    %test:assertEquals("a", "b", "c", "d")
function arr:join2() {
    array:join((["a", "b"], ["c", "d"], []))?*
};

declare
    %test:assertEquals(5)
function arr:join3() {
    array:size(array:join((["a", "b"], ["c", "d"], [["e", "f"]])))
};

declare
    %test:assertEquals("A", "B", "C", "D")
function arr:for-each1() {
    array:for-each(["a", "b", "c", "d"], upper-case#1)?*
};

declare
    %test:assertEquals("false", "false", "true", "true")
function arr:for-each2() {
    array:for-each(["a", "b", 1, 2], function($z) { $z instance of xs:integer })?*
};

declare
    %test:assertEquals("1", "2")
function arr:filter1() {
    array:filter(["a", "b", 1, 2], function($z) { $z instance of xs:integer })?*
};

declare
    %test:assertEquals("a", "b", 1)
function arr:filter2() {
    array:filter(["a", "b", "", 0, 1], boolean#1)?*
};

declare
    %test:assertEquals("the cat", "on the mat")
function arr:filter3() {
    array:filter(["the cat", "sat", "on the mat"], function($s) { count(tokenize($s, " ")) gt 1 })?*
};

declare
    %test:assertFalse
function arr:fold-left1() {
    array:fold-left([true(), true(), false()], true(), function($x, $y) { $x and $y })
};

declare
    %test:assertTrue
function arr:fold-left2() {
    array:fold-left([true(), true(), false()], false(), function($x, $y) { $x or $y })
};

declare
    %test:assertEquals(1)
function arr:fold-left3() {
    array:fold-left([], 1, function($x, $y) { $x + $y })
};

declare
    %test:assertEquals(8)
function arr:fold-left4() {
    array:fold-left(["abc", "def", "gh"], 0, function($x, $y) { $x + string-length($y) })
};

declare
    %test:assertEquals("abcdefgh")
    %test:pending
function arr:fold-left5() {
    array:fold-left(["abc", "def", "gh"], "a", function($x, $y) { $x || $y })
};

declare
    %test:assertEquals(1)
function arr:fold-left6() {
    array:fold-left([1, 2], (), function($x, $y) { [$x, $y] })?1?2
};

declare
    %test:assertFalse
function arr:fold-right1() {
    array:fold-right([true(), true(), false()], true(), function($x, $y) { $x and $y })
};

declare
    %test:assertTrue
function arr:fold-right2() {
    array:fold-right([true(), true(), false()], false(), function($x, $y) { $x or $y })
};

declare
    %test:assertEquals(2)
function arr:fold-right3() {
    array:fold-right([1, 2, 3], (), function($x, $y) { [$x, $y] })?2?1
};

declare
    %test:assertEquals(5, 4, 3, 2, 1)
function arr:fold-right4() {
    array:fold-right(
        array { 1 to 5 },
        (),
        function($a, $b) { $b, $a }
    )
};

declare
    %test:assertEquals("AB", "BC", "CD")
function arr:for-each-pair1() {
    let $a := ["A", "B", "C", "D"]
    return
        array:for-each-pair($a, array:tail($a), concat#2)?*
};

declare
    %test:assertEquals(5, 7, 9)
function arr:for-each-pair2() {
    array:for-each-pair(
        array { 1 to 3 },
        array { 4 to 6 },
        function($a, $b) { $a + $b }
    )?*
};

declare
    %test:assertEmpty
function arr:for-each-pair3() {
    array:for-each-pair(
        [],
        array { 4 to 6 },
        function($a, $b) { $a + $b }
    )?*
};

declare
    %test:assertEquals(1, 4, 6, 5, 3)
function arr:flatten1() {
    array:flatten([1, 4, 6, 5, 3])
};

declare
    %test:assertEquals(1, 2, 5, 10, 11, 12, 13)
function arr:flatten2() {
    array:flatten(([1, 2, 5], [[10, 11], 12], [], 13))
};

declare
    %test:assertEquals(1, 1, 0, 1, 1)
function arr:flatten3() {
    array:flatten((1, [(1, 0), (1, 1)]))
};

declare
    %test:assertEmpty
function arr:flatten4() {
    array:flatten(())
};

declare
    %test:assertEquals("a", "b", "x", "c", "d")
function arr:insert-before1() {
    array:insert-before(["a", "b", "c", "d"], 3, "x")?*
};

declare
    %test:assertEquals("x", "y")
function arr:insert-before2() {
    array:insert-before(["a", "b", "c", "d"], 3, ("x", "y"))?3
};

declare
    %test:assertEquals("a", "b", "c", "d", "x")
function arr:insert-before3() {
    array:insert-before(["a", "b", "c", "d"], 5, "x")?*
};

declare
    %test:assertEquals("array")
function arr:array-type1() {
    let $a := [1, 2]
    return
        typeswitch($a)
            case array(*) return
                "array"
            default return
                "no array"
};

declare
    %test:assertEquals("<t>test</t>")
function arr:apply-inline() {
    let $fn := function($a as xs:string, $b as xs:int, $c as element()) {
        $c
    }
    return
        apply($fn, ["a", 1, <t>test</t>])
};

declare
    %test:assertEquals("abc")
function arr:apply-internal() {
    apply(concat#3, ["a", "b", "c"])
};

declare
    %test:assertEquals("a", 1)
function arr:apply-param-array() {
    let $fn := function($a as array(*)) {
        $a?*
    }
    return
        apply($fn, [["a", 1]])
};

declare
    %test:assertEquals("key")
function arr:apply-param-map() {
    let $fn := function($m as map(*)) {
        $m?*
    }
    return
        apply($fn, [map { "key": 1 }])
};

declare
    %test:assertEquals(3)
function arr:apply-param-seq() {
    let $fn := function($s as item()*) {
        count($s)
    }
    return
        apply($fn, [(1, 2, 3)])
};

declare
    %test:assertEquals("HELLO WORLD")
function arr:apply-closure() {
    let $s := "Hello world"
    let $fn := function() {
        upper-case($s)
    }
    return
        apply($fn, [])
};

declare
    %test:args('"a"')
    %test:assertEquals("a")
    %test:args("2")
    %test:assertEquals(2)
    %test:args("true")
    %test:assertTrue
    %test:args("false")
    %test:assertFalse
    %test:args("null")
    %test:assertEmpty
    %test:args("[]")
    %test:assertXPath("array:size($result) = 0")
    %test:args('[1, "a"]')
    %test:assertXPath("$result?2 = 'a'")
    %test:args('[1, ["a", "b"]]')
    %test:assertXPath("$result?2?2 = 'b'")
    %test:args('[1, []]')
    %test:assertXPath("array:size($result?2) = 0")
    %test:args('[1, null]')
    %test:assertXPath("array:size($result) = 2")
    %test:args('{"key1": "val1", "key2": "val2"}')
    %test:assertXPath("$result?key2 = 'val2'")
    %test:args('{"key1": "val1", "key2": [1, 2]}')
    %test:assertXPath("$result?key2?2 = 2")
    %test:args('{"key1": null, "key2": "val2"}')
    %test:assertXPath("empty($result?key1)")
    %test:args('{key: "value"}')
    %test:assertError("FOJS0001")
    %test:args("{key: 'value'}")
    %test:assertError("FOJS0001")
    %test:args('{"k1": "v1", "k1": "v2"}')
    %test:assertXPath("$result?k1 = 'v2'")
function arr:parse-json($json as xs:string) {
    parse-json($json)
};

declare
    %test:args('{key: "value"}')
    %test:assertXPath("$result?key = 'value'")
    %test:args("{'key': 'value'}")
    %test:assertXPath("$result?key = 'value'")
function arr:parse-json-liberal($json as xs:string) {
    parse-json($json, map { "liberal": true() })
};

declare
    %test:args('{"k1": "v1", "k1": "v2"}', "reject")
    %test:assertError("FOJS0003")
    %test:args('{"k1": "v1", "k1": "v2"}', "use-first")
    %test:assertXPath("$result?k1 = 'v1'")
    %test:args('{"k1": "v1", "k1": "v2"}', "use-last")
    %test:assertXPath("$result?k1 = 'v2'")
function arr:parse-json-duplicates($json as xs:string, $duplicates as xs:string) {
    parse-json($json, map { "duplicates": $duplicates })
};

declare
    %test:assertXPath("$result?1?key2 = 'value2'")
function arr:json-doc-db() {
    json-doc("/db/array-test/test.json")
};

declare
    %test:assertError("FOJS0003")
function arr:json-doc-options() {
    json-doc("/db/array-test/test.json", map { "duplicates": "reject" })
};

declare
    %test:assertXPath("$result?1?key2 = 'value2'")
function arr:json-doc-http() {
    json-doc("http://localhost:8080/exist/rest/db/array-test/test.json")
};

declare
    %test:assertEquals('{"a1":22,"a2":["z","b","c"]}')
function arr:serialize() {
    let $json :=
        map {
            "a1": 22,
            "a2": [ "z", "b", "c" ]
        }
    let $serialized :=
        serialize($json, $arr:SERIALIZE_JSON)
    return
        $serialized
};

declare
    %test:assertTrue
function arr:serialize-roundtrip() {
    let $json :=
        map {
            "k1": 22,
            "k2": [ "z", "b", "c" ],
            "k3": map { "k4": array { 1 to 100 }, "k5": () }
        }
    let $serialized :=
        serialize($json, $arr:SERIALIZE_JSON)
    let $parsed := parse-json($serialized)
    return
        deep-equal($json, $parsed)
};

declare
    %test:assertEquals('{ "status" : true }')
function arr:serialize-old-json-compat() {
    let $xmlJson := <result><status json:literal="true">true</status></result>
    return
        serialize($xmlJson, $arr:SERIALIZE_JSON)
};

(:~
 : Tests for the array datatype and associated functions.
 :)
module namespace arr="http://exist-db.org/test/arrays";

declare namespace test="http://exist-db.org/xquery/xqsuite";

declare
    %test:setUp
function arr:setup() {
    let $json := '[{"key1": "value1", "key2": "value2", "key1": "value3"}]'
    let $coll := xmldb:create-collection("/db", "array-test")
    return
        xmldb:store($coll, "test.json", $json)
};

(:declare:)
(:    %test:tearDown:)
(:function arr:cleanup() {:)
(:    xmldb:remove("/db/array-test"):)
(:};:)

declare
    %test:args(1)
    %test:assertEquals(13)
    %test:args(3)
    %test:assertEquals(14)
function arr:function-item($pos as xs:int) {
    [13, 10, 14]($pos)
};

declare
    %test:args(1)
    %test:assertEquals(13)
    %test:args(3)
    %test:assertEquals(14)
function arr:function-item2($pos as xs:int) {
    let $a := [13, 10, 14]
    return
        $a($pos)
};

declare
    %test:args(1)
    %test:assertEquals(13)
    %test:args(3)
    %test:assertEquals(14)
function arr:function-item3($pos as xs:int) {
    let $a := [13, 10, 14]
    let $f := function($array as function(*), $position as xs:int) {
        $array($position)
    }
    return
        $f($a, $pos)
};

declare
    %test:assertError("FOAY0001")
function arr:function-item-out-of-bounds() {
    let $a := [13, 10, 14]
    return
        $a(22)
};

declare
    %test:assertError("err:XPTY0004")
function arr:function-item-invalid() {
    let $a := [13, 10, 14]
    return
        $a("x")
};

declare
    %test:args(1)
    %test:assertEmpty
    %test:args(2)
    %test:assertEquals(27, 17, 0)
function arr:square-constructor1($pos as xs:int) {
    let $a := [(), (27, 17, 0)]
    return $a($pos)
};

declare
    %test:args(1)
    %test:assertEquals("
test
")
    %test:args(2)
    %test:assertEquals(55)
function arr:square-constructor2($pos as xs:int) {
    let $a := [
test
, 55]
    return $a($pos)
};

declare
    %test:assertEquals(0)
function arr:square-constructor3() {
    let $a := []
    return array:size($a)
};

declare
    %test:args(1)
    %test:assertEquals(27)
    %test:args(2)
    %test:assertEquals(17)
function arr:curly-constructor1($pos as xs:int) {
    let $x := (27, 17, 0)
    let $a := array { $x }
    return $a($pos)
};

declare
    %test:args(1)
    %test:assertEquals(27)
    %test:args(2)
    %test:assertEquals(17)
function arr:curly-constructor2($pos as xs:int) {
    let $a := array { (), (27, 17, 0) }
    return $a($pos)
};

declare
    %test:assertEquals(2)
function arr:with-map1() {
    let $a := [ map { "a": 1, "b": 2 } ]
    return $a(1)("b")
};

declare
    %test:assertEquals(3)
function arr:with-map2() {
    let $a := map { "a": 1, "b": [2, 3, 4] }
    return $a("b")(2)
};

declare
    %test:assertEquals(5)
function arr:nested1() {
    let $a := [ [1, 2, 3], [4, 5, 6] ]
    return $a(2)(2)
};

declare
    %test:assertEquals(5)
function arr:nested2() {
    let $a := array { [1, 2, 3], [4, 5, 6] }
    return $a(2)(2)
};

declare
    %test:args(2)
function arr:size1() {
    array:size([
test
, 55])
};

declare
    %test:args(1)
function arr:size2() {
    array:size([[]])
};

declare
    %test:assertEquals(3, 4, 5)
function arr:append1() {
    array:append([1, 2], (3, 4, 5))(3)
};

declare
    %test:assertEmpty
function arr:append2() {
    array:append([1, 2], ())(3)
};

declare
    %test:assertEquals("a")
function arr:head1() {
    array:head(["a", "b"])
};

declare
    %test:assertEquals(2)
function arr:head2() {
    array:head([[1, 2], 3, 4, 5])(2)
};

declare
    %test:assertError("FOAY0001")
function arr:head-empty() {
    array:head([])
};

declare
    %test:assertEquals(2)
function arr:tail1() {
    array:tail([1, 2, 3])(1)
};

declare
    %test:assertEquals(2)
function arr:tail2() {
    array:tail([1, [2, 3]])(1)
};

declare
    %test:assertEquals(0)
function arr:tail3() {
    array:size(array:tail([1]))
};

declare
    %test:assertError("FOAY0001")
function arr:tail-empty() {
    array:tail([])
};

declare
    %test:args(2)
    %test:assertEquals("b", "c", "d")
    %test:args(5)
    %test:assertEmpty
function arr:subarray1($start as xs:int) {
    let $a := array:subarray(["a", "b", "c", "d"], $start)
    return
        $a?*
};

declare
    %test:args(5, 0)
    %test:assertEmpty
    %test:args(2, 0)
    %test:assertEmpty
    %test:args(2, 1)
    %test:assertEquals("b")
    %test:args(2, 2)
    %test:assertEquals("b", "c")
    %test:args(4, 1)
    %test:assertEquals("d")
    %test:args(1, 2)
    %test:assertEquals("a", "b")
    %test:args(1, 5)
    %test:assertError("FOAY0001")
    %test:args(0, 1)
    %test:assertError("FOAY0001")
    %test:args(1, "-1")
    %test:assertError("FOAY0002")
function arr:subarray2($start as xs:int, $length as xs:int) {
    let $a := array:subarray(["a", "b", "c", "d"], $start, $length)
    return
        $a?*
};

declare
    %test:args(1)
    %test:assertEquals("b", "c", "d")
    %test:args(2)
    %test:assertEquals("a", "c", "d")
    %test:args(4)
    %test:assertEquals("a", "b", "c")
    %test:args(0)
    %test:assertError("FOAY0001")
    %test:args(5)
    %test:assertError("FOAY0001")
function arr:remove1($pos as xs:int) {
    array:remove(["a", "b", "c", "d"], $pos)?*
};

declare
    %test:assertEmpty
function arr:remove2() {
    array:remove(["a"], 1)?*
};

declare
    %test:assertEquals("d", "c", "b", "a")
function arr:reverse1() {
    array:reverse(["a", "b", "c", "d"])?*
};

declare
    %test:assertEquals("c", "d", "a", "b")
function arr:reverse2() {
    array:reverse([("a", "b"), ("c", "d")])?*
};

declare
    %test:assertEquals("1", "2", "3", "4", "5")
function arr:reverse3() {
    array:reverse([(1 to 5)])?*
};

declare
    %test:assertEmpty
function arr:reverse4() {
    array:reverse([])?*
};

declare
    %test:assertEmpty
function arr:join1() {
    array:join(())?*
};

declare
    %test:assertEquals("a", "b", "c", "d")
function arr:join2() {
    array:join((["a", "b"], ["c", "d"], []))?*
};

declare
    %test:assertEquals(5)
function arr:join3() {
    array:size(array:join((["a", "b"], ["c", "d"], [["e", "f"]])))
};

declare
    %test:assertEquals("A", "B", "C", "D")
function arr:for-each1() {
    array:for-each(["a", "b", "c", "d"], upper-case#1)?*
};

declare
    %test:assertEquals("false", "false", "true", "true")
function arr:for-each2() {
    array:for-each(["a", "b", 1, 2], function($z) { $z instance of xs:integer })?*
};

declare
    %test:assertEquals("1", "2")
function arr:filter1() {
    array:filter(["a", "b", 1, 2], function($z) { $z instance of xs:integer })?*
};

declare
    %test:assertEquals("a", "b", 1)
function arr:filter2() {
    array:filter(["a", "b", "", 0, 1], boolean#1)?*
};

declare
    %test:assertEquals("the cat", "on the mat")
function arr:filter3() {
    array:filter(["the cat", "sat", "on the mat"], function($s) { count(tokenize($s, " ")) gt 1 })?*
};

declare
    %test:assertFalse
function arr:fold-left1() {
    array:fold-left([true(), true(), false()], true(), function($x, $y) { $x and $y })
};

declare
    %test:assertTrue
function arr:fold-left2() {
    array:fold-left([true(), true(), false()], false(), function($x, $y) { $x or $y })
};

declare
    %test:assertEquals(1)
function arr:fold-left3() {
    array:fold-left([], 1, function($x, $y) { $x + $y })
};

declare
    %test:assertEquals(8)
function arr:fold-left4() {
    array:fold-left(["abc", "def", "gh"], 0, function($x, $y) { $x + string-length($y) })
};

declare
    %test:assertEquals("abcdefgh")
    %test:pending
function arr:fold-left5() {
    array:fold-left(["abc", "def", "gh"], "a", function($x, $y) { $x || $y })
};

declare
    %test:assertEquals(1)
function arr:fold-left6() {
    array:fold-left([1, 2], (), function($x, $y) { [$x, $y] })?1?2
};

declare
    %test:assertFalse
function arr:fold-right1() {
    array:fold-right([true(), true(), false()], true(), function($x, $y) { $x and $y })
};

declare
    %test:assertTrue
function arr:fold-right2() {
    array:fold-right([true(), true(), false()], false(), function($x, $y) { $x or $y })
};

declare
    %test:assertEquals(2)
function arr:fold-right3() {
    array:fold-right([1, 2, 3], (), function($x, $y) { [$x, $y] })?2?1
};

declare
    %test:assertEquals(5, 4, 3, 2, 1)
function arr:fold-right4() {
    array:fold-right(
        array { 1 to 5 },
        (),
        function($a, $b) { $b, $a }
    )
};

declare
    %test:assertEquals("AB", "BC", "CD")
function arr:for-each-pair1() {
    let $a := ["A", "B", "C", "D"]
    return
        array:for-each-pair($a, array:tail($a), concat#2)?*
};

declare
    %test:assertEquals(5, 7, 9)
function arr:for-each-pair2() {
    array:for-each-pair(
        array { 1 to 3 },
        array { 4 to 6 },
        function($a, $b) { $a + $b }
    )?*
};

declare
    %test:assertEmpty
function arr:for-each-pair3() {
    array:for-each-pair(
        [],
        array { 4 to 6 },
        function($a, $b) { $a + $b }
    )?*
};

declare
    %test:assertEquals(1, 4, 6, 5, 3)
function arr:flatten1() {
    array:flatten([1, 4, 6, 5, 3])
};

declare
    %test:assertEquals(1, 2, 5, 10, 11, 12, 13)
function arr:flatten2() {
    array:flatten(([1, 2, 5], [[10, 11], 12], [], 13))
};

declare
    %test:assertEquals(1, 1, 0, 1, 1)
function arr:flatten3() {
    array:flatten((1, [(1, 0), (1, 1)]))
};

declare
    %test:assertEmpty
function arr:flatten4() {
    array:flatten(())
};

declare
    %test:assertEquals("a", "b", "x", "c", "d")
function arr:insert-before1() {
    array:insert-before(["a", "b", "c", "d"], 3, "x")?*
};

declare
    %test:assertEquals("x", "y")
function arr:insert-before2() {
    array:insert-before(["a", "b", "c", "d"], 3, ("x", "y"))?3
};

declare
    %test:assertEquals("a", "b", "c", "d", "x")
function arr:insert-before3() {
    array:insert-before(["a", "b", "c", "d"], 5, "x")?*
};

declare
    %test:assertEquals("array")
function arr:array-type1() {
    let $a := [1, 2]
    return
        typeswitch($a)
            case array(*) return
                "array"
            default return
                "no array"
};

declare
    %test:assertEquals("<t>test</t>")
function arr:apply-inline() {
    let $fn := function($a as xs:string, $b as xs:int, $c as element()) {
        $c
    }
    return
        apply($fn, ["a", 1, <t>test</t>])
};

declare
    %test:assertEquals("abc")
function arr:apply-internal() {
    apply(concat#3, ["a", "b", "c"])
};

declare
    %test:assertEquals("a", 1)
function arr:apply-param-array() {
    let $fn := function($a as array(*)) {
        $a?*
    }
    return
        apply($fn, [["a", 1]])
};

declare
    %test:assertEquals("key")
function arr:apply-param-map() {
    let $fn := function($m as map(*)) {
        $m?*
    }
    return
        apply($fn, [map { "key": 1 }])
};

declare
    %test:assertEquals(3)
function arr:apply-param-seq() {
    let $fn := function($s as item()*) {
        count($s)
    }
    return
        apply($fn, [(1, 2, 3)])
};

declare
    %test:assertEquals("HELLO WORLD")
function arr:apply-closure() {
    let $s := "Hello world"
    let $fn := function() {
        upper-case($s)
    }
    return
        apply($fn, [])
};

declare
    %test:args('"a"')
    %test:assertEquals("a")
    %test:args("2")
    %test:assertEquals(2)
    %test:args("true")
    %test:assertTrue
    %test:args("false")
    %test:assertFalse
    %test:args("null")
    %test:assertEmpty
    %test:args("[]")
    %test:assertXPath("array:size($result) = 0")
    %test:args('[1, "a"]')
    %test:assertXPath("$result?2 = 'a'")
    %test:args('[1, ["a", "b"]]')
    %test:assertXPath("$result?2?2 = 'b'")
    %test:args('[1, []]')
    %test:assertXPath("array:size($result?2) = 0")
    %test:args('[1, null]')
    %test:assertXPath("array:size($result) = 2")
    %test:args('{"key1": "val1", "key2": "val2"}')
    %test:assertXPath("$result?key2 = 'val2'")
    %test:args('{"key1": "val1", "key2": [1, 2]}')
    %test:assertXPath("$result?key2?2 = 2")
    %test:args('{"key1": null, "key2": "val2"}')
    %test:assertXPath("empty($result?key1)")
    %test:args('{key: "value"}')
    %test:assertError("FOJS0001")
    %test:args("{key: 'value'}")
    %test:assertError("FOJS0001")
    %test:args('{"k1": "v1", "k1": "v2"}')
    %test:assertXPath("$result?k1 = 'v2'")
function arr:parse-json($json as xs:string) {
    parse-json($json)
};

declare
    %test:args('{key: "value"}')
    %test:assertXPath("$result?key = 'value'")
    %test:args("{'key': 'value'}")
    %test:assertXPath("$result?key = 'value'")
function arr:parse-json-liberal($json as xs:string) {
    parse-json($json, map { "liberal": true() })
};

declare
    %test:args('{"k1": "v1", "k1": "v2"}', "reject")
    %test:assertError("FOJS0003")
    %test:args('{"k1": "v1", "k1": "v2"}', "use-first")
    %test:assertXPath("$result?k1 = 'v1'")
    %test:args('{"k1": "v1", "k1": "v2"}', "use-last")
    %test:assertXPath("$result?k1 = 'v2'")
function arr:parse-json-duplicates($json as xs:string, $duplicates as xs:string) {
    parse-json($json, map { "duplicates": $duplicates })
};

declare
    %test:assertXPath("$result?1?key2 = 'value2'")
function arr:json-doc-db() {
    json-doc("/db/array-test/test.json")
};

declare
    %test:assertError("FOJS0003")
function arr:json-doc-options() {
    json-doc("/db/array-test/test.json", map { "duplicates": "reject" })
};

declare
    %test:assertXPath("$result?1?key2 = 'value2'")
function arr:json-doc-http() {
    json-doc("http://localhost:8080/exist/rest/db/array-test/test.json")
};


Updated test for new JSON module options schema.

import module namespace doc = "http://www.zorba-xquery.com/modules/store/dynamic/documents";

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

j:serialize(
  doc:document("meat_poultry.xml")/root/row,
  <options xmlns="http://www.zorba-xquery.com/modules/converters/json-options" >
    <json-format value="JsonML-array" />
  </options>
)

import module namespace doc = "http://www.zorba-xquery.com/modules/store/dynamic/documents";

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

j:serialize(
  doc:document("meat_poultry.xml")/root/row,
  <options xmlns="http://www.zorba-xquery.com/modules/converters/json-options" >
    <json-param name="mapping" value="json-ml" />
  </options>
)


[ignore] Add note to old admin webapp login.

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

xquery version "3.0";
(:
    $Id$

    Main module of the database administration interface.
:)

declare namespace admin = "http://exist-db.org/xquery/admin-interface";

declare namespace request = "http://exist-db.org/xquery/request";
declare namespace session = "http://exist-db.org/xquery/session";
declare namespace util = "http://exist-db.org/xquery/util";
declare namespace xdb = "http://exist-db.org/xquery/xmldb";

import module namespace status = "http://exist-db.org/xquery/admin-interface/status" at "status.xqm";
import module namespace browse = "http://exist-db.org/xquery/admin-interface/browse" at "browse.xqm";
import module namespace users = "http://exist-db.org/xquery/admin-interface/users" at "users.xqm";
import module namespace xqueries = "http://exist-db.org/xquery/admin-interface/xqueries" at "xqueries.xqm";
import module namespace rev="http://exist-db.org/xquery/admin-interface/revisions" at "versions.xqm";
import module namespace prof="http://exist-db.org/xquery/profiling" at "trace.xqm";
import module namespace grammar="http://exist-db.org/xquery/admin-interface/grammar" at "grammar.xqm";
import module namespace install="http://exist-db.org/xquery/install-tools" at "install.xqm";
import module namespace repomanager="http://exist-db.org/xquery/admin-interface/repo" at "repo.xqm";
import module namespace indexes="http://exist-db.org/xquery/admin-interface/indexes" at "indexes.xqm";

declare option exist:serialize "method=xhtml media-type=text/html";

(:
    Display the version, SVN revision and user info in the top right corner
:)
declare function admin:info-header() as element()
{

Version: { util:system-property( "product-version" ) }

SVN Revision: { util:system-property( "svn-revision" ) }

Build: {util:system-property("product-build")}

User: { xdb:get-current-user() }

};

(:
    Display the menu on left side
:)
declare function admin:menu-panel() as element()
{
    let $link := session:encode-url(request:get-uri())
    return

Select a Page

Browse

Collections

Indexes

System

Status

Tooling

Package Repository

Query Profiling

View

Grammar cache

Running Jobs

Home

Logout

};

(:
    Select the page to show. Every page is defined in its own module
:)
declare function admin:panel() as element()
{
    let $panel := request:get-parameter("panel", "status")[1] return
        switch ($panel)
            case "browse"    return  browse:main()
            case "xqueries"  return  xqueries:main()
            case "repo"      return  repomanager:main()
            case "revisions" return  rev:main()
            case "trace"     return  prof:main()
            case "grammar"   return  grammar:main()
            case "install"   return  install:main()
            case "indexes"   return  indexes:main()
            default          return  status:main()
};

declare function admin:panel-header() {
    let $panel := request:get-parameter("panel", "status")[1]
    return
        if ($panel[1] eq "install") then
            install:header()
        else()
};

(:~
    Display the login form.
:)
declare function admin:display-login-form() as element()
{

Login

This is the old web admin client. We're in the processes of moving all its functionality
        into the dashboard. Some panels have not been moved though, so they are still provided
        via this old interface.

        <form action="{session:encode-url(request:get-uri())}" method="post">

                    Please Login

                    Username:
                    <input name="user" type="text" size="20"/>

                    Password:
                    <input name="pass" type="password" size="20"/>

                    <input type="submit" value="Login"/>

            {
                for $param in request:get-parameter-names()
                return
                    if ( $param = ("user","pass") ) then
                        ()
                    else
                        <input type="hidden" name="{$param}" value="{request:get-parameter($param, ())}"/>

            }
        </form>

};

(: main entry point :)
let $userParam := request:get-parameter("user", ())
let $passwdParam := request:get-parameter("pass", ())
let $isLoggedIn :=  if(xdb:get-current-user() eq "guest") then
    (
        (: is this a login attempt? :)
        if($userParam and not(empty($passwdParam)))then
        (
            if($userParam = ("", "guest") )then
            (
                (: prevent the guest user from accessing the admin webapp :)
                false()
            )
            else
            (
                (: try and log the user in :)
                xdb:login( "/db", $userParam, $passwdParam, true() )
            )
        )
        else
        (
            (: prevent the guest user from accessing the admin webapp :)
            false()
        )
    )
    else
    (
        (: if we are already logged in, are we logging out - i.e. set permissions back to guest :)
        if(request:get-parameter("logout",()))then
        (
            let $null  := xdb:login("/db", "guest", "guest")
            let $inval := session:invalidate()

            return false()
        )
        else
        (
             (: we are already logged in and we are not the guest user :)
            true()
        )
    )
return (
    <?css-conversion no?>,

            <title>eXist Database Administration</title>
            <link type="text/css" href="admin.css" rel="stylesheet"/>
            <link type="text/css" href="styles/prettify.css" rel="stylesheet"/>
            <link rel="shortcut icon" href="../resources/exist_icon_16x16.ico"/>
            <link rel="icon" href="../resources/exist_icon_16x16.png" type="image/png"/>

            { admin:panel-header() }

                { admin:info-header() }

                    {
                        if($isLoggedIn) then
                            admin:menu-panel()
                        else
                        ()
                    }

                        Logged in as: {xdb:get-current-user()}

                {
                    if($isLoggedIn)then
                    (
                        admin:panel()
                    )
                    else
                    (
                        admin:display-login-form()
                    )
                }

)

xquery version "3.0";
(:
    $Id$

    Main module of the database administration interface.
:)

declare namespace admin = "http://exist-db.org/xquery/admin-interface";

declare namespace request = "http://exist-db.org/xquery/request";
declare namespace session = "http://exist-db.org/xquery/session";
declare namespace util = "http://exist-db.org/xquery/util";
declare namespace xdb = "http://exist-db.org/xquery/xmldb";

import module namespace status = "http://exist-db.org/xquery/admin-interface/status" at "status.xqm";
import module namespace browse = "http://exist-db.org/xquery/admin-interface/browse" at "browse.xqm";
import module namespace users = "http://exist-db.org/xquery/admin-interface/users" at "users.xqm";
import module namespace xqueries = "http://exist-db.org/xquery/admin-interface/xqueries" at "xqueries.xqm";
import module namespace rev="http://exist-db.org/xquery/admin-interface/revisions" at "versions.xqm";
import module namespace prof="http://exist-db.org/xquery/profiling" at "trace.xqm";
import module namespace grammar="http://exist-db.org/xquery/admin-interface/grammar" at "grammar.xqm";
import module namespace install="http://exist-db.org/xquery/install-tools" at "install.xqm";
import module namespace repomanager="http://exist-db.org/xquery/admin-interface/repo" at "repo.xqm";
import module namespace indexes="http://exist-db.org/xquery/admin-interface/indexes" at "indexes.xqm";

declare option exist:serialize "method=xhtml media-type=text/html";

(:
    Display the version, SVN revision and user info in the top right corner
:)
declare function admin:info-header() as element()
{

Version: { util:system-property( "product-version" ) }

SVN Revision: { util:system-property( "svn-revision" ) }

Build: {util:system-property("product-build")}

User: { xdb:get-current-user() }

};

(:
    Display the menu on left side
:)
declare function admin:menu-panel() as element()
{
    let $link := session:encode-url(request:get-uri())
    return

Select a Page

Browse

Collections

Indexes

System

Status

Tooling

Package Repository

Query Profiling

View

Grammar cache

Running Jobs

Home

Logout

};

(:
    Select the page to show. Every page is defined in its own module
:)
declare function admin:panel() as element()
{
    let $panel := request:get-parameter("panel", "status")[1] return
        switch ($panel)
            case "browse"    return  browse:main()
            case "xqueries"  return  xqueries:main()
            case "repo"      return  repomanager:main()
            case "revisions" return  rev:main()
            case "trace"     return  prof:main()
            case "grammar"   return  grammar:main()
            case "install"   return  install:main()
            case "indexes"   return  indexes:main()
            default          return  status:main()
};

declare function admin:panel-header() {
    let $panel := request:get-parameter("panel", "status")[1]
    return
        if ($panel[1] eq "install") then
            install:header()
        else()
};

(:~
    Display the login form.
:)
declare function admin:display-login-form() as element()
{

Login

This is a protected resource. Only registered database users can log
        in. If you have not set up any users, login as "admin" and leave the
        password field empty. Note that the "guest" user is not permitted access.

        <form action="{session:encode-url(request:get-uri())}" method="post">

                    Please Login

                    Username:
                    <input name="user" type="text" size="20"/>

                    Password:
                    <input name="pass" type="password" size="20"/>

                    <input type="submit" value="Login"/>

            {
                for $param in request:get-parameter-names()
                return
                    if ( $param = ("user","pass") ) then
                        ()
                    else
                        <input type="hidden" name="{$param}" value="{request:get-parameter($param, ())}"/>

            }
        </form>

};

(: main entry point :)
let $userParam := request:get-parameter("user", ())
let $passwdParam := request:get-parameter("pass", ())
let $isLoggedIn :=  if(xdb:get-current-user() eq "guest") then
    (
        (: is this a login attempt? :)
        if($userParam and not(empty($passwdParam)))then
        (
            if($userParam = ("", "guest") )then
            (
                (: prevent the guest user from accessing the admin webapp :)
                false()
            )
            else
            (
                (: try and log the user in :)
                xdb:login( "/db", $userParam, $passwdParam, true() )
            )
        )
        else
        (
            (: prevent the guest user from accessing the admin webapp :)
            false()
        )
    )
    else
    (
        (: if we are already logged in, are we logging out - i.e. set permissions back to guest :)
        if(request:get-parameter("logout",()))then
        (
            let $null  := xdb:login("/db", "guest", "guest")
            let $inval := session:invalidate()

            return false()
        )
        else
        (
             (: we are already logged in and we are not the guest user :)
            true()
        )
    )
return (
    <?css-conversion no?>,

            <title>eXist Database Administration</title>
            <link type="text/css" href="admin.css" rel="stylesheet"/>
            <link type="text/css" href="styles/prettify.css" rel="stylesheet"/>
            <link rel="shortcut icon" href="../resources/exist_icon_16x16.ico"/>
            <link rel="icon" href="../resources/exist_icon_16x16.png" type="image/png"/>

            { admin:panel-header() }

                { admin:info-header() }

                    {
                        if($isLoggedIn) then
                            admin:menu-panel()
                        else
                        ()
                    }

                        Logged in as: {xdb:get-current-user()}

                {
                    if($isLoggedIn)then
                    (
                        admin:panel()
                    )
                    else
                    (
                        admin:display-login-form()
                    )
                }

)


Replacing XsltForms submit-done message with more attractive Bootstrap modal

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

(:
Punch list:

(might want)
  * Add trigger to see preview on save (may involve xq)

:)

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

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

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

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

let $form :=

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

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

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

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

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

      <xf:bind id="foliation-modern"
        nodeset="//tei:msDesc/tei:physDesc/tei:objectDesc/tei:supportDesc/tei:foliation[@xml:id='modern']"></xf:bind>

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

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

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

      <xf:action ev:observer="save" ev:event="xforms-submit-done">
        <xf:load resource="javascript:customSaveMessage()"></xf:load>
      </xf:action>

    </xf:model>

Metadata Editor

for the Shelley-Godwin Archive

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

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

1 of 7

Basic Item Description

          <xf:group>

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

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

              </xf:input>

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

          </xf:group>

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

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

2 of 7

Holdings Information

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

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

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

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

          </xf:group>

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

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

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

3 of 7

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

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

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

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

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

              <xf:textarea id="foliation-mod" bind="foliation-modern">
              <xf:label>Describe any additional foliation schemes present: </xf:label>
            </xf:textarea>

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

          </xf:group>

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

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

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

4 of 7

Manuscript Hands

          <xf:group>

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

          </xf:group>

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

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

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

5 of 7

Data Provenance

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

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

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

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

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

6 of 7

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

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

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

          </xf:group>

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

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

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

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

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

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

Select an item:

Loading

Retrieving page images for this item. Please wait.

                                filename
                                link
                                label

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

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

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

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

7 of 7

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

                Timestamp
                Author
                Change(s)

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

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

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

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

            </xf:repeat>

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

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

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

Success!

Metadata for this item has been saved. Thank you.

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

            Powered by XSLTForms, Bootstrap, Font Awesome, fancyBox, and poetry.

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

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

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

(:
Punch list:

(might want)
  * Add trigger to see preview on save (may involve xq)

:)

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

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

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

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

let $form :=

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

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

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

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

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

      <xf:bind id="foliation-modern"
        nodeset="//tei:msDesc/tei:physDesc/tei:objectDesc/tei:supportDesc/tei:foliation[@xml:id='modern']"></xf:bind>

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

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

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

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

    </xf:model>

Metadata Editor

for the Shelley-Godwin Archive

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

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

1 of 7

Basic Item Description

          <xf:group>

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

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

              </xf:input>

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

          </xf:group>

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

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

2 of 7

Holdings Information

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

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

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

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

          </xf:group>

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

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

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

3 of 7

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

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

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

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

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

              <xf:textarea id="foliation-mod" bind="foliation-modern">
              <xf:label>Describe any additional foliation schemes present: </xf:label>
            </xf:textarea>

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

          </xf:group>

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

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

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

4 of 7

Manuscript Hands

          <xf:group>

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

          </xf:group>

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

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

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

5 of 7

Data Provenance

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

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

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

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

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

6 of 7

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

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

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

          </xf:group>

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

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

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

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

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

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

Select an item:

Loading

Retrieving page images for this item. Please wait.

                                filename
                                link
                                label

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

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

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

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

7 of 7

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

                Timestamp
                Author
                Change(s)

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

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

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

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

            </xf:repeat>

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

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

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

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

            Powered by XSLTForms, Bootstrap, Font Awesome, fancyBox, and poetry.

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

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


[ignore] add link to issue for failing tests

refs #291

xquery version "3.1";

module namespace concat="http://exist-db.org/xquery/test/string-concatenation";

declare namespace test="http://exist-db.org/xquery/xqsuite";

declare variable $concat:XML :=

            <c>CC1</c>
            <c>CC2</c>

    ;

declare
    %test:assertEquals("ungrateful")
function concat:strings() {
    ('un' ||  'grateful')
};

declare
    %test:assertEquals("The result is 22.5")
function concat:numbers() {
    ("The result is " ||  22.5)
};

declare
    %test:assertEquals("unGRATEFUL")
function concat:function-call() {
    ("un" ||  upper-case("grateful"))
};

declare
    %test:assertEquals("The result is ")
function concat:empty-sequence1() {
    ("The result is " ||  ())
};

declare
    %test:assertTrue
function concat:empty-sequence2() {
    (() || ()) instance of xs:string
};

declare
    %test:assertTrue
function concat:empty-sequence3() {
    (() || ()) eq ""
};

declare
    %test:assertEquals(1)
function concat:empty-strings() {
    count("" || "")
};

declare
    %test:assertEquals("zzzzz123")
function concat:nested() {
    (("zzz" || "zz") || "123")
};

declare
    %test:assertEquals("ungrateful death")
function concat:multiple() {
    ('un' || () || 'grateful' || ' ' || "death")
};

declare
    %test:assertEquals("12-16")
function concat:precedence1() {
    12 || 34 - 50
};

declare
    %test:assertTrue
function concat:precedence2() {
    "1234" eq 12 || 34
};

declare
    %test:assertEquals("1234")
function concat:precedence3() {
    if (true()) then ("1234") else (12)
    || "I belong to someone else"
};

declare
    %test:assertTrue
function concat:nodes() {
    $concat:XML/b/c[1] || 2 eq "CC12"
};

declare
    %test:assertError("FOTY0013")
function concat:bad-argument() {
    ("abc" || "abc" || fn:concat#3)
};

(: test for #1828 :)
declare
    %test:assertEquals("||||")
function concat:mode-safe-element-content() {
    || || '||'
};

declare
    %test:assertEquals("||||")
function concat:mode-safe-attribute-value() {
    /@b || '||'
};

declare
    %test:assertEquals("bc")
function concat:enclosed-in-element-content() {
    { "b" || "c" }
};

declare
    %test:assertEquals("")
function concat:decimal-in-attribute-value() {

};

declare
    %test:assertEquals("")
function concat:variables-in-attribute-value() {
    let $c := "c"
    let $d := "d"
    return
};

(:~
 : The two tests below cannot be commented in.
 : They cause a parsing error and therefore must
 : stay like this, until the underlying issue has
 : been fixed
 : https://github.com/eXist-db/exist/issues/291
 :)
(:
declare
    %test:pending
    %test:assertEquals("")
function concat:sq-strings-in-attribute-value() {

};

declare
    %test:pending
    %test:assertEquals("")
function concat:dq-strings-in-attribute-value() {

};
:)

declare
    %test:assertEquals("|| ||||")
function concat:string-constructor() {
    ``[|| `{ "||" || '||' }`]``
};

xquery version "3.1";

module namespace concat="http://exist-db.org/xquery/test/string-concatenation";

declare namespace test="http://exist-db.org/xquery/xqsuite";

declare variable $concat:XML :=

            <c>CC1</c>
            <c>CC2</c>

    ;

declare
    %test:assertEquals("ungrateful")
function concat:strings() {
    ('un' ||  'grateful')
};

declare
    %test:assertEquals("The result is 22.5")
function concat:numbers() {
    ("The result is " ||  22.5)
};

declare
    %test:assertEquals("unGRATEFUL")
function concat:function-call() {
    ("un" ||  upper-case("grateful"))
};

declare
    %test:assertEquals("The result is ")
function concat:empty-sequence1() {
    ("The result is " ||  ())
};

declare
    %test:assertTrue
function concat:empty-sequence2() {
    (() || ()) instance of xs:string
};

declare
    %test:assertTrue
function concat:empty-sequence3() {
    (() || ()) eq ""
};

declare
    %test:assertEquals(1)
function concat:empty-strings() {
    count("" || "")
};

declare
    %test:assertEquals("zzzzz123")
function concat:nested() {
    (("zzz" || "zz") || "123")
};

declare
    %test:assertEquals("ungrateful death")
function concat:multiple() {
    ('un' || () || 'grateful' || ' ' || "death")
};

declare
    %test:assertEquals("12-16")
function concat:precedence1() {
    12 || 34 - 50
};

declare
    %test:assertTrue
function concat:precedence2() {
    "1234" eq 12 || 34
};

declare
    %test:assertEquals("1234")
function concat:precedence3() {
    if (true()) then ("1234") else (12)
    || "I belong to someone else"
};

declare
    %test:assertTrue
function concat:nodes() {
    $concat:XML/b/c[1] || 2 eq "CC12"
};

declare
    %test:assertError("FOTY0013")
function concat:bad-argument() {
    ("abc" || "abc" || fn:concat#3)
};

(: test for #1828 :)
declare
    %test:assertEquals("||||")
function concat:mode-safe-element-content() {
    || || '||'
};

declare
    %test:assertEquals("||||")
function concat:mode-safe-attribute-value() {
    /@b || '||'
};

declare
    %test:assertEquals("bc")
function concat:enclosed-in-element-content() {
    { "b" || "c" }
};

declare
    %test:assertEquals("")
function concat:decimal-in-attribute-value() {

};

declare
    %test:assertEquals("")
function concat:variables-in-attribute-value() {
    let $c := "c"
    let $d := "d"
    return
};

(:~
 : The two tests below cannot be commented in.
 : They cause a parsing error and therefore must
 : stay like this, until the underlying issue has
 : been fixed
 :)
(:
declare
    %test:pending
    %test:assertEquals("")
function concat:sq-strings-in-attribute-value() {

};

declare
    %test:pending
    %test:assertEquals("")
function concat:dq-strings-in-attribute-value() {

};
:)

declare
    %test:assertEquals("|| ||||")
function concat:string-constructor() {
    ``[|| `{ "||" || '||' }`]``
};