Move "holiday" functions into its own module named libholiday. libholiday is used by all three calendar based reports.

svn: r11759
This commit is contained in:
Brian Matherly 2009-01-30 02:19:04 +00:00
parent 497e8d8233
commit 1adf052e55
6 changed files with 1525 additions and 1351 deletions

View File

@ -10,6 +10,7 @@ SUBDIRS = \
gramplet \
graph \
import \
lib \
quickview \
rel \
textreport \
@ -20,7 +21,6 @@ pkgdatadir = $(datadir)/@PACKAGE@/plugins
pkgdata_PYTHON = \
BookReport.py\
Calendar.py \
ExportXml.py\
ImportGpkg.py\
ImportXml.py\

View File

@ -2,8 +2,10 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Thom Sturgill
# Copyright (C) 2007-2008 Brian G. Matherly
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2008 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2008 Jason Simanek
# Copyright (C) 2008 Kees Bakker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Pubilc License as published by
@ -25,21 +27,6 @@
"""
Web Calendar generator.
Menu selection: Reports -> Web Page -> Web Calendar
Created 4/22/07 by Thom Sturgill based on Calendar.py (with patches)
by Doug Blank with input dialog based on NarrativeWeb.py by Don Allingham.
2008-05-11 Jason Simanek
Improving markup for optimal separation of content and presentation.
2008-June-22 Rob G. Healey
*** Remove StyleEditor, make it css based as is NarrativeWeb,
move title to first tab, re-word note tabs, complete re-write of
calendar build, added year glance, and blank year, added easter and
dst start/stop from Calendar.py, etc.
2008 Kees Bakker
Refactoring. This is an ongoing job until this plugin is in a better shape.
TODO list:
- change filename for one_day pages to yyyy/mm/dd.html (just numbers)
@ -60,12 +47,9 @@ import os
import time
import datetime
import calendar
import math
import codecs
import shutil
from gettext import gettext as _
from xml.parsers import expat
#------------------------------------------------------------------------
#
@ -90,11 +74,13 @@ from gen.plug.menu import BooleanOption, NumberOption, StringOption, \
DestinationOption
import Utils
import GrampsLocale
from QuestionDialog import ErrorDialog, WarningDialog
from QuestionDialog import WarningDialog
from Utils import probably_alive
from DateHandler import displayer as _dd
from DateHandler import parser as _dp
import libholiday
#------------------------------------------------------------------------
#
# constants
@ -256,8 +242,8 @@ class WebCalReport(Report):
self.author = self.author.replace(',,,', '')
self.email = researcher.email
self.start_month = 1 # set to January, and it can change
self.end_month = 12 # set to December, this value never changes
self.start_month = 1 # set to January, and it can change
self.end_month = 12 # set to December, this value never changes
today = time.localtime() # set to today's date
self.today = datetime.date(today[0], today[1], today[2])
@ -316,68 +302,22 @@ class WebCalReport(Report):
except ValueError:
my_date = '...'
else:
my_date = '...' #Incomplete date as in about, circa, etc.
my_date = '...' #Incomplete date as in about, circa, etc.
day_list.append((text, event, my_date))
month_dict[day] = day_list
self.calendar[month] = month_dict
def get_holidays(self, year, country="United States"):
"""
Looks in multiple places for holidays.xml file.
the holidays file will be used first if it exists in user's plugins, otherwise,
the GRAMPS plugins will be checked.
"""
holiday_file = 'holidays.xml'
holiday_full_path = ""
fname1 = os.path.join(const.USER_PLUGINS, holiday_file)
fname2 = os.path.join(const.PLUGINS_DIR, holiday_file)
if os.path.exists(fname1):
holiday_full_path = fname1
elif os.path.exists(fname2):
holiday_full_path = fname2
if holiday_full_path != "":
self.process_holiday_file(year, holiday_full_path, country)
def process_holiday_file(self, year, filename, country):
"""
This will process the holidays file for the selected country.
All holidays, except Easter, and Daylight Saving start/ stop, will be
processed from the holidays.xml file. Easter and DST will be handled by
specific mathematical formulas within this plugin ...
"Easter" -- _easter()
"Daylight Saving Time" -- _get_dst_start_stop()
0 = year, 1 = month, 2 = day
"""
parser = Xml2Obj()
element = parser.Parse(filename)
holidays_calendar = Holidays(element, country)
date = datetime.date(year, 1, 1)
while date.year == year:
holidays = holidays_calendar.check_date(date)
for text in holidays:
if text == "Easter": # TODO. Verify if this needs translation, and how
easter = _easter(year)
self.add_holiday_item(_("Easter"), easter.get_year(), easter.get_month(), easter.get_day())
elif text == "Daylight Saving begins":
# TODO. There is more than USA and Europe.
if Utils.xml_lang() == "en-US": # DST for United States of America
dst_start, dst_stop = _get_dst_start_stop(year)
else: # DST for Europe
dst_start, dst_stop = _get_dst_start_stop(year, "eu")
self.add_holiday_item(_("Daylight Saving begins"), \
dst_start[0], dst_start[1], dst_start[2])
self.add_holiday_item(_("Daylight Saving ends"), \
dst_stop[0], dst_stop[1], dst_stop[2])
elif text == "Daylight Saving ends":
pass # end is already done above
else: # not easter, or Daylight Saving Time
self.add_holiday_item(text, date.year, date.month, date.day)
date = date.fromordinal( date.toordinal() + 1)
def __get_holidays(self, year):
""" Get the holidays for the specified country and year """
holiday_table = libholiday.HolidayTable()
country = holiday_table.get_countries()[self.country]
holiday_table.load_holidays(year, country)
for month in range(1, 13):
for day in range(1, 32):
holiday_names = holiday_table.get_holidays(month, day)
for holiday_name in holiday_names:
self.add_holiday_item(holiday_name, year, month, day)
def add_holiday_item(self, text, year, month, day):
if day == 0:
@ -481,7 +421,8 @@ class WebCalReport(Report):
of.write('\t<ul>\n')
cols = 0
cal_year = self.start_year
while ((0 <= cols < 25) and (self.start_year <= cal_year <= self.end_year)):
while ((0 <= cols < 25) and
(self.start_year <= cal_year <= self.end_year)):
url = ''
cs = False
@ -497,7 +438,8 @@ class WebCalReport(Report):
if cal_year == self.today.year:
lng_month = _get_long_month_name(self.today.month)
# Note. We use '/' here because it is a URL, not a OS dependent pathname
# Note. We use '/' here because it is a URL, not a OS dependent
# pathname.
url = '/'.join(subdirs + [lng_month]) + self.ext
# determine if we need to highlight???
@ -518,7 +460,8 @@ class WebCalReport(Report):
of.write('\t</ul>\n')
of.write('</div>\n\n')
def calendar_common(self, of, nr_up, year, currsec1, title, body_id, use_home=False, add_print=True):
def calendar_common(self, of, nr_up, year, currsec1, title, body_id,
use_home=False, add_print=True):
"""
Will create the common information for each calendar being created
"""
@ -591,13 +534,13 @@ class WebCalReport(Report):
day = col2day[col]
return day_names[day]
# monthinfo is filled using standard Python library calendar.monthcalendar
# It fills a list of 7-day-lists. The first day of the 7-day-list is
# determined by calendar.firstweekday
# monthinfo is filled using standard Python library
# calendar.monthcalendar. It fills a list of 7-day-lists. The first day
# of the 7-day-list is determined by calendar.firstweekday.
monthinfo = calendar.monthcalendar(year, month)
# Begin calendar head. We'll use the capitalized name, because here it seems
# appropriate for most countries.
# Begin calendar head. We'll use the capitalized name, because here it
# seems appropriate for most countries.
month_name = lng_month.capitalize()
th_txt = month_name
if cal == 'wc': # normal_cal()
@ -653,11 +596,11 @@ class WebCalReport(Report):
dayclass = get_class_for_daycol(day_col)
day = week[day_col]
if day == 0: # a day in the previous or next month
if week_row == 0: # a day in the previous month
if day == 0: # a day in the previous or next month
if week_row == 0: # a day in the previous month
specday = lastweek_prevmonth[day_col]
specclass = "previous " + dayclass
elif week_row == nweeks-1: # a day in the next month
elif week_row == nweeks-1: # a day in the next month
specday = firstweek_nextmonth[day_col]
specclass = "next " + dayclass
@ -665,7 +608,7 @@ class WebCalReport(Report):
of.write('\t\t\t\t<div class="date">%d</div>\n' % specday)
of.write('\t\t\t</td>\n')
else: # normal day number in current month
else: # normal day number in current month
thisday = datetime.date.fromordinal(current_ord)
of.write('\t\t\t<td id="%s%02d" ' % (shrt_month, day))
if thisday.month == month: # Something this month
@ -799,7 +742,6 @@ class WebCalReport(Report):
# Note. We use '/' here because it is a URL, not a OS dependent pathname
fname = '/'.join(subdirs + ['images'] + ['somerights20.gif'])
text = _CC[copy_nr] % {'gif_fname' : fname}
self.use_copyright = True
else:
text = "&copy; %s %s" % (self.today.year, self.author)
of.write('\t<p id="copyright">%s</p>\n' % text)
@ -981,9 +923,9 @@ class WebCalReport(Report):
# initialize the holidays dict to fill:
self.holidays = {}
# get the information from holidays for every year being created
if self.country != 0: # Don't include holidays
self.get_holidays(cal_year, _COUNTRIES[self.country]) # _country is currently global
# get the information, first from holidays:
if self.country != 0:
self.__get_holidays(cal_year)
# adjust the months being created if self.partyear is True,
# and if the year is the current year, then start month is current month
@ -1004,10 +946,10 @@ class WebCalReport(Report):
cal_year = self.start_year
self.holidays = {}
# get the information from holidays for each year being created
if self.country != 0: # Don't include holidays
self.get_holidays(cal_year, _COUNTRIES[self.country]) # _COUNTRIES is currently global
# get the information, first from holidays:
if self.country != 0:
self.__get_holidays(cal_year)
# generate progress pass for single year
#self.progress.set_pass(_('Creating calendars'), self.end_month - self.start_month)
@ -1263,7 +1205,7 @@ class WebCalOptions(MenuReportOptions):
# set to today's date for use in menu, etc.
# 0 = year, 1 = month, 2 = day
today = time.localtime()
self.today = datetime.date(today[0], today[1], today[2])
today = datetime.date(today[0], today[1], today[2])
partyear = BooleanOption(_('Create Partial Year calendar'), False)
partyear.set_help(_('Create a partial year calendar. The start month will be'
@ -1275,11 +1217,11 @@ class WebCalOptions(MenuReportOptions):
menu.add_option(category_name, 'multiyear', self.__multiyear)
self.__multiyear.connect('value-changed', self.__multiyear_changed)
self.__start_year = NumberOption(_('Start Year for the Calendar(s)'), self.today.year, 1900, 3000)
self.__start_year = NumberOption(_('Start Year for the Calendar(s)'), today.year, 1900, 3000)
self.__start_year.set_help(_('Enter the starting year for the calendars between 1900 - 3000'))
menu.add_option(category_name, 'start_year', self.__start_year)
self.__end_year = NumberOption(_('End Year for the Calendar(s)'), self.today.year, 1900, 3000)
self.__end_year = NumberOption(_('End Year for the Calendar(s)'), today.year, 1900, 3000)
self.__end_year.set_help(_('Enter the ending year for the calendars between 1900 - 3000.'
' if multiple years is selected, then only twenty years at any given time'))
menu.add_option(category_name, 'end_year', self.__end_year)
@ -1291,7 +1233,8 @@ class WebCalOptions(MenuReportOptions):
menu.add_option(category_name, 'fullyear', fullyear)
country = EnumeratedListOption(_('Country for holidays'), 0 )
for index, item in enumerate(_COUNTRIES):
holiday_table = libholiday.HolidayTable()
for index, item in enumerate(holiday_table.get_countries()):
country.add_item(index, item)
country.set_help(_("Holidays will be included for the selected "
"country"))
@ -1417,292 +1360,6 @@ class WebCalOptions(MenuReportOptions):
else:
self.__end_year.set_available(False)
#------------------------------------------------------------------------
#
#
#
#------------------------------------------------------------------------
class Element:
""" A parsed XML element """
def __init__(self, name, attributes):
'Element constructor'
# The element's tag name
self.name = name
# The element's attribute dictionary
self.attributes = attributes
# The element's cdata
self.cdata = ''
# The element's child element list (sequence)
self.children = []
def addChild(self, element):
'Add a reference to a child element'
self.children.append(element)
def getAttribute(self, key):
'Get an attribute value'
return self.attributes.get(key)
def getData(self):
'Get the cdata'
return self.cdata
def getElements(self, name=''):
'Get a list of child elements'
#If no tag name is specified, return the all children
if not name:
return self.children
else:
# else return only those children with a matching tag name
elements = []
for element in self.children:
if element.name == name:
elements.append(element)
return elements
def toString(self, level=0):
retval = " " * level
retval += "<%s" % self.name
for attribute in self.attributes:
retval += " %s=\"%s\"" % (attribute, self.attributes[attribute])
c = ""
for child in self.children:
c += child.toString(level+1)
if c == "":
retval += "/>\n"
else:
retval += ">\n" + c + ("</%s>\n" % self.name)
return retval
class Xml2Obj:
""" XML to Object """
def __init__(self):
self.root = None
self.nodeStack = []
def StartElement(self, name, attributes):
'SAX start element even handler'
# Instantiate an Element object
element = Element(name.encode(), attributes)
# Push element onto the stack and make it a child of parent
if len(self.nodeStack) > 0:
parent = self.nodeStack[-1]
parent.addChild(element)
else:
self.root = element
self.nodeStack.append(element)
def EndElement(self, name):
'SAX end element event handler'
self.nodeStack = self.nodeStack[:-1]
def CharacterData(self, data):
'SAX character data event handler'
if data.strip():
data = data.encode()
element = self.nodeStack[-1]
element.cdata += data
return
def Parse(self, filename):
# Create a SAX parser
Parser = expat.ParserCreate()
# SAX event handlers
Parser.StartElementHandler = self.StartElement
Parser.EndElementHandler = self.EndElement
Parser.CharacterDataHandler = self.CharacterData
# Parse the XML File
ParserStatus = Parser.Parse(open(filename, 'r').read(), 1)
return self.root
class Holidays:
""" Class used to read XML holidays to add to calendar. """
def __init__(self, elements, country="US"):
self.debug = 0
self.elements = elements
self.country = country
self.dates = []
self.initialize()
def set_country(self, country):
self.country = country
self.dates = []
self.initialize()
def initialize(self):
# parse the date objects
for country_set in self.elements.children:
if country_set.name == "country" and country_set.attributes["name"] == self.country:
for date in country_set.children:
if date.name == "date":
data = {"value" : "",
"name" : "",
"offset": "",
"type": "",
"if": "",
} # defaults
for attr in date.attributes:
data[attr] = date.attributes[attr]
self.dates.append(data)
def get_daynames(self, y, m, dayname):
if self.debug:
print "%s's in %d %d..." % (dayname, m, y)
retval = [0]
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(dayname)
for d in range(1, 32):
try:
date = datetime.date(y, m, d)
except ValueError:
continue
if date.weekday() == dow:
retval.append( d )
if self.debug:
print "dow=", dow, "days=", retval
return retval
def check_date(self, date):
retval = []
for rule in self.dates:
if self.debug:
print "Checking ", rule["name"], "..."
offset = 0
if rule["offset"] != "":
if rule["offset"].isdigit():
offset = int(rule["offset"])
elif rule["offset"][0] in ["-", "+"] and rule["offset"][1:].isdigit():
offset = int(rule["offset"])
else:
# must be a dayname
offset = rule["offset"]
if rule["value"].count("/") == 3: # year/num/day/month, "3rd wednesday in april"
y, num, dayname, mon = rule["value"].split("/")
if y == "*":
y = date.year
else:
y = int(y)
if mon.isdigit():
m = int(mon)
elif mon == "*":
m = date.month
else:
m = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
'jul', 'aug', 'sep', 'oct', 'nov', 'dec'].index(mon) + 1
dates_of_dayname = self.get_daynames(y, m, dayname)
if self.debug:
print "num =", num
d = dates_of_dayname[int(num)]
elif rule["value"].count("/") == 2: # year/month/day
y, m, d = rule["value"].split("/")
if y == "*":
y = date.year
else:
y = int(y)
if m == "*":
m = date.month
else:
m = int(m)
if d == "*":
d = date.day
else:
d = int(d)
ndate = datetime.date(y, m, d)
if self.debug:
print ndate, offset, type(offset)
if isinstance(offset, int):
if offset != 0:
ndate = ndate.fromordinal(ndate.toordinal() + offset)
elif isinstance(offset, basestring):
dir_ = 1
if offset[0] == "-":
dir_ = -1
offset = offset[1:]
if offset in ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']:
# next tuesday you come to, including this one
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(offset)
ord_ = ndate.toordinal()
while ndate.fromordinal(ord_).weekday() != dow:
ord_ += dir_
ndate = ndate.fromordinal(ord_)
if self.debug:
print "ndate:", ndate, "date:", date
if ndate == date:
if rule["if"] != "":
if not eval(rule["if"]):
continue
retval.append(rule["name"])
return retval
def _get_countries_from_holiday_file(filename):
""" This will process a holiday file for country names """
parser = Xml2Obj()
element = parser.Parse(filename)
country_list = []
for country_set in element.children:
if country_set.name == "country":
if country_set.attributes["name"] not in country_list:
country_list.append(country_set.attributes["name"])
return country_list
def _get_countries():
""" Looks in multiple places for holidays.xml files """
locations = [const.PLUGINS_DIR, const.USER_PLUGINS]
holiday_file = 'holidays.xml'
country_list = []
for dir_ in locations:
holiday_full_path = os.path.join(dir_, holiday_file)
if os.path.exists(holiday_full_path):
cs = _get_countries_from_holiday_file(holiday_full_path)
for c in cs:
if c not in country_list:
country_list.append(c)
country_list.sort()
country_list.insert(0, _("Don't include holidays"))
return country_list
# TODO: Only load this once the first time it is actually needed so Gramps
# doesn't take so long to start up.
_COUNTRIES = _get_countries()
# code snippets for Easter and Daylight saving start/ stop
# are borrowed from Calendar.py
def _easter(year):
"""
Computes the year/month/day of easter. Based on work by
J.-M. Oudin (1940) and is reprinted in the "Explanatory Supplement
to the Astronomical Almanac", ed. P. K. Seidelmann (1992). Note:
Ash Wednesday is 46 days before Easter Sunday.
"""
c = year / 100
n = year - 19 * (year / 19)
k = (c - 17) / 25
i = c - c / 4 - (c - k) / 3 + 19 * n + 15
i = i - 30 * (i / 30)
i = i - (i / 28) * (1 - (i / 28) * (29 / (i + 1)) * ((21 - n) / 11))
j = year + year / 4 + i + 2 - c + c / 4
j = j - 7 * (j / 7)
l = i - j
month = 3 + (l + 40) / 44
day = l + 28 - 31 * (month / 4)
return _make_date(year, month, day)
def _get_dst_start_stop(year, area="us"):
"""
Return Daylight Saving Time start/stop in a given area ("us", "eu").
US calculation valid 1976-2099; EU 1996-2099
"""
if area == "us":
if year > 2006:
start = "%d/%d/%d" % (year, 3, 14 - (math.floor(1 + year * 5 / 4) % 7)) # March
stop = "%d/%d/%d" % (year, 11, 7 - (math.floor(1 + year * 5 / 4) % 7)) # November
else:
start = "%d/%d/%d" % (year, 4, (2 + 6 * year - math.floor(year / 4)) % 7 + 1) # April
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # October
elif area == "eu":
start = "%d/%d/%d" % (year, 3, (31 - (math.floor(year * 5 / 4) + 4) % 7)) # March
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # Oct
return (start, stop)
def _get_regular_surname(sex, name):
"""

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
# This is the src/plugins/lib level Makefile for Gramps
# We could use GNU make's ':=' syntax for nice wildcard use,
# but that is not necessarily portable.
# If not using GNU make, then list all .py files individually
pkgdatadir = $(datadir)/@PACKAGE@/plugins/lib
pkgdata_PYTHON = \
libholiday.py
pkgpyexecdir = @pkgpyexecdir@/plugins/lib
pkgpythondir = @pkgpythondir@/plugins/lib
# Clean up all the byte-compiled files
MOSTLYCLEANFILES = *pyc *pyo
GRAMPS_PY_MODPATH = "../../"
pycheck:
(export PYTHONPATH=$(GRAMPS_PY_MODPATH); \
pychecker $(pkgdata_PYTHON));

