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 adsearch       = "http://art-decor.org/ns/decor/search";

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 vs      = "http://art-decor.org/ns/decor/valueset" at "api-decor-valueset.xqm";
import module namespace templ   = "http://art-decor.org/ns/decor/template" at "api-decor-template.xqm";
import module namespace gg      = "http://art-decor.org/ns/decor/governancegroups" at "api-decor-governancegroups.xqm";
import module namespace decor   = "http://art-decor.org/ns/decor" at "api-decor.xqm";
import module namespace aduser  = "http://art-decor.org/ns/art-decor-users" at "api-user-settings.xqm";

declare namespace f             = "http://hl7.org/fhir";

(:~
:   All functions support their own override, but this is the fallback for the maximum number of results returned on a search
:)
declare variable $adsearch:maxResults := xs:integer('50');

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

(:~
:   Returns all projects optionally filtered on project/name. 
:   Projects carry only attributes and a name and optionally governance group info
:   <result current="10" total="531">
:       <project id="1.2.3" prefix="peri20-" defaultLanguage="en-US" experimental="false" repository="false" private="false">
:   ...
:       </project>
:   </result>
:
:   Logic: 
:   - start with every available project under decor/data or decor/releases (only when $version has a value)
:   - if there is no $searchTerm carry on with every available project
:       - else if there is 1 numerical $searchTerm, get any project that has a project@id that ends with $searchTerm
:       - else if there is 1 OID pattern $searchTerm, get any project that has a project@id that exactly matches $searchTerm
:       - else do Lucene match on any project/name and if there is 1 searchterm, also match project/@prefix
:
:   Now we have our start set and we going to build our intermediate result set. There are a few options:
:   - if there are no search terms
:       - get registered last visit by user/guest (user-info.xml)
:       - get last modification date of project file
:       - get if project is marked private
:       
:       Private projects are only returned to their authors, regardless of anything. Project are sorted descending by newest date from last-visit and last-edit
:
:   - if there were search terms then results are sorted by name, except that private projects are skipped unless user is an author
:
:   Now for the final processing step:
:       - Return the first $maxResults projects from the intermediary result set, sorted by name, connected to their governance groups if any
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchProject($searchTerms as xs:string*, $maxResults as xs:integer?) as element(result) {
    adsearch:searchProject($searchTerms, $maxResults, (), ())
};
declare function adsearch:searchProject($searchTerms as xs:string*, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults < 10) then $maxResults else 10
    
    let $decorObjects   := 
        if (empty($version)) then (
            $get:colDecorData//decor/project
        ) else (
            $get:colDecorVersion//decor[@versionDate = $version]/project
        )
    
    let $results        := 
        if (count($searchTerms)=0) then 
            $decorObjects
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
            if (count($searchTerms)=1 and matches($searchTerms,'^\d+(\.\d+)*$')) then
                $decorObjects[ends-with(@id,concat('.',$searchTerms[1]))]
            else 
            if (count($searchTerms)=1 and matches($searchTerms,'^[0-2](\.(0|[1-9][0-9]*))*$') ) then
                $decorObjects[@id = $searchTerms]
            else (
                let $searchPrefix   := if (count($searchTerms)=1 and not(ends-with($searchTerms, '-'))) then concat($searchTerms,'-') else ($searchTerms)
                
                return
                $decorObjects[ft:query(name,$luceneQuery,$luceneOptions)] | $decorObjects[@prefix = $searchPrefix]
            )
        )
    
    let $hasPrefs       := map:merge(for $p in aduser:getProjectPreference(get:strCurrentUserName(), ()) return map:entry($p/@id, $p))
    
    let $results        :=
        if (count($searchTerms)=0) then 
            (:most relevant first:)
            for $r in $results
            let $decor      := $r/ancestor::decor
            let $lastvisit  := try {map:get($hasPrefs, $r/@id)} catch * {()}
            let $last       := if ($lastvisit) then $lastvisit/xs:dateTime(@at) else xs:dateTime('1970-01-01T00:00:00')
            order by $last descending
            return 
                if (empty($lastvisit)) then
                    if (decor:isActiveAuthorP($decor, get:strCurrentUserName())) then $r else ()
                else 
                    if (decor:isPrivateP($decor) and get:strCurrentUserName() = 'guest') then () else ($r) 
        else (
            (:alphabetic:)
            for $r in $results
            let $decor  := $r/ancestor::decor
            order by string-length(if ($r/name[@language = $language]) then $r/name[@language = $language][1] else $r/name[1])
            return 
                if (decor:isActiveAuthorP($decor, get:strCurrentUserName())) then $r else 
                if (decor:isPrivateP($decor)) then () else ($r)
        )
    
    let $hostedgroups   := gg:getHostedGovernanceGroups()
    let $govgroups      := 
        if (count($searchTerms)=0) then ($hostedgroups/group) else 
        if (count($searchTerms)=1 and matches($searchTerms,'^\d+(\.\d+)*$')) then
            $hostedgroups[ends-with(@id, concat('.',$searchTerms[1]))]
        else 
        if (count($searchTerms)=1 and matches($searchTerms, '^[0-2](\.(0|[1-9][0-9]*))*$') ) then
            $hostedgroups[@id = $searchTerms]
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            
            return
            $hostedgroups/group[ft:query(name,$luceneQuery,$luceneOptions)]
        )
    let $govgroups      :=
        (:alphabetic:)
        for $r in $govgroups
        order by string-length(if ($r/name[@language = $language]) then $r/name[@language = $language][1] else $r/name[1])
        return $r
        
    let $count          := count($results | $govgroups)
    
return
    <result current="{if ($count <= $maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence(($results, $govgroups), 1, $maxResults)
        return 
            if ($object[self::project]) then (
                let $decor              := $object/ancestor::decor
                let $gglinks            := gg:getLinkedGovernanceGroups($object/@id)
                let $defaultLanguage    := $object/@defaultLanguage
                (:let $displayName        := if ($object/name[@language = $defaultLanguage]) then $object/name[@language = $defaultLanguage] else $object/name[1]
                order by string-length($displayName):)
                let $userPref           := map:get($hasPrefs, $object/@id)
                return
                element {'project'} {
                    $object/(@* except (@repository|@private|@lastmodified|@count|@at)),
                    attribute repository {$decor/@repository='true'},
                    attribute private {$decor/@private='true'},
                    attribute lastmodified {xmldb:last-modified(util:collection-name($object), util:document-name($object))},
                    $userPref/@count | $userPref/@at,
                    art:serializeNodes($object/name, $object/name/@language)
                    ,
                    for $partOf in distinct-values($gglinks/partOf/@ref)
                    let $gg := gg:getGovernanceGroup($partOf)
                    return
                        element {'governance'}
                        {
                            $gg/@*,
                            art:serializeNodes($gg/name, $object/name/@language)
                        }
                }
            )
            else (
                element {name($object)} {$object/@*, art:serializeNodes($object/name, $object/name/@language)}
            )
    }
    </result>
};

(:~
:   Returns all code systems optionally filtered on project/id|ref/name. 
:   Code systems carry only attributes  and their normal name elements (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the value set is in
:   Example output:
:   <result current="3" total="3">
:       <codeSystem project="peri20-" name="EthnicGroup" displayName="EthnicGroup" effectiveDate="2009-10-01T00:00:00" id="2.16.840.1.113883.2.4.11.3" statusCode="final"/>
:       <codeSystem project="peri20-" id="2.16.840.1.113883.2.4.11.3" effectiveDate="2013-01-10T12:51:30" name="EthnicGroup" displayName="EthnicGroup" statusCode="final"/>
:       <codeSystem project="peri20-" id="2.16.840.1.113883.2.4.11.3" effectiveDate="2014-05-19T14:35:30" statusCode="draft" name="EthnicGroup" displayName="EthnicGroup"/>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchCodesystem($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?) as element(result) {
    adsearch:searchCodesystem($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchCodesystem($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $decorObjects   := 
        if (empty($projectPrefix)) then (
            if (empty($version)) then (
                $get:colDecorData//codeSystem[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])]
             ) else (
                $get:colDecorVersion//codeSystem[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
             )
        ) else
        if ($projectPrefix='*') then (
            if (empty($version)) then (
                $get:colDecorData//codeSystem[not(ancestor::decor[@private='true'])]
            ) else (
                $get:colDecorVersion//codeSystem[not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
            )
        ) else (
            if (empty($version)) then (
                $get:colDecorData//codeSystem[ancestor::decor/project[@prefix=$projectPrefix]]
            ) else (
                $get:colDecorVersion//codeSystem[ancestor::decor/project[@prefix=$projectPrefix]][ancestor::decor/@versionDate=$version]
            )
        )
    
    let $results        :=
        if (count($searchTerms)=0) then 
            $decorObjects
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
            if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
                $decorObjects[ends-with(@id, $searchTerms[1])] | $decorObjects[ends-with(@ref, $searchTerms[1])]
            else if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
                $decorObjects[@id=$searchTerms[1]] | $decorObjects[@ref=$searchTerms[1]]
            else (
                $decorObjects[ft:query(@name,$luceneQuery,$luceneOptions)] | $decorObjects[ft:query(@displayName,$luceneQuery,$luceneOptions)]
            )
        )
    
    (:shortest match first:)
    let $results        :=
        for $r in $results
        order by string-length($r/@displayName)
        return $r
    
    let $count          := count($results)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($results,1,$maxResults)
        let $displayName   := if ($object[@displayName]) then $object/@displayName else $object/@name
        order by string-length($displayName)
        return 
        element {$object/local-name()} {
            attribute project {$object/ancestor::decor/project/@prefix},
            attribute projectName {$object/ancestor::decor/project/name[1]},
            $object/(@* except (@project|@projectName))
        }
    }
    </result>
};

(:~
:   Returns matching concepts based on id/name and optionally type. Concepts that inherit do not have names so in order 
:   to find those we first match all concepts in repositories and (if supplied) within the project that the supplied 
:   dataset-id is in. With that result we recursively find all concepts that inherit from the resultset.
:   Concepts carry only attributes and their normal name elements (whatever was available in the db). However to pin 
:   point them back to where they belong in ART, they also carry these attributes: 
:   @uuid - UUID
:   @datasetId / @datasetName - Dataset-id and name (first found name) the concept is in
:   @project / @projectName - Project prefix and name (first found name) the concept is in
:   @repository / @private - Project attributes of the project the concept is in
:   Example output:
:   <result current="50" total="271">
:       <concept uuid="7b5d95b7-f821-471e-9758-acdeac358775" datasetId="1.2.3.4" datasetName="Dataset 4" project="pfx-" projectName="Project name" repository="false" private="false" id="1.2.4.4" effectiveDate="2012-08-06T00:00:00" statusCode="final" type="group">
:           <name language="en-US">English concept name</name>
:           <name language="nl-NL">Nederlandse conceptnaam</name>
:       </concept>
:   ...
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix
:   @param $searchTerms required sequence of terms to look for
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $datasetOrTransactionId optional DECOR full dataset/transaction id. If resolves to a dataset or transaction only returns concepts from that dataset or transaction
:   @param $datasetOrTransactionEffectiveDate optional DECOR full dataset effectiveDate. Only useful with parameter $datasetOrTransactionId
:   @param $conceptType optional DECOR concept type ('group' or 'item'). Returns both types if not given
:   @param $originalConceptsOnly required returns original concepts (that do not inherit) if true, otherwise returns every hit including concepts that inherit from matching concepts
:   @param $localConceptsOnly required returns only concepts within the given prefix
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchConcept($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?, $datasetOrTransactionId as xs:string?, $datasetOrTransactionEffectiveDate as xs:string?, $conceptType as xs:string?, $originalConceptsOnly as xs:boolean, $localConceptsOnly as xs:boolean) as element(result) {
    adsearch:searchConcept($projectPrefix, $searchTerms, $maxResults, (), (), $datasetOrTransactionId, $datasetOrTransactionEffectiveDate, $conceptType, (), $originalConceptsOnly, $localConceptsOnly)
};
declare function adsearch:searchConcept($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?, $datasetOrTransactionId as xs:string?, $datasetOrTransactionEffectiveDate as xs:string?, $conceptType as xs:string?, $originalConceptsOnly as xs:boolean, $localConceptsOnly as xs:boolean) as element(result) {
    adsearch:searchConcept($projectPrefix, $searchTerms, $maxResults, (), (), $datasetOrTransactionId, $datasetOrTransactionEffectiveDate, $conceptType, (), $originalConceptsOnly, $localConceptsOnly)
};
declare function adsearch:searchConcept($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?, $datasetOrTransactionId as xs:string?, $datasetOrTransactionEffectiveDate as xs:string?, $conceptType as xs:string?, $statusCodes as xs:string*, $originalConceptsOnly as xs:boolean, $localConceptsOnly as xs:boolean) as element(result) {
    adsearch:searchConcept($projectPrefix, $searchTerms, $maxResults, (), (), $datasetOrTransactionId, $datasetOrTransactionEffectiveDate, $conceptType, (), $originalConceptsOnly, $localConceptsOnly, ())
};
declare function adsearch:searchConcept($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?, $datasetOrTransactionId as xs:string?, $datasetOrTransactionEffectiveDate as xs:string?, $conceptType as xs:string?, $statusCodes as xs:string*, $originalConceptsOnly as xs:boolean, $localConceptsOnly as xs:boolean, $searchPrefix as xs:string*) as element(result) {
    let $maxResults         := if ($maxResults) then $maxResults else $adsearch:maxResults
    let $queryOnId          := if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then true() else false()
    
    let $resultsOnId        := 
        if ($queryOnId) then (
            if ($projectPrefix = '*') then
                if (empty($conceptType)) then
                    $get:colDecorData//concept[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)]
                else (
                    $get:colDecorData//concept[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType]
                )
            else
            if (empty($projectPrefix)) then
                if (empty($conceptType)) then
                    $get:colDecorData//concept[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets][not(ancestor::conceptList | ancestor::history)]
                else (
                    $get:colDecorData//concept[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType]
                )
            else
            if (empty($version)) then
                if (empty($conceptType)) then
                    $get:colDecorData//concept[ends-with(@id, $searchTerms[1])][ancestor::decor/project[@prefix=$projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)]
                else (
                    $get:colDecorData//concept[ends-with(@id, $searchTerms[1])][ancestor::decor/project[@prefix=$projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType]
                )
            else (
                if ($get:colDecorVersion/decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]) then
                    if (empty($conceptType)) then
                        $get:colDecorVersion//concept[ends-with(@id, $searchTerms[1])][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)]
                    else (
                        $get:colDecorVersion//concept[ends-with(@id, $searchTerms[1])][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType]
                    )
                else (
                    if (empty($conceptType)) then
                        art:getDecorByPrefix($projectPrefix, $version, $language)//concept[ends-with(@id, $searchTerms[1])][ancestor::datasets]
                    else (
                        art:getDecorByPrefix($projectPrefix, $version, $language)//concept[ends-with(@id, $searchTerms[1])][ancestor::datasets][@type = $conceptType]
                    )
                )
            )
        ) else ()
        
    let $resultsOnName      :=
        if ($queryOnId) then () else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
            
            if ($projectPrefix = '*') then
                if (empty($conceptType)) then
                    ($get:colDecorData//concept[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)] |
                     $get:colDecorData//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[not(@private='true')]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)])
                else (
                    ($get:colDecorData//concept[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType] |
                     $get:colDecorData//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[not(@private='true')]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType])
                )
            else
            if (empty($projectPrefix)) then
                if (empty($conceptType)) then
                    ($get:colDecorData//concept[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets][not(ancestor::conceptList | ancestor::history)] |
                     $get:colDecorData//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets][not(ancestor::conceptList | ancestor::history)])
                else (
                    ($get:colDecorData//concept[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType] |
                     $get:colDecorData//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType])
                )
            else
            if (empty($version)) then
                if (empty($conceptType)) then
                    ($get:colDecorData//concept[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true'] | ancestor::decor/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)] |
                     $get:colDecorData//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true'] | ancestor::decor/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)])
                else (
                    ($get:colDecorData//concept[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true'] | ancestor::decor/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType] |
                     $get:colDecorData//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true'] | ancestor::decor/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType])
                )
            else (
                if ($get:colDecorVersion/decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]) then
                    if (empty($conceptType)) then
                        ($get:colDecorVersion//concept[ft:query(name,$luceneQuery)][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)] |
                         $get:colDecorVersion//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)])
                    else (
                        ($get:colDecorVersion//concept[ft:query(name,$luceneQuery)][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType] |
                         $get:colDecorVersion//concept[ft:query(synonym,$luceneQuery)][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix = $projectPrefix]][ancestor::datasets][not(ancestor::conceptList | ancestor::history)][@type = $conceptType])
                    )
                else (
                    if (empty($conceptType)) then
                        (art:getDecorByPrefix($projectPrefix, $version, $language)//concept[ft:query(name,$luceneQuery)][ancestor::datasets] |
                         art:getDecorByPrefix($projectPrefix, $version, $language)//concept[ft:query(synonym,$luceneQuery)][ancestor::datasets])
                    else (
                        (art:getDecorByPrefix($projectPrefix, $version, $language)//concept[ft:query(name,$luceneQuery)][ancestor::datasets][@type = $conceptType] |
                         art:getDecorByPrefix($projectPrefix, $version, $language)//concept[ft:query(synonym,$luceneQuery)][ancestor::datasets][@type = $conceptType])
                    )
                )
            )
        )
    
    let $allResults         := 
        if ($originalConceptsOnly) then (
            $resultsOnName[not(inherit | contains)] | $resultsOnId[not(inherit | contains)]
        ) else
        if ($queryOnId) then (
            $resultsOnName | $resultsOnId
        )
        else
        if (empty($version)) then (
            for $concept in ($resultsOnName | $resultsOnId) 
            return adsearch:getConceptsThatInheritFromConcept($concept, ())
        )
        else (
            (: everything should already match in compiled projects. No need to re-look-up :)
            $resultsOnName | $resultsOnId
        )
    
    let $transaction        := if ($datasetOrTransactionId) then (art:getTransaction($datasetOrTransactionId, $datasetOrTransactionEffectiveDate, $version, $language)) else ()
    let $resultsFiltered    := 
        if ($transaction) then (
            $allResults[@id = $transaction//concept/@ref]
        )
        else 
        if ($datasetOrTransactionId) then (
            if ($datasetOrTransactionEffectiveDate castable as xs:dateTime) then (
                $allResults[ancestor::dataset[@id = $datasetOrTransactionId][@effectiveDate = $datasetOrTransactionEffectiveDate]] |
                $allResults[@datasetId = $datasetOrTransactionId][@datasetEffectiveDate = $datasetOrTransactionEffectiveDate]
            ) else (
                $allResults[ancestor::dataset[@id = $datasetOrTransactionId]] |
                $allResults[@datasetId = $datasetOrTransactionId]
            )
        ) else ($allResults)
    let $resultsFiltered    := 
        if ($localConceptsOnly) then $resultsFiltered[(ancestor::decor/project/@prefix | @project) = $projectPrefix] else $resultsFiltered
    let $resultsFiltered    :=
        if (empty($statusCodes)) then $resultsFiltered else $resultsFiltered[@statusCode = $statusCodes]
    let $resultsFiltered    :=
        if (empty($searchPrefix)) then $resultsFiltered else $resultsFiltered[(ancestor::decor/project/@prefix | @project) = $searchPrefix]
    
    (:deduplicate and sort by shortest match first:)
    let $resultsFiltered    :=
        for $concepts in $resultsFiltered
        let $conceptId      := concat($concepts/@id, $concepts/@effectiveDate)
        group by $conceptId
        order by string-length(($concepts/name/text())[1])
        return $concepts[1]
    
    let $resultsFiltered    :=
        for $concepts in $resultsFiltered
        let $conceptLength  := string-length(($concepts/name/text())[1])
        group by $conceptLength 
        order by $conceptLength
        return (
            for $concept in $concepts[ancestor::decor/project[@prefix=$projectPrefix] | @project[.=$projectPrefix]]
            order by $concept/ancestor::dataset/@id, $concept/@datasetId
            return $concept
            ,
            for $concept in $concepts[not(ancestor::decor/project[@prefix=$projectPrefix] | @project[.=$projectPrefix])]
            order by $concept/ancestor::dataset/@id, $concept/@datasetId
            return $concept
        )
    
    let $count              := count($resultsFiltered)
    
return
    <result current="{if ($count le $maxResults) then $count else $maxResults}" total="{$count}" q="{if (request:exists()) then request:get-query-string() else ()}">
    {
        for $concepts in subsequence($resultsFiltered,1,$maxResults)
        let $ancestorProject    := $concepts[1]/ancestor::decor
        let $displayName        := $concepts[1]/name[1]
        order by string-length($displayName)
        return
            element {$concepts[1]/local-name()}
            {
                attribute uuid {util:uuid()},
                $concepts[1]/(@* except (@uuid|@conceptId|@datasetId|@datasetEffectiveDate|@datasetName|@project|@projectName|@repository|@private)),
                attribute conceptId {$concepts[1]/@id},
                if ($ancestorProject) then (
                    attribute datasetId             {$concepts[1]/ancestor::dataset/@id},
                    attribute datasetEffectiveDate  {$concepts[1]/ancestor::dataset/@effectiveDate},
                    attribute datasetName           {$concepts[1]/ancestor::dataset/string-join((name[1],@versionLabel),' ')},
                    attribute project               {$ancestorProject/project/@prefix},
                    attribute projectName           {$ancestorProject/project/name[1]},
                    attribute repository            {$ancestorProject/@repository='true'},
                    attribute private               {$ancestorProject/@private='true'}
                )
                else (
                    $concepts[1]/@datasetId,
                    $concepts[1]/@datasetEffectiveDate,
                    $concepts[1]/@datasetName,
                    $concepts[1]/@project,
                    $concepts[1]/@projectName,
                    $concepts[1]/@repository,
                    $concepts[1]/@private
                ),
                $concepts[1]/name,
                $concepts[1]/inherit,
                
                let $dbconcept  := $get:colDecorData//concept[@id = $concepts[1]/@id][not(ancestor::history)]
                let $dbconcept  := $dbconcept[@effectiveDate = $concepts[1]/@effectiveDate]
                let $languages  := $dbconcept/ancestor::decor/project/name/@language
                
                for $language in $languages
                return
                <path language="{$language}">
                {
                    for $ancestor in $dbconcept/ancestor::concept
                    let $originalConcept    := art:getOriginalForConcept($ancestor)
                    let $conceptName        := if ($originalConcept/name[@language=$language]) then $originalConcept/name[@language=$language] else ($originalConcept/name[1])
                    return
                        concat(($conceptName/text())[1], ' / ')
                }
                </path>
            }
    }
    </result>
};

(:~
:   Returns all datasets optionally filtered on project/id/name. 
:   Dataset carry only attributes and their normal name elements (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the dataset is in
:   Example output:
:   <result current="5" total="5">
:       <dataset project="peri20-" id="2.16.840.1.113883.2.4.3.11.60.90.77.1.1" effectiveDate="2009-10-01T00:00:00" statusCode="final">
:           <name language="nl-NL">Spirit dataset 1a</name>
:       </dataset>
:       <dataset project="peri20-" id="2.16.840.1.113883.2.4.3.11.60.90.77.1.2" effectiveDate="2011-01-28T00:00:00" statusCode="final">
:           <name language="nl-NL">Spirit dataset 1c</name>
:       </dataset>
:   ...
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchDataset($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?) as element(result) {
    adsearch:searchDataset($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchDataset($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults         := if ($maxResults) then $maxResults else $adsearch:maxResults
    let $queryOnId          := if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then true() else false()
    
    let $resultsOnId        := 
        if ($queryOnId) then () else (
            if ($projectPrefix = '*') then
                $get:colDecorData//dataset[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')]][ancestor::datasets]
            else
            if (empty($projectPrefix)) then
                $get:colDecorData//dataset[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets]
            else
            if (empty($version)) then
                $get:colDecorData//dataset[ends-with(@id, $searchTerms[1])][ancestor::decor/project[@prefix=$projectPrefix]][ancestor::datasets]
            else (
                if ($get:colDecorVersion/decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]) then
                    $get:colDecorVersion//dataset[ends-with(@id, $searchTerms[1])][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]][ancestor::datasets]
                else (
                    art:getDecorByPrefix($projectPrefix, $version, $language)//dataset[ends-with(@id, $searchTerms[1])][ancestor::datasets]
                )
            )
        )
        
    let $resultsOnName      :=
        if ($queryOnId) then () else if (count($searchTerms)=0) then (
            if ($projectPrefix = '*') then
                $get:colDecorData//dataset[ancestor::decor[not(@private='true')]][ancestor::datasets]
            else
            if (empty($projectPrefix)) then
                $get:colDecorData//dataset[ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets]
            else
            if (empty($version)) then
                $get:colDecorData//dataset[ancestor::decor/project[@prefix = $projectPrefix]][ancestor::datasets]
            else (
                if ($get:colDecorVersion/decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]) then
                    $get:colDecorVersion//dataset[ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]][ancestor::datasets]
                else (
                    art:getDecorByPrefix($projectPrefix, $version, $language)//dataset[ancestor::datasets]
                )
            )
        ) else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
            
            if ($projectPrefix = '*') then
                $get:colDecorData//dataset[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')]][ancestor::datasets]
            else
            if (empty($projectPrefix)) then
                $get:colDecorData//dataset[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::datasets]
            else
            if (empty($version)) then
                $get:colDecorData//dataset[ft:query(name,$luceneQuery)][ancestor::decor/project[@prefix = $projectPrefix]][ancestor::datasets]
            else (
                if ($get:colDecorVersion/decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]) then
                    $get:colDecorVersion//dataset[ft:query(name,$luceneQuery)][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]][ancestor::datasets]
                else (
                    art:getDecorByPrefix($projectPrefix, $version, $language)//dataset[ft:query(name,$luceneQuery)][ancestor::datasets]
                )
            )
        )
    
    let $results         := $resultsOnName | $resultsOnId
    
    (:shortest match first:)
    let $results        :=
        for $r in $results
        let $displayName   := if ($r/name[@language = $language]) then $r/name[@language = $language][1] else $r/name[1]
        order by string-length($displayName)
        return $r
    
    let $count          := count($results)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($results,1,$maxResults)
        let $displayName   := if ($object/name[@language = $language]) then $object/name[@language = $language][1] else $object/name[1]
        order by string-length($displayName)
        return 
        element {$object/local-name()} {
            attribute project {$object/ancestor::decor/project/@prefix},
            attribute projectName {$object/ancestor::decor/project/name[1]},
            $object/(@* except (@project|@projectName)),
            $object/name
        }
    }
    </result>
};

(:~
:   Returns all issues optionally filtered on project/id/name. 
:   Issues carry only attributes (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the issue is in
:   Example output:
:   <result current="10" total="531">
:       <issue project="peri20-" id="2.16.840.1.113883.2.4.3.11.60.90.77.6.1" displayName="Care Provision ID" type="RFC"/>
:   ...
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchIssue($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?) as element(result) {
    adsearch:searchIssue($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchIssue($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $decorObjects   := 
        if (empty($projectPrefix)) then (
            if (empty($version)) then (
                $get:colDecorData//issue[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])]
             ) else (
                $get:colDecorVersion//issue[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
             )
        ) else
        if ($projectPrefix='*') then (
            if (empty($version)) then (
                $get:colDecorData//issue[not(ancestor::decor[@private='true'])]
            ) else (
                $get:colDecorVersion//issue[not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
            )
        ) else (
            if (empty($version)) then (
                $get:colDecorData//issue[ancestor::decor/project[@prefix=$projectPrefix]]
            ) else (
                $get:colDecorVersion//issue[ancestor::decor/project[@prefix=$projectPrefix]][ancestor::decor/@versionDate=$version]
            )
        )
    
    let $results        := 
        if (count($searchTerms)=0) then 
            $decorObjects
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
            if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
                $decorObjects[ends-with(@id, $searchTerms[1])]
            else if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
                $decorObjects[@id=$searchTerms[1]]
            else (
                $decorObjects[ft:query(@displayName,$luceneQuery,$luceneOptions)] | $decorObjects[ft:query(*/desc,$luceneQuery,$luceneOptions)]
            )
        )
    
    (:shortest match first:)
    let $results        :=
        for $r in $results
        order by string-length($r/@displayName)
        return $r
    
    let $count          := count($results)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($results,1,$maxResults)
        let $displayName   := if ($object[@displayName]) then $object/@displayName else $object/@name
        order by string-length($displayName)
        return 
        element {$object/local-name()} {
            attribute project {$object/ancestor::decor/project/@prefix},
            attribute projectName {$object/ancestor::decor/project/name[1]},
            $object/(@* except (@project|@projectName))
        }
    }
    </result>
};

(:~
:   Returns all templates optionally filtered on project/id/name. 
:   Templates carry only attributes (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the template is in
:   Example output:
:   <result current="1" total="1">
:       <template project="peri20-" id="2.16.840.1.113883.2.4.6.10.90.900853" name="Ethnicgroup" displayName="Ethnic group" effectiveDate="2012-06-26T00:00:00" statusCode="draft"/>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms required sequence of terms to look for
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchTemplate($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?) as element(result) {
    adsearch:searchTemplate($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchTemplate($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $decorObjects   := 
        if (empty($projectPrefix)) then (
            if (empty($version)) then (
                $get:colDecorData//rules[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])]
             ) else (
                $get:colDecorVersion//rules[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
             )
        ) else
        if ($projectPrefix='*') then (
            if (empty($version)) then (
                $get:colDecorData//rules[not(ancestor::decor[@private='true'])]
            ) else (
                $get:colDecorVersion//rules[not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
            )
        ) else (
            if (empty($version)) then (
                $get:colDecorData//rules[ancestor::decor/project[@prefix=$projectPrefix]]
            ) else (
                $get:colDecorVersion//rules[ancestor::decor/project[@prefix=$projectPrefix]][ancestor::decor/@versionDate=$version]
            )
        )
    
return
    adsearch:searchTemplatesInRuleSet($searchTerms, $decorObjects, $maxResults)
};

declare function adsearch:searchTemplatesInRuleSet($searchTerms as xs:string+, $ruleSet as element()*, $maxResults as xs:integer?) as element(result) {
    let $maxResults         := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $luceneQuery        := adsearch:getSimpleLuceneQuery($searchTerms)
    let $luceneOptions      := adsearch:getSimpleLuceneOptions()
            
    let $results        := 
        if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
            $ruleSet/template[ends-with(@id, $searchTerms[1])] | $ruleSet/template[ends-with(@ref, $searchTerms[1])]
        else if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
            $ruleSet/template[@id=$searchTerms[1]] | $ruleSet/template[@ref=$searchTerms[1]]
        else (
            $ruleSet/template[ft:query(@name,$luceneQuery,$luceneOptions)] | 
            $ruleSet/template[ft:query(@displayName,$luceneQuery,$luceneOptions)]
        )
    let $resultsById    := (
            $results[@id],
            for $template in $results[@ref]
            let $displayName   := if ($template[@displayName]) then $template/@displayName else $template/@name
            order by string-length($displayName)
            return 
            templ:getTemplateById($template/@ref,(),$template/ancestor::decor/project/@prefix)/*/template[@url]
        )
        
    let $count := count($resultsById)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($resultsById, 1, $maxResults)
        let $decorOrigin    := if ($object[ancestor::decor]) then $object/ancestor::decor else art:getDecorByPrefix($object/@ident, (), ())
        let $ident          := ($decorOrigin/project/@prefix, $object/ancestor::cacheme/@bbrident)[1]
        let $url            := ($object/ancestor::cacheme/@bbrurl)[1]
        let $displayName    := if ($object[@displayName]) then $object/@displayName else $object/@name
        order by string-length($displayName)
        return 
        element {$object/local-name()} {
            $object/(@* except (@project|@projectName|@sortname)),
            attribute project {$ident},
            attribute projectName {$decorOrigin/project/name[1]},
            attribute sortname {if (string-length($object/@displayName)>0) then $object/@displayName else $object/@name},
            if ($object/@ident) then () else if ($ident) then attribute ident {$ident} else (),
            if ($object/@url) then () else if ($url) then attribute url {$url} else (),
            for $node in $decorOrigin/project/name
            return
                <projectName>{$node/@*, $node/node()}</projectName>
            ,
            art:serializeDescriptionNodes($object/desc),
            $object/classification
        }
    }
    </result>
};

(:~
:   Returns all questionnaires optionally filtered on project/id/name. 
:   Questionnaires carry only attributes (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the template is in
:   Example output:
:   <result current="1" total="1">
:       <questionnaire project="peri20-" id="2.16.840.1.113883.2.4.6.10.90.900853" name="Ethnicgroup" displayName="Ethnic group" effectiveDate="2012-06-26T00:00:00" statusCode="draft"/>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms required sequence of terms to look for
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchQuestionnaire($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?) as element(result) {
    adsearch:searchQuestionnaire($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchQuestionnaire($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $decorObjects   := 
        if (empty($projectPrefix)) then (
            if (empty($version)) then (
                $get:colDecorData//questionnaire[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])]
             ) else (
                $get:colDecorVersion//questionnaire[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
             )
        ) else
        if ($projectPrefix='*') then (
            if (empty($version)) then (
                $get:colDecorData//questionnaire[not(ancestor::decor[@private='true'])]
            ) else (
                $get:colDecorVersion//questionnaire[not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
            )
        ) else (
            if (empty($version)) then (
                $get:colDecorData//questionnaire[ancestor::decor/project[@prefix=$projectPrefix]]
            ) else (
                $get:colDecorVersion//questionnaire[ancestor::decor/project[@prefix=$projectPrefix]][ancestor::decor/@versionDate=$version]
            )
        )
    
return
    adsearch:searchQuestionnaireInSet($searchTerms, $decorObjects, $maxResults)
};

declare function adsearch:searchQuestionnaireInSet($searchTerms as xs:string+, $ruleSet as element()*, $maxResults as xs:integer?) as element(result) {
    let $maxResults         := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $luceneQuery        := adsearch:getSimpleLuceneQuery($searchTerms)
    let $luceneOptions      := adsearch:getSimpleLuceneOptions()
            
    let $results        := 
        if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
            $ruleSet[ends-with(@id, $searchTerms[1])] | $ruleSet[ends-with(@ref, $searchTerms[1])]
        else if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
            $ruleSet[@id=$searchTerms[1]] | $ruleSet[@ref=$searchTerms[1]]
        else (
            $ruleSet[ft:query(name,$luceneQuery,$luceneOptions)]
        )
    let $resultsById    := (
            $results[@id],
            for $template in $results[@ref]
            let $displayName   := if ($template[@displayName]) then $template/@displayName else $template/@name
            order by string-length($displayName)
            return 
            $get:colDecorData//questionnaire[@id = $template/@ref]
        )
        
    let $count := count($resultsById)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($resultsById, 1, $maxResults)
        let $decorOrigin    := if ($object[ancestor::decor]) then $object/ancestor::decor else art:getDecorByPrefix($object/@ident, (), ())
        let $ident          := ($decorOrigin/project/@prefix, $object/ancestor::cacheme/@bbrident)[1]
        let $url            := ($object/ancestor::cacheme/@bbrurl)[1]
        let $displayName    := $object/name[1]
        order by string-length($displayName)
        return 
        element {$object/local-name()} {
            $object/(@* except (@project|@projectName|@sortname)),
            attribute project {$ident},
            attribute projectName {$decorOrigin/project/name[1]},
            attribute sortname {$displayName},
            if ($object/@ident) then () else if ($ident) then attribute ident {$ident} else (),
            if ($object/@url) then () else if ($url) then attribute url {$url} else (),
            $object/name,
            for $node in $decorOrigin/project/name
            return
                <projectName>{$node/@*, $node/node()}</projectName>
            ,
            art:serializeDescriptionNodes($object/desc),
            $object/classification
        }
    }
    </result>
};

(:~
:   Returns all questionnaireResponses optionally filtered on project/id/name. 
:   Questionnaires carry only attributes (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the template is in
:   Example output:
:   <result current="1" total="1">
:       <questionnaire project="peri20-" id="2.16.840.1.113883.2.4.6.10.90.900853" name="Ethnicgroup" displayName="Ethnic group" effectiveDate="2012-06-26T00:00:00" statusCode="draft"/>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms required sequence of terms to look for
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchQuestionnaireResponse($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?) as element(result) {
    adsearch:searchQuestionnaireResponse($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchQuestionnaireResponse($projectPrefix as xs:string?, $searchTerms as xs:string+, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $decorObjects   := 
        if (empty($projectPrefix)) then
            $get:colDecorData//f:QuestionnaireResponse
        else (
            collection(util:collection-name(art:getDecorByPrefix($projectPrefix, $version, $language)))//f:QuestionnaireResponse
        )
    let $maxResults         := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $luceneQuery        := adsearch:getSimpleLuceneQuery($searchTerms)
    let $luceneOptions      := adsearch:getSimpleLuceneOptions()
            
    let $results        := 
        if (count($searchTerms) = 1) then
            $decorObjects[f:id/@value = $searchTerms]
        else (
            $decorObjects[ft:query(f:questionnaire/@value,$luceneQuery,$luceneOptions)]
        )
    let $resultsById    := $results

    let $count          := count($resultsById)
    
    return
        <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
        {
            for $object in subsequence($resultsById, 1, $maxResults)
            let $decorOrigin    := collection(string-join(tokenize(util:collection-name($object), '/')[position() lt last()], '/'))/decor
            let $ident          := ($decorOrigin/project/@prefix, $object/ancestor::cacheme/@bbrident)[1]
            let $url            := ($object/ancestor::cacheme/@bbrurl)[1]
            let $displayName    := 'QuestionnaireResponse for ' || $object/f:questionnaire/@value
            order by string-length($displayName)
            return 
            element {$object/local-name()} {
                attribute id {$object/f:id/@value},
                if ($object/f:status) then attribute statusCode {$object/f:status/@value} else (),
                attribute project {$ident},
                attribute projectName {$decorOrigin/project/name[1]},
                attribute sortname {$displayName},
                if ($object/@ident) then () else if ($ident) then attribute ident {$ident} else (),
                if ($object/@url) then () else if ($url) then attribute url {$url} else (),
                for $node in $decorOrigin/project/name
                return
                    <name>{$node/@*, $displayName}</name>
                ,
                for $node in $decorOrigin/project/name
                return
                    <projectName>{$node/@*, $node/node()}</projectName>
            }
        }
        </result>
};

(:~
:   Returns all scenarios optionally filtered on project/id/name. 
:   Scenarios carry only attributes  and their normal name elements (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the scenario is in
:   Example output:
:   <result current="1" total="1">
:       <scenario project="peri20-" id="2.16.840.1.113883.2.4.3.11.60.90.77.3.1" effectiveDate="2011-01-28T00:00:00" statusCode="final">
:           <name language="nl-NL">Start Zorg bericht</name>
:       </scenario>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchScenario($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?) as element(result) {
    adsearch:searchScenario($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchScenario($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults         := if ($maxResults) then $maxResults else $adsearch:maxResults
    let $queryOnId          := if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then true() else false()
    
    let $resultsOnId        := 
        if ($queryOnId) then () else (
            if ($projectPrefix = '*') then
                $get:colDecorData//scenario[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')]][ancestor::scenarios]
            else
            if (empty($projectPrefix)) then
                $get:colDecorData//scenario[ends-with(@id, $searchTerms[1])][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::scenarios]
            else
            if (empty($version)) then
                $get:colDecorData//scenario[ends-with(@id, $searchTerms[1])][ancestor::decor/project[@prefix=$projectPrefix]][ancestor::scenarios]
            else (
                if ($get:colDecorVersion/decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]) then
                    $get:colDecorVersion//scenario[ends-with(@id, $searchTerms[1])][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]][ancestor::scenarios]
                else (
                    art:getDecorByPrefix($projectPrefix, $version, $language)//scenario[ends-with(@id, $searchTerms[1])][ancestor::scenario]
                )
            )
        )
        
    let $resultsOnName      :=
        if ($queryOnId) then () else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
            
            if ($projectPrefix = '*') then
                $get:colDecorData//scenario[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')]][ancestor::scenarios]
            else
            if (empty($projectPrefix)) then
                $get:colDecorData//scenario[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')][@repository = 'true']][ancestor::scenarios]
            else
            if (empty($version)) then
                $get:colDecorData//scenario[ft:query(name,$luceneQuery)][ancestor::decor[not(@private='true')] | ancestor::decor/project[@prefix = $projectPrefix]][ancestor::scenarios]
            else (
                if ($get:colDecorVersion/decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]) then
                    $get:colDecorVersion//scenario[ft:query(name,$luceneQuery)][ancestor::decor[@versionDate = $version][@language = $language]/project[@prefix=$projectPrefix]][ancestor::scenarios]
                else (
                    art:getDecorByPrefix($projectPrefix, $version, $language)//scenario[ft:query(name,$luceneQuery)][ancestor::scenario]
                )
            )
        )
    
    let $results         := $resultsOnName | $resultsOnId
    
    (:shortest match first:)
    let $results        :=
        for $r in $results
        order by string-length($r/name[1])
        return $r
    
    let $count          := count($results)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($results,1,$maxResults)
        let $displayName   := $object/name[1]
        order by string-length($displayName)
        return 
        element {$object/local-name()} {
            attribute project {$object/ancestor::decor/project/@prefix},
            attribute projectName {$object/ancestor::decor/project/name[1]},
            $object/(@* except (@project|@projectName)),
            $object/name
        }
    }
    </result>
};

(:~
:   Returns all transactions optionally filtered on project/id/name. 
:   Transactions carry only attributes  and their normal name elements (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the transaction is in
:   Example output:
:   <result current="1" total="1">
:       <transaction project="peri20-" type="group" id="2.16.840.1.113883.2.4.3.11.60.90.77.4.1" effectiveDate="2011-01-28T00:00:00" statusCode="final">
:           <name language="nl-NL">Berichten Start Zorg fase 1a naar registraties</name>
:       </transaction>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchTransaction($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?) as element(result) {
    adsearch:searchTransaction($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchTransaction($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $decorObjects   := 
        if (empty($projectPrefix)) then (
            if (empty($version)) then (
                $get:colDecorData//transaction[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])]
             ) else (
                $get:colDecorVersion//transaction[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
             )
        ) else
        if ($projectPrefix='*') then (
            if (empty($version)) then (
                $get:colDecorData//transaction[not(ancestor::decor[@private='true'])]
            ) else (
                $get:colDecorVersion//transaction[not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
            )
        ) else (
            if (empty($version)) then (
                $get:colDecorData//transaction[ancestor::decor/project[@prefix=$projectPrefix]]
            ) else (
                $get:colDecorVersion//transaction[ancestor::decor/project[@prefix=$projectPrefix]][ancestor::decor/@versionDate=$version]
            )
        )
    
    let $results        := 
        if (count($searchTerms)=0) then 
            $decorObjects
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
            if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
                $decorObjects[ends-with(@id, $searchTerms[1])]
            else if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
                $decorObjects[@id=$searchTerms[1]]
            else (
                $decorObjects[ft:query(name,$luceneQuery,$luceneOptions)]
            )
        )
    
    (:shortest match first:)
    let $results        :=
        for $r in $results
        let $displayName   := $r/name[1]
        order by string-length($displayName)
        return $r
    
    let $count          := count($results)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($results,1,$maxResults)
        let $displayName   := $object/name[1]
        order by string-length($displayName)
        return 
        element {$object/local-name()} {
            attribute project {$object/ancestor::decor/project/@prefix},
            attribute projectName {$object/ancestor::decor/project/name[1]},
            $object/(@* except (@project|@projectName)),
            $object/name
        }
    }
    </result>
};

(:~
:   Returns all value sets optionally filtered on project/id|ref/name. 
:   Value sets carry only attributes  and their normal name elements (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the value set is in
:   Example output:
:   <result current="3" total="3">
:       <valueSet project="peri20-" name="EthnicGroup" displayName="EthnicGroup" effectiveDate="2009-10-01T00:00:00" id="2.16.840.1.113883.2.4.11.3" statusCode="final"/>
:       <valueSet project="peri20-" id="2.16.840.1.113883.2.4.11.3" effectiveDate="2013-01-10T12:51:30" name="EthnicGroup" displayName="EthnicGroup" statusCode="final"/>
:       <valueSet project="peri20-" id="2.16.840.1.113883.2.4.11.3" effectiveDate="2014-05-19T14:35:30" statusCode="draft" name="EthnicGroup" displayName="EthnicGroup"/>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchValueset($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?) as element(result) {
    adsearch:searchValueset($projectPrefix, $searchTerms, $maxResults, (), ())
};
declare function adsearch:searchValueset($projectPrefix as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    let $decorObjects   := 
        if (empty($projectPrefix)) then (
            if (empty($version)) then (
                $get:colDecorData//valueSet[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])]
             ) else (
                $get:colDecorVersion//valueSet[ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])][ancestor::decor[@versionDate=$version]]
             )
        ) else
        if ($projectPrefix='*') then (
            if (empty($version)) then (
                $get:colDecorData//valueSet[not(ancestor::decor[@private='true'])]
            ) else (
                $get:colDecorVersion//valueSet[not(ancestor::decor[@private='true'])][ancestor::decor[@versionDate=$version]]
            )
        ) else (
            if (empty($version)) then (
                $get:colDecorData//valueSet[ancestor::decor/project[@prefix=$projectPrefix]]
            ) else (
                $get:colDecorVersion//valueSet[ancestor::decor/project[@prefix=$projectPrefix]][ancestor::decor[@versionDate=$version]]
            )
        )
    
    let $results        := 
        if (count($searchTerms)=0) then 
            $decorObjects
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
                if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
                    $decorObjects[ends-with(@id, $searchTerms[1])] | $decorObjects[ends-with(@ref, $searchTerms[1])] |
                    $decorObjects[descendant::*[ends-with(@codeSystem, $searchTerms[1])]]
                else 
                if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
                    $decorObjects[@id = $searchTerms[1]] | $decorObjects[@ref = $searchTerms[1]] |
                    $decorObjects[.//@codeSystem[. = $searchTerms[1]]]
                else (
                    $decorObjects[ft:query(@name,$luceneQuery)] | $decorObjects[ft:query(@displayName,$luceneQuery,$luceneOptions)]
                )
        )
    
    (:shortest match first:)
    let $results        :=
        for $r in $results
        let $displayName   := if ($r[@displayName]) then $r/@displayName else $r/@name
        order by string-length($displayName)
        return $r
    
    let $count          := count($results)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($results,1,$maxResults)
        let $displayName   := if ($object[@displayName]) then $object/@displayName else $object/@name
        order by string-length($displayName)
        return
        element {$object/local-name()} {
            $object/@*,
            if ($object[@displayName])  then () else (attribute displayName {$object/@name}),
            if ($object[@project])      then () else (attribute project {$object/ancestor::decor/project/@prefix}),
            if ($object[@projectName])  then () else (attribute projectName {$object/ancestor::decor/project/name[1]})
        }
    }
    </result>
};

(:~
:   Returns all OIDs optionally filtered on project/id|ref/name. 
:   Value sets carry only attributes  and their normal name elements (whatever was available in the db). 
:   To pin point them back to where they belong in ART, they also carry these attributes: 
:   @project - Project prefix the value set is in
:   Example output:
:   <result current="3" total="3">
:       <valueSet project="peri20-" name="EthnicGroup" displayName="EthnicGroup" effectiveDate="2009-10-01T00:00:00" id="2.16.840.1.113883.2.4.11.3" statusCode="final"/>
:       <valueSet project="peri20-" id="2.16.840.1.113883.2.4.11.3" effectiveDate="2013-01-10T12:51:30" name="EthnicGroup" displayName="EthnicGroup" statusCode="final"/>
:       <valueSet project="peri20-" id="2.16.840.1.113883.2.4.11.3" effectiveDate="2014-05-19T14:35:30" statusCode="draft" name="EthnicGroup" displayName="EthnicGroup"/>
:   </result>
:
:   @param $projectPrefix optional DECOR project prefix to search in. Special value '*' searches every non-private DECOR project. Empty searches every non-private Building Block Repository DECOR project. Any other value is assumed to be project/@prefix.
:   @param $searchTerms optional sequence of terms to look for. Returns every object if empty
:   @param $maxResults optional maximum number of results to return, defaults to $adsearch:maxResults
:   @param $version optional. Go to some archived release. Defaults to active data
:   @return resultset with max $maxResults results
:   @since 2014-06-06
:)
declare function adsearch:searchOID($searchRegistry as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $projectPrefix as xs:string?) as element(result) {
    adsearch:searchOID($projectPrefix, $searchTerms, $maxResults, $projectPrefix, (), ())
};
declare function adsearch:searchOID($searchRegistry as xs:string?, $searchTerms as xs:string*, $maxResults as xs:integer?, $projectPrefix as xs:string?, $version as xs:string?, $language as xs:string?) as element(result) {
    let $maxResults     := if ($maxResults) then $maxResults else $adsearch:maxResults
    
    (: Yields
        <oid oid="1.0.3166.1.2.2">
            <name language="en-US">ISO 3166 Alpha 2</name>
            <desc language="en-US">ISO 3166 2 alpha Landcodes</desc>
            <name language="nl-NL">ISO 3166 Alpha 2</name>
            <desc language="nl-NL">ISO 3166 2 alpha Landcodes</desc>
        </oid>
    :)
    let $registryObjects            :=
        if (empty($searchRegistry)) then
            $get:colOidsData//oid[ancestor::oidList]
        else (
            $get:colOidsData//oid[ancestor::oidList[@name = $searchRegistry]]
        )
    
    let $registryResults            := 
        if (count($searchTerms)=0) then 
            $registryObjects
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
                if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
                    $registryObjects[ends-with(@oid, $searchTerms)]
                else 
                if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
                    $registryObjects[@oid=$searchTerms] | $registryObjects[starts-with(@oid,concat($searchTerms,'.'))]
                else (
                    $registryObjects[ft:query(name | desc,$luceneQuery)]
                )
        )
    
    let $decorObjects               :=
        if (empty($projectPrefix)) then (
            if (empty($version)) then (
                $get:colDecorData//id[ancestor::ids][ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])]
             ) else (
                $get:colDecorVersion//id[ancestor::ids][ancestor::decor[@repository='true']][not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
             )
        ) else
        if ($projectPrefix='*') then (
            if (empty($version)) then (
                $get:colDecorData//id[ancestor::ids][not(ancestor::decor[@private='true'])]
            ) else (
                $get:colDecorVersion//id[ancestor::ids][not(ancestor::decor[@private='true'])][ancestor::decor/@versionDate=$version]
            )
        ) else (
            if (empty($version)) then (
                $get:colDecorData//id[ancestor::ids][ancestor::decor/project[@prefix=$projectPrefix]]
            ) else (
                $get:colDecorVersion//id[ancestor::ids][ancestor::decor/project[@prefix=$projectPrefix]][ancestor::decor/@versionDate=$version]
            )
        )
    
    (: Yields
        <id root="2.16.840.1.113883.2.4.6.3">
            <designation language="nl-NL" type="preferred" displayName="Burgerservicenummer BSN">Landelijk Nederlands Burger Service Nummer, dient ter identificatie van patiënten of zorgcliënten. Formaat: 9N, met voorloopnullen indien korter dan 9 cijfers. </designation>
            <designation language="en-US" type="preferred" displayName="Dutch National Person ID">Dutch National Person ID</designation>
        </id>
    :)
    let $projectResults            := 
        if (count($searchTerms)=0) then 
            $decorObjects
        else (
            let $luceneQuery    := adsearch:getSimpleLuceneQuery($searchTerms)
            let $luceneOptions  := adsearch:getSimpleLuceneOptions()
            return
                if (count($searchTerms)=1 and matches($searchTerms[1],'^\d+(\.\d+)*$')) then
                    $decorObjects[ends-with(@root, $searchTerms)]
                else 
                if (count($searchTerms)=1 and matches($searchTerms[1],'^[0-2](\.(0|[1-9][0-9]*))*$')) then
                    $decorObjects[@root=$searchTerms] | $decorObjects[starts-with(@root,concat($searchTerms,'.'))]
                else (
                    $decorObjects[ft:query(designation/@displayName,$luceneQuery)]
                )
        )
    
    (:shortest match first:)
    let $results        :=
        for $object in $registryResults | $projectResults
        let $oid           := $object/@oid | $object/@root
        (:let $displayName   := if ($object[name]) then $object/name else $object/*/@displayName:)
        group by $oid
        order by string-length((if ($object[name]) then $object/name else $object/*/@displayName)[1])
        return $object[1]
    
    let $count          := count($results)
    
return
    <result current="{if ($count<=$maxResults) then $count else $maxResults}" total="{$count}">
    {
        for $object in subsequence($results,1,$maxResults)
        let $displayName    := if ($object[name]) then $object/name else $object/*/@displayName
        order by string-length($displayName[1])
        return
        <id oid="{$object/@oid | $object/@root}">
        {
            if ($object[ancestor::oidList/@name]) 
            then attribute registry {$object/ancestor::oidList/@name} 
            else if ($projectPrefix = '*') 
            then attribute prefix {$object/ancestor::decor/project/@prefix}
            else attribute prefix {$projectPrefix}
            ,
            for $node in $object/name | $object/designation
            let $lang   := $node/@language
            group by $lang
            return
                <name language="{$lang}">{if ($node[@displayName]) then data(($node/@displayName)[1]) else data($node[1])}</name>
        }
        </id>
    }
    </result>
};


(:~
:   Internal helper function that recursively finds concepts inheriting from the current concept
:)
declare %private function adsearch:getConceptsThatInheritFromConcept($concept as element(concept), $results as element(concept)*) as item()* {
    (:
        When you search on id, it might find inherited concepts. 
        This does not happen for search by name as inherited concepts do not have a name
    :)
    let $concept        :=
        if ($concept[not(name)]) then (
            let $originalConcept    := art:getOriginalForConcept($concept)
            return
            element {$concept/name()} {
                if ($concept[@ref]) then (
                    $concept/(@ref|@flexibility),
                    $originalConcept/@statusCode
                ) else (
                    $concept/(@id|@effectiveDate|@statusCode)
                ),
                attribute type                  {$originalConcept/@type},
                attribute datasetId             {$concept/ancestor::dataset/@id},
                attribute datasetEffectiveDate  {$concept/ancestor::dataset/@effectiveDate},
                attribute datasetName           {$concept/ancestor::dataset/string-join((name[1],@versionLabel),' ')},
                attribute project               {$concept/ancestor::decor/project/@prefix},
                attribute projectName           {$concept/ancestor::decor/project/name[1]},
                attribute repository            {$concept/ancestor::decor/@repository='true'},
                attribute private               {$concept/ancestor::decor/@private='true'},
                $originalConcept/name,
                $concept/inherit
            }
        ) else ($concept)
    let $inheritingConcepts := $get:colDecorData//inherit[@ref = $concept/@id][not(ancestor::history)]
    let $inheritingConcepts := $inheritingConcepts[@effectiveDate=$concept/@effectiveDate]/parent::concept
    
    let $containingConcepts := $get:colDecorData//contains[@ref = $concept/@id][not(ancestor::history)]
    let $containingConcepts := $containingConcepts[@flexibility = $concept/@effectiveDate]/parent::concept
    
    let $currentResult  :=
        for $currentResultConcept in ($inheritingConcepts | $containingConcepts)
        let $currentResultConceptWithName :=
            element {$currentResultConcept/name()} {
                attribute id                    {$currentResultConcept/@id}, 
                attribute effectiveDate         {$currentResultConcept/@effectiveDate},
                attribute statusCode            {$currentResultConcept/@statusCode},
                attribute type                  {$concept/@type},
                attribute datasetId             {$currentResultConcept/ancestor::dataset/@id},
                attribute datasetEffectiveDate  {$currentResultConcept/ancestor::dataset/@effectiveDate},
                attribute datasetName           {$currentResultConcept/ancestor::dataset/string-join((name[1],@versionLabel),' ')},
                attribute project               {$currentResultConcept/ancestor::decor/project/@prefix},
                attribute projectName           {$currentResultConcept/ancestor::decor/project/name[1]},
                attribute repository            {$currentResultConcept/ancestor::decor/@repository='true'},
                attribute private               {$currentResultConcept/ancestor::decor/@private='true'},
                $concept/name,
                $currentResultConcept/inherit | $currentResultConcept/contains
            }
        return
            adsearch:getConceptsThatInheritFromConcept($currentResultConceptWithName,$results)
    
    return $concept | $results | $currentResult
};
