• Publicity: Public Only All

tag-init.tcl

Tag Handlers for the ArsDigita Templating System Copyright (C) 1999-2000 ArsDigita Corporation Authors: Karl Goldstein (karlg@arsdigita.com) Stanislav Freidin (sfreidin@arsdigita.com) Christian Brechbuehler (chrisitan@arsdigita.com) $Id: tag-init.tcl,v 1.55 2024/11/13 08:40:51 gustafn Exp $ This is free software distributed under the terms of the GNU Public License. Full text of the license is available from the GNU Project: http://www.fsf.org/copyleft/gpl.html

Location:
packages/acs-templating/tcl/tag-init.tcl

Procedures in this file

Detailed information

[ hide source ] | [ make this the default ]

Content File Source

ad_library {
    Tag Handlers for the ArsDigita Templating System

    Copyright (C) 1999-2000 ArsDigita Corporation
    Authors: Karl Goldstein         (karlg@arsdigita.com)
             Stanislav Freidin      (sfreidin@arsdigita.com)
             Christian Brechbuehler (chrisitan@arsdigita.com)

    $Id: tag-init.tcl,v 1.55 2024/11/13 08:40:51 gustafn Exp $

    This is free software distributed under the terms of the GNU Public
    License.  Full text of the license is available from the GNU Project:
    http://www.fsf.org/copyleft/gpl.html
}

#-----------------------------------------------------------
# Putting tag handlers in namespaces does not seem to work!
#-----------------------------------------------------------

template::tag tcl { chunk params } {
    #
    # Add some escaped Tcl
    #

    # if the chunk begins with = then add our own append
    if { [string index $chunk 0] eq "=" } {
        template::adp_append_code "append __adp_output [string range $chunk 1 end]"
    } else {
        template::adp_append_code $chunk
    }
}

