# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2007 Thom Sturgill # Copyright (C) 2007-2009 Brian G. Matherly # Copyright (C) 2008-2009 Rob G. Healey # Copyright (C) 2008 Jason Simanek # # 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 # 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 # # $Id: WebCal.py 12238 2009-03-07 09:51:27Z s_charette $ """ Web Calendar generator. Refactoring. This is an ongoing job until this plugin is in a better shape. """ #------------------------------------------------------------------------ # # python modules # #------------------------------------------------------------------------ import os, codecs, shutil import time, datetime, calendar from gettext import gettext as _ from gettext import ngettext #------------------------------------------------------------------------ # # Set up logging # #------------------------------------------------------------------------ import logging log = logging.getLogger(".WebPage") #------------------------------------------------------------------------ # # GRAMPS module # #------------------------------------------------------------------------ import gen.lib import const from GrampsCfg import get_researcher from gen.plug import PluginManager from ReportBase import Report, ReportUtils, MenuReportOptions, CATEGORY_WEB from gen.plug.menu import BooleanOption, NumberOption, StringOption, \ EnumeratedListOption, FilterOption, PersonOption, \ DestinationOption import Utils import GrampsLocale from QuestionDialog import WarningDialog from Utils import probably_alive from DateHandler import displayer as _dd from DateHandler import parser as _dp import libholiday from html import Html #------------------------------------------------------------------------ # # constants # #------------------------------------------------------------------------ # Web page filename extensions _WEB_EXT = ['.html', '.htm', '.shtml', '.php', '.php3', '.cgi'] # Calendar stylesheet names _CALENDARSCREEN = 'calendar-screen.css' _CALENDARPRINT = 'calendar-print.css' # Mainz stylesheet graphics # will only be used if Mainz is slected as the stylesheet _WEBBKGD = "Web_Mainz_Bkgd.png" _WEBHEADER = "Web_Mainz_Header.png" _WEBMID = "Web_Mainz_Mid.png" _WEBMIDLIGHT = "Web_Mainz_MidLight.png" # This information defines the list of styles in the Web calendar # options dialog as well as the location of the corresponding # stylesheets. _CSS_FILES = [ # First is used as default selection. [_("Basic-Ash"), 'Web_Basic-Ash.css'], [_("Basic-Cypress"), 'Web_Basic-Cypress.css'], [_("Basic-Lilac"), 'Web_Basic-Lilac.css'], [_("Basic-Peach"), 'Web_Basic-Peach.css'], [_("Basic-Spruce"), 'Web_Basic-Spruce.css'], [_("Mainz"), 'Web_Mainz.css'], [_("Nebraska"), 'Web_Nebraska.css'], [_("Visually Impaired"), 'Web_Visually.css'], [_("No style sheet"), ''], ] _CHARACTER_SETS = [ # First is used as default selection. # As seen on the internet, ISO-xxx are listed as capital letters [_('Unicode UTF-8 (recommended)'), 'UTF-8'], ['ISO-8859-1', 'ISO-8859-1' ], ['ISO-8859-2', 'ISO-8859-2' ], ['ISO-8859-3', 'ISO-8859-3' ], ['ISO-8859-4', 'ISO-8859-4' ], ['ISO-8859-5', 'ISO-8859-5' ], ['ISO-8859-6', 'ISO-8859-6' ], ['ISO-8859-7', 'ISO-8859-7' ], ['ISO-8859-8', 'ISO-8859-8' ], ['ISO-8859-9', 'ISO-8859-9' ], ['ISO-8859-10', 'ISO-8859-10' ], ['ISO-8859-13', 'ISO-8859-13' ], ['ISO-8859-14', 'ISO-8859-14' ], ['ISO-8859-15', 'ISO-8859-15' ], ['koi8_r', 'koi8_r', ], ] _CC = [ '', '' 'Creative Commons License - By attribution', '' 'Creative Commons License - By attribution, No derivations', '' 'Creative Commons License - By attribution, Share-alike', '' 'Creative Commons License - By attribution, Non-commercial', '' 'Creative Commons License - By attribution, Non-commercial, No derivations', '' 'Creative Commons License - By attribution, Non-commerical, Share-alike' ] _COPY_OPTIONS = [ _('Standard copyright'), # This must match _CC _('Creative Commons - By attribution'), _('Creative Commons - By attribution, No derivations'), _('Creative Commons - By attribution, Share-alike'), _('Creative Commons - By attribution, Non-commercial'), _('Creative Commons - By attribution, Non-commercial, No derivations'), _('Creative Commons - By attribution, Non-commercial, Share-alike'), _('No copyright notice'), ] #------------------------------------------------------------------------ # # WebCalReport # #------------------------------------------------------------------------ class WebCalReport(Report): """ Create WebCalReport object that produces the report. """ def __init__(self, database, options): Report.__init__(self, database, options) mgobn = lambda name:options.menu.get_option_by_name(name).get_value() self.database = database self.options = options self.html_dir = mgobn('target') self.title_text = mgobn('title') filter_option = options.menu.get_option_by_name('filter') self.filter = filter_option.get_filter() self.ext = mgobn('ext') self.copy = mgobn('cright') self.css = mgobn('css') self.country = mgobn('country') self.start_dow = mgobn('start_dow') self.multiyear = mgobn('multiyear') self.start_year = mgobn('start_year') self.end_year = mgobn('end_year') self.maiden_name = mgobn('maiden_name') self.alive = mgobn('alive') self.birthday = mgobn('birthdays') self.anniv = mgobn('anniversaries') self.home_link = mgobn('home_link') self.month_notes = [mgobn('note_' + month) \ for month in ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']] self.encoding = mgobn('encoding') self.fullyear = mgobn('fullyear') self.makeoneday = mgobn('makeoneday') # identify researcher name and e-mail address # as NarrativeWeb already does researcher = get_researcher() self.author = researcher.name if self.author: self.author = self.author.replace(',,,', '') self.email = researcher.email # set to today's date today = time.localtime() self.today = gen.lib.Date(today[0], today[1], today[2]) self.warn_dir = True # Only give warning once. # self.calendar is a dict; key is the month number # Each entry in the dict is also a dict; key is the day number. # The day dict is a list of things to display for that day. # These things are: birthdays and anniversaries self.calendar = {} calendar.setfirstweekday(dow_gramps2iso[self.start_dow]) # --------------------------------------------------------------------------------------- # # Copy files to their destination # # --------------------------------------------------------------------------------------- def copy_file(self, from_fname, to_fname, to_dir=''): """ Copy a file from a source to a (report) destination. If to_dir is not present and if the target is not an archive, then the destination directory will be created. Normally 'to_fname' will be just a filename, without directory path. 'to_dir' is the relative path name in the destination root. It will be prepended before 'to_fname'. """ dest = os.path.join(self.html_dir, to_dir, to_fname) destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) if from_fname != dest: shutil.copyfile(from_fname, dest) elif self.warn_dir: WarningDialog( _("Possible destination error") + "\n" + _("You appear to have set your target directory " "to a directory used for data storage. This " "could create problems with file management. " "It is recommended that you consider using " "a different directory to store your generated " "web pages.")) self.warn_dir = False # --------------------------------------------------------------------------------------- # # Adds Birthdays and Anniversaries # # --------------------------------------------------------------------------------------- def add_day_item(self, text, year, month, day, event): if day == 0: # This may happen for certain "about" dates. day = 1 # Use first day of the month month_dict = self.calendar.get(month, {}) day_list = month_dict.get(day, []) if month > 0: try: event_date = gen.lib.Date() event_date.set_yr_mon_day(year, month, day) except ValueError: event_date = '...' else: event_date = '...' #Incomplete date as in about, circa, etc. day_list.append((text, event, event_date)) month_dict[day] = day_list self.calendar[month] = month_dict # --------------------------------------------------------------------------------------- # # Retrieves Holidays from the Holiday file, src/plugins/lib/holidays.xml # # --------------------------------------------------------------------------------------- def __get_holidays(self, year): self.progress.set_pass(_('Calculating Holidays for year %d' % year), 365) """ 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, month, day) # increment progress bar self.progress.step() # --------------------------------------------------------------------------------------- # # Adds Holiday events # # --------------------------------------------------------------------------------------- def add_holiday_item(self, text, month, day): """ add holiday to its dictionary text -- holiday title month -- month of holiday day -- day of holiday """ if day == 0: # This may happen for certain "about" dates. day = 1 # Use first day of the month month_dict = self.holidays.get(month, {}) day_list = month_dict.get(day, []) day_list.append(text) month_dict[day] = day_list self.holidays[month] = month_dict # --------------------------------------------------------------------------------------- # # Copies all of the calendar files for all calendars # # --------------------------------------------------------------------------------------- def copy_calendar_files(self): """ Copies all the necessary stylesheets and images for these calendars """ # Copy the screen stylesheet if self.css: fname = os.path.join(const.DATA_DIR, self.css) self.copy_file(fname, _CALENDARSCREEN, "styles") # copy print stylesheet fname = os.path.join(const.DATA_DIR, "Web_Print-Default.css") self.copy_file(fname, _CALENDARPRINT, "styles") # set imgs to empty imgs = [] if self.css == "Web_Mainz.css": # Copy Mainz Style Images imgs += [_WEBBKGD, _WEBHEADER, _WEBMID, _WEBMIDLIGHT] # Copy GRAMPS favicon imgs += ['favicon.ico'] # copy copyright image if 0 < self.copy <= len(_CC): imgs += ['somerights20.gif'] for fname in imgs: from_path = os.path.join(const.IMAGE_DIR, fname) self.copy_file(from_path, fname, "images") # --------------------------------------------------------------------------------------- # # Creates file name passed to it # # --------------------------------------------------------------------------------------- def create_file(self, fname, subdir): """ Create a file in the html_dir tree. If the directory does not exist, create it. fname -- filename to be created subdir -- any subdirs to be added """ fname = os.path.join(self.html_dir, subdir, fname) if not _has_webpage_extension(fname): fname += self.ext destdir = os.path.dirname(fname) if not os.path.isdir(destdir): os.makedirs(destdir) of = codecs.EncodedFile(open(fname, "w"), 'utf-8', self.encoding, 'xmlcharrefreplace') return of # --------------------------------------------------------------------------------------- # # Closes all file name passed to it # # --------------------------------------------------------------------------------------- def close_file(self, of): """ will close whatever filename is passed to it """ of.close() # --------------------------------------------------------------------------------------- # # Beginning of Calendar Creation # # --------------------------------------------------------------------------------------- def write_header(self, nr_up, title, add_print=True): """ This creates the header for the Calendars 'nr_up' - number of directory levels up, started from current page, to the root of the directory tree (i.e. to self.html_dir). title -- to be inserted into page header section add_print -- whether to add printer stylesheet or not * only webcalendar() and one_day() only! """ # number of subdirectories up to reach root subdirs = '../'*nr_up # Header contants xmllang = Utils.xml_lang() _XMLNS = 'http://www.w3.org/1999/xhtml' _META1 = 'http-equiv="content-type" content="text/html;charset=%s"' % self.encoding _META2 = 'http-equiv="Content-Style-Type" content="text/css"' _META3 = 'name="generator" content="%s %s %s"' % (const.PROGRAM_NAME, const.VERSION, const.URL_HOMEPAGE) _META4 = 'name="author" content="%s"' % self.author # GRAMPS favicon fname1 = '/'.join([subdirs] + ['images'] + ['favicon.ico']) # _CALENDARSCREEN stylesheet fname2 = '/'.join([subdirs] + ['styles'] + [_CALENDARSCREEN]) header = Html(xmlns=_XMLNS, attr='xml:lang="%s" lang="%s"' % ((xmllang,)*2)) header.addXML() header.addDOCTYPE() head = Html('head') + ( # header title Html('title', title, indent=True, inline=True), # meta tags for head section Html('meta', attr = _META1, indent = True, inline = True), Html('meta', attr = _META2, indent = True, inline = True), Html('meta', attr = _META3, indent = True, inline = True), Html('meta', attr = _META4, indent = True, inline = True), # links for GRAMPS favicon and stylesheets Html('link',rel='shortcut icon', href=fname1,type='image/x-icon',indent=True), Html('link',rel='stylesheet', href=fname2,type='text/css',media='screen',indent=True) ) # add printer stylesheet to webcalendar() and one_day() only if add_print: fname = '/'.join([subdirs] + ['styles'] + [_CALENDARPRINT]) head += Html('link',rel='stylesheet', href=fname,type='text/css',media='print',indent=True) # add head section to page header header += head # return header section to its caller # either webcalendar(), year_glance(), or one_day() return header # --------------------------------------------------------------------------------------- # # Creates year navigation, if multiyear # # --------------------------------------------------------------------------------------- def display_year_navs(self, nr_up, currentsection): """ This will create the year navigation menu bar nr_up = number of directories up to reach root directory currentsection = proper styling of this navigation bar """ # creating more than one year if not self.multiyear: return num_years = (self.end_year - self.start_year) cal_year = self.start_year # stylesheets other than "Web_Visually.css" will hold 22 years in a row # otherwise, 18 years in a row years_in_row = 22 if self.css is not 'Web_Visually.css' else 18 # figure out number of rows nrows = get_num_of_rows(num_years, years_in_row) for rows in range(0, nrows): yearnav = Html('div', id="navigation", indent=True) ul = Html('ul', indent=True) cols = 1 while (cols <= years_in_row and cal_year <= self.end_year): url = '' # begin subdir level subdirs = ['..'] * nr_up subdirs.append(str(cal_year)) # each year will link to current month. # this will always need an extension added full_month_name = get_full_month_name(self.today.get_month()) # Note. We use '/' here because it is a URL, not a OS dependent # pathname. url = '/'.join(subdirs + [full_month_name]) + self.ext # Figure out if we need
  • or just plain
  • cs = str(cal_year) == currentsection and 'class="CurrentSection"' or '' li = Html('li',attr=cs , indent=True, inline=True) # create hyperlink ahref = Html('a', str(cal_year), href = url, inline=True) # add hyperlink to
  • cell li += ahref # add cell to unordered list ul += li # increase columns cols += 1 # increase calendar year cal_year += 1 # add ul to yearnav yearnav += ul # return yearnav to its caller return yearnav # --------------------------------------------------------------------------------------- # # Creates month navigation for all Calendars # # --------------------------------------------------------------------------------------- def display_month_navs(self, nr_up, year, currentsection, add_home): """ Will create and display the navigation menu bar of = calendar filename being created nr_up = number of directories up to reach root directory year = year being created currentsection = month name being created for proper CSS styling use_home = if creating a link to home -- a link to root directory of website """ navs = [] # An optional link to a home page navs.append((self.home_link, _('Home'), add_home)) for month in range(1, 13): navs.append((month, month, True)) # Add a link for year_glance() if requested navs.append(('fullyearlinked', _('Year Glance'), self.fullyear)) monthnav = Html('div', id="subnavigation", indent=True) ul = Html('ul', indent=True) navs = [(u, n) for u, n, c in navs if c] for url_fname, nav_text in navs: subdirs = ['..'] * nr_up subdirs.append(str(year)) if type(url_fname) == int: url_fname = get_full_month_name(url_fname) if type(nav_text) == int: nav_text = get_short_month_name(nav_text) # Note. We use '/' here because it is a URL, not a OS dependent pathname # need to leave home link alone, so look for it ... url = url_fname add_subdirs = True if not (url.startswith('http:') or url.startswith('/')): for ext in _WEB_EXT: if url_fname.endswith(ext): add_subdirs = False # whether to add subdirs or not??? if add_subdirs: url = '/'.join(subdirs + [url_fname]) if not _has_webpage_extension(url): url += self.ext # Figure out if we need
  • or just plain
  • cs = url_fname == currentsection and 'class="CurrentSection"' or '' li = Html('li', attr = cs, indent=True, inline=True) # create hyperlink ahref = Html('a', nav_text, href = url) # add hyperlink to
  • cell li += ahref # add
  • to