# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2007 Johan Gonqvist # Copyright (C) 2007 Gary Burton # Copyright (C) 2007-2009 Stephane Charette # Copyright (C) 2008 Brian G. Matherly # Copyright (C) 2008 Jason M. Simanek # Copyright (C) 2008-2009 Rob G. Healey # # 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 # # $Id$ """ Narrative Web Page generator. """ #------------------------------------------------------------------------ # # Suggested pylint usage: # --max-line-length=100 Yes, I know PEP8 suggest 80, but this has longer lines # --argument-rgx='[a-z_][a-z0-9_]{1,30}$' Several identifiers are two characters # --variable-rgx='[a-z_][a-z0-9_]{1,30}$' Several identifiers are two characters # #------------------------------------------------------------------------ #------------------------------------------------------------------------ # # python modules # #------------------------------------------------------------------------ import os import re try: from hashlib import md5 except ImportError: from md5 import md5 import time import locale import shutil import codecs import tarfile import operator from TransUtils import sgettext as _ from cStringIO import StringIO from textwrap import TextWrapper from unicodedata import normalize #------------------------------------------------------------------------ # # Set up logging # #------------------------------------------------------------------------ import logging log = logging.getLogger(".WebPage") #------------------------------------------------------------------------ # # GRAMPS module # #------------------------------------------------------------------------ import gen.lib import const from GrampsCfg import get_researcher import Sort import GrampsLocale from gen.plug import PluginManager from gen.plug.menu import PersonOption, NumberOption, StringOption, \ BooleanOption, EnumeratedListOption, FilterOption, \ NoteOption, MediaOption, DestinationOption from ReportBase import (Report, ReportUtils, MenuReportOptions, CATEGORY_WEB, Bibliography) import Utils import ThumbNails import ImgManip import Mime from Utils import probably_alive from QuestionDialog import ErrorDialog, WarningDialog from BasicUtils import name_displayer as _nd from DateHandler import displayer as _dd from DateHandler import parser as _dp from gen.proxy import PrivateProxyDb, LivingProxyDb from gen.lib.eventroletype import EventRoleType #------------------------------------------------------------------------ # # constants # #------------------------------------------------------------------------ _PERSON = 0 _PLACE = 1 _INCLUDE_LIVING_VALUE = 99 # Arbitrary number _NAME_COL = 3 _MAX_IMG_WIDTH = 800 # resize images that are wider than this _MAX_IMG_HEIGHT = 600 # resize images that are taller than this _WIDTH = 160 _HEIGHT = 50 _VGAP = 10 _HGAP = 30 _SHADOW = 5 _XOFFSET = 5 # This information defines the list of styles in the Narrative Web # options dialog as well as the location of the corresponding SCREEN # 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. [_('Unicode (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'), _('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'), ] wrapper = TextWrapper() wrapper.break_log_words = True wrapper.width = 20 _html_dbl_quotes = re.compile(r'([^"]*) " ([^"]*) " (.*)', re.VERBOSE) _html_sng_quotes = re.compile(r"([^']*) ' ([^']*) ' (.*)", re.VERBOSE) _html_replacement = { "&" : "&", ">" : ">", "<" : "<", } # This command then defines the 'html_escape' option for escaping # special characters for presentation in HTML based on the above list. def html_escape(text): """Convert the text and replace some characters with a &# variant.""" # First single characters, no quotes text = ''.join([_html_replacement.get(c, c) for c in text]) # Deal with double quotes. while 1: m = _html_dbl_quotes.match(text) if not m: break text = m.group(1) + '“' + m.group(2) + '”' + m.group(3) # Replace remaining double quotes. text = text.replace('"', '"') # Deal with single quotes. text = text.replace("'s ", '’s ') while 1: m = _html_sng_quotes.match(text) if not m: break text = m.group(1) + '‘' + m.group(2) + '’' + m.group(3) # Replace remaining single quotes. text = text.replace("'", ''') return text def name_to_md5(text): """This creates an MD5 hex string to be used as filename.""" return md5(text).hexdigest() class BasePage: """ This is the base class to write certain HTML pages. """ def __init__(self, report, title, gid=None): """ report - instance of NavWebReport title - text for the tag gid - Gramps ID """ self.report = report self.title_str = title self.gid = gid self.src_list = {} self.page_title = "" self.author = get_researcher().get_name() if self.author: self.author = self.author.replace(',,,', '') self.up = False # TODO. All of these attributes are not necessary, because we have # also the options in self.options. Besides, we need to check which # are still required. options = report.options self.html_dir = options['target'] self.ext = options['ext'] self.noid = options['nogid'] self.linkhome = options['linkhome'] self.use_gallery = options['gallery'] def alphabet_navigation(self, of, db, handle_list, key): """ Will create the alphabetical navigation bar... """ first_letter_dict = get_first_letter_dict(db, handle_list, key) sorted_set = {} for ltr in first_letter_dict: try: sorted_set[ltr] += 1 except KeyError: sorted_set[ltr] = 1 sorted_first_letter = sorted_set.keys() sorted_first_letter.sort(locale.strcoll) num_ltrs = len(sorted_first_letter) if num_ltrs <= 25: of.write('\t<div id="navigation">\n') of.write('\t\t<ul>\n') for ltr in sorted_first_letter: of.write('\t\t\t<li><a href="#%s">%s</a> </li>' % (ltr, ltr)) of.write('\t\t</ul>\n') of.write('\t</div>\n') else: nrows = (num_ltrs / 25) index = 0 for rows in range(0, nrows): of.write('\t<div id="navigation">\n') of.write('\t\t<ul>\n') cols = 0 while (cols <= 24 and index <= num_ltrs): of.write('\t\t\t<li><a href="#%s">%s</a></li>\n' % (sorted_first_letter[index], sorted_first_letter[index])) cols += 1 index += 1 of.write('\t\t<ul>\n') of.write('\t</div>\n') return of def write_footer(self, of): of.write('</div>\n') # Terminate div_content of.write('<div id="footer">\n') footer = self.report.options['footernote'] if footer: note = self.report.database.get_note_from_gramps_id(footer) of.write('\t<div id="user_footer">\n') of.write('\t\t<p>') of.write(note.get()) of.write('</p>\n') of.write('\t</div>\n') value = _dp.parse(time.strftime('%b %d %Y')) value = _dd.display(value) msg = _('Generated by <a href="http://gramps-project.org">' 'GRAMPS</a> on %(date)s') % {'date' : value} if self.report.options['linkhome']: home_person = self.report.database.get_default_person() if home_person: home_person_url = self.report.build_url_fname_html(home_person.handle, 'ppl', self.up) home_person_name = home_person.get_primary_name().get_regular_name() msg += _('<br />for <a href="%s">%s</a>') % (home_person_url, home_person_name) of.write('\t<p id="createdate">%s</p>\n' % msg) copy_nr = self.report.copyright text = '' if copy_nr == 0: if self.author: year = time.localtime()[0] text = '© %(year)d %(person)s' % { 'person' : self.author, 'year' : year} elif 0 < copy_nr < len(_CC): fname = os.path.join("images", "somerights20.gif") fname = self.report.build_url_fname(fname, None, self.up) text = _CC[copy_nr] % {'gif_fname' : fname} of.write('\t<p id="copyright">%s</p>\n' % text) of.write('\t\t<div class="fullclear"></div>\n') of.write('</div>\n') of.write('</body>\n') of.write('</html>') def write_header(self, of, title): """ Note. 'title' is used as currentsection in the navigation links. """ of.write('<!DOCTYPE html PUBLIC \n') of.write('\t"-//W3C//DTD XHTML 1.0 Strict//EN" \n') of.write('\t\t"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n') xmllang = Utils.xml_lang() of.write('<html xmlns="http://www.w3.org/1999/xhtml" ' 'xml:lang="%s" lang="%s">\n' % (xmllang, xmllang)) of.write('<head>\n') of.write('\t<title>%s - %s\n' % (html_escape(self.title_str), html_escape(title))) of.write('\t\n' % self.report.encoding) of.write('\t\n' % (const.PROGRAM_NAME, const.VERSION, const.URL_HOMEPAGE)) of.write('\t\n' % self.author) # Link to css behaviours fname = os.path.join("styles", "behaviour.css") url = self.report.build_url_fname(fname, None, self.up) of.write('\t\n' % url) # Link to screen stylesheet fname = os.path.join("styles", self.report.css) url = self.report.build_url_fname(fname, None, self.up) of.write('\t\n' % url) # Link to printer stylesheet fname = os.path.join("styles", "Web_Print-Default.css") url = self.report.build_url_fname(fname, None, self.up) of.write('\t\n' % url) # Link to GRAMPS favicon url = self.report.build_url_image('favicon.ico', 'images', self.up) of.write('\t\n' % url) of.write('\n\n') of.write('\n') # Terminated in write_footer() of.write('\n') # Begin Navigation Menu self.display_nav_links(of, title) def display_nav_links(self, of, currentsection): """ Creates the navigation menu """ # Determine if there will be a link to WebCal or not??? use_webcal = False webcal_link = self.report.webcal_link if webcal_link.strip() != '': if not webcal_link.endswith(self.ext): webcal_link += self.ext if os.path.isfile(webcal_link): use_webcal = True navs = [ (self.report.index_fname, _('Home'), self.report.use_home), (self.report.intro_fname, _('Introduction'), self.report.use_intro), (self.report.surname_fname, _('Surnames'), True), ('individuals', _('Individuals'), True), ('places', _('Places'), True), ('gallery', _('Gallery'), self.use_gallery), ('download', _('Download'), self.report.inc_download), ('contact', _('Contact'), self.report.use_contact), ('sources', _('Sources'), True), (webcal_link, _('Web Calendar'), use_webcal), ] of.write('\t\n') # End Navigation Menu def display_first_image_as_thumbnail( self, of, photolist=None): if not photolist or not self.use_gallery: return photo_handle = photolist[0].get_reference_handle() photo = self.report.database.get_object_from_handle(photo_handle) mime_type = photo.get_mime_type() if mime_type: try: lnkref = (self.report.cur_fname, self.page_title, self.gid) self.report.add_lnkref_to_photo(photo, lnkref) real_path, newpath = self.report.prepare_copy_media(photo) of.write('\t
\n') # TODO. Check if build_url_fname can be used. newpath = '/'.join(['..']*3 + [newpath]) self.media_link(of, photo_handle, newpath, '', up=True) of.write('\t
\n\n') except (IOError, OSError), msg: WarningDialog(_("Could not add photo to page"), str(msg)) else: of.write('\t
\n') descr = " ".join(wrapper.wrap(photo.get_description())) self.doc_link(of, photo_handle, descr, up=True) of.write('\t
\n\n') lnk = (self.report.cur_fname, self.page_title, self.gid) # FIXME. Is it OK to add to the photo_list of report? photo_list = self.report.photo_list if photo_handle in photo_list: if lnk not in photo_list[photo_handle]: photo_list[photo_handle].append(lnk) else: photo_list[photo_handle] = [lnk] def display_additional_images_as_gallery( self, of, photolist=None): if not photolist or not self.use_gallery: return db = self.report.database of.write('\t
\n') of.write('\t\t