template::tag property { chunk params } {
    #
    # Set a property for the template.  Properties are primarily for
    # the benefit of the master template to display appropriate
    # context, such as the title, navigation links, etc.
    #

    set name [ns_set iget $params name]
    set adp  [ns_set iget $params adp 0]

    # quote dollar signs, square bracket and quotes
    regsub -all -- {[\]\[\"\\$]} $chunk {\\&} quoted_chunk
    if {$adp} {
        regsub -all -- {<tcl>} $quoted_chunk {<%} quoted_chunk
        regsub -all -- {</tcl>} $quoted_chunk {%>} quoted_chunk

        template::adp_append_code "set __adp_properties($name) \[ns_adp_parse -string \"$quoted_chunk\"\]"
    } else {
        template::adp_append_code "set __adp_properties($name) \"$quoted_chunk\""
    }
}

template::tag master { params } {
    #
    # Set the master template.
    #

    set src [ns_set iget $params src]
    set slave_properties_p [template::get_attribute master $params slave-properties-p 0]

    if {[string is true -strict $slave_properties_p]} {
        template::adp_append_code {
            foreach {__key __value} $__args {
                if {$__key ne "__adp_slave"} {
                    set __adp_properties($__key$__value
                }
            }
        }
    }

    # default to the site-wide master
    if {$src eq ""} {
        set src {[parameter::get -package_id [ad_conn subsite_id] -parameter DefaultMaster -default "/www/default-master"]}
    }

    template::adp_append_code [subst -nocommands {
        set __adp_master [template::util::master_to_file "$src" "\$__adp_stub"]
    }]
}

template::tag slave { params } {
    #
    # Insert the slave template
    #

    #Start developer support frame around subordinate template.
    if { [namespace which ::ds_enabled_p] ne ""
         && [namespace which ::ds_adp_start_box] ne "" } {

        ::ds_adp_start_box
    }

    template::adp_append_code {
        if { [info exists __adp_slave] } {
            append __adp_output $__adp_slave
        }
    }

    #End developer support frame around subordinate template.
    if { [namespace which ::ds_enabled_p] ne ""
         && [namespace which ::ds_adp_end_box] ne "" } {

        ::ds_adp_end_box
    }

}

d_proc -private template::template_tag_include_helper_code {
    -command
    -src
    {-ds_avail_p 0}
} {
    if {$ds_avail_p} {
        set __DS_CODE__ {
            if {[info exists ::ds_enabled_p] && [info exists ::ds_collection_enabled_p] } {
                set __include_errors {}
                ns_cache get ds_page_bits [ad_conn request]:error __include_errors
                ns_cache set ds_page_bits [ad_conn request]:error [lappend __include_errors [list "__SRC__" $::errorInfo]]
            }
        }
    } else {
        set __DS_CODE__ ""
    }

    # Handle errors in the included snippet the following way:
    # - script-aborts are passed to the upper levels via "ad_try".
    # - If the included snippet leads to an error, include
    #   it in the result and log it in the error log

    set snippet {
        ad_try {
            append __adp_output [__COMMAND__]

        } on error {errorMsg} {
            set templateFile [template::util::url_to_file __SRC__ $__adp_stub]
            append __adp_output "Error in include template \"$templateFile\": [ns_quotehtml $errorMsg]"
            # JCD: If we have the ds_page_bits cache maybe save the error for later
            __DS_CODE__
            ad_log Error "Error in include template \"$templateFile\": $errorMsg"
        }
    }

    #
    # In order to avoid potential problems with substitution
    # patterns containing ampersand or backslashes, we use here a
    # scripted, purely string based substitution (which applies only
    # at "compilation time", therefore, performance is less critical.
    #
    # We have still to protect the case, that the passed-in $src
    # contains "__COMMAND__" which has to be protected. The
    # __DS_CODE__ is under our control, the content of __COMMAND__
    # will not be substituted.
    #
    set containsMagicString [regsub -all __COMMAND__ $src \u0001 __SRC__]

    set __COMMAND__ $command
    foreach v {__DS_CODE__ __SRC__ __COMMAND__} {
        set startPos 0
        set s ""
        set l [string length $v]
        while {1} {
            set p [string first $v $snippet $startPos]
            if {$p == -1} {
                append s [string range $snippet $startPos end]
                break
            }
            append s [string range $snippet $startPos $p-1] [set $v]
            set startPos $p
            incr startPos $l
        }
        #ns_log notice "=== include SNIPPET after $v substitution\n$s"
        set snippet $s
    }
    if {$containsMagicString} {
        regsub -all \u0001 $snippet __COMMAND__ snippet
    }
    #ns_log notice "final include SNIPPET\n$snippet"
    return $snippet
}

ad_proc -private template::template_tag_include_command {src params} {
    # pass additional arguments as key-value pairs

    set command "template::adp_parse"
    append command " \[template::util::url_to_file \"$src\" \"\$__adp_stub\"\]"
    append command " \[list"

    foreach {key value} [ns_set array $params] {
        if {$key in {src ds}} { continue }
        append command [subst { $key "$value"}]
    }
    append command "\]"
    return $command
}

ad_proc -private template::template_tag_include_helper {params} {
    Include another template in the current template
} {
    set src [ns_set iget $params src]
    set ds [ns_set iget $params ds 1]
    set ds_avail_p [expr {[namespace which ::ds_adp_start_box] ne "" }]

    #Start developer support frame around subordinate template.
    if { $ds && [namespace which ::ds_enabled_p] ne "" && $ds_avail_p } {
        ::ds_adp_start_box -stub "\[template::util::url_to_file \"$src\" \"\$__adp_stub\"\]"
    }

    template::adp_append_code [template::template_tag_include_helper_code \
                                   -ds_avail_p [expr {[namespace which ::ds_adp_start_box] ne "" }] \
                                   -src $src \
                                   -command [template::template_tag_include_command $src $params]]

    # End developer support frame around subordinate template.
    if { $ds && [namespace which ::ds_enabled_p] ne "" && $ds_avail_p } {
        ::ds_adp_end_box -stub "\[template::util::url_to_file \"$src\" \"\$__adp_stub\"\]"
    }
}

template::tag include { params } {
    #
    # Check, if the src can be resolved against resources/templates in
    # the theme package
    #
    ns_set iupdate $params src [template::themed_template [ns_set iget $params src]]
    template::template_tag_include_helper $params
}

template::tag widget { params } {
    #
    # <widget> is very similar to <include>, but uses widget specific
    # name resolution based on themes.  If the theme package contains
    # resources/widgets/$specifiedName it is used from
    # there. Otherwise it behaves exactly like <include> (without the
    # resources/templates/ theming)
    #
    set src [ns_set iget $params src]
    set adp_stub [template::resource_path -type widgets -style $src -relative]
    if {[file exists $::acs::rootdir/$adp_stub.adp]} {
        ns_set iupdate $params src $adp_stub
    }
    template::template_tag_include_helper $params
}

template::tag multiple { chunk params } {
    #
    # Repeat a template chunk for each row of a multirow data source
    #

    set name      [template::get_attribute multiple $params name       ]
    set startrow  [template::get_attribute multiple $params startrow  0]
    set maxrows   [template::get_attribute multiple $params maxrows  -1]; #unlimit
    set delimiter [template::get_attribute multiple $params delimiter ""]

    set tag_id [template::current_tag]

    set i "__${tag_id}_i"

    template::adp_append_code "

  if {\[info exists $name\]} {
      upvar 0 $name __${tag_id}_swap
  }

  for { set $i [expr {1 + $startrow}] } { \$$i <= \${$name:rowcount}"

    if {$maxrows >= 0} {
        template::adp_append_code " && \$$i <= $maxrows + $startrow" \
            -nobreak
    }

    template::adp_append_code " } { incr $i } {
    upvar 0 $name:\$$i $name
  " -nobreak
    template::adp_compile_chunk $chunk

    if { $delimiter ne "" } {
        template::adp_append_code " if { \$$i < \${$name:rowcount}"

        if {$maxrows >= 0} {
            template::adp_append_code " && \$$i < $maxrows + $startrow" \
                -nobreak
        }

        template::adp_append_code " } {\n"
        template::adp_append_string $delimiter
        template::adp_append_code "\n}\n"
    }

    template::adp_append_code "}

  if {\[info exists __${tag_id}_swap\]} {
      upvar 0 __${tag_id}_swap $name
  }"
}

template::tag list { chunk params } {
    #
    # Repeat a template chunk for each item in a list
    #

    # the list tag accepts a value so that it may be used without a data
    # source in the Tcl script

    set value [ns_set iget $params value]

    # If the value exists, use it and create a fake name for it

    if { ![template::util::is_nil value] } {

        set name [ns_set iget $params name "__ats_list_value"]
        template::adp_append_code [list set $name $value]
        template::adp_append_code "set $name:rowcount \[llength \$$name\]\n"

    } else {

        # Expect a data source from the Tcl script
        set name [template::get_attribute list $params name]
        template::adp_append_code "\nset {$name:rowcount} \[llength \${$name}\]\n"
    }

    template::adp_append_code "

  for { set __ats_${name}_i 0 } { \$__ats_${name}_i < \${$name:rowcount} } { incr __ats_${name}_i } {
    set $name:item \[lindex \${$name} \$__ats_${name}_i\]
    set $name:rownum \[expr {\$__ats_${name}_i + 1}\]
  "
    template::adp_compile_chunk $chunk

    template::adp_append_code "}"
}

template::tag group { chunk params } {
    #
    # Create a recursed group, generating a recursive multirow block
    # until the column name stays the same
    #

    set column [template::get_attribute group $params column]
    set delimiter [template::get_attribute group $params delimiter ""]

    # Scan the parameter stack backward, looking for the tag name

    set multiple_tag_id [template::enclosing_tag multiple]

    if {$multiple_tag_id eq {}} {
        error "No enclosing MULTIPLE tag for GROUP tag on column $column"
    }

    # Get the name of the multiple variable we're looping over
    set name [template::tag_attribute $multiple_tag_id name]

    set tag_id [template::current_tag]

    # If we're inside another group tag, we'll need to save and restore that tag's groupnum and groupnum_last_p values
    # Find enclosing group tag, if one exists
    set group_tag_id [template::enclosing_tag group]

    # Save groupnum pseudocolumns from surrounding group tag
    # We don't care about saving groupnum_last_p, since this doesn't work
    # for group tags that have other group tags inside them, since we can't know
    # if we're the last row until the inner group tag has eaten up all the
    # rows between the start of this tag and the end.
    if { $group_tag_id ne "" } {
        template::adp_append_code "
      if { \[info exists ${name}(groupnum)\] } {
        set __${tag_id}_${group_tag_id}_groupnum \$${name}(groupnum)
      }
    "
    }

    set i "__${multiple_tag_id}_i"

    # while the value of name(column) stays the same
    template::adp_append_code "
    set __${tag_id}_group_rowcount 1
    while { 1 } {
      set ${name}(groupnum) \$__${tag_id}_group_rowcount
      if { \$$i >= \${$name:rowcount} } {
        set ${name}(groupnum_last_p) 1
      } else {
        upvar 0 ${name}:\[expr {\$$i + 1}\] $name:next
        set ${name}(groupnum_last_p) \[expr {\${${name}:next(${column})} ne \$${name}($column)}\]
      }
  "

    template::adp_compile_chunk $chunk

    # look ahead to the next value and break if it is different
    # otherwise advance the cursor to the next row
    template::adp_append_code [subst -nocommands {
        if { \$$i >= \${$name:rowcount} } {
            break
        }
        upvar 0 ${name}:[expr {\$$i + 1}] $name:next
        if { \${${name}:next($column)} ne \$${name}(${column}) } {
            break
        }
    }]

    if { $delimiter ne "" } {
        template::adp_append_string $delimiter
    }

    template::adp_append_code "
      incr $i
      upvar 0 $name:\$$i $name
      incr __${tag_id}_group_rowcount
    }
  "

    # Restore saved groupnum pseudocolumns
    if { $group_tag_id ne "" } {
        set varName __${tag_id}_${group_tag_id}_groupnum
        template::adp_append_code [subst -nocommands {
            if { [info exists $varName] } {
                set ${name}(groupnum) \$$varName
            }
        }]
    }
}

template::tag grid { chunk params } {
    #
    # Repeat a template chunk consisting of a grid cell for each row
    # of a multirow data source
    #

    set name [template::get_attribute grid $params name]
    # cols must be a float for ceil to work
    set cols [template::get_attribute grid $params cols]
    # Horizontal or vertical ?
    set orientation [template::get_attribute grid $params orientation vertical]

    template::adp_append_code "
  set rows \[expr {ceil(\${$name:rowcount} / $cols.0)}\]
  for { set __r 1 } { \$__r <= \$rows } { incr __r } {
    for { set __c 1 } { \$__c <= $cols } { incr __c } {
"

    if {$orientation eq "vertical"} {
        template::adp_append_code "
      set rownum \[expr {1 + int((\$__r - 1) + ((\$__c - 1) * \$rows))}\]
"
    } else {
        template::adp_append_code "
      set rownum \[expr {1 + int((\$__c - 1) + ((\$__r - 1) * $cols))}\]
"
    }

    template::adp_append_code "
      upvar 0 $name:\$rownum $name
      set ${name}(rownum) \$rownum
      set ${name}(row) \$__r
      set ${name}(col) \$__c
  "

    template::adp_compile_chunk $chunk

    template::adp_append_code "
    }
  }"
}

template::tag if { chunk params } {
    template::template_tag_if_condition $chunk $params if
}

template::tag elseif { chunk params } {
    template::template_tag_if_condition $chunk $params elseif
}

template::tag else { chunk params } {
    #
    # Append an "else" clause to the if expression
    #

    template::adp_append_code "else {" -nobreak

    template::adp_compile_chunk $chunk

    template::adp_append_code "}"
}

template::tag noparse { chunk params } {
    #
    # Output a template chunk without parsing, for preprocessed
    # templates
    #

    # escape quotes
    regsub -all -- {[\]\[""\\$]} $chunk {\\&} quoted

    template::adp_append_string $quoted
}

template::tag formwidget { params } {
    #
    # Render the HTML for the form widget, incorporating any
    # additional markup attributes specified in the template.
    #

    set id [template::get_attribute formwidget $params id]

    # get any additional HTML attributes specified by the designer
    set tag_attributes [dict remove \
                            [ns_set array $params] \
                            id]

    template::adp_append_string \
        "\[template::element render \${form:id} [list $id] { $tag_attributes } \]"
}

template::tag formhelp { params } {
    #
    # Display the help information for an element
    #

    set id [template::get_attribute formhelp $params id]

    # get any additional HTML attributes specified by the designer
    set tag_attributes [dict remove \
                            [ns_set array $params] \
                            id]

    template::adp_append_string \
        "\[template::element render_help \${form:id} [list $id] { $tag_attributes } \]"
}

template::tag formerror { chunk params } {
    #
    # Report a form error if one is specified.
    #

    set id [template::get_attribute formerror $params id]
    set type [ns_set iget $params type]

    if {$type eq {}} {
        set key $id
    } else {
        set key $id:$type
    }

    template::adp_append_code "
    if \{ \[info exists formerror($key)\] \} \{
      set formerror($id) \$formerror($key)
    "

    if {$chunk eq {}} {

        template::adp_append_string "\$formerror($key)"

    } else {

        template::adp_compile_chunk $chunk
    }

    template::adp_append_code "\}"
}

template::tag formgroup { chunk params } {
    #
    # Render a group of form widgets
    #

    set id [template::get_attribute formgroup $params id]

    # get any additional HTML attributes specified by the designer
    set tag_attributes [dict remove \
                            [ns_set array $params] \
                            id]

    # generate a list of options and option labels as a data source

    template::adp_append_code \
        "template::element options \${form:id} [list $id] { $tag_attributes }"

    # make sure name is a parameter to pass to the rendering tag handler
    ns_set iupdate $params name formgroup
    ns_set iupdate $params id formgroup

    # Use the multiple or grid tag to render the form group depending on
    # whether the cols attribute was specified

    if { [ns_set ifind $params cols] == -1 } {
        template_tag_multiple $chunk $params
    } else {
        template_tag_grid $chunk $params
    }
}

template::tag formgroup-widget { chunk params } {
    #
    # Render one element from a formgroup
    #

    set id [template::get_attribute formgroup-widget $params id]

    set row [template::get_attribute formgroup-widget $params row]
    # get any additional HTML attributes specified by the designer
    set tag_attributes [dict remove \
                            [ns_set array $params] \
                            id row]

    # generate a list of options and option labels as a data source

    template::adp_append_code \
        "template::element options \${form:id} [list $id] { $tag_attributes }"

    # make sure name is a parameter to pass to the rendering tag handler
    ns_set iupdate $params name formgroup
    ns_set iupdate $params id formgroup
    template::adp_append_code "append __adp_output \"\$\{formgroup:${row}(widget)\} \$\{formgroup:${row}(label)\}\""

}

template::tag formtemplate { chunk params } {
    #
    # Render a form, incorporating any additional markup attributes
    # specified in the template.  Set the magic variable "form:id" for
    # elements to reference
    #

    set level [template::adp_level]
    set id [template::get_attribute formtemplate $params id]

    upvar #$level $id:properties form_properties

    template::adp_append_code [list set form:id $id]

    # Set optional attributes for the grid template
    template::adp_append_code \
        [list upvar 0 $id:properties form_properties]

    foreach varname {headers title cols} {

        set form_properties($varname) [ns_set iget $params $varname]
        template::adp_append_code \
            [list set form_properties($varname$form_properties($varname)]
    }

    # get any additional HTML attributes specified by the designer
    set tag_attributes [dict remove \
                            [ns_set array $params] \
                            id style method title cols headers]

    template::adp_append_string \
        [subst -nocommands {[template::form render $id { $tag_attributes } ]}]

    if {[string trim $chunk] eq {}} {

        # generate the form body dynamically if none specified.
        set style [ns_set iget $params style]
        template::adp_append_string "\[template::form generate $id $style\]"

    } else {

        # compile the static form layout specified in the template
        template::adp_compile_chunk $chunk

        # Render any hidden variables that have not been rendered yet
        template::adp_append_string \
            [subst -nocommands {[template::form check_elements $id]}]
    }

    if { [info exists form_properties(fieldset)] } {
        template::adp_append_string "</fieldset>"
    }
    template::adp_append_string "</form>"
}

template::tag child { params } {
    #
    # @private tag_child
    #
    # Implements the <tt>child</tt> tag which renders a child item.
    # See the Developer Guide for more information. <br> The child tag
    # format is <blockquote><tt> &lt;child tag=<i>tag</i> index=<i>n
    # embed args</i>&gt; </blockquote>
    #
    # @param params  The ns_set id for extra HTML parameters
    #

    publish::process_tag child $params
}

template::tag relation { params } {
    #
    # @private tag_relation
    #
    # Implements the <tt>relation</tt> tag which renders a related
    # item.  See the Developer Guide for more information. <br> The
    # relation tag format is <blockquote><tt> &lt;relation
    # tag=<i>tag</i> index=<i>n embed args</i>&gt; </tt></blockquote>
    #
    # @param params  The ns_set id for extra HTML parameters
    #

    publish::process_tag relation $params
}

template::tag content { params } {
    #
    # @private tag_content
    #
    # Implements the <tt>content</tt> tag which renders the content of
    # the current item.  See the Developer Guide for more
    # information. <br> The content tag format is simply
    # <tt>&lt;content&gt;</tt>. The <tt>embed</tt> and
    # <tt>no_merge</tt> parameters are implicit to the tag.
    #
    # @param params  The ns_set id for extra HTML parameters
    #

    # Get item id/revision_id
    set item_id [publish::get_main_item_id]
    set revision_id [publish::get_main_revision_id]

    # Concatenate all other keys into the extra arguments list
    set extra_args [ns_set array $params]

    # Add code to flush the cache

    # Render the item, return the html
    set    command "publish::get_html_body \[publish::handle_item"
    append command " \$::content::item_id"
    append command " -html \{$extra_args\} -no_merge -embed"
    append command " -revision_id \[publish::get_main_revision_id\]\]"

    template::adp_append_code "append __adp_output \[$command\]"
}

template::tag include-optional { chunk params } {
    #
    # Include another template in the current template, but make some
    # other chunk dependent on whether or not the included template
    # returned something.
    #
    # This is useful if, say, you want to wrap the template with some
    # HTML, for example, a frame in a portal, but if there's nothing
    # to show, you don't want to show the frame either.
    #
    # @author Lars Pind (lars@collaboraid.net)
    #

    #
    # Check, if the src can be resolved against resources/templates in
    # the theme package
    #
    set src [template::themed_template [ns_set iget $params src]]
    set ds [ns_set iget $params ds 1]

    #Start developer support frame around subordinate template.
    if { $ds && [namespace which ::ds_enabled_p] ne ""
         && [namespace which ::ds_adp_start_box] ne ""} {

        ::ds_adp_start_box -stub "\[template::util::url_to_file \"$src\" \"\$__adp_stub\"\]"
    }

    set command [template::template_tag_include_command $src $params]

    # __adp_include_optional_output is a list that operates like a
    # stack, so first we execute the include template, and push the
    # result onto this stack, then, if the output contained anything
    # but whitespace, we also output the chunk inside the
    # include-optional tag.  Finally, we pop the output off of the
    # __adp_include_optional_output stack.

    template::adp_append_code "ad_try { lappend __adp_include_optional_output \[$command\] } on error {errmsg} {"
    template::adp_append_code "    append __adp_output \"Error in include template \\\"\[template::util::url_to_file \"$src\" \"\$__adp_stub\"\]\\\": \[ns_quotehtml \$errmsg\]\""
    template::adp_append_code "    ad_log Error \"Error in include template \\\"\[template::util::url_to_file \"$src\" \"\$__adp_stub\"\]\\\": \$errmsg\""
    template::adp_append_code "} on ok {r} {"
    template::adp_append_code "if { \[string trim \[lindex \$__adp_include_optional_output end\]\] ne {} } {"

    template::adp_compile_chunk $chunk
    template::adp_append_code "
    }
    template::util::lpop __adp_include_optional_output
  }
  "

    #End developer support frame around subordinate template.
    if { $ds && [namespace which ::ds_enabled_p] ne ""
         && [namespace which ::ds_adp_end_box] ne "" } {
        ::ds_adp_end_box -stub [
                                subst -nocommands {[template::util::url_to_file "$src" "\$__adp_stub"]}]
    }

}

template::tag include-output { params } {
    #
    # Insert the output from the include-optional tag
    #
    # @author Lars Pind (lars@collaboraid.net)
    #

    template::adp_append_code {
        if { [info exists __adp_include_optional_output] } {
            append __adp_output [lindex $__adp_include_optional_output end]
        }
    }
}

template::tag trn { chunk params } {
    # DRB: we have to do our own variable substitution as the template
    # framework doesn't handle it for us ... being able to use page
    # variables here is consistent with the rest of the templating
    # engine.
    # LARS: Note that this version of the <TRN> tag requires a body,
    # like this: <trn key="...">default</trn>.
    # This is the way to give a default value, and is okay, because we
    # now have the #...# notation for when there's no default value.
    # Will register the key in the message catalog if it doesn't exist.

    foreach {key value} [ns_set array $params] {
        # substitute array variables
        regsub {@([a-zA-Z0-9_]+)\.([a-zA-Z0-9_.]+)@} $value {${\1(\2)}$key
        # substitute regular variables
        regsub {@([a-zA-Z0-9_:]+)@} $value {${\1}$key
    }

    # And this needs to be executed at page execution time due to
    # interactions with the preferences changing code.  This is
    # consistent with what the way #notation# is handled (the ad_conn
    # call is dumped into the code, not executed on the spot)

    if { ![info exists locale] } {
        # We need this to be executed at template execution time,
        # because the template's compiled code will be cached and
        # reused for many requests.
        set locale "\[ad_conn locale\]"
    } else {
        # Check to see if we should register this into the message
        # catalog
        if { [string length $locale] == 2 } {
            set locale [lang::util::default_locale_from_lang $locale]
        }

        # Check the cache
        if { ![lang::message::message_exists_p $locale $key] } {
            lang::message::register $locale $key $chunk
        }
    }

    # quote dollar signs, square bracket and quotes
    regsub -all -- {[\]\[""\\$]} $chunk {\\&} quoted_chunk

    template::adp_append_code \
        [subst -nocommands {append __adp_output [_ $locale $key $quoted_chunk]}]
}

template::tag switch { chunk params } {
    #
    # DanW: implements a switch statement just like in Tcl
    # use as follows:
    #
    # <switch flag=regexp @some_var@>
    #     <case in "foo" "bar" "baz">
    #         Foo, Bar or Baz was selected
    #     </case>
    #     <case value="a.+">
    #         A was selected
    #     </case>
    #     <case value="b.+">
    #         B was selected
    #     </case>
    #     <case value="c.+">
    #         C was selected
    #     </case>
    #     <default>
    #         Not a valid selection
    #     </default>
    # </switch>
    #
    # The flag switch is optional and it defaults to exact if not specified.
    # Valid values are exact, regexp, and glob
    #

    set sw ""
    set arg ""

    # get the switch flags and the switch var
    foreach {key value} [ns_set array $params] {
        if {$key eq $value} {
            set arg $key
        } elseif {$key eq "flag"} {
            append sw " -$value "
        }
    }

    # append the switch statement and eval tags in between

    template::adp_append_code "switch $sw -- $arg {"
    template::adp_compile_chunk $chunk
    template::adp_append_code "}"
}

template::tag case { chunk params } {
    #
    # case tag, to be used inside of the switch tag
    #

    # Scan the parameter stack backward, looking for the tag name

    set tag_id [template::enclosing_tag switch]
    if {$tag_id eq {}} {
        error "No enclosing SWITCH tag for CASE tag on value $value"
    }

    # get the case value

    set value [ns_set iget $params value]

    # insert the case statement and eval the chunk in between

    if { $value ne "" } {

        # processing <case value= ...> form

        template::adp_append_code "[list $value] {" -nobreak
        template::adp_compile_chunk $chunk
        template::adp_append_code "}"

    } else {

        # processing <case in ...> form

        set switches ""
        set size [ns_set size $params]
        set size_1 [expr {$size - 1}]

        for { set i 0 } { $i < $size } { incr i } {

            set key [ns_set key $params $i]
            set value [ns_set value $params $i]

            # pass over the first arg (syntax sugar), but check format
            if { $i == 0 } {

                if {$key ne "in" } {
                    error "Format error: should be <case in \"foo\" \"bar\" ...>"
                }

            } else {

                if {$key eq $value} {

                    # last item in list so process the chunk
                    if { $i == $size_1 } {

                        template::adp_append_code "$switches $value {" -nobreak
                        template::adp_compile_chunk $chunk
                        template::adp_append_code "}"

                    } else {

                        # previous items default to pass-through
                        append switches $key - "
                    }

                } else {
                    error "Format error: should be <case in \"foo\" \"bar\" ...>"
                }
            }
        }
    }
}

template::tag default { chunk params } {
    #
    # default value for case statement which is a sub-tag in switch
    # tag
    #

    # Scan the parameter stack backward, looking for the tag name

    set tag_id [template::enclosing_tag switch]
    if {$tag_id eq {}} {
        error "No enclosing SWITCH tag for DEFAULT tag"
    }

    # insert the default value and evaluate the chunk

    template::adp_append_code "default {" -nobreak
    template::adp_compile_chunk $chunk
    template::adp_append_code "}"
}

template::tag contract { chunk params } {
    #
    # contract tag for adding inline
    # documentation to adp files.
    #
    # @author Ben Bytheway (ben@vrusp.com)
    #
}

template::tag comment { chunk params } {
    #
    # comment tag for adding inline
    # documentation to adp files.
    #
    # @author Ben Bytheway (ben@vrusp.com)
    #
}



template::tag box { chunk params } {
    set class [ns_set iget $params class]
    set title [ns_set iget $params title]

    template::adp_append_code [subst -nocommands {append __adp_output "
<div class='portlet-wrapper'>
<div class='portlet-header'>
<div class='portlet-title-no-controls'>
<h1>$title</h1>
</div>
</div>
<div class='portlet'>"}]
    template::adp_compile_chunk $chunk
    template::adp_append_code "append __adp_output {</div></div>}"
}



template::tag adp:icon { params } {
    set d [::template::icon \
               -alt [ns_set iget $params alt] \
               -class [ns_set iget $params class] \
               -iconset [ns_set iget $params iconset] \
               -name [ns_set iget $params name] \
               -style [ns_set iget $params style] \
               -title [ns_set iget $params title] \
               -invisible=[ns_set iget $params invisible f] \
              ]
    dict with d {
        template::adp_append_string $HTML
        if {$cmd ne ""} {
            template::adp_append_code $cmd
        }
    }
}

# template::tag adp:class { params } {
#     #
#     # Not sure, we need toolkit switching, but it is useful for
#     # testing purposes
#     #
#     set toolkit [ns_set iget $params toolkit ""]
#     if {$toolkit ne ""} {
#         template::adp_append_string [::template::CSS class -toolkit $toolkit [ns_set iget $params name]]
#     } else {
#         template::adp_append_string [::template::CSS class [ns_set iget $params name]]
#     }
# }

template::tag adp:button { chunk params } {
    #
    # Implementation of value added <button> with specialized
    # attribute handling:
    #
    #    <adp:button ...>...</adp:button>
    #
    # Specially handled attributes for <adp:button ...> are:
    #    - "data-*" use potentially the bootstrap5 prefix
    #               (we still want to be able to use other data-* attributes without the prefix)
    #    - "class"  map provided CSS class names to tooklit specific class names
    #
    set data [expr {[template::toolkit] eq "bootstrap5" ? "data-bs" : "data"}]
    set attributes ""
    foreach {key value} [ns_set array $params] {
        switch -glob $key {
            data-dismiss -
            data-toggle -
            data-target {
                if {$data ne "data"} {
                    set suffix [string range $key 5 end]
                    append attributes $data-[string range $key 5 end]='$value'"
                } else {
                    append attributes $key='$value'"
                }
            }
            class   { append attributes $key='[::template::CSS classes $value]'" }
            default { append attributes $key='$value'" }
        }
    }
    template::adp_append_string "<button $attributes>"
    template::adp_compile_chunk $chunk
    template::adp_append_string "</button>"
}


template::tag adp:toggle_button { chunk params } {
    #
    # Implementation of dropdown-toggles, which have to be - depending
    # on the toolkit - realized sometimes as "<a ....>" and sometimes
    # "<button>" markups.
    #
    #    <adp:toggle_button>....</adp:toggle_button>
    #
    # Potential attributes for <adp:toggle_button ...> are:
    #    - "id"
    #    - "tag" (default "button")
    #    - "toggle"
    #    - "target"
    #    - "type" (default "button", just used for "button" tag)
    #    - "href" (default "#", just used for "a" tag)
    #
    # Use <... tag="a"... > for use of tabs with Bootstrap 3.
    #
    # Bootstrap3 is picky and allows just "<a>" tags for the
    # navigation buttons, whereas Bootstrap4 and 5 would also allow
    # <button> with appropriate classes for nav-items and nav-links.
    #
    set data [expr {[template::toolkit] eq "bootstrap5" ? "data-bs" : "data"}]

    set id [ns_set iget $params id ""]
    set tag [ns_set iget $params tag button]

    set attributes [expr {$id eq "" ? "" : "id='$id'"}]
    switch $tag {
        "button" {append attributes " type='[ns_set iget $params type button]'"}
        "a"      {append attributes " href='[ns_set iget $params href #]'"}
    }
    append attributes \
        " class='[ns_set iget $params class]'" \
        $data-toggle='[ns_set iget $params toggle]'" \
        $data-target='[ns_set iget $params target]'"
    template::adp_append_string "<$tag $attributes>"
    template::adp_compile_chunk $chunk
    template::adp_append_string "</$tag>"
}


# Local variables:
#    mode: tcl
#    tcl-indent-level: 4
#    indent-tabs-mode: nil
# End: