xquery version "3.1";
(:
    ART-DECOR® STANDARD COPYRIGHT AND LICENSE NOTE
    Copyright © ART-DECOR Expert Group and ART-DECOR Open Tools GmbH
    see https://docs.art-decor.org/copyright and https://docs.art-decor.org/licenses

    This file is part of the ART-DECOR® tools suite.
:)

module namespace vs                 = "http://art-decor.org/ns/decor/valueset";

import module namespace adserver    = "http://art-decor.org/ns/art-decor-server" at "api-server-settings.xqm";
import module namespace get         = "http://art-decor.org/ns/art-decor-settings" at "../modules/art-decor-settings.xqm";
import module namespace art         = "http://art-decor.org/ns/art" at "../modules/art-decor.xqm";
import module namespace templ       = "http://art-decor.org/ns/decor/template" at "api-decor-template.xqm";
import module namespace cs          = "http://art-decor.org/ns/decor/codesystem" at "api-decor-codesystem.xqm";

declare namespace error             = "http://art-decor.org/ns/decor/valueset/error";
declare namespace xs                = "http://www.w3.org/2001/XMLSchema";
declare namespace http              = "http://expath.org/ns/http-client";

declare variable $vs:strDecorServicesURL            := adserver:getServerURLServices();
(:  normally we return contents for ART XForms. These require serialize desc nodes. When we return for other purposes like 
:   XSL there is no need for serialization or worse it hurts :)
(:declare variable $vs:boolSerialize as xs:boolean    := true();:)

(:relevant for valueSet list. If treetype is 'limited' and param object is valued then a list is built for just this object.:)
declare variable $vs:TREETYPELIMITED                := 'limited';
(:relevant for valueSet list. If treetype is 'marked' and param object is valued then a full list is built where every valueSet hanging off this object is marked as such.:)
declare variable $vs:TREETYPEMARKED                 := 'marked';
(:relevant for valueSet list. If treetype is 'limitedmarked' and param object is valued then a limited list is built containing only valueSets hanging off this template.:)
declare variable $vs:TREETYPELIMITEDMARKED          := 'limitedmarked';

(:~
:   Return zero or more valuesets as-is wrapped in a &lt;return/&gt; element, and subsequently inside a &lt;repository&gt; element. This repository element
:   holds at least the attribute @ident with the originating project prefix and optionally the attribute @url with the repository URL in case of an external
:   repository. Id based references can match both valueSet/@id and valueSet/@ref. The latter is resolved. Note that duplicate valueSet matches may be 
:   returned. Example output:
:   &lt;return>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/repository>
:   &lt;/return>
:   
:   @param $id           - required. Identifier of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id, yyyy-mm-ddThh:mm:ss gets this specific version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetById($id as xs:string, $flexibility as xs:string?, $serialize as xs:boolean) as element(return) {
    let $valueSets  := $get:colDecorData//valueSet[@id = $id] | $get:colDecorCache//valueSet[@id = $id]
    let $valueSets  :=
        if ($flexibility castable as xs:dateTime) then (
            $valueSets[@effectiveDate = $flexibility]
        ) else (
            $valueSets[@effectiveDate = max($valueSets/xs:dateTime(@effectiveDate))][1]
        )
    return
    <return>
    {
        for $repository in $valueSets
        let $prefix    := $repository/ancestor::decor/project/@prefix
        let $urlident  := concat($repository/ancestor::cacheme/@bbrurl, $prefix)
        group by $urlident
        return
            <project ident="{$prefix}">
            {
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage
                ,
                for $valueSet in $repository
                let $ideff  := concat($valueSet/@id, $valueSet/@ref, $valueSet/@effectiveDate)
                group by $ideff
                order by $valueSet[1]/@effectiveDate descending
                return 
                    vs:getRawValueSet($valueSet[1], (), $repository[1]/ancestor::decor/project/@defaultLanguage, $prefix, (), false(), $serialize)
            }
            </project>
    }
    </return>
    (:<return>
    {
        for $prefix in $get:colDecorData//decor[.//valueSet[@id=$id] | .//valueSet[@ref=$id]]/project/@prefix
        return
            vs:getValueSetById ($id, $flexibility, $prefix, $serialize)/*
    }
    </return>:)
};

(:~
:   See vs:getValueSetById ($id as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?, $serialize as xs:boolean) for documentation
:   
:   @param $id           - required. Identifier of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - required. determines search scope. pfx- limits scope to this project only
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetById($id as xs:string, $flexibility as xs:string?, $prefix as xs:string, $serialize as xs:boolean) as element(return) {
    vs:getValueSetById($id, $flexibility, $prefix, (), $serialize)
};

(:~
:   Return zero or more valuesets as-is wrapped in a &lt;return/&gt; element, and subsequently inside a &lt;repository&gt; element. This repository element
:   holds at least the attribute @ident with the originating project prefix and optionally the attribute @url with the repository URL in case of an external
:   repository. Id based references can match both valueSet/@id and valueSet/@ref. The latter is resolved. Note that duplicate valueSet matches may be 
:   returned. Example output for valueSet/@ref:<br/>
:   &lt;return>
:       &lt;project ident="epsos-">
:           &lt;valueSet ref="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature">
:               ...
:           &lt;/valueSet>
:       &lt;/project>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-" referencedFrom="epsos-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/repository>
:   &lt;/return>
:   <p>Example output for valueSet/@id:</p>
:   &lt;return>
:       &lt;project ident="epsos-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/repository>
:   &lt;/return>
:   
:   @param $id           - required. Identifier of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id (regardless of name), yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetById($id as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?, $serialize as xs:boolean) as element(return) {
let $argumentCheck              :=
    if (string-length($id)=0) then
        error(xs:QName('error:NotEnoughArguments'),'Argument id is required')
    else if (string-length($prefix)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument prefix is required')
    else ()

let $internalrepositories       := art:getDecorByPrefix($prefix, $version, ())[1]

(:let $internalvaluesets          := $internalrepositories//valueSet[@id = $id] | $internalrepositories//valueSet[@ref = $id]:)
let $internalvaluesets          :=
    if (empty($version)) then
        $get:colDecorData//valueSet[@id = $id][ancestor::decor/project/@prefix = $prefix] | $get:colDecorData//valueSet[@ref = $id][ancestor::decor/project/@prefix = $prefix]
    else (
        $get:colDecorVersion//decor[@versionDate = $version][project/@prefix = $prefix][1]/terminology/valueSet[@id = $id] | $get:colDecorVersion//decor[@versionDate = $version][project/@prefix = $prefix][1]/terminology/valueSet[@ref = $id]
        (:
        (\: get all valueSets matching by id or ref from the terminology section of the first compilation hit because they are also compiled into datasets :\)
        let $vsset  := 
            for $vs in $get:colDecorVersion//valueSet[@id = $id] | $get:colDecorVersion//valueSet[@ref = $id]
            let $vsv := $vs/ancestor::decor/@versionDate
            let $vsp := $vs/ancestor::decor/project/@prefix
            group by $vsv, $vsp
            return
                if ($vsv = $version and $vsp = $prefix) then $vs else ()
        let $vsfl   := string(($vsset/ancestor::decor/@language)[1])
        return
            for $vs in $vsset
            return
                if ($vs[ancestor::terminology][ancestor::decor/@language = $vsfl]) then $vs else ()
        :)
    )

let $repositoryValueSetLists    :=
    <repositoryValueSetLists>
    {
        (:  don't go looking in repositories when this is an archived project version. the project should be compiled already and 
            be self contained. Repositories in their current state would give a false picture of the status quo when the project 
            was archived.
        :)
        if (empty($version)) then
            let $buildingBlockRepositories  := $internalrepositories/project/buildingBlockRepository[empty(@format)] | 
                                               $internalrepositories/project/buildingBlockRepository[@format='decor']
            (:this is the starting point for the list of servers we already visited to avoid circular reference problems:)
            (:let $bbrList                    := <buildingBlockRepository url="{$vs:strDecorServicesURL}" ident="{$prefix}"/>:)
            return
                vs:getValueSetByIdFromBBR($prefix, $id, $prefix, $buildingBlockRepositories, (), (), ())/descendant-or-self::*[valueSet[@id]]
        else ()
    }
    {
        (:from the requested project, return valueSet/(@id and @ref):)
        (: when retrieving value sets from a compiled project, the @url/@ident they came from are on the valueSet element
           reinstate that info on the repositoryValueSetList element so downstream logic works as if it really came from 
           the repository again.
        :)
        for $repository in $internalrepositories
        for $valuesets in $internalvaluesets
        let $source := concat($valuesets/@url,$valuesets/@ident)
        group by $source
        return
            if (string-length($valuesets[1]/@url)=0) then
                <repositoryValueSetList ident="{$repository/project/@prefix}">
                {
                    $valuesets
                }
                </repositoryValueSetList>
            else (
                <repositoryValueSetList url="{$valuesets[1]/@url}" ident="{$valuesets[1]/@ident}" referencedFrom="{$prefix}">
                {
                    for $valueset in $valuesets
                    return
                        <valueSet>{$valueset/(@* except (@url|@ident|@referencedFrom)), $valueset/node()}</valueSet>
                }
                </repositoryValueSetList>
            )
    }
    </repositoryValueSetLists>

let $max                        :=
    if ($flexibility castable as xs:dateTime) then $flexibility else 
    if (empty($flexibility)) then () else (
        max($repositoryValueSetLists//valueSet/xs:dateTime(@effectiveDate))
    )

return
    <return>
    {
        if (empty($max)) then
            for $segment in $repositoryValueSetLists/*
            let $elmname := if ($segment[empty(@url)][@ident=$prefix]) then 'project' else 'repository'
            return
                element {$elmname} {
                    $segment/@*,
                    for $valueSet in $segment/valueSet
                    let $ideff  := concat($valueSet/@id, $valueSet/@ref, $valueSet/@effectiveDate)
                    group by $ideff
                    order by $valueSet[1]/@effectiveDate descending
                    return (
                        vs:getRawValueSet($valueSet[1], (), (), $prefix, $version, false(), $serialize)
                    )
                }
        else (
            for $segment in $repositoryValueSetLists/*[valueSet[@effectiveDate = $max]]
            let $elmname := if ($segment[empty(@url)][@ident=$prefix]) then 'project' else 'repository'
            return
                element {$elmname} {
                    $segment/@*, 
                    for $valueSet in $segment/valueSet[@ref] | $segment/valueSet[@effectiveDate = $max]
                    let $ideff  := concat($valueSet/@id, $valueSet/@ref, $valueSet/@effectiveDate)
                    group by $ideff
                    order by $valueSet[1]/@effectiveDate descending
                    return (
                        vs:getRawValueSet($valueSet[1], (), (), $prefix, $version, false(), $serialize)
                    )
                }
        )
    }
    </return>
};

(:~
:   Return zero or more valuesets as-is. Name based references cannot return valueSet/@ref, only valueSet/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate valueSet matches may be returned. Example output:
:   &lt;return>
:       &lt;project ident="ad2bbr-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/project>
:   &lt;/return>
:   
:   @param $name             - required. Name of the valueset to retrieve (valueSet/@name) Regex pattern matching is allowed. Searching is case sensitive.
:   @param $flexibility      - optional. null gets all versions, 'dynamic' gets the newest version based on name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $useRegexMatching - required. determines whether or not $name is regular expression or not
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetByName($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean, $serialize as xs:boolean) as element(return) {
    let $valueSets  :=
        if ($useRegexMatching) then 
            $get:colDecorData//valueSet[matches(@name, $name)] | $get:colDecorCache//valueSet[matches(@name, $name)]
        else (
            $get:colDecorData//valueSet[@name = $name] | $get:colDecorCache//valueSet[@name = $name]
        )
    let $valueSets  :=
        if ($flexibility castable as xs:dateTime) then (
            $valueSets[@effectiveDate = $flexibility]
        )
        else (
            for $valueSet in $valueSets[@id]
            let $id := $valueSet/@id
            group by $id
            return (
                let $max := max($valueSet/xs:dateTime(@effectiveDate))
                return
                $valueSet[@effectiveDate = $max][1]
            )
        )
    return
    <return>
    {
        for $repository in $valueSets
        let $prefix    := $repository/ancestor::decor/project/@prefix
        let $urlident  := concat($repository/ancestor::cacheme/@bbrurl,$prefix)
        group by $urlident
        return
            <project ident="{$prefix}">
            {
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage,
                for $valueSet in $repository
                return vs:getRawValueSet($valueSet, (), $repository[1]/ancestor::decor/project/@defaultLanguage, $prefix, (), false(), $serialize)
            }
            </project>
    }
    </return>
    (:<return>
    {
        for $prefix in $get:colDecorData//decor[not(@private='true')][@repository='true']/project/@prefix
        return
            vs:getValueSetByName($name, $flexibility, $prefix, $useRegexMatching, $serialize)/*
    }
    </return>:)
};

(:~
:   See vs:getValueSetByName ($name as xs:string, $flexibility as xs:string?, $prefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?, $serialize as xs:boolean) for documentation
:   
:   @param $name             - required. Name of the valueset to retrieve (valueSet/@name) Regex pattern matching is allowed. Searching is case sensitive.
:   @param $flexibility      - optional. null gets all versions, 'dynamic' gets the newest version based on name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix           - required. determines search scope. pfx- limits scope to this project only
:   @param $useRegexMatching - required. determines whether or not $name is regular expression or not
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetByName($name as xs:string, $flexibility as xs:string?, $prefix as xs:string, $useRegexMatching as xs:boolean, $serialize as xs:boolean) as element()* {
    vs:getValueSetByName($name, $flexibility, $prefix, $useRegexMatching, (), $serialize)
};

(:~
:   Return zero or more valuesets as-is. Name based references cannot return valueSet/@ref, only valueSet/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate valueSet matches may be returned. Example output:
:   &lt;return>
:       &lt;project ident="ad2bbr-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/project>
:   &lt;/return>
:   
:   @param $name             - required. Name of the valueset to retrieve (valueSet/@name) Regex pattern matching is allowed. Searching is case sensitive.
:   @param $flexibility      - optional. null gets all versions, 'dynamic' gets the newest version based on name (regardless of id), yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix           - required. determines search scope. pfx- limits scope to this project only
:   @param $useRegexMatching - required. determines whether or not $name is regular expression or not
:   @param $version          - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:   @since 2014-04-02 Changed behavior so 'dynamic' does not retrieve the latest version per id, but the latest calculated over all ids
:)
declare function vs:getValueSetByName($name as xs:string, $flexibility as xs:string?, $prefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?, $serialize as xs:boolean) as element(return) {
let $argumentCheck          :=
    if (string-length($name)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument name is required')
    else if (string-length($prefix)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument prefix is required')
    else ()

let $decor                  := art:getDecorByPrefix($prefix, $version, ())[1]

let $internalrepositories   := 
    if ($useRegexMatching) then 
        let $luceneQuery    := vs:getSimpleLuceneQuery(tokenize(lower-case($name),'\s'))
        let $luceneOptions  := vs:getSimpleLuceneOptions()
        return
            $decor/terminology/valueSet[ft:query(@name, $luceneQuery, $luceneOptions)]
    else
        $decor/terminology/valueSet[@name = $name]

let $repositoryValueSetLists    :=
    <repositoryValueSetLists>
    {
        (:  don't go looking in repositories when this is an archived project version. the project should be compiled already and 
            be self contained. Repositories in their current state would give a false picture of the status quo when the project 
            was archived. Also don't go looking in repositories when there's no valueSet/@ref matching our id
        :)
        if (empty($version) and $internalrepositories[@ref]) then
            let $buildingBlockRepositories  := $decor/project/buildingBlockRepository[empty(@format)] |
                                               $decor/project/buildingBlockRepository[@format='decor']
            (:this is the starting point for the list of servers we already visited to avoid circular reference problems:)
            let $bbrList                    := <buildingBlockRepository url="{$vs:strDecorServicesURL}" ident="{$prefix}"/>
            return
                vs:getValueSetByNameFromBBR($prefix, $name, $prefix, $buildingBlockRepositories, (), ())[valueSet[@id]]
        else ()
    }
    </repositoryValueSetLists>

let $valueSetsById := 
    if ($internalrepositories/(@id|@ref)) then
        (:if the project has our valueSet[@id|@ref] by name, do not check any other ids in the repositories:)
        for $id in distinct-values($internalrepositories/@id | $internalrepositories/@ref | $repositoryValueSetLists/repositoryValueSetList/valueSet[@id= ($internalrepositories/@id | $internalrepositories/@ref)]/@id)
        return
            vs:getValueSetById($id, $flexibility, $prefix, $version, $serialize)/*
    else (
        (:if the project has our valueSet[@id|@ref] by name, check any hit from the repositories:)
        for $id in distinct-values($internalrepositories/@id | $internalrepositories/@ref | $repositoryValueSetLists/repositoryValueSetList/valueSet/@id)
        return
            vs:getValueSetById($id, $flexibility, $prefix, $version, $serialize)/*
    )

return
    <return>
    {
        if (empty($flexibility)) then
            for $repositoryValueSetList in $valueSetsById[valueSet]
            let $elmname := $repositoryValueSetList/name()
            return
                element {$elmname}
                {
                    $repositoryValueSetList/@*,
                    for $valueSet in $repositoryValueSetList/valueSet
                    order by xs:dateTime($valueSet/@effectiveDate) descending
                    return   $valueSet
                }
        else if ($flexibility castable as xs:dateTime) then
            for $repositoryValueSetList in $valueSetsById[valueSet[@ref or @effectiveDate=$flexibility]]
            let $elmname := $repositoryValueSetList/name()
            return
                element {$elmname}
                {
                    $repositoryValueSetList/@*,
                    $repositoryValueSetList/valueSet[@ref or @effectiveDate=$flexibility]
                }
        else
            for $repositoryValueSetList in $valueSetsById[valueSet[@ref or @effectiveDate=string((max($valueSetsById//valueSet/xs:dateTime(@effectiveDate)))[1])]]
            let $elmname := $repositoryValueSetList/name()
            return
                element {$elmname}
                {
                    $repositoryValueSetList/@*,
                    $repositoryValueSetList/valueSet[@ref or @effectiveDate=string((max($valueSetsById//valueSet/xs:dateTime(@effectiveDate)))[1])]
                }
    }
    </return>
};

(:~
:   See vs:getValueSetByRef ($idOrName as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?, $serialize as xs:boolean) for documentation
:   
:   @param $idOrName     - required. Identifier (@id) or name (@name) of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id or name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - required. determines search scope. pfx- limits scope to this project only
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetByRef($idOrName as xs:string, $flexibility as xs:string?, $prefix as xs:string, $serialize as xs:boolean) as element()* {
    vs:getValueSetByRef($idOrName, $flexibility, $prefix, (), $serialize)
};

(:~
:   Returns zero or more valuesets as-is. This function is useful to call from a vocabulary reference inside a template, or from a 
:   templateAssociation where @id or @name may have been used as the reference key. Uses vs:getValueSetById() and vs:getValueSetByName() to get the
:   result and returns that as-is. See those functions for more documentation on the output format.
:   
:   @param $idOrName     - required. Identifier (@id) or name (@name) of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id or name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetByRef($idOrName as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?, $serialize as xs:boolean) as element()* {

let $argumentCheck :=
    if (string-length($idOrName)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument idOrName is required')
    else if (string-length($prefix)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument prefix is required')
    else ()

return
    if (matches($idOrName,'^[\d\.]+$')) then
        vs:getValueSetById($idOrName, $flexibility, $prefix, $version, $serialize)
    else
        vs:getValueSetByName($idOrName, $flexibility, $prefix, false(), $version, $serialize)
};

(:~
:   Return zero or more expanded valuesets wrapped in a &lt;return/&gt; element, and subsequently inside a &lt;repository&gt; element. This repository element
:   holds at least the attribute @ident with the originating project prefix and optionally the attribute @url with the repository URL in case of an external
:   repository. Id based references can match both valueSet/@id and valueSet/@ref. The latter is resolved. Note that duplicate valueSet matches may be 
:   returned. Example output:
:   &lt;return>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li> Valuesets get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>valueSet/@ref is treated as if it were the referenced valueset</li>
:   <li>If parameter prefix is not used then only valueSet/@id is matched. If that does not give results, then 
:     valueSet/@ref is matched, potentially expanding into a buildingBlockRepository</li>
:   <li>Any (nested) include @refs in the valueset are resolved, if applicable using the buildingBlockRepository 
:     configuration in the project that the valueSet/@id was found in</li>
:                                
:   @param $id           - required. Identifier of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id, yyyy-mm-ddThh:mm:ss gets this specific version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getExpandedValueSetById($id as xs:string, $flexibility as xs:string?, $serialize as xs:boolean) as element() {
    let $valueSets  :=
        $get:colDecorData//valueSet[@id = $id] | $get:colDecorCache//valueSet[@id = $id]
    let $valueSets  :=
        if ($flexibility castable as xs:dateTime) then (
            $valueSets[@effectiveDate = $flexibility]
        ) else (
            $valueSets[@effectiveDate = max($valueSets/xs:dateTime(@effectiveDate))][1]
        )
    return
    <return>
    {
        for $repository in $valueSets
        let $prefix    := $repository/ancestor::decor/project/@prefix
        let $urlident  := concat($repository/ancestor::cacheme/@bbrurl,$prefix)
        group by $urlident
        return
            <project ident="{$prefix}">
            {
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage,
                for $valueSet in $repository[@id]
                return
                    vs:getExpandedValueSet($valueSet, $prefix, (), (), $serialize)
            }
            </project>
    }
    </return>
    (:<result>
    {
        for $prefix in $get:colDecorData//decor[.//valueSet[@id=$id] | .//valueSet[@ref=$id]]/project/@prefix
        return
            vs:getExpandedValueSetById($id, $flexibility, $prefix, (), (), false())/*
    }
    </result>:)
};

(:~
:   Return zero or more expanded valuesets wrapped in a &lt;return/&gt; element, and subsequently inside a &lt;repository&gt; element. This repository element
:   holds at least the attribute @ident with the originating project prefix and optionally the attribute @url with the repository URL in case of an external
:   repository. Id based references can match both valueSet/@id and valueSet/@ref. The latter is resolved. Note that duplicate valueSet matches may be 
:   returned. Example output:
:   &lt;return>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li> Valuesets get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>valueSet/@ref is treated as if it were the referenced valueset</li>
:   <li>If parameter prefix is not used then only valueSet/@id is matched. If that does not give results, then 
:     valueSet/@ref is matched, potentially expanding into a buildingBlockRepository</li>
:   <li>Any (nested) include @refs in the valueset are resolved, if applicable using the buildingBlockRepository 
:     configuration in the project that the valueSet/@id was found in</li>
:                                
:   @param $id           - required. Identifier of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getExpandedValueSetById($id as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?, $language as xs:string?, $serialize as xs:boolean) as element() {
let $argumentCheck :=
    if (string-length($id)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument id is required')
    else if (string-length($prefix)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument prefix is required')
    else ()

let $valueSets := vs:getValueSetById($id, $flexibility, $prefix, $version, $serialize)

return
    <result>
    {
        for $repository in $valueSets/project
        return
            element {name($repository[1])}
            {
                $repository[1]/@*,
                $repository[1]/valueSet[@ref],
                for $valueSet in $repository[1]/valueSet[@id]
                return
                    vs:getExpandedValueSet($valueSet, $prefix, $version, $language, $serialize)
            }
    }
    {
        let $repositories   :=
            for $repository in $valueSets/repository
            let $urlident  := $repository/concat(@url, @ident)
            group by $urlident
            return
                element {name($repository[1])}
                {
                    $repository[1]/@*,
                    for $valueSet in $repository[1]/valueSet[@ref]
                    return
                        if ($valueSets/project/valueSet[deep-equal(self::valueSet, $valueSet)]) then () else (
                            $valueSet
                        )
                    ,
                    for $valueSet in $repository[1]/valueSet[@id]
                    return
                        if ($valueSets/project/valueSet[deep-equal(self::valueSet, $valueSet)]) then () else (
                            vs:getExpandedValueSet($valueSet, $prefix, $version, $language, $serialize)
                        )
                }
        return
            $repositories[*]
    }
    </result>
};

(:~
:   Return zero or more expanded valuesets. Name based references cannot return valueSet/@ref, only valueSet/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate valueSet matches may be returned. Example output:
:   &lt;return>
:       &lt;repository ident="ad2bbr-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li>Valuesets get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>If parameter prefix is used and parameter id leads to a valueSet/@ref, then this is treated as if it were the referenced valueset</li>
:   <li>If parameter prefix is not used then only valueSet/@id is matched. If that does not give results, then valueSet/@ref is matched, 
:   potentially expanding into a buildingBlockRepository</li>
:   <li>Any (nested) include @refs in the valueset are resolved, if applicable using the buildingBlockRepository 
:   configuration in the project that the valueSet/@id was found in</li>
:
:   @param $name             - required. Name of the valueset to retrieve (valueSet/@name) Regex pattern matching is allowed. Searching is case sensitive.
:   @param $flexibility      - optional. null gets all versions, 'dynamic' gets the newest version based on name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $useRegexMatching - required. determines whether or not $name is regular expression or not
:   @return The expanded valueSet
:   @since 2013-06-14
:)
declare function vs:getExpandedValueSetByName($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean, $serialize as xs:boolean) as element() {
    let $valueSets  :=
        if ($useRegexMatching) then 
            $get:colDecorData//valueSet[matches(@name, $name)] | $get:colDecorCache//valueSet[matches(@name, $name)]
        else (
            $get:colDecorData//valueSet[@name = $name] | $get:colDecorCache//valueSet[@name = $name]
        )
    let $valueSets  :=
        if ($flexibility castable as xs:dateTime) then (
            $valueSets[@effectiveDate = $flexibility]
        ) else (
            for $valueSet in $valueSets[@id]
            let $id := $valueSet/@id
            group by $id
            return (
                let $max := max($valueSet/xs:dateTime(@effectiveDate))
                return
                $valueSet[@effectiveDate = $max][1]
            )
        )
    return
    <return>
    {
        for $repository in $valueSets
        let $prefix    := $repository/ancestor::decor/project/@prefix
        let $urlident  := concat($repository/ancestor::cacheme/@bbrurl,$prefix)
        group by $urlident
        return
            <project ident="{$prefix}">
            {
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage,
                for $valueSet in $repository[@id]
                return
                    vs:getExpandedValueSet($valueSet, $prefix, (), (), $serialize)
            }
            </project>
    }
    </return>
    (:<result>
    {
        for $prefix in $get:colDecorData//decor[not(@private='true')][@repository='true']/project/@prefix
        return
            vs:getExpandedValueSetByName($name, $flexibility, $prefix, $useRegexMatching)/*
    }
    </result>:)
};

(:~
:   Return zero or more expanded valuesets. Name based references cannot return valueSet/@ref, only valueSet/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate valueSet matches may be returned. Example output:
:   &lt;return>
:       &lt;repository ident="ad2bbr-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318">
:               ...
:           &lt;/valueSet>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li>Valuesets get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>If parameter prefix is used and parameter id leads to a valueSet/@ref, then this is treated as if it were the referenced valueset</li>
:   <li>If parameter prefix is not used then only valueSet/@id is matched. If that does not give results, then valueSet/@ref is matched, 
:   potentially expanding into a buildingBlockRepository</li>
:   <li>Any (nested) include @refs in the valueset are resolved, if applicable using the buildingBlockRepository 
:   configuration in the project that the valueSet/@id was found in</li>
:
:   @param $name             - required. Name of the valueset to retrieve (valueSet/@name) Regex pattern matching is allowed. Searching is case sensitive.
:   @param $flexibility      - optional. null gets all versions, 'dynamic' gets the newest version based on name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix           - required. determines search scope. pfx- limits scope to this project only
:   @param $useRegexMatching - required. determines whether or not $name is regular expression or not
:   @param $version          - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return The expanded valueSet
:   @since 2013-06-14
:)
declare function vs:getExpandedValueSetByName($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean, $prefix as xs:string, $version as xs:string?, $language as xs:string?, $serialize as xs:boolean) as element() {
let $argumentCheck :=
    if (string-length($name)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument name is required')
    else if (string-length($prefix)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument prefix is required')
    else ()

let $valueSets := vs:getValueSetByName($name, $flexibility, $prefix, $useRegexMatching, $version, $serialize)

return
    <result>
    {
        for $repository in $valueSets/(project|repository)
        return
            element {name($repository)}
            {
                $repository/@*,
                $repository/valueSet[@ref],
                for $valueSet in $repository/valueSet[@id]
                return
                    vs:getExpandedValueSet($valueSet, $prefix, $version, $language, $serialize)
            }
    }
    </result>
};

(:~
:   Returns zero or more expanded valuesets. This function is useful to call from a vocabulary reference inside a template, or from a 
:   templateAssociation where @id or @name may have been used as the reference key. Uses vs:getExpandedValueSetById() and vs:getExpandedValueSetByName() 
:   to get the result and returns that as-is. See those functions for more documentation on the output format.
:   
:   @param $idOrName     - required. Identifier (@id) or name (@name) of the valueset to retrieve
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id or name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getExpandedValueSetByRef($idOrName as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?, $language as xs:string?, $serialize as xs:boolean) as element()* {
let $argumentCheck :=
    if (string-length($idOrName)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument idOrName is required')
    else if (string-length($prefix)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument prefix is required')
    else ()

return
    if (matches($idOrName,'^[\d\.]+$')) then
        vs:getExpandedValueSetById($idOrName, $flexibility, $prefix, $version, $language, $serialize)
    else
        vs:getExpandedValueSetByName($idOrName, $flexibility, false(), $prefix, $version, $language, $serialize)
};

(:~ Expands a valueSet with @id by recursively resolving all includes. Use vs:getExpandedValueSetsById to resolve a valueSet/@ref first. 
<valueSet all-attributes of the input>
  <desc all />
  <sourceCodeSystem id="any-concept-or-exception-codesystem-in-valueSet" identifierName="codeSystemName" canonicalUri="xs:anyURI" canonicalUriDSTU2="xs:anyURI" canonicalUriSTU3="xs:anyURI" canonicalUriR4="xs:anyURI" />
  <completeCodeSystem all-attributes of the input>
  <conceptList>
    <concept all-attributes-and-desc-elements />
    <exception all-attributes-and-desc-elements exceptions-contain-no-duplicates />
  </conceptList>
</valueSet>
                                
@param $valueSet     - required. The valueSet element with content to expand
@param $prefix       - required. The origin of valueSet. pfx- limits scope this project only
@param $version      - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
@return The expanded valueSet
@since 2013-06-14
:)
declare function vs:getExpandedValueSet($valueSet as element(), $prefix as xs:string, $version as xs:string?, $language as xs:string?, $serialize as xs:boolean) as element(valueSet) {
let $decor                  := art:getDecorByPrefix($prefix, $version, $language)[1]
let $language               := ($decor/@language, $decor/project/@defaultLanguage)[1]

let $language               :=
    if (empty($language) and $valueSet[../@defaultLanguage]) then $valueSet/../@defaultLanguage else adserver:getServerLanguage()

let $rawValueSet            := 
    vs:getRawValueSet($valueSet, <include ref="{$valueSet/(@id|@ref)}" flexibility="{$valueSet/@effectiveDate}"/>, $language, $prefix, $version, true(), $serialize)
let $orgprefix              := if ($valueSet/../@ident) then $valueSet/../@ident else $prefix

let $codesystemmap          := 
    map:merge(
        for $sourceCodeSystem in distinct-values($rawValueSet//@codeSystem)
        let $codeSystemName     := replace(normalize-space(art:getNameForOID($sourceCodeSystem, $language, $orgprefix)),'\s','&#160;')
        return
            map:entry($sourceCodeSystem, $codeSystemName)
    )
let $codesystemcontentmap   :=
    map:merge(
        for $sourceCodeSystem in $rawValueSet/completeCodeSystem[empty(filter)] | $rawValueSet/conceptList/include[@codeSystem][empty(@op | @code | @ref | filter)]
        let $csid               := $sourceCodeSystem/@codeSystem
        let $csed               := if ($sourceCodeSystem[@flexibility castable as xs:dateTime]) then $sourceCodeSystem/@flexibility else 'dynamic'
        let $csided             := concat($csid, $csed)
        group by $csided
        return (
            let $codeSystemContents := (cs:getCodeSystemById($csid[1], $csed[1], $orgprefix, $version)//codeSystem[@id])[1]
            return
            if ($codeSystemContents) then map:entry($csided, $codeSystemContents) else ()
        )
    )

let $completeCodeSystems    := $rawValueSet//completeCodeSystem | $rawValueSet//conceptList/include[@codeSystem][empty(@op | @code | @ref | filter | @exception[. = 'true'])]

return
    <valueSet>
    {
        $rawValueSet/@*
        ,
        $rawValueSet/desc[.//text()[not(normalize-space() = '')]]
    }
    {
        (: when a value set is pulled from a compiled project version, the sourceCodeSystem should already be present :)
        if ($rawValueSet[sourceCodeSystem]) then $rawValueSet//sourceCodeSystem else (
            for $sourceCodeSystem in $rawValueSet//@codeSystem
            let $csid   := $sourceCodeSystem
            let $csed   := $sourceCodeSystem/../@codeSystemVersion[. castable as xs:dateTime]
            group by $csid, $csed
            return
                <sourceCodeSystem id="{$csid}" identifierName="{replace(map:get($codesystemmap, $csid),'\s','&#160;')}" 
                                  canonicalUri="{art:getCanonicalUriForOID('CodeSystem', $csid, $csed, $prefix, $get:strKeyCanonicalUriPrefd)}">
                {
                    for $fhirVersion in $get:arrKeyFHIRVersions
                    return
                        attribute {concat('canonicalUri', $fhirVersion)} {art:getCanonicalUriForOID('CodeSystem', $csid, $csed, $prefix, $fhirVersion)}
                    ,
                    let $hl7v2Table0396Code     := art:getHL7v2Table0396MnemonicForOID($csid, (), $prefix)
                    return
                    if (empty($hl7v2Table0396Code)) then () else attribute hl7v2table0396 {$hl7v2Table0396Code}
                    ,
                    (: ART-DECOR 3: get for example external/hl7 :)
                    let $coll   := collection($get:strCodesystemStableData)//identifier[@id = 'urn:oid:' || $csid]
                    
                    return
                        if (empty($coll)) then () else attribute context {substring-after(util:collection-name($coll[1]), $get:strCodesystemStableData || '/')}
                }
                </sourceCodeSystem>
        )
    }
    {
        $rawValueSet/publishingAuthority
        ,
        $rawValueSet/endorsingAuthority
        ,
        $rawValueSet/revisionHistory
        ,
        $rawValueSet/purpose
        ,
        $rawValueSet/copyright
    }
    {
        if ($rawValueSet/conceptList[include | exclude | duplicate] or count(map:keys($codesystemcontentmap)) gt 0) then (
            <conceptList>
            {
                (:get defined and included codeSystems:)
                for $sourceCodeSystem in $completeCodeSystems
                let $csid               := $sourceCodeSystem/@codeSystem
                let $csed               := if ($sourceCodeSystem[@flexibility castable as xs:dateTime]) then $sourceCodeSystem/@flexibility else 'dynamic'
                let $csided             := concat($csid, $csed)
                let $codeSystemName     := map:get($codesystemmap, $csid)
                return
                    if (map:contains($codesystemcontentmap, $csided)) then (
                        let $conceptList        := map:get($codesystemcontentmap, $csided)//conceptList
                        let $checkParentChild   := exists($conceptList/codedConcept[parent | child])
                        let $topLevelConcepts   := if ($checkParentChild) then $conceptList/codedConcept[empty(parent)] else $conceptList/codedConcept[@level = '0']
                        
                        return
                            vs:getCodedConcepts($topLevelConcepts, $csid, 0, $conceptList, $topLevelConcepts/@code, $checkParentChild, $language)
                    ) else (
                        <include codeSystem="{$csid}">
                        {
                            if ($codeSystemName = '') then () else attribute codeSystemName {$codeSystemName}, 
                            $sourceCodeSystem/@codeSystemVersion, 
                            $sourceCodeSystem/@flexibility,
                            (: 2020-12 Added for deprecation of a codeSystem in the context of a valueSet :)
                            $sourceCodeSystem/@type[. = 'D'],
                            for $f in $sourceCodeSystem/filter
                            return
                                <filter>{$f/@property, $f/@op, $f/@value}</filter>
                        }
                        </include>
                    )
            }
            {
                for $source in $rawValueSet/conceptList/include[@ref][valueSet]
                return
                    comment { ' include ref=' || $source/@ref || ' flexibility=' || ($source/@flexibility[not(. = '')], 'dynamic')[1] || ' displayName=' || replace(($source/valueSet/@displayName)[1], '--', '-\\-') || ' effectiveDate=' || ($source/valueSet/@effectiveDate)[1] || ' '}
            }
            {
                for $source in $rawValueSet//conceptList/concept[not(ancestor::include[@exception = 'true'])] | 
                               $rawValueSet//conceptList/include[not(.[@ref][valueSet] | .[@exception = 'true'])] except $completeCodeSystems |
                               $rawValueSet//conceptList/exclude
                let $csid               := $source/@codeSystem
                let $csed               := if ($source[@flexibility castable as xs:dateTime]) then $source/@flexibility else 'dynamic'
                let $csided             := concat($csid, $csed)
                return
                    if ($source[self::concept | self::exclude]) then $source else
                    if (map:contains($codesystemcontentmap, $csided)) then () else (
                        $source
                    )
            }
            {
                for $node in $rawValueSet//conceptList/exception |
                             $rawValueSet//conceptList/concept[ancestor::include[@exception='true']]
                let $code       := $node/@code
                let $codeSystem := $node/@codeSystem
                group by $code, $codeSystem
                return
                    (:<exception>{$exceptions/@*, $exceptions/*}</exception>:)
                    <exception>{$node[1]/@*, $node[1]/*}</exception>
            }
            {
                $rawValueSet//conceptList/duplicate
            }
            </conceptList>
        ) else (
            $rawValueSet/conceptList
        )
    }
    </valueSet>
};

declare %private function vs:getCodedConcepts($codedConcepts as element(codedConcept)*, $codeSystemId as xs:string, $level as xs:integer, $conceptList as element(conceptList), $sofar as xs:string*, $checkParentChild as xs:boolean, $language as xs:string) {
    for $codedConcept in $codedConcepts[not(@statusCode = ('cancelled', 'rejected'))]
    let $children       := 
        if ($checkParentChild) then
            $conceptList/codedConcept[parent/@code = $codedConcept/@code][not(@code = $codedConcept/@code)]
        else (
            let $nextFirstOtherHierarchy            := $codedConcept/following-sibling::codedConcept[@level castable as xs:integer][xs:integer(@level) le $level]
            return
                $codedConcept/following-sibling::codedConcept[@level castable as xs:integer][xs:integer(@level) = ($level + 1)][not(preceding-sibling::codedConcept[@code = $nextFirstOtherHierarchy/@code])]
        )
    let $type           := 
        if ($codedConcept[@statusCode = ('deprecated', 'retired', 'cancelled', 'rejected')]) then 'D' else
        if ($codedConcept[@type = 'A'] | $codedConcept[@abstract = 'true'] | $codedConcept/property[@code = 'notSelectable']/valueBoolean[@value = 'true']) then 'A' else
        if ($children) then 'S'
        else ('L')
    return (
        <concept code="{$codedConcept/@code}" 
                 codeSystem="{$codeSystemId}" 
                 codeSystemName="{$codedConcept/ancestor::codeSystem/@displayName}" 
                 codeSystemVersion="{$codedConcept/ancestor::codeSystem/@effectiveDate}" 
                 displayName="{vs:getDesignationFromCodedConcept($codedConcept, $language)}" 
                 level="{$level}" type="{$type}">
        {
            for $designation in $codedConcept/designation
            let $designationtype    := if ($designation[@type]) then $designation/@type else 'preferred'
            return
                <designation>
                {
                    $designation/@displayName, 
                    attribute type {$designationtype}, 
                    $designation/@language, 
                    $designation/@lastTranslated, 
                    $designation/@mimeType
                }
                </designation>
        }
        {
            $codedConcept/desc[.//text()[not(normalize-space() = '')]]
        }
        </concept>
        ,
        vs:getCodedConcepts($children[not(@code = $sofar)], $codeSystemId, $level + 1, $conceptList, distinct-values(($sofar, $children/@code)), $checkParentChild, $language)
    )
};

declare %private function vs:getDesignationFromCodedConcept($codedConcept as element(codedConcept), $language as xs:string?) as xs:string? {
    let $designation    := $codedConcept/designation[@language = $language]
    let $designation    := if ($designation) then $designation else $codedConcept/designation
    let $designation    :=
        if ($designation[@type = 'fsn']) then $designation[@type = 'fsn'][1] else
        if ($designation[@type = 'preferred']) then $designation[@type = 'preferred'][1] else
        if ($designation[empty(@type)]) then $designation[empty(@type)][1] else $designation[1]
    
    return
        $designation/@displayName
};

(:~
:   See vs:getValueSetList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?, $version as xs:string?) for documentation
:   
:   @param $id           - optional. Identifier of the valueset to retrieve
:   @param $name         - optional. Name of the valueset to retrieve (valueSet/@name)
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id or name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - optional. determines search scope. null is full server, pfx- limits scope to this project only
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetList($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?) as element()? {
    vs:getValueSetList($id, $name, $flexibility, $prefix, (), ())
};

(:~
:   Returns zero or more valuesets as listed in the terminology section. This function is useful e.g. to call from a ValueSetIndex. Parameter id, name or prefix is required.
:   &lt;return>
:       &lt;repository ident="epsos-">
:           &lt;valueSet ref="2.16.840.1.113883.1.11.159331" name="ActStatus" displayName="ActStatus"/>
:           &lt;valueSet id="1.3.6.1.4.1.12559.11.10.1.3.1.42.4" name="epSOSCountry" displayName="epSOS Country" effectiveDate="2013-06-03T00:00:00" statusCode="draft"/>
:       &lt;/repository>
:       &lt;repository ident="naw-">
:           &lt;valueSet id="2.16.840.1.113883.2.4.3.11.60.101.11.13" name="Land" displayName="Land" effectiveDate="2013-03-25T14:13:00" statusCode="final"/>
:           &lt;valueSet id="2.16.840.1.113883.2.4.3.11.60.101.11.1" name="VerzekeringsSoort" displayName="Verzekeringssoort" effectiveDate="2013-03-25T14:13:00" statusCode="final"/>
:       &lt;/repository>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-" referencedFrom="epsos-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.159331" name="ActStatus" displayName="ActStatus" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318"/>
:       &lt;/repository>
:   &lt;/return>
:   
:   @param $id           - optional. Identifier of the valueset to retrieve
:   @param $name         - optional. Name of the valueset to retrieve (valueSet/@name)
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id or name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - optional. determines search scope. null is full server, pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetList($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?, $version as xs:string?) as element()? {
    vs:getValueSetList($id, $name, $flexibility, $prefix, $version, ())
};

(:~
:   Returns zero or more valuesets as listed in the terminology section. This function is useful e.g. to call from a ValueSetIndex. Parameter id, name or prefix is required.
:   &lt;return>
:       &lt;repository ident="epsos-">
:           &lt;valueSet ref="2.16.840.1.113883.1.11.159331" name="ActStatus" displayName="ActStatus"/>
:           &lt;valueSet id="1.3.6.1.4.1.12559.11.10.1.3.1.42.4" name="epSOSCountry" displayName="epSOS Country" effectiveDate="2013-06-03T00:00:00" statusCode="draft"/>
:       &lt;/repository>
:       &lt;repository ident="naw-">
:           &lt;valueSet id="2.16.840.1.113883.2.4.3.11.60.101.11.13" name="Land" displayName="Land" effectiveDate="2013-03-25T14:13:00" statusCode="final"/>
:           &lt;valueSet id="2.16.840.1.113883.2.4.3.11.60.101.11.1" name="VerzekeringsSoort" displayName="Verzekeringssoort" effectiveDate="2013-03-25T14:13:00" statusCode="final"/>
:       &lt;/repository>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-" referencedFrom="epsos-">
:           &lt;valueSet id="2.16.840.1.113883.1.11.159331" name="ActStatus" displayName="ActStatus" effectiveDate="2013-03-11T00:00:00" statusCode="final" versionLabel="DEFN=UV=VO=1206-20130318"/>
:       &lt;/repository>
:   &lt;/return>
:   
:   @param $id           - optional. Identifier of the valueset to retrieve
:   @param $name         - optional. Name of the valueset to retrieve (valueSet/@name)
:   @param $flexibility  - optional. null gets all versions, 'dynamic' gets the newest version based on id or name, yyyy-mm-ddThh:mm:ss gets this specific version
:   @param $prefix       - optional. determines search scope. null is full server, pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the valueset will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero value sets in case no matches are found, one if only one exists or if a specific version was requested, or more if more versions exist and no specific version was requested
:   @since 2013-06-14
:)
declare function vs:getValueSetList($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?, $version as xs:string?, $codeSystem as xs:string?) as element()? {
let $decor                      := art:getDecorByPrefix($prefix, $version, ())[1]

let $projectvaluesets           :=
    if (empty($id) and empty($name)) then
        $decor/terminology/valueSet
    else if (empty($name)) then
        $decor/terminology/valueSet[@id = $id] | $decor/terminology/valueSet[@ref = $id]
    else if (empty($id)) then
        $decor/terminology/valueSet[@name = $name]
    else
        $decor/terminology/valueSet[@id = $id] | $decor/terminology/valueSet[@ref = $id] | $decor/terminology/valueSet[@name = $id]

let $projectvaluesets           :=
    if (empty($codeSystem)) then $projectvaluesets else $projectvaluesets[.//@codeSystem = $codeSystem]

(: local server and external building block repository handling :)
let $repositoryValueSetLists    :=
    <repositoryValueSetLists>
    {
        (: when retrieving value sets from a compiled project, the @url/@ident they came from are on the valueSet element
           reinstate that info on the repositoryValueSetList element so downstream logic works as if it really came from 
           the repository again.
        :)
        for $valuesets in $projectvaluesets[@url]
        let $source := concat($valuesets/@url,$valuesets/@ident)
        group by $source
        return
            <repositoryValueSetList url="{$valuesets[1]/@url}" ident="{$valuesets[1]/@ident}" referencedFrom="{$prefix}">
            {
                for $valueset in $valuesets
                return
                    <valueSet>{$valueset/(@* except (@url|@ident|@referencedFrom)), $valueset/node()}</valueSet>
            }
            </repositoryValueSetList>
    }
    </repositoryValueSetLists>
    
(:now prune projectvaluesets from any valueSet[@url] as those are 'moved' to the repository section:)
let $projectvaluesets := $projectvaluesets[empty(@url)]
    
return 
    <return>
    {
        if (empty($flexibility)) then (
            let $groupedValueSets :=
                for $valueSet in $projectvaluesets
                let $ident      := $valueSet/ancestor::decor/project/@prefix
                group by $ident
                return
                    element {if ($valueSet/ancestor::decor/project/@prefix=$prefix) then 'project' else 'repository'}
                    {
                        attribute ident {$ident}
                        ,
                        for $version in $valueSet
                        return
                            <valueSet>{ $version/@* }</valueSet>
                    }
            let $groupedRepositories :=
                for $section in $repositoryValueSetLists/repositoryValueSetList[valueSet]
                let $url            := $section/@url
                let $ident          := $section/@ident
                let $referencedFrom := $section/@referencedFrom
                group by $url, $ident, $referencedFrom
                return
                    element {if ($section[1][@url]) then 'repository' else 'project'}
                    {
                        $section[1]/@ident,
                        $section[1]/@url,
                        if ($section[1][@url]) then attribute {'referencedFrom'} {string-join(distinct-values($section/@referencedFrom),' ')} else (),
                        for $valueSet in $section/valueSet
                        return
                            <valueSet>{ $valueSet/@* }</valueSet>
                    }
            
            return ($groupedValueSets | $groupedRepositories)
            
        ) else if ($flexibility castable as xs:dateTime) then (
            let $valueSetIds := distinct-values(
                $repositoryValueSetLists/repositoryValueSetList/valueSet[@id][@effectiveDate = $flexibility]/@id |
                $projectvaluesets[@id][@effectiveDate = $flexibility]/@id)
            
            let $groupedValueSets :=
                for $valueSet in $projectvaluesets[@ref = $valueSetIds] | $projectvaluesets[@effectiveDate = $flexibility]
                let $ident      := $valueSet/ancestor::decor/project/@prefix
                group by $ident
                return
                    element {if ($valueSet/ancestor::decor/project/@prefix=$prefix) then 'project' else 'repository'}
                    {
                        attribute ident {$ident}
                        ,
                        for $valueSet in $valueSet[@ref=$valueSetIds] | $valueSet[@effectiveDate = $flexibility]
                        return
                            <valueSet>{ $valueSet/@* }</valueSet>
                    }
            let $groupedRepositories :=
                for $section in $repositoryValueSetLists/repositoryValueSetList[valueSet[@ref = $valueSetIds] | valueSet[@effectiveDate = $flexibility]]
                let $url            := $section/@url
                let $ident          := $section/@ident
                let $referencedFrom := $section/@referencedFrom
                group by $url, $ident, $referencedFrom
                return
                    element {if ($section[1][@url]) then 'repository' else 'project'}
                    {
                        $section[1]/@ident,
                        $section[1]/@url,
                        if ($section[1][@url]) then attribute {'referencedFrom'} {string-join(distinct-values($section/@referencedFrom),' ')} else (),
                        for $valueSet in $section/valueSet[@ref = $valueSetIds] | $section/valueSet[@effectiveDate = $flexibility]
                        return
                            <valueSet>{ $valueSet/@* }</valueSet>
                    }
            
            return ($groupedValueSets | $groupedRepositories)
            
        ) else (
            let $valueSetIds := distinct-values($projectvaluesets/(@id|@ref))
            
            let $groupedValueSets :=
                for $valueSet in $projectvaluesets
                let $ident      := $valueSet/ancestor::decor/project/@prefix
                group by $ident
                return
                    element {if ($valueSet/ancestor::decor/project/@prefix = $prefix) then 'project' else 'repository'}
                    {
                        attribute ident {$ident}
                        ,
                        for $valueSet in $valueSet[@id]
                        let $valueSetNewest := string(
                                max(
                                    ($projectvaluesets[@id=$valueSet/@id]/xs:dateTime(@effectiveDate),
                                    $repositoryValueSetLists/repositoryValueSetList/valueSet[@id = $valueSet/@id]/xs:dateTime(@effectiveDate))
                                )
                            ) 
                        return
                            if ($valueSet/@effectiveDate=$valueSetNewest) then
                                <valueSet>{ $valueSet/@* }</valueSet>
                            else ()
                        ,
                        (:for $valueSet in $versions[@ref]:)
                        for $valueSet in $valueSet[@ref]
                        return
                            <valueSet>{ $valueSet/@* }</valueSet>
                    }
            let $groupedRepositories :=
                for $section in $repositoryValueSetLists/repositoryValueSetList[valueSet]
                let $url            := $section/@url
                let $ident          := $section/@ident
                let $referencedFrom := $section/@referencedFrom
                group by $url, $ident, $referencedFrom
                return
                    element {if ($section[1][@url]) then 'repository' else 'project'}
                    {
                        $section[1]/@ident,
                        $section[1]/@url,
                        if ($section[1][@url]) then attribute {'referencedFrom'} {string-join(distinct-values($section/@referencedFrom),' ')} else (),
                        for $valueSet in $section/valueSet[@id]
                        let $valueSetNewest := string(
                                max(
                                    ($projectvaluesets[@id=$valueSet/@id]/xs:dateTime(@effectiveDate),
                                    $repositoryValueSetLists/repositoryValueSetList/valueSet[@id=$valueSet/@id]/xs:dateTime(@effectiveDate))
                                )
                            )
                        return
                            if ($valueSet/@effectiveDate=$valueSetNewest) then 
                                <valueSet>{ $valueSet/@* }</valueSet>
                            else ()
                    }
                    
            return ($groupedValueSets | $groupedRepositories)
        )
    }
    </return>
};

(:~
:   Returns zero or more valuesets as listed in the terminology section of all BBR projects, or in requested project (param prefix).
:   The list MAY be trimmed down valueSets related to params id | name | flexibility. This combination may point to a valueSet, concept or template.
:
:   1. If id | name | flexibility points to a valueSet from the project set, then the valueSet list contains this valueSet. 
:   2. If id | name | flexibility points to a dataset concept from the project set, then the valueSet list contains any valueSets associated with this concept.
:   3. If id | name | flexibility points to a template from the project set, then the valueSet list contains any valueSets associated with this template.
:
:   TODO? and any (nested) inclusions of the valueSet
:   TODO? anticipate transaction concepts with their own binding?
:)
declare function vs:getValueSetList-v2($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?, $version as xs:string?, $classified as xs:boolean, $treetype as xs:string?) as element()? {
let $treetype               :=
    if (empty($id) and empty($name)) then ($vs:TREETYPELIMITED) else 
    if ($treetype = ($vs:TREETYPELIMITED, $vs:TREETYPEMARKED, $vs:TREETYPELIMITEDMARKED)) then $treetype 
    else $vs:TREETYPELIMITED

let $decorProjects          := if (empty($prefix)) then $get:colDecorData/decor[not(@private = 'true')] else art:getDecorByPrefix($prefix, $version, ())
    
let $objects                :=
    if (string-length($id) = 0) then (
        $decorProjects/terminology/valueSet[@name = $name]
    ) else (
        let $t  := $decorProjects/terminology/valueSet[@id = $id] | $decorProjects/terminology/valueSet[@ref = $id] | $decorProjects/terminology/valueSet[@name = $name]
        let $t  := if ($t) then $t else (art:getConcept($id, $flexibility, $version)[ancestor::decor/project/@prefix = $decorProjects/project/@prefix])
        let $t  := 
            if ($t) then $t else 
            if (string-length($prefix) = 0) then (
                templ:getExpandedTemplateById($id, $flexibility, false())/template/template[@id]
            ) 
            else (
                templ:getExpandedTemplateById($id, $flexibility, $prefix, $version, (), false())/template/template[@id]
            ) 
        return $t
    )
let $objectValueSets        :=
    if ($objects) then (
        for $object in $objects[self::concept]
        return art:getConceptAssociations($object)[@valueSet]
        ,
        $objects[self::template]//vocabulary[@valueSet]
        ,
        $objects[self::valueSet]
    ) else ()
let $objectType             := $objects[1]/name()
    
let $projectvaluesets       :=
    if ($objects and $treetype = $vs:TREETYPELIMITED) then
        $decorProjects/terminology/valueSet[@id = $objectValueSets/@valueSet] | 
        $decorProjects/terminology/valueSet[@ref = $objectValueSets/@valueSet] | 
        $decorProjects/terminology/valueSet[@name = $objectValueSets/@valueSet] |
        $decorProjects/terminology/valueSet[@id = $objectValueSets/@id] |
        $decorProjects/terminology/valueSet[@ref = $objectValueSets/@id] |
        $decorProjects/terminology/valueSet[@id = $objectValueSets/@ref] |
        $decorProjects/terminology/valueSet[@ref = $objectValueSets/@ref]
    else
        $decorProjects/terminology/valueSet

let $referencedvaluesets    :=
    if (empty($version)) then (
        for $ref in $projectvaluesets/@ref
        let $p      := $ref/ancestor::decor/project/@prefix
        let $pref   := concat($p, $ref)
        group by $pref
        return
            vs:getValueSetById($ref[1], (), $p[1], false())//valueSet[@id]
    ) else ()
    
(: local server and external building block repository handling :)
let $repositoryValueSetLists :=
    (: when retrieving value sets from a compiled project, the @url/@ident they came from are on the valueSet element
       reinstate that info on the repositoryValueSetList element so downstream logic works as if it really came from 
       the repository again.
    :)
    for $valuesets in $projectvaluesets[@url] | $referencedvaluesets
    let $url        := if ($valuesets/@url) then ($valuesets/@url) else ($valuesets/parent::*/@url)
    let $ident      := if ($valuesets/@url) then ($valuesets/@ident) else ($valuesets/parent::*/@ident)
    let $refFrom    := if ($valuesets/@referencedFrom) then ($valuesets/@referencedFrom) else ($valuesets/parent::*/@referencedFrom)
    let $refFrom    := distinct-values(($prefix, $refFrom))
    let $source     := concat($url, $ident)
    group by $source
    return
        <repository url="{$url[1]}" ident="{$ident[1]}" referencedFrom="{$refFrom}">
        {
            for $valueset in $valuesets
            
            let $refItems       :=
                $objectValueSets[@valueSet = $valueset/@ref] | 
                $objectValueSets[@ref = $valueset/@ref] | 
                $objectValueSets[@valueSet = $valueset/@id][@flexibility = $valueset/@effectiveDate] | 
                $objectValueSets[@id = $valueset/@id][@effectiveDate = $valueset/@effectiveDate] |
                $objectValueSets[@valueSet = $valueset/@id][not(@flexibility castable as xs:dateTime)][$valueset[@effectiveDate = string(max(($projectvaluesets[@id=$valueset/@id]/xs:dateTime(@effectiveDate),$referencedvaluesets[@id=$valueset/@id]/xs:dateTime(@effectiveDate))))]]
                
            let $shouldInclude  := 
                if ((string-length($id) = 0 and string-length($name) = 0)) then (true()) else 
                if ($treetype = $vs:TREETYPEMARKED) then (true()) else
                if ($refItems) then (true()) else (false())
            
            return
                if ($shouldInclude) then (
                    <valueSet uuid="{util:uuid()}">
                    {
                        $valueset/@*,
                        if ($valueset[@displayName])    then () else (attribute displayName {$valueset/@name}),
                        if ($valueset[@url])            then () else (attribute url {$url[1]}),
                        if ($valueset[@ident])          then () else (attribute ident {$ident[1]}),
                        if ($valueset[@referencedFrom]) then () else (attribute referencedFrom {distinct-values(tokenize(normalize-space(string-join($refFrom, ' ')),'\s'))}),
                        if ($treetype = $vs:TREETYPELIMITED) then () else if ($refItems) then (
                            if ($objects[self::valueSet]) then
                                for $object in $objects[empty(@effectiveDate)] | $objects[@effectiveDate = $valueset/@effectiveDate]
                                return
                                <ref type="{$objectType}">{$object/(@id | @ref | @name | @displayName | @effectiveDate)}</ref>
                            else (
                                for $object in $objects
                                return
                                <ref type="{$objectType}">{$object/(@id | @ref | @name | @displayName | @effectiveDate)}</ref>
                            )
                        ) else ()
                    }
                    </valueSet>
                ) else ()
        }
        </repository>
    
(:now prune projectvaluesets from any valueSet[@url] as those are 'moved' to the repository section:)
let $projectvaluesets := 
    for $valuesets in $projectvaluesets[empty(@url)]
    let $ident := $valuesets/ancestor::decor/project/@prefix
    group by $ident
    return
        element {if ($valuesets[ancestor::decor[project[@prefix = $prefix]]]) then 'project' else 'repository'}
        {
            attribute ident {$ident}
            ,
            for $valueset in $valuesets
            let $refItems       :=
                $objectValueSets[@valueSet = $valueset/@ref] | 
                $objectValueSets[@ref = $valueset/@ref] | 
                $objectValueSets[@valueSet = $valueset/@id][@flexibility = $valueset/@effectiveDate] | 
                $objectValueSets[@id = $valueset/@id][@effectiveDate = $valueset/@effectiveDate] |
                $objectValueSets[@valueSet = $valueset/@id][not(@flexibility castable as xs:dateTime)][$valueset[@effectiveDate = string(max(($projectvaluesets[@id=$valueset/@id]/xs:dateTime(@effectiveDate),$referencedvaluesets[@id=$valueset/@id]/xs:dateTime(@effectiveDate))))]]
                
            let $shouldInclude  := 
                if ((string-length($id) = 0 and string-length($name) = 0)) then (true()) else 
                if ($treetype = $vs:TREETYPEMARKED) then (true()) else
                if ($refItems) then (true()) else (false())
            
            return
                if ($shouldInclude) then (
                    <valueSet uuid="{util:uuid()}">
                    {
                        $valueset/(@* except (@url|@ident|@referencedFrom)),
                        if ($valueset/@displayName)     then () else (attribute displayName {$valueset/@name}),
                        if ($treetype = $vs:TREETYPELIMITED) then () else if ($refItems) then (
                            if ($objects[self::valueSet]) then
                                for $object in $objects[empty(@effectiveDate)] | $objects[@effectiveDate = $valueset/@effectiveDate]
                                return
                                <ref type="{$objectType}">{$object/(@id | @ref | @name | @displayName | @effectiveDate)}</ref>
                            else (
                                for $object in $objects
                                return
                                <ref type="{$objectType}">{$object/(@id | @ref | @name | @displayName | @effectiveDate)}</ref>
                            )
                        ) else ()
                    }
                    </valueSet>
                ) else ()
        }

return 
    <return treetype="{$treetype}">
    {
        if ($classified) then (
            for $valueSetsById in $projectvaluesets//valueSet | $repositoryValueSetLists//valueSet
            let $vsid       := $valueSetsById/@id | $valueSetsById/@ref
            group by $vsid
            order by ($valueSetsById[@effectiveDate = max($valueSetsById/xs:dateTime(@effectiveDate))], $valueSetsById)[1]/lower-case(@displayName)
            return
            <valueSet uuid="{util:uuid()}" id="{$vsid[1]}">
            {
                for $valueSet in $valueSetsById
                order by $valueSet/@effectiveDate descending
                return
                    $valueSet
            }
            </valueSet>
        ) else (
            $projectvaluesets[*] | $repositoryValueSetLists[*]
        )
    }
    </return>
};

(:~
 :   Get contents of a valueSet and return like this:
 :   <valueSet id|ref="oid" ...>
 :       <completeCodeSystem .../>         -- if applicable
 :       <conceptList>
 :           <concept .../>
 :           <include ...>                 -- handled by vs:getValueSetInclude() 
 :               <valueSet ...>
 :                   ...
 :               </valueSet>
 :           </include>
 :           <exception .../>
 :       </conceptList>
 :   </valueSet>
 :)
declare function vs:getRawValueSet($valueSet as element(), $includetrail as element()*, $language as xs:string?, $prefix as xs:string, $version as xs:string?, $getincludes as xs:boolean, $serialize as xs:boolean) as element()* {
<valueSet>
{
    (:$valueSet/@*[string-length()>0]:)
    $valueSet/@*
}
{
    if ($serialize) then 
        for $desc in $valueSet/desc
        return
            art:serializeNode($desc)
    else ($valueSet/desc)
}
{
    $valueSet/publishingAuthority
    ,
    if ($valueSet[@id]) then 
        if ($valueSet[publishingAuthority]) then () else (
            let $decor      := art:getDecorByPrefix($prefix, $version, $language)
            let $copyright  := ($decor/project/copyright[empty(@type)] | $decor/project/copyright[@type = 'author'])[1]
            return
            if ($copyright) then
                <publishingAuthority name="{$copyright/@by}" inherited="project">
                {
                    (: Currently there is no @id, but if it ever would be there ...:)
                    $copyright/@id,
                    $copyright/addrLine
                }
                </publishingAuthority>
            else ()
        )
    else ()
    ,
    $valueSet/endorsingAuthority
    ,
    $valueSet/purpose
    ,
    let $copyrights     :=    
        if ($valueSet[@id]) then
            vs:handleValueSetCopyrights($valueSet/copyright, distinct-values($valueSet//@codeSystem))
        else (
            $valueSet/copyright
        )
    return
        if ($serialize) then 
            for $node in $copyrights
            return
                art:serializeNode($node)
        else ($copyrights)
}
{
    for $revisionHistory in $valueSet/revisionHistory
    return
        <revisionHistory>
        {
            $revisionHistory/@*
            ,
            if ($serialize) then 
                for $desc in $revisionHistory/desc
                return
                    art:serializeNode($desc)
            else ($revisionHistory/desc)
        }
        </revisionHistory>
}
{
    if ($valueSet/completeCodeSystem | $valueSet/conceptList[*]) then (
        <conceptList>
        {
            for $codeSystem in $valueSet/completeCodeSystem
            return 
                <include>
                {
                    $codeSystem/@*, 
                    $codeSystem/filter
                }
                </include>
            ,
            for $node in $valueSet/conceptList/concept | 
                         $valueSet/conceptList/include | 
                         $valueSet/conceptList/exclude | 
                         $valueSet/conceptList/exception
            return (
                if ($node[self::include[@ref]]) then (
                    if ($getincludes) then vs:getValueSetInclude($node, $includetrail, $language, $prefix, $version, $serialize) else $node
                ) else 
                if ($node[self::include[@op]] | $node[self::exclude[@op]]) then (
                    $node
                ) 
                else (
                    element {$node/name()} 
                    {
                        $node/@*
                        ,
                        if ($serialize) then (
                            for $subnode in ($node/designation, $node/desc)
                            return
                                art:serializeNode($subnode)
                            ,
                            $node/filter
                        )
                        else (
                            $node/designation,
                            $node/desc,
                            $node/filter
                        )
                    }
                )
            )
        }
        </conceptList>
    ) else ()
}
</valueSet>
};

(:~
 :   Get contents of an include and return like this:
 :   <duplicate ref="oid" flexibility="flex" exception="exception"/>  -- duplicate include found
 :   
 :   or
 :   
 :   <include ref="oid" flexibility="flex" exception="exception">     -- note that the include may be omitted if it's not found
 :       <valueSet ...>                                               -- handled by vs:getValueSet()
 :           ...
 :       </valueSet>
 :   </include>
 :)
declare %private function vs:getValueSetInclude ($include as element(), $includetrail as element()*, $language as xs:string, $prefix as xs:string, $version as xs:string?, $serialize as xs:boolean) as element()* {
let $valuesetId              := $include/@ref
let $valuesetFlex            := $include/@flexibility

(: local server and external building block repository handling :)
let $internalrepositories    :=
    if (empty($version)) then
        $get:colDecorData/decor[project/@prefix=$prefix][1]
    else (
        $get:colDecorVersion/decor[@versionDate=$version][project/@prefix=$prefix][1]
    )
let $externalrepositories    := 
    if ($internalrepositories/terminology/valueSet[@id=$valuesetId]) then () else (
        $internalrepositories/project/buildingBlockRepository[empty(@format)] |
        $internalrepositories/project/buildingBlockRepository[@format='decor']
    )
(:let $requestHeaders          := <headers><header name="Content-Type" value="text/xml"/></headers>:)
let $repositoryValueSetLists :=
    <repositoryValueSetLists>
    {
        vs:getValueSetByIdFromBBR($prefix, $valuesetId, $prefix, $externalrepositories, (), (), ())[valueSet[@id]]
    }
    {
        for $repository in $internalrepositories
        return
            <repositoryValueSetList ident="{$repository/project/@prefix}">
            {
                $repository/terminology/valueSet[@id=$valuesetId]
            }
            </repositoryValueSetList>
    }
    </repositoryValueSetLists>

let $effectiveDate := if ($valuesetFlex castable as xs:dateTime) then $valuesetFlex else string(max($repositoryValueSetLists//valueSet[@id=$valuesetId]/xs:dateTime(@effectiveDate))[1])
let $valueSet      := ($repositoryValueSetLists//valueSet[@id=$valuesetId][@effectiveDate=$effectiveDate])[1]

return
    if ($includetrail[@ref = $include/@ref][@flexibility = $effectiveDate]) then (
        <duplicate>{$include/@*}</duplicate>
    )
    else (
        let $includetrail := $includetrail | <include ref="{$include/@ref}" flexibility="{$effectiveDate}"/>
        return
        <include>
        {
            $include/@ref,
            $include/@flexibility,
            $include/@exception
        }
        {
            if (exists($valueSet)) then 
                vs:getRawValueSet($valueSet, $includetrail, $language, $prefix, $version, true(), $serialize)
            else ()
        }
        </include>
    )
};

(:~
 :   Look for valueSet[@id] and recurse if valueSet[@ref] is returned based on the buildingBlockRepositories in the project that returned it.
 :   If we get a valueSet[@ref] from an external repository (through RetrieveValueSet), then tough luck, nothing can help us. The returned 
 :   data is a nested repositoryValueSetList element allowing you to see the full trail should you need that. Includes duplicate protection 
 :   so every project is checked once only.
 :   Example below reads:
 :      - We checked hwg- and found BBR hg-
 :      - We checked hg- and found BBR nictz2bbr-
 :      - We checked nictiz2bbr- and found the requested valueSet
 :   <repositoryValueSetList url="http://decor.nictiz.nl/decor/services/" ident="hg-" referencedFrom="hwg-">
 :      <repositoryValueSetList url="http://decor.nictiz.nl/decor/services/" ident="nictiz2bbr-" referencedFrom="hg-">
 :          <valueSet id="2.16.840.1.113883.2.4.3.11.60.1.11.2" name="RoleCodeNLZorgverlenertypen" displayName="RoleCodeNL - zorgverlenertype (personen)" effectiveDate="2011-10-01T00:00:00" statusCode="final">
 :          ...
 :          </valueSet>
 :      </repositoryValueSetList>
 :   </repositoryValueSetList>
 :
 :)
declare %private function vs:getValueSetByIdFromBBR($basePrefix as xs:string, $id as xs:string, $prefix as xs:string, $externalrepositorylist as element()*, $bbrmap as map(*)?, $localbyid as element(valueSet)*, $localbyref as element(valueSet)*) as element()* {

let $newmap                 := 
    map:merge(
        for $bbr in $externalrepositorylist return map:entry(concat($bbr/@ident, $bbr/@url), '')
    )

let $valueSetsById          := if ($localbyid) then $localbyid else $get:colDecorData//valueSet[@id = $id]
let $valueSetsByRef         := if ($localbyref) then $localbyref else $get:colDecorData//valueSet[@ref = $id]

let $return                 := 
    for $repository in $externalrepositorylist
    let $repourl                := $repository/@url
    let $repoident              := $repository/@ident
    let $hasBeenProcessedBefore := 
        if (empty($bbrmap)) then false() else (
            map:contains($bbrmap, concat($repoident, $repourl)) or map:contains($bbrmap, concat($basePrefix, $vs:strDecorServicesURL))
        )
    (:let $newmap                 :=
        if (empty($bbrmap)) then $newmap else map:merge(($bbrmap, $newmap)):)
    (: map:merge does not exist in eXist 2.2 :)
    let $newmap                 :=
        if (empty($bbrmap)) then $newmap else map:merge((
            for $k in map:keys($bbrmap) return map:entry($k, ''),
            for $k in map:keys($newmap) return if (map:contains($bbrmap, $k)) then () else map:entry($k, '')
        ))
    return
        if ($hasBeenProcessedBefore) then () else (
            <repositoryValueSetList url="{$repourl}" ident="{$repoident}" referencedFrom="{$prefix}">
            {
                (: if this buildingBlockRepository resolves to our own server, then get it directly from the db. :)
                if ($repository[@url = $vs:strDecorServicesURL]) then (
                    let $valueSets          := $valueSetsById[ancestor::decor/project[@prefix = $repoident]]
                    let $valueSetsRef       := $valueSetsByRef[ancestor::decor/project[@prefix = $repoident]]
                    
                    let $project            := ($valueSets/ancestor::decor | $valueSetsRef/ancestor::decor)[1]
                    let $bbrs               :=
                        $project//buildingBlockRepository[empty(@format)] |
                        $project//buildingBlockRepository[@format='decor']
                    return 
                    if ($project) then (
                        attribute projecttype {'local'},
                        $valueSets,
                        if (not($valueSets) or $valueSetsRef) then (
                            vs:getValueSetByIdFromBBR($basePrefix, $id, $repoident, $bbrs, $newmap, $valueSetsById, $valueSetsByRef)
                        ) else ()
                    ) else ()
                )
                (: check cache first and do a server call as last resort. :)
                else (
                    let $cachedProject          := $get:colDecorCache//cacheme[@bbrurl = $repourl][@bbrident = $repoident]
                    let $cachedValuesets        := $cachedProject//valueSet[@id = $id] | $cachedProject//valueSet[@ref = $id]
                    let $bbrs               :=
                        $cachedProject//buildingBlockRepository[empty(@format)] |
                        $cachedProject//buildingBlockRepository[@format='decor']
                    return
                    if ($cachedProject) then (
                        attribute projecttype {'cached'},
                        $cachedValuesets[@id],
                        if (not($cachedValuesets) or $cachedValuesets[@ref]) then (
                            vs:getValueSetByIdFromBBR($basePrefix, $id, $repoident, $bbrs, $newmap, $valueSetsById, $valueSetsByRef)
                        ) else ()
                    )
                    (: http call as last resort. :)
                    else (
                        try {
                            let $service-uri    := concat($repourl,'RetrieveValueSet?format=xml&amp;prefix=',$repoident, '&amp;id=', $id)
                            let $requestHeaders := 
                                <http:request method="GET" href="{$service-uri}">
                                    <http:header name="Content-Type" value="text/xml"/>
                                    <http:header name="Cache-Control" value="no-cache"/>
                                    <http:header name="Max-Forwards" value="1"/>
                                </http:request>
                            let $server-response := http:send-request($requestHeaders)
                            let $server-check    :=
                                if ($server-response[1]/@status='200') then () else (
                                    error(QName('http://art-decor.org/ns/error', 'RetrieveError'), concat('Server returned HTTP status: ',$server-response[1]/@status, ' for URI ''',$service-uri,''' with body ''',string-join($server-response[2], ' '),''''))
                                )
                            let $valuesets      := $server-response//valueSet[@id]
                            return (
                                attribute projecttype {'remote'},
                                $valuesets
                            )
                        }
                        catch * {()}
                    )
                )
            }
            </repositoryValueSetList>
        )

return 
    $return
};

(:~
 :   Look for valueSet[@id] and recurse if valueSet[@ref] is returned based on the buildingBlockRepositories in the project that returned it.
 :   If we get a valueSet[@ref] from an external repository (through RetrieveValueSet), then tough luck, nothing can help us. The returned 
 :   data is a nested repositoryValueSetList element allowing you to see the full trail should you need that. Includes duplicate protection 
 :   so every project is checked once only.
 :   Example below reads:
 :      - We checked hwg- and found BBR hg-
 :      - We checked hg- and found BBR nictz2bbr-
 :      - We checked nictiz2bbr- and found the requested valueSet
 :   <repositoryValueSetList url="http://decor.nictiz.nl/decor/services/" ident="hg-" referencedFrom="hwg-">
 :      <repositoryValueSetList url="http://decor.nictiz.nl/decor/services/" ident="nictiz2bbr-" referencedFrom="hg-">
 :          <valueSet id="2.16.840.1.113883.2.4.3.11.60.1.11.2" name="RoleCodeNLZorgverlenertypen" displayName="RoleCodeNL - zorgverlenertype (personen)" effectiveDate="2011-10-01T00:00:00" statusCode="final">
 :          ...
 :          </valueSet>
 :      </repositoryValueSetList>
 :   </repositoryValueSetList>
 :
 :)
declare %private function vs:getValueSetByNameFromBBR($basePrefix as xs:string, $name as xs:string, $prefix as xs:string, $externalrepositorylist as element()*, $bbrmap as map(*)?, $localbyname as element()*) as element()* {

let $newmap                 := 
    map:merge(
        for $bbr in $externalrepositorylist return map:entry(concat($bbr/@ident, $bbr/@url), '')
    )

let $valueSetsByName          := if ($localbyname) then $localbyname else $get:colDecorData//valueSet[@name = $name]

let $return                 := 
    for $repository in $externalrepositorylist
    let $repourl                := $repository/@url
    let $repoident              := $repository/@ident
    let $hasBeenProcessedBefore := 
        if (empty($bbrmap)) then false() else (
            map:contains($bbrmap, concat($repoident, $repourl)) or map:contains($bbrmap, concat($basePrefix, $vs:strDecorServicesURL))
        )
    (:let $newmap                 :=
        if (empty($bbrmap)) then $newmap else map:merge(($bbrmap, $newmap)):)
    (: map:merge does not exist in eXist 2.2 :)
    let $newmap                 :=
        if (empty($bbrmap)) then $newmap else map:merge((
            for $k in map:keys($bbrmap) return map:entry($k, ''),
            for $k in map:keys($newmap) return if (map:contains($bbrmap, $k)) then () else map:entry($k, '')
        ))
    return
        if ($hasBeenProcessedBefore) then () else (
            <repositoryValueSetList url="{$repourl}" ident="{$repoident}" referencedFrom="{$prefix}">
            {
                (: if this buildingBlockRepository resolves to our own server, then get it directly from the db. :)
                if ($repository[@url = $vs:strDecorServicesURL]) then (
                    let $valueSets          := $valueSetsByName[ancestor::decor/project[@prefix = $repoident]]
                    
                    let $project            := $valueSets/ancestor::decor
                    let $bbrs               :=
                        $project//buildingBlockRepository[empty(@format)] |
                        $project//buildingBlockRepository[@format='decor']
                    return 
                    if ($project) then (
                        attribute projecttype {'local'},
                        $valueSets[@id]
                        ,
                        for $vsref in $valueSets[@ref]
                        let $id := $vsref/@ref
                        group by $id
                        return 
                            vs:getValueSetByIdFromBBR($basePrefix, $id, $repoident, $bbrs, $newmap, $valueSetsByName[@id], $valueSetsByName[@ref])
                    ) else ()
                )
                (: check cache first and do a server call as last resort. :)
                else (
                    let $cachedProject          := $get:colDecorCache//cacheme[@bbrurl = $repourl][@bbrident = $repoident]
                    let $cachedValuesets        := $cachedProject//valueSet[@name = $name]
                    let $bbrs               :=
                        $cachedProject//buildingBlockRepository[empty(@format)] |
                        $cachedProject//buildingBlockRepository[@format='decor']
                    return
                    if ($cachedProject) then (
                        attribute projecttype {'cached'},
                        $cachedValuesets[@id],
                        if (empty($cachedValuesets)) then
                            vs:getValueSetByNameFromBBR($basePrefix, $name, $repoident, $bbrs, $newmap, $valueSetsByName)
                        else (
                            for $vsref in $cachedValuesets[@ref]
                            let $id := $vsref/@ref
                            group by $id
                            return 
                                vs:getValueSetByIdFromBBR($basePrefix, $id, $repoident, $bbrs, $newmap, $valueSetsByName[@id], $valueSetsByName[@ref])
                        )
                    )
                    (: http call as last resort. :)
                    else (
                        try {
                            let $service-uri    := concat($repourl,'RetrieveValueSet?format=xml&amp;prefix=',$repoident, '&amp;name=', $name)
                            let $requestHeaders := 
                                <http:request method="GET" href="{$service-uri}">
                                    <http:header name="Content-Type" value="text/xml"/>
                                    <http:header name="Cache-Control" value="no-cache"/>
                                    <http:header name="Max-Forwards" value="1"/>
                                </http:request>
                            let $server-response := http:send-request($requestHeaders)
                            let $server-check    :=
                                if ($server-response[1]/@status='200') then () else (
                                    error(QName('http://art-decor.org/ns/error', 'RetrieveError'), concat('Server returned HTTP status: ',$server-response[1]/@status, ' for URI ''',$service-uri,''' with body ''',string-join($server-response[2], ' '),''''))
                                )
                            let $valuesets      := $server-response//valueSet[@id]
                            return (
                                attribute projecttype {'remote'},
                                $valuesets
                            )
                        }
                        catch * {()}
                    )
                )
            }
            </repositoryValueSetList>
        )

return 
    $return
};

(:~
:   Returns lucene config xml for a sequence of strings. The search will find yield results that match all terms+trailing wildcard in the sequence
:   Example output:
:   <query>
:       <bool>
:           <wildcard occur="must">term1*</wildcard>
:           <wildcard occur="must">term2*</wildcard>
:       </bool>
:   </query>
:
:   @param $searchTerms required sequence of terms to look for
:   @return lucene config
:   @since 2014-06-06
:)
declare %private function vs:getSimpleLuceneQuery($searchTerms as xs:string+) as element() {
    <query>
        <bool>{
            for $term in $searchTerms
            return
                <wildcard occur="must">{concat($term,'*')}</wildcard>
        }</bool>
    </query>
};

(:~
:   Returns lucene options xml that instruct filter-rewrite=yes
:)
declare %private function vs:getSimpleLuceneOptions() as element() {
    <options>
        <filter-rewrite>yes</filter-rewrite>
    </options>
};

(:
    Returns appropriate copyright texts based on $usedCodeSystems
    <copyright language="en-US">Licensing note: .......</copyright>
:)
declare %private function vs:handleValueSetCopyrights($copyrightsource as element()*, $usedCodeSystems as item()*) as element()* {

    let $extracopyrights :=
        <extracopyright oid="2.16.840.1.113883.6.96" urn="http://snomed.info/sct">This artefact includes content from SNOMED Clinical Terms® (SNOMED CT®) which is copyright of the International Health Terminology Standards Development Organisation (IHTSDO). Implementers of these artefacts must have the appropriate SNOMED CT Affiliate license - for more information contact http://www.snomed.org/snomed-ct/getsnomed-ct or info@snomed.org.</extracopyright>
        
    let $extra :=
        for $t in $extracopyrights
        return
            (: if codesystem is used :) 
            if ($t/@oid = $usedCodeSystems)
            then (
                (: if text is not yet in the copyright texts :)
                if ($copyrightsource[@language='en-US'][empty(@inherited)]) then 
                    <copyright language="en-US">{$copyrightsource[@language='en-US']/(node() except *:div[@data-source='inherited'])}<div data-source="inherited" style="border-top: 1px solid black">{$t/text()}</div></copyright>
                else (
                    <copyright language="en-US" inherited="true">{$t/text()}</copyright>
                )
            )
            else ()
    
    return
        (
            if (empty($extra))
            then $copyrightsource
            else ($copyrightsource[not(@language='en-US')], $extra)
        )
    
};