%s

\n' % _('Gallery')) displayed = [] for mediaref in photolist: photo_handle = mediaref.get_reference_handle() photo = db.get_object_from_handle(photo_handle) if photo_handle in displayed: continue mime_type = photo.get_mime_type() title = photo.get_description() if title == "": title = "(untitled)" if mime_type: try: lnkref = (self.report.cur_fname, self.page_title, self.gid) self.report.add_lnkref_to_photo(photo, lnkref) real_path, newpath = self.report.prepare_copy_media(photo) descr = " ".join(wrapper.wrap(title)) # TODO. Check if build_url_fname can be used. newpath = '/'.join(['..']*3 + [newpath]) self.media_link(of, photo_handle, newpath, descr, up=True) except (IOError, OSError), msg: WarningDialog(_("Could not add photo to page"), str(msg)) else: try: descr = " ".join(wrapper.wrap(title)) self.doc_link(of, photo_handle, descr, up=True) lnk = (self.report.cur_fname, self.page_title, self.gid) # FIXME. Is it OK to add to the photo_list of report? photo_list = self.report.photo_list if photo_handle in photo_list: if lnk not in photo_list[photo_handle]: photo_list[photo_handle].append(lnk) else: photo_list[photo_handle] = [lnk] except (IOError, OSError), msg: WarningDialog(_("Could not add photo to page"), str(msg)) displayed.append(photo_handle) of.write('\t\t
\n') of.write('\t
\n\n') def display_note_list(self, of, notelist=None): if not notelist: return db = self.report.database for notehandle in notelist: note = db.get_note_from_handle(notehandle) format = note.get_format() text = note.get() try: text = unicode(text) except UnicodeDecodeError: text = unicode(str(text), errors='replace') if text: of.write('\t
\n') of.write('\t\t

