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

import module namespace adserver    = "http://art-decor.org/ns/art-decor-server" at "api-server-settings.xqm";
import module namespace get         = "http://art-decor.org/ns/art-decor-settings" at "../modules/art-decor-settings.xqm";
import module namespace art         = "http://art-decor.org/ns/art" at "../modules/art-decor.xqm";
import module namespace adsearch    = "http://art-decor.org/ns/decor/search" at "api-decor-search.xqm";
import module namespace vs          = "http://art-decor.org/ns/decor/valueset" at "api-decor-valueset.xqm";
import module namespace decor       = "http://art-decor.org/ns/decor" at "api-decor.xqm";

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

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

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

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   server local repositories that aren't private and either refer to or define the requested template(s).
:   See templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) for more info
:   
:   @param $id           - required. Identifier of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getTemplateById ($id as xs:string, $flexibility as xs:string?) as element(return) {
    let $templates  := $get:colDecorData//template[@id=$id] | $get:colDecorCache//template[@id=$id]
    let $templates  :=
        if ($flexibility castable as xs:dateTime) then (
            $templates[@effectiveDate = $flexibility]
        ) else (
            $templates[@effectiveDate = max($templates/xs:dateTime(@effectiveDate))]
        )
    return
    <return>
    {
        for $repository in $templates
        let $prefix    := $repository/ancestor::decor/project/@prefix
        let $url  := ($repository/ancestor::cacheme/@bbrurl, $templ:strDecorServicesURL)[1]
        group by $url, $prefix
        return
            <project ident="{$prefix}">
            {
                (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                for $ns at $i in art:getDecorNamespaces($repository/ancestor::decor)
                return 
                    attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                (:for $ns-prefix at $i in in-scope-prefixes($repository/ancestor::decor)[not(.=('xml'))]
                let $ns-uri := namespace-uri-for-prefix($ns-prefix, $repository/ancestor::decor)
                return
                    attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                ,
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage,
                $repository
            }
            </project>
    }
    </return>
};

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix.
:   See templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) for more info
:   
:   @param $id           - required. Identifier of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix - required. determines search scope. pfx- limits scope to this project only
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as item()) as element(return) {
    templ:getTemplateById($id, $flexibility, $decorOrPrefix, ())
};

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix and optionally from the archived version, but by default from the active project.
:   The template element has attribute @ident to indicate the project it holds data for, and carries the @ref attribute when the project has a 
:   reference to the requested template(s). If a project happens to refer to AND define the template there will be at least 2 template elements, 
:   with the project prefix in @ident, one carrying @ref and one carrying @id.
:
:   Inside the path /return/template[@id or @ref][@ident] is 1..* template element. This may is the actual template (@id or @ref) as-is with one 
:   exception: when the template originates from a different project than parent::template/@ident, the origin is added using template/@url and 
:   template/@ident.
:
:   Example output:
:   &lt;return>
:       &lt;template id="1.2.3.4" ident="vacc-">
:           &lt;!-- Reference found in project vacc- with template found in BBR ccda- -->
:           &lt;template id="1.2.3.4" name="AgeObservation" displayName="Age Observation" statusCode="active" effectiveDate="2011-01-01T00:00:00" url="http://art-decor.org/decor/services/" ident="ccda-">
:               ...
:           &lt;/template>
:           &lt;template id="1.2.3.4" name="AgeObservation" displayName="Age Observation" statusCode="active" effectiveDate="2012-02-02T00:00:00" url="http://art-decor.org/decor/services/" ident="ccda-">
:               ...
:           &lt;/template>
:           &lt;template ref="1.2.3.4" name="AgeObservation" displayName="Age Observation"/>
:           &lt;!-- Defined templates found in project vacc- -->
:           &lt;template id="1.2.3.4" name="AgeObservation" displayName="Age Observation" statusCode="review" effectiveDate="2013-03-03T00:00:00">
:               ...
:           &lt;/template>
:           &lt;template id="1.2.3.4" name="AgeObservation" displayName="Age Observation" statusCode="draft" effectiveDate="2014-04-04T00:00:00">
:               ...
:           &lt;/template>
:       &lt;/template>
:   &lt;/return>
:   
:   @param $id           - required. Identifier of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the template will come explicitly from that archived project version which is expected to be a compiled version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:)
declare function templ:getTemplateById($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as item(), $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 ()

let $internalrepositories       := templ:getDecorByPrefix($decorOrPrefix, $version, ())[1]
let $internalrepositories       := if ($internalrepositories) then $internalrepositories else $get:colDecorCache//decor[project/@prefix = $decorOrPrefix]
let $prefix                     := $internalrepositories/project/@prefix

(:let $internaltemplates          := $internalrepositories//template[@id = $id] | $internalrepositories//template[@ref = $id]:)
let $internaltemplates          :=
    if (empty($version)) then
        $get:colDecorData//template[@id = $id] | $get:colDecorData//template[@ref = $id]
    else (
        $get:colDecorVersion//template[@id = $id][ancestor::decor[@versionDate = $version]] | $get:colDecorVersion//template[@ref = $id][ancestor::decor[@versionDate = $version]]
    )
let $internaltemplates          := $internaltemplates[ancestor::decor/project[@prefix = $prefix]]
let $internaltemplates          :=
    if ($flexibility castable as xs:dateTime) then
        (:flexibility explicit timestamp -- return ref and matches only:)
        $internaltemplates[@ref] |
        $internaltemplates[@effectiveDate = $flexibility]
    else (
        $internaltemplates
    )
    
let $repositoryTemplateLists    :=
    <repositoryTemplateLists>
    {
        (:from the requested project, return template/(@id and @ref):)
        (: when retrieving templates from a compiled project, the @url/@ident they came from are on the template element
           reinstate that info on the repositoryTemplateList element so downstream logic works as if it really came from 
           the repository again.
        :)
        for $templates in $internaltemplates
        let $source     := $templates/concat(@url, @ident)
        group by $source
        return
            if (string-length($templates[1]/concat(@url, @ident)) = 0) then
                <repositoryTemplateList ident="{$templates[1]/ancestor::decor/project/@prefix}">
                {
                    (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                    for $ns at $i in art:getDecorNamespaces($templates[1]/ancestor::decor)
                    return 
                        attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                    (:for $ns-prefix at $i in in-scope-prefixes($templates[1]/ancestor::decor)[not(.=('xml'))]
                    let $ns-uri := namespace-uri-for-prefix($ns-prefix, $templates[1]/ancestor::decor)
                    return
                        attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                    ,
                    $templates
                }
                </repositoryTemplateList>
            else (
                <repositoryTemplateList url="{$templates[1]/@url}" ident="{$templates[1]/@ident}" referencedFrom="{$prefix}">
                {
                    (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                    for $ns at $i in art:getDecorNamespaces($templates[1]/ancestor::decor)
                    return 
                        attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                    (:for $ns-prefix at $i in in-scope-prefixes($templates[1]/ancestor::decor)[not(.=('xml'))]
                    let $ns-uri := namespace-uri-for-prefix($ns-prefix, $templates[1]/ancestor::decor)
                    return
                        attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                    ,
                    for $template in $templates
                    return
                        <template>{$template/(@* except (@url|@ident|@referencedFrom)), $template/node()}</template>
                }
                </repositoryTemplateList>
            )
    }
    {
        (:  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 ($flexibility castable as xs:dateTime and $internaltemplates[@id]) then () else
        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="{$templ:strDecorServicesURL}" ident="{$prefix}"/>:)
            return
                (:templ:getTemplateByIdFromBBR($basePrefix as xs:string, $id as xs:string, $prefix as xs:string, $externalrepositorylist as element()*, $bbrmap as item()?, $localbyid as element(template)*, $localbyref as element(template)*):)
                templ:getTemplateByIdFromBBR($prefix, $id, $prefix, $buildingBlockRepositories, (), (), ())/descendant-or-self::*[template[@id]]
        else ()
    }
    </repositoryTemplateLists>

let $allTemplates               :=
    if (empty($flexibility)) then
        (:flexibility empty -- return all ref+id:)
        $repositoryTemplateLists/repositoryTemplateList/template
    else if ($flexibility castable as xs:dateTime) then
        (:flexibility explicit timestamp -- return ref and matches only:)
        $repositoryTemplateLists/repositoryTemplateList/template[@ref] |
        $repositoryTemplateLists/repositoryTemplateList/template[@effectiveDate = $flexibility]
    else
        (:flexibility probably 'dynamic' -- return ref and newest only:)
        $repositoryTemplateLists/repositoryTemplateList/template[@ref] |
        $repositoryTemplateLists/repositoryTemplateList/template[@effectiveDate = max($repositoryTemplateLists/*/template/xs:dateTime(@effectiveDate))]
return
    <return>
    {
        for $templates in $allTemplates
        let $id := $templates/@id | $templates/@ref
        group by $id
        return (
            <template id="{$id}" ident="{$prefix}">
            {
                for $template in $templates
                let $tmed   := $template/@effectiveDate
                group by $tmed
                order by xs:dateTime($template[1]/@effectiveDate) descending
                return 
                    <template>
                    {
                        $template[1]/(@* except @*[contains(name(),'dummy-')]),
                        if ($template[1]/@url | $template[1]/@ident) then () else (
                            $template[1]/parent::*/@url,
                            $template[1]/parent::*/@ident
                        ),
                        if ($template[1]/../@*[contains(name(),'dummy-')]) then
                            $template[1]/../@*[contains(name(),'dummy-')]
                        else
                        if ($template[1]/ancestor::decor) then (
                            (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                            for $ns at $i in art:getDecorNamespaces($templates[1]/ancestor::decor)
                            return 
                                attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                        )
                        else (
                            for $ns-prefix at $i in in-scope-prefixes($template[1])[not(.=('xml'))]
                            let $ns-uri := namespace-uri-for-prefix($ns-prefix, $template[1])
                            order by $ns-prefix
                            return
                                attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}
                        )
                        ,
                        $template[1]/node()
                    }
                    </template>
            }
            </template>
        )
    }
    </return>
};

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   server local repositories that aren't private and either refer to or define the requested template(s).
:   See templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?) for more info
:   
:   @param $name                - required. Name of the template to retrieve. Matches template/@name
:   @param $flexibility         - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $useRegexMatching    - required. When true uses Lucene to find the template/@name match. When false uses case sensitive exact matching.
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getTemplateByName ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?)
:)
declare function templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean) as element(return) {
    let $templates  :=
        if ($useRegexMatching) then 
            $get:colDecorData//template[matches(@name, $name)] | $get:colDecorCache//template[matches(@name, $name)]
        else (
            $get:colDecorData//template[@name = $name] | $get:colDecorCache//template[@name = $name]
        )
    let $templates  :=
        if ($flexibility castable as xs:dateTime) then (
            $templates[@effectiveDate = $flexibility]
        )
        else (
            for $template in $templates[@id]
            let $id := $template/@id
            group by $id
            return (
                let $max := max($template/xs:dateTime(@effectiveDate))
                return
                $template[@effectiveDate = $max]
            )
        )
    return
    <return>
    {
        for $repository in $templates
        let $prefix     := $repository/ancestor::decor/project/@prefix
        let $url        := ($repository/ancestor::cacheme/@bbrurl, $templ:strDecorServicesURL)[1]
        group by $url, $prefix
        return
            <project ident="{$prefix}">
            {
                (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                for $ns at $i in art:getDecorNamespaces($repository/ancestor::decor)
                return 
                    attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                (:for $ns-prefix at $i in in-scope-prefixes($repository/ancestor::decor)[not(.=('xml'))]
                let $ns-uri := namespace-uri-for-prefix($ns-prefix, $repository[1]/ancestor::decor)
                return
                    attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                ,
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage,
                $repository
            }
            </project>
    }
    </return>
};

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix.
:   See templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?) for more info
:   
:   @param $name                - required. Name of the template to retrieve. Matches template/@name
:   @param $flexibility         - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix              - required. determines search scope. pfx- limits scope to this project only
:   @param $useRegexMatching    - required. When true uses Lucene to find the template/@name match. When false uses case sensitive exact matching.
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getTemplateByName ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?)
:)
declare function templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as item(), $useRegexMatching as xs:boolean) as element()* {
    templ:getTemplateByName($name, $flexibility, $decorOrPrefix, $useRegexMatching, ())
};

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix and optionally from the archived version, but by default from the active project.
:   The results are found as follows:
:   1. Get all matches based on template/@name from project
:   2. Use template/@id + param $flexibility from matches and return result of templ:getTemplateById($id, $flexibility, $decorOrPrefix, $version)
:   
:   @param $name                - required. Name of the template to retrieve. Matches template/@name
:   @param $flexibility         - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $useRegexMatching    - required. When true uses Lucene to find the template/@name match. When false uses case sensitive exact matching.
:   @param $version             - optional. if empty defaults to current version. if valued then the template will come explicitly from that archived project version which is expected to be a compiled version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as item(), $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 ()

let $decor                  := templ:getDecorByPrefix($decorOrPrefix, $version, ())[1]
let $prefix                 := $decor/project/@prefix

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

let $repositoryTemplateLists    :=
    <repositoryTemplateLists>
    {
        (:  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 template/@ref matching our id
        :)
        if (empty($version) and $internalrepositories[@ref]) then
            let $buildingBlockRepositories  := $decor/project/buildingBlockRepository[empty(@format)] |
                                               $decor/project/buildingBlockRepository[@format='decor']
            (:this is the starting point for the list of servers we already visited to avoid circular reference problems:)
            let $bbrList                    := <buildingBlockRepository url="{$templ:strDecorServicesURL}" ident="{$prefix}"/>
            return
                templ:getTemplateByNameFromBBR($prefix, $name, $prefix, $buildingBlockRepositories, $bbrList)[template[@id]]
        else ()
    }
    </repositoryTemplateLists>

let $templatesById := 
    if ($internalrepositories/(@id|@ref)) then
        (:if the project has our template[@id|@ref] by name, do not check any other ids in the repositories:)
        for $id in distinct-values($internalrepositories/@id | $internalrepositories/@ref | $repositoryTemplateLists/repositoryTemplateList/template[@id= ($internalrepositories/@id | $internalrepositories/@ref)]/@id)
        return
            templ:getTemplateById($id, $flexibility, $decorOrPrefix, $version)/template
    else (
        (:if the project has our valueSet[@id|@ref] by name, check any hit from the repositories:)
        for $id in distinct-values($internalrepositories/@id | $internalrepositories/@ref | $repositoryTemplateLists/repositoryTemplateList/template/@id)
        return
            templ:getTemplateById($id, $flexibility, $decorOrPrefix, $version)/template
    )