View File

@ -0,0 +1,441 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2008-2009 Brian G. Matherly
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gettext import gettext as _
from xml.parsers import expat
import datetime
import math
import const
import os
#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from gen.plug import PluginManager, Plugin
#------------------------------------------------------------------------
#
# Support functions
#
#------------------------------------------------------------------------
def easter(year):
"""
Computes the year/month/day of easter. Based on work by
J.-M. Oudin (1940) and is reprinted in the "Explanatory Supplement
to the Astronomical Almanac", ed. P. K. Seidelmann (1992). Note:
Ash Wednesday is 46 days before Easter Sunday.
"""
c = year / 100
n = year - 19 * (year / 19)
k = (c - 17) / 25
i = c - c / 4 - (c - k) / 3 + 19 * n + 15
i = i - 30 * (i / 30)
i = i - (i / 28) * (1 - (i / 28) * (29 / (i + 1))
* ((21 - n) / 11))
j = year + year / 4 + i + 2 - c + c / 4
j = j - 7 * (j / 7)
l = i - j
month = 3 + (l + 40) / 44
day = l + 28 - 31 * (month / 4)
return "%d/%d/%d" % (year, month, day)
def g2iso(dow):
""" Converst GRAMPS day of week to ISO day of week """
# GRAMPS: SUN = 1
# ISO: MON = 1
return (dow + 5) % 7 + 1
def dst(year, area="us"):
"""
Return Daylight Saving Time start/stop in a given area ("us", "eu").
US calculation valid 1976-2099; EU 1996-2099
"""
if area == "us":
if year > 2006:
start = "%d/%d/%d" % (year, 3, 14 - (math.floor(1 + year * 5 / 4) % 7)) # March
stop = "%d/%d/%d" % (year, 11, 7 - (math.floor(1 + year * 5 / 4) % 7)) # November
else:
start = "%d/%d/%d" % (year, 4, (2 + 6 * year - math.floor(year / 4)) % 7 + 1) # April
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # October
elif area == "eu":
start = "%d/%d/%d" % (year, 3, (31 - (math.floor(year * 5 / 4) + 4) % 7)) # March
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # Oct
return (start, stop)
#------------------------------------------------------------------------
#
# HolidayTable
#
#------------------------------------------------------------------------
class HolidayTable:
"""
HolidayTable is a class which provides holidays for various
countries and years.
"""
__holiday_files = []
__countries = []
def __init__(self):
"""
Find the holiday files and load the countries if it has not already
been done.
"""
if( not HolidayTable.__holiday_files ):
self.__find_holiday_files()
if( not HolidayTable.__countries ):
self.__build_country_list()
# Initialize the holiday table to be empty
self.__holidays = {}
self.__init_table()
def __find_holiday_files(self):
""" Looks in multiple places for holidays.xml files """
holiday_file = 'holidays.xml'
# Look for holiday files in the installation plugins directory and all
# subdirectories.
holiday_full_path = os.path.join(const.PLUGINS_DIR, holiday_file)
if os.path.exists(holiday_full_path):
HolidayTable.__holiday_files.append(holiday_full_path)
for (dirpath, dirnames, filenames) in os.walk(const.PLUGINS_DIR):
for directory in dirnames:
holiday_full_path = os.path.join(directory, holiday_file)
if os.path.exists(holiday_full_path):
HolidayTable.__holiday_files.append(holiday_full_path)
# Look for holiday files in the user plugins directory and all
# subdirectories.
holiday_full_path = os.path.join(const.USER_PLUGINS, holiday_file)
if os.path.exists(holiday_full_path):
HolidayTable.__holiday_files.append(holiday_full_path)
for (dirpath, dirnames, filenames) in os.walk(const.USER_PLUGINS):
for directory in dirnames:
holiday_full_path = os.path.join(directory, holiday_file)
if os.path.exists(holiday_full_path):
HolidayTable.__holiday_files.append(holiday_full_path)
def __build_country_list(self):
""" Generate the list of countries that have holiday information. """
for holiday_file_path in HolidayTable.__holiday_files:
parser = _Xml2Obj()
root_element = parser.parse(holiday_file_path)
for country_element in root_element.get_children():
if country_element.get_name() == "country":
country_name = country_element.get_attribute("name")
if country_name not in HolidayTable.__countries:
HolidayTable.__countries.append(country_name)
def __init_table(self):
""" Initialize the holiday table structure. """
for month in range(1, 13):
self.__holidays[month] = {}
for day in range(1, 32):
self.__holidays[month][day] = []
def get_countries(self):
"""
Get all the country names that holidays are available for.
@return: nothing
"""
return HolidayTable.__countries
def load_holidays(self, year, country):
"""
Load the holiday table for the specified year and country.
This must be called before get_holidays().
@param year: The year for which the holidays should be loaded.
Example: 2010
@type year: int
@param country: The country for which the holidays should be loaded.
Example: "United States"
@type country: str
@return: nothing
"""
self.__init_table()
for holiday_file_path in HolidayTable.__holiday_files:
parser = _Xml2Obj()
element = parser.parse(holiday_file_path)
calendar = _Holidays(element, country)
date = datetime.date(year, 1, 1)
while date.year == year:
holidays = calendar.check_date(date)
for text in holidays:
self.__holidays[date.month][date.day].append(text)
date = date.fromordinal(date.toordinal() + 1)
def get_holidays(self, month, day):
"""
Get the holidays for the given day of the year.
@param month: The month for the requested holidays.
Example: 1
@type month: int
@param month: The day for the requested holidays.
Example: 1
@type month: int
@return: An array of strings with holiday names.
@return type: [str]
"""
return self.__holidays[month][day]
#------------------------------------------------------------------------
#
# _Element
#
#------------------------------------------------------------------------
class _Element:
""" A parsed XML element """
def __init__(self, name, attributes):
'Element constructor'
# The element's tag name
self.__name = name
# The element's attribute dictionary
self.__attributes = attributes
# The element's child element list (sequence)
self.__children = []
def add_child(self, element):
'Add a reference to a child element'
self.__children.append(element)
def get_attribute(self, key):
'Get an attribute value'
return self.__attributes.get(key)
def get_attributes(self):
'Get all the attributes'
return self.__attributes
def get_name(self):
""" Get the name of this element. """
return self.__name
def get_children(self):
""" Get the children elements for this element. """
return self.__children
#------------------------------------------------------------------------
#
# _Xml2Obj
#
#------------------------------------------------------------------------
class _Xml2Obj:
""" XML to Object """
def __init__(self):
self.root = None
self.nodeStack = []
def start_element(self, name, attributes):
'SAX start element even handler'
# Instantiate an Element object
element = _Element(name.encode(), attributes)
# Push element onto the stack and make it a child of parent
if len(self.nodeStack) > 0:
parent = self.nodeStack[-1]
parent.add_child(element)
else:
self.root = element
self.nodeStack.append(element)
def end_element(self, name):
'SAX end element event handler'
self.nodeStack = self.nodeStack[:-1]
def parse(self, filename):
'Create a SAX parser and parse filename '
parser = expat.ParserCreate()
# SAX event handlers
parser.StartElementHandler = self.start_element
parser.EndElementHandler = self.end_element
# Parse the XML File
parser.Parse(open(filename, 'r').read(), 1)
return self.root
#------------------------------------------------------------------------
#
# _Holidays
#
#------------------------------------------------------------------------
class _Holidays:
""" Class used to read XML holidays to add to calendar. """
def __init__(self, elements, country="US"):
self.debug = 0
self.elements = elements
self.country = country
self.dates = []
self.initialize()
def set_country(self, country):
""" Set the contry of holidays to read """
self.country = country
self.dates = []
self.initialize()
def initialize(self):
""" Parse the holiday date XML items """
for country_set in self.elements.get_children():
if country_set.get_name() == "country" and \
country_set.get_attribute("name") == self.country:
for date in country_set.get_children():
if date.get_name() == "date":
data = {"value" : "",
"name" : "",
"offset": "",
"type": "",
"if": "",
} # defaults
for attr in date.get_attributes():
data[attr] = date.get_attribute(attr)
self.dates.append(data)
def get_daynames(self, year, month, dayname):
""" Get the items for a particular year/month and day of week """
if self.debug:
print "%s's in %d %d..." % (dayname, month, year)
retval = [0]
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(dayname)
for day in range(1, 32):
try:
date = datetime.date(year, month, day)
except ValueError:
continue
if date.weekday() == dow:
retval.append(day)
if self.debug:
print "dow=", dow, "days=", retval
return retval
def check_date(self, date):
""" Return items that match rules """
retval = []
for rule in self.dates:
if self.debug:
print "Checking ", rule["name"], "..."
offset = 0
if rule["offset"] != "":
if rule["offset"].isdigit():
offset = int(rule["offset"])
elif rule["offset"][0] in ["-", "+"] and \
rule["offset"][1:].isdigit():
offset = int(rule["offset"])
else:
# must be a dayname
offset = rule["offset"]
if len(rule["value"]) > 0 and rule["value"][0] == '>':
# eval exp -> year/num[/day[/month]]
y, m, d = date.year, date.month, date.day
rule["value"] = eval(rule["value"][1:])
if self.debug:
print "rule['value']:", rule["value"]
if rule["value"].count("/") == 3: # year/num/day/month, "3rd wednesday in april"
y, num, dayname, mon = rule["value"].split("/")
if y == "*":
y = date.year
else:
y = int(y)
if mon.isdigit():
m = int(mon)
elif mon == "*":
m = date.month
else:
m = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
'jul', 'aug', 'sep', 'oct', 'nov', 'dec'].index(mon) + 1
dates_of_dayname = self.get_daynames(y, m, dayname)
if self.debug:
print "num =", num
d = dates_of_dayname[int(num)]
elif rule["value"].count("/") == 2: # year/month/day
y, m, d = rule["value"].split("/")
if y == "*":
y = date.year
else:
y = int(y)
if m == "*":
m = date.month
else:
m = int(m)
if d == "*":
d = date.day
else:
d = int(d)
ndate = datetime.date(y, m, d)
if self.debug:
print ndate, offset, type(offset)
if isinstance(offset, int):
if offset != 0:
ndate = ndate.fromordinal(ndate.toordinal() + offset)
elif isinstance(offset, basestring):
direction = 1
if offset[0] == "-":
direction = -1
offset = offset[1:]
if offset in ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']:
# next tuesday you come to, including this one
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(offset)
ordinal = ndate.toordinal()
while ndate.fromordinal(ordinal).weekday() != dow:
ordinal += direction
ndate = ndate.fromordinal(ordinal)
if self.debug:
print "ndate:", ndate, "date:", date
if ndate == date:
if rule["if"] != "":
if not eval(rule["if"]):
continue
retval.append(rule["name"])
return retval
#------------------------------------------------------------------------
#
# Register the plugins
#
#------------------------------------------------------------------------
PluginManager.get_instance().register_plugin(
Plugin(
name = __name__,
description = _("Provides holiday information for different countries."),
module_name = __name__
)
)

View File

@ -0,0 +1,488 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2008-2009 Brian G. Matherly
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gettext import gettext as _
import datetime
import time
#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
import BaseDoc
from BasicUtils import name_displayer
from gen.plug import PluginManager
from ReportBase import Report, ReportUtils, MenuReportOptions, CATEGORY_TEXT
from gen.plug.menu import BooleanOption, StringOption, NumberOption, \
EnumeratedListOption, FilterOption, PersonOption
import GrampsLocale
import gen.lib
from Utils import probably_alive, ProgressMeter
import libholiday
#------------------------------------------------------------------------
#
# Support functions
#
#------------------------------------------------------------------------
def make_date(year, month, day):
"""
Return a Date object of the particular year/month/day.
"""
retval = gen.lib.Date()
retval.set_yr_mon_day(year, month, day)
return retval
#------------------------------------------------------------------------
#
# Calendar
#
#------------------------------------------------------------------------
class CalendarReport(Report):
"""
Create the Calendar object that produces the report.
"""
def __init__(self, database, options_class):
Report.__init__(self, database, options_class)
menu = options_class.menu
self.titletext = menu.get_option_by_name('titletext').get_value()
self.relationships = \
menu.get_option_by_name('relationships').get_value()
self.year = menu.get_option_by_name('year').get_value()
self.name_format = menu.get_option_by_name('name_format').get_value()
self.country = menu.get_option_by_name('country').get_value()
self.anniversaries = menu.get_option_by_name('anniversaries').get_value()
self.start_dow = menu.get_option_by_name('start_dow').get_value()
self.maiden_name = menu.get_option_by_name('maiden_name').get_value()
self.alive = menu.get_option_by_name('alive').get_value()
self.birthdays = menu.get_option_by_name('birthdays').get_value()
self.text1 = menu.get_option_by_name('text1').get_value()
self.text2 = menu.get_option_by_name('text2').get_value()
self.text3 = menu.get_option_by_name('text3').get_value()
self.filter_option = menu.get_option_by_name('filter')
self.filter = self.filter_option.get_filter()
pid = menu.get_option_by_name('pid').get_value()
self.center_person = database.get_person_from_gramps_id(pid)
def get_name(self, person, maiden_name = None):
"""
Return person's name, unless maiden_name given, unless married_name
listed.
"""
# Get all of a person's names:
primary_name = person.get_primary_name()
married_name = None
names = [primary_name] + person.get_alternate_names()
for name in names:
if int(name.get_type()) == gen.lib.NameType.MARRIED:
married_name = name
break # use first
# Now, decide which to use:
if maiden_name is not None:
if married_name is not None:
name = gen.lib.Name(married_name)
else:
name = gen.lib.Name(primary_name)
name.set_surname(maiden_name)
else:
name = gen.lib.Name(primary_name)
name.set_display_as(self.name_format)
return name_displayer.display_name(name)
def add_day_item(self, text, month, day):
""" Add an item to a day. """
month_dict = self.calendar.get(month, {})
day_list = month_dict.get(day, [])
day_list.append(text)
month_dict[day] = day_list
self.calendar[month] = month_dict
def __get_holidays(self):
""" Get the holidays for the specified country and year """
holiday_table = libholiday.HolidayTable()
country = holiday_table.get_countries()[self.country]
holiday_table.load_holidays(self.year, country)
for month in range(1, 13):
for day in range(1, 32):
holiday_names = holiday_table.get_holidays(month, day)
for holiday_name in holiday_names:
self.add_day_item(holiday_name, month, day)
def write_report(self):
""" The short method that runs through each month and creates a page. """
# initialize the dict to fill:
self.progress = ProgressMeter(_('Birthday and Anniversary Report'))
self.calendar = {}
# get the information, first from holidays:
if self.country != 0:
self.__get_holidays()
# get data from database:
self.collect_data()
# generate the report:
self.doc.start_paragraph('BIR-Title')
self.doc.write_text(str(self.titletext) + ": " + str(self.year))
self.doc.end_paragraph()
if self.text1.strip() != "":
self.doc.start_paragraph('BIR-Text1style')
self.doc.write_text(str(self.text1))
self.doc.end_paragraph()
if self.text2.strip() != "":
self.doc.start_paragraph('BIR-Text2style')
self.doc.write_text(str(self.text2))
self.doc.end_paragraph()
if self.text3.strip() != "":
self.doc.start_paragraph('BIR-Text3style')
self.doc.write_text(str(self.text3))
self.doc.end_paragraph()
if self.relationships:
name = self.center_person.get_primary_name()
self.doc.start_paragraph('BIR-Text3style')
self.doc.write_text(_("Relationships shown are to %s") % name_displayer.display_name(name))
self.doc.end_paragraph()
self.progress.set_pass(_('Formatting months...'), 12)
for month in range(1, 13):
self.progress.step()
self.print_page(month)
self.progress.close()
def print_page(self, month):
""" Prints a month as a page """
year = self.year
self.doc.start_paragraph('BIR-Monthstyle')
self.doc.write_text(GrampsLocale.long_months[month].capitalize())
self.doc.end_paragraph()
current_date = datetime.date(year, month, 1)
current_ord = current_date.toordinal()
started_day = {}
for i in range(31):
thisday = current_date.fromordinal(current_ord)
if thisday.month == month:
list = self.calendar.get(month, {}).get(thisday.day, [])
for p in list:
p = p.replace("\n", " ")
if thisday not in started_day:
self.doc.start_paragraph("BIR-Daystyle")
self.doc.write_text(str(thisday.day))
self.doc.end_paragraph()
started_day[thisday] = 1
self.doc.start_paragraph("BIR-Datastyle")
self.doc.write_text(p)
self.doc.end_paragraph()
current_ord += 1
def collect_data(self):
"""
This method runs through the data, and collects the relevant dates
and text.
"""
people = self.database.get_person_handles(sort_handles=False)
self.progress.set_pass(_('Applying Filter...'), len(people))
people = self.filter.apply(self.database, people, self.progress)
pmgr = PluginManager.get_instance()
rel_calc = pmgr.get_relationship_calculator()
self.progress.set_pass(_('Reading database...'), len(people))
for person_handle in people:
self.progress.step()
person = self.database.get_person_from_handle(person_handle)
birth_ref = person.get_birth_ref()
birth_date = None
if birth_ref:
birth_event = self.database.get_event_from_handle(birth_ref.ref)
birth_date = birth_event.get_date_object()
if self.birthdays and birth_date is not None:
year = birth_date.get_year()
month = birth_date.get_month()
day = birth_date.get_day()
age = self.year - year
# add some things to handle maiden name:
father_lastname = None # husband, actually
if self.maiden_name in ['spouse_first', 'spouse_last']: # get husband's last name:
if person.get_gender() == gen.lib.Person.FEMALE:
family_list = person.get_family_handle_list()
if len(family_list) > 0:
if self.maiden_name == 'spouse_first':
fhandle = family_list[0]
else:
fhandle = family_list[-1]
fam = self.database.get_family_from_handle(fhandle)
father_handle = fam.get_father_handle()
mother_handle = fam.get_mother_handle()
if mother_handle == person_handle:
if father_handle:
father = self.database.get_person_from_handle(father_handle)
if father is not None:
father_lastname = father.get_primary_name().get_surname()
short_name = self.get_name(person, father_lastname)
if age >= 0:
alive = probably_alive(person, self.database, make_date(self.year, month, day))
if ((self.alive and alive) or not self.alive):
comment = ""
if self.relationships:
relation = rel_calc.get_one_relationship(
self.database,
self.center_person,
person)
if relation:
comment = " --- %s" % relation
self.add_day_item("%s, %d%s" % (short_name, age, comment), month, day)
if self.anniversaries:
family_list = person.get_family_handle_list()
for fhandle in family_list:
fam = self.database.get_family_from_handle(fhandle)
father_handle = fam.get_father_handle()
mother_handle = fam.get_mother_handle()
if father_handle == person.get_handle():
spouse_handle = mother_handle
else:
continue # with next person if the father is not "person"
# this will keep from duplicating the anniversary
if spouse_handle:
spouse = self.database.get_person_from_handle(spouse_handle)
if spouse:
spouse_name = self.get_name(spouse)
short_name = self.get_name(person)
# TEMP: this will hanlde ordered events
# GRAMPS 3.0 will have a new mechanism for start/stop events
are_married = None
for event_ref in fam.get_event_ref_list():
event = self.database.get_event_from_handle(event_ref.ref)
if event.type in [gen.lib.EventType.MARRIAGE,
gen.lib.EventType.MARR_ALT]:
are_married = event
elif event.type in [gen.lib.EventType.DIVORCE,
gen.lib.EventType.ANNULMENT,
gen.lib.EventType.DIV_FILING]:
are_married = None
if are_married is not None:
for event_ref in fam.get_event_ref_list():
event = self.database.get_event_from_handle(event_ref.ref)
event_obj = event.get_date_object()
year = event_obj.get_year()
month = event_obj.get_month()
day = event_obj.get_day()
years = self.year - year
if years >= 0:
text = _("%(spouse)s and\n %(person)s, %(nyears)d") % {
'spouse' : spouse_name,
'person' : short_name,
'nyears' : years,
}
alive1 = probably_alive(person, self.database, make_date(self.year, month, day))
alive2 = probably_alive(spouse, self.database, make_date(self.year, month, day))
if ((self.alive and alive1 and alive2) or not self.alive):
self.add_day_item(text, month, day)
#------------------------------------------------------------------------
#
# CalendarOptions
#
#------------------------------------------------------------------------
class CalendarOptions(MenuReportOptions):
""" Options for the Birthday/Anniversary Report """
def __init__(self, name, dbase):
self.__db = dbase
self.__pid = None
self.__filter = None
MenuReportOptions.__init__(self, name, dbase)
def add_menu_options(self, menu):
""" Add the options for the graphical calendar """
category_name = _("Report Options")
year = NumberOption(_("Year of calendar"), time.localtime()[0],
1000, 3000)
year.set_help(_("Year of calendar"))
menu.add_option(category_name, "year", year)
self.__filter = FilterOption(_("Filter"), 0)
self.__filter.set_help(
_("Select filter to restrict people that appear on calendar"))
menu.add_option(category_name, "filter", self.__filter)
self.__pid = PersonOption(_("Center Person"))
self.__pid.set_help(_("The center person for the report"))
menu.add_option(category_name, "pid", self.__pid)
self.__pid.connect('value-changed', self.__update_filters)
self.__update_filters()
# We must figure out the value of the first option before we can
# create the EnumeratedListOption
fmt_list = name_displayer.get_name_format()
name_format = EnumeratedListOption(_("Name format"), fmt_list[0][0])
for num, name, fmt_str, act in fmt_list:
name_format.add_item(num, name)
name_format.set_help(_("Select the format to display names"))
menu.add_option(category_name, "name_format", name_format)
country = EnumeratedListOption(_("Country for holidays"), 0)
holiday_table = libholiday.HolidayTable()
count = 0
for c in holiday_table.get_countries():
country.add_item(count, c)
count += 1
country.set_help(_("Select the country to see associated holidays"))
menu.add_option(category_name, "country", country)
start_dow = EnumeratedListOption(_("First day of week"), 1)
for count in range(1, 8):
# conversion between gramps numbering (sun=1) and iso numbering (mon=1) of weekdays below
start_dow.add_item((count+5) % 7 + 1, GrampsLocale.long_days[count].capitalize())
start_dow.set_help(_("Select the first day of the week for the calendar"))
menu.add_option(category_name, "start_dow", start_dow)
maiden_name = EnumeratedListOption(_("Birthday surname"), "own")
maiden_name.add_item("spouse_first", _("Wives use husband's surname (from first family listed)"))
maiden_name.add_item("spouse_last", _("Wives use husband's surname (from last family listed)"))
maiden_name.add_item("own", _("Wives use their own surname"))
maiden_name.set_help(_("Select married women's displayed surname"))
menu.add_option(category_name, "maiden_name", maiden_name)
alive = BooleanOption(_("Include only living people"), True)
alive.set_help(_("Include only living people in the calendar"))
menu.add_option(category_name, "alive", alive)
birthdays = BooleanOption(_("Include birthdays"), True)
birthdays.set_help(_("Include birthdays in the calendar"))
menu.add_option(category_name, "birthdays", birthdays)
anniversaries = BooleanOption(_("Include anniversaries"), True)
anniversaries.set_help(_("Include anniversaries in the calendar"))
menu.add_option(category_name, "anniversaries", anniversaries)
option = BooleanOption(_("Include relationships to center person"),
False)
option.set_help(_("Include relationships to center person (slower)"))
menu.add_option(category_name, "relationships", option)
category_name = _("Text Options")
titletext = StringOption(_("Title text"),
_("Birthday and Anniversary Report"))
titletext.set_help(_("Title of calendar"))
menu.add_option(category_name, "titletext", titletext)
text1 = StringOption(_("Text Area 1"), _("My Calendar"))
text1.set_help(_("First line of text at bottom of calendar"))
menu.add_option(category_name, "text1", text1)
text2 = StringOption(_("Text Area 2"), _("Produced with GRAMPS"))
text2.set_help(_("Second line of text at bottom of calendar"))
menu.add_option(category_name, "text2", text2)
text3 = StringOption(_("Text Area 3"), "http://gramps-project.org/",)
text3.set_help(_("Third line of text at bottom of calendar"))
menu.add_option(category_name, "text3", text3)
def __update_filters(self):
"""
Update the filter list based on the selected person
"""
gid = self.__pid.get_value()
person = self.__db.get_person_from_gramps_id(gid)
filter_list = ReportUtils.get_person_filters(person, False)
self.__filter.set_filters(filter_list)
def make_my_style(self, default_style, name, description,
size=9, font=BaseDoc.FONT_SERIF, justified ="left",
color=None, align=BaseDoc.PARA_ALIGN_CENTER,
shadow = None, italic=0, bold=0, borders=0, indent=None):
""" Create paragraph and graphic styles of the same name """
# Paragraph:
f = BaseDoc.FontStyle()
f.set_size(size)
f.set_type_face(font)
f.set_italic(italic)
f.set_bold(bold)
p = BaseDoc.ParagraphStyle()
p.set_font(f)
p.set_alignment(align)
p.set_description(description)
p.set_top_border(borders)
p.set_left_border(borders)
p.set_bottom_border(borders)
p.set_right_border(borders)
if indent:
p.set(first_indent=indent)
if justified == "left":
p.set_alignment(BaseDoc.PARA_ALIGN_LEFT)
elif justified == "right":
p.set_alignment(BaseDoc.PARA_ALIGN_RIGHT)
elif justified == "center":
p.set_alignment(BaseDoc.PARA_ALIGN_CENTER)
default_style.add_paragraph_style(name, p)
# Graphics:
g = BaseDoc.GraphicsStyle()
g.set_paragraph_style(name)
if shadow:
g.set_shadow(*shadow)
if color is not None:
g.set_fill_color(color)
if not borders:
g.set_line_width(0)
default_style.add_draw_style(name, g)
def make_default_style(self, default_style):
""" Add the styles used in this report """
self.make_my_style(default_style, "BIR-Title",
_('Title text style'), 14,
bold=1, justified="center")
self.make_my_style(default_style, "BIR-Datastyle",
_('Data text display'), 12, indent=1.0)
self.make_my_style(default_style, "BIR-Daystyle",
_('Day text style'), 12, indent=.5,
italic=1, bold=1)
self.make_my_style(default_style, "BIR-Monthstyle",
_('Month text style'), 14, bold=1)
self.make_my_style(default_style, "BIR-Text1style",
_('Text at bottom, line 1'), 12, justified="center")
self.make_my_style(default_style, "BIR-Text2style",
_('Text at bottom, line 2'), 12, justified="center")
self.make_my_style(default_style, "BIR-Text3style",
_('Text at bottom, line 3'), 12, justified="center")
#------------------------------------------------------------------------
#
# Register the plugins
#
#------------------------------------------------------------------------
pmgr = PluginManager.get_instance()
pmgr.register_report(
name = 'birthday_report',
category = CATEGORY_TEXT,
report_class = CalendarReport,
options_class = CalendarOptions,
modes = PluginManager.REPORT_MODE_GUI | \
PluginManager.REPORT_MODE_BKI | \
PluginManager.REPORT_MODE_CLI,
translated_name = _("Birthday and Anniversary Report"),
status = _("Stable"),
author_name = "Douglas S. Blank",
author_email = "dblank@cs.brynmawr.edu",
description = _("Produces a report of birthdays and anniversaries"),
)