%s

\n' % _('Narrative')) if format: text = u"
%s
" % text else: text = u"
".join(text.split("\n")) of.write('\t\t

%s

\n' % text) of.write('\t
\n\n') def display_url_list(self, of, urllist=None): if not urllist: return of.write('\t\n\n') # Only used in IndividualPage.display_ind_sources # and MediaPage.display_media_sources def display_source_refs(self, of, bibli): if bibli.get_citation_count() == 0: return db = self.report.database of.write('\t
\n') of.write('\t\t

%s

\n' % _('Source References')) of.write('\t\t
    \n') cindex = 0 for citation in bibli.get_citation_list(): cindex += 1 # Add this source to the global list of sources to be displayed # on each source page. lnk = (self.report.cur_fname, self.page_title, self.gid) shandle = citation.get_source_handle() if shandle in self.src_list: if lnk not in self.src_list[shandle]: self.src_list[shandle].append(lnk) else: self.src_list[shandle] = [lnk] # Add this source and its references to the page source = db.get_source_from_handle(shandle) title = source.get_title() of.write('\t\t\t
  1. is done in source_link() self.source_link(of, source.handle, title, source.gramps_id, True) of.write('\n') of.write('\t\t\t\t
      \n') for key, sref in citation.get_ref_list(): tmp = [] confidence = Utils.confidence.get(sref.confidence, _('Unknown')) if confidence == _('Normal'): confidence = None for (label, data) in [(_('Date'), _dd.display(sref.date)), (_('Page'), sref.page), (_('Confidence'), confidence)]: if data: tmp.append("%s: %s" % (label, data)) notelist = sref.get_note_list() for notehandle in notelist: note = db.get_note_from_handle(notehandle) tmp.append("%s: %s" % (_('Text'), note.get())) if len(tmp) > 0: of.write('\t\t\t\t\t
    1. ' % (cindex, key)) of.write(';   '.join(tmp)) of.write('
    2. \n') of.write('\t\t\t\t
    \n') of.write('\t\t\t
  2. \n') of.write('\t\t
\n') of.write('\t
\n\n') def display_references(self, of, handlelist, up=False): if not handlelist: return of.write('\t
\n') of.write('\t\t

%s

\n' % _('References')) of.write('\t\t
    \n') sortlist = sorted(handlelist, key = operator.itemgetter(1), cmp = locale.strcoll) for (path, name, gid) in sortlist: of.write('\t\t\t
  1. ') # Note. 'path' already has a filename extension url = self.report.build_url_fname(path, None, self.up) self.person_link(of, url, name, gid) of.write('
  2. \n') of.write('\t\t
\n') of.write('\t
\n') def person_link(self, of, url, name, gid=None, thumbnailUrl=None): of.write('') if thumbnailUrl: of.write('Image of %s' % (thumbnailUrl, name)) of.write('%s' % name) if not self.noid and gid: of.write(' [%s]' % gid) of.write('') # TODO. Check img_url of callers def media_link(self, of, handle, img_url, name, up, usedescr=True): url = self.report.build_url_fname_html(handle, 'img', up) of.write('\t\t
\n') of.write('\t\t\t' % url) of.write('\n' % name) if usedescr: of.write('\t\t\t

%s

\n' % html_escape(name)) of.write('\t\t
\n') def doc_link(self, of, handle, name, up, usedescr=True): # TODO. Check extension of handle url = self.report.build_url_fname(handle, 'img', up) of.write('\t\t
\n') of.write('\t\t\t' % url) url = self.report.build_url_image('document.png', 'images', up) of.write('\n' % html_escape(name)) if usedescr: of.write('\t\t\t

%s

\n' % html_escape(name)) of.write('\t\t
\n') def source_link(self, of, handle, name, gid=None, up=False): url = self.report.build_url_fname_html(handle, 'src', up) of.write(' href="%s">%s' % (url, html_escape(name))) if not self.noid and gid: of.write(' [%s]' % gid) of.write('') def place_link(self, of, handle, name, gid=None, up=False): url = self.report.build_url_fname_html(handle, 'plc', up) of.write('%s' % (url, html_escape(name))) if not self.noid and gid: of.write(' [%s]' % gid) of.write('') def place_link_str(self, handle, name, gid=None, up=False): url = self.report.build_url_fname_html(handle, 'plc', up) retval = '%s' % (url, html_escape(name)) if not self.noid and gid: retval = retval + ' [%s]' % gid return retval + '' class IndividualListPage(BasePage): def __init__(self, report, title, person_handle_list): BasePage.__init__(self, report, title) db = report.database of = self.report.create_file("individuals") self.write_header(of, _('Individuals')) # begin alphabetic navigation self.alphabet_navigation(of, db, person_handle_list, _PERSON) of.write('
\n') msg = _("This page contains an index of all the individuals in the " "database, sorted by their last names. Selecting the person’s " "name will take you to that person’s individual page.") showbirth = report.options['showbirth'] showdeath = report.options['showdeath'] showspouse = report.options['showspouse'] showparents = report.options['showparents'] of.write('\t

%s

\n' % msg) of.write('\t\n') of.write('\t\n') of.write('\t\t\n') of.write('\t\t\t\n' % _('Surname')) of.write('\t\t\t\n' % _('Name')) column_count = 2 if showbirth: of.write('\t\t\t\n' % _('Birth')) column_count += 1 if showdeath: of.write('\t\t\t\n' % _('Death')) column_count += 1 if showspouse: of.write('\t\t\t\n' % _('Partner')) column_count += 1 if showparents: of.write('\t\t\t\n' % _('Parents')) column_count += 1 of.write('\t\t\n') of.write('\t\n') of.write('\t\n') person_handle_list = sort_people(db, person_handle_list) for (surname, handle_list) in person_handle_list: first = True for person_handle in handle_list: person = db.get_person_from_handle(person_handle) # surname column if first: of.write('\t\t\n') if surname: of.write('\t\t\t\n' % (surname[0], surname)) else: of.write('\t\t\t\n') of.write('\t\t\t\n') # firstname column of.write('\t\t\t\n') # birth column if showbirth: of.write('\t\t\t\n') # death column if showdeath: of.write('\t\t\t\n') # spouse (partner) column if showspouse: of.write('\t\t\t\n') # parents column if showparents: of.write('\t\t\t\n') # finished writing all columns of.write('\t\t\n') first = False of.write('\t\n') of.write('\t
%s%s%s%s%s%s
%s \n') else: of.write('\t\t
 ') of.write('') url = self.report.build_url_fname_html(person.handle, 'ppl') first_suffix = _get_prefix_suffix_name(person.gender, person.primary_name) self.person_link(of, url, first_suffix, person.gramps_id) of.write('') birth = ReportUtils.get_birth_or_fallback(db, person) if birth: if birth.get_type() == gen.lib.EventType.BIRTH: of.write(_dd.display(birth.get_date_object())) else: of.write('') of.write(_dd.display(birth.get_date_object())) of.write('') of.write('') death = ReportUtils.get_death_or_fallback(db, person) if death: if death.get_type() == gen.lib.EventType.DEATH: of.write(_dd.display(death.get_date_object())) else: of.write('') of.write(_dd.display(death.get_date_object())) of.write('') of.write('') family_list = person.get_family_handle_list() first_family = True spouse_name = None if family_list: for family_handle in family_list: family = db.get_family_from_handle(family_handle) spouse_id = ReportUtils.find_spouse(person, family) if spouse_id: spouse = db.get_person_from_handle(spouse_id) spouse_name = spouse.get_primary_name().get_regular_name() if not first_family: of.write(', ') of.write('%s' % spouse_name) first_family = False of.write('') parent_handle_list = person.get_parent_family_handle_list() if parent_handle_list: parent_handle = parent_handle_list[0] family = db.get_family_from_handle(parent_handle) father_name = '' mother_name = '' father_id = family.get_father_handle() mother_id = family.get_mother_handle() father = db.get_person_from_handle(father_id) mother = db.get_person_from_handle(mother_id) if father: father_name = father.get_primary_name().get_regular_name() if mother: mother_name = mother.get_primary_name().get_regular_name() if mother and father: of.write('%s %s' % (father_name, mother_name)) elif mother: of.write('%s' % mother_name) elif father: of.write('%s' % father_name) of.write('
\n') self.write_footer(of) self.report.close_file(of) class SurnamePage(BasePage): def __init__(self, report, title, surname, person_handle_list): BasePage.__init__(self, report, title) db = report.database of = self.report.create_file(name_to_md5(surname), 'srn') self.up = True self.write_header(of, "%s - %s" % (_('Surname'), surname)) of.write('
\n') msg = _("This page contains an index of all the individuals in the " "database with the surname of %s. Selecting the person’s name " "will take you to that person’s individual page.") % surname showbirth = report.options['showbirth'] showdeath = report.options['showdeath'] showspouse = report.options['showspouse'] showparents = report.options['showparents'] of.write('\t

%s

\n' % html_escape(surname)) of.write('\t

%s

\n' % msg) of.write('\t\n') of.write('\t\n') of.write('\t\t\n') of.write('\t\t\t\n' % _('Name')) if showbirth: of.write('\t\t\t\n' % _('Birth')) if showdeath: of.write('\t\t\t\n' % _('Death')) if showspouse: of.write('\t\t\t\n' % _('Partner')) if showparents: of.write('\t\t\t\n' % _('Parents')) of.write('\t\t\n') of.write('\t\n') of.write('\t\n') for person_handle in person_handle_list: # firstname column person = db.get_person_from_handle(person_handle) of.write('\t\t\n') of.write('\t\t\t\n') # birth column if showbirth: of.write('\t\t\t\n') # death column if showdeath: of.write('\t\t\t\n') # spouse (partner) column if showspouse: of.write('\t\t\t\n') # parents column if showparents: of.write('\t\t\t\n') # finished writing all columns of.write('\t\t\n') of.write('\t\n') of.write('\t
%s%s%s%s%s
') url = self.report.build_url_fname_html(person.handle, 'ppl', True) first_suffix = _get_prefix_suffix_name(person.gender, person.primary_name) self.person_link(of, url, first_suffix, person.gramps_id) of.write('') birth = ReportUtils.get_birth_or_fallback(db, person) if birth: if birth.get_type() == gen.lib.EventType.BIRTH: of.write(_dd.display(birth.get_date_object())) else: of.write('') of.write(_dd.display(birth.get_date_object())) of.write('') of.write('') death = ReportUtils.get_death_or_fallback(db, person) if death: if death.get_type() == gen.lib.EventType.DEATH: of.write(_dd.display(death.get_date_object())) else: of.write('') of.write(_dd.display(death.get_date_object())) of.write('') of.write('') family_list = person.get_family_handle_list() first_family = True spouse_name = None if family_list: for family_handle in family_list: family = db.get_family_from_handle(family_handle) spouse_id = ReportUtils.find_spouse(person, family) if spouse_id: spouse = db.get_person_from_handle(spouse_id) spouse_name = spouse.get_primary_name().get_regular_name() if not first_family: of.write(', ') of.write('%s' % spouse_name) first_family = False of.write('') parent_handle_list = person.get_parent_family_handle_list() if parent_handle_list: parent_handle = parent_handle_list[0] family = db.get_family_from_handle(parent_handle) father_name = '' mother_name = '' father_id = family.get_father_handle() mother_id = family.get_mother_handle() father = db.get_person_from_handle(father_id) mother = db.get_person_from_handle(mother_id) if father: father_name = father.get_primary_name().get_regular_name() if mother: mother_name = mother.get_primary_name().get_regular_name() if mother and father: of.write('%s %s' % (father_name, mother_name)) elif mother: of.write('%s' % mother_name) elif father: of.write('%s' % father_name) of.write('
\n') self.write_footer(of) self.report.close_file(of) class PlaceListPage(BasePage): def __init__(self, report, title, place_handles, src_list): BasePage.__init__(self, report, title) self.src_list = src_list # TODO verify that this is correct db = report.database of = self.report.create_file("places") self.write_header(of, _('Places')) # begin alphabetic navigation self.alphabet_navigation(of, db, place_handles, _PLACE) of.write('
\n') msg = _("This page contains an index of all the places in the " "database, sorted by their title. Clicking on a place’s " "title will take you to that place’s page.") of.write('\t

%s

\n' % msg ) of.write('\t\n') of.write('\t\n') of.write('\t\t\n') of.write('\t\t\t\n' % _('Letter')) of.write('\t\t\t\n' % _('Name')) of.write('\t\t\n') of.write('\t\n') of.write('\t\n\n') sort = Sort.Sort(db) handle_list = place_handles.keys() handle_list.sort(sort.by_place_title) last_letter = '' for handle in handle_list: place = db.get_place_from_handle(handle) place_title = ReportUtils.place_name(db, handle) if not place_title: continue letter = normalize('NFKC', place_title)[0].upper() if letter != last_letter: last_letter = letter of.write('\t\t\n') of.write('\t\t\t\n' % (last_letter, last_letter)) else: of.write('\t\t\n') of.write('\t\t\t\n') of.write('\t\t\t\n') of.write('\t\t\n') of.write('\t\n') of.write('\t
%s%s
%s
 ') self.place_link(of, place.handle, place_title, place.gramps_id) of.write('
\n') self.write_footer(of) self.report.close_file(of) class PlacePage(BasePage): def __init__(self, report, title, place_handle, src_list, place_list): db = report.database place = db.get_place_from_handle(place_handle) BasePage.__init__(self, report, title, place.gramps_id) self.src_list = src_list # TODO verify that this is correct of = self.report.create_file(place.get_handle(), 'plc') self.up = True self.page_title = ReportUtils.place_name(db, place_handle) self.write_header(of, "%s - %s" % (_('Places'), self.page_title)) of.write('
\n') media_list = place.get_media_list() self.display_first_image_as_thumbnail(of, media_list) of.write('\t

%s

\n\n' % html_escape(self.page_title.strip())) of.write('\t
\n') of.write('\t\t\n') if not self.noid: of.write('\t\t\t\n') of.write('\t\t\t\t\n' % _('GRAMPS ID')) of.write('\t\t\t\t\n' % place.gramps_id) of.write('\t\t\t\n') if place.main_loc: ml = place.main_loc for val in [(_('Street'), ml.street), (_('City'), ml.city), (_('Church Parish'), ml.parish), (_('County'), ml.county), (_('State/Province'), ml.state), (_('ZIP/Postal Code'), ml.postal), (_('Country'), ml.country)]: if val[1]: of.write('\t\t\t\n') of.write('\t\t\t\t\n' % val[0]) of.write('\t\t\t\t\n' % val[1]) of.write('\t\t\t\n') if place.lat: of.write('\t\t\t\n') of.write('\t\t\t\t\n' % _('Latitude')) of.write('\t\t\t\t\n' % place.lat) of.write('\t\t\t\n') if place.long: of.write('\t\t\t\n') of.write('\t\t\t\t\n' % _('Longitude')) of.write('\t\t\t\t\n' % place.long) of.write('\t\t\t\n') of.write('\t\t
%s%s
%s%s
%s%s
%s%s
\n') of.write('\t
\n') if self.use_gallery: self.display_additional_images_as_gallery(of, media_list) self.display_note_list(of, place.get_note_list()) self.display_url_list(of, place.get_url_list()) self.display_references(of, place_list[place.handle]) self.write_footer(of) self.report.close_file(of) class MediaPage(BasePage): def __init__(self, report, title, handle, src_list, my_media_list, info): (prev, next, page_number, total_pages) = info db = report.database photo = db.get_object_from_handle(handle) # TODO. How do we pass my_media_list down for use in BasePage? BasePage.__init__(self, report, title, photo.gramps_id) """ ************************************* GRAMPS feature #2634 -- attempt to highlight subregions in media objects and link back to the relevant web page. This next section of code builds up the "records" we'll need to generate the html/css code to support the subregions ************************************* """ # get all of the backlinks to this media object; meaning all of # the people, events, places, etc..., that use this image _region_items = set() for (classname, newhandle) in db.find_backlink_handles(handle): # for each of the backlinks, get the relevant object from the db # and determine a few important things, such as a text name we # can use, and the URL to a relevant web page _obj = None _name = "" _linkurl = "#" if classname == "Person": _obj = db.get_person_from_handle( newhandle ) # what is the shortest possible name we could use for this person? _name = _obj.get_primary_name().get_call_name() if not _name or _name == "": _name = _obj.get_primary_name().get_first_name() _linkurl = report.build_url_fname_html(_obj.handle, 'ppl', True) if classname == "Event": _obj = db.get_event_from_handle( newhandle ) _name = _obj.get_description() # keep looking if we don't have an object if _obj is None: continue # get a list of all media refs for this object medialist = _obj.get_media_list() # go media refs looking for one that points to this image for mediaref in medialist: # is this mediaref for this image? do we have a rect? if mediaref.ref == handle and mediaref.rect is not None: (x1, y1, x2, y2) = mediaref.rect # GRAMPS gives us absolute coordinates, # but we need relative width + height w = x2 - x1 h = y2 - y1 # remember all this information, cause we'll need # need it later when we output the
  • ...
  • tags item = (_name, x1, y1, w, h, _linkurl) _region_items.add(item) """ ************************************* end of code that looks for and prepares the media object regions ************************************* """ of = self.report.create_file(handle, 'img') self.up = True self.src_list = src_list self.bibli = Bibliography() mime_type = photo.get_mime_type() if mime_type: note_only = False newpath = self.copy_source_file(handle, photo) target_exists = newpath is not None else: note_only = True target_exists = False self.copy_thumbnail(handle, photo) self.page_title = photo.get_description() self.write_header(of, "%s - %s" % (_('Gallery'), self.page_title)) of.write('
    \n') # gallery navigation of.write('\t
    \n') of.write('\t\t') if prev: self.gallery_nav_link(of, prev, _('Previous'), True) data = _('%(page_number)d of %(total_pages)d' ) % { 'page_number' : page_number, 'total_pages' : total_pages } of.write(' %s ' % data) if next: self.gallery_nav_link(of, next, _('Next'), True) of.write('\n') of.write('\t
    \n\n') of.write('\t
    \n') if mime_type: if mime_type.startswith("image/"): if not target_exists: of.write('\t\t
    \n') of.write('\t\t\t(%s)' % _("The file has been moved or deleted")) else: # if the image is spectacularly large, then force the client # to resize it, and include a "\n' % (width, height)) # Feature #2634; display the mouse-selectable regions. # See the large block at the top of this function where # the various regions are stored in _region_items if len(_region_items) > 0: of.write('\t\t\t
      \n') while len(_region_items) > 0: (name, x, y, w, h, linkurl) = _region_items.pop() of.write('\t\t\t\t
    1. ' '%s
    2. \n' % (x, y, w, h, linkurl, name)) of.write('\t\t\t
    \n') # display the image of.write('\t\t\t') if scale != 1.0: of.write('' % url) of.write('%s' % (width, height, url, html_escape(self.page_title))) if scale != 1.0: of.write('') of.write('\n') of.write('\t\t
    \n\n') else: import tempfile dirname = tempfile.mkdtemp() thmb_path = os.path.join(dirname, "temp.png") if ThumbNails.run_thumbnailer(mime_type, Utils.media_path_full(db, photo.get_path()), thmb_path, 320): try: path = self.report.build_path('preview', photo.handle) self.report.copy_file(thmb_path, os.path.join(path, photo.handle) + '.png') os.unlink(thmb_path) except IOError: path = os.path.join('images', 'document.png') else: path = os.path.join('images', 'document.png') os.rmdir(dirname) of.write('\t\t
    \n') if target_exists: # TODO. Convert disk path to URL url = self.report.build_url_fname(newpath, None, self.up) of.write('\t\t\t\n' % (url, html_escape(self.page_title))) # TODO. Mixup url and path # path = convert_disk_path_to_url(path) url = self.report.build_url_fname(path, None, self.up) of.write('\t\t\t\t%s\n' % (url, html_escape(self.page_title))) if target_exists: of.write('\t\t\t\n') else: of.write('\t\t\t(%s)' % _("The file has been moved or deleted")) of.write('\t\t
    \n\n') else: of.write('\t\t
    \n') url = self.report.build_url_image('document.png', 'images', self.up) of.write('\t\t\t%s\n' % (url, html_escape(self.page_title))) of.write('\t\t
    \n\n') of.write('\t\t

    %s

    \n' % html_escape(self.page_title.strip())) of.write('\t\t\n') if not self.noid: of.write('\t\t\t\n') of.write('\t\t\t\t\n' % _('GRAMPS ID')) of.write('\t\t\t\t\n' % photo.gramps_id) of.write('\t\t\t\n') if not note_only and not mime_type.startswith("image/"): of.write('\t\t\t\n') of.write('\t\t\t\t\n' % _('File type')) of.write('\t\t\t\t\n' % Mime.get_description(mime_type)) of.write('\t\t\t\n') date = _dd.display(photo.get_date_object()) if date: of.write('\t\t\t\n') of.write('\t\t\t\t\n' % _('Date')) of.write('\t\t\t\t\n' % date) of.write('\t\t\t\n') of.write('\t\t\n') of.write('\t
    \n\n') self.display_note_list(of, photo.get_note_list()) self.display_attr_list(of, photo.get_attribute_list()) self.display_media_sources(of, photo) self.display_references(of, my_media_list) self.write_footer(of) self.report.close_file(of) def gallery_nav_link(self, of, handle, name, up=False): url = self.report.build_url_fname_html(handle, 'img', up) of.write('%s' % (html_escape(name), url, html_escape(name))) def display_media_sources(self, of, photo): for sref in photo.get_source_references(): self.bibli.add_reference(sref) self.display_source_refs(of, self.bibli) def display_attr_list(self, of, attrlist=None): if not attrlist: return of.write('\t
    \n') of.write('\t\t

    %s

    \n' % _('Attributes')) of.write('\t\t\n') for attr in attrlist: atType = str( attr.get_type() ) of.write('\t\t\t\n') of.write('\t\t\t\t\n' % atType) of.write('\t\t\t\t\n' % attr.get_value()) of.write('\t\t\t\n') of.write('\t\t
    %s%s
    \n') of.write('\t
    \n\n') def copy_source_file(self, handle, photo): ext = os.path.splitext(photo.get_path())[1] to_dir = self.report.build_path('images', handle) newpath = os.path.join(to_dir, handle) + ext db = self.report.database fullpath = Utils.media_path_full(db, photo.get_path()) try: if self.report.archive: self.report.archive.add(fullpath, str(newpath)) else: to_dir = os.path.join(self.html_dir, to_dir) if not os.path.isdir(to_dir): os.makedirs(to_dir) shutil.copyfile(fullpath, os.path.join(self.html_dir, newpath)) return newpath except (IOError, OSError), msg: error = _("Missing media object:") + \ "%s (%s)" % (photo.get_description(), photo.get_gramps_id()) WarningDialog(error, str(msg)) return None def copy_thumbnail(self, handle, photo): to_dir = self.report.build_path('thumb', handle) to_path = os.path.join(to_dir, handle) + '.png' if photo.get_mime_type(): db = self.report.database from_path = ThumbNails.get_thumbnail_path(Utils.media_path_full( db, photo.get_path()), photo.get_mime_type()) if not os.path.isfile(from_path): from_path = os.path.join(const.IMAGE_DIR, "document.png") else: from_path = os.path.join(const.IMAGE_DIR, "document.png") self.report.copy_file(from_path, to_path) class SurnameListPage(BasePage): ORDER_BY_NAME = 0 ORDER_BY_COUNT = 1 def __init__(self, report, title, person_handle_list, order_by=ORDER_BY_NAME, filename="surnames"): BasePage.__init__(self, report, title) db = report.database if order_by == self.ORDER_BY_NAME: of = self.report.create_file(filename) self.write_header(of, _('Surnames')) else: of = self.report.create_file("surnames_count") self.write_header(of, _('Surnames by person count')) # begin alphabetic navigation self.alphabet_navigation(of, db, person_handle_list, _PERSON) of.write('
    \n') of.write('\t

    %s

    \n' % _( 'This page contains an index of all the ' 'surnames in the database. Selecting a link ' 'will lead to a list of individuals in the ' 'database with this same surname.')) if order_by == self.ORDER_BY_COUNT: of.write('\t\n') of.write('\t\n') of.write('\t\t\n') else: of.write('\t
    \n') of.write('\t\n') of.write('\t\t\n') of.write('\t\t\t\n' % _('Letter')) fname = self.report.surname_fname + self.ext of.write('\t\t\t\n' % (fname, _('Surname'))) fname = "surnames_count" + self.ext of.write('\t\t\t\n' % (fname, _('Number of people'))) of.write('\t\t\n') of.write('\t\n') of.write('\t\n') person_handle_list = sort_people(db, person_handle_list) if order_by == self.ORDER_BY_COUNT: temp_list = {} for (surname, data_list) in person_handle_list: index_val = "%90d_%s" % (999999999-len(data_list), surname) temp_list[index_val] = (surname, data_list) temp_keys = temp_list.keys() temp_keys.sort() person_handle_list = [] for key in temp_keys: person_handle_list.append(temp_list[key]) last_letter = '' last_surname = '' for (surname, data_list) in person_handle_list: if len(surname) == 0: continue # Get a capital normalized version of the first letter of # the surname letter = normalize('NFKC', surname)[0].upper() if letter is not last_letter: last_letter = letter of.write('\t\t\n') of.write('\t\t\t\n' % (last_letter, last_letter)) of.write('\t\t\t\n') elif surname != last_surname: of.write('\t\t\n') of.write('\t\t\t\n') of.write('\t\t\t\n') last_surname = surname of.write('\t\t\t\n' % len(data_list)) of.write('\t\t\n') of.write('\t\n') of.write('\t
    %s%s%s
    %s') self.surname_link(of, name_to_md5(surname), surname) of.write('
     ') self.surname_link(of, name_to_md5(surname), surname) of.write('%d
    \n') self.write_footer(of) self.report.close_file(of) def surname_link(self, of, fname, name, opt_val=None, up=False): url = self.report.build_url_fname_html(fname, 'srn', up) of.write('%s' % (url, name)) if opt_val is not None: of.write(' (%d)' % opt_val) of.write('') class IntroductionPage(BasePage): def __init__(self, report, title): BasePage.__init__(self, report, title) db = report.database of = self.report.create_file(report.intro_fname) # Note. In old NarrativeWeb.py the content_divid depended on filename. self.write_header(of, _('Introduction')) of.write('
    \n') report.add_image(of, 'introimg') note_id = report.options['intronote'] if note_id: note_obj = db.get_note_from_gramps_id(note_id) text = note_obj.get() if note_obj.get_format(): of.write(u'\t
    %s
    \n' % text) else: of.write(u'
    '.join(text.split("\n"))) self.write_footer(of) self.report.close_file(of) class HomePage(BasePage): def __init__(self, report, title): BasePage.__init__(self, report, title) db = report.database of = self.report.create_file("index") self.write_header(of, _('Home')) of.write('
    \n') report.add_image(of, 'homeimg') note_id = report.options['homenote'] if note_id: note_obj = db.get_note_from_gramps_id(note_id) text = note_obj.get() if note_obj.get_format(): of.write(u'\t
    %s
    ' % text) else: of.write(u'
    '.join(text.split("\n"))) self.write_footer(of) self.report.close_file(of) class SourcesPage(BasePage): def __init__(self, report, title, handle_set): BasePage.__init__(self, report, title) db = report.database of = self.report.create_file("sources") self.write_header(of, _('Sources')) of.write('
    \n') handle_list = list(handle_set) source_dict = {} #Sort the sources for handle in handle_list: source = db.get_source_from_handle(handle) key = source.get_title() + str(source.get_gramps_id()) source_dict[key] = (source, handle) keys = source_dict.keys() keys.sort(locale.strcoll) msg = _("This page contains an index of all the sources in the " "database, sorted by their title. Clicking on a source’s " "title will take you to that source’s page.") of.write('\t

    ') of.write(msg) of.write('

    \n') of.write('\t\n') of.write('\t\n') of.write('\t\t\n') of.write('\t\t\t\n') of.write('\t\t\t\n' % _('Name')) of.write('\t\t\n') of.write('\t\n') of.write('\t\n') index = 1 for key in keys: (source, handle) = source_dict[key] of.write('\t\t\n') of.write('\t\t\t\n' % index) of.write('\t\t\t\n') index += 1 of.write('\t\n') of.write('\t
     %s
    %d.\n') of.write('\t\t
    \n') self.write_footer(of) self.report.close_file(of) class SourcePage(BasePage): def __init__(self, report, title, handle, src_list): db = report.database source = db.get_source_from_handle(handle) BasePage.__init__(self, report, title, source.gramps_id) of = self.report.create_file(source.get_handle(), 'src') self.up = True self.page_title = source.get_title() self.write_header(of, "%s - %s" % (_('Sources'), self.page_title)) of.write('
    \n') media_list = source.get_media_list() self.display_first_image_as_thumbnail(of, media_list) of.write('\t

    %s

    \n\n' % html_escape(self.page_title.strip())) of.write('\t
    \n') of.write('\t\t\n') grampsid = None if not self.noid: grampsid = source.gramps_id for (label, val) in [(_('GRAMPS ID'), grampsid), (_('Author'), source.author), (_('Publication information'), source.pubinfo), (_('Abbreviation'), source.abbrev)]: if val: of.write('\t\t\t\n') of.write('\t\t\t\t\n' % label) of.write('\t\t\t\t\n' % val) of.write('\t\t\t\n') of.write('\t\t
    %s%s
    \n') of.write('\t
    \n\n') self.display_additional_images_as_gallery(of, media_list) self.display_note_list(of, source.get_note_list()) self.display_references(of, src_list[source.handle]) self.write_footer(of) self.report.close_file(of) class GalleryPage(BasePage): def __init__(self, report, title): BasePage.__init__(self, report, title) db = report.database of = self.report.create_file("gallery") self.write_header(of, _('Gallery')) of.write('