return
    <return>
    {
        $templatesById
    }
    </return>
};

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix.
:   See templ:getTemplateByRef ($idOrName as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) for more info
:   
:   @param $idOrName     - required. Identifier or Name of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix       - required. determines search scope. pfx- limits scope to this project only
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getTemplateByRef ($idOrName as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getTemplateByRef ($idOrName as xs:string, $flexibility as xs:string?, $decorOrPrefix as item()) as element()* {
    templ:getTemplateByRef($idOrName, $flexibility, $decorOrPrefix, ())
};

(:~
:   Return zero or more templates as-is wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix and optionally from the archived version, but by default from the active project.
:   See templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) and
:       templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) for more info
:   
:   @param $idOrName     - required. Identifier or Name of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the template will come explicitly from that archived project version which is expected to be a compiled version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:   @see templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getTemplateByRef ($idOrName as xs:string, $flexibility as xs:string?, $decorOrPrefix as item(), $version as xs:string?) as element()* {

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

return
    if (matches($idOrName,'^[\d\.]+$')) then
        templ:getTemplateById($idOrName,$flexibility,$decorOrPrefix,$version)
    else
        templ:getTemplateByName($idOrName,$flexibility,$decorOrPrefix,false(),$version)
};

(:~
:   Return zero or more expanded templates wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   server local repositories that aren't private and either refer to or define the requested template(s).
:   See templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) for more info
:   
:   @param $id           - required. Identifier of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getExpandedTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getExpandedTemplateById ($id as xs:string, $flexibility as xs:string?, $serialize as xs:boolean) as element() {
    let $templates  :=
        $get:colDecorData//template[@id = $id] | $get:colDecorCache//template[@id = $id]
    let $templates  :=
        if ($flexibility castable as xs:dateTime) then (
            $templates[@effectiveDate = $flexibility]
        ) else (
            $templates[@effectiveDate = max($templates/xs:dateTime(@effectiveDate))]
        )
    return
    <return>
    {
        for $repository in $templates
        let $prefix     := $repository/ancestor::decor/project/@prefix
        let $url        := ($repository/ancestor::cacheme/@bbrurl, $templ:strDecorServicesURL)[1]
        group by $url, $prefix
        return
            <project ident="{$prefix}">
            {
                (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                for $ns at $i in art:getDecorNamespaces($repository/ancestor::decor)
                return 
                    attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                (:for $ns-prefix at $i in in-scope-prefixes($repository/ancestor::decor)[not(.=('xml'))]
                let $ns-uri := namespace-uri-for-prefix($ns-prefix, $repository/ancestor::decor)
                return
                    attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                ,
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage,
                for $template in $repository[@id]
                return
                    templ:getExpandedTemplate($template, $prefix, (), (), $serialize)
            }
            </project>
    }
    </return>
};

(:~
:   Return zero or more templates wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   server local repositories that aren't private and either refer to or define the requested template(s).
:   The template element has attribute @ident to indicate the project it holds data for, and carries the @ref attribute when the project has a 
:   reference to the requested template(s). If a project happens to refer to AND define the template there will be at least 2 template elements, 
:   with the project prefix in @ident, one carrying @ref and one carrying @id.
:
:   Inside the path /return/template[@id or @ref][@ident] is 1..* template element. This may is the expanded template (@id or @ref) with additions:
:   - when the template originates from a different project than parent::template/@ident, the origin is added using template/@url and template/@ident.
:   - the item element is added at every relevant level where the template itself doesn't explicitly define it
:   - the include element will have the extra attributes tmid, tmname, tmdisplayName containing the id/name/displayName of the referred template (unless it cannot be found)
:   - the include element will have an extra attribute linkedartefactmissing='true' when the referred template cannot be found
:   - the include element will contain the template it refers (unless it cannot be found)
:   - the element[@contains] element will have the extra attributes tmid, tmname, tmdisplayName containing the id/name/displayName of the contained template (unless it cannot be found)
:   - the element[@contains] element will have an extra attribute linkedartefactmissing='true' when the contained template cannot be found
:   - the vocabulary[@valueSet] element will have the extra attributes vsid, vsname, vsdisplayName containing the id/name/displayName of the referred value set (unless it cannot be found)
:   - the vocabulary[@valueSet] element will have an extra attribute linkedartefactmissing='true' when the referred value set cannot be found
:   - at the bottom of the template there may be extra elements:
:     - If this is a template that is used in a transaction: &lt;representingTemplate ref="..." flexibility="..." model="..." sourceDataset="..." type="stationary" schematron="vacc-vacccda2"/>
:     - For every include[@ref] / element[@contains]        : &lt;ref type="contains|include" id="..." name="..." displayName="..." effectiveDate="...."/>
:     - For every project template that calls this template : &lt;ref type="dependency" id="..." name="..." displayName="..." effectiveDate="...."/>
:       &lt;staticAssociations>
:
:           - For every template association:
:           &lt;origconcept datasetId="..." datasetEffectiveDate="..." ref="..." effectiveDate="..." elementId="..." path="...">
:               &lt;concept id=".." effectiveDate="...">
:                   &lt;name language="nl-NL">...</name>
:                   &lt;desc language="nl-NL">...</desc>
:               &lt;/concept>
:           &lt;/origconcept>
:
:       &lt;/staticAssociations>
:
:   Example output:
:   &lt;return>
:       &lt;!-- Defined templates found in project vacc- -->
:       <template ref="1.3.6.1.4.1.19376.1.5.3.1.3.4" ident="vacc-">
:           &lt;!-- Defined templates found in project vacc- -->
:           &lt;template id="1.2.3.4" name="HistoryofPresentIllness" displayName="History of Present Illness Section" isClosed="false" effectiveDate="2012-06-01T00:00:00" statusCode="draft" versionLabel="" expirationDate="" officialReleaseDate="" url="http://art-decor.org/decor/services/" ident="ad3bbr-">
:               &lt;desc language="en-US">...</desc>
:               &lt;desc language="de-DE">...</desc>
:               &lt;classification type="cdasectionlevel">
:                   &lt;item label="HistoryofPresentIllness"/>
:               &lt;/classification>
:               &lt;context id="**">
:                   &lt;item label="HistoryofPresentIllness"/>
:               &lt;/context>
:               &lt;example>
:                   &lt;section classCode="DOCSECT" moodCode="EVN">
:                       &lt;templateId root="2.16.840.1.113883.2.11.10.103"/>
:                       &lt;code code="10164-2" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC" displayName="History of Present Illness"/>
:                       &lt;title>title</title>
:                       &lt;text>text</text>
:                   &lt;/section>
:               &lt;/example>
:               &lt;element name="hl7:section">
:                   &lt;item label="HistoryofPresentIllness"/>
:                   &lt;attribute name="classCode" value="DOCSECT" isOptional="true"/>
:                   &lt;attribute name="moodCode" value="EVN" isOptional="true"/>
:                   &lt;element name="hl7:templateId" minimumMultiplicity="1" maximumMultiplicity="1" datatype="II">
:                       &lt;item label="HistoryofPresentIllness"/>
:                       &lt;attribute name="root" value="1.3.6.1.4.1.19376.1.5.3.1.3.4"/>
:                   &lt;/element>
:                   &lt;element name="hl7:code" minimumMultiplicity="1" maximumMultiplicity="1" isMandatory="true" datatype="CD">
:                       &lt;item label="HistoryofPresentIllness"/>
:                       &lt;vocabulary code="10164-2" codeSystem="2.16.840.1.113883.6.1" vsid="" vsname="" vsdisplayName=""/>
:                   &lt;/element>
:                   &lt;element name="hl7:title" minimumMultiplicity="1" maximumMultiplicity="1" isMandatory="true" datatype="ST">
:                       &lt;item label="HistoryofPresentIllness"/>
:                   &lt;/element>
:                   &lt;element name="hl7:text" minimumMultiplicity="1" maximumMultiplicity="1" isMandatory="true" datatype="SD.TEXT">
:                       &lt;item label="HistoryofPresentIllness"/>
:                   &lt;/element>
:               &lt;/element>
:               &lt;-- Extra elements: -->
:               &lt;representingTemplate ref="1.3.6.1.4.1.19376.1.5.3.1.3.4" flexibility="2012-06-01T00:00:00" model="POCD_MT000040NL" sourceDataset="2.16.840.1.113883.3.1937.99.61.7.1.1" type="stationary" schematron="vacc-vacccda2"/>
:               &lt;ref type="contains" id="2.16.840.1.113883.3.1937.99.61.7.10.900222" name="ImmunizationActivity" displayName="CCDA Immunization Activity" effectiveDate="2014-03-07T11:56:16"/>
:               &lt;ref type="dependency" id="2.16.840.1.113883.3.1937.99.61.7.10.900220" name="ImmunizationSection" displayName="CCDA Immunization Section" effectiveDate="2014-03-07T11:45:34"/>
:               &lt;ref type="dependency" id="2.16.840.1.113883.3.1937.99.61.7.10.1" name="MinimalCDAVaccdocument" displayName="Minimal CDA vaccination document" effectiveDate="2013-10-10T00:00:00"/>
:               &lt;staticAssociations/>
:           &lt;/template>
:           &lt;template ref="1.3.6.1.4.1.19376.1.5.3.1.3.4" name="HistoryofPresentIllness" displayName="History of Present Illness Section"/>
:       &lt;/template>
:   &lt;/return>
:   
:   @param $id           - required. Identifier of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the template will come explicitly from that archived project version which is expected to be a compiled version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:)
declare function templ:getExpandedTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as item(), $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 ()

let $templates  := templ:getTemplateById($id, $flexibility, $decorOrPrefix, $version)

return
    <result>
    {
        for $templatesById in $templates/*
        return
            element {name($templatesById)}
            {
                $templatesById/@*,
                
                for $template in $templatesById/template
                return
                if ($template[@ref]) then
                    $template
                else
                    templ:getExpandedTemplate($template, $decorOrPrefix, $version, $language, $serialize)
            }
    }
    </result>
};

(:~
:   Return zero or more exapnded templates wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   server local repositories that aren't private and either refer to or define the requested template(s).
:   See templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?) for more info
:   
:   @param $name                - required. Name of the template to retrieve. Matches template/@name
:   @param $flexibility         - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $useRegexMatching    - required. When true uses Lucene to find the template/@name match. When false uses case sensitive exact matching.
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getExpandedTemplateByName ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $useRegexMatching as xs:boolean, $version as xs:string?)
:)
declare function templ:getExpandedTemplateByName ($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean, $serialize as xs:boolean) as element() {
    let $templates  :=
        if ($useRegexMatching) then 
            $get:colDecorData//template[matches(@name, $name)] | $get:colDecorCache//template[matches(@name, $name)]
        else (
            $get:colDecorData//template[@name = $name] | $get:colDecorCache//template[@name = $name]
        )
    let $templates  :=
        if ($flexibility castable as xs:dateTime) then (
            $templates[@effectiveDate = $flexibility]
        ) else (
            for $template in $templates[@id]
            let $id := $template/@id
            group by $id
            return (
                let $max := max($template/xs:dateTime(@effectiveDate))
                return
                $template[@effectiveDate = $max][1]
            )
        )
    return
    <return>
    {
        for $repository in $templates
        let $prefix     := $repository/ancestor::decor/project/@prefix
        let $url        := ($repository/ancestor::cacheme/@bbrurl, $templ:strDecorServicesURL)[1]
        group by $url, $ident
        return
            <project ident="{$prefix}">
            {
                (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                for $ns at $i in art:getDecorNamespaces($repository/ancestor::decor)
                return 
                    attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                (:for $ns-prefix at $i in in-scope-prefixes($repository/ancestor::decor)[not(.=('xml'))]
                let $ns-uri := namespace-uri-for-prefix($ns-prefix, $repository/ancestor::decor)
                return
                    attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                ,
                if ($repository[ancestor::cacheme]) then
                    attribute url {$repository[1]/ancestor::cacheme/@bbrurl}
                else (),
                $repository[1]/ancestor::decor/project/@defaultLanguage,
                for $template in $repository[@id]
                return
                    templ:getExpandedTemplate($template, $repository/ancestor::decor, (), (), $serialize)
            }
            </project>
    }
    </return>
};

(:~
:   Return zero or more expanded templates wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix and optionally from the archived version, but by default from the active project.
:   The results are found as follows:
:   1. Get all matches based on template/@name from project
:   2. Use template/@id + param $flexibility from matches and return result of templ:getTemplateById($id, $flexibility, $decorOrPrefix, $version)
:   
:   @param $name                - required. Name of the template to retrieve. Matches template/@name
:   @param $flexibility         - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix              - required. determines search scope. pfx- limits scope to this project only
:   @param $useRegexMatching    - required. When true uses Lucene to find the template/@name match. When false uses case sensitive exact matching.
:   @param $version             - optional. if empty defaults to current version. if valued then the template will come explicitly from that archived project version which is expected to be a compiled version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getExpandedTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getExpandedTemplateByName ($name as xs:string, $flexibility as xs:string?, $useRegexMatching as xs:boolean, $decorOrPrefix as item(), $version as xs:string?, $language as xs:boolean?, $serialize as xs:boolean) as element() {
let $argumentCheck :=
    if (string-length($name)=0) then 
        error(xs:QName('error:NotEnoughArguments'),'Argument name is required')
    else ()

let $templates := templ:getTemplateByName($name, $flexibility, $decorOrPrefix, $useRegexMatching, $version)

return
    <result>
    {
        for $templatesById in $templates/*
        return
            element {name($templatesById)}
            {
                $templatesById/@*,
                $templatesById/template[@ref],
                for $template in $templatesById/template[@id]
                return
                    templ:getExpandedTemplate($template, $decorOrPrefix, $version, $language, $serialize)
            }
    }
    </result>
};

(:~
:   Return zero or more expanded templates wrapped in a &lt;return/&gt; element, and subsequently inside one or more &lt;template&gt; elements for
:   the project as indicated by param $decorOrPrefix and optionally from the archived version, but by default from the active project.
:   See templ:getTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) and
:       templ:getTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?) for more info
:   
:   @param $idOrName     - required. Identifier or Name of the template to retrieve
:   @param $flexibility  - optional. null gets all versions, yyyy-mm-ddThh:mm:ss gets this specific version, anything that doesn't cast to xs:dateTime gets latest version
:   @param $decorOrPrefix       - required. determines search scope. pfx- limits scope to this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the template will come explicitly from that archived project version which is expected to be a compiled version
:   @return Matching templates in &lt;return/&gt; element
:   @since 2014-06-20
:   @see templ:getExpandedTemplateById ($id as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:   @see templ:getExpandedTemplateByName ($name as xs:string, $flexibility as xs:string?, $decorOrPrefix as xs:string, $version as xs:string?)
:)
declare function templ:getExpandedTemplateByRef ($idOrName as xs:string, $flexibility as xs:string?, $decorOrPrefix as item(), $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 ()

return
    if (matches($idOrName,'^[\d\.]+$')) then
        templ:getExpandedTemplateById($idOrName, $flexibility, $decorOrPrefix, $version, $language, $serialize)
    else
        templ:getExpandedTemplateByName($idOrName, $flexibility, false(), $decorOrPrefix, $version, $language, $serialize)
};

(:~
:   Expands a template with @id by recursively resolving all includes. Use templ:getExpandedTemplateById to resolve a template/@ref first. 
:                                
:   @param $template     - required. The template element with content to expand
:   @param $decorOrPrefix       - required. The origin of template. pfx- limits scope this project only
:   @param $version      - optional. if empty defaults to current version. if valued then the template will come explicitly from that archived project version which is expected to be a compiled version
:   @return The expanded template
:   @since 2013-06-14
:)
declare function templ:getExpandedTemplate($template as element(), $decorOrPrefix as item(), $version as xs:string?, $language as xs:string?, $serialize as xs:boolean) as element() {
(: all rules and terminologies of this project for later use :)
let $decor                          := templ:getDecorByPrefix($decorOrPrefix, $version, $language)[1]
let $decor                          := if ($decor) then $decor else ($get:colDecorCache//decor[project/@prefix = $decorOrPrefix])[1]
let $prefix                         := $decor/project/@prefix
let $language                       := ($decor/@language, $decor/project/@defaultLanguage)[1]

let $decorRules                     := $decor[1]/rules
let $decorTerms                     := $decor[1]/terminology
(: all transactions where this template is ref'ed :)
let $decorRepresentingTemplates     := $get:colDecorData//representingTemplate[@ref = $template/@id]
let $newestTemplateEffectiveDate    := string(max(($get:colDecorData//template[@id = $template/@id]/xs:dateTime(@effectiveDate) , $get:colDecorCache//template[@id = $template/@id]/xs:dateTime(@effectiveDate))))
let $currentTemplateIsNewest        := exists(
                                           $template[@effectiveDate = $newestTemplateEffectiveDate] |
                                           $template[@ref][empty(@flexibility)] |
                                           $template[@ref][@flexibility = 'dynamic'] |
                                           $template[@ref][@flexibility = $newestTemplateEffectiveDate]
                                       )

return
    <template>
    {
        $template/@id,
        $template/@name,
        $template/@displayName,
        $template/@effectiveDate,
        $template/@statusCode,
        $template/@versionLabel,
        $template/@expirationDate,
        $template/@officialReleaseDate,
        $template/@canonicalUri,
        attribute isClosed {$template/@isClosed = 'true'},
        $template/@lastModifiedDate,
        $template/@url,
        $template/@ident,
        $template/@*[contains(name(), 'dummy')],
        if ($serialize) then 
            art:serializeDescriptionNodes($template/desc, $decorRules/../project/name/@language)
        else ($template/desc)
    }
    {
        let $theitem := if ($template/item) then $template/item else <item label="{$template/@name}"/>
        
        for $node in $template/(* except desc)
        return
            templ:copyNodes($node, $theitem, 1, $decorRules, $decorTerms, $language, $serialize)
    }
    {
        (: get where the template is a representing template :)
        for $rtemp in $decorRepresentingTemplates
        let $rtempFlexibility   := 
            if ($rtemp[@flexibility castable as xs:dateTime]) then (
                (:starts with 4 digits, explicit dateTime:)
                string($rtemp/@flexibility)
            ) else (
                (:empty or dynamic:)
                $newestTemplateEffectiveDate
            )
        let $rtempFlexValue     :=
            if (string-length($rtemp/@flexibility)>0) then (
                $rtemp/@flexibility
            ) else (
                'dynamic'
            )
        let $rtempTransaction   := $rtemp/parent::transaction
        let $ident              := $rtemp/ancestor::decor/project/@prefix
        return
        if ($rtempFlexibility = $template/@effectiveDate) then
            <representingTemplate ref="{$rtemp/@ref}" flexibility="{$rtempFlexValue}" schematron="{$prefix}{$rtempTransaction/@label}" transactionId="{$rtempTransaction/@id}" transactionEffectiveDate="{$rtempTransaction/@effectiveDate}">
            {
                $rtemp/@sourceDataset,
                $rtemp/@sourceDatasetFlexibility,
                $rtempTransaction/@type,
                $rtempTransaction/@model,
                $rtempTransaction/@statusCode,
                $rtempTransaction/@versionLabel,
                attribute ident {$ident},
                $rtempTransaction/name
            }
            </representingTemplate>
        else()
    }
    {
        templ:createStaticAssociationElement($decorRules/templateAssociation[@templateId = $template/@id][@effectiveDate = $template/@effectiveDate], $language)
    }
    </template>
};

declare function templ:createStaticAssociationElement($templateAssociations as element(templateAssociation)*, $language as xs:string?) as element(staticAssociations) {
    <staticAssociations>
    {
        for $association in $templateAssociations/concept
        let $concept            := art:getConcept($association/@ref, $association/@effectiveDate)
        let $dataset            := $concept/ancestor::dataset
        let $originalConcept    := art:getOriginalForConcept($concept)
        (: 2018-12-19 Disabled for performance reasons :)
        let $refdisplay         := if (1=1) then attribute refdisplay {art:getNameForOID($association/@ref, $language, $concept/ancestor::decor)} else ()
        let $path               := 
            for $c in $concept/ancestor::concept
            let $originalConceptName    := art:getOriginalForConcept($c)
            return 
                if ($originalConceptName/name[@language=$language]) then $originalConceptName/name[@language=$language] else ($originalConceptName/name[1])
        return
            <origconcept>
            {
                $association/@ref,
                $association/@effectiveDate,
                $refdisplay,
                attribute conceptStatusCode {$concept/@statusCode},
                if ($concept/@expirationDate) then attribute conceptExpirationDate {$concept/@expirationDate} else (),
                if ($concept/@versionLabel) then attribute conceptVersionLabel {$concept/@versionLabel} else (),
                attribute datasetId {$dataset/@id},
                attribute datasetEffectiveDate {$dataset/@effectiveDate},
                attribute datasetStatusCode {$dataset/@statusCode},
                if ($dataset/@expirationDate) then attribute datasetExpirationDate {$dataset/@expirationDate} else (),
                if ($dataset/@versionLabel) then attribute datasetVersionLabel {$dataset/@versionLabel} else (),
                attribute datasetName {$dataset/name[1]},
                attribute ident {$concept/ancestor::decor/project/@prefix},
                $association/@elementId,
                attribute templateId {$association/../@templateId},
                attribute templateEffectiveDate {$association/../@effectiveDate},
                $association/@elementPath,
                attribute path {string-join($path,' / ')}
                ,
                $originalConcept/name,
                $originalConcept/desc,
                for $name in $dataset/name
                return
                    <datasetName>{$name/@*, $name/node()}</datasetName>
                ,
                for $name in $concept/ancestor::decor/project/name
                return
                    <projectName>{$name/@*, $name/node()}</projectName>
            }
            </origconcept>
    }
    </staticAssociations>
};

(:~
:   Returns template with elements as-is except:
:   - template/@isClosed gets an explicit value
:   - attribute is normalized as name/value pair
:   - element/include/vocabulary that could have @flexibility but don't get flexibility='dynamic'
:   - empty attributes on element/include/choice/attribute/classification/relationship/vocabulary/example are stripped
:)
declare function templ:getNormalizedTemplate($template as element()) as element(template) {
let $theitem := if ($template/item) then $template/item else <item label="{$template/@name}"/>
return
    <template>
    {
        $template/(@*[not(. = '')] except (@isClosed)),
        if ($template[@id]) then 
            attribute isClosed {$template/@isClosed='true'}
        else ()
    }
    {
        for $node in $template/*
        return
            templ:normalizeNodes($node)
    }
    </template>
};

(:
:   Rewrite all shorthands to the same name/value format to ease processing
:   Remove @isOptional if @probited='true'. Explicitly set @isOptional otherwise. (default value for @isOptional is 'false')
:)
declare function templ:normalizeAttributes($attributes as element(attribute)*) as element(attribute)* {
    let $mapping    :=
        <attributes>
            <attribute id="1" name="classCode" datatype="cs"/>
            <attribute id="2" name="contextConductionInd" datatype="bl"/>
            <attribute id="3" name="contextControlCode" datatype="cs"/>
            <attribute id="4" name="determinerCode" datatype="cs"/>
            <attribute id="5" name="extension" datatype="st"/>
            <attribute id="6" name="independentInd" datatype="bl"/>
            <attribute id="7" name="institutionSpecified" datatype="bl"/>
            <attribute id="8" name="inversionInd" datatype="bl"/>
            <attribute id="9" name="mediaType" datatype="st"/>
            <attribute id="10" name="moodCode" datatype="cs"/>
            <attribute id="11" name="negationInd" datatype="bl"/>
            <attribute id="12" name="nullFlavor" datatype="cs"/>
            <attribute id="13" name="operator" datatype="cs"/>
            <attribute id="14" name="qualifier" datatype="set_cs"/>
            <attribute id="15" name="representation" datatype="cs"/>
            <attribute id="16" name="root" datatype="uid"/>
            <attribute id="17" name="typeCode" datatype="cs"/>
            <attribute id="18" name="unit" datatype="cs"/>
            <attribute id="19" name="use" datatype="set_cs"/>
        </attributes>
    
    for $attribute in $attributes
    for $att  at $i in $attribute/(@name|@classCode|@contextConductionInd|@contextControlCode|@determinerCode|
                                         @extension|@independentInd|@institutionSpecified|@inversionInd|
                                         @mediaType|@moodCode|@negationInd|@nullFlavor|
                                         @operator|@qualifier|@representation|@root|
                                         @typeCode|@unit|@use)
    let $anme := if ($att[name()='name']) then $att/string() else ($att/name())
    let $aval := if ($att[name()='name']) then $attribute/@value/string() else ($att/string())
    let $adt  := 
        if ($attribute/@datatype[not(.='')]) 
        then ($attribute/@datatype) 
        else if ($mapping/attribute/@name=$anme) 
        then (attribute datatype {$mapping/attribute[@name=$anme]/@datatype}) 
        else ()
    return
        <attribute name="{$anme}">
        {
            if ($adt=('bl','cs','set_cs') and contains($aval,'|')) then (
                (:boolean can only be true|false so no need to re-specify, cs/set_cs should be done in vocabulary:)
            ) else if (string-length($aval)>0) then (
                attribute value {$aval}
            ) else ()
            ,
            if ($attribute/@prohibited='true') then
                attribute prohibited {'true'}
            else (
                attribute isOptional {$attribute/@isOptional='true'}
            )
            ,
            $adt
            ,
            (:could lead to duplicates...:)
            if ($i=1) then ($attribute/@id) else ()
            ,
            $attribute/@selected
            ,
            $attribute/(desc|item|comment()),
            if ($adt=('cs','set_cs')) then (
                if (contains($aval,'|')) then (
                    for $v in tokenize($aval,'\|')
                    return <vocabulary code="{normalize-space($v)}"/>
                ) else ()
                ,
                $attribute/vocabulary
            ) else (
                $attribute/constraint
            )
        }
        </attribute>
};

declare function templ:getTemplateList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $decorOrPrefix as item()?) as element()? {
    templ:getTemplateList($id, $name, $flexibility, $decorOrPrefix, (), false(), ())
};

declare function templ:getTemplateList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $decorOrPrefix as item()?, $version as xs:string?) as element()? {
    templ:getTemplateList($id, $name, $flexibility, $decorOrPrefix, $version, false(), ())
};

declare function templ:getTemplateList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $decorOrPrefix as item()?, $version as xs:string?, $classified as xs:boolean) as element()? {
    templ:getTemplateList($id, $name, $flexibility, $decorOrPrefix, $version, false(), ())
};

declare function templ:getTemplateList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $decorOrPrefix as item()?, $version as xs:string?, $classified as xs:boolean, $treetype as xs:string?) as element()? {
let $treetype           :=
    if (empty($id) and empty($name)) then ($templ:TREETYPELIMITED) else 
    if ($treetype = ($templ:TREETYPELIMITED, $templ:TREETYPEMARKED, $templ:TREETYPELIMITEDMARKED)) then $treetype 
    else $templ:TREETYPELIMITED

let $decor              := templ:getDecorByPrefix($decorOrPrefix, $version, ())[1]
let $prefix             := $decor/project/@prefix
let $bbrs               :=
    $decor/project/buildingBlockRepository[empty(@format)] |
    $decor/project/buildingBlockRepository[@format='decor']

let $projecttemplates   :=
    if ((empty($id) and empty($name)) or $treetype != $templ:TREETYPELIMITED) then
        $decor/rules/template
    else if (string-length($id) gt 0) then
        $decor/rules/template[@id = $id] | $decor/rules/template[@ref = $id]
    else if (string-length($name) gt 0) then
        $decor/rules/template[@name = $name]
    else
        $decor/rules/template[@id = $id] | $decor/rules/template[@ref = $id] | $decor/rules/template[@name = $name]

let $reffedtemplates    :=
    if (empty($version)) then 
        for $template in $projecttemplates[@ref]
        let $ref := $template/@ref
        group by $ref
        return
            templ:getTemplateByIdFromBBR($decor/project/@prefix, $ref, $decor/project/@prefix, $bbrs, (), (), ())
    else ()

let $starttemplate  := (
    let $starttemplate  := 
        if (string-length($id) gt 0) then
            $projecttemplates[@id[.=$id] | @name[.=$name]] | templ:getTemplateByIdFromBBR($prefix, $id, $prefix, $bbrs, (), (), ())//template[@id]
        else
        if (string-length($name) gt 0) then
            $projecttemplates[@id[.=$id] | @name[.=$name]] | templ:getTemplateByNameFromBBR($prefix, $name, $prefix, $bbrs, ())//template[@id]
        else ()
    return (
        if ($flexibility castable as xs:dateTime) 
        then $starttemplate[@effectiveDate = $flexibility] 
        else $starttemplate[@effectiveDate = max($starttemplate/xs:dateTime(@effectiveDate))]
    )
)

let $templateChain      := if ($decor) then $starttemplate | templ:getTemplateChain($decor, $starttemplate, map:merge(map:entry(concat($starttemplate/@id, $starttemplate/@effectiveDate), ''))) else $starttemplate
let $templateChainArr   := $templateChain/concat(@id,@effectiveDate)

let $templateSet        :=
    if ($treetype=($templ:TREETYPELIMITED, $templ:TREETYPEMARKED)) then (
        $projecttemplates | $reffedtemplates//template[@id]
    )
    else (
        for $template in $projecttemplates | $reffedtemplates//template[@id]
        return
            $template[@ref = $templateChain/@id] | $template[concat(@id,@effectiveDate) = $templateChainArr]
    )
(:let $decorRepresentingTemplates := $decorRules/ancestor::decor[project/@prefix=$prefix]/scenarios//representingTemplate[@ref]:)

let $result             :=
    for $projectTemplatesById in $templateSet
    let $idref                  := $projectTemplatesById/@id | $projectTemplatesById/@ref
    group by $idref
    return (
        let $representingTemplates          := $decor//representingTemplate[@ref = $idref][ancestor::decor/project[@prefix = $prefix]]
        let $newestTemplateEffectiveDate    := string(max($projectTemplatesById/xs:dateTime(@effectiveDate)))
        let $newestTemplate                 := $projectTemplatesById[@effectiveDate = $newestTemplateEffectiveDate]
        let $templateSet                    :=
            for $template in $projectTemplatesById
            let $currentTemplateEffectiveDate   := $template/@effectiveDate
            let $lookingForNewest               := $template[@effectiveDate = $newestTemplateEffectiveDate]
            group by $currentTemplateEffectiveDate
            order by $template[1]/@effectiveDate descending
            return (
                <template uuid="{util:uuid()}">
                {
                    $template[1]/(@* except (@displayName|@ident|@url)),
                    attribute displayName {
                        if ($newestTemplate and $template[@ref]) then (
                            if ($newestTemplate/@displayName) then ($newestTemplate/@displayName)[1] else ($newestTemplate/@name)[1]
                        )
                        else (
                            if ($template/@displayName) then ($template/@displayName)[1] else ($template/@name)[1]
                        )
                    },
                    if ($template[1]/ancestor-or-self::*[@url]) then (
                        $template[1]/ancestor-or-self::*[@url][1]/@url,
                        $template[1]/ancestor-or-self::*[@url][1]/@ident
                    ) else ($template[1]/@ident | $template[1]/@url),
                    $template[1]/classification
                }
                {
                    for $rtemp in $representingTemplates[@flexibility = $currentTemplateEffectiveDate]
                    return
                        <representingTemplate>{$rtemp/@ref, $rtemp/@flexibility}</representingTemplate>
                    ,
                    if ($lookingForNewest) then
                        for $rtemp in $representingTemplates[not(@flexibility castable as xs:dateTime)]
                        return
                            <representingTemplate>{$rtemp/@ref}</representingTemplate>
                    else ()
                }
                {
                    if ($starttemplate) then
                        if ($treetype = $templ:TREETYPELIMITEDMARKED) then (
                            <ref type="template">{$starttemplate[1]/(@id | @name | @displayName | @effectiveDate)}</ref>
                        ) else
                        if ($template[@effectiveDate = $templateChain[@id=$template/@id]/@effectiveDate]) then
                            <ref type="template">{$starttemplate[1]/(@id | @name | @displayName | @effectiveDate)}</ref>
                        else ()
                    else ()
                }
                </template>
            )
        
        return
        <template uuid="{util:uuid()}">
        {
            $idref,
            ($templateSet[@id], $templateSet)[1]/(@* except (@uuid | @id | @ref | @class)),
            attribute class {($templateSet[@id][1]/classification/@type, 'notype')[1]}
            
        }
        {
            $templateSet
        }
        </template>
    )
    
let $startDisplayName   := if ($starttemplate/@displayName) then $starttemplate/@displayName else $starttemplate/@name
return
    <return id="{$id}" name="{$name}" displayName="{$startDisplayName}" flexibility="{$flexibility}" treetype="{$treetype}" starttemplate="{exists($starttemplate)}">
    {
        if ($classified) then (
            let $schemaTypes        := art:getDecorTypes()//TemplateTypes/enumeration
            
            (: this is the code for a classification based hierarchical tree view :)
            (:get templates with and without classification:)
            for $templateSets in $result
            let $class  := $templateSets/@class
            group by $class
            order by count($schemaTypes[@value = $class]/preceding-sibling::enumeration)
            return
            <class uuid="{util:uuid()}" type="{$class}">
            {
                for $label in $schemaTypes[@value = $class]/label
                return
                    <label language="{$label/@language}">{$label/text()}</label>
            }
            {
                for $templateSet in $templateSets
                order by lower-case(($templateSet/@displayName, $templateSet/@name)[1])
                return
                    $templateSet
            }
            </class>
        )
        else (
            for $r in $result
            order by count($r//representingTemplate)=0, lower-case(($r/template[1]/@displayName, $r/template[1]/@name)[1])
            return $r
        )
    }
    </return>
};

declare %private function templ:getTemplateByIdFromBBR($basePrefix as xs:string, $id as xs:string, $prefix as xs:string, $externalrepositorylist as element()*, $bbrmap as item()?, $localbyid as element(template)*, $localbyref as element(template)*) as element()* {

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

let $templatesById          := if ($localbyid) then $localbyid else $get:colDecorData//template[@id = $id]
let $templatesByRef         := if ($localbyref) then $localbyref else $get:colDecorData//template[@ref = $id]

let $return                 := 
    for $repository in $externalrepositorylist
    let $repourl                := $repository/@url
    let $repoident              := $repository/@ident
    let $hasBeenProcessedBefore := 
        if (empty($bbrmap)) then false() else (
            map:contains($bbrmap, concat($repoident, $repourl)) or map:contains($bbrmap, concat($basePrefix, $templ:strDecorServicesURL))
        )
    (:let $newmap                 :=
        if (empty($bbrmap)) then $newmap else map:merge(($bbrmap, $newmap)):)
    (: map:merge does not exist in eXist 2.2 :)
    let $newmap                 :=
        if (empty($bbrmap)) then $newmap else map:merge((
            for $k in map:keys($bbrmap) return map:entry($k, ''),
            for $k in map:keys($newmap) return if (map:contains($bbrmap, $k)) then () else map:entry($k, '')
        ))
    return
        if ($hasBeenProcessedBefore) then () else (
            <repositoryTemplateList url="{$repourl}" ident="{$repoident}" referencedFrom="{$prefix}">
            {
                (: if this buildingBlockRepository resolves to our own server, then get it directly from the db. :)
                if ($repository[@url = $templ:strDecorServicesURL]) then (
                    let $templates          := $templatesById[ancestor::decor/project[@prefix = $repoident]]
                    let $templatesRef       := $templatesByRef[ancestor::decor/project[@prefix = $repoident]]
                    
                    let $project            := ($templates/ancestor::decor | $templatesRef/ancestor::decor)[1]
                    let $bbrs               :=
                        $project//buildingBlockRepository[empty(@format)] |
                        $project//buildingBlockRepository[@format='decor']
                    return 
                    if ($project) then (
                        attribute projecttype {'local'},
                        (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                        for $ns at $i in art:getDecorNamespaces($project)
                        return 
                            attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                        (:for $ns-prefix at $i in in-scope-prefixes($project)[not(.=('xml'))]
                        let $ns-uri := namespace-uri-for-prefix($ns-prefix, $project)
                        return
                            attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                        ,
                        $templates,
                        if (not($templates) or $templatesRef) then (
                            templ:getTemplateByIdFromBBR($basePrefix, $id, $repoident, $bbrs, $newmap, $templatesById, $templatesByRef)
                        ) else ()
                    ) else ()
                )
                (: check cache first and do a server call as last resort. :)
                else (
                    let $cachedProject          := $get:colDecorCache//cacheme[@bbrurl = $repourl][@bbrident = $repoident]
                    let $cachedTemplates        := $cachedProject//template[@id = $id] | $cachedProject//template[@ref = $id]
                    let $bbrs               :=
                        $cachedProject//buildingBlockRepository[empty(@format)] |
                        $cachedProject//buildingBlockRepository[@format='decor']
                    return
                    if ($cachedProject) then (
                        attribute projecttype {'cached'},
                        if (empty($cachedTemplates)) then () else (
                            (: <ns uri="urn:hl7-org:v3" prefix="hl7" default="{$ns-default='hl7'}" readonly="true"/> :)
                            for $ns at $i in art:getDecorNamespaces(($cachedTemplates[@id], $cachedTemplates[@ref])[1]/ancestor::decor)
                            return 
                                attribute {QName($ns/@uri,concat($ns/@prefix,':dummy-', if ($ns/@default = 'true') then 'default' else $i))} {$ns/@uri}
                            (:for $ns-prefix at $i in in-scope-prefixes(($cachedTemplates[@id], $cachedTemplates[@ref])[1])[not(.=('xml'))]
                            let $ns-uri := namespace-uri-for-prefix($ns-prefix, ($cachedTemplates[@id], $cachedTemplates[@ref])[1])
                            return
                                attribute {QName($ns-uri,concat($ns-prefix,':dummy-',$i))} {$ns-uri}:)
                        )
                        ,
                        $cachedTemplates[@id],
                        if (not($cachedTemplates) or $cachedTemplates[@ref]) then (
                            templ:getTemplateByIdFromBBR($basePrefix, $id, $repoident, $bbrs, $newmap, $templatesById, $templatesByRef)
                        ) else ()
                    )
                    (: http call as last resort. :)
                    else (
                        try {
                            let $service-uri    := concat($repourl,'RetrieveTemplate?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 $templates      := $server-response//template/template[@id]
                            return (
                                attribute projecttype {'remote'},
                                $templates
                            )
                            (:doc(xs:anyURI(concat($repository/@url,'RetrieveTemplate?format=xml&amp;prefix=',$repository/@ident, '&amp;id=', $id)))/rules/template[@id]:)
                        }
                        catch * {()}
                    )
                )
            }
            </repositoryTemplateList>
        )
return
    $return
};

declare %private function templ:getTemplateByNameFromBBR($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[@ident=$repoident] | $repository[@ident=$basePrefix]
    let $hasBeenProcessedBefore := $hasBeenProcessedBefore[@url=($repourl,$templ:strDecorServicesURL)]
    return
        if ($hasBeenProcessedBefore) then () else (
            <repositoryTemplateList url="{$repourl}" ident="{$repoident}" referencedFrom="{$prefix}">
            {
                (: if this buildingBlockRepository resolves to our own server, then get it directly from the db. :)
                if ($repository[@url = $templ:strDecorServicesURL]) then (
                    let $project    := templ:getDecorByPrefix($repoident, (), ())
                    let $bbrs               :=
                        $project//buildingBlockRepository[empty(@format)] |
                        $project//buildingBlockRepository[@format='decor']
                    return 
                    if ($project) then (
                        let $templates          := $project//template[@name=$name]
                        
                        return (
                            $templates[@id],
                            if (not($templates) or $templates[@ref]) then (
                                templ:getTemplateByNameFromBBR($basePrefix, $name, $repoident, $bbrs, ($bbrList | $externalrepositorylist))
                            ) 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 $cachedTemplates    := $cachedProject//template[@name = $name]
                        let $bbrs               :=
                            $cachedProject//buildingBlockRepository[empty(@format)] |
                            $cachedProject//buildingBlockRepository[@format='decor']
                        return (
                            $cachedTemplates[@id],
                            if (not($cachedTemplates) or $cachedTemplates[@ref]) then (
                                templ:getTemplateByNameFromBBR($basePrefix, $name, $repoident, $bbrs, ($bbrList | $externalrepositorylist))
                            ) else ()
                        )
                    )
                    (: http call as last resort. :)
                    else (
                        try {
                            let $service-uri    := concat($repourl,'RetrieveTemplate?format=xml&amp;prefix=',$repoident, '&amp;name=', $name)
                            let $requestHeaders := 
                                <http:request method="GET" href="{$service-uri}">
                                    <http:header name="Content-Type" value="text/xml"/>
                                    <http:header name="Cache-Control" value="no-cache"/>
                                    <http:header name="Max-Forwards" value="1"/>
                                </http:request>
                            let $server-response := http:send-request($requestHeaders)
                            let $server-check    :=
                                if ($server-response[1]/@status='200') then () else (
                                    error(QName('http://art-decor.org/ns/error', 'RetrieveError'), concat('Server returned HTTP status: ',$server-response[1]/@status, ' for URI ''',$service-uri,''' with body ''',string-join($server-response[2], ' '),''''))
                                )
                            let $templates      := $server-response//template/template[@id]
                            return (
                                attribute projecttype {'remote'},
                                $templates
                            )
                            (:doc(xs:anyURI(concat($repository/@url,'RetrieveTemplate?format=xml&amp;prefix=',$repository/@ident, '&amp;name=', $name)))/rules/template[@id]:)
                        }
                        catch * {()}
                    )
                )
            }
            </repositoryTemplateList>
        )
    
};

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

(:~
:   Returns template element as-is except:
:   - attribute is normalized as name/value pair
:   - element/include/vocabulary that could have @flexibility but don't get flexibility='dynamic'
:   - empty attributes on element/include/choice/attribute/classification/relationship/vocabulary/example are stripped
:)
declare %private function templ:normalizeNodes($tnode as element()) as element()* {
    let $elmname := name($tnode)
    return
        if ($elmname='element') then (
            element element {
                $tnode/@*[not(. = '')],
                if ($tnode[string-length(@contains)>0][string-length(@flexibility)=0]) then (
                    attribute flexibility {'dynamic'}
                ) else (),
                for $node in $tnode/*
                return
                    templ:normalizeNodes($node)
            }
        ) else if ($elmname='include') then (
            element include {
                $tnode/@*[not(. = '')],
                if ($tnode[string-length(@ref)>0][string-length(@flexibility)=0]) then (
                    attribute flexibility {'dynamic'}
                ) else (),
                for $node in $tnode/*
                return
                    templ:normalizeNodes($node)
            }
        ) else if ($elmname='choice') then (
            element choice {
                $tnode/@*[not(. = '')],
                for $node in $tnode/*
                return
                    templ:normalizeNodes($node)
            }
        ) else if ($elmname='attribute') then (
            for $s in templ:normalizeAttributes($tnode)
            return
            element attribute {
                $s/@*[not(. = '')],
                for $node in $s/*
                return
                    templ:normalizeNodes($node)
            }
        ) else if ($elmname='classification') then (
            element classification {
                $tnode/@*[not(. = '')],
                $tnode/node()
            }
        ) else if ($elmname='relationship') then (
            element relationship {
                $tnode/@*[not(. = '')],
                $tnode/node()
            }
        ) else if ($elmname='vocabulary') then (
            element vocabulary {
                $tnode/@*[not(. = '')],
                if ($tnode[string-length(@valueSet)>0][string-length(@flexibility)=0]) then (
                    attribute flexibility {'dynamic'}
                ) else (),
                $tnode/node()
            }
        ) else if ($elmname='example') then (
            element example {
                $tnode/@*[not(. = '')],
                $tnode/node()
            }
        ) else (
            (:desc,item,text,constraint,example,let,defineVariable,assert,report,...:)
            $tnode
        )
};
(:=======================:)

declare function templ:getRepositoryAndBBRTemplateList ($searchTerms as xs:string*, $prefix as xs:string?) as element()* {

    (: all rules on this server (repositories only) and in this server's cache :)
    let $repository := 
        if (string-length($prefix) gt 0) then 
            $get:colDecorData//decor[project/@prefix = $prefix]/rules | $get:colDecorCache//decor[project/@prefix = $prefix]/rules
        else (
            $get:colDecorData//decor[@repository = 'true']/rules | $get:colDecorCache//decor/rules
        )

    (: get project templates with an @id, either by searching a specific one with searchTerms or all :)
    let $templateSet    :=
        if (empty($searchTerms)) then 
            $repository/template[@id]
        else (
            adsearch:searchTemplatesInRuleSet($searchTerms, $repository, $adsearch:maxResults)/*
        )
    
    let $result         :=
        for $projectTemplatesById in $templateSet
        let $idref                  := $projectTemplatesById/@id | $projectTemplatesById/@ref
        group by $idref
        return (
            let $newestTemplateEffectiveDate    := string(max($projectTemplatesById/xs:dateTime(@effectiveDate)))
            let $newestTemplate                 := $projectTemplatesById[@effectiveDate = $newestTemplateEffectiveDate]
            let $templateSet                    :=
                for $template in $projectTemplatesById
                let $currentTemplateEffectiveDate   := $template/@effectiveDate
                let $lookingForNewest               := $template[@effectiveDate = $newestTemplateEffectiveDate]
                group by $currentTemplateEffectiveDate
                order by $template[1]/@effectiveDate descending
                return (
                    <template uuid="{util:uuid()}">
                    {
                        $template[1]/(@* except (@displayName|@ident|@url)),
                        attribute displayName {
                            if ($newestTemplate and $template[@ref]) then (
                                if ($newestTemplate/@displayName) then ($newestTemplate/@displayName)[1] else ($newestTemplate/@name)[1]
                            )
                            else (
                                if ($template/@displayName) then ($template/@displayName)[1] else ($template/@name)[1]
                            )
                        },
                        if ($template[1]/ancestor-or-self::*[@url]) then (
                            $template[1]/ancestor-or-self::*[@url][1]/@url,
                            $template[1]/ancestor-or-self::*[@url][1]/@ident
                        ) else ($template[1]/@ident | $template[1]/@url),
                        $template[1]/desc,
                        $template[1]/classification
                    }
                    </template>
                )
            return
                <template uuid="{util:uuid()}">
                {
                    $idref,
                    ($templateSet[@id], $templateSet)[1]/(@* except (@uuid | @id | @ref | @class)),
                    attribute class {($templateSet[@id][1]/classification/@type, 'notype')[1]}
                    
                }
                {
                    $templateSet
                }
                </template>
        )
    
    return
        <rules>
        {
             for $r in $result
             order by lower-case($r/@displayName)
             return $r
        }
        </rules>

};

(:
    Accepts 1 or more templates, although typically only 1 (TODO: change $e to element(template)? instead of element()* ?), and will use parameters 
    $minimumMultiplicity, $maximumMultiplicity, $isMandatory and $conformance to override all top level element of those templates.
    
    This is applicable to template content that is called via <include/> where the card/conf of the include overrides corresponding properties of the 
    top level template element it is calling.
    
    Overrides are applicable to <element/>, <include/> and <choice/>. If any of the card/conf parameters has no value then the value (if any) of the 
    top level element applies.
:)
declare %private function templ:cardconfs1element ($e as element()*, $minimumMultiplicity as xs:string?, $maximumMultiplicity as xs:string?, $isMandatory as xs:string?, $conformance as xs:string? ) as element()* {
    for $child in $e/(element|include|choice|attribute|assert|report|let|defineVariable|constraint)
    let $minimumMultiplicity := if (string-length($minimumMultiplicity)=0) then ($child/@minimumMultiplicity) else ($minimumMultiplicity)
    let $maximumMultiplicity := if (string-length($maximumMultiplicity)=0) then ($child/@maximumMultiplicity) else ($maximumMultiplicity)
    let $isMandatory := if (string-length($isMandatory)=0) then ($child/@isMandatory) else ($isMandatory)
    let $conformance := if (string-length($conformance)=0) then ($child/@conformance) else ($conformance)
    return
        if ($child/self::element | $child/self::include) then (
            element {$child/name()} {
                $child/(@* except (@minimumMultiplicity|@maximumMultiplicity|@isMandatory|@conformance)),
                if (string-length($minimumMultiplicity)>0) then attribute minimumMultiplicity {$minimumMultiplicity} else (),
                if (string-length($maximumMultiplicity)>0) then attribute maximumMultiplicity {$maximumMultiplicity} else (),
                if (string-length($isMandatory)>0) then attribute isMandatory {$isMandatory} else (),
                if (string-length($conformance)>0) then attribute conformance {$conformance} else (),
                $child/node()
            }
        ) else if ($child/self::choice) then (
            element {$child/name()} {
                $child/(@* except (@minimumMultiplicity|@maximumMultiplicity|@isMandatory|@conformance)),
                if (string-length($minimumMultiplicity)>0) then attribute minimumMultiplicity {$minimumMultiplicity} else (),
                if (string-length($maximumMultiplicity)>0) then attribute maximumMultiplicity {$maximumMultiplicity} else (),
                $child/node()
            } 
        ) else (
            $child
        )
};

declare %private function templ:artefactMissing($what as xs:string, $ref as xs:string?, $flexibility as xs:string?, $decorRules as element()*, $decorTerms as element()*) as element()? {
    let $flexibility    := if ($flexibility castable as xs:dateTime) then $flexibility else 'dynamic'
    return
    (: returns <artefact missing="true"... if artefact cannot be found in decor project :)
    if ($what='template' and string-length($ref)>0) then (
        let $tmp        := templ:getTemplateByRef($ref, $flexibility, $decorRules/ancestor::decor/project/@prefix)/template/template[@id]
        
        return
            <artefact missing="{empty($tmp)}" id="{$tmp[1]/(@id|@ref)}">{$tmp[1]/(@* except (@id|@ref))}</artefact>
            
    ) else if ($what='valueSet' and string-length($ref)>0) then (
        (: find out effectiveDate for this value set :)
        let $tmp        := vs:getValueSetByRef($ref, $flexibility, $decorRules/ancestor::decor/project/@prefix, false())/*/valueSet[@id]
        
        return
            <artefact missing="{empty($tmp)}" id="{$tmp[1]/(@id|@ref)}">{$tmp[1]/(@* except (@id|@ref))}</artefact>
    ) else (
        <artefact missing="false" id="" name="" displayName="" effectiveDate="" statusCode=""/>
    )
};

declare %private function templ:copyNodes($tnode as element(), $item as element(), $nesting as xs:integer, $decorRules as element()*, $decorTerms as element()*, $language as xs:string?, $serialize as xs:boolean) as element()* {
    let $elmname    := name($tnode)
    let $theitem    := if ($tnode/item) then $tnode/item else ($item)
    let $language   := if (empty($language)) then $decorRules/ancestor::decor/project/@defaultLanguage else ($language)
    return
        if ($nesting > 30) then (
            (: too deeply nested, raise error and give up :)
            element templateerror {
                attribute {'type'} {'nesting'}(:,
                $tnode:)
            }
        ) else 
        if ($elmname='include') then (
            let $searchTemplate     := 
                templ:getTemplateByRef(
                    $tnode/@ref,
                    if ($tnode[@flexibility]) then $tnode/@flexibility else ('dynamic'),
                    $decorRules/ancestor::decor/project/@prefix)/template/template[@id][@effectiveDate]
            let $recent             := $searchTemplate[1]
            (:let $artefact         := templ:artefactMissing('template', $tnode/@ref, $tnode/@flexibility, $decorRules, $decorTerms):)
            return
            element include {
                $tnode/(@* except (@tmid|@tmname|@tmdisplayName|@tmeffectiveDate|@tmexpirationDate|@tmstatusCode|@tmversionLabel|@linkedartefactmissing|@url|@ident)),
                if (empty($recent)) then () else (
                    attribute {'tmid'} {$recent/@id},
                    attribute {'tmname'} {$recent/@name},
                    attribute {'tmdisplayName'} {if ($recent[@displayName]) then $recent/@displayName else $recent/@name},
                    if ($recent/@effectiveDate) then attribute {'tmeffectiveDate'} {$recent/@effectiveDate} else (),
                    if ($recent/@expirationDate) then attribute {'tmexpirationDate'} {$recent/@expirationDate} else (),
                    if ($recent/@statusCode) then attribute {'tmstatusCode'} {$recent/@statusCode} else (),
                    if ($recent/@versionLabel) then attribute {'tmversionLabel'} {$recent/@versionLabel} else (),
                    $recent/@url,
                    $recent/@ident
                )
                ,
                attribute {'linkedartefactmissing'} {empty($recent)},
                $tnode/text(),
                if ($serialize) then 
                    for $desc in $tnode/desc
                    return
                        art:serializeNode($desc)
                else ($tnode/desc)
                ,
                $theitem,
                for $s in $tnode/(* except (desc|item))
                return
                templ:copyNodes($s, $theitem, $nesting+1, $decorRules, $decorTerms, $language, $serialize)
                ,
                let $recentcardconf := templ:cardconfs1element ($recent, $tnode/@minimumMultiplicity, $tnode/@maximumMultiplicity, $tnode/@isMandatory, $tnode/@conformance)
                let $theitem        := if ($recent/item) then $recent/item else <item label="{$recent/@name}"/>
                for $t in $recentcardconf
                return
                templ:copyNodes($t, $theitem, $nesting+1, $decorRules, $decorTerms, $language, $serialize),
                if (empty($recent)) then () else (
                    templ:createStaticAssociationElement($decorRules/templateAssociation[@templateId = $recent/@id][@effectiveDate = $recent/@effectiveDate], $language)
                )
            }
        ) else 
        if ($elmname=('desc','constraint','purpose','copyright')) then (
            if ($serialize) then 
                art:serializeNode($tnode)
            else ($tnode)
        ) else 
        if ($elmname=('example')) then (
            let $tnode := <example type="{($tnode/@type, 'neutral')[1]}">{$tnode/@caption, $tnode/node()}</example>
            return 
                if ($serialize) then art:serializeNode($tnode) else ($tnode)
        ) else 
        if ($elmname=('report','assert')) then (
            $tnode
        ) else 
        if ($elmname='relationship') then (
            let $ref            := $tnode/@template[string-length()>0]
            let $searchTemplate := 
                if (empty($ref)) then () else (
                    templ:getTemplateById(
                        $ref,
                        if ($tnode[@flexibility]) then $tnode/@flexibility else ('dynamic'),
                        $decorRules/ancestor::decor/project/@prefix)/template/template[@id][@effectiveDate]
                )
            let $recent         := $searchTemplate[1]
            return
            element relationship {
                $tnode/(@* except (@tmid|@tmname|@tmdisplayName|@tmeffectiveDate|@tmexpirationDate|@tmstatusCode|@tmversionLabel|@linkedartefactmissing|@url|@ident)),
                if (empty($ref)) then () else (
                    if (empty($recent)) then () else (
                        attribute {'tmid'} {$recent/@id},
                        attribute {'tmname'} {$recent/@name},
                        attribute {'tmdisplayName'} {if ($recent[@displayName]) then $recent/@displayName else $recent/@name},
                        if ($recent/@effectiveDate) then attribute {'tmeffectiveDate'} {$recent/@effectiveDate} else (),
                        if ($recent/@expirationDate) then attribute {'tmexpirationDate'} {$recent/@expirationDate} else (),
                        if ($recent/@statusCode) then attribute {'tmstatusCode'} {$recent/@statusCode} else (),
                        if ($recent/@versionLabel) then attribute {'tmversionLabel'} {$recent/@versionLabel} else (),
                        $recent/@url,
                        $recent/@ident
                    )
                    ,
                    attribute {'linkedartefactmissing'} {empty($recent)}
                ),
                $tnode/*
            }
        ) else 
        if ($elmname='vocabulary') then (
            let $artefact := templ:artefactMissing('valueSet', $tnode/@valueSet, $tnode/@flexibility, $decorRules, $decorTerms)
            return
            element vocabulary {
                $tnode/(@* except (@vsid|@vsname|@vsdisplayName|@vseffectiveDate|@vsexpirationDate|@vsstatusCode|@vsversionLabel|@linkedartefactmissing|@url|@ident)),
                if ($tnode/@valueSet) then (
                    attribute {'vsid'} {$artefact/@id},
                    attribute {'vsname'} {$artefact/@name},
                    attribute {'vsdisplayName'} {if ($artefact[@displayName]) then $artefact/@displayName else $artefact/@name},
                    if ($artefact/@effectiveDate) then attribute {'vseffectiveDate'} {$artefact/@effectiveDate} else (),
                    if ($artefact/@expirationDate) then attribute {'vsexpirationDate'} {$artefact/@expirationDate} else (),
                    if ($artefact/@statusCode) then attribute {'vsstatusCode'} {$artefact/@statusCode} else (),
                    if ($artefact/@versionLabel) then attribute {'vsversionLabel'} {$artefact/@versionLabel} else (),
                    $artefact/@url,
                    $artefact/@ident,
                    attribute {'linkedartefactmissing'} {$artefact/@missing}
                ) else (),
                $tnode/*
            }
        ) else 
        if ($elmname='attribute') then (
            for $s in templ:normalizeAttributes($tnode)
            return
            element attribute {
                $s/@*,
                for $t in $s/* 
                return templ:copyNodes($t, $theitem, $nesting+1, $decorRules, $decorTerms, $language, $serialize)
            }
        ) else 
        if ($elmname=('let','defineVariable','classification','publishingAuthority','context','text')) then (
            $tnode
        ) else (
            let $artefact := if ($tnode/@contains) then templ:artefactMissing('template', $tnode/@contains, $tnode/@flexibility, $decorRules, $decorTerms) else ()
            return
            element {$elmname} {
                $tnode/(@* except (@tmid|@tmname|@tmdisplayName|@tmeffectiveDate|@tmstatusCode|@linkedartefactmissing)),
                if ($tnode/@contains) then (
                    attribute {'tmid'} {$artefact/@id},
                    attribute {'tmname'} {$artefact/@name},
                    attribute {'tmdisplayName'} {$artefact/@displayName},
                    if ($artefact/@effectiveDate) then attribute {'tmeffectiveDate'} {$artefact/@effectiveDate} else (),
                    if ($artefact/@expirationDate) then attribute {'tmexpirationDate'} {$artefact/@expirationDate} else (),
                    if ($artefact/@statusCode) then attribute {'tmstatusCode'} {$artefact/@statusCode} else (),
                    if ($artefact/@versionLabel) then attribute {'tmversionLabel'} {$artefact/@versionLabel} else (),
                    $artefact/@url,
                    $artefact/@ident,
                    attribute {'linkedartefactmissing'} {$artefact/@missing}
                ) else (),
                $tnode/text(),
                if ($serialize) then 
                    for $desc in $tnode/desc
                    return
                        art:serializeNode($desc)
                else ($tnode/desc)
                ,
                if ($elmname = ('template', 'item')) then () else $theitem,
                for $s in $tnode/(* except (desc|item))
                return
                templ:copyNodes($s, $theitem, $nesting+1, $decorRules, $decorTerms, $language, $serialize)
            }
        )
};

declare function templ:getDependendies($version as element(), $self as xs:string?, $latest as xs:string?, $level as xs:int, $decorRules as element()*) as element()* {
    
    let $ref            := $version/@id/string()
    let $flexibility    := $version/@effectiveDate/string()
    let $lvtextinclude  := if ($level=1) then 'include' else 'dependency'
    let $lvtextcontains := if ($level=1) then 'contains' else 'dependency'

    let $dep :=
        if ($level > 5) then
            (: too deeply nested, raise error and give up :)
            ()
        else if (($ref = $self) and ($level > 1)) then
            (: ref to myself in a level greater than 1, give up, you are done :)
            ()
        else (
            (: all include statements anywhere with @ref and @flexibility :)
            (: get all referenced templates pointed to by @ref and @flexibility :)
            for $chain in $decorRules//include[@ref=$ref][@flexibility=$flexibility]/ancestor::template
            let $bbrurl     := $chain/ancestor::cacheme/@bbrurl
            let $bbrident   := ($chain/ancestor::cacheme/@bbrident, $chain/ancestor::decor/project/@prefix)[1] 
            return (
                <ref type="{$lvtextinclude}" prefix="{$bbrident}" flexibility="{$flexibility}">
                {
                    $chain/(@* except (@type|@prefix|@flexibility)),
                    if ($chain/@ident) then ()  else if ($bbrident) then attribute ident {$bbrident} else (),
                    if ($chain/@url) then () else if ($bbrurl) then attribute url {$bbrurl} else ()
                }
                </ref>,
                templ:getDependendies($chain, $self, $latest, $level+1, $decorRules)
            )
            ,
            (: all elements anywhere with @contains and @flexibility :)
            (: get all referenced templates pointed to by @contains and @flexibility :)
            for $chain in $decorRules//element[@contains=$ref][@flexibility=$flexibility]/ancestor::template
            let $bbrurl     := $chain/ancestor::cacheme/@bbrurl
            let $bbrident   := ($chain/ancestor::cacheme/@bbrident, $chain/ancestor::decor/project/@prefix)[1] 
            return (
                <ref type="{$lvtextcontains}" prefix="{$bbrident}" flexibility="{$flexibility}">
                {
                    $chain/(@* except (@type|@prefix|@flexibility)),
                    if ($chain/@ident) then ()  else if ($bbrident) then attribute ident {$bbrident} else (),
                    if ($chain/@url) then () else if ($bbrurl) then attribute url {$bbrurl} else ()
                }
                </ref>,
                templ:getDependendies($chain, $self, $latest, $level+1, $decorRules)
            )
            ,
            (: all 
                   include statements anywhere with @ref but no @flexibility 
                   or elements with @contains but no @flexibility 
                   BUT ONLY in case $flexibility = $latest, i.e. dynamic binding 
            :)
            if ($flexibility = $latest) then (
                (: get all referenced templates pointed to by @ref :)
                let $ttis   := 
                    for $i in $decorRules//include[@ref = $ref]
                    let $rf := $i/@ref
                    let $fl := if ($i[@flexibility]) then $i/@flexibility else 'dynamic'
                    group by $rf, $fl
                    return if ($fl = 'dynamic') then $i else ()
                let $ttis   := $ttis/ancestor::template
                
                for $chain in $ttis
                let $bbrurl     := $chain/ancestor::cacheme/@bbrurl
                let $bbrident   := ($chain/ancestor::cacheme/@bbrident, $chain/ancestor::decor/project/@prefix)[1] 
                return (
                    <ref type="{$lvtextinclude}" prefix="{$bbrident}" flexibility="dynamic">
                    {
                        $chain/(@* except (@type|@prefix|@flexibility)),
                        if ($chain/@ident) then ()  else if ($bbrident) then attribute ident {$bbrident} else (),
                        if ($chain/@url) then () else if ($bbrurl) then attribute url {$bbrurl} else ()
                    }
                    </ref>,
                    templ:getDependendies($chain, $self, $latest, $level+1, $decorRules)
                ),
                
                (: get all referenced templates pointed to by @contains :)
                let $ttis   := 
                    for $i in $decorRules//element[@contains = $ref]
                    let $rf := $i/@contains
                    let $fl := if ($i[@flexibility]) then $i/@flexibility else 'dynamic'
                    group by $rf, $fl
                    return if ($fl = 'dynamic') then $i else ()
                let $ttis   := $ttis/ancestor::template
                
                for $chain in $ttis
                let $bbrurl     := $chain/ancestor::cacheme/@bbrurl
                let $bbrident   := ($chain/ancestor::cacheme/@bbrident, $chain/ancestor::decor/project/@prefix)[1] 
                return (
                    <ref type="{$lvtextcontains}" prefix="{$bbrident}" flexibility="dynamic">
                    {
                        $chain/(@* except (@type|@prefix|@flexibility)),
                        if ($chain/@ident) then ()  else if ($bbrident) then attribute ident {$bbrident} else (),
                        if ($chain/@url) then () else if ($bbrurl) then attribute url {$bbrurl} else ()
                    }
                    </ref>,
                    templ:getDependendies($chain, $self, $latest, $level+1, $decorRules)
                )
            )
            else ()
        )
            
     return $dep
     
};

declare function templ:templateUses($version as element(), $self as xs:string?, $level as xs:int, $decorRules as element()*, $decorOrPrefix as item()?) as element(uses)* {

    let $prefix     := if ($decorOrPrefix instance of element()) then $decorOrPrefix/project/@prefix else $decorOrPrefix
    let $uses       := 
        if ($level > 7) then
            (: too deeply nested, raise error and give up :)
            ()
        else if (($version/@id = $self) and ($level > 1)) then
            (: ref to myself in a level greater than 1, give up, you are done :)
            ()
        else
            (
                for $lc in ($version//element[@contains] | $version//include[@ref])
                let $xid            := if ($lc/name() = 'element') then $lc/@contains else $lc/@ref
                let $xtype          := if ($lc/name() = 'element') then 'contains' else 'include'
                let $flex           := if ($lc[@flexibility]) then $lc/@flexibility else 'dynamic'
                (:
                let $searchTemplate := $decorRules/template[@id=$xid] | $decorRules/template[@name=$xid]
                let $effd           := 
                    if (matches($flex,'^\d{4}')) 
                    then $flex
                    else max($searchTemplate/xs:dateTime(@effectiveDate))
                let $templ          := $searchTemplate[@effectiveDate=$effd]
                :)
                let $tmp            := templ:getTemplateByRef($xid, $flex, $decorOrPrefix)/template/template
                let $effd           := 
                    if ($flex castable as xs:dateTime) 
                    then $flex
                    else max($tmp/xs:dateTime(@effectiveDate))
                let $templ          := $tmp[@effectiveDate=$effd]
                return
                    if ($templ) then
                        for $x in $templ
                        return
                            <uses type="{$xtype}" prefix="{$prefix}" flexibility="{$flex}">{$x/(@* except (@type|@prefix|@flexibility|@*[contains(name(), 'dummy-')]))}</uses>
                    else (
                            <uses type="{$xtype}" prefix="{$prefix}" id="{$xid}" name="" displayName="" effectiveDate="{$flex}"/>
                    )
            )
    return
        (:
        for $d in distinct-values($uses/@id)
        return $uses[@id = $d][1]
        :)
        $uses

};

declare function templ:getDependenciesAndUsage($ref as xs:string, $effectiveDate as xs:string?, $decorOrPrefix as item()) {

let $decor          := templ:getDecorByPrefix($decorOrPrefix, (), ())
let $prefix         := $decor/project/@prefix
let $templateall    := (templ:getTemplateByRef($ref, '', $decorOrPrefix)/template/template[@id=$ref])[1]
let $templatest     := max($templateall/xs:dateTime(@effectiveDate))
let $template       := (templ:getTemplateByRef($ref, $effectiveDate, $decorOrPrefix)/template/template[@id=$ref][@effectiveDate=$effectiveDate])[1]

let $result         :=
    if ($template) then (
        (: get dependencies in THIS project :)
        templ:getDependendies($template, $template/@id, $templatest, 1, $decor/rules),
        (: get dependencies in other projects than this one :)
        templ:getDependendies($template, $template/@id, $templatest, 1, $get:colDecorData//decor/rules[ancestor::decor/project[not(@prefix = $prefix)]]),
        (: get dependencies in cache :)
        templ:getDependendies($template, $template/@id, $templatest, 1, $get:colDecorCache//decor/rules),
        (: get uses :)
        templ:templateUses($template, $template/@id, 1, $decor/rules, $decorOrPrefix),
        (: template dependencies, automagically determined :)
        for $template in $decor/rules
        return 
            if ($template[@effectiveDate = $effectiveDate]) then () else if ($template[@effectiveDate]) then (
                <ref type="template">
                {
                    $template/(@* except (@type|@*[contains(name(), 'dummy-')]))
                }
                </ref> 
            ) else ()
    ) else ()

let $transactionReferences  :=
    for $ref in $result[self::ref]
    let $tmid           := $ref/@id
    let $tmed           := $ref/@effectiveDate
    group by $tmid, $tmed
    return 
        if ($tmed = max(($get:colDecorData//template[@id = $tmid]/xs:dateTime(@effectiveDate), $get:colDecorCache//template[@id = $tmid]/xs:dateTime(@effectiveDate)))) then
            for $tm in $get:colDecorData//representingTemplate[@ref = $tmid] | $get:colDecorCache//representingTemplate[@ref = $tmid]
            let $tm2ed  := $tm/@flexibility[. castable as xs:dateTime]
            return
                if (empty($tm2ed)) then $tm/ancestor::transaction[1] else
                if ($tm2ed = $tmed) then $tm/ancestor::transaction[1] else ()
        else (
            $get:colDecorData//representingTemplate[@ref = $tmid][@flexibility = $tmed] | 
            $get:colDecorCache//representingTemplate[@ref = $tmid][@flexibility = $tmed]
        )/ancestor::transaction[1]
    
(:<representingTemplate ref="2.16.840.1.113883.2.4.3.11.60.55.10.6" flexibility="dynamic" schematron="acutezorg-cda-amb-seh" transactionId="2.16.840.1.113883.2.4.3.11.60.55.4.23" transactionEffectiveDate="2015-11-05T14:53:34" sourceDataset="2.16.840.1.113883.2.4.3.11.60.55.1.1" type="initial" statusCode="draft" ident="acutezorg-">
    <name language="nl-NL">Sturen Ambulanceoverdracht SEH</name>
</representingTemplate>:)
let $transactionReferences  :=
    for $transaction in $transactionReferences
    let $trident    := $transaction/ancestor::decor/project/@prefix
    let $trurl      := $transaction/ancestor::decor/@deeplinkprefixservices
    return
        <representingTemplate>
        {
            $transaction/representingTemplate/@ref,
            $transaction/representingTemplate/@flexibility[. castable as xs:dateTime],
            attribute schematron {concat($trident, $transaction/@label)},
            attribute transactionId {$transaction/@id},
            attribute transactionEffectiveDate {$transaction/@effectiveDate},
            $transaction/representingTemplate/@sourceDataset,
            $transaction/representingTemplate/@sourceDatasetFlexibility,
            $transaction/@type,
            $transaction/@model,
            $transaction/@statusCode,
            $transaction/@versionLabel,
            attribute ident {$trident},
            if ($trurl) then attribute url {$trurl} else (),
            $transaction/name
        }
        </representingTemplate>

return (
    $transactionReferences,
    (: uniquify result re/ type of ref element, type, id and effectiveDate, sort project ref's first :)
    for $d in $result
    let $id := concat(name($d), $d/@type, $d/@id, $d/@effectiveDate)
    group by $id
    order by (not($d[1]/@prefix=$prefix))
    return
        $d[1]
)
};

(:recursive function to get all templates hanging off from the current template. has circular reference protection. Does not return the start templates :)
declare function templ:getTemplateChain($decorOrPrefix as item(), $startTemplates as element(template)*, $resultsmap as item()) as element(template)* {
    for $ref in ($startTemplates//element/@contains | $startTemplates//include/@ref)
    let $refflex   := concat($ref, if ($ref/../@flexibility) then $ref/../@flexibility else ('dynamic'))
    group by $refflex
    return (
        let $flexibility    := if ($ref[1]/../@flexibility) then $ref[1]/../@flexibility else ('dynamic')
        let $template       := templ:getTemplateByRef($ref[1], $flexibility, $decorOrPrefix)/template/template[@id]
        let $newkey         := concat($template/@id, $template/@effectiveDate)
        return
        if ($template) then
            if (map:contains($resultsmap, $newkey)) then () else (
                (: map:merge does not exist in eXist 2.2 :)
                (:let $newmap         := map:merge(($resultsmap, map:merge(map:entry($newkey, '')))):)
                let $newmap         :=
                    map:merge((
                        for $k in map:keys($resultsmap) return map:entry($k, ''),
                        map:entry($newkey, '')
                    ))
                
                return $template | templ:getTemplateChain($decorOrPrefix, $template, $newmap)
            )
        else ()
    )
};

declare %private function templ:getDecorByPrefix($decorOrPrefix as item(), $decorVersion as xs:string?, $language as xs:string?) as element(decor)* {
    typeswitch ($decorOrPrefix)
    case element(decor) return $decorOrPrefix
    default return art:getDecorByPrefix($decorOrPrefix, $decorVersion, $language)
};

(: ===== EXAMPLE GENERATION ===== :)

declare function templ:processElement($element as element(),$decor as element()) as element()? {
    if ($element/@selected and not($element/@conformance='NP')) then
        (: strip namespace prefix and predicate from element/@name :)
        let $elmpfx     := substring-before($element/@name,':')
        let $elmns      := if ($elmpfx=('hl7','cda','',())) then () else if ($elmpfx='art') then 'urn:art-decor:example' else (namespace-uri-for-prefix($elmpfx,$decor))
        let $elmname    := replace($element/@name,'^([^:]+:)?([^\s\[]+)\s*(\[.*)?','$2')
        (: in older (hand created) templates people may have used double declarations in one attribute element, e.g.
            <attribute classCode="OBS" moodCode="EVN"/>
           Also we might encounter a mix of name/value versus shorthands. Normalize before processing to name/value
        :)
        let $attributes := templ:normalizeAttributes($element/attribute[@selected][not(@prohibited='true')])
        return
        element {if ($elmns) then QName($elmns,concat($elmpfx,':',$elmname)) else $elmname} 
        {
            if ($element[@originalType='ANY']) then
                (: poor mans solution for INT.POS, AD.NL and other flavors. Should check
                    DECOR-supported-datatypes.xml
                :)
                attribute xsi:type {tokenize($element/@datatype,'\.')[1]}
            else()
            ,
            (: check normalized element/attribute :)
            for $att in $attributes
            let $anme := $att/@name/string()
            group by $anme
            return
                if ($anme = 'xsi:type') then (
                    if ($element[@originalType='ANY']) then () else 
                    if ($element[@datatype]) then
                        attribute xsi:type {tokenize($element/@datatype,'\.')[1]}
                    else if ($att[@value]) then
                        attribute xsi:type {tokenize($att/@value,'\.')[1]}
                    else (
                        attribute xsi:type {'--TODO--'}
                    )
                )
                else if ($att[1]/@value) then 
                    attribute {$anme} {$att[1]/@value}
                else if ($att[1]/vocabulary) then (
                    if ($att[1]/vocabulary[1]/@valueSet) then
                        let $vsref     := $att[1]/vocabulary[1]/@valueSet
                        let $vsflex    := $att[1]/vocabulary[1]/@flexibility
                        let $valueSet  := vs:getExpandedValueSetByRef($vsref, $vsflex, $decor/project/@prefix, (), (), false())
                        let $firstCode := ($valueSet//conceptList/concept[not(@type=('D','A'))])[1]
                        return (
                            attribute {$anme} {$firstCode/@code}
                        )
                    else if ($att[1]/vocabulary[1]/@code) then (
                        attribute {$anme} {$att[1]/vocabulary[1]/@code}
                    )
                    else ()
                )
                else if ($att[1][@datatype=('bn','bl')]) then
                    attribute {$anme} {'false'}
                else if ($att[1][@datatype=('set_cs','cs')]) then
                    attribute {$anme} {'cs'}
                else if ($att[1][@datatype=('int')]) then
                    let $int            := if ($element/property/@minInclude) then $element/property/@minInclude else (1)
                    return
                    attribute {$anme} {$int}
                else if ($att[1][@datatype=('real')]) then
                    let $int            := if ($element/property/@minInclude) then $element/property/@minInclude else (1)
                    let $intfrac        := tokenize($int,'\.')[2]
                    let $fractionDigits := if ($element/property[1]/@fractionDigits[matches(.,'\d')]) then xs:integer(replace($element/property[1]/@fractionDigits,'[^\d]','')) else (0)
                    let $intfracadd     := 
                        string-join(if (string-length($intfrac) lt $fractionDigits) then 
                            for $i in (1 to ($fractionDigits - string-length($intfrac)))
                            return '0'
                        else (),'')
                    let $real           := concat($int,if (not(contains($int,'.')) and string-length($intfracadd)>0) then '.' else(),$intfracadd)
                    return
                    attribute {$anme} {$real}
                else if ($att[1][@datatype=('ts')]) then
                    attribute {$anme} {format-dateTime(current-dateTime(),'[Y0001][M01][D01][H01][m01][s01]','en',(),())}
                else if ($att[1][@datatype=('uid','oid')]) then
                    attribute {$anme} {'1.2.3.999'}
                else if ($att[1][@datatype=('uuid')]) then
                    attribute {$anme} {'550e8400-e29b-41d4-a716-446655440000'}
                else if ($att[1][@datatype=('ruid')]) then
                    attribute {$anme} {'FsLo5xllxHinTYAGyEVldE'}
                else (
                    attribute {$anme} {'--TODO--'}
                )
            ,
            (: check element vocabulary :)
            if ($element/vocabulary[1]/@valueSet) then
                let $vsref     := $element/vocabulary[1]/@valueSet
                let $vsflex    := $element/vocabulary[1]/@flexibility
                let $valueSet  := vs:getExpandedValueSetByRef($vsref, $vsflex, $decor/project/@prefix, (), (), false())
                let $firstCode := $valueSet//conceptList/concept[not(@type=('D','A'))]
                return (
                    if ($firstCode) then (
                        attribute code {$firstCode[1]/@code},
                        (:this fails for e.g. hl7:statusCode without a datatype and vocabulary with @codeSystem:)
                        if (starts-with($element/@datatype,'CS')) then () else ( 
                            attribute displayName {$firstCode[1]/@displayName},
                            attribute codeSystem {$firstCode[1]/@codeSystem}
                        )
                    ) else ()
                )
            else if ($element/vocabulary[1]/@code) then (
                $element/vocabulary[1]/@code,
                (:this fails for e.g. hl7:statusCode without a datatype and vocabulary with @codeSystem:)
                if (starts-with($element/@datatype,'CS')) then () else (
                    $element/vocabulary[1]/@codeSystem,
                    $element/vocabulary[1]/@displayName
                )
            )
            else if ($element/vocabulary[1]/@codeSystem) then (
                attribute code {'--code--'},
                (:this fails for e.g. hl7:statusCode without a datatype and vocabulary with @codeSystem:)
                if (starts-with($element/@datatype,'CS')) then () else (
                    $element/vocabulary[1]/@codeSystem,
                    $element/vocabulary[1]/@displayName
                )
            )
            else()
            ,
            if ($element[empty(@contains)][not($attributes[@name='root' or @name='extension'])]/@datatype='II') then (
                attribute root {'1.2.3.999'},
                attribute extension {'--example only--'}
            )
            else ()
            ,
            if ($element[empty(@contains)][not($attributes/@name='value')]/@datatype=('BL','BN')) then
                attribute value {'false'}
            else if ($element[not($attributes/@name='value')]/@datatype=('INT','INT.POS','INT.NONNEG')) then
                let $int := if ($element/property/@minInclude) then $element/property/@minInclude else (1)
                return
                attribute value {$int}
            else if ($element[empty(@contains)][not($attributes/@name='value')]/@datatype=('REAL','REAL.POS','REAL.NONNEG')) then
                let $int            := if ($element/property/@minInclude) then $element/property/@minInclude else (1)
                let $intfrac        := tokenize($int,'\.')[2]
                let $fractionDigits := if ($element/property[1]/@fractionDigits[matches(.,'\d')]) then xs:integer(replace($element/property[1]/@fractionDigits,'[^\d]','')) else (0)
                let $intfracadd     := 
                    string-join(if (string-length($intfrac) lt $fractionDigits) then 
                        for $i in (1 to ($fractionDigits - string-length($intfrac)))
                        return '0'
                    else (),'')
                let $real           := concat($int,if (not(contains($int,'.')) and string-length($intfracadd)>0) then '.' else(),$intfracadd)
                return
                attribute value {$real}
            else if ($element[empty(@contains)][not($attributes/@name='value')]/@datatype='TEL') then
                attribute value {'tel:+1-12345678'}
            else if ($element[empty(@contains)][not($attributes/@name='value')]/@datatype='URL') then
                attribute value {'http:mydomain.org'}
            else if ($element[empty(@contains)][not($attributes/@name='value')]/@datatype='TS') then
                attribute value {format-dateTime(current-dateTime(),'[Y0001][M01][D01][H01][m01][s01]','en',(),())}
            else if ($element[empty(@contains)][not($attributes/@name='value')]/@datatype='TS.DATE') then
                attribute value {format-dateTime(current-dateTime(),'[Y0001][M01][D01]','en',(),())}
            else if ($element[empty(@contains)][not(include|choice|element)]/@datatype='IVL_TS') then
                element low {attribute value {format-dateTime(current-dateTime(),'[Y0001][M01][D01][H01][m01][s01]','en',(),())}}
            else if ($element[empty(@contains)]/@datatype='PQ') then (
                if ($element[$attributes/@name='value']) then () else (attribute value {1}), 
                if ($element[$attributes/@name='unit']) then () else ($element/property[1]/@unit)
            )
            else if ($element[empty(@contains)][not($element/(include|choice|element))]/@datatype='IVL_PQ') then
                element low {attribute value {1}, $element/property[1]/@unit}
            else if ($element[empty(@contains)][not($element/(include|choice|element))]/@datatype='MO') then
                (attribute value {1}, $element/property[1]/@currency)
            else if ($element[empty(@contains)][not($element/(include|choice|element))]/@datatype='IVL_MO') then
                element low {attribute value {1}, $element/property[1]/@currency}
            else ()
            ,
            if ($element/text) then
                $element/text[1]/node()
            else ()
            ,
            if ($element[not(@contains|include|choice|element|text)][@datatype=('EN','ON','PN','TN','ADXP','ENXP','SC','AD')]) then
                $elmname
            else ()
            ,
            for $child in $element/element | $element/include | $element/choice
            return
                if ($child/self::element) then
                    templ:processElement($child,$decor)
                else if ($child/self::include) then
                    templ:processInclude($child,$decor)
                else if ($child/self::choice) then
                    templ:processChoice($child,$decor)
                else ()
            ,
            if ($element[@contains]) then (
                let $prefix := $decor/project/@prefix
                let $ref    := $element/@contains
                let $flex   := if ($element/@flexibility) then $element/@flexibility else 'dynamic'
                let $obj    := templ:getTemplateByRef($ref, $flex, $decor)/template/template[@id][1]
                let $id     := if ($obj/@id) then $obj/@id else $ref
                let $nm     := if ($obj/@displayName) then $obj/@displayName else if ($obj/@name) then $obj/@name else ('?')
                return (
                    '&#10;',
                    comment {concat(' template ',$id,' ''',$nm,''' (',$flex,') ')}
                )
            )
            else ()
        }
    else()
};

declare function templ:processElement-hl7v2.5xml($element as element(),$decor as element()) as element()? {
    if ($element/@selected and not($element/@conformance='NP')) then
        (: strip namespace prefix and predicate from element/@name :)
        let $elmpfx     := substring-before($element/@name,':')
        let $elmns      := if ($elmpfx=('hl7v2','hl7','cda','',())) then () else if ($elmpfx='art') then 'urn:art-decor:example' else (namespace-uri-for-prefix($elmpfx,$decor))
        let $elmname    := replace($element/@name,'^([^:]+:)?([^\s\[]+)\s*(\[.*)?','$2')
        
        (: in older (hand created) templates people may have used double declarations in one attribute element, e.g.
            <attribute classCode="OBS" moodCode="EVN"/>
           Also we might encounter a mix of name/value versus shorthands. Normalize before processing to name/value
        :)
        let $attributes := templ:normalizeAttributes($element/attribute[@selected][not(@prohibited='true')])
        
        let $datatype   := 
            if ($element[@datatype]) 
            then $element/@datatype 
            else
            if ($element[@name='hl7:OBX.5']/preceding-sibling::element[@name='hl7:OBX.2'][text])
            then $element/preceding-sibling::element[@name='hl7:OBX.2'][1]/text[1]
            else
            if ($attributes[@name='Type'][@value])
            then $attributes[@name='Type'][1]/@value
            else ()
        let $datatype   := if (contains($datatype[1],':')) then substring-after($datatype[1],':') else data($datatype[1])
        return
        element {if ($elmns) then QName($elmns,concat($elmpfx,':',$elmname)) else $elmname} 
        {
            if ($element/@originalType='ANY') then
                (: poor mans solution for INT.POS, AD.NL and other flavors. Should check
                    DECOR-supported-datatypes.xml
                :)
                attribute xsi:type {tokenize($element/@datatype,'\.')[1]}
            else()
            ,
            (: check normalized element/attribute :)
            for $att in $attributes
            let $anme := $att/@name/string()
            group by $anme
            return
                if ($att[1]/@value) then 
                    attribute {$anme} {$att[1]/@value}
                else if ($att[1]/vocabulary) then (
                    if ($att[1]/vocabulary[1]/@valueSet) then
                        let $vsref     := $att[1]/vocabulary[1]/@valueSet
                        let $vsflex    := $att[1]/vocabulary[1]/@flexibility
                        let $valueSet  := vs:getExpandedValueSetByRef($vsref, $vsflex, $decor/project/@prefix, (), (), false())
                        let $firstCode := ($valueSet//conceptList/concept[not(@type=('D','A'))])[1]
                        return (
                            attribute {$anme} {$firstCode/@code}
                        )
                    else if ($att[1]/vocabulary[1]/@code) then (
                        attribute {$anme} {$att[1]/vocabulary[1]/@code}
                    )
                    else ()
                )
                else if ($att[1][@datatype=('bn','bl')]) then
                    attribute {$anme} {'false'}
                else if ($att[1][@datatype=('set_cs','cs')]) then
                    attribute {$anme} {'cs'}
                else if ($att[1][@datatype=('int')]) then
                    let $int            := if ($element/property/@minInclude) then $element/property/@minInclude else (1)
                    return
                    attribute {$anme} {$int}
                else if ($att[1][@datatype=('real')]) then
                    let $int            := if ($element/property/@minInclude) then $element/property/@minInclude else (1)
                    let $intfrac        := tokenize($int,'\.')[2]
                    let $fractionDigits := if ($element/property[1]/@fractionDigits[matches(.,'\d')]) then xs:integer(replace($element/property[1]/@fractionDigits,'[^\d]','')) else (0)
                    let $intfracadd     := 
                        string-join(if (string-length($intfrac) lt $fractionDigits) then 
                            for $i in (1 to ($fractionDigits - string-length($intfrac)))
                            return '0'
                        else (),'')
                    let $real           := concat($int,if (not(contains($int,'.')) and string-length($intfracadd)>0) then '.' else(),$intfracadd)
                    return
                    attribute {$anme} {$real}
                else if ($att[1][@datatype=('ts')]) then
                    attribute {$anme} {format-dateTime(current-dateTime(),'[Y0001][M01][D01][H01][m01][s01]','en',(),())}
                else if ($att[1][@datatype=('uid','oid')]) then
                    attribute {$anme} {'1.2.3.999'}
                else if ($att[1][@datatype=('uuid')]) then
                    attribute {$anme} {'550e8400-e29b-41d4-a716-446655440000'}
                else if ($att[1][@datatype=('ruid')]) then
                    attribute {$anme} {'FsLo5xllxHinTYAGyEVldE'}
                else (
                    attribute {$anme} {'--TODO--'}
                )
            ,
            (: check element vocabulary :)
            if ($datatype[.=('CD','CE','CNE','CWE','IS','ID','PREF','EXPL')]) then
                if ($element/vocabulary[1]/@valueSet) then
                    let $vsref     := $element/vocabulary[1]/@valueSet
                    let $vsflex    := $element/vocabulary[1]/@flexibility
                    let $valueSet  := vs:getExpandedValueSetByRef($vsref, $vsflex, $decor/project/@prefix, (), (), false())
                    let $firstCode := $valueSet//conceptList/concept[not(@type=('D','A'))]
                    return (
                        if ($firstCode) then (
                            element {concat($datatype,'.1')} {data($firstCode[1]/@code)},
                            (:this fails for e.g. hl7:statusCode without a datatype and vocabulary with @codeSystem:)
                            if ($datatype=('ID','IS')) then () else ( 
                                element {concat($datatype,'.2')} {data($firstCode[1]/@displayName)},
                                element {concat($datatype,'.3')} {data($firstCode[1]/@codeSystem)}
                            )
                        ) else ()
                    )
                else if ($element/vocabulary[1]/@code) then (
                    element {concat($datatype,'.1')} {data($element/vocabulary[1]/@code)},
                    (:this fails for e.g. hl7:statusCode without a datatype and vocabulary with @codeSystem:)
                    if ($datatype=('ID','IS')) then () else (
                        element {concat($datatype,'.2')} {data($element/vocabulary[1]/@codeSystem)},
                        element {concat($datatype,'.3')} {data($element/vocabulary[1]/@displayName)}
                    )
                )
                else if ($element/vocabulary[1]/@codeSystem) then (
                    element {concat($datatype,'.1')} {'--code--'},
                    (:this fails for e.g. hl7:statusCode without a datatype and vocabulary with @codeSystem:)
                    if ($datatype=('ID','IS')) then () else (
                        element {concat($datatype,'.2')} {data($element/vocabulary[1]/@codeSystem)},
                        element {concat($datatype,'.3')} {data($element/vocabulary[1]/@displayName)}
                    )
                )
                (:these table indicators are textual references, e.g. HL70127 that likely do not resolve in a 
                  normal fashion, hence we also check the repositories directly. The valueSet/@name should have 
                  this reference in the appropriate repository:)
                else if ($attributes[@name="Table"][@value]) then (
                    let $vsref     := $attributes[@name='Table']/@value
                    let $vsflex    := ()
                    let $valueSet  := vs:getExpandedValueSetByRef($vsref, $vsflex, $decor/project/@prefix, (), (), false())
                    let $valueSet  := 
                        if ($valueSet//valueSet[@id]) then ($valueSet) else (
                            let $valueSets      :=
                                for $repository in ($decor/project/buildingBlockRepository[empty(@format)] | $decor/project/buildingBlockRepository[@format='decor'])
                                let $service-uri    := xs:anyURI(concat($repository/@url,'/RetrieveValueSet?ref=',encode-for-uri($vsref),'&amp;prefix=',$repository/@ident,'&amp;format=xml'))
                                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)
                                return
                                    $server-response//valueSet[@id]
                            
                            for $valueSet in $valueSets
                            order by $valueSet/@effectiveDate descending
                            return $valueSet
                        )
                    let $firstCode := $valueSet//conceptList/concept[not(@type=('D','A'))]
                    return (
                        if ($firstCode) then (
                            element {concat($datatype,'.1')} {data($firstCode[1]/@code)},
                            (:this fails for e.g. hl7:statusCode without a datatype and vocabulary with @codeSystem:)
                            if ($datatype=('ID','IS')) then () else (
                                element {concat($datatype,'.2')} {data($firstCode[1]/@displayName)},
                                element {concat($datatype,'.3')} {$vsref}
                            )
                        ) else ()
                    )
                )
                else ()
            else ()
            ,
            if ($element[not(@contains|include|choice|element|text)]) then
                if ($datatype=('AD','XAD')) then
                    element {concat($datatype,'.1')} {$elmname}
                else
                if ($datatype=('BL','BN')) then
                    element {concat($datatype,'.1')} {'false'}
                else 
                if ($datatype=('NM','NM.POS','NM.NONNEG')) then
                    let $int := if ($element/property/@minInclude) then $element/property/@minInclude else (1)
                    return
                    element {concat($datatype,'.1')} {$int}
                else 
                if ($datatype=('TN','XTN')) then
                    element {concat($datatype,'.1')} {'tel:+1-12345678'}
                else 
                if ($datatype='TS') then
                    element {concat($datatype,'.1')} {format-dateTime(current-dateTime(),'[Y0001][M01][D01][H01][m01][s01]','en',(),())}
                else 
                if ($datatype='TS.DATE') then
                    element {concat($datatype,'.1')} {format-dateTime(current-dateTime(),'[Y0001][M01][D01]','en',(),())}
                else 
                if ($datatype='DTM') then
                    format-dateTime(current-dateTime(),'[Y0001][M01][D01][H01][m01][s01]','en',(),())
                else 
                if ($datatype='DT') then
                    format-dateTime(current-dateTime(),'[Y0001][M01][D01]','en',(),())
                else 
                if ($datatype='TM') then
                    format-dateTime(current-dateTime(),'[H01][m01][s01]','en',(),())
                else
                if ($element[not(@contains|include|choice|element|text)][$datatype=('SI')]) then
                    '1' (:SetID:)
                else
                if ($datatype=('ON','PN','XON','XPN')) then
                    element {concat($datatype,'.1')} {$elmname}
                else ()
            else (
                $element/text[1]/node()
            )
            ,
            for $child in $element/element | $element/include | $element/choice
            return
                if ($child/self::element) then
                    templ:processElement-hl7v2.5xml($child,$decor)
                else if ($child/self::include) then
                    templ:processInclude($child,$decor)
                else if ($child/self::choice) then
                    templ:processChoice($child,$decor)
                else ()
            ,
            if ($element[@contains]) then (
                let $prefix := $decor/project/@prefix
                let $ref    := $element/@contains
                let $flex   := if ($element/@flexibility) then $element/@flexibility else 'dynamic'
                let $obj    := templ:getTemplateByRef($ref, $flex, $decor)/template/template[@id][1]
                let $id     := if ($obj/@id) then $obj/@id else $ref
                let $nm     := if ($obj/@displayName) then $obj/@displayName else if ($obj/@name) then $obj/@name else ('?')
                return (
                    '&#10;',
                    comment {concat(' template ',$id,' ''',$nm,''' (',$flex,') ')}
                )
            )
            else ()
        }
    else()
};

declare function templ:processInclude($element as element(),$decor as element()) as item()* {
    let $prefix := $decor/project/@prefix
    let $ref    := $element/@ref
    let $flex   := if ($element/@flexibility) then $element/@flexibility else 'dynamic'
    let $obj    := templ:getTemplateByRef($ref, $flex, $decor)/template/template[@id][1]
    let $id     := if ($obj/@id) then $obj/@id else $ref
    let $nm     := if ($obj/@displayName) then $obj/@displayName else if ($obj/@name) then $obj/@name else ('?')
    return
    if ($element/@selected and not($element/@conformance='NP')) then (
        '&#10;',
        comment {concat(' include template ',$id,' ''',$nm,''' (',$flex,') ', templ:processCardConf($element),' ')}
    )
    else ()
};

declare function templ:processChoice($element as element(),$decor as element()) as item()* {
    if ($element/@selected) then (
        '&#10;',
        comment {
            concat(' choice: ',templ:processCardConf($element),'&#10;')
            ,
            for $child in $element/(element|include|choice)[@selected]
            let $prefix := $decor/project/@prefix
            let $ref    := $child/(@contains|@ref)
            let $flex   := if ($element/@flexibility) then $element/@flexibility else 'dynamic'
            let $obj    := if ($ref) then templ:getTemplateByRef($ref, $flex, $decor)/template/template[@id][1] else ()
            let $id     := if ($obj/@id) then $obj/@id else $ref
            let $nm     := if ($obj/@displayName) then $obj/@displayName else if ($obj/@name) then $obj/@name else ('?')
            return (
                if ($child/self::element) then (
                    concat('    element ',$child/@name,if ($child/@contains) then concat(' containing template ',$child/@contains,' (',if ($child/@flexibility) then $child/@flexibility else ('dynamic'),')') else (),'&#10;')
                )
                else if ($child/self::include) then
                    concat('    include template ',$id,' ''',$nm,''' (',$flex,') ', templ:processCardConf($element))
                else if ($child/self::choice) then
                    concat('    choice: ',templ:processCardConf($element),'&#10;')
                else ()
            )
            ,
            ' '
        }
    )
    else ()
};

declare function templ:processCardConf($element as element()) as xs:string? {
    let $min    := if ($element[@conformance='NP']) then () else $element/@minimumMultiplicity
    let $max    := if ($element[@conformance='NP']) then () else $element/@maximumMultiplicity
    let $conf   := if ($element/@isMandatory='true') then 'M' else ($element/@conformance) 
    
    return
        string-join((string-join($min|$max,'..'),$conf),' ')
};

(: ===== WRITE FUNCTIONS ===== :)

(:  Adds project unique ids to template elements and attributes where they do not exist yet, 
:   based on the defaultBaseId for type EL in the project
:   
:   @param $decor               - required. The <decor/> element for the project the template is in
:   @param $id                  - optional. Id of the template. Matches template/@id. If empty does all templates.
:   @param $effectiveDate       - optional. Considered only if $id is not empty. null does all versions, yyyy-mm-ddThh:mm:ss gets this specific version
:   @return nothing or error
:   @since 2016-11-30

:)
declare function templ:addTemplateElementAndAttributeIds($decor as element(decor), $id as xs:string?, $effectiveDate as xs:string?) {
let $template               :=
    if (string-length($id)>0 and string-length($effectiveDate)>0) then
        $decor//template[@id=$id][@effectiveDate=$effectiveDate]
    else if (string-length($id)>0) then
        $decor//template[@id=$id]
    else (
        $decor//template[@id]
    )
let $defaultElementBaseId   := decor:getDefaultBaseIdsP($decor, $decor:OBJECTTYPE-TEMPLATEELEMENT)/@id
let $elementBaseId          := 
    if ($defaultElementBaseId) then
        if (ends-with($defaultElementBaseId,'.')) 
        then $defaultElementBaseId[1]/string()
        else concat($defaultElementBaseId[1],'.') 
    else ()
let $existingIds            := $decor//element[matches(@id,concat('^',$elementBaseId,'\d+$'))] | $decor//attribute[matches(@id,concat('^',$elementBaseId,'\d+$'))]
let $elementIncr            :=
    if ($existingIds) then
        max($existingIds/xs:integer(tokenize(@id,'\.')[last()]))
    else (
        0
    )
(: 2014-12-16 AH Add support for ids on attributes without id and with no more than 1 attribute defined. Excludes things like:
    <attribute classCode="OBS" moodCode="EVN"/>
    Adding an id on this type of attribute would be ambiguous
:)
let $update                 :=
    if ($elementBaseId) then
        for $element at $pos in ($template//element[empty(@id)] | 
                                 $template//attribute[empty(@id)][count(@name|@classCode|@contextConductionInd|@contextControlCode|
                                                                      @determinerCode|@extension|@independentInd|@institutionSpecified|
                                                                      @inversionInd|@mediaType|@moodCode|@negationInd|
                                                                      @nullFlavor|@operator|@qualifier|@representation|
                                                                      @root|@typeCode|@unit|@use)=1])
        let $i  := $elementIncr + $pos
        let $u  := update insert attribute id {concat($elementBaseId, $i)} into $element
        return $i
    else ()

return ()
};