rivet/packages/calendar/calendar.tcl (415 lines of code) (raw):
# calendar.tcl ---
#
#
# Copyright 2010 The Apache Rivet Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# $Id: calendar.tcl 916 2010-07-03 00:37:44Z massimo.manghi $
#
package provide Calendar 1.2
package require Itcl
# Calendar: base class to create a calendar table.
#
# Calendar prints an ascii calendar following the output form of a Unix
# 'cal' command. Even though it can be used as a concrete class it was
# designed to have methods and mechanisms abstract enough to be easly
# customized and specialized through derivation of other classes (see XmlCalendar)
#
# The output of Calendar (method 'emit')
#
#
# Jun 2010 | header | banner
# Su Mo Tu We Th Fr Sa | | weekdays
# 1 2 3 4 5 | table
# 6 7 8 9 10 11 12 |
# 13 14 15 16 17 18 19 |
# 20 21 22 23 24 25 26
# 27 28 29 30
#
#
::itcl::class Calendar {
public common month_names
public common day_names
private variable month_year_processed {}
# language to be used: key to be used in 'month_names'
# and in case in other databases
public variable language en
private method numberOfDays { month year }
private method cal { month year }
protected method weekdays { }
protected method banner { mth yr }
protected method header { mth yr }
protected method first_week { mth yr wkday }
protected method formatDayCell { day }
protected method openRow { wkn }
protected method closeRow { }
protected method table { mth yr }
protected method startOutput { }
protected method closeOutput { }
public method cal_processed {} { return $month_year_processed }
public method emit { args }
constructor {args} {
set month_names(en) { Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec }
set month_names(it) { Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic }
set day_names(en) { Su Mo Tu We Th Fr Sa }
set day_names(it) { Do Lu Ma Me Gi Ve Sa }
}
}
# numberOfDays <month> <year>: private method that returns the number of days in
# the current month.
#
::itcl::body Calendar::numberOfDays {month year} {
if {$month == 12} { set month 1; incr year }
return [clock format [clock scan "[incr month]/1/$year 1 day ago"] -format %d]
}
::itcl::body Calendar::banner {month_idx yr} {
set month_name [lindex $month_names($language) $month_idx]
return " $month_name $yr\n"
}
::itcl::body Calendar::weekdays {} {
return "$day_names($language)\n"
}
# header <month_idx> <year>
# returns the header of the calendar table. The header is made of a banner (e.g. "Jul 2010")
# and a list of the weekdays (Su Mo ... Sa)
#
# Arguments: <month_idx> month index (0: jan, 11: dec).
# <year> year number.
#
# Returned value: text of the cal table header.
#
::itcl::body Calendar::header {mth_idx yr} {
return "[$this banner $mth_idx $yr][$this weekdays]"
}
# first_week: cal tables are organized in columns corresponding to weekdays (from Sunday to Saturday).
# first_week returns as many blank cells as the number of weekdays starting from Sun up to the first day of the
# month.
#
::itcl::body Calendar::first_week {month_idx year weekday} {
return [string repeat " " $weekday]
}
::itcl::body Calendar::formatDayCell { day } { return [format %3d $day] }
::itcl::body Calendar::openRow { wkn } { return "" }
::itcl::body Calendar::closeRow { } { return "\n" }
# table <month> <year>:
::itcl::body Calendar::table {month_idx year} {
set wk 0
set tbl [$this openRow $wk]
set month [lindex $month_names(en) $month_idx]
set weekday [clock format [clock scan "1 $month $year"] -format %w]
append tbl [$this first_week $month_idx $year $weekday]
scan [clock format [clock scan "1 $month $year"] -format %m] %d decm
set maxd [numberOfDays $decm $year]
for {set d 1} {$d <= $maxd} {incr d} {
if {$weekday == 0} {
incr wk
append tbl [$this openRow $wk]
}
append tbl [formatDayCell $d]
if {[incr weekday] > 6} {append tbl [$this closeRow]; set weekday 0}
}
return $tbl
}
# abstract base methods for starting and closing the output buffer.
::itcl::body Calendar::startOutput {} { return "" }
::itcl::body Calendar::closeOutput {} { return "" }
# cal <month> <year>: cal does the real heavy lifting of building the
# calendar table. cal is designed to be the most abstract possible:
# - the output buffer is initialized by startOutput (this class does nothing)
# - the output buffer is filled with the header: in the classical Unix cal
# command output this corresponds to the 2 lines showing the year, the month and
# the weekdays
# - the output buffer is appended filled with the actual table of days of the month
# - the output is closed. This class does basically nothing
::itcl::body Calendar::cal {month_idx year} {
set month_year_processed [list $month_idx $year]
set res [$this startOutput]
append res [$this header $month_idx $year]
append res [$this table $month_idx $year]
append res [$this closeOutput]
return $res
}
# emit args:
#
# emit returns the text of the calendar. If one argument is passed
# to this method its value is taken as a year number and the whole
# calendar for that year is printed, thus cycling this same method
# for each month of the year and concatenating the output in a single
# buffer. If 2 arguments are passed emit interprets them as month
# and year. <month> can be specified both in number (1-12) or
# abbreviated name (Jan,Feb,....,Dec). A minimal support for other
# languages exists. If no arguments are passed to 'emit' the current
# month calendar is displayed.
#
::itcl::body Calendar::emit { args } {
set argsnumber [llength $args]
# if we have just one argument therefore it be an year and we proceed to
# generate a whole year calendar, otherwise we have to examine possible
# options and values
if {$argsnumber > 1} {
if {$argsnumber%2 == 0} {
set primo_chr [string range [lindex $args 0] 0 0]
if {$primo_chr == "-"} {
# we proceed to eval import_arguments $args
set numeric_parameters {}
eval $this configure $args
} else {
# arguments number is even. If the first switch is not an option (-opt)
# we assume we are passing 2 parameters to the methods, while the
# remaining list are actually an -opt val pairs list
# we assume the rest of the args are in the form -opt1 val1 -opt2 val2 ...
# we proceed to eval import_arguments [lrange $args 2 end]
set numeric_parameters [lrange $args 0 1]
eval $this configure [lrange $args 2 end]
}
} else {
# we assume the rest of the args are in the form -opt1 val1 -opt2 val2 ...
# and then we eval import_arguments [lrange $args 1 end]
set numeric_parameters [lrange $args 0 0]
eval $this configure [lrange $args 1 end]
}
} else {
set numeric_parameters $args
}
set argsnumber [llength $numeric_parameters]
switch $argsnumber {
1 {
# if only one argument is passed to this procedure then we treat it as either as a
# year (therefore must be a number) or a month name of the current year
if {[regexp {^[0-9]+$} $numeric_parameters]} {
set res {}
set year $numeric_parameters
for {set m 0} {$m < 12} {incr m} {
append res [cal $m $year]\n\n
}
return [string trimright $res]
}
set month_idx [lsearch $month_names($language) $numeric_parameters]
if {$month_idx >= 0} {
set year [clock format [clock sec] -format %Y]
return [cal $month_idx $year]
} else {
return ""
}
}
2 {
# two args: the first is the month, the second the year.
set month [lindex $numeric_parameters 0]
set year [lindex $numeric_parameters 1]
if {[regexp {^\d{1,2}$} $month mat] && ($month > 0) && ($month <= 12)} {
return [cal [incr month -1] $year]
} elseif { [lsearch $month_names($language) $month] >= 0} {
return [cal [lsearch $month_names($language) $month] $year]
}
}
0 -
default {
# no arguments, we take today as reference
scan [clock format [clock seconds] -format %m] "%d" month
set year [format "%d" [clock format [clock sec] -format %Y]]
return [cal [incr month -1] $year]
}
}
}
# XmlCalendar: XmlCalendar inherits the table structure of Calendar and
# adds XML markup to a calendar table. The design is driven by the layout
# of a calendar table. This is probably a rather naive approach.
# A better implementation would require separate data and layout classes,
# but it's only a calendar table anyway
::itcl::class XmlCalendar {
inherit Calendar
private method validateWeekday { wkd }
# dictionary of table generation parameters (tag , attributes). key for the dictionary can be
#
# - container:
# - header
# - weekdays
# - days_row
# - days_cell
#
# for every key a 'tag' and 'attr' key is defined. attr is a even-length list storing
# attribute-value pairs
public variable parameters
# we are emitting (x)html code that has to be encapsulated
# in this root element. If the value is a list the first element is
# the tag name and the rest is treated as a list of <attr>,<value pairs
# so this list has to have an odd length
# These public variables are listed in order to enable the corresponding configuration options:
#
# $calObj configure -current_day 4 -container table -banner ....
#
# They work as transit variables as the values are actually stored in the dictionary 'parameters'
#
public variable container {} { $this expandValues container $container }
public variable header {} { $this expandValues header $header }
public variable body {} { $this expandValues body $body }
public variable foot {} { $this expandValues foot $foot }
public variable banner {} { $this expandValues banner $banner }
public variable banner_month {} { $this expandValues banner_month $banner_month }
public variable banner_year {} { $this expandValues banner_year $banner_year }
public variable weekdays {} { $this expandValues wkdays_bar $weekdays }
public variable weekday_cell {} { $this expandValues wkday_cell $weekday_cell }
public variable days_row {} { $this expandValues days_row $days_row }
public variable days_cell {} { $this expandValues days_cell $days_cell }
public variable cell_function ""
public variable current_day 0
public variable current_weekday -1 { $this validateWeekday $current_weekday }
private method expandValues { element values_list }
protected method startOutput { }
protected method closeOutput { }
protected method mkOpenTag { tag {attrib {}} }
protected method mkCloseTag { tag }
protected method header { mth yr }
protected method table { mth yr }
protected method weekdays { }
protected method banner { mth yr }
protected method first_week { mth yr wkday }
protected method openRow { wkn }
protected method closeRow { }
protected method formatDayCell { day }
protected method getParameters { param what }
constructor {args} {Calendar::constructor $args} {
set parameters [dict create container {tag "calendar" attr "" } \
header {tag "calheader" attr "" } \
body {tag "calbody" attr "" } \
foot {tag "calfoot" attr "" } \
banner {tag "monthyear" attr "" } \
banner_month {tag "month" attr "" } \
banner_year {tag "year" attr "" } \
wkdays_bar {tag "weekdays" attr "" } \
wkday_cell {tag "wkday" attr "" } \
days_row {tag "week" attr "" } \
days_cell {tag "day" attr "" }]
}
}
::itcl::body XmlCalendar::getParameters {param what} {
if {[dict exists $parameters $param $what]} {
return [dict get $parameters $param $what]
} else {
return ""
}
}
::itcl::body XmlCalendar::expandValues { element value_list } {
dict set parameters $element tag [lindex $value_list 0]
dict set parameters $element attr [lrange $value_list 1 end]
}
::itcl::body XmlCalendar::validateWeekday { wkd } {
if {$wkd == "today"} {
set current_weekday [clock format [clock scan today] -format %w]
}
}
::itcl::body XmlCalendar::startOutput {} {
return [$this mkOpenTag [getParameters container tag] [getParameters container attr]]
}
::itcl::body XmlCalendar::closeOutput {} {
return [$this mkCloseTag [getParameters container tag]]
}
::itcl::body XmlCalendar::mkOpenTag {tag {attrib {}}} {
set open_tag "<$tag"
foreach {a v} $attrib {
append open_tag " $a=\"$v\""
}
append open_tag ">"
return $open_tag
}
::itcl::body XmlCalendar::mkCloseTag {tag} { return "</$tag>" }
# The Xml header is made of a banner (i.e Month Year) and
# a bar showing the weekdays with their markup.
#
::itcl::body XmlCalendar::header {mth_idx yr} {
set header_tag [getParameters header tag]
set header_att [getParameters header attr]
return "[mkOpenTag $header_tag $header_att][Calendar::header $mth_idx $yr][mkCloseTag $header_tag]\n"
}
::itcl::body XmlCalendar::weekdays { } {
set rowtag [getParameters wkdays_bar tag]
set xml [mkOpenTag $rowtag]
set tagname [getParameters wkday_cell tag]
set wdn 0
foreach dn $day_names($language) {
if {$wdn == $current_weekday} {
append xml "[mkOpenTag $tagname {class current_wkday}]$dn[mkCloseTag $tagname]"
} else {
append xml "[mkOpenTag $tagname]$dn[mkCloseTag $tagname]"
}
incr wdn
}
append xml [mkCloseTag $rowtag]
return $xml
}
::itcl::body XmlCalendar::banner {month_idx yr} {
set month_name [lindex $month_names($language) $month_idx]
set header_tag [getParameters banner tag]
set month_open_tag [mkOpenTag [getParameters banner_month tag] [getParameters banner_month attr]]
set year_open_tag [mkOpenTag [getParameters banner_year tag] [getParameters banner_year attr]]
set banner_html [mkOpenTag $header_tag]
append banner_html "${month_open_tag}${month_name}[mkCloseTag [getParameters banner_month tag]]"
append banner_html "${year_open_tag}$yr[mkCloseTag [getParameters banner_year tag]]"
append banner_html [mkCloseTag $header_tag]
return $banner_html
}
::itcl::body XmlCalendar::formatDayCell { day } {
set tagname [getParameters days_cell tag]
set tagattr [getParameters days_cell attr]
array set attributes $tagattr
if {$day == $current_day} {
set attributes(class) current
}
if {$cell_function != "" && $day != ""} {
set month_year [$this cal_processed]
set month [lindex $month_names(en) [lindex $month_year 0]]
set year [lindex $month_year 1]
set wkday [clock format [clock scan "$month $day $year"] -format %w]
array set attributes [eval $cell_function $day $month_year $wkday]
}
set tagattr [array get attributes]
return "[mkOpenTag $tagname $tagattr]$day[mkCloseTag $tagname]"
}
::itcl::body XmlCalendar::first_week { mth yr wkday } {
set emptyCell [formatDayCell ""]
return [string repeat $emptyCell $wkday]
}
::itcl::body XmlCalendar::table {month_idx year} {
set body_tag [getParameters body tag]
set body_att [getParameters body attr]
return "[mkOpenTag $body_tag $body_att][Calendar::table $month_idx $year][mkCloseTag $body_tag]\n"
}
::itcl::body XmlCalendar::openRow { wkn } {
set tagname [getParameters days_row tag]
set attributes [concat class week_${wkn} [getParameters days_row attr]]
return [mkOpenTag $tagname $attributes]
}
::itcl::body XmlCalendar::closeRow {} {
set tagname [getParameters days_row tag]
return "[mkCloseTag $tagname]\n"
}
# HtmlCalendar: concrete class for generating Html formatted cal output.
#
#
::itcl::class HtmlCalendar {
inherit XmlCalendar
constructor {args} {XmlCalendar::constructor $args} {
$this configure -container table \
-header thead \
-body tbody \
-banner tr \
-banner_month {th colspan 3 style "text-align: right;"} \
-banner_year {th colspan 4 style "text-align: left;"} \
-weekdays tr \
-weekday_cell th \
-days_row tr \
-days_cell td
}
}