xquery version "3.0";
(:
    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 cs                 = "http://art-decor.org/ns/decor/codesystem";

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

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

declare variable $cs: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 $cs:boolSerialize as xs:boolean    := true();:)

(:~
:   Return zero or more codesystems 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 codeSystem/@id and codeSystem/@ref. The latter is resolved. Note that duplicate codeSystem matches may be 
:   returned. Example output:
:   &lt;return>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/repository>
:   &lt;/return>
:   
:   @param $id           - required. Identifier of the codesystem 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 code systems 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 cs:getCodeSystemById ($id as xs:string, $flexibility as xs:string?) as element(return) {
    let $codeSystems    := $get:colDecorData//codeSystem[@id = $id] | $get:colDecorCache//codeSystem[@id = $id]
    let $codeSystems    :=
        if ($flexibility castable as xs:dateTime) then (
            $codeSystems[@effectiveDate = $flexibility]
        ) else (
            $codeSystems[@effectiveDate = max($codeSystems/xs:dateTime(@effectiveDate))][1]
        )
    return
    <return>
    {
        for $repository in $codeSystems
        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 $codeSystem in $repository
                let $ideff  := concat($codeSystem/@id, $codeSystem/@ref, $codeSystem/@effectiveDate)
                group by $ideff
                order by $codeSystem[1]/@effectiveDate descending
                return 
                    cs:getRawCodeSystem($codeSystem[1], (), $prefix, (), $repository[1]/ancestor::decor/project/@defaultLanguage, false())
            }
            </project>
    }
    </return>
    (:<return>
    {
        for $prefix in $get:colDecorData//decor[not(string(@private)='true')]/project/@prefix
        return
            cs:getCodeSystemById ($id, $flexibility, $prefix)/*
    }
    </return>:)
};

(:~
:   See cs:getCodeSystemById ($id as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?) for documentation
:   
:   @param $id           - required. Identifier of the codesystem 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 code systems 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 cs:getCodeSystemById ($id as xs:string, $flexibility as xs:string?, $prefix as xs:string) as element()* {
    cs:getCodeSystemById($id, $flexibility, $prefix, ())
};

(:~
:   Return zero or more codesystems 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 codeSystem/@id and codeSystem/@ref. The latter is resolved. Note that duplicate codeSystem matches may be 
:   returned. Example output for codeSystem/@ref:<br/>
:   &lt;return>
:       &lt;project ident="epsos-">
:           &lt;codeSystem ref="2.16.840.1.113883.1.11.10282" name="ParticipationSignature" displayName="ParticipationSignature">
:               ...
:           &lt;/codeSystem>
:       &lt;/project>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-" referencedFrom="epsos-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/repository>
:   &lt;/return>
:   <p>Example output for codeSystem/@id:</p>
:   &lt;return>
:       &lt;project ident="epsos-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/repository>
:   &lt;/return>
:   
:   @param $id           - required. Identifier of the codesystem 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 codesystem will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero code systems 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 cs:getCodeSystemById ($id as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?) 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 $internalcodesystems        := $internalrepositories//codeSystem[@id = $id] | $internalrepositories//codeSystem[@ref = $id]:)
let $internalcodesystems        :=
    if (empty($version)) then
        $get:colDecorData//codeSystem[@id = $id][ancestor::decor/project/@prefix = $prefix] | $get:colDecorData//codeSystem[@ref = $id][ancestor::decor/project/@prefix = $prefix]
    else (
        $get:colDecorVersion//decor[@versionDate = $version][project/@prefix = $prefix][1]//codeSystem[@id = $id] | $get:colDecorVersion//decor[@versionDate = $version][project/@prefix = $prefix][1]//codeSystem[@ref = $id]
    )

let $repositoryCodeSystemLists :=
    <repositoryCodeSystemLists>
    {
        (:  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="{$cs:strDecorServicesURL}" ident="{$prefix}"/>:)
            return
                cs:getCodeSystemByIdFromBBR($prefix, $id, $prefix, $buildingBlockRepositories, ())/descendant-or-self::*[codeSystem[@id]]
        else ()
    }
    {
        (:from the requested project, return codeSystem/(@id and @ref):)
        (: when retrieving code systems from a compiled project, the @url/@ident they came from are on the codeSystem element
           reinstate that info on the repositoryCodeSystemList element so downstream logic works as if it really came from 
           the repository again.
        :)
        for $repository in $internalrepositories
        for $codesystems in $internalcodesystems
        let $source := concat($codesystems/@url,$codesystems/@ident)
        group by $source
        return
            if (string-length($codesystems/@url)=0) then
                <repositoryCodeSystemList ident="{$repository/project/@prefix}">
                {
                    $codesystems
                }
                </repositoryCodeSystemList>
            else (
                <repositoryCodeSystemList url="{$codesystems[1]/@url}" ident="{$codesystems[1]/@ident}" referencedFrom="{$prefix}">
                {
                    for $codesystem in $codesystems
                    return
                        <codeSystem>{$codesystem/(@* except (@url|@ident|@referencedFrom)), $codesystem/node()}</codeSystem>
                }
                </repositoryCodeSystemList>
            )
    }
    </repositoryCodeSystemLists>

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

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

(:~
:   Return zero or more codesystems as-is. Name based references cannot return codeSystem/@ref, only codeSystem/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate codeSystem matches may be returned. Example output:
:   &lt;return>
:       &lt;project ident="ad2bbr-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/project>
:   &lt;/return>
:   
:   @param $name             - required. Name of the codesystem to retrieve (codeSystem/@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 code systems 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 cs:getCodeSystemByName ($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean) as element() {
    let $codeSystems  :=
        if ($useRegexMatching) then 
            $get:colDecorData//codeSystem[matches(@name, $name)] | $get:colDecorCache//codeSystem[matches(@name, $name)]
        else (
            $get:colDecorData//codeSystem[@name = $name] | $get:colDecorCache//codeSystem[@name = $name]
        )
    let $codeSystems  :=
        if ($flexibility castable as xs:dateTime) then (
            $codeSystems[@effectiveDate = $flexibility]
        )
        else (
            for $codeSystem in $codeSystems[@id]
            let $id := $codeSystem/@id
            group by $id
            return (
                let $max := max($codeSystem/xs:dateTime(@effectiveDate))
                return
                $codeSystem[@effectiveDate = $max][1]
            )
        )
    return
    <return>
    {
        for $repository in $codeSystems
        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 $codeSystem in $repository
                return cs:getRawCodeSystem($codeSystem, (), $prefix, (), $repository[1]/ancestor::decor/project/@defaultLanguage, false())
            }
            </project>
    }
    </return>
    (:<return>
    {
        for $prefix in $get:colDecorData//decor[not(string(@private)='true')]/project/@prefix
        return
            cs:getCodeSystemByName($name, $flexibility, $prefix, $useRegexMatching)/*
    }
    </return>:)
};

(:~
:   See cs:getCodeSystemByName ($name as xs:string, $flexibility as xs:string?, $prefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?) for documentation
:   
:   @param $name             - required. Name of the codesystem to retrieve (codeSystem/@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 code systems 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 cs:getCodeSystemByName ($name as xs:string, $flexibility as xs:string?, $prefix as xs:string, $useRegexMatching as xs:boolean) as element()* {
    cs:getExpandedCodeSystemByName($name, $flexibility, $useRegexMatching, $prefix, (), (), true())
};

(:~
:   Return zero or more codesystems as-is. Name based references cannot return codeSystem/@ref, only codeSystem/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate codeSystem matches may be returned. Example output:
:   &lt;return>
:       &lt;project ident="ad2bbr-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/project>
:   &lt;/return>
:   
:   @param $name             - required. Name of the codesystem to retrieve (codeSystem/@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 codesystem will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero code systems 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 cs:getCodeSystemByName ($name as xs:string, $flexibility as xs:string?, $prefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?) 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    := cs:getSimpleLuceneQuery(tokenize(lower-case($name),'\s'))
        let $luceneOptions  := cs:getSimpleLuceneOptions()
        return
            $decor/terminology/codeSystem[ft:query(@name, $luceneQuery, $luceneOptions)]
    else
        $decor/terminology/codeSystem[@name = $name]

let $repositoryCodeSystemLists    :=
    <repositoryCodeSystemLists>
    {
        (:  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 codeSystem/@ref matching our id
        :)
        if (empty($version) and not($internalrepositories[@id])) 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="{$cs:strDecorServicesURL}" ident="{$prefix}"/>
            return
                cs:getCodeSystemByNameFromBBR($prefix, $name, $prefix, $buildingBlockRepositories, $bbrList)[codeSystem[@id]]
        else ()
    }
    </repositoryCodeSystemLists>

let $codeSystemsById := 
    for $id in distinct-values($internalrepositories/@id | $internalrepositories/@ref | $repositoryCodeSystemLists/repositoryCodeSystemList/codeSystem/@id)
    return
        cs:getCodeSystemById($id,$flexibility,$prefix,$version)/*

return
    <return>
    {
        if (empty($flexibility)) then
            for $repositoryCodeSystemList in $codeSystemsById[codeSystem]
            let $elmname := $repositoryCodeSystemList/name()
            return
                element {$elmname}
                {
                    $repositoryCodeSystemList/@*,
                    for $codeSystem in $repositoryCodeSystemList/codeSystem
                    order by xs:dateTime($codeSystem/@effectiveDate) descending
                    return   $codeSystem
                }
        else if (matches($flexibility,'^\d{4}')) then
            for $repositoryCodeSystemList in $codeSystemsById[codeSystem[@ref or @effectiveDate=$flexibility]]
            let $elmname := $repositoryCodeSystemList/name()
            return
                element {$elmname}
                {
                    $repositoryCodeSystemList/@*,
                    $repositoryCodeSystemList/codeSystem[@ref or @effectiveDate=$flexibility]
                }
        else
            for $repositoryCodeSystemList in $codeSystemsById[codeSystem[@ref or @effectiveDate=string((max($codeSystemsById//codeSystem/xs:dateTime(@effectiveDate)))[1])]]
            let $elmname := $repositoryCodeSystemList/name()
            return
                element {$elmname}
                {
                    $repositoryCodeSystemList/@*,
                    $repositoryCodeSystemList/codeSystem[@ref or @effectiveDate=string((max($codeSystemsById//codeSystem/xs:dateTime(@effectiveDate)))[1])]
                }
    }
    </return>
};

(:~
:   See cs:getCodeSystemByRef ($idOrName as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?) for documentation
:   
:   @param $idOrName     - required. Identifier (@id) or name (@name) of the codesystem 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 code systems 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 cs:getCodeSystemByRef ($idOrName as xs:string, $flexibility as xs:string?, $prefix as xs:string) as element()* {
    cs:getCodeSystemByRef($idOrName, $flexibility, $prefix, ())
};

(:~
:   Returns zero or more codesystems 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 cs:getCodeSystemById() and cs:getCodeSystemByName() 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 codesystem 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 codesystem will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero code systems 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 cs:getCodeSystemByRef ($idOrName as xs:string, $flexibility as xs:string?, $prefix as xs:string, $version as xs:string?) 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
        cs:getCodeSystemById($idOrName,$flexibility,$prefix,$version)
    else
        cs:getCodeSystemByName($idOrName,$flexibility,$prefix,false(),$version)
};

(:~
:   Return zero or more expanded codesystems 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 codeSystem/@id and codeSystem/@ref. The latter is resolved. Note that duplicate codeSystem matches may be 
:   returned. Example output:
:   &lt;return>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li> Codesystems get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>codeSystem/@ref is treated as if it were the referenced codesystem</li>
:   <li>If parameter prefix is not used then only codeSystem/@id is matched. If that does not give results, then 
:     codeSystem/@ref is matched, potentially expanding into a buildingBlockRepository</li>
:                                
:   @param $id           - required. Identifier of the codesystem 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 code systems 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 cs:getExpandedCodeSystemById ($id as xs:string, $flexibility as xs:string?, $serialize as xs:boolean) as element() {
    let $codeSystems  :=
        $get:colDecorData//codeSystem[@id = $id] | $get:colDecorCache//codeSystem[@id = $id]
    let $codeSystems  :=
        if ($flexibility castable as xs:dateTime) then (
            $codeSystems[@effectiveDate = $flexibility]
        ) else (
            $codeSystems[@effectiveDate = max($codeSystems/xs:dateTime(@effectiveDate))][1]
        )
    return
    <return>
    {
        for $repository in $codeSystems
        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 $codeSystem in $repository[@id]
                return
                    cs:getExpandedCodeSystem($codeSystem, $prefix, (), (), $serialize)
            }
            </project>
    }
    </return>
    (:<result>
    {
        for $prefix in $get:colDecorData//decor[not(string(@private)='true')]/project/@prefix
        return
            cs:getExpandedCodeSystemById($id, $flexibility, $prefix)/*
    }
    </result>:)
};

(:~
:   Return zero or more expanded codesystems 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 codeSystem/@id and codeSystem/@ref. The latter is resolved. Note that duplicate codeSystem matches may be 
:   returned. Example output:
:   &lt;return>
:       &lt;repository url="http://art-decor.org/decor/services/" ident="ad2bbr-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li> Codesystems get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>codeSystem/@ref is treated as if it were the referenced codesystem</li>
:   <li>If parameter prefix is not used then only codeSystem/@id is matched. If that does not give results, then 
:     codeSystem/@ref is matched, potentially expanding into a buildingBlockRepository</li>
:                                
:   @param $id           - required. Identifier of the codesystem 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 codesystem will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero code systems 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 cs:getExpandedCodeSystemById ($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 $codeSystems := cs:getCodeSystemById($id, $flexibility, $prefix, $version)

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

(:~
:   Return zero or more expanded codesystems. Name based references cannot return codeSystem/@ref, only codeSystem/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate codeSystem matches may be returned. Example output:
:   &lt;return>
:       &lt;repository ident="ad2bbr-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li>Codesystems get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>If parameter prefix is used and parameter id leads to a codeSystem/@ref, then this is treated as if it were the referenced codesystem</li>
:   <li>If parameter prefix is not used then only codeSystem/@id is matched. If that does not give results, then codeSystem/@ref is matched, 
:   potentially expanding into a buildingBlockRepository</li>
:
:   @param $name             - required. Name of the codesystem to retrieve (codeSystem/@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 codeSystem
:   @since 2013-06-14
:)
declare function cs:getExpandedCodeSystemByName ($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean, $serialize as xs:boolean) as element() {
    <result>
    {
        for $prefix in $get:colDecorData//decor[not(string(@private)='true')]/project/@prefix
        return
            cs:getExpandedCodeSystemByName($name, $flexibility, $useRegexMatching, $prefix, (), (), $serialize)/*
    }
    </result>
};

(:~
:   Return zero or more expanded codesystems. Name based references cannot return codeSystem/@ref, only codeSystem/@id. Name based references do not cross project 
:   boundaries, so no repositories beit local or remote are consulted. Note that duplicate codeSystem matches may be returned. Example output:
:   &lt;return>
:       &lt;repository ident="ad2bbr-">
:           &lt;codeSystem 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;/codeSystem>
:       &lt;/repository>
:   &lt;/return>
:   <ul><li>Codesystems get an extra attribute @fromRepository containing the originating project prefix/ident</li> 
:   <li>If parameter prefix is used and parameter id leads to a codeSystem/@ref, then this is treated as if it were the referenced codesystem</li>
:   <li>If parameter prefix is not used then only codeSystem/@id is matched. If that does not give results, then codeSystem/@ref is matched, 
:   potentially expanding into a buildingBlockRepository</li>
:
:   @param $name             - required. Name of the codesystem to retrieve (codeSystem/@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 codesystem will come explicitly from that archived project version which is expected to be a compiled version
:   @return The expanded codeSystem
:   @since 2013-06-14
:)
declare function cs:getExpandedCodeSystemByName ($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 $codeSystems := cs:getCodeSystemByName($name, $flexibility, $prefix, $useRegexMatching, $version)

return
    <result>
    {
        for $repository in $codeSystems/(project|repository)
        return
            element {name($repository)}
            {
                $repository/@*,
                $repository/codeSystem[@ref],
                for $codeSystem in $repository/codeSystem[@id]
                return
                    cs:getExpandedCodeSystem($codeSystem, $prefix, $version, $language, $serialize)
            }
    }
    </result>
};

(:~
:   Returns zero or more expanded codesystems. 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 cs:getExpandedCodeSystemById() and cs:getExpandedCodeSystemByName() 
:   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 codesystem 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 codesystem will come explicitly from that archived project version which is expected to be a compiled version
:   @return Zero code systems 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 cs:getExpandedCodeSystemByRef ($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
        cs:getExpandedCodeSystemById($idOrName, $flexibility, $prefix, $version, $language, $serialize)
    else
        cs:getExpandedCodeSystemByName($idOrName, $flexibility, false(), $prefix, $version, $language, $serialize)
};

(:~
:   Expands a codeSystem with @id. Use cs:getExpandedCodeSystemsById to resolve a codeSystem/@ref first. 
:   &lt;codeSystem all-attributes of the input&gt;
:       &lt;desc all /&gt;
:       &lt;conceptList&gt;
:           &lt;codedConcept all-attributes-and-designation-elements /&gt;
:       &lt;/conceptList&gt;
:   &lt;/codeSystem&gt;
:                                
:   @param $codeSystem     - required. The codeSystem element with content to expand
:   @param $prefix       - required. The origin of codeSystem. pfx- limits scope this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the codesystem will come explicitly from that archived project version which is expected to be a compiled version
:   @return The expanded codeSystem
:   @since 2013-06-14
:)
declare function cs:getExpandedCodeSystem($codeSystem as element(), $prefix as xs:string, $version as xs:string?, $language as xs:string?, $serialize as xs:boolean) as element() {
let $decor                  := art:getDecorByPrefix($prefix, $version, ())[1]
let $language               := if (empty($language)) then $decor/project/@defaultLanguage else $language
let $language               := if (empty($language)) then $codeSystem/../@defaultLanguage else $language
let $language               := if (empty($language)) then adserver:getServerLanguage() else $language

let $rawCodeSystem := 
    cs:getRawCodeSystem($codeSystem, <include ref="{$codeSystem/(@ref|@id)}" flexibility="{$codeSystem/@effectiveDate}"/>, $prefix, $version, $language, $serialize)
return
    <codeSystem>
    {
        $rawCodeSystem/@*
        ,
        $rawCodeSystem/desc
        ,
        if ($rawCodeSystem/publishingAuthority) then $rawCodeSystem/publishingAuthority else (
            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 ()
        )
        ,
        $rawCodeSystem/endorsingAuthority
        ,
        $rawCodeSystem/revisionHistory
        ,
        $rawCodeSystem/copyright
        ,
        $rawCodeSystem/property
    }
    {
        if ($rawCodeSystem//conceptList[*]) then (
            <conceptList>
            {
                $rawCodeSystem//conceptList/codedConcept
            }
            </conceptList>
        ) else ()
    }
    </codeSystem>
};

(:~
:   See cs:getCodeSystemList ($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 codesystem to retrieve
:   @param $name         - optional. Name of the codesystem to retrieve (codeSystem/@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 code systems 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 cs:getCodeSystemList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?) as element()? {
    cs:getCodeSystemList($id, $name, $flexibility, $prefix, ())
};

(:~
:   Returns zero or more codesystems as listed in the terminology section. This function is useful e.g. to call from a CodeSystemIndex. Parameter id, name or prefix is required.
:   &lt;return>
:       &lt;repository ident="epsos-">
:           &lt;codeSystem ref="2.16.840.1.113883.1.11.159331" name="ActStatus" displayName="ActStatus"/>
:           &lt;codeSystem 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;codeSystem 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;codeSystem 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;codeSystem 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 codeSystem to retrieve
:   @param $name         - optional. Name of the codeSystem to retrieve (codeSystem/@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 codeSystem 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 cs:getCodeSystemList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?, $version as xs:string?) as element()? {
let $decor                      := art:getDecorByPrefix($prefix, $version, ())[1]

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

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

(:~
:   Returns zero or more codesystems as listed in the terminology section of all BBR projects, or in requested project (param prefix).
:   The list MAY be trimmed down codeSystems related to params id | name | flexibility. This combination may point to a codeSystem, concept or template.
:
:   1. If id | name | flexibility points to a codeSystem from the project set, then the codeSystem list contains this codeSystem. 
:   2. If id | name | flexibility points to a dataset concept from the project set, then the codeSystem list contains any codeSystems associated with this concept.
:   3. If id | name | flexibility points to a template from the project set, then the codeSystem list contains any codeSystems associated with this template.
:
:   TODO? and any (nested) inclusions of the codeSystem
:   TODO? anticipate transaction concepts with their own binding?
:)
declare function cs:getCodeSystemList-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) as element()? {
let $decorProjects              := art:getDecorByPrefix($prefix, $version, ())[1]
    
let $objects                    :=
    if (string-length($id) = 0) then (
        $decorProjects//codeSystem[@name = $name]
    ) else (
        $decorProjects//codeSystem[@id = $id] | $decorProjects//codeSystem[@ref = $id] | $decorProjects//codeSystem[@name = $name]
    )
let $objectCodeSystems          :=
    if ($objects) then (
        for $object in $objects[self::concept]
        return art:getConceptAssociations($object)[@codeSystem]
        ,
        $objects[self::template]//vocabulary[@codeSystem]
        ,
        $objects[self::codeSystem]
    ) else ()
let $objectType                 := $objects[1]/name()
    
let $projectcodesystems         := $decorProjects//codeSystem

let $referencedcodesystems      :=
    if (empty($version)) then (
        for $ref in $projectcodesystems/@ref
        let $p      := $ref/ancestor::decor/project/@prefix
        let $pref   := concat($p, $ref)
        group by $pref
        return
            cs:getCodeSystemById($ref[1], (), $p[1])//codeSystem[@id]
    ) else ()
    
(: local server and external building block repository handling :)
let $repositoryCodeSystemLists :=
    (: when retrieving value sets from a compiled project, the @url/@ident they came from are on the codeSystem element
       reinstate that info on the repositoryCodeSystemList element so downstream logic works as if it really came from 
       the repository again.
    :)
    for $codesystems in $projectcodesystems[@url] | $referencedcodesystems
    let $url        := if ($codesystems/@url) then ($codesystems/@url) else ($codesystems/parent::*/@url)
    let $ident      := if ($codesystems/@url) then ($codesystems/@ident) else ($codesystems/parent::*/@ident)
    let $refFrom    := if ($codesystems/@referencedFrom) then ($codesystems/@referencedFrom) else ($codesystems/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 $codesystem in $codesystems
            
            let $refItems       :=
                $objectCodeSystems[@codeSystem = $codesystem/@ref] | 
                $objectCodeSystems[@ref = $codesystem/@ref] | 
                $objectCodeSystems[@codeSystem = $codesystem/@id][@flexibility = $codesystem/@effectiveDate] | 
                $objectCodeSystems[@id = $codesystem/@id][@effectiveDate = $codesystem/@effectiveDate] |
                $objectCodeSystems[@codeSystem = $codesystem/@id][not(@flexibility castable as xs:dateTime)][$codesystem[@effectiveDate = string(max(($projectcodesystems[@id=$codesystem/@id]/xs:dateTime(@effectiveDate),$referencedcodesystems[@id=$codesystem/@id]/xs:dateTime(@effectiveDate))))]]
                
            let $shouldInclude  := 
                if ((string-length($id) = 0 and string-length($name) = 0)) then (true()) else 
                if ($refItems) then (true()) else (false())
            
            return
                if ($shouldInclude) then (
                    <codeSystem uuid="{util:uuid()}">
                    {
                        $codesystem/@*,
                        if ($codesystem[@displayName])    then () else (attribute displayName {$codesystem/@name}),
                        if ($codesystem[@url])            then () else (attribute url {$url[1]}),
                        if ($codesystem[@ident])          then () else (attribute ident {$ident[1]}),
                        if ($codesystem[@referencedFrom]) then () else (attribute referencedFrom {distinct-values(tokenize($refFrom,'\s'))}),
                        if ($refItems) then (
                            if ($objects[self::codeSystem]) then
                                for $object in $objects[empty(@effectiveDate)] | $objects[@effectiveDate = $codesystem/@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 ()
                    }
                    </codeSystem>
                ) else ()
        }
        </repository>
    
(:now prune projectcodesystems from any codeSystem[@url] as those are 'moved' to the repository section:)
let $projectcodesystems := 
    for $codesystems in $projectcodesystems[empty(@url)]
    let $ident := $codesystems/ancestor::decor/project/@prefix
    group by $ident
    return
        element {if ($codesystems[ancestor::decor[project[@prefix = $prefix]]]) then 'project' else 'repository'}
        {
            attribute ident {$ident}
            ,
            for $codesystem in $codesystems
            let $refItems       :=
                $objectCodeSystems[@codeSystem = $codesystem/@ref] | 
                $objectCodeSystems[@ref = $codesystem/@ref] | 
                $objectCodeSystems[@codeSystem = $codesystem/@id][@flexibility = $codesystem/@effectiveDate] | 
                $objectCodeSystems[@id = $codesystem/@id][@effectiveDate = $codesystem/@effectiveDate] |
                $objectCodeSystems[@codeSystem = $codesystem/@id][not(@flexibility castable as xs:dateTime)][$codesystem[@effectiveDate = string(max(($projectcodesystems[@id=$codesystem/@id]/xs:dateTime(@effectiveDate),$referencedcodesystems[@id=$codesystem/@id]/xs:dateTime(@effectiveDate))))]]
                
            let $shouldInclude  := 
                if ((string-length($id) = 0 and string-length($name) = 0)) then (true()) else 
                if ($refItems) then (true()) else (false())
            
            return
                if ($shouldInclude) then (
                    <codeSystem uuid="{util:uuid()}">
                    {
                        $codesystem/(@* except (@url|@ident|@referencedFrom)),
                        if ($codesystem/@displayName)     then () else (attribute displayName {$codesystem/@name}),
                        if ($refItems) then (
                            if ($objects[self::codeSystem]) then
                                for $object in $objects[empty(@effectiveDate)] | $objects[@effectiveDate = $codesystem/@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 ()
                    }
                    </codeSystem>
                ) else ()
        }

return 
    <return>
    {
        if ($classified) then (
            for $codeSystemsById in $projectcodesystems//codeSystem | $repositoryCodeSystemLists//codeSystem
            let $vsid       := $codeSystemsById/@id | $codeSystemsById/@ref
            group by $vsid
            order by lower-case($codeSystemsById[1]/@displayName)
            return
            <codeSystem uuid="{util:uuid()}" id="{$vsid[1]}">
            {
                for $codeSystem in $codeSystemsById
                order by $codeSystem/@effectiveDate descending
                return
                    $codeSystem
            }
            </codeSystem>
        ) else (
            $projectcodesystems[*] | $repositoryCodeSystemLists[*]
        )
    }
    </return>
};

(:~
 :   Get contents of a codeSystem and return like this:
 :   <codeSystem id|ref="oid" ...>
 :       <desc/>                           -- if applicable
 :       <conceptList>
 :           <codedConcept .../>
 :       </conceptList>
 :   </codeSystem>
 :)
declare function cs:getRawCodeSystem ($codeSystem as element(), $includetrail as element()*, $prefix as xs:string, $version as xs:string?, $language as xs:string?, $serialize as xs:boolean) as element()* {
let $decor      := 
    if (empty($language) or empty($codeSystem/publishingAuthority)) then (
        if (string-length($prefix) = 0) then () else (
            let $d : = $codeSystem/ancestor::decor 
            return if ($d) then $d else art:getDecorByPrefix($prefix, $version, $language)
        )
    )
    else ()
let $language   := if (string-length($language) = 0) then $decor/project/@defaultLanguage else $language

return
<codeSystem>
{
    $codeSystem/@*,
    (: this helps to establish the default display for a given codedConcept :)
    if ($codeSystem/@language) then () else 
    if (string-length($language) = 0) then () else attribute language {$language}
}
{
    if ($serialize) then 
        for $desc in $codeSystem/desc
        return
            art:serializeNode($desc)
    else ($codeSystem/desc)
}
{
    $codeSystem/publishingAuthority
    ,
    if ($codeSystem[@id]) then 
        if ($codeSystem[publishingAuthority]) then () else (
            let $copyright  := ($decor/project/copyright[empty(@type)] | $decor/project/copyright[@type = 'author'])[1]
            return
            if ($copyright) then
                <publishingAuthority name="{$copyright/@by}">
                {
                    (: Currently there is no @id, but if it ever would be there ...:)
                    $copyright/@id,
                    $copyright/addrLine
                }
                </publishingAuthority>
            else ()
        )
    else ()
    ,
    $codeSystem/endorsingAuthority
    ,
    $codeSystem/purpose
    ,
    $codeSystem/copyright
    ,
    $codeSystem/property
}
{
    for $revisionHistory in $codeSystem/revisionHistory
    return
        <revisionHistory>
        {
            $revisionHistory/@*
            ,
            if ($serialize) then 
                for $desc in $revisionHistory/desc
                return
                    art:serializeNode($desc)
            else ($revisionHistory/desc)
        }
        </revisionHistory>
}
{
    if ($codeSystem/conceptList[*]) then (
        <conceptList>
        {
            for $node in $codeSystem/conceptList/codedConcept
            return
                <codedConcept>
                {
                    $node/@*
                    ,
                    if ($serialize) then 
                        for $desc in $node/designation
                        return
                            art:serializeNode($desc)
                    else ($node/designation)
                    ,
                    if ($serialize) then 
                        for $desc in $node/desc
                        return
                            art:serializeNode($desc)
                    else ($node/desc)
                    ,
                    $node/property,
                    $node/parent,
                    $node/child
                }
                </codedConcept>
        }
        </conceptList>
    ) else ()
}
</codeSystem>
};

(:~
 :   Look for codeSystem[@id] and recurse if codeSystem[@ref] is returned based on the buildingBlockRepositories in the project that returned it.
 :   If we get a codeSystem[@ref] from an external repository (through RetrieveCodeSystem), then tough luck, nothing can help us. The returned 
 :   data is a nested repositoryCodeSystemList element allowing you to see the full trail should you need that. 
 :   Example below reads:
 :      - We checked hwg- and found BBR hg-
 :      - We checked hg- and found BBR nictz2bbr-
 :      - We checked nictiz2bbr- and found the requested codeSystem
 :   <repositoryCodeSystemList url="http://decor.nictiz.nl/decor/services/" ident="hg-" referencedFrom="hwg-">
 :      <repositoryCodeSystemList url="http://decor.nictiz.nl/decor/services/" ident="nictiz2bbr-" referencedFrom="hg-">
 :          <codeSystem 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">
 :          ...
 :          </codeSystem>
 :      </repositoryCodeSystemList>
 :   </repositoryCodeSystemList>
 :
 :)
declare %private function cs:getCodeSystemByIdFromBBR($basePrefix as xs:string, $id as xs:string, $prefix as xs:string, $externalrepositorylist as element()*, $bbrmap as item()?) as element()* {
    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, $cs:strDecorServicesURL))
        )
    let $newmap                 :=
        map:merge(
            for $bbr in $externalrepositorylist
            let $newkey := concat($bbr/@ident, $bbr/@url)
            return map:entry($newkey, '')
        )
    (: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, map:get($bbrmap, $k)),
            for $k in map:keys($newmap) return if (map:contains($bbrmap, $k)) then () else map:entry($k, map:get($newmap, $k))
        ))
    return
        if ($hasBeenProcessedBefore) then () else if ($repourl=$cs:strDecorServicesURL and $repoident=$basePrefix) then () else (
            <repositoryCodeSystemList url="{$repourl}" ident="{$repoident}" referencedFrom="{$prefix}">
            {
                (: if this buildingBlockRepository resolves to our own server, then get it directly from the db. :)
                if ($repository[@url = $cs:strDecorServicesURL]) then (
                    let $project    := art:getDecorByPrefix(string($repoident))
                    
                    return 
                    if ($project) then (
                        let $codeSystems                := $project//codeSystem[@id = $id]
                        let $codeSystemsRef             := $project//codeSystem[@ref = $id]
                        
                        return (
                            attribute projecttype {'local'},
                            $codeSystems,
                            if (not($codeSystems) or $codeSystemsRef) then (
                                cs:getCodeSystemByIdFromBBR($basePrefix, $id, $repoident, $project/project/buildingBlockRepository[empty(@format) or @format='decor'], $newmap)
                            ) else ()
                        )
                    ) else ()
                )
                (: check cache first and do a server call as last resort. :)
                else (
                    let $cachedProject  := $get:colDecorCache//cacheme[@bbrurl = $repourl][@bbrident = $repoident]
                    
                    return
                    if ($cachedProject) then (
                        let $cachedCodeSystems := $get:colDecorCache//codeSystem[@id = $id] | $get:colDecorCache//codeSystem[@ref = $id]
                        let $cachedCodeSystems := $cachedCodeSystems[ancestor::cacheme[@bbrurl = $repourl][@bbrident = $repoident]]
                        
                        return (
                            attribute projecttype {'cached'},
                            $cachedCodeSystems[@id],
                            if (not($cachedCodeSystems) or $cachedCodeSystems[@ref]) then (
                                cs:getCodeSystemByIdFromBBR($basePrefix, $id, $repoident, $cachedProject/buildingBlockRepository[empty(@format) or @format='decor'], $newmap)
                            ) else ()
                        )
                    )
                    (: http call as last resort. :)
                    else (
                        try {
                            let $service-uri    := concat($repourl,'RetrieveCodeSystem?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 $codeSystems    := $server-response//codeSystems/project[@ident = $repoident]/codeSystem[@id]
                            return (
                                attribute projecttype {'remote'},
                                $codeSystems
                            )
                        }
                        catch * {()}
                    )
                )
            }
            </repositoryCodeSystemList>
        )
    
};

(:~
 :   Look for codeSystem[@id] and recurse if codeSystem[@ref] is returned based on the buildingBlockRepositories in the project that returned it.
 :   If we get a codeSystem[@ref] from an external repository (through RetrieveCodeSystem), then tough luck, nothing can help us. The returned 
 :   data is a nested repositoryCodeSystemList element allowing you to see the full trail should you need that. 
 :   Example below reads:
 :      - We checked hwg- and found BBR hg-
 :      - We checked hg- and found BBR nictz2bbr-
 :      - We checked nictiz2bbr- and found the requested codeSystem
 :   <repositoryCodeSystemList url="http://decor.nictiz.nl/decor/services/" ident="hg-" referencedFrom="hwg-">
 :      <repositoryCodeSystemList url="http://decor.nictiz.nl/decor/services/" ident="nictiz2bbr-" referencedFrom="hg-">
 :          <codeSystem 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">
 :          ...
 :          </codeSystem>
 :      </repositoryCodeSystemList>
 :   </repositoryCodeSystemList>
 :
 :)
declare %private function cs:getCodeSystemByNameFromBBR($basePrefix as xs:string, $name as xs:string, $prefix as xs:string, $externalrepositorylist as element()*, $bbrList as element()*) as element()* {
    for $repository in $externalrepositorylist
    let $repourl                := $repository/@url
    let $repoident              := $repository/@ident
    let $hasBeenProcessedBefore := $bbrList[@url=$repourl][@ident=$repoident]
    return
        if ($hasBeenProcessedBefore) then () else if ($repourl=$cs:strDecorServicesURL and $repoident=$basePrefix) then () else (
            <repositoryCodeSystemList url="{$repository/@url}" ident="{$repository/@ident}" referencedFrom="{$prefix}">
            {
                (: if this buildingBlockRepository resolves to our own server, then get it directly from the db. :)
                if ($repository[@url = $cs:strDecorServicesURL]) then (
                    let $project    := art:getDecorByPrefix(string($repoident))
                    
                    return 
                    if ($project) then (
                        let $codeSystems  := $project//codeSystem[@name = $name]
                        
                        return (
                            $codeSystems,
                            if (not($codeSystems) or $codeSystems[@ref]) then (
                                cs:getCodeSystemByNameFromBBR($basePrefix, $name, $repoident, $project/project/buildingBlockRepository[empty(@format) or @format='decor'], ($bbrList | $externalrepositorylist))
                            ) else ()
                        )
                    ) else ()
                )
                (: check cache first and do a server call as last resort. :)
                else (
                    let $cachedProject  := $get:colDecorCache//project[@prefix = $repoident][ancestor::decor/@deeplinkprefixservices = $repourl]
                    
                    return
                    if ($cachedProject) then (
                        let $cachedCodeSystems := $get:colDecorCache//codeSystem[@name = $name][ancestor::decor/@deeplinkprefixservices = $repourl][ancestor::decor/project/@prefix = $repoident]
                        
                        return (
                            $cachedCodeSystems,
                            if (not($cachedCodeSystems) or $cachedCodeSystems[@ref]) then (
                                cs:getCodeSystemByNameFromBBR($basePrefix, $name, $repoident, $cachedProject/buildingBlockRepository[empty(@format) or @format='decor'], ($bbrList | $externalrepositorylist))
                            ) else ()
                        )
                    )
                    (: http call as last resort. :)
                    else (
                        try {
                            let $service-uri    := concat($repourl,'RetrieveCodeSystem?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], ' '),''''))
                                )
                            return $server-response[2]//codeSystems/project[@ident = $repoident]/codeSystem[@id]
                            (:doc(xs:anyURI(concat($repourl,'/RetrieveCodeSystem?format=xml&amp;prefix=',$repoident, '&amp;name=', $name)))/codeSystems/project[@ident=$repoident]/codeSystem[@id]:)
                        }
                        catch * {()}
                    )
                )
            }
            </repositoryCodeSystemList>
        )
    
};

(:~
:   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 cs: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 cs:getSimpleLuceneOptions() as element() {
    <options>
        <filter-rewrite>yes</filter-rewrite>
    </options>
